From 633c92eae865e957121e08de634aeee11a8b3992 Mon Sep 17 00:00:00 2001 From: RaindropsSys Date: Mon, 24 Apr 2023 14:03:36 +0200 Subject: Updated 18 files, added 1692 files and deleted includes/system/compare.inc (automated) --- .../matrix-js-sdk/dist/browser-matrix.js | 101316 ++++++++++++++++++ 1 file changed, 101316 insertions(+) create mode 100644 includes/external/matrix/node_modules/matrix-js-sdk/dist/browser-matrix.js (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/dist/browser-matrix.js') diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/dist/browser-matrix.js b/includes/external/matrix/node_modules/matrix-js-sdk/dist/browser-matrix.js new file mode 100644 index 0000000..01029d7 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/dist/browser-matrix.js @@ -0,0 +1,101316 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i> 6]; + const primitive = (tag & 0x20) === 0; + + // Multi-octet tag - load + if ((tag & 0x1f) === 0x1f) { + let oct = tag; + tag = 0; + while ((oct & 0x80) === 0x80) { + oct = buf.readUInt8(fail); + if (buf.isError(oct)) + return oct; + + tag <<= 7; + tag |= oct & 0x7f; + } + } else { + tag &= 0x1f; + } + const tagStr = der.tag[tag]; + + return { + cls: cls, + primitive: primitive, + tag: tag, + tagStr: tagStr + }; +} + +function derDecodeLen(buf, primitive, fail) { + let len = buf.readUInt8(fail); + if (buf.isError(len)) + return len; + + // Indefinite form + if (!primitive && len === 0x80) + return null; + + // Definite form + if ((len & 0x80) === 0) { + // Short form + return len; + } + + // Long form + const num = len & 0x7f; + if (num > 4) + return buf.error('length octect is too long'); + + len = 0; + for (let i = 0; i < num; i++) { + len <<= 8; + const j = buf.readUInt8(fail); + if (buf.isError(j)) + return j; + len |= j; + } + + return len; +} + +},{"../base/buffer":4,"../base/node":6,"../constants/der":8,"bn.js":18,"inherits":146}],11:[function(require,module,exports){ +'use strict'; + +const decoders = exports; + +decoders.der = require('./der'); +decoders.pem = require('./pem'); + +},{"./der":10,"./pem":12}],12:[function(require,module,exports){ +'use strict'; + +const inherits = require('inherits'); +const Buffer = require('safer-buffer').Buffer; + +const DERDecoder = require('./der'); + +function PEMDecoder(entity) { + DERDecoder.call(this, entity); + this.enc = 'pem'; +} +inherits(PEMDecoder, DERDecoder); +module.exports = PEMDecoder; + +PEMDecoder.prototype.decode = function decode(data, options) { + const lines = data.toString().split(/[\r\n]+/g); + + const label = options.label.toUpperCase(); + + const re = /^-----(BEGIN|END) ([^-]+)-----$/; + let start = -1; + let end = -1; + for (let i = 0; i < lines.length; i++) { + const match = lines[i].match(re); + if (match === null) + continue; + + if (match[2] !== label) + continue; + + if (start === -1) { + if (match[1] !== 'BEGIN') + break; + start = i; + } else { + if (match[1] !== 'END') + break; + end = i; + break; + } + } + if (start === -1 || end === -1) + throw new Error('PEM section not found for: ' + label); + + const base64 = lines.slice(start + 1, end).join(''); + // Remove excessive symbols + base64.replace(/[^a-z0-9+/=]+/gi, ''); + + const input = Buffer.from(base64, 'base64'); + return DERDecoder.prototype.decode.call(this, input, options); +}; + +},{"./der":10,"inherits":146,"safer-buffer":251}],13:[function(require,module,exports){ +'use strict'; + +const inherits = require('inherits'); +const Buffer = require('safer-buffer').Buffer; +const Node = require('../base/node'); + +// Import DER constants +const der = require('../constants/der'); + +function DEREncoder(entity) { + this.enc = 'der'; + this.name = entity.name; + this.entity = entity; + + // Construct base tree + this.tree = new DERNode(); + this.tree._init(entity.body); +} +module.exports = DEREncoder; + +DEREncoder.prototype.encode = function encode(data, reporter) { + return this.tree._encode(data, reporter).join(); +}; + +// Tree methods + +function DERNode(parent) { + Node.call(this, 'der', parent); +} +inherits(DERNode, Node); + +DERNode.prototype._encodeComposite = function encodeComposite(tag, + primitive, + cls, + content) { + const encodedTag = encodeTag(tag, primitive, cls, this.reporter); + + // Short form + if (content.length < 0x80) { + const header = Buffer.alloc(2); + header[0] = encodedTag; + header[1] = content.length; + return this._createEncoderBuffer([ header, content ]); + } + + // Long form + // Count octets required to store length + let lenOctets = 1; + for (let i = content.length; i >= 0x100; i >>= 8) + lenOctets++; + + const header = Buffer.alloc(1 + 1 + lenOctets); + header[0] = encodedTag; + header[1] = 0x80 | lenOctets; + + for (let i = 1 + lenOctets, j = content.length; j > 0; i--, j >>= 8) + header[i] = j & 0xff; + + return this._createEncoderBuffer([ header, content ]); +}; + +DERNode.prototype._encodeStr = function encodeStr(str, tag) { + if (tag === 'bitstr') { + return this._createEncoderBuffer([ str.unused | 0, str.data ]); + } else if (tag === 'bmpstr') { + const buf = Buffer.alloc(str.length * 2); + for (let i = 0; i < str.length; i++) { + buf.writeUInt16BE(str.charCodeAt(i), i * 2); + } + return this._createEncoderBuffer(buf); + } else if (tag === 'numstr') { + if (!this._isNumstr(str)) { + return this.reporter.error('Encoding of string type: numstr supports ' + + 'only digits and space'); + } + return this._createEncoderBuffer(str); + } else if (tag === 'printstr') { + if (!this._isPrintstr(str)) { + return this.reporter.error('Encoding of string type: printstr supports ' + + 'only latin upper and lower case letters, ' + + 'digits, space, apostrophe, left and rigth ' + + 'parenthesis, plus sign, comma, hyphen, ' + + 'dot, slash, colon, equal sign, ' + + 'question mark'); + } + return this._createEncoderBuffer(str); + } else if (/str$/.test(tag)) { + return this._createEncoderBuffer(str); + } else if (tag === 'objDesc') { + return this._createEncoderBuffer(str); + } else { + return this.reporter.error('Encoding of string type: ' + tag + + ' unsupported'); + } +}; + +DERNode.prototype._encodeObjid = function encodeObjid(id, values, relative) { + if (typeof id === 'string') { + if (!values) + return this.reporter.error('string objid given, but no values map found'); + if (!values.hasOwnProperty(id)) + return this.reporter.error('objid not found in values map'); + id = values[id].split(/[\s.]+/g); + for (let i = 0; i < id.length; i++) + id[i] |= 0; + } else if (Array.isArray(id)) { + id = id.slice(); + for (let i = 0; i < id.length; i++) + id[i] |= 0; + } + + if (!Array.isArray(id)) { + return this.reporter.error('objid() should be either array or string, ' + + 'got: ' + JSON.stringify(id)); + } + + if (!relative) { + if (id[1] >= 40) + return this.reporter.error('Second objid identifier OOB'); + id.splice(0, 2, id[0] * 40 + id[1]); + } + + // Count number of octets + let size = 0; + for (let i = 0; i < id.length; i++) { + let ident = id[i]; + for (size++; ident >= 0x80; ident >>= 7) + size++; + } + + const objid = Buffer.alloc(size); + let offset = objid.length - 1; + for (let i = id.length - 1; i >= 0; i--) { + let ident = id[i]; + objid[offset--] = ident & 0x7f; + while ((ident >>= 7) > 0) + objid[offset--] = 0x80 | (ident & 0x7f); + } + + return this._createEncoderBuffer(objid); +}; + +function two(num) { + if (num < 10) + return '0' + num; + else + return num; +} + +DERNode.prototype._encodeTime = function encodeTime(time, tag) { + let str; + const date = new Date(time); + + if (tag === 'gentime') { + str = [ + two(date.getUTCFullYear()), + two(date.getUTCMonth() + 1), + two(date.getUTCDate()), + two(date.getUTCHours()), + two(date.getUTCMinutes()), + two(date.getUTCSeconds()), + 'Z' + ].join(''); + } else if (tag === 'utctime') { + str = [ + two(date.getUTCFullYear() % 100), + two(date.getUTCMonth() + 1), + two(date.getUTCDate()), + two(date.getUTCHours()), + two(date.getUTCMinutes()), + two(date.getUTCSeconds()), + 'Z' + ].join(''); + } else { + this.reporter.error('Encoding ' + tag + ' time is not supported yet'); + } + + return this._encodeStr(str, 'octstr'); +}; + +DERNode.prototype._encodeNull = function encodeNull() { + return this._createEncoderBuffer(''); +}; + +DERNode.prototype._encodeInt = function encodeInt(num, values) { + if (typeof num === 'string') { + if (!values) + return this.reporter.error('String int or enum given, but no values map'); + if (!values.hasOwnProperty(num)) { + return this.reporter.error('Values map doesn\'t contain: ' + + JSON.stringify(num)); + } + num = values[num]; + } + + // Bignum, assume big endian + if (typeof num !== 'number' && !Buffer.isBuffer(num)) { + const numArray = num.toArray(); + if (!num.sign && numArray[0] & 0x80) { + numArray.unshift(0); + } + num = Buffer.from(numArray); + } + + if (Buffer.isBuffer(num)) { + let size = num.length; + if (num.length === 0) + size++; + + const out = Buffer.alloc(size); + num.copy(out); + if (num.length === 0) + out[0] = 0; + return this._createEncoderBuffer(out); + } + + if (num < 0x80) + return this._createEncoderBuffer(num); + + if (num < 0x100) + return this._createEncoderBuffer([0, num]); + + let size = 1; + for (let i = num; i >= 0x100; i >>= 8) + size++; + + const out = new Array(size); + for (let i = out.length - 1; i >= 0; i--) { + out[i] = num & 0xff; + num >>= 8; + } + if(out[0] & 0x80) { + out.unshift(0); + } + + return this._createEncoderBuffer(Buffer.from(out)); +}; + +DERNode.prototype._encodeBool = function encodeBool(value) { + return this._createEncoderBuffer(value ? 0xff : 0); +}; + +DERNode.prototype._use = function use(entity, obj) { + if (typeof entity === 'function') + entity = entity(obj); + return entity._getEncoder('der').tree; +}; + +DERNode.prototype._skipDefault = function skipDefault(dataBuffer, reporter, parent) { + const state = this._baseState; + let i; + if (state['default'] === null) + return false; + + const data = dataBuffer.join(); + if (state.defaultBuffer === undefined) + state.defaultBuffer = this._encodeValue(state['default'], reporter, parent).join(); + + if (data.length !== state.defaultBuffer.length) + return false; + + for (i=0; i < data.length; i++) + if (data[i] !== state.defaultBuffer[i]) + return false; + + return true; +}; + +// Utility methods + +function encodeTag(tag, primitive, cls, reporter) { + let res; + + if (tag === 'seqof') + tag = 'seq'; + else if (tag === 'setof') + tag = 'set'; + + if (der.tagByName.hasOwnProperty(tag)) + res = der.tagByName[tag]; + else if (typeof tag === 'number' && (tag | 0) === tag) + res = tag; + else + return reporter.error('Unknown tag: ' + tag); + + if (res >= 0x1f) + return reporter.error('Multi-octet tag encoding unsupported'); + + if (!primitive) + res |= 0x20; + + res |= (der.tagClassByName[cls || 'universal'] << 6); + + return res; +} + +},{"../base/node":6,"../constants/der":8,"inherits":146,"safer-buffer":251}],14:[function(require,module,exports){ +'use strict'; + +const encoders = exports; + +encoders.der = require('./der'); +encoders.pem = require('./pem'); + +},{"./der":13,"./pem":15}],15:[function(require,module,exports){ +'use strict'; + +const inherits = require('inherits'); + +const DEREncoder = require('./der'); + +function PEMEncoder(entity) { + DEREncoder.call(this, entity); + this.enc = 'pem'; +} +inherits(PEMEncoder, DEREncoder); +module.exports = PEMEncoder; + +PEMEncoder.prototype.encode = function encode(data, options) { + const buf = DEREncoder.prototype.encode.call(this, data); + + const p = buf.toString('base64'); + const out = [ '-----BEGIN ' + options.label + '-----' ]; + for (let i = 0; i < p.length; i += 64) + out.push(p.slice(i, i + 64)); + out.push('-----END ' + options.label + '-----'); + return out.join('\n'); +}; + +},{"./der":13,"inherits":146}],16:[function(require,module,exports){ +(function (global){(function (){ +'use strict'; + +var possibleNames = [ + 'BigInt64Array', + 'BigUint64Array', + 'Float32Array', + 'Float64Array', + 'Int16Array', + 'Int32Array', + 'Int8Array', + 'Uint16Array', + 'Uint32Array', + 'Uint8Array', + 'Uint8ClampedArray' +]; + +var g = typeof globalThis === 'undefined' ? global : globalThis; + +module.exports = function availableTypedArrays() { + var out = []; + for (var i = 0; i < possibleNames.length; i++) { + if (typeof g[possibleNames[i]] === 'function') { + out[out.length] = possibleNames[i]; + } + } + return out; +}; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],17:[function(require,module,exports){ +'use strict' + +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function getLens (b64) { + var len = b64.length + + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len + + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) + + return [validLen, placeHoldersLen] +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) + + var curByte = 0 + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen + + var i + for (i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } + + return parts.join('') +} + +},{}],18:[function(require,module,exports){ +(function (module, exports) { + 'use strict'; + + // Utils + function assert (val, msg) { + if (!val) throw new Error(msg || 'Assertion failed'); + } + + // Could use `inherits` module, but don't want to move from single file + // architecture yet. + function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + + // BN + + function BN (number, base, endian) { + if (BN.isBN(number)) { + return number; + } + + this.negative = 0; + this.words = null; + this.length = 0; + + // Reduction context + this.red = null; + + if (number !== null) { + if (base === 'le' || base === 'be') { + endian = base; + base = 10; + } + + this._init(number || 0, base || 10, endian || 'be'); + } + } + if (typeof module === 'object') { + module.exports = BN; + } else { + exports.BN = BN; + } + + BN.BN = BN; + BN.wordSize = 26; + + var Buffer; + try { + if (typeof window !== 'undefined' && typeof window.Buffer !== 'undefined') { + Buffer = window.Buffer; + } else { + Buffer = require('buffer').Buffer; + } + } catch (e) { + } + + BN.isBN = function isBN (num) { + if (num instanceof BN) { + return true; + } + + return num !== null && typeof num === 'object' && + num.constructor.wordSize === BN.wordSize && Array.isArray(num.words); + }; + + BN.max = function max (left, right) { + if (left.cmp(right) > 0) return left; + return right; + }; + + BN.min = function min (left, right) { + if (left.cmp(right) < 0) return left; + return right; + }; + + BN.prototype._init = function init (number, base, endian) { + if (typeof number === 'number') { + return this._initNumber(number, base, endian); + } + + if (typeof number === 'object') { + return this._initArray(number, base, endian); + } + + if (base === 'hex') { + base = 16; + } + assert(base === (base | 0) && base >= 2 && base <= 36); + + number = number.toString().replace(/\s+/g, ''); + var start = 0; + if (number[0] === '-') { + start++; + this.negative = 1; + } + + if (start < number.length) { + if (base === 16) { + this._parseHex(number, start, endian); + } else { + this._parseBase(number, base, start); + if (endian === 'le') { + this._initArray(this.toArray(), base, endian); + } + } + } + }; + + BN.prototype._initNumber = function _initNumber (number, base, endian) { + if (number < 0) { + this.negative = 1; + number = -number; + } + if (number < 0x4000000) { + this.words = [ number & 0x3ffffff ]; + this.length = 1; + } else if (number < 0x10000000000000) { + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff + ]; + this.length = 2; + } else { + assert(number < 0x20000000000000); // 2 ^ 53 (unsafe) + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff, + 1 + ]; + this.length = 3; + } + + if (endian !== 'le') return; + + // Reverse the bytes + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initArray = function _initArray (number, base, endian) { + // Perhaps a Uint8Array + assert(typeof number.length === 'number'); + if (number.length <= 0) { + this.words = [ 0 ]; + this.length = 1; + return this; + } + + this.length = Math.ceil(number.length / 3); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + var off = 0; + if (endian === 'be') { + for (i = number.length - 1, j = 0; i >= 0; i -= 3) { + w = number[i] | (number[i - 1] << 8) | (number[i - 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } else if (endian === 'le') { + for (i = 0, j = 0; i < number.length; i += 3) { + w = number[i] | (number[i + 1] << 8) | (number[i + 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } + return this.strip(); + }; + + function parseHex4Bits (string, index) { + var c = string.charCodeAt(index); + // 'A' - 'F' + if (c >= 65 && c <= 70) { + return c - 55; + // 'a' - 'f' + } else if (c >= 97 && c <= 102) { + return c - 87; + // '0' - '9' + } else { + return (c - 48) & 0xf; + } + } + + function parseHexByte (string, lowerBound, index) { + var r = parseHex4Bits(string, index); + if (index - 1 >= lowerBound) { + r |= parseHex4Bits(string, index - 1) << 4; + } + return r; + } + + BN.prototype._parseHex = function _parseHex (number, start, endian) { + // Create possibly bigger array to ensure that it fits the number + this.length = Math.ceil((number.length - start) / 6); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + // 24-bits chunks + var off = 0; + var j = 0; + + var w; + if (endian === 'be') { + for (i = number.length - 1; i >= start; i -= 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } + } + } else { + var parseLength = number.length - start; + for (i = parseLength % 2 === 0 ? start + 1 : start; i < number.length; i += 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } + } + } + + this.strip(); + }; + + function parseBase (str, start, end, mul) { + var r = 0; + var len = Math.min(str.length, end); + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r *= mul; + + // 'a' + if (c >= 49) { + r += c - 49 + 0xa; + + // 'A' + } else if (c >= 17) { + r += c - 17 + 0xa; + + // '0' - '9' + } else { + r += c; + } + } + return r; + } + + BN.prototype._parseBase = function _parseBase (number, base, start) { + // Initialize as zero + this.words = [ 0 ]; + this.length = 1; + + // Find length of limb in base + for (var limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= base) { + limbLen++; + } + limbLen--; + limbPow = (limbPow / base) | 0; + + var total = number.length - start; + var mod = total % limbLen; + var end = Math.min(total, total - mod) + start; + + var word = 0; + for (var i = start; i < end; i += limbLen) { + word = parseBase(number, i, i + limbLen, base); + + this.imuln(limbPow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + if (mod !== 0) { + var pow = 1; + word = parseBase(number, i, number.length, base); + + for (i = 0; i < mod; i++) { + pow *= base; + } + + this.imuln(pow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + this.strip(); + }; + + BN.prototype.copy = function copy (dest) { + dest.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + dest.words[i] = this.words[i]; + } + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + BN.prototype.clone = function clone () { + var r = new BN(null); + this.copy(r); + return r; + }; + + BN.prototype._expand = function _expand (size) { + while (this.length < size) { + this.words[this.length++] = 0; + } + return this; + }; + + // Remove leading `0` from `this` + BN.prototype.strip = function strip () { + while (this.length > 1 && this.words[this.length - 1] === 0) { + this.length--; + } + return this._normSign(); + }; + + BN.prototype._normSign = function _normSign () { + // -0 = 0 + if (this.length === 1 && this.words[0] === 0) { + this.negative = 0; + } + return this; + }; + + BN.prototype.inspect = function inspect () { + return (this.red ? ''; + }; + + /* + + var zeros = []; + var groupSizes = []; + var groupBases = []; + + var s = ''; + var i = -1; + while (++i < BN.wordSize) { + zeros[i] = s; + s += '0'; + } + groupSizes[0] = 0; + groupSizes[1] = 0; + groupBases[0] = 0; + groupBases[1] = 0; + var base = 2 - 1; + while (++base < 36 + 1) { + var groupSize = 0; + var groupBase = 1; + while (groupBase < (1 << BN.wordSize) / base) { + groupBase *= base; + groupSize += 1; + } + groupSizes[base] = groupSize; + groupBases[base] = groupBase; + } + + */ + + var zeros = [ + '', + '0', + '00', + '000', + '0000', + '00000', + '000000', + '0000000', + '00000000', + '000000000', + '0000000000', + '00000000000', + '000000000000', + '0000000000000', + '00000000000000', + '000000000000000', + '0000000000000000', + '00000000000000000', + '000000000000000000', + '0000000000000000000', + '00000000000000000000', + '000000000000000000000', + '0000000000000000000000', + '00000000000000000000000', + '000000000000000000000000', + '0000000000000000000000000' + ]; + + var groupSizes = [ + 0, 0, + 25, 16, 12, 11, 10, 9, 8, + 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5 + ]; + + var groupBases = [ + 0, 0, + 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216, + 43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625, + 16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, + 6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149, + 24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176 + ]; + + BN.prototype.toString = function toString (base, padding) { + base = base || 10; + padding = padding | 0 || 1; + + var out; + if (base === 16 || base === 'hex') { + out = ''; + var off = 0; + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = this.words[i]; + var word = (((w << off) | carry) & 0xffffff).toString(16); + carry = (w >>> (24 - off)) & 0xffffff; + if (carry !== 0 || i !== this.length - 1) { + out = zeros[6 - word.length] + word + out; + } else { + out = word + out; + } + off += 2; + if (off >= 26) { + off -= 26; + i--; + } + } + if (carry !== 0) { + out = carry.toString(16) + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + if (base === (base | 0) && base >= 2 && base <= 36) { + // var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(base)); + var groupSize = groupSizes[base]; + // var groupBase = Math.pow(base, groupSize); + var groupBase = groupBases[base]; + out = ''; + var c = this.clone(); + c.negative = 0; + while (!c.isZero()) { + var r = c.modn(groupBase).toString(base); + c = c.idivn(groupBase); + + if (!c.isZero()) { + out = zeros[groupSize - r.length] + r + out; + } else { + out = r + out; + } + } + if (this.isZero()) { + out = '0' + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + assert(false, 'Base should be between 2 and 36'); + }; + + BN.prototype.toNumber = function toNumber () { + var ret = this.words[0]; + if (this.length === 2) { + ret += this.words[1] * 0x4000000; + } else if (this.length === 3 && this.words[2] === 0x01) { + // NOTE: at this stage it is known that the top bit is set + ret += 0x10000000000000 + (this.words[1] * 0x4000000); + } else if (this.length > 2) { + assert(false, 'Number can only safely store up to 53 bits'); + } + return (this.negative !== 0) ? -ret : ret; + }; + + BN.prototype.toJSON = function toJSON () { + return this.toString(16); + }; + + BN.prototype.toBuffer = function toBuffer (endian, length) { + assert(typeof Buffer !== 'undefined'); + return this.toArrayLike(Buffer, endian, length); + }; + + BN.prototype.toArray = function toArray (endian, length) { + return this.toArrayLike(Array, endian, length); + }; + + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + var byteLength = this.byteLength(); + var reqLength = length || Math.max(1, byteLength); + assert(byteLength <= reqLength, 'byte array longer than desired length'); + assert(reqLength > 0, 'Requested array length <= 0'); + + this.strip(); + var littleEndian = endian === 'le'; + var res = new ArrayType(reqLength); + + var b, i; + var q = this.clone(); + if (!littleEndian) { + // Assume big-endian + for (i = 0; i < reqLength - byteLength; i++) { + res[i] = 0; + } + + for (i = 0; !q.isZero(); i++) { + b = q.andln(0xff); + q.iushrn(8); + + res[reqLength - i - 1] = b; + } + } else { + for (i = 0; !q.isZero(); i++) { + b = q.andln(0xff); + q.iushrn(8); + + res[i] = b; + } + + for (; i < reqLength; i++) { + res[i] = 0; + } + } + + return res; + }; + + if (Math.clz32) { + BN.prototype._countBits = function _countBits (w) { + return 32 - Math.clz32(w); + }; + } else { + BN.prototype._countBits = function _countBits (w) { + var t = w; + var r = 0; + if (t >= 0x1000) { + r += 13; + t >>>= 13; + } + if (t >= 0x40) { + r += 7; + t >>>= 7; + } + if (t >= 0x8) { + r += 4; + t >>>= 4; + } + if (t >= 0x02) { + r += 2; + t >>>= 2; + } + return r + t; + }; + } + + BN.prototype._zeroBits = function _zeroBits (w) { + // Short-cut + if (w === 0) return 26; + + var t = w; + var r = 0; + if ((t & 0x1fff) === 0) { + r += 13; + t >>>= 13; + } + if ((t & 0x7f) === 0) { + r += 7; + t >>>= 7; + } + if ((t & 0xf) === 0) { + r += 4; + t >>>= 4; + } + if ((t & 0x3) === 0) { + r += 2; + t >>>= 2; + } + if ((t & 0x1) === 0) { + r++; + } + return r; + }; + + // Return number of used bits in a BN + BN.prototype.bitLength = function bitLength () { + var w = this.words[this.length - 1]; + var hi = this._countBits(w); + return (this.length - 1) * 26 + hi; + }; + + function toBitArray (num) { + var w = new Array(num.bitLength()); + + for (var bit = 0; bit < w.length; bit++) { + var off = (bit / 26) | 0; + var wbit = bit % 26; + + w[bit] = (num.words[off] & (1 << wbit)) >>> wbit; + } + + return w; + } + + // Number of trailing zero bits + BN.prototype.zeroBits = function zeroBits () { + if (this.isZero()) return 0; + + var r = 0; + for (var i = 0; i < this.length; i++) { + var b = this._zeroBits(this.words[i]); + r += b; + if (b !== 26) break; + } + return r; + }; + + BN.prototype.byteLength = function byteLength () { + return Math.ceil(this.bitLength() / 8); + }; + + BN.prototype.toTwos = function toTwos (width) { + if (this.negative !== 0) { + return this.abs().inotn(width).iaddn(1); + } + return this.clone(); + }; + + BN.prototype.fromTwos = function fromTwos (width) { + if (this.testn(width - 1)) { + return this.notn(width).iaddn(1).ineg(); + } + return this.clone(); + }; + + BN.prototype.isNeg = function isNeg () { + return this.negative !== 0; + }; + + // Return negative clone of `this` + BN.prototype.neg = function neg () { + return this.clone().ineg(); + }; + + BN.prototype.ineg = function ineg () { + if (!this.isZero()) { + this.negative ^= 1; + } + + return this; + }; + + // Or `num` with `this` in-place + BN.prototype.iuor = function iuor (num) { + while (this.length < num.length) { + this.words[this.length++] = 0; + } + + for (var i = 0; i < num.length; i++) { + this.words[i] = this.words[i] | num.words[i]; + } + + return this.strip(); + }; + + BN.prototype.ior = function ior (num) { + assert((this.negative | num.negative) === 0); + return this.iuor(num); + }; + + // Or `num` with `this` + BN.prototype.or = function or (num) { + if (this.length > num.length) return this.clone().ior(num); + return num.clone().ior(this); + }; + + BN.prototype.uor = function uor (num) { + if (this.length > num.length) return this.clone().iuor(num); + return num.clone().iuor(this); + }; + + // And `num` with `this` in-place + BN.prototype.iuand = function iuand (num) { + // b = min-length(num, this) + var b; + if (this.length > num.length) { + b = num; + } else { + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = this.words[i] & num.words[i]; + } + + this.length = b.length; + + return this.strip(); + }; + + BN.prototype.iand = function iand (num) { + assert((this.negative | num.negative) === 0); + return this.iuand(num); + }; + + // And `num` with `this` + BN.prototype.and = function and (num) { + if (this.length > num.length) return this.clone().iand(num); + return num.clone().iand(this); + }; + + BN.prototype.uand = function uand (num) { + if (this.length > num.length) return this.clone().iuand(num); + return num.clone().iuand(this); + }; + + // Xor `num` with `this` in-place + BN.prototype.iuxor = function iuxor (num) { + // a.length > b.length + var a; + var b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = a.words[i] ^ b.words[i]; + } + + if (this !== a) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = a.length; + + return this.strip(); + }; + + BN.prototype.ixor = function ixor (num) { + assert((this.negative | num.negative) === 0); + return this.iuxor(num); + }; + + // Xor `num` with `this` + BN.prototype.xor = function xor (num) { + if (this.length > num.length) return this.clone().ixor(num); + return num.clone().ixor(this); + }; + + BN.prototype.uxor = function uxor (num) { + if (this.length > num.length) return this.clone().iuxor(num); + return num.clone().iuxor(this); + }; + + // Not ``this`` with ``width`` bitwidth + BN.prototype.inotn = function inotn (width) { + assert(typeof width === 'number' && width >= 0); + + var bytesNeeded = Math.ceil(width / 26) | 0; + var bitsLeft = width % 26; + + // Extend the buffer with leading zeroes + this._expand(bytesNeeded); + + if (bitsLeft > 0) { + bytesNeeded--; + } + + // Handle complete words + for (var i = 0; i < bytesNeeded; i++) { + this.words[i] = ~this.words[i] & 0x3ffffff; + } + + // Handle the residue + if (bitsLeft > 0) { + this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft)); + } + + // And remove leading zeroes + return this.strip(); + }; + + BN.prototype.notn = function notn (width) { + return this.clone().inotn(width); + }; + + // Set `bit` of `this` + BN.prototype.setn = function setn (bit, val) { + assert(typeof bit === 'number' && bit >= 0); + + var off = (bit / 26) | 0; + var wbit = bit % 26; + + this._expand(off + 1); + + if (val) { + this.words[off] = this.words[off] | (1 << wbit); + } else { + this.words[off] = this.words[off] & ~(1 << wbit); + } + + return this.strip(); + }; + + // Add `num` to `this` in-place + BN.prototype.iadd = function iadd (num) { + var r; + + // negative + positive + if (this.negative !== 0 && num.negative === 0) { + this.negative = 0; + r = this.isub(num); + this.negative ^= 1; + return this._normSign(); + + // positive + negative + } else if (this.negative === 0 && num.negative !== 0) { + num.negative = 0; + r = this.isub(num); + num.negative = 1; + return r._normSign(); + } + + // a.length > b.length + var a, b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) + (b.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + + this.length = a.length; + if (carry !== 0) { + this.words[this.length] = carry; + this.length++; + // Copy the rest of the words + } else if (a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + return this; + }; + + // Add `num` to `this` + BN.prototype.add = function add (num) { + var res; + if (num.negative !== 0 && this.negative === 0) { + num.negative = 0; + res = this.sub(num); + num.negative ^= 1; + return res; + } else if (num.negative === 0 && this.negative !== 0) { + this.negative = 0; + res = num.sub(this); + this.negative = 1; + return res; + } + + if (this.length > num.length) return this.clone().iadd(num); + + return num.clone().iadd(this); + }; + + // Subtract `num` from `this` in-place + BN.prototype.isub = function isub (num) { + // this - (-num) = this + num + if (num.negative !== 0) { + num.negative = 0; + var r = this.iadd(num); + num.negative = 1; + return r._normSign(); + + // -this - num = -(this + num) + } else if (this.negative !== 0) { + this.negative = 0; + this.iadd(num); + this.negative = 1; + return this._normSign(); + } + + // At this point both numbers are positive + var cmp = this.cmp(num); + + // Optimization - zeroify + if (cmp === 0) { + this.negative = 0; + this.length = 1; + this.words[0] = 0; + return this; + } + + // a > b + var a, b; + if (cmp > 0) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) - (b.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + + // Copy rest of the words + if (carry === 0 && i < a.length && a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = Math.max(this.length, i); + + if (a !== this) { + this.negative = 1; + } + + return this.strip(); + }; + + // Subtract `num` from `this` + BN.prototype.sub = function sub (num) { + return this.clone().isub(num); + }; + + function smallMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + var len = (self.length + num.length) | 0; + out.length = len; + len = (len - 1) | 0; + + // Peel one iteration (compiler can't do it, because of code complexity) + var a = self.words[0] | 0; + var b = num.words[0] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + var carry = (r / 0x4000000) | 0; + out.words[0] = lo; + + for (var k = 1; k < len; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = carry >>> 26; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = (k - j) | 0; + a = self.words[i] | 0; + b = num.words[j] | 0; + r = a * b + rword; + ncarry += (r / 0x4000000) | 0; + rword = r & 0x3ffffff; + } + out.words[k] = rword | 0; + carry = ncarry | 0; + } + if (carry !== 0) { + out.words[k] = carry | 0; + } else { + out.length--; + } + + return out.strip(); + } + + // TODO(indutny): it may be reasonable to omit it for users who don't need + // to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit + // multiplication (like elliptic secp256k1). + var comb10MulTo = function comb10MulTo (self, num, out) { + var a = self.words; + var b = num.words; + var o = out.words; + var c = 0; + var lo; + var mid; + var hi; + var a0 = a[0] | 0; + var al0 = a0 & 0x1fff; + var ah0 = a0 >>> 13; + var a1 = a[1] | 0; + var al1 = a1 & 0x1fff; + var ah1 = a1 >>> 13; + var a2 = a[2] | 0; + var al2 = a2 & 0x1fff; + var ah2 = a2 >>> 13; + var a3 = a[3] | 0; + var al3 = a3 & 0x1fff; + var ah3 = a3 >>> 13; + var a4 = a[4] | 0; + var al4 = a4 & 0x1fff; + var ah4 = a4 >>> 13; + var a5 = a[5] | 0; + var al5 = a5 & 0x1fff; + var ah5 = a5 >>> 13; + var a6 = a[6] | 0; + var al6 = a6 & 0x1fff; + var ah6 = a6 >>> 13; + var a7 = a[7] | 0; + var al7 = a7 & 0x1fff; + var ah7 = a7 >>> 13; + var a8 = a[8] | 0; + var al8 = a8 & 0x1fff; + var ah8 = a8 >>> 13; + var a9 = a[9] | 0; + var al9 = a9 & 0x1fff; + var ah9 = a9 >>> 13; + var b0 = b[0] | 0; + var bl0 = b0 & 0x1fff; + var bh0 = b0 >>> 13; + var b1 = b[1] | 0; + var bl1 = b1 & 0x1fff; + var bh1 = b1 >>> 13; + var b2 = b[2] | 0; + var bl2 = b2 & 0x1fff; + var bh2 = b2 >>> 13; + var b3 = b[3] | 0; + var bl3 = b3 & 0x1fff; + var bh3 = b3 >>> 13; + var b4 = b[4] | 0; + var bl4 = b4 & 0x1fff; + var bh4 = b4 >>> 13; + var b5 = b[5] | 0; + var bl5 = b5 & 0x1fff; + var bh5 = b5 >>> 13; + var b6 = b[6] | 0; + var bl6 = b6 & 0x1fff; + var bh6 = b6 >>> 13; + var b7 = b[7] | 0; + var bl7 = b7 & 0x1fff; + var bh7 = b7 >>> 13; + var b8 = b[8] | 0; + var bl8 = b8 & 0x1fff; + var bh8 = b8 >>> 13; + var b9 = b[9] | 0; + var bl9 = b9 & 0x1fff; + var bh9 = b9 >>> 13; + + out.negative = self.negative ^ num.negative; + out.length = 19; + /* k = 0 */ + lo = Math.imul(al0, bl0); + mid = Math.imul(al0, bh0); + mid = (mid + Math.imul(ah0, bl0)) | 0; + hi = Math.imul(ah0, bh0); + var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w0 >>> 26)) | 0; + w0 &= 0x3ffffff; + /* k = 1 */ + lo = Math.imul(al1, bl0); + mid = Math.imul(al1, bh0); + mid = (mid + Math.imul(ah1, bl0)) | 0; + hi = Math.imul(ah1, bh0); + lo = (lo + Math.imul(al0, bl1)) | 0; + mid = (mid + Math.imul(al0, bh1)) | 0; + mid = (mid + Math.imul(ah0, bl1)) | 0; + hi = (hi + Math.imul(ah0, bh1)) | 0; + var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w1 >>> 26)) | 0; + w1 &= 0x3ffffff; + /* k = 2 */ + lo = Math.imul(al2, bl0); + mid = Math.imul(al2, bh0); + mid = (mid + Math.imul(ah2, bl0)) | 0; + hi = Math.imul(ah2, bh0); + lo = (lo + Math.imul(al1, bl1)) | 0; + mid = (mid + Math.imul(al1, bh1)) | 0; + mid = (mid + Math.imul(ah1, bl1)) | 0; + hi = (hi + Math.imul(ah1, bh1)) | 0; + lo = (lo + Math.imul(al0, bl2)) | 0; + mid = (mid + Math.imul(al0, bh2)) | 0; + mid = (mid + Math.imul(ah0, bl2)) | 0; + hi = (hi + Math.imul(ah0, bh2)) | 0; + var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w2 >>> 26)) | 0; + w2 &= 0x3ffffff; + /* k = 3 */ + lo = Math.imul(al3, bl0); + mid = Math.imul(al3, bh0); + mid = (mid + Math.imul(ah3, bl0)) | 0; + hi = Math.imul(ah3, bh0); + lo = (lo + Math.imul(al2, bl1)) | 0; + mid = (mid + Math.imul(al2, bh1)) | 0; + mid = (mid + Math.imul(ah2, bl1)) | 0; + hi = (hi + Math.imul(ah2, bh1)) | 0; + lo = (lo + Math.imul(al1, bl2)) | 0; + mid = (mid + Math.imul(al1, bh2)) | 0; + mid = (mid + Math.imul(ah1, bl2)) | 0; + hi = (hi + Math.imul(ah1, bh2)) | 0; + lo = (lo + Math.imul(al0, bl3)) | 0; + mid = (mid + Math.imul(al0, bh3)) | 0; + mid = (mid + Math.imul(ah0, bl3)) | 0; + hi = (hi + Math.imul(ah0, bh3)) | 0; + var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w3 >>> 26)) | 0; + w3 &= 0x3ffffff; + /* k = 4 */ + lo = Math.imul(al4, bl0); + mid = Math.imul(al4, bh0); + mid = (mid + Math.imul(ah4, bl0)) | 0; + hi = Math.imul(ah4, bh0); + lo = (lo + Math.imul(al3, bl1)) | 0; + mid = (mid + Math.imul(al3, bh1)) | 0; + mid = (mid + Math.imul(ah3, bl1)) | 0; + hi = (hi + Math.imul(ah3, bh1)) | 0; + lo = (lo + Math.imul(al2, bl2)) | 0; + mid = (mid + Math.imul(al2, bh2)) | 0; + mid = (mid + Math.imul(ah2, bl2)) | 0; + hi = (hi + Math.imul(ah2, bh2)) | 0; + lo = (lo + Math.imul(al1, bl3)) | 0; + mid = (mid + Math.imul(al1, bh3)) | 0; + mid = (mid + Math.imul(ah1, bl3)) | 0; + hi = (hi + Math.imul(ah1, bh3)) | 0; + lo = (lo + Math.imul(al0, bl4)) | 0; + mid = (mid + Math.imul(al0, bh4)) | 0; + mid = (mid + Math.imul(ah0, bl4)) | 0; + hi = (hi + Math.imul(ah0, bh4)) | 0; + var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w4 >>> 26)) | 0; + w4 &= 0x3ffffff; + /* k = 5 */ + lo = Math.imul(al5, bl0); + mid = Math.imul(al5, bh0); + mid = (mid + Math.imul(ah5, bl0)) | 0; + hi = Math.imul(ah5, bh0); + lo = (lo + Math.imul(al4, bl1)) | 0; + mid = (mid + Math.imul(al4, bh1)) | 0; + mid = (mid + Math.imul(ah4, bl1)) | 0; + hi = (hi + Math.imul(ah4, bh1)) | 0; + lo = (lo + Math.imul(al3, bl2)) | 0; + mid = (mid + Math.imul(al3, bh2)) | 0; + mid = (mid + Math.imul(ah3, bl2)) | 0; + hi = (hi + Math.imul(ah3, bh2)) | 0; + lo = (lo + Math.imul(al2, bl3)) | 0; + mid = (mid + Math.imul(al2, bh3)) | 0; + mid = (mid + Math.imul(ah2, bl3)) | 0; + hi = (hi + Math.imul(ah2, bh3)) | 0; + lo = (lo + Math.imul(al1, bl4)) | 0; + mid = (mid + Math.imul(al1, bh4)) | 0; + mid = (mid + Math.imul(ah1, bl4)) | 0; + hi = (hi + Math.imul(ah1, bh4)) | 0; + lo = (lo + Math.imul(al0, bl5)) | 0; + mid = (mid + Math.imul(al0, bh5)) | 0; + mid = (mid + Math.imul(ah0, bl5)) | 0; + hi = (hi + Math.imul(ah0, bh5)) | 0; + var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w5 >>> 26)) | 0; + w5 &= 0x3ffffff; + /* k = 6 */ + lo = Math.imul(al6, bl0); + mid = Math.imul(al6, bh0); + mid = (mid + Math.imul(ah6, bl0)) | 0; + hi = Math.imul(ah6, bh0); + lo = (lo + Math.imul(al5, bl1)) | 0; + mid = (mid + Math.imul(al5, bh1)) | 0; + mid = (mid + Math.imul(ah5, bl1)) | 0; + hi = (hi + Math.imul(ah5, bh1)) | 0; + lo = (lo + Math.imul(al4, bl2)) | 0; + mid = (mid + Math.imul(al4, bh2)) | 0; + mid = (mid + Math.imul(ah4, bl2)) | 0; + hi = (hi + Math.imul(ah4, bh2)) | 0; + lo = (lo + Math.imul(al3, bl3)) | 0; + mid = (mid + Math.imul(al3, bh3)) | 0; + mid = (mid + Math.imul(ah3, bl3)) | 0; + hi = (hi + Math.imul(ah3, bh3)) | 0; + lo = (lo + Math.imul(al2, bl4)) | 0; + mid = (mid + Math.imul(al2, bh4)) | 0; + mid = (mid + Math.imul(ah2, bl4)) | 0; + hi = (hi + Math.imul(ah2, bh4)) | 0; + lo = (lo + Math.imul(al1, bl5)) | 0; + mid = (mid + Math.imul(al1, bh5)) | 0; + mid = (mid + Math.imul(ah1, bl5)) | 0; + hi = (hi + Math.imul(ah1, bh5)) | 0; + lo = (lo + Math.imul(al0, bl6)) | 0; + mid = (mid + Math.imul(al0, bh6)) | 0; + mid = (mid + Math.imul(ah0, bl6)) | 0; + hi = (hi + Math.imul(ah0, bh6)) | 0; + var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w6 >>> 26)) | 0; + w6 &= 0x3ffffff; + /* k = 7 */ + lo = Math.imul(al7, bl0); + mid = Math.imul(al7, bh0); + mid = (mid + Math.imul(ah7, bl0)) | 0; + hi = Math.imul(ah7, bh0); + lo = (lo + Math.imul(al6, bl1)) | 0; + mid = (mid + Math.imul(al6, bh1)) | 0; + mid = (mid + Math.imul(ah6, bl1)) | 0; + hi = (hi + Math.imul(ah6, bh1)) | 0; + lo = (lo + Math.imul(al5, bl2)) | 0; + mid = (mid + Math.imul(al5, bh2)) | 0; + mid = (mid + Math.imul(ah5, bl2)) | 0; + hi = (hi + Math.imul(ah5, bh2)) | 0; + lo = (lo + Math.imul(al4, bl3)) | 0; + mid = (mid + Math.imul(al4, bh3)) | 0; + mid = (mid + Math.imul(ah4, bl3)) | 0; + hi = (hi + Math.imul(ah4, bh3)) | 0; + lo = (lo + Math.imul(al3, bl4)) | 0; + mid = (mid + Math.imul(al3, bh4)) | 0; + mid = (mid + Math.imul(ah3, bl4)) | 0; + hi = (hi + Math.imul(ah3, bh4)) | 0; + lo = (lo + Math.imul(al2, bl5)) | 0; + mid = (mid + Math.imul(al2, bh5)) | 0; + mid = (mid + Math.imul(ah2, bl5)) | 0; + hi = (hi + Math.imul(ah2, bh5)) | 0; + lo = (lo + Math.imul(al1, bl6)) | 0; + mid = (mid + Math.imul(al1, bh6)) | 0; + mid = (mid + Math.imul(ah1, bl6)) | 0; + hi = (hi + Math.imul(ah1, bh6)) | 0; + lo = (lo + Math.imul(al0, bl7)) | 0; + mid = (mid + Math.imul(al0, bh7)) | 0; + mid = (mid + Math.imul(ah0, bl7)) | 0; + hi = (hi + Math.imul(ah0, bh7)) | 0; + var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w7 >>> 26)) | 0; + w7 &= 0x3ffffff; + /* k = 8 */ + lo = Math.imul(al8, bl0); + mid = Math.imul(al8, bh0); + mid = (mid + Math.imul(ah8, bl0)) | 0; + hi = Math.imul(ah8, bh0); + lo = (lo + Math.imul(al7, bl1)) | 0; + mid = (mid + Math.imul(al7, bh1)) | 0; + mid = (mid + Math.imul(ah7, bl1)) | 0; + hi = (hi + Math.imul(ah7, bh1)) | 0; + lo = (lo + Math.imul(al6, bl2)) | 0; + mid = (mid + Math.imul(al6, bh2)) | 0; + mid = (mid + Math.imul(ah6, bl2)) | 0; + hi = (hi + Math.imul(ah6, bh2)) | 0; + lo = (lo + Math.imul(al5, bl3)) | 0; + mid = (mid + Math.imul(al5, bh3)) | 0; + mid = (mid + Math.imul(ah5, bl3)) | 0; + hi = (hi + Math.imul(ah5, bh3)) | 0; + lo = (lo + Math.imul(al4, bl4)) | 0; + mid = (mid + Math.imul(al4, bh4)) | 0; + mid = (mid + Math.imul(ah4, bl4)) | 0; + hi = (hi + Math.imul(ah4, bh4)) | 0; + lo = (lo + Math.imul(al3, bl5)) | 0; + mid = (mid + Math.imul(al3, bh5)) | 0; + mid = (mid + Math.imul(ah3, bl5)) | 0; + hi = (hi + Math.imul(ah3, bh5)) | 0; + lo = (lo + Math.imul(al2, bl6)) | 0; + mid = (mid + Math.imul(al2, bh6)) | 0; + mid = (mid + Math.imul(ah2, bl6)) | 0; + hi = (hi + Math.imul(ah2, bh6)) | 0; + lo = (lo + Math.imul(al1, bl7)) | 0; + mid = (mid + Math.imul(al1, bh7)) | 0; + mid = (mid + Math.imul(ah1, bl7)) | 0; + hi = (hi + Math.imul(ah1, bh7)) | 0; + lo = (lo + Math.imul(al0, bl8)) | 0; + mid = (mid + Math.imul(al0, bh8)) | 0; + mid = (mid + Math.imul(ah0, bl8)) | 0; + hi = (hi + Math.imul(ah0, bh8)) | 0; + var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w8 >>> 26)) | 0; + w8 &= 0x3ffffff; + /* k = 9 */ + lo = Math.imul(al9, bl0); + mid = Math.imul(al9, bh0); + mid = (mid + Math.imul(ah9, bl0)) | 0; + hi = Math.imul(ah9, bh0); + lo = (lo + Math.imul(al8, bl1)) | 0; + mid = (mid + Math.imul(al8, bh1)) | 0; + mid = (mid + Math.imul(ah8, bl1)) | 0; + hi = (hi + Math.imul(ah8, bh1)) | 0; + lo = (lo + Math.imul(al7, bl2)) | 0; + mid = (mid + Math.imul(al7, bh2)) | 0; + mid = (mid + Math.imul(ah7, bl2)) | 0; + hi = (hi + Math.imul(ah7, bh2)) | 0; + lo = (lo + Math.imul(al6, bl3)) | 0; + mid = (mid + Math.imul(al6, bh3)) | 0; + mid = (mid + Math.imul(ah6, bl3)) | 0; + hi = (hi + Math.imul(ah6, bh3)) | 0; + lo = (lo + Math.imul(al5, bl4)) | 0; + mid = (mid + Math.imul(al5, bh4)) | 0; + mid = (mid + Math.imul(ah5, bl4)) | 0; + hi = (hi + Math.imul(ah5, bh4)) | 0; + lo = (lo + Math.imul(al4, bl5)) | 0; + mid = (mid + Math.imul(al4, bh5)) | 0; + mid = (mid + Math.imul(ah4, bl5)) | 0; + hi = (hi + Math.imul(ah4, bh5)) | 0; + lo = (lo + Math.imul(al3, bl6)) | 0; + mid = (mid + Math.imul(al3, bh6)) | 0; + mid = (mid + Math.imul(ah3, bl6)) | 0; + hi = (hi + Math.imul(ah3, bh6)) | 0; + lo = (lo + Math.imul(al2, bl7)) | 0; + mid = (mid + Math.imul(al2, bh7)) | 0; + mid = (mid + Math.imul(ah2, bl7)) | 0; + hi = (hi + Math.imul(ah2, bh7)) | 0; + lo = (lo + Math.imul(al1, bl8)) | 0; + mid = (mid + Math.imul(al1, bh8)) | 0; + mid = (mid + Math.imul(ah1, bl8)) | 0; + hi = (hi + Math.imul(ah1, bh8)) | 0; + lo = (lo + Math.imul(al0, bl9)) | 0; + mid = (mid + Math.imul(al0, bh9)) | 0; + mid = (mid + Math.imul(ah0, bl9)) | 0; + hi = (hi + Math.imul(ah0, bh9)) | 0; + var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w9 >>> 26)) | 0; + w9 &= 0x3ffffff; + /* k = 10 */ + lo = Math.imul(al9, bl1); + mid = Math.imul(al9, bh1); + mid = (mid + Math.imul(ah9, bl1)) | 0; + hi = Math.imul(ah9, bh1); + lo = (lo + Math.imul(al8, bl2)) | 0; + mid = (mid + Math.imul(al8, bh2)) | 0; + mid = (mid + Math.imul(ah8, bl2)) | 0; + hi = (hi + Math.imul(ah8, bh2)) | 0; + lo = (lo + Math.imul(al7, bl3)) | 0; + mid = (mid + Math.imul(al7, bh3)) | 0; + mid = (mid + Math.imul(ah7, bl3)) | 0; + hi = (hi + Math.imul(ah7, bh3)) | 0; + lo = (lo + Math.imul(al6, bl4)) | 0; + mid = (mid + Math.imul(al6, bh4)) | 0; + mid = (mid + Math.imul(ah6, bl4)) | 0; + hi = (hi + Math.imul(ah6, bh4)) | 0; + lo = (lo + Math.imul(al5, bl5)) | 0; + mid = (mid + Math.imul(al5, bh5)) | 0; + mid = (mid + Math.imul(ah5, bl5)) | 0; + hi = (hi + Math.imul(ah5, bh5)) | 0; + lo = (lo + Math.imul(al4, bl6)) | 0; + mid = (mid + Math.imul(al4, bh6)) | 0; + mid = (mid + Math.imul(ah4, bl6)) | 0; + hi = (hi + Math.imul(ah4, bh6)) | 0; + lo = (lo + Math.imul(al3, bl7)) | 0; + mid = (mid + Math.imul(al3, bh7)) | 0; + mid = (mid + Math.imul(ah3, bl7)) | 0; + hi = (hi + Math.imul(ah3, bh7)) | 0; + lo = (lo + Math.imul(al2, bl8)) | 0; + mid = (mid + Math.imul(al2, bh8)) | 0; + mid = (mid + Math.imul(ah2, bl8)) | 0; + hi = (hi + Math.imul(ah2, bh8)) | 0; + lo = (lo + Math.imul(al1, bl9)) | 0; + mid = (mid + Math.imul(al1, bh9)) | 0; + mid = (mid + Math.imul(ah1, bl9)) | 0; + hi = (hi + Math.imul(ah1, bh9)) | 0; + var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w10 >>> 26)) | 0; + w10 &= 0x3ffffff; + /* k = 11 */ + lo = Math.imul(al9, bl2); + mid = Math.imul(al9, bh2); + mid = (mid + Math.imul(ah9, bl2)) | 0; + hi = Math.imul(ah9, bh2); + lo = (lo + Math.imul(al8, bl3)) | 0; + mid = (mid + Math.imul(al8, bh3)) | 0; + mid = (mid + Math.imul(ah8, bl3)) | 0; + hi = (hi + Math.imul(ah8, bh3)) | 0; + lo = (lo + Math.imul(al7, bl4)) | 0; + mid = (mid + Math.imul(al7, bh4)) | 0; + mid = (mid + Math.imul(ah7, bl4)) | 0; + hi = (hi + Math.imul(ah7, bh4)) | 0; + lo = (lo + Math.imul(al6, bl5)) | 0; + mid = (mid + Math.imul(al6, bh5)) | 0; + mid = (mid + Math.imul(ah6, bl5)) | 0; + hi = (hi + Math.imul(ah6, bh5)) | 0; + lo = (lo + Math.imul(al5, bl6)) | 0; + mid = (mid + Math.imul(al5, bh6)) | 0; + mid = (mid + Math.imul(ah5, bl6)) | 0; + hi = (hi + Math.imul(ah5, bh6)) | 0; + lo = (lo + Math.imul(al4, bl7)) | 0; + mid = (mid + Math.imul(al4, bh7)) | 0; + mid = (mid + Math.imul(ah4, bl7)) | 0; + hi = (hi + Math.imul(ah4, bh7)) | 0; + lo = (lo + Math.imul(al3, bl8)) | 0; + mid = (mid + Math.imul(al3, bh8)) | 0; + mid = (mid + Math.imul(ah3, bl8)) | 0; + hi = (hi + Math.imul(ah3, bh8)) | 0; + lo = (lo + Math.imul(al2, bl9)) | 0; + mid = (mid + Math.imul(al2, bh9)) | 0; + mid = (mid + Math.imul(ah2, bl9)) | 0; + hi = (hi + Math.imul(ah2, bh9)) | 0; + var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w11 >>> 26)) | 0; + w11 &= 0x3ffffff; + /* k = 12 */ + lo = Math.imul(al9, bl3); + mid = Math.imul(al9, bh3); + mid = (mid + Math.imul(ah9, bl3)) | 0; + hi = Math.imul(ah9, bh3); + lo = (lo + Math.imul(al8, bl4)) | 0; + mid = (mid + Math.imul(al8, bh4)) | 0; + mid = (mid + Math.imul(ah8, bl4)) | 0; + hi = (hi + Math.imul(ah8, bh4)) | 0; + lo = (lo + Math.imul(al7, bl5)) | 0; + mid = (mid + Math.imul(al7, bh5)) | 0; + mid = (mid + Math.imul(ah7, bl5)) | 0; + hi = (hi + Math.imul(ah7, bh5)) | 0; + lo = (lo + Math.imul(al6, bl6)) | 0; + mid = (mid + Math.imul(al6, bh6)) | 0; + mid = (mid + Math.imul(ah6, bl6)) | 0; + hi = (hi + Math.imul(ah6, bh6)) | 0; + lo = (lo + Math.imul(al5, bl7)) | 0; + mid = (mid + Math.imul(al5, bh7)) | 0; + mid = (mid + Math.imul(ah5, bl7)) | 0; + hi = (hi + Math.imul(ah5, bh7)) | 0; + lo = (lo + Math.imul(al4, bl8)) | 0; + mid = (mid + Math.imul(al4, bh8)) | 0; + mid = (mid + Math.imul(ah4, bl8)) | 0; + hi = (hi + Math.imul(ah4, bh8)) | 0; + lo = (lo + Math.imul(al3, bl9)) | 0; + mid = (mid + Math.imul(al3, bh9)) | 0; + mid = (mid + Math.imul(ah3, bl9)) | 0; + hi = (hi + Math.imul(ah3, bh9)) | 0; + var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w12 >>> 26)) | 0; + w12 &= 0x3ffffff; + /* k = 13 */ + lo = Math.imul(al9, bl4); + mid = Math.imul(al9, bh4); + mid = (mid + Math.imul(ah9, bl4)) | 0; + hi = Math.imul(ah9, bh4); + lo = (lo + Math.imul(al8, bl5)) | 0; + mid = (mid + Math.imul(al8, bh5)) | 0; + mid = (mid + Math.imul(ah8, bl5)) | 0; + hi = (hi + Math.imul(ah8, bh5)) | 0; + lo = (lo + Math.imul(al7, bl6)) | 0; + mid = (mid + Math.imul(al7, bh6)) | 0; + mid = (mid + Math.imul(ah7, bl6)) | 0; + hi = (hi + Math.imul(ah7, bh6)) | 0; + lo = (lo + Math.imul(al6, bl7)) | 0; + mid = (mid + Math.imul(al6, bh7)) | 0; + mid = (mid + Math.imul(ah6, bl7)) | 0; + hi = (hi + Math.imul(ah6, bh7)) | 0; + lo = (lo + Math.imul(al5, bl8)) | 0; + mid = (mid + Math.imul(al5, bh8)) | 0; + mid = (mid + Math.imul(ah5, bl8)) | 0; + hi = (hi + Math.imul(ah5, bh8)) | 0; + lo = (lo + Math.imul(al4, bl9)) | 0; + mid = (mid + Math.imul(al4, bh9)) | 0; + mid = (mid + Math.imul(ah4, bl9)) | 0; + hi = (hi + Math.imul(ah4, bh9)) | 0; + var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w13 >>> 26)) | 0; + w13 &= 0x3ffffff; + /* k = 14 */ + lo = Math.imul(al9, bl5); + mid = Math.imul(al9, bh5); + mid = (mid + Math.imul(ah9, bl5)) | 0; + hi = Math.imul(ah9, bh5); + lo = (lo + Math.imul(al8, bl6)) | 0; + mid = (mid + Math.imul(al8, bh6)) | 0; + mid = (mid + Math.imul(ah8, bl6)) | 0; + hi = (hi + Math.imul(ah8, bh6)) | 0; + lo = (lo + Math.imul(al7, bl7)) | 0; + mid = (mid + Math.imul(al7, bh7)) | 0; + mid = (mid + Math.imul(ah7, bl7)) | 0; + hi = (hi + Math.imul(ah7, bh7)) | 0; + lo = (lo + Math.imul(al6, bl8)) | 0; + mid = (mid + Math.imul(al6, bh8)) | 0; + mid = (mid + Math.imul(ah6, bl8)) | 0; + hi = (hi + Math.imul(ah6, bh8)) | 0; + lo = (lo + Math.imul(al5, bl9)) | 0; + mid = (mid + Math.imul(al5, bh9)) | 0; + mid = (mid + Math.imul(ah5, bl9)) | 0; + hi = (hi + Math.imul(ah5, bh9)) | 0; + var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w14 >>> 26)) | 0; + w14 &= 0x3ffffff; + /* k = 15 */ + lo = Math.imul(al9, bl6); + mid = Math.imul(al9, bh6); + mid = (mid + Math.imul(ah9, bl6)) | 0; + hi = Math.imul(ah9, bh6); + lo = (lo + Math.imul(al8, bl7)) | 0; + mid = (mid + Math.imul(al8, bh7)) | 0; + mid = (mid + Math.imul(ah8, bl7)) | 0; + hi = (hi + Math.imul(ah8, bh7)) | 0; + lo = (lo + Math.imul(al7, bl8)) | 0; + mid = (mid + Math.imul(al7, bh8)) | 0; + mid = (mid + Math.imul(ah7, bl8)) | 0; + hi = (hi + Math.imul(ah7, bh8)) | 0; + lo = (lo + Math.imul(al6, bl9)) | 0; + mid = (mid + Math.imul(al6, bh9)) | 0; + mid = (mid + Math.imul(ah6, bl9)) | 0; + hi = (hi + Math.imul(ah6, bh9)) | 0; + var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w15 >>> 26)) | 0; + w15 &= 0x3ffffff; + /* k = 16 */ + lo = Math.imul(al9, bl7); + mid = Math.imul(al9, bh7); + mid = (mid + Math.imul(ah9, bl7)) | 0; + hi = Math.imul(ah9, bh7); + lo = (lo + Math.imul(al8, bl8)) | 0; + mid = (mid + Math.imul(al8, bh8)) | 0; + mid = (mid + Math.imul(ah8, bl8)) | 0; + hi = (hi + Math.imul(ah8, bh8)) | 0; + lo = (lo + Math.imul(al7, bl9)) | 0; + mid = (mid + Math.imul(al7, bh9)) | 0; + mid = (mid + Math.imul(ah7, bl9)) | 0; + hi = (hi + Math.imul(ah7, bh9)) | 0; + var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w16 >>> 26)) | 0; + w16 &= 0x3ffffff; + /* k = 17 */ + lo = Math.imul(al9, bl8); + mid = Math.imul(al9, bh8); + mid = (mid + Math.imul(ah9, bl8)) | 0; + hi = Math.imul(ah9, bh8); + lo = (lo + Math.imul(al8, bl9)) | 0; + mid = (mid + Math.imul(al8, bh9)) | 0; + mid = (mid + Math.imul(ah8, bl9)) | 0; + hi = (hi + Math.imul(ah8, bh9)) | 0; + var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w17 >>> 26)) | 0; + w17 &= 0x3ffffff; + /* k = 18 */ + lo = Math.imul(al9, bl9); + mid = Math.imul(al9, bh9); + mid = (mid + Math.imul(ah9, bl9)) | 0; + hi = Math.imul(ah9, bh9); + var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w18 >>> 26)) | 0; + w18 &= 0x3ffffff; + o[0] = w0; + o[1] = w1; + o[2] = w2; + o[3] = w3; + o[4] = w4; + o[5] = w5; + o[6] = w6; + o[7] = w7; + o[8] = w8; + o[9] = w9; + o[10] = w10; + o[11] = w11; + o[12] = w12; + o[13] = w13; + o[14] = w14; + o[15] = w15; + o[16] = w16; + o[17] = w17; + o[18] = w18; + if (c !== 0) { + o[19] = c; + out.length++; + } + return out; + }; + + // Polyfill comb + if (!Math.imul) { + comb10MulTo = smallMulTo; + } + + function bigMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + out.length = self.length + num.length; + + var carry = 0; + var hncarry = 0; + for (var k = 0; k < out.length - 1; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = hncarry; + hncarry = 0; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = k - j; + var a = self.words[i] | 0; + var b = num.words[j] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0; + lo = (lo + rword) | 0; + rword = lo & 0x3ffffff; + ncarry = (ncarry + (lo >>> 26)) | 0; + + hncarry += ncarry >>> 26; + ncarry &= 0x3ffffff; + } + out.words[k] = rword; + carry = ncarry; + ncarry = hncarry; + } + if (carry !== 0) { + out.words[k] = carry; + } else { + out.length--; + } + + return out.strip(); + } + + function jumboMulTo (self, num, out) { + var fftm = new FFTM(); + return fftm.mulp(self, num, out); + } + + BN.prototype.mulTo = function mulTo (num, out) { + var res; + var len = this.length + num.length; + if (this.length === 10 && num.length === 10) { + res = comb10MulTo(this, num, out); + } else if (len < 63) { + res = smallMulTo(this, num, out); + } else if (len < 1024) { + res = bigMulTo(this, num, out); + } else { + res = jumboMulTo(this, num, out); + } + + return res; + }; + + // Cooley-Tukey algorithm for FFT + // slightly revisited to rely on looping instead of recursion + + function FFTM (x, y) { + this.x = x; + this.y = y; + } + + FFTM.prototype.makeRBT = function makeRBT (N) { + var t = new Array(N); + var l = BN.prototype._countBits(N) - 1; + for (var i = 0; i < N; i++) { + t[i] = this.revBin(i, l, N); + } + + return t; + }; + + // Returns binary-reversed representation of `x` + FFTM.prototype.revBin = function revBin (x, l, N) { + if (x === 0 || x === N - 1) return x; + + var rb = 0; + for (var i = 0; i < l; i++) { + rb |= (x & 1) << (l - i - 1); + x >>= 1; + } + + return rb; + }; + + // Performs "tweedling" phase, therefore 'emulating' + // behaviour of the recursive algorithm + FFTM.prototype.permute = function permute (rbt, rws, iws, rtws, itws, N) { + for (var i = 0; i < N; i++) { + rtws[i] = rws[rbt[i]]; + itws[i] = iws[rbt[i]]; + } + }; + + FFTM.prototype.transform = function transform (rws, iws, rtws, itws, N, rbt) { + this.permute(rbt, rws, iws, rtws, itws, N); + + for (var s = 1; s < N; s <<= 1) { + var l = s << 1; + + var rtwdf = Math.cos(2 * Math.PI / l); + var itwdf = Math.sin(2 * Math.PI / l); + + for (var p = 0; p < N; p += l) { + var rtwdf_ = rtwdf; + var itwdf_ = itwdf; + + for (var j = 0; j < s; j++) { + var re = rtws[p + j]; + var ie = itws[p + j]; + + var ro = rtws[p + j + s]; + var io = itws[p + j + s]; + + var rx = rtwdf_ * ro - itwdf_ * io; + + io = rtwdf_ * io + itwdf_ * ro; + ro = rx; + + rtws[p + j] = re + ro; + itws[p + j] = ie + io; + + rtws[p + j + s] = re - ro; + itws[p + j + s] = ie - io; + + /* jshint maxdepth : false */ + if (j !== l) { + rx = rtwdf * rtwdf_ - itwdf * itwdf_; + + itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_; + rtwdf_ = rx; + } + } + } + } + }; + + FFTM.prototype.guessLen13b = function guessLen13b (n, m) { + var N = Math.max(m, n) | 1; + var odd = N & 1; + var i = 0; + for (N = N / 2 | 0; N; N = N >>> 1) { + i++; + } + + return 1 << i + 1 + odd; + }; + + FFTM.prototype.conjugate = function conjugate (rws, iws, N) { + if (N <= 1) return; + + for (var i = 0; i < N / 2; i++) { + var t = rws[i]; + + rws[i] = rws[N - i - 1]; + rws[N - i - 1] = t; + + t = iws[i]; + + iws[i] = -iws[N - i - 1]; + iws[N - i - 1] = -t; + } + }; + + FFTM.prototype.normalize13b = function normalize13b (ws, N) { + var carry = 0; + for (var i = 0; i < N / 2; i++) { + var w = Math.round(ws[2 * i + 1] / N) * 0x2000 + + Math.round(ws[2 * i] / N) + + carry; + + ws[i] = w & 0x3ffffff; + + if (w < 0x4000000) { + carry = 0; + } else { + carry = w / 0x4000000 | 0; + } + } + + return ws; + }; + + FFTM.prototype.convert13b = function convert13b (ws, len, rws, N) { + var carry = 0; + for (var i = 0; i < len; i++) { + carry = carry + (ws[i] | 0); + + rws[2 * i] = carry & 0x1fff; carry = carry >>> 13; + rws[2 * i + 1] = carry & 0x1fff; carry = carry >>> 13; + } + + // Pad with zeroes + for (i = 2 * len; i < N; ++i) { + rws[i] = 0; + } + + assert(carry === 0); + assert((carry & ~0x1fff) === 0); + }; + + FFTM.prototype.stub = function stub (N) { + var ph = new Array(N); + for (var i = 0; i < N; i++) { + ph[i] = 0; + } + + return ph; + }; + + FFTM.prototype.mulp = function mulp (x, y, out) { + var N = 2 * this.guessLen13b(x.length, y.length); + + var rbt = this.makeRBT(N); + + var _ = this.stub(N); + + var rws = new Array(N); + var rwst = new Array(N); + var iwst = new Array(N); + + var nrws = new Array(N); + var nrwst = new Array(N); + var niwst = new Array(N); + + var rmws = out.words; + rmws.length = N; + + this.convert13b(x.words, x.length, rws, N); + this.convert13b(y.words, y.length, nrws, N); + + this.transform(rws, _, rwst, iwst, N, rbt); + this.transform(nrws, _, nrwst, niwst, N, rbt); + + for (var i = 0; i < N; i++) { + var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i]; + iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i]; + rwst[i] = rx; + } + + this.conjugate(rwst, iwst, N); + this.transform(rwst, iwst, rmws, _, N, rbt); + this.conjugate(rmws, _, N); + this.normalize13b(rmws, N); + + out.negative = x.negative ^ y.negative; + out.length = x.length + y.length; + return out.strip(); + }; + + // Multiply `this` by `num` + BN.prototype.mul = function mul (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return this.mulTo(num, out); + }; + + // Multiply employing FFT + BN.prototype.mulf = function mulf (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return jumboMulTo(this, num, out); + }; + + // In-place Multiplication + BN.prototype.imul = function imul (num) { + return this.clone().mulTo(num, this); + }; + + BN.prototype.imuln = function imuln (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + + // Carry + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = (this.words[i] | 0) * num; + var lo = (w & 0x3ffffff) + (carry & 0x3ffffff); + carry >>= 26; + carry += (w / 0x4000000) | 0; + // NOTE: lo is 27bit maximum + carry += lo >>> 26; + this.words[i] = lo & 0x3ffffff; + } + + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + + return this; + }; + + BN.prototype.muln = function muln (num) { + return this.clone().imuln(num); + }; + + // `this` * `this` + BN.prototype.sqr = function sqr () { + return this.mul(this); + }; + + // `this` * `this` in-place + BN.prototype.isqr = function isqr () { + return this.imul(this.clone()); + }; + + // Math.pow(`this`, `num`) + BN.prototype.pow = function pow (num) { + var w = toBitArray(num); + if (w.length === 0) return new BN(1); + + // Skip leading zeroes + var res = this; + for (var i = 0; i < w.length; i++, res = res.sqr()) { + if (w[i] !== 0) break; + } + + if (++i < w.length) { + for (var q = res.sqr(); i < w.length; i++, q = q.sqr()) { + if (w[i] === 0) continue; + + res = res.mul(q); + } + } + + return res; + }; + + // Shift-left in-place + BN.prototype.iushln = function iushln (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + var carryMask = (0x3ffffff >>> (26 - r)) << (26 - r); + var i; + + if (r !== 0) { + var carry = 0; + + for (i = 0; i < this.length; i++) { + var newCarry = this.words[i] & carryMask; + var c = ((this.words[i] | 0) - newCarry) << r; + this.words[i] = c | carry; + carry = newCarry >>> (26 - r); + } + + if (carry) { + this.words[i] = carry; + this.length++; + } + } + + if (s !== 0) { + for (i = this.length - 1; i >= 0; i--) { + this.words[i + s] = this.words[i]; + } + + for (i = 0; i < s; i++) { + this.words[i] = 0; + } + + this.length += s; + } + + return this.strip(); + }; + + BN.prototype.ishln = function ishln (bits) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushln(bits); + }; + + // Shift-right in-place + // NOTE: `hint` is a lowest bit before trailing zeroes + // NOTE: if `extended` is present - it will be filled with destroyed bits + BN.prototype.iushrn = function iushrn (bits, hint, extended) { + assert(typeof bits === 'number' && bits >= 0); + var h; + if (hint) { + h = (hint - (hint % 26)) / 26; + } else { + h = 0; + } + + var r = bits % 26; + var s = Math.min((bits - r) / 26, this.length); + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + var maskedWords = extended; + + h -= s; + h = Math.max(0, h); + + // Extended mode, copy masked part + if (maskedWords) { + for (var i = 0; i < s; i++) { + maskedWords.words[i] = this.words[i]; + } + maskedWords.length = s; + } + + if (s === 0) { + // No-op, we should not move anything at all + } else if (this.length > s) { + this.length -= s; + for (i = 0; i < this.length; i++) { + this.words[i] = this.words[i + s]; + } + } else { + this.words[0] = 0; + this.length = 1; + } + + var carry = 0; + for (i = this.length - 1; i >= 0 && (carry !== 0 || i >= h); i--) { + var word = this.words[i] | 0; + this.words[i] = (carry << (26 - r)) | (word >>> r); + carry = word & mask; + } + + // Push carried bits as a mask + if (maskedWords && carry !== 0) { + maskedWords.words[maskedWords.length++] = carry; + } + + if (this.length === 0) { + this.words[0] = 0; + this.length = 1; + } + + return this.strip(); + }; + + BN.prototype.ishrn = function ishrn (bits, hint, extended) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushrn(bits, hint, extended); + }; + + // Shift-left + BN.prototype.shln = function shln (bits) { + return this.clone().ishln(bits); + }; + + BN.prototype.ushln = function ushln (bits) { + return this.clone().iushln(bits); + }; + + // Shift-right + BN.prototype.shrn = function shrn (bits) { + return this.clone().ishrn(bits); + }; + + BN.prototype.ushrn = function ushrn (bits) { + return this.clone().iushrn(bits); + }; + + // Test if n bit is set + BN.prototype.testn = function testn (bit) { + assert(typeof bit === 'number' && bit >= 0); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) return false; + + // Check bit and return + var w = this.words[s]; + + return !!(w & q); + }; + + // Return only lowers bits of number (in-place) + BN.prototype.imaskn = function imaskn (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + + assert(this.negative === 0, 'imaskn works only with positive numbers'); + + if (this.length <= s) { + return this; + } + + if (r !== 0) { + s++; + } + this.length = Math.min(s, this.length); + + if (r !== 0) { + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + this.words[this.length - 1] &= mask; + } + + return this.strip(); + }; + + // Return only lowers bits of number + BN.prototype.maskn = function maskn (bits) { + return this.clone().imaskn(bits); + }; + + // Add plain number `num` to `this` + BN.prototype.iaddn = function iaddn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.isubn(-num); + + // Possible sign change + if (this.negative !== 0) { + if (this.length === 1 && (this.words[0] | 0) < num) { + this.words[0] = num - (this.words[0] | 0); + this.negative = 0; + return this; + } + + this.negative = 0; + this.isubn(num); + this.negative = 1; + return this; + } + + // Add without checks + return this._iaddn(num); + }; + + BN.prototype._iaddn = function _iaddn (num) { + this.words[0] += num; + + // Carry + for (var i = 0; i < this.length && this.words[i] >= 0x4000000; i++) { + this.words[i] -= 0x4000000; + if (i === this.length - 1) { + this.words[i + 1] = 1; + } else { + this.words[i + 1]++; + } + } + this.length = Math.max(this.length, i + 1); + + return this; + }; + + // Subtract plain number `num` from `this` + BN.prototype.isubn = function isubn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.iaddn(-num); + + if (this.negative !== 0) { + this.negative = 0; + this.iaddn(num); + this.negative = 1; + return this; + } + + this.words[0] -= num; + + if (this.length === 1 && this.words[0] < 0) { + this.words[0] = -this.words[0]; + this.negative = 1; + } else { + // Carry + for (var i = 0; i < this.length && this.words[i] < 0; i++) { + this.words[i] += 0x4000000; + this.words[i + 1] -= 1; + } + } + + return this.strip(); + }; + + BN.prototype.addn = function addn (num) { + return this.clone().iaddn(num); + }; + + BN.prototype.subn = function subn (num) { + return this.clone().isubn(num); + }; + + BN.prototype.iabs = function iabs () { + this.negative = 0; + + return this; + }; + + BN.prototype.abs = function abs () { + return this.clone().iabs(); + }; + + BN.prototype._ishlnsubmul = function _ishlnsubmul (num, mul, shift) { + var len = num.length + shift; + var i; + + this._expand(len); + + var w; + var carry = 0; + for (i = 0; i < num.length; i++) { + w = (this.words[i + shift] | 0) + carry; + var right = (num.words[i] | 0) * mul; + w -= right & 0x3ffffff; + carry = (w >> 26) - ((right / 0x4000000) | 0); + this.words[i + shift] = w & 0x3ffffff; + } + for (; i < this.length - shift; i++) { + w = (this.words[i + shift] | 0) + carry; + carry = w >> 26; + this.words[i + shift] = w & 0x3ffffff; + } + + if (carry === 0) return this.strip(); + + // Subtraction overflow + assert(carry === -1); + carry = 0; + for (i = 0; i < this.length; i++) { + w = -(this.words[i] | 0) + carry; + carry = w >> 26; + this.words[i] = w & 0x3ffffff; + } + this.negative = 1; + + return this.strip(); + }; + + BN.prototype._wordDiv = function _wordDiv (num, mode) { + var shift = this.length - num.length; + + var a = this.clone(); + var b = num; + + // Normalize + var bhi = b.words[b.length - 1] | 0; + var bhiBits = this._countBits(bhi); + shift = 26 - bhiBits; + if (shift !== 0) { + b = b.ushln(shift); + a.iushln(shift); + bhi = b.words[b.length - 1] | 0; + } + + // Initialize quotient + var m = a.length - b.length; + var q; + + if (mode !== 'mod') { + q = new BN(null); + q.length = m + 1; + q.words = new Array(q.length); + for (var i = 0; i < q.length; i++) { + q.words[i] = 0; + } + } + + var diff = a.clone()._ishlnsubmul(b, 1, m); + if (diff.negative === 0) { + a = diff; + if (q) { + q.words[m] = 1; + } + } + + for (var j = m - 1; j >= 0; j--) { + var qj = (a.words[b.length + j] | 0) * 0x4000000 + + (a.words[b.length + j - 1] | 0); + + // NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max + // (0x7ffffff) + qj = Math.min((qj / bhi) | 0, 0x3ffffff); + + a._ishlnsubmul(b, qj, j); + while (a.negative !== 0) { + qj--; + a.negative = 0; + a._ishlnsubmul(b, 1, j); + if (!a.isZero()) { + a.negative ^= 1; + } + } + if (q) { + q.words[j] = qj; + } + } + if (q) { + q.strip(); + } + a.strip(); + + // Denormalize + if (mode !== 'div' && shift !== 0) { + a.iushrn(shift); + } + + return { + div: q || null, + mod: a + }; + }; + + // NOTE: 1) `mode` can be set to `mod` to request mod only, + // to `div` to request div only, or be absent to + // request both div & mod + // 2) `positive` is true if unsigned mod is requested + BN.prototype.divmod = function divmod (num, mode, positive) { + assert(!num.isZero()); + + if (this.isZero()) { + return { + div: new BN(0), + mod: new BN(0) + }; + } + + var div, mod, res; + if (this.negative !== 0 && num.negative === 0) { + res = this.neg().divmod(num, mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.iadd(num); + } + } + + return { + div: div, + mod: mod + }; + } + + if (this.negative === 0 && num.negative !== 0) { + res = this.divmod(num.neg(), mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + return { + div: div, + mod: res.mod + }; + } + + if ((this.negative & num.negative) !== 0) { + res = this.neg().divmod(num.neg(), mode); + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.isub(num); + } + } + + return { + div: res.div, + mod: mod + }; + } + + // Both numbers are positive at this point + + // Strip both numbers to approximate shift value + if (num.length > this.length || this.cmp(num) < 0) { + return { + div: new BN(0), + mod: this + }; + } + + // Very short reduction + if (num.length === 1) { + if (mode === 'div') { + return { + div: this.divn(num.words[0]), + mod: null + }; + } + + if (mode === 'mod') { + return { + div: null, + mod: new BN(this.modn(num.words[0])) + }; + } + + return { + div: this.divn(num.words[0]), + mod: new BN(this.modn(num.words[0])) + }; + } + + return this._wordDiv(num, mode); + }; + + // Find `this` / `num` + BN.prototype.div = function div (num) { + return this.divmod(num, 'div', false).div; + }; + + // Find `this` % `num` + BN.prototype.mod = function mod (num) { + return this.divmod(num, 'mod', false).mod; + }; + + BN.prototype.umod = function umod (num) { + return this.divmod(num, 'mod', true).mod; + }; + + // Find Round(`this` / `num`) + BN.prototype.divRound = function divRound (num) { + var dm = this.divmod(num); + + // Fast case - exact division + if (dm.mod.isZero()) return dm.div; + + var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod; + + var half = num.ushrn(1); + var r2 = num.andln(1); + var cmp = mod.cmp(half); + + // Round down + if (cmp < 0 || r2 === 1 && cmp === 0) return dm.div; + + // Round up + return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1); + }; + + BN.prototype.modn = function modn (num) { + assert(num <= 0x3ffffff); + var p = (1 << 26) % num; + + var acc = 0; + for (var i = this.length - 1; i >= 0; i--) { + acc = (p * acc + (this.words[i] | 0)) % num; + } + + return acc; + }; + + // In-place division by number + BN.prototype.idivn = function idivn (num) { + assert(num <= 0x3ffffff); + + var carry = 0; + for (var i = this.length - 1; i >= 0; i--) { + var w = (this.words[i] | 0) + carry * 0x4000000; + this.words[i] = (w / num) | 0; + carry = w % num; + } + + return this.strip(); + }; + + BN.prototype.divn = function divn (num) { + return this.clone().idivn(num); + }; + + BN.prototype.egcd = function egcd (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var x = this; + var y = p.clone(); + + if (x.negative !== 0) { + x = x.umod(p); + } else { + x = x.clone(); + } + + // A * x + B * y = x + var A = new BN(1); + var B = new BN(0); + + // C * x + D * y = y + var C = new BN(0); + var D = new BN(1); + + var g = 0; + + while (x.isEven() && y.isEven()) { + x.iushrn(1); + y.iushrn(1); + ++g; + } + + var yp = y.clone(); + var xp = x.clone(); + + while (!x.isZero()) { + for (var i = 0, im = 1; (x.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + x.iushrn(i); + while (i-- > 0) { + if (A.isOdd() || B.isOdd()) { + A.iadd(yp); + B.isub(xp); + } + + A.iushrn(1); + B.iushrn(1); + } + } + + for (var j = 0, jm = 1; (y.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + y.iushrn(j); + while (j-- > 0) { + if (C.isOdd() || D.isOdd()) { + C.iadd(yp); + D.isub(xp); + } + + C.iushrn(1); + D.iushrn(1); + } + } + + if (x.cmp(y) >= 0) { + x.isub(y); + A.isub(C); + B.isub(D); + } else { + y.isub(x); + C.isub(A); + D.isub(B); + } + } + + return { + a: C, + b: D, + gcd: y.iushln(g) + }; + }; + + // This is reduced incarnation of the binary EEA + // above, designated to invert members of the + // _prime_ fields F(p) at a maximal speed + BN.prototype._invmp = function _invmp (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var a = this; + var b = p.clone(); + + if (a.negative !== 0) { + a = a.umod(p); + } else { + a = a.clone(); + } + + var x1 = new BN(1); + var x2 = new BN(0); + + var delta = b.clone(); + + while (a.cmpn(1) > 0 && b.cmpn(1) > 0) { + for (var i = 0, im = 1; (a.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + a.iushrn(i); + while (i-- > 0) { + if (x1.isOdd()) { + x1.iadd(delta); + } + + x1.iushrn(1); + } + } + + for (var j = 0, jm = 1; (b.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + b.iushrn(j); + while (j-- > 0) { + if (x2.isOdd()) { + x2.iadd(delta); + } + + x2.iushrn(1); + } + } + + if (a.cmp(b) >= 0) { + a.isub(b); + x1.isub(x2); + } else { + b.isub(a); + x2.isub(x1); + } + } + + var res; + if (a.cmpn(1) === 0) { + res = x1; + } else { + res = x2; + } + + if (res.cmpn(0) < 0) { + res.iadd(p); + } + + return res; + }; + + BN.prototype.gcd = function gcd (num) { + if (this.isZero()) return num.abs(); + if (num.isZero()) return this.abs(); + + var a = this.clone(); + var b = num.clone(); + a.negative = 0; + b.negative = 0; + + // Remove common factor of two + for (var shift = 0; a.isEven() && b.isEven(); shift++) { + a.iushrn(1); + b.iushrn(1); + } + + do { + while (a.isEven()) { + a.iushrn(1); + } + while (b.isEven()) { + b.iushrn(1); + } + + var r = a.cmp(b); + if (r < 0) { + // Swap `a` and `b` to make `a` always bigger than `b` + var t = a; + a = b; + b = t; + } else if (r === 0 || b.cmpn(1) === 0) { + break; + } + + a.isub(b); + } while (true); + + return b.iushln(shift); + }; + + // Invert number in the field F(num) + BN.prototype.invm = function invm (num) { + return this.egcd(num).a.umod(num); + }; + + BN.prototype.isEven = function isEven () { + return (this.words[0] & 1) === 0; + }; + + BN.prototype.isOdd = function isOdd () { + return (this.words[0] & 1) === 1; + }; + + // And first word and num + BN.prototype.andln = function andln (num) { + return this.words[0] & num; + }; + + // Increment at the bit position in-line + BN.prototype.bincn = function bincn (bit) { + assert(typeof bit === 'number'); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) { + this._expand(s + 1); + this.words[s] |= q; + return this; + } + + // Add bit and propagate, if needed + var carry = q; + for (var i = s; carry !== 0 && i < this.length; i++) { + var w = this.words[i] | 0; + w += carry; + carry = w >>> 26; + w &= 0x3ffffff; + this.words[i] = w; + } + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + return this; + }; + + BN.prototype.isZero = function isZero () { + return this.length === 1 && this.words[0] === 0; + }; + + BN.prototype.cmpn = function cmpn (num) { + var negative = num < 0; + + if (this.negative !== 0 && !negative) return -1; + if (this.negative === 0 && negative) return 1; + + this.strip(); + + var res; + if (this.length > 1) { + res = 1; + } else { + if (negative) { + num = -num; + } + + assert(num <= 0x3ffffff, 'Number is too big'); + + var w = this.words[0] | 0; + res = w === num ? 0 : w < num ? -1 : 1; + } + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Compare two numbers and return: + // 1 - if `this` > `num` + // 0 - if `this` == `num` + // -1 - if `this` < `num` + BN.prototype.cmp = function cmp (num) { + if (this.negative !== 0 && num.negative === 0) return -1; + if (this.negative === 0 && num.negative !== 0) return 1; + + var res = this.ucmp(num); + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Unsigned comparison + BN.prototype.ucmp = function ucmp (num) { + // At this point both numbers have the same sign + if (this.length > num.length) return 1; + if (this.length < num.length) return -1; + + var res = 0; + for (var i = this.length - 1; i >= 0; i--) { + var a = this.words[i] | 0; + var b = num.words[i] | 0; + + if (a === b) continue; + if (a < b) { + res = -1; + } else if (a > b) { + res = 1; + } + break; + } + return res; + }; + + BN.prototype.gtn = function gtn (num) { + return this.cmpn(num) === 1; + }; + + BN.prototype.gt = function gt (num) { + return this.cmp(num) === 1; + }; + + BN.prototype.gten = function gten (num) { + return this.cmpn(num) >= 0; + }; + + BN.prototype.gte = function gte (num) { + return this.cmp(num) >= 0; + }; + + BN.prototype.ltn = function ltn (num) { + return this.cmpn(num) === -1; + }; + + BN.prototype.lt = function lt (num) { + return this.cmp(num) === -1; + }; + + BN.prototype.lten = function lten (num) { + return this.cmpn(num) <= 0; + }; + + BN.prototype.lte = function lte (num) { + return this.cmp(num) <= 0; + }; + + BN.prototype.eqn = function eqn (num) { + return this.cmpn(num) === 0; + }; + + BN.prototype.eq = function eq (num) { + return this.cmp(num) === 0; + }; + + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + BN.red = function red (num) { + return new Red(num); + }; + + BN.prototype.toRed = function toRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + assert(this.negative === 0, 'red works only with positives'); + return ctx.convertTo(this)._forceRed(ctx); + }; + + BN.prototype.fromRed = function fromRed () { + assert(this.red, 'fromRed works only with numbers in reduction context'); + return this.red.convertFrom(this); + }; + + BN.prototype._forceRed = function _forceRed (ctx) { + this.red = ctx; + return this; + }; + + BN.prototype.forceRed = function forceRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + return this._forceRed(ctx); + }; + + BN.prototype.redAdd = function redAdd (num) { + assert(this.red, 'redAdd works only with red numbers'); + return this.red.add(this, num); + }; + + BN.prototype.redIAdd = function redIAdd (num) { + assert(this.red, 'redIAdd works only with red numbers'); + return this.red.iadd(this, num); + }; + + BN.prototype.redSub = function redSub (num) { + assert(this.red, 'redSub works only with red numbers'); + return this.red.sub(this, num); + }; + + BN.prototype.redISub = function redISub (num) { + assert(this.red, 'redISub works only with red numbers'); + return this.red.isub(this, num); + }; + + BN.prototype.redShl = function redShl (num) { + assert(this.red, 'redShl works only with red numbers'); + return this.red.shl(this, num); + }; + + BN.prototype.redMul = function redMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.mul(this, num); + }; + + BN.prototype.redIMul = function redIMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.imul(this, num); + }; + + BN.prototype.redSqr = function redSqr () { + assert(this.red, 'redSqr works only with red numbers'); + this.red._verify1(this); + return this.red.sqr(this); + }; + + BN.prototype.redISqr = function redISqr () { + assert(this.red, 'redISqr works only with red numbers'); + this.red._verify1(this); + return this.red.isqr(this); + }; + + // Square root over p + BN.prototype.redSqrt = function redSqrt () { + assert(this.red, 'redSqrt works only with red numbers'); + this.red._verify1(this); + return this.red.sqrt(this); + }; + + BN.prototype.redInvm = function redInvm () { + assert(this.red, 'redInvm works only with red numbers'); + this.red._verify1(this); + return this.red.invm(this); + }; + + // Return negative clone of `this` % `red modulo` + BN.prototype.redNeg = function redNeg () { + assert(this.red, 'redNeg works only with red numbers'); + this.red._verify1(this); + return this.red.neg(this); + }; + + BN.prototype.redPow = function redPow (num) { + assert(this.red && !num.red, 'redPow(normalNum)'); + this.red._verify1(this); + return this.red.pow(this, num); + }; + + // Prime numbers with efficient reduction + var primes = { + k256: null, + p224: null, + p192: null, + p25519: null + }; + + // Pseudo-Mersenne prime + function MPrime (name, p) { + // P = 2 ^ N - K + this.name = name; + this.p = new BN(p, 16); + this.n = this.p.bitLength(); + this.k = new BN(1).iushln(this.n).isub(this.p); + + this.tmp = this._tmp(); + } + + MPrime.prototype._tmp = function _tmp () { + var tmp = new BN(null); + tmp.words = new Array(Math.ceil(this.n / 13)); + return tmp; + }; + + MPrime.prototype.ireduce = function ireduce (num) { + // Assumes that `num` is less than `P^2` + // num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P) + var r = num; + var rlen; + + do { + this.split(r, this.tmp); + r = this.imulK(r); + r = r.iadd(this.tmp); + rlen = r.bitLength(); + } while (rlen > this.n); + + var cmp = rlen < this.n ? -1 : r.ucmp(this.p); + if (cmp === 0) { + r.words[0] = 0; + r.length = 1; + } else if (cmp > 0) { + r.isub(this.p); + } else { + if (r.strip !== undefined) { + // r is BN v4 instance + r.strip(); + } else { + // r is BN v5 instance + r._strip(); + } + } + + return r; + }; + + MPrime.prototype.split = function split (input, out) { + input.iushrn(this.n, 0, out); + }; + + MPrime.prototype.imulK = function imulK (num) { + return num.imul(this.k); + }; + + function K256 () { + MPrime.call( + this, + 'k256', + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f'); + } + inherits(K256, MPrime); + + K256.prototype.split = function split (input, output) { + // 256 = 9 * 26 + 22 + var mask = 0x3fffff; + + var outLen = Math.min(input.length, 9); + for (var i = 0; i < outLen; i++) { + output.words[i] = input.words[i]; + } + output.length = outLen; + + if (input.length <= 9) { + input.words[0] = 0; + input.length = 1; + return; + } + + // Shift by 9 limbs + var prev = input.words[9]; + output.words[output.length++] = prev & mask; + + for (i = 10; i < input.length; i++) { + var next = input.words[i] | 0; + input.words[i - 10] = ((next & mask) << 4) | (prev >>> 22); + prev = next; + } + prev >>>= 22; + input.words[i - 10] = prev; + if (prev === 0 && input.length > 10) { + input.length -= 10; + } else { + input.length -= 9; + } + }; + + K256.prototype.imulK = function imulK (num) { + // K = 0x1000003d1 = [ 0x40, 0x3d1 ] + num.words[num.length] = 0; + num.words[num.length + 1] = 0; + num.length += 2; + + // bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390 + var lo = 0; + for (var i = 0; i < num.length; i++) { + var w = num.words[i] | 0; + lo += w * 0x3d1; + num.words[i] = lo & 0x3ffffff; + lo = w * 0x40 + ((lo / 0x4000000) | 0); + } + + // Fast length reduction + if (num.words[num.length - 1] === 0) { + num.length--; + if (num.words[num.length - 1] === 0) { + num.length--; + } + } + return num; + }; + + function P224 () { + MPrime.call( + this, + 'p224', + 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001'); + } + inherits(P224, MPrime); + + function P192 () { + MPrime.call( + this, + 'p192', + 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff'); + } + inherits(P192, MPrime); + + function P25519 () { + // 2 ^ 255 - 19 + MPrime.call( + this, + '25519', + '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed'); + } + inherits(P25519, MPrime); + + P25519.prototype.imulK = function imulK (num) { + // K = 0x13 + var carry = 0; + for (var i = 0; i < num.length; i++) { + var hi = (num.words[i] | 0) * 0x13 + carry; + var lo = hi & 0x3ffffff; + hi >>>= 26; + + num.words[i] = lo; + carry = hi; + } + if (carry !== 0) { + num.words[num.length++] = carry; + } + return num; + }; + + // Exported mostly for testing purposes, use plain name instead + BN._prime = function prime (name) { + // Cached version of prime + if (primes[name]) return primes[name]; + + var prime; + if (name === 'k256') { + prime = new K256(); + } else if (name === 'p224') { + prime = new P224(); + } else if (name === 'p192') { + prime = new P192(); + } else if (name === 'p25519') { + prime = new P25519(); + } else { + throw new Error('Unknown prime ' + name); + } + primes[name] = prime; + + return prime; + }; + + // + // Base reduction engine + // + function Red (m) { + if (typeof m === 'string') { + var prime = BN._prime(m); + this.m = prime.p; + this.prime = prime; + } else { + assert(m.gtn(1), 'modulus must be greater than 1'); + this.m = m; + this.prime = null; + } + } + + Red.prototype._verify1 = function _verify1 (a) { + assert(a.negative === 0, 'red works only with positives'); + assert(a.red, 'red works only with red numbers'); + }; + + Red.prototype._verify2 = function _verify2 (a, b) { + assert((a.negative | b.negative) === 0, 'red works only with positives'); + assert(a.red && a.red === b.red, + 'red works only with red numbers'); + }; + + Red.prototype.imod = function imod (a) { + if (this.prime) return this.prime.ireduce(a)._forceRed(this); + return a.umod(this.m)._forceRed(this); + }; + + Red.prototype.neg = function neg (a) { + if (a.isZero()) { + return a.clone(); + } + + return this.m.sub(a)._forceRed(this); + }; + + Red.prototype.add = function add (a, b) { + this._verify2(a, b); + + var res = a.add(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.iadd = function iadd (a, b) { + this._verify2(a, b); + + var res = a.iadd(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res; + }; + + Red.prototype.sub = function sub (a, b) { + this._verify2(a, b); + + var res = a.sub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.isub = function isub (a, b) { + this._verify2(a, b); + + var res = a.isub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res; + }; + + Red.prototype.shl = function shl (a, num) { + this._verify1(a); + return this.imod(a.ushln(num)); + }; + + Red.prototype.imul = function imul (a, b) { + this._verify2(a, b); + return this.imod(a.imul(b)); + }; + + Red.prototype.mul = function mul (a, b) { + this._verify2(a, b); + return this.imod(a.mul(b)); + }; + + Red.prototype.isqr = function isqr (a) { + return this.imul(a, a.clone()); + }; + + Red.prototype.sqr = function sqr (a) { + return this.mul(a, a); + }; + + Red.prototype.sqrt = function sqrt (a) { + if (a.isZero()) return a.clone(); + + var mod3 = this.m.andln(3); + assert(mod3 % 2 === 1); + + // Fast case + if (mod3 === 3) { + var pow = this.m.add(new BN(1)).iushrn(2); + return this.pow(a, pow); + } + + // Tonelli-Shanks algorithm (Totally unoptimized and slow) + // + // Find Q and S, that Q * 2 ^ S = (P - 1) + var q = this.m.subn(1); + var s = 0; + while (!q.isZero() && q.andln(1) === 0) { + s++; + q.iushrn(1); + } + assert(!q.isZero()); + + var one = new BN(1).toRed(this); + var nOne = one.redNeg(); + + // Find quadratic non-residue + // NOTE: Max is such because of generalized Riemann hypothesis. + var lpow = this.m.subn(1).iushrn(1); + var z = this.m.bitLength(); + z = new BN(2 * z * z).toRed(this); + + while (this.pow(z, lpow).cmp(nOne) !== 0) { + z.redIAdd(nOne); + } + + var c = this.pow(z, q); + var r = this.pow(a, q.addn(1).iushrn(1)); + var t = this.pow(a, q); + var m = s; + while (t.cmp(one) !== 0) { + var tmp = t; + for (var i = 0; tmp.cmp(one) !== 0; i++) { + tmp = tmp.redSqr(); + } + assert(i < m); + var b = this.pow(c, new BN(1).iushln(m - i - 1)); + + r = r.redMul(b); + c = b.redSqr(); + t = t.redMul(c); + m = i; + } + + return r; + }; + + Red.prototype.invm = function invm (a) { + var inv = a._invmp(this.m); + if (inv.negative !== 0) { + inv.negative = 0; + return this.imod(inv).redNeg(); + } else { + return this.imod(inv); + } + }; + + Red.prototype.pow = function pow (a, num) { + if (num.isZero()) return new BN(1).toRed(this); + if (num.cmpn(1) === 0) return a.clone(); + + var windowSize = 4; + var wnd = new Array(1 << windowSize); + wnd[0] = new BN(1).toRed(this); + wnd[1] = a; + for (var i = 2; i < wnd.length; i++) { + wnd[i] = this.mul(wnd[i - 1], a); + } + + var res = wnd[0]; + var current = 0; + var currentLen = 0; + var start = num.bitLength() % 26; + if (start === 0) { + start = 26; + } + + for (i = num.length - 1; i >= 0; i--) { + var word = num.words[i]; + for (var j = start - 1; j >= 0; j--) { + var bit = (word >> j) & 1; + if (res !== wnd[0]) { + res = this.sqr(res); + } + + if (bit === 0 && current === 0) { + currentLen = 0; + continue; + } + + current <<= 1; + current |= bit; + currentLen++; + if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue; + + res = this.mul(res, wnd[current]); + currentLen = 0; + current = 0; + } + start = 26; + } + + return res; + }; + + Red.prototype.convertTo = function convertTo (num) { + var r = num.umod(this.m); + + return r === num ? r.clone() : r; + }; + + Red.prototype.convertFrom = function convertFrom (num) { + var res = num.clone(); + res.red = null; + return res; + }; + + // + // Montgomery method engine + // + + BN.mont = function mont (num) { + return new Mont(num); + }; + + function Mont (m) { + Red.call(this, m); + + this.shift = this.m.bitLength(); + if (this.shift % 26 !== 0) { + this.shift += 26 - (this.shift % 26); + } + + this.r = new BN(1).iushln(this.shift); + this.r2 = this.imod(this.r.sqr()); + this.rinv = this.r._invmp(this.m); + + this.minv = this.rinv.mul(this.r).isubn(1).div(this.m); + this.minv = this.minv.umod(this.r); + this.minv = this.r.sub(this.minv); + } + inherits(Mont, Red); + + Mont.prototype.convertTo = function convertTo (num) { + return this.imod(num.ushln(this.shift)); + }; + + Mont.prototype.convertFrom = function convertFrom (num) { + var r = this.imod(num.mul(this.rinv)); + r.red = null; + return r; + }; + + Mont.prototype.imul = function imul (a, b) { + if (a.isZero() || b.isZero()) { + a.words[0] = 0; + a.length = 1; + return a; + } + + var t = a.imul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.mul = function mul (a, b) { + if (a.isZero() || b.isZero()) return new BN(0)._forceRed(this); + + var t = a.mul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.invm = function invm (a) { + // (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R + var res = this.imod(a._invmp(this.m).mul(this.r2)); + return res._forceRed(this); + }; +})(typeof module === 'undefined' || module, this); + +},{"buffer":20}],19:[function(require,module,exports){ +var r; + +module.exports = function rand(len) { + if (!r) + r = new Rand(null); + + return r.generate(len); +}; + +function Rand(rand) { + this.rand = rand; +} +module.exports.Rand = Rand; + +Rand.prototype.generate = function generate(len) { + return this._rand(len); +}; + +// Emulate crypto API using randy +Rand.prototype._rand = function _rand(n) { + if (this.rand.getBytes) + return this.rand.getBytes(n); + + var res = new Uint8Array(n); + for (var i = 0; i < res.length; i++) + res[i] = this.rand.getByte(); + return res; +}; + +if (typeof self === 'object') { + if (self.crypto && self.crypto.getRandomValues) { + // Modern browsers + Rand.prototype._rand = function _rand(n) { + var arr = new Uint8Array(n); + self.crypto.getRandomValues(arr); + return arr; + }; + } else if (self.msCrypto && self.msCrypto.getRandomValues) { + // IE + Rand.prototype._rand = function _rand(n) { + var arr = new Uint8Array(n); + self.msCrypto.getRandomValues(arr); + return arr; + }; + + // Safari's WebWorkers do not have `crypto` + } else if (typeof window === 'object') { + // Old junk + Rand.prototype._rand = function() { + throw new Error('Not implemented yet'); + }; + } +} else { + // Node.js or Web worker with no crypto support + try { + var crypto = require('crypto'); + if (typeof crypto.randomBytes !== 'function') + throw new Error('Not supported'); + + Rand.prototype._rand = function _rand(n) { + return crypto.randomBytes(n); + }; + } catch (e) { + } +} + +},{"crypto":20}],20:[function(require,module,exports){ + +},{}],21:[function(require,module,exports){ +// based on the aes implimentation in triple sec +// https://github.com/keybase/triplesec +// which is in turn based on the one from crypto-js +// https://code.google.com/p/crypto-js/ + +var Buffer = require('safe-buffer').Buffer + +function asUInt32Array (buf) { + if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf) + + var len = (buf.length / 4) | 0 + var out = new Array(len) + + for (var i = 0; i < len; i++) { + out[i] = buf.readUInt32BE(i * 4) + } + + return out +} + +function scrubVec (v) { + for (var i = 0; i < v.length; v++) { + v[i] = 0 + } +} + +function cryptBlock (M, keySchedule, SUB_MIX, SBOX, nRounds) { + var SUB_MIX0 = SUB_MIX[0] + var SUB_MIX1 = SUB_MIX[1] + var SUB_MIX2 = SUB_MIX[2] + var SUB_MIX3 = SUB_MIX[3] + + var s0 = M[0] ^ keySchedule[0] + var s1 = M[1] ^ keySchedule[1] + var s2 = M[2] ^ keySchedule[2] + var s3 = M[3] ^ keySchedule[3] + var t0, t1, t2, t3 + var ksRow = 4 + + for (var round = 1; round < nRounds; round++) { + t0 = SUB_MIX0[s0 >>> 24] ^ SUB_MIX1[(s1 >>> 16) & 0xff] ^ SUB_MIX2[(s2 >>> 8) & 0xff] ^ SUB_MIX3[s3 & 0xff] ^ keySchedule[ksRow++] + t1 = SUB_MIX0[s1 >>> 24] ^ SUB_MIX1[(s2 >>> 16) & 0xff] ^ SUB_MIX2[(s3 >>> 8) & 0xff] ^ SUB_MIX3[s0 & 0xff] ^ keySchedule[ksRow++] + t2 = SUB_MIX0[s2 >>> 24] ^ SUB_MIX1[(s3 >>> 16) & 0xff] ^ SUB_MIX2[(s0 >>> 8) & 0xff] ^ SUB_MIX3[s1 & 0xff] ^ keySchedule[ksRow++] + t3 = SUB_MIX0[s3 >>> 24] ^ SUB_MIX1[(s0 >>> 16) & 0xff] ^ SUB_MIX2[(s1 >>> 8) & 0xff] ^ SUB_MIX3[s2 & 0xff] ^ keySchedule[ksRow++] + s0 = t0 + s1 = t1 + s2 = t2 + s3 = t3 + } + + t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++] + t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++] + t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++] + t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++] + t0 = t0 >>> 0 + t1 = t1 >>> 0 + t2 = t2 >>> 0 + t3 = t3 >>> 0 + + return [t0, t1, t2, t3] +} + +// AES constants +var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36] +var G = (function () { + // Compute double table + var d = new Array(256) + for (var j = 0; j < 256; j++) { + if (j < 128) { + d[j] = j << 1 + } else { + d[j] = (j << 1) ^ 0x11b + } + } + + var SBOX = [] + var INV_SBOX = [] + var SUB_MIX = [[], [], [], []] + var INV_SUB_MIX = [[], [], [], []] + + // Walk GF(2^8) + var x = 0 + var xi = 0 + for (var i = 0; i < 256; ++i) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4) + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63 + SBOX[x] = sx + INV_SBOX[sx] = x + + // Compute multiplication + var x2 = d[x] + var x4 = d[x2] + var x8 = d[x4] + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100) + SUB_MIX[0][x] = (t << 24) | (t >>> 8) + SUB_MIX[1][x] = (t << 16) | (t >>> 16) + SUB_MIX[2][x] = (t << 8) | (t >>> 24) + SUB_MIX[3][x] = t + + // Compute inv sub bytes, inv mix columns tables + t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100) + INV_SUB_MIX[0][sx] = (t << 24) | (t >>> 8) + INV_SUB_MIX[1][sx] = (t << 16) | (t >>> 16) + INV_SUB_MIX[2][sx] = (t << 8) | (t >>> 24) + INV_SUB_MIX[3][sx] = t + + if (x === 0) { + x = xi = 1 + } else { + x = x2 ^ d[d[d[x8 ^ x2]]] + xi ^= d[d[xi]] + } + } + + return { + SBOX: SBOX, + INV_SBOX: INV_SBOX, + SUB_MIX: SUB_MIX, + INV_SUB_MIX: INV_SUB_MIX + } +})() + +function AES (key) { + this._key = asUInt32Array(key) + this._reset() +} + +AES.blockSize = 4 * 4 +AES.keySize = 256 / 8 +AES.prototype.blockSize = AES.blockSize +AES.prototype.keySize = AES.keySize +AES.prototype._reset = function () { + var keyWords = this._key + var keySize = keyWords.length + var nRounds = keySize + 6 + var ksRows = (nRounds + 1) * 4 + + var keySchedule = [] + for (var k = 0; k < keySize; k++) { + keySchedule[k] = keyWords[k] + } + + for (k = keySize; k < ksRows; k++) { + var t = keySchedule[k - 1] + + if (k % keySize === 0) { + t = (t << 8) | (t >>> 24) + t = + (G.SBOX[t >>> 24] << 24) | + (G.SBOX[(t >>> 16) & 0xff] << 16) | + (G.SBOX[(t >>> 8) & 0xff] << 8) | + (G.SBOX[t & 0xff]) + + t ^= RCON[(k / keySize) | 0] << 24 + } else if (keySize > 6 && k % keySize === 4) { + t = + (G.SBOX[t >>> 24] << 24) | + (G.SBOX[(t >>> 16) & 0xff] << 16) | + (G.SBOX[(t >>> 8) & 0xff] << 8) | + (G.SBOX[t & 0xff]) + } + + keySchedule[k] = keySchedule[k - keySize] ^ t + } + + var invKeySchedule = [] + for (var ik = 0; ik < ksRows; ik++) { + var ksR = ksRows - ik + var tt = keySchedule[ksR - (ik % 4 ? 0 : 4)] + + if (ik < 4 || ksR <= 4) { + invKeySchedule[ik] = tt + } else { + invKeySchedule[ik] = + G.INV_SUB_MIX[0][G.SBOX[tt >>> 24]] ^ + G.INV_SUB_MIX[1][G.SBOX[(tt >>> 16) & 0xff]] ^ + G.INV_SUB_MIX[2][G.SBOX[(tt >>> 8) & 0xff]] ^ + G.INV_SUB_MIX[3][G.SBOX[tt & 0xff]] + } + } + + this._nRounds = nRounds + this._keySchedule = keySchedule + this._invKeySchedule = invKeySchedule +} + +AES.prototype.encryptBlockRaw = function (M) { + M = asUInt32Array(M) + return cryptBlock(M, this._keySchedule, G.SUB_MIX, G.SBOX, this._nRounds) +} + +AES.prototype.encryptBlock = function (M) { + var out = this.encryptBlockRaw(M) + var buf = Buffer.allocUnsafe(16) + buf.writeUInt32BE(out[0], 0) + buf.writeUInt32BE(out[1], 4) + buf.writeUInt32BE(out[2], 8) + buf.writeUInt32BE(out[3], 12) + return buf +} + +AES.prototype.decryptBlock = function (M) { + M = asUInt32Array(M) + + // swap + var m1 = M[1] + M[1] = M[3] + M[3] = m1 + + var out = cryptBlock(M, this._invKeySchedule, G.INV_SUB_MIX, G.INV_SBOX, this._nRounds) + var buf = Buffer.allocUnsafe(16) + buf.writeUInt32BE(out[0], 0) + buf.writeUInt32BE(out[3], 4) + buf.writeUInt32BE(out[2], 8) + buf.writeUInt32BE(out[1], 12) + return buf +} + +AES.prototype.scrub = function () { + scrubVec(this._keySchedule) + scrubVec(this._invKeySchedule) + scrubVec(this._key) +} + +module.exports.AES = AES + +},{"safe-buffer":250}],22:[function(require,module,exports){ +var aes = require('./aes') +var Buffer = require('safe-buffer').Buffer +var Transform = require('cipher-base') +var inherits = require('inherits') +var GHASH = require('./ghash') +var xor = require('buffer-xor') +var incr32 = require('./incr32') + +function xorTest (a, b) { + var out = 0 + if (a.length !== b.length) out++ + + var len = Math.min(a.length, b.length) + for (var i = 0; i < len; ++i) { + out += (a[i] ^ b[i]) + } + + return out +} + +function calcIv (self, iv, ck) { + if (iv.length === 12) { + self._finID = Buffer.concat([iv, Buffer.from([0, 0, 0, 1])]) + return Buffer.concat([iv, Buffer.from([0, 0, 0, 2])]) + } + var ghash = new GHASH(ck) + var len = iv.length + var toPad = len % 16 + ghash.update(iv) + if (toPad) { + toPad = 16 - toPad + ghash.update(Buffer.alloc(toPad, 0)) + } + ghash.update(Buffer.alloc(8, 0)) + var ivBits = len * 8 + var tail = Buffer.alloc(8) + tail.writeUIntBE(ivBits, 0, 8) + ghash.update(tail) + self._finID = ghash.state + var out = Buffer.from(self._finID) + incr32(out) + return out +} +function StreamCipher (mode, key, iv, decrypt) { + Transform.call(this) + + var h = Buffer.alloc(4, 0) + + this._cipher = new aes.AES(key) + var ck = this._cipher.encryptBlock(h) + this._ghash = new GHASH(ck) + iv = calcIv(this, iv, ck) + + this._prev = Buffer.from(iv) + this._cache = Buffer.allocUnsafe(0) + this._secCache = Buffer.allocUnsafe(0) + this._decrypt = decrypt + this._alen = 0 + this._len = 0 + this._mode = mode + + this._authTag = null + this._called = false +} + +inherits(StreamCipher, Transform) + +StreamCipher.prototype._update = function (chunk) { + if (!this._called && this._alen) { + var rump = 16 - (this._alen % 16) + if (rump < 16) { + rump = Buffer.alloc(rump, 0) + this._ghash.update(rump) + } + } + + this._called = true + var out = this._mode.encrypt(this, chunk) + if (this._decrypt) { + this._ghash.update(chunk) + } else { + this._ghash.update(out) + } + this._len += chunk.length + return out +} + +StreamCipher.prototype._final = function () { + if (this._decrypt && !this._authTag) throw new Error('Unsupported state or unable to authenticate data') + + var tag = xor(this._ghash.final(this._alen * 8, this._len * 8), this._cipher.encryptBlock(this._finID)) + if (this._decrypt && xorTest(tag, this._authTag)) throw new Error('Unsupported state or unable to authenticate data') + + this._authTag = tag + this._cipher.scrub() +} + +StreamCipher.prototype.getAuthTag = function getAuthTag () { + if (this._decrypt || !Buffer.isBuffer(this._authTag)) throw new Error('Attempting to get auth tag in unsupported state') + + return this._authTag +} + +StreamCipher.prototype.setAuthTag = function setAuthTag (tag) { + if (!this._decrypt) throw new Error('Attempting to set auth tag in unsupported state') + + this._authTag = tag +} + +StreamCipher.prototype.setAAD = function setAAD (buf) { + if (this._called) throw new Error('Attempting to set AAD in unsupported state') + + this._ghash.update(buf) + this._alen += buf.length +} + +module.exports = StreamCipher + +},{"./aes":21,"./ghash":26,"./incr32":27,"buffer-xor":67,"cipher-base":71,"inherits":146,"safe-buffer":250}],23:[function(require,module,exports){ +var ciphers = require('./encrypter') +var deciphers = require('./decrypter') +var modes = require('./modes/list.json') + +function getCiphers () { + return Object.keys(modes) +} + +exports.createCipher = exports.Cipher = ciphers.createCipher +exports.createCipheriv = exports.Cipheriv = ciphers.createCipheriv +exports.createDecipher = exports.Decipher = deciphers.createDecipher +exports.createDecipheriv = exports.Decipheriv = deciphers.createDecipheriv +exports.listCiphers = exports.getCiphers = getCiphers + +},{"./decrypter":24,"./encrypter":25,"./modes/list.json":35}],24:[function(require,module,exports){ +var AuthCipher = require('./authCipher') +var Buffer = require('safe-buffer').Buffer +var MODES = require('./modes') +var StreamCipher = require('./streamCipher') +var Transform = require('cipher-base') +var aes = require('./aes') +var ebtk = require('evp_bytestokey') +var inherits = require('inherits') + +function Decipher (mode, key, iv) { + Transform.call(this) + + this._cache = new Splitter() + this._last = void 0 + this._cipher = new aes.AES(key) + this._prev = Buffer.from(iv) + this._mode = mode + this._autopadding = true +} + +inherits(Decipher, Transform) + +Decipher.prototype._update = function (data) { + this._cache.add(data) + var chunk + var thing + var out = [] + while ((chunk = this._cache.get(this._autopadding))) { + thing = this._mode.decrypt(this, chunk) + out.push(thing) + } + return Buffer.concat(out) +} + +Decipher.prototype._final = function () { + var chunk = this._cache.flush() + if (this._autopadding) { + return unpad(this._mode.decrypt(this, chunk)) + } else if (chunk) { + throw new Error('data not multiple of block length') + } +} + +Decipher.prototype.setAutoPadding = function (setTo) { + this._autopadding = !!setTo + return this +} + +function Splitter () { + this.cache = Buffer.allocUnsafe(0) +} + +Splitter.prototype.add = function (data) { + this.cache = Buffer.concat([this.cache, data]) +} + +Splitter.prototype.get = function (autoPadding) { + var out + if (autoPadding) { + if (this.cache.length > 16) { + out = this.cache.slice(0, 16) + this.cache = this.cache.slice(16) + return out + } + } else { + if (this.cache.length >= 16) { + out = this.cache.slice(0, 16) + this.cache = this.cache.slice(16) + return out + } + } + + return null +} + +Splitter.prototype.flush = function () { + if (this.cache.length) return this.cache +} + +function unpad (last) { + var padded = last[15] + if (padded < 1 || padded > 16) { + throw new Error('unable to decrypt data') + } + var i = -1 + while (++i < padded) { + if (last[(i + (16 - padded))] !== padded) { + throw new Error('unable to decrypt data') + } + } + if (padded === 16) return + + return last.slice(0, 16 - padded) +} + +function createDecipheriv (suite, password, iv) { + var config = MODES[suite.toLowerCase()] + if (!config) throw new TypeError('invalid suite type') + + if (typeof iv === 'string') iv = Buffer.from(iv) + if (config.mode !== 'GCM' && iv.length !== config.iv) throw new TypeError('invalid iv length ' + iv.length) + + if (typeof password === 'string') password = Buffer.from(password) + if (password.length !== config.key / 8) throw new TypeError('invalid key length ' + password.length) + + if (config.type === 'stream') { + return new StreamCipher(config.module, password, iv, true) + } else if (config.type === 'auth') { + return new AuthCipher(config.module, password, iv, true) + } + + return new Decipher(config.module, password, iv) +} + +function createDecipher (suite, password) { + var config = MODES[suite.toLowerCase()] + if (!config) throw new TypeError('invalid suite type') + + var keys = ebtk(password, false, config.key, config.iv) + return createDecipheriv(suite, keys.key, keys.iv) +} + +exports.createDecipher = createDecipher +exports.createDecipheriv = createDecipheriv + +},{"./aes":21,"./authCipher":22,"./modes":34,"./streamCipher":37,"cipher-base":71,"evp_bytestokey":106,"inherits":146,"safe-buffer":250}],25:[function(require,module,exports){ +var MODES = require('./modes') +var AuthCipher = require('./authCipher') +var Buffer = require('safe-buffer').Buffer +var StreamCipher = require('./streamCipher') +var Transform = require('cipher-base') +var aes = require('./aes') +var ebtk = require('evp_bytestokey') +var inherits = require('inherits') + +function Cipher (mode, key, iv) { + Transform.call(this) + + this._cache = new Splitter() + this._cipher = new aes.AES(key) + this._prev = Buffer.from(iv) + this._mode = mode + this._autopadding = true +} + +inherits(Cipher, Transform) + +Cipher.prototype._update = function (data) { + this._cache.add(data) + var chunk + var thing + var out = [] + + while ((chunk = this._cache.get())) { + thing = this._mode.encrypt(this, chunk) + out.push(thing) + } + + return Buffer.concat(out) +} + +var PADDING = Buffer.alloc(16, 0x10) + +Cipher.prototype._final = function () { + var chunk = this._cache.flush() + if (this._autopadding) { + chunk = this._mode.encrypt(this, chunk) + this._cipher.scrub() + return chunk + } + + if (!chunk.equals(PADDING)) { + this._cipher.scrub() + throw new Error('data not multiple of block length') + } +} + +Cipher.prototype.setAutoPadding = function (setTo) { + this._autopadding = !!setTo + return this +} + +function Splitter () { + this.cache = Buffer.allocUnsafe(0) +} + +Splitter.prototype.add = function (data) { + this.cache = Buffer.concat([this.cache, data]) +} + +Splitter.prototype.get = function () { + if (this.cache.length > 15) { + var out = this.cache.slice(0, 16) + this.cache = this.cache.slice(16) + return out + } + return null +} + +Splitter.prototype.flush = function () { + var len = 16 - this.cache.length + var padBuff = Buffer.allocUnsafe(len) + + var i = -1 + while (++i < len) { + padBuff.writeUInt8(len, i) + } + + return Buffer.concat([this.cache, padBuff]) +} + +function createCipheriv (suite, password, iv) { + var config = MODES[suite.toLowerCase()] + if (!config) throw new TypeError('invalid suite type') + + if (typeof password === 'string') password = Buffer.from(password) + if (password.length !== config.key / 8) throw new TypeError('invalid key length ' + password.length) + + if (typeof iv === 'string') iv = Buffer.from(iv) + if (config.mode !== 'GCM' && iv.length !== config.iv) throw new TypeError('invalid iv length ' + iv.length) + + if (config.type === 'stream') { + return new StreamCipher(config.module, password, iv) + } else if (config.type === 'auth') { + return new AuthCipher(config.module, password, iv) + } + + return new Cipher(config.module, password, iv) +} + +function createCipher (suite, password) { + var config = MODES[suite.toLowerCase()] + if (!config) throw new TypeError('invalid suite type') + + var keys = ebtk(password, false, config.key, config.iv) + return createCipheriv(suite, keys.key, keys.iv) +} + +exports.createCipheriv = createCipheriv +exports.createCipher = createCipher + +},{"./aes":21,"./authCipher":22,"./modes":34,"./streamCipher":37,"cipher-base":71,"evp_bytestokey":106,"inherits":146,"safe-buffer":250}],26:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer +var ZEROES = Buffer.alloc(16, 0) + +function toArray (buf) { + return [ + buf.readUInt32BE(0), + buf.readUInt32BE(4), + buf.readUInt32BE(8), + buf.readUInt32BE(12) + ] +} + +function fromArray (out) { + var buf = Buffer.allocUnsafe(16) + buf.writeUInt32BE(out[0] >>> 0, 0) + buf.writeUInt32BE(out[1] >>> 0, 4) + buf.writeUInt32BE(out[2] >>> 0, 8) + buf.writeUInt32BE(out[3] >>> 0, 12) + return buf +} + +function GHASH (key) { + this.h = key + this.state = Buffer.alloc(16, 0) + this.cache = Buffer.allocUnsafe(0) +} + +// from http://bitwiseshiftleft.github.io/sjcl/doc/symbols/src/core_gcm.js.html +// by Juho Vähä-Herttua +GHASH.prototype.ghash = function (block) { + var i = -1 + while (++i < block.length) { + this.state[i] ^= block[i] + } + this._multiply() +} + +GHASH.prototype._multiply = function () { + var Vi = toArray(this.h) + var Zi = [0, 0, 0, 0] + var j, xi, lsbVi + var i = -1 + while (++i < 128) { + xi = (this.state[~~(i / 8)] & (1 << (7 - (i % 8)))) !== 0 + if (xi) { + // Z_i+1 = Z_i ^ V_i + Zi[0] ^= Vi[0] + Zi[1] ^= Vi[1] + Zi[2] ^= Vi[2] + Zi[3] ^= Vi[3] + } + + // Store the value of LSB(V_i) + lsbVi = (Vi[3] & 1) !== 0 + + // V_i+1 = V_i >> 1 + for (j = 3; j > 0; j--) { + Vi[j] = (Vi[j] >>> 1) | ((Vi[j - 1] & 1) << 31) + } + Vi[0] = Vi[0] >>> 1 + + // If LSB(V_i) is 1, V_i+1 = (V_i >> 1) ^ R + if (lsbVi) { + Vi[0] = Vi[0] ^ (0xe1 << 24) + } + } + this.state = fromArray(Zi) +} + +GHASH.prototype.update = function (buf) { + this.cache = Buffer.concat([this.cache, buf]) + var chunk + while (this.cache.length >= 16) { + chunk = this.cache.slice(0, 16) + this.cache = this.cache.slice(16) + this.ghash(chunk) + } +} + +GHASH.prototype.final = function (abl, bl) { + if (this.cache.length) { + this.ghash(Buffer.concat([this.cache, ZEROES], 16)) + } + + this.ghash(fromArray([0, abl, 0, bl])) + return this.state +} + +module.exports = GHASH + +},{"safe-buffer":250}],27:[function(require,module,exports){ +function incr32 (iv) { + var len = iv.length + var item + while (len--) { + item = iv.readUInt8(len) + if (item === 255) { + iv.writeUInt8(0, len) + } else { + item++ + iv.writeUInt8(item, len) + break + } + } +} +module.exports = incr32 + +},{}],28:[function(require,module,exports){ +var xor = require('buffer-xor') + +exports.encrypt = function (self, block) { + var data = xor(block, self._prev) + + self._prev = self._cipher.encryptBlock(data) + return self._prev +} + +exports.decrypt = function (self, block) { + var pad = self._prev + + self._prev = block + var out = self._cipher.decryptBlock(block) + + return xor(out, pad) +} + +},{"buffer-xor":67}],29:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer +var xor = require('buffer-xor') + +function encryptStart (self, data, decrypt) { + var len = data.length + var out = xor(data, self._cache) + self._cache = self._cache.slice(len) + self._prev = Buffer.concat([self._prev, decrypt ? data : out]) + return out +} + +exports.encrypt = function (self, data, decrypt) { + var out = Buffer.allocUnsafe(0) + var len + + while (data.length) { + if (self._cache.length === 0) { + self._cache = self._cipher.encryptBlock(self._prev) + self._prev = Buffer.allocUnsafe(0) + } + + if (self._cache.length <= data.length) { + len = self._cache.length + out = Buffer.concat([out, encryptStart(self, data.slice(0, len), decrypt)]) + data = data.slice(len) + } else { + out = Buffer.concat([out, encryptStart(self, data, decrypt)]) + break + } + } + + return out +} + +},{"buffer-xor":67,"safe-buffer":250}],30:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer + +function encryptByte (self, byteParam, decrypt) { + var pad + var i = -1 + var len = 8 + var out = 0 + var bit, value + while (++i < len) { + pad = self._cipher.encryptBlock(self._prev) + bit = (byteParam & (1 << (7 - i))) ? 0x80 : 0 + value = pad[0] ^ bit + out += ((value & 0x80) >> (i % 8)) + self._prev = shiftIn(self._prev, decrypt ? bit : value) + } + return out +} + +function shiftIn (buffer, value) { + var len = buffer.length + var i = -1 + var out = Buffer.allocUnsafe(buffer.length) + buffer = Buffer.concat([buffer, Buffer.from([value])]) + + while (++i < len) { + out[i] = buffer[i] << 1 | buffer[i + 1] >> (7) + } + + return out +} + +exports.encrypt = function (self, chunk, decrypt) { + var len = chunk.length + var out = Buffer.allocUnsafe(len) + var i = -1 + + while (++i < len) { + out[i] = encryptByte(self, chunk[i], decrypt) + } + + return out +} + +},{"safe-buffer":250}],31:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer + +function encryptByte (self, byteParam, decrypt) { + var pad = self._cipher.encryptBlock(self._prev) + var out = pad[0] ^ byteParam + + self._prev = Buffer.concat([ + self._prev.slice(1), + Buffer.from([decrypt ? byteParam : out]) + ]) + + return out +} + +exports.encrypt = function (self, chunk, decrypt) { + var len = chunk.length + var out = Buffer.allocUnsafe(len) + var i = -1 + + while (++i < len) { + out[i] = encryptByte(self, chunk[i], decrypt) + } + + return out +} + +},{"safe-buffer":250}],32:[function(require,module,exports){ +var xor = require('buffer-xor') +var Buffer = require('safe-buffer').Buffer +var incr32 = require('../incr32') + +function getBlock (self) { + var out = self._cipher.encryptBlockRaw(self._prev) + incr32(self._prev) + return out +} + +var blockSize = 16 +exports.encrypt = function (self, chunk) { + var chunkNum = Math.ceil(chunk.length / blockSize) + var start = self._cache.length + self._cache = Buffer.concat([ + self._cache, + Buffer.allocUnsafe(chunkNum * blockSize) + ]) + for (var i = 0; i < chunkNum; i++) { + var out = getBlock(self) + var offset = start + i * blockSize + self._cache.writeUInt32BE(out[0], offset + 0) + self._cache.writeUInt32BE(out[1], offset + 4) + self._cache.writeUInt32BE(out[2], offset + 8) + self._cache.writeUInt32BE(out[3], offset + 12) + } + var pad = self._cache.slice(0, chunk.length) + self._cache = self._cache.slice(chunk.length) + return xor(chunk, pad) +} + +},{"../incr32":27,"buffer-xor":67,"safe-buffer":250}],33:[function(require,module,exports){ +exports.encrypt = function (self, block) { + return self._cipher.encryptBlock(block) +} + +exports.decrypt = function (self, block) { + return self._cipher.decryptBlock(block) +} + +},{}],34:[function(require,module,exports){ +var modeModules = { + ECB: require('./ecb'), + CBC: require('./cbc'), + CFB: require('./cfb'), + CFB8: require('./cfb8'), + CFB1: require('./cfb1'), + OFB: require('./ofb'), + CTR: require('./ctr'), + GCM: require('./ctr') +} + +var modes = require('./list.json') + +for (var key in modes) { + modes[key].module = modeModules[modes[key].mode] +} + +module.exports = modes + +},{"./cbc":28,"./cfb":29,"./cfb1":30,"./cfb8":31,"./ctr":32,"./ecb":33,"./list.json":35,"./ofb":36}],35:[function(require,module,exports){ +module.exports={ + "aes-128-ecb": { + "cipher": "AES", + "key": 128, + "iv": 0, + "mode": "ECB", + "type": "block" + }, + "aes-192-ecb": { + "cipher": "AES", + "key": 192, + "iv": 0, + "mode": "ECB", + "type": "block" + }, + "aes-256-ecb": { + "cipher": "AES", + "key": 256, + "iv": 0, + "mode": "ECB", + "type": "block" + }, + "aes-128-cbc": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes-192-cbc": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes-256-cbc": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes128": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes192": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes256": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CBC", + "type": "block" + }, + "aes-128-cfb": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CFB", + "type": "stream" + }, + "aes-192-cfb": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CFB", + "type": "stream" + }, + "aes-256-cfb": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CFB", + "type": "stream" + }, + "aes-128-cfb8": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CFB8", + "type": "stream" + }, + "aes-192-cfb8": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CFB8", + "type": "stream" + }, + "aes-256-cfb8": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CFB8", + "type": "stream" + }, + "aes-128-cfb1": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CFB1", + "type": "stream" + }, + "aes-192-cfb1": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CFB1", + "type": "stream" + }, + "aes-256-cfb1": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CFB1", + "type": "stream" + }, + "aes-128-ofb": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "OFB", + "type": "stream" + }, + "aes-192-ofb": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "OFB", + "type": "stream" + }, + "aes-256-ofb": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "OFB", + "type": "stream" + }, + "aes-128-ctr": { + "cipher": "AES", + "key": 128, + "iv": 16, + "mode": "CTR", + "type": "stream" + }, + "aes-192-ctr": { + "cipher": "AES", + "key": 192, + "iv": 16, + "mode": "CTR", + "type": "stream" + }, + "aes-256-ctr": { + "cipher": "AES", + "key": 256, + "iv": 16, + "mode": "CTR", + "type": "stream" + }, + "aes-128-gcm": { + "cipher": "AES", + "key": 128, + "iv": 12, + "mode": "GCM", + "type": "auth" + }, + "aes-192-gcm": { + "cipher": "AES", + "key": 192, + "iv": 12, + "mode": "GCM", + "type": "auth" + }, + "aes-256-gcm": { + "cipher": "AES", + "key": 256, + "iv": 12, + "mode": "GCM", + "type": "auth" + } +} + +},{}],36:[function(require,module,exports){ +(function (Buffer){(function (){ +var xor = require('buffer-xor') + +function getBlock (self) { + self._prev = self._cipher.encryptBlock(self._prev) + return self._prev +} + +exports.encrypt = function (self, chunk) { + while (self._cache.length < chunk.length) { + self._cache = Buffer.concat([self._cache, getBlock(self)]) + } + + var pad = self._cache.slice(0, chunk.length) + self._cache = self._cache.slice(chunk.length) + return xor(chunk, pad) +} + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"buffer":68,"buffer-xor":67}],37:[function(require,module,exports){ +var aes = require('./aes') +var Buffer = require('safe-buffer').Buffer +var Transform = require('cipher-base') +var inherits = require('inherits') + +function StreamCipher (mode, key, iv, decrypt) { + Transform.call(this) + + this._cipher = new aes.AES(key) + this._prev = Buffer.from(iv) + this._cache = Buffer.allocUnsafe(0) + this._secCache = Buffer.allocUnsafe(0) + this._decrypt = decrypt + this._mode = mode +} + +inherits(StreamCipher, Transform) + +StreamCipher.prototype._update = function (chunk) { + return this._mode.encrypt(this, chunk, this._decrypt) +} + +StreamCipher.prototype._final = function () { + this._cipher.scrub() +} + +module.exports = StreamCipher + +},{"./aes":21,"cipher-base":71,"inherits":146,"safe-buffer":250}],38:[function(require,module,exports){ +var DES = require('browserify-des') +var aes = require('browserify-aes/browser') +var aesModes = require('browserify-aes/modes') +var desModes = require('browserify-des/modes') +var ebtk = require('evp_bytestokey') + +function createCipher (suite, password) { + suite = suite.toLowerCase() + + var keyLen, ivLen + if (aesModes[suite]) { + keyLen = aesModes[suite].key + ivLen = aesModes[suite].iv + } else if (desModes[suite]) { + keyLen = desModes[suite].key * 8 + ivLen = desModes[suite].iv + } else { + throw new TypeError('invalid suite type') + } + + var keys = ebtk(password, false, keyLen, ivLen) + return createCipheriv(suite, keys.key, keys.iv) +} + +function createDecipher (suite, password) { + suite = suite.toLowerCase() + + var keyLen, ivLen + if (aesModes[suite]) { + keyLen = aesModes[suite].key + ivLen = aesModes[suite].iv + } else if (desModes[suite]) { + keyLen = desModes[suite].key * 8 + ivLen = desModes[suite].iv + } else { + throw new TypeError('invalid suite type') + } + + var keys = ebtk(password, false, keyLen, ivLen) + return createDecipheriv(suite, keys.key, keys.iv) +} + +function createCipheriv (suite, key, iv) { + suite = suite.toLowerCase() + if (aesModes[suite]) return aes.createCipheriv(suite, key, iv) + if (desModes[suite]) return new DES({ key: key, iv: iv, mode: suite }) + + throw new TypeError('invalid suite type') +} + +function createDecipheriv (suite, key, iv) { + suite = suite.toLowerCase() + if (aesModes[suite]) return aes.createDecipheriv(suite, key, iv) + if (desModes[suite]) return new DES({ key: key, iv: iv, mode: suite, decrypt: true }) + + throw new TypeError('invalid suite type') +} + +function getCiphers () { + return Object.keys(desModes).concat(aes.getCiphers()) +} + +exports.createCipher = exports.Cipher = createCipher +exports.createCipheriv = exports.Cipheriv = createCipheriv +exports.createDecipher = exports.Decipher = createDecipher +exports.createDecipheriv = exports.Decipheriv = createDecipheriv +exports.listCiphers = exports.getCiphers = getCiphers + +},{"browserify-aes/browser":23,"browserify-aes/modes":34,"browserify-des":39,"browserify-des/modes":40,"evp_bytestokey":106}],39:[function(require,module,exports){ +var CipherBase = require('cipher-base') +var des = require('des.js') +var inherits = require('inherits') +var Buffer = require('safe-buffer').Buffer + +var modes = { + 'des-ede3-cbc': des.CBC.instantiate(des.EDE), + 'des-ede3': des.EDE, + 'des-ede-cbc': des.CBC.instantiate(des.EDE), + 'des-ede': des.EDE, + 'des-cbc': des.CBC.instantiate(des.DES), + 'des-ecb': des.DES +} +modes.des = modes['des-cbc'] +modes.des3 = modes['des-ede3-cbc'] +module.exports = DES +inherits(DES, CipherBase) +function DES (opts) { + CipherBase.call(this) + var modeName = opts.mode.toLowerCase() + var mode = modes[modeName] + var type + if (opts.decrypt) { + type = 'decrypt' + } else { + type = 'encrypt' + } + var key = opts.key + if (!Buffer.isBuffer(key)) { + key = Buffer.from(key) + } + if (modeName === 'des-ede' || modeName === 'des-ede-cbc') { + key = Buffer.concat([key, key.slice(0, 8)]) + } + var iv = opts.iv + if (!Buffer.isBuffer(iv)) { + iv = Buffer.from(iv) + } + this._des = mode.create({ + key: key, + iv: iv, + type: type + }) +} +DES.prototype._update = function (data) { + return Buffer.from(this._des.update(data)) +} +DES.prototype._final = function () { + return Buffer.from(this._des.final()) +} + +},{"cipher-base":71,"des.js":79,"inherits":146,"safe-buffer":250}],40:[function(require,module,exports){ +exports['des-ecb'] = { + key: 8, + iv: 0 +} +exports['des-cbc'] = exports.des = { + key: 8, + iv: 8 +} +exports['des-ede3-cbc'] = exports.des3 = { + key: 24, + iv: 8 +} +exports['des-ede3'] = { + key: 24, + iv: 0 +} +exports['des-ede-cbc'] = { + key: 16, + iv: 8 +} +exports['des-ede'] = { + key: 16, + iv: 0 +} + +},{}],41:[function(require,module,exports){ +(function (Buffer){(function (){ +var BN = require('bn.js') +var randomBytes = require('randombytes') + +function blind (priv) { + var r = getr(priv) + var blinder = r.toRed(BN.mont(priv.modulus)).redPow(new BN(priv.publicExponent)).fromRed() + return { blinder: blinder, unblinder: r.invm(priv.modulus) } +} + +function getr (priv) { + var len = priv.modulus.byteLength() + var r + do { + r = new BN(randomBytes(len)) + } while (r.cmp(priv.modulus) >= 0 || !r.umod(priv.prime1) || !r.umod(priv.prime2)) + return r +} + +function crt (msg, priv) { + var blinds = blind(priv) + var len = priv.modulus.byteLength() + var blinded = new BN(msg).mul(blinds.blinder).umod(priv.modulus) + var c1 = blinded.toRed(BN.mont(priv.prime1)) + var c2 = blinded.toRed(BN.mont(priv.prime2)) + var qinv = priv.coefficient + var p = priv.prime1 + var q = priv.prime2 + var m1 = c1.redPow(priv.exponent1).fromRed() + var m2 = c2.redPow(priv.exponent2).fromRed() + var h = m1.isub(m2).imul(qinv).umod(p).imul(q) + return m2.iadd(h).imul(blinds.unblinder).umod(priv.modulus).toArrayLike(Buffer, 'be', len) +} +crt.getr = getr + +module.exports = crt + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"bn.js":42,"buffer":68,"randombytes":244}],42:[function(require,module,exports){ +(function (module, exports) { + 'use strict'; + + // Utils + function assert (val, msg) { + if (!val) throw new Error(msg || 'Assertion failed'); + } + + // Could use `inherits` module, but don't want to move from single file + // architecture yet. + function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + + // BN + + function BN (number, base, endian) { + if (BN.isBN(number)) { + return number; + } + + this.negative = 0; + this.words = null; + this.length = 0; + + // Reduction context + this.red = null; + + if (number !== null) { + if (base === 'le' || base === 'be') { + endian = base; + base = 10; + } + + this._init(number || 0, base || 10, endian || 'be'); + } + } + if (typeof module === 'object') { + module.exports = BN; + } else { + exports.BN = BN; + } + + BN.BN = BN; + BN.wordSize = 26; + + var Buffer; + try { + if (typeof window !== 'undefined' && typeof window.Buffer !== 'undefined') { + Buffer = window.Buffer; + } else { + Buffer = require('buffer').Buffer; + } + } catch (e) { + } + + BN.isBN = function isBN (num) { + if (num instanceof BN) { + return true; + } + + return num !== null && typeof num === 'object' && + num.constructor.wordSize === BN.wordSize && Array.isArray(num.words); + }; + + BN.max = function max (left, right) { + if (left.cmp(right) > 0) return left; + return right; + }; + + BN.min = function min (left, right) { + if (left.cmp(right) < 0) return left; + return right; + }; + + BN.prototype._init = function init (number, base, endian) { + if (typeof number === 'number') { + return this._initNumber(number, base, endian); + } + + if (typeof number === 'object') { + return this._initArray(number, base, endian); + } + + if (base === 'hex') { + base = 16; + } + assert(base === (base | 0) && base >= 2 && base <= 36); + + number = number.toString().replace(/\s+/g, ''); + var start = 0; + if (number[0] === '-') { + start++; + this.negative = 1; + } + + if (start < number.length) { + if (base === 16) { + this._parseHex(number, start, endian); + } else { + this._parseBase(number, base, start); + if (endian === 'le') { + this._initArray(this.toArray(), base, endian); + } + } + } + }; + + BN.prototype._initNumber = function _initNumber (number, base, endian) { + if (number < 0) { + this.negative = 1; + number = -number; + } + if (number < 0x4000000) { + this.words = [number & 0x3ffffff]; + this.length = 1; + } else if (number < 0x10000000000000) { + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff + ]; + this.length = 2; + } else { + assert(number < 0x20000000000000); // 2 ^ 53 (unsafe) + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff, + 1 + ]; + this.length = 3; + } + + if (endian !== 'le') return; + + // Reverse the bytes + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initArray = function _initArray (number, base, endian) { + // Perhaps a Uint8Array + assert(typeof number.length === 'number'); + if (number.length <= 0) { + this.words = [0]; + this.length = 1; + return this; + } + + this.length = Math.ceil(number.length / 3); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + var off = 0; + if (endian === 'be') { + for (i = number.length - 1, j = 0; i >= 0; i -= 3) { + w = number[i] | (number[i - 1] << 8) | (number[i - 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } else if (endian === 'le') { + for (i = 0, j = 0; i < number.length; i += 3) { + w = number[i] | (number[i + 1] << 8) | (number[i + 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } + return this._strip(); + }; + + function parseHex4Bits (string, index) { + var c = string.charCodeAt(index); + // '0' - '9' + if (c >= 48 && c <= 57) { + return c - 48; + // 'A' - 'F' + } else if (c >= 65 && c <= 70) { + return c - 55; + // 'a' - 'f' + } else if (c >= 97 && c <= 102) { + return c - 87; + } else { + assert(false, 'Invalid character in ' + string); + } + } + + function parseHexByte (string, lowerBound, index) { + var r = parseHex4Bits(string, index); + if (index - 1 >= lowerBound) { + r |= parseHex4Bits(string, index - 1) << 4; + } + return r; + } + + BN.prototype._parseHex = function _parseHex (number, start, endian) { + // Create possibly bigger array to ensure that it fits the number + this.length = Math.ceil((number.length - start) / 6); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + // 24-bits chunks + var off = 0; + var j = 0; + + var w; + if (endian === 'be') { + for (i = number.length - 1; i >= start; i -= 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } + } + } else { + var parseLength = number.length - start; + for (i = parseLength % 2 === 0 ? start + 1 : start; i < number.length; i += 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } + } + } + + this._strip(); + }; + + function parseBase (str, start, end, mul) { + var r = 0; + var b = 0; + var len = Math.min(str.length, end); + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r *= mul; + + // 'a' + if (c >= 49) { + b = c - 49 + 0xa; + + // 'A' + } else if (c >= 17) { + b = c - 17 + 0xa; + + // '0' - '9' + } else { + b = c; + } + assert(c >= 0 && b < mul, 'Invalid character'); + r += b; + } + return r; + } + + BN.prototype._parseBase = function _parseBase (number, base, start) { + // Initialize as zero + this.words = [0]; + this.length = 1; + + // Find length of limb in base + for (var limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= base) { + limbLen++; + } + limbLen--; + limbPow = (limbPow / base) | 0; + + var total = number.length - start; + var mod = total % limbLen; + var end = Math.min(total, total - mod) + start; + + var word = 0; + for (var i = start; i < end; i += limbLen) { + word = parseBase(number, i, i + limbLen, base); + + this.imuln(limbPow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + if (mod !== 0) { + var pow = 1; + word = parseBase(number, i, number.length, base); + + for (i = 0; i < mod; i++) { + pow *= base; + } + + this.imuln(pow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + this._strip(); + }; + + BN.prototype.copy = function copy (dest) { + dest.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + dest.words[i] = this.words[i]; + } + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + function move (dest, src) { + dest.words = src.words; + dest.length = src.length; + dest.negative = src.negative; + dest.red = src.red; + } + + BN.prototype._move = function _move (dest) { + move(dest, this); + }; + + BN.prototype.clone = function clone () { + var r = new BN(null); + this.copy(r); + return r; + }; + + BN.prototype._expand = function _expand (size) { + while (this.length < size) { + this.words[this.length++] = 0; + } + return this; + }; + + // Remove leading `0` from `this` + BN.prototype._strip = function strip () { + while (this.length > 1 && this.words[this.length - 1] === 0) { + this.length--; + } + return this._normSign(); + }; + + BN.prototype._normSign = function _normSign () { + // -0 = 0 + if (this.length === 1 && this.words[0] === 0) { + this.negative = 0; + } + return this; + }; + + // Check Symbol.for because not everywhere where Symbol defined + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Browser_compatibility + if (typeof Symbol !== 'undefined' && typeof Symbol.for === 'function') { + try { + BN.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspect; + } catch (e) { + BN.prototype.inspect = inspect; + } + } else { + BN.prototype.inspect = inspect; + } + + function inspect () { + return (this.red ? ''; + } + + /* + + var zeros = []; + var groupSizes = []; + var groupBases = []; + + var s = ''; + var i = -1; + while (++i < BN.wordSize) { + zeros[i] = s; + s += '0'; + } + groupSizes[0] = 0; + groupSizes[1] = 0; + groupBases[0] = 0; + groupBases[1] = 0; + var base = 2 - 1; + while (++base < 36 + 1) { + var groupSize = 0; + var groupBase = 1; + while (groupBase < (1 << BN.wordSize) / base) { + groupBase *= base; + groupSize += 1; + } + groupSizes[base] = groupSize; + groupBases[base] = groupBase; + } + + */ + + var zeros = [ + '', + '0', + '00', + '000', + '0000', + '00000', + '000000', + '0000000', + '00000000', + '000000000', + '0000000000', + '00000000000', + '000000000000', + '0000000000000', + '00000000000000', + '000000000000000', + '0000000000000000', + '00000000000000000', + '000000000000000000', + '0000000000000000000', + '00000000000000000000', + '000000000000000000000', + '0000000000000000000000', + '00000000000000000000000', + '000000000000000000000000', + '0000000000000000000000000' + ]; + + var groupSizes = [ + 0, 0, + 25, 16, 12, 11, 10, 9, 8, + 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5 + ]; + + var groupBases = [ + 0, 0, + 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216, + 43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625, + 16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, + 6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149, + 24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176 + ]; + + BN.prototype.toString = function toString (base, padding) { + base = base || 10; + padding = padding | 0 || 1; + + var out; + if (base === 16 || base === 'hex') { + out = ''; + var off = 0; + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = this.words[i]; + var word = (((w << off) | carry) & 0xffffff).toString(16); + carry = (w >>> (24 - off)) & 0xffffff; + off += 2; + if (off >= 26) { + off -= 26; + i--; + } + if (carry !== 0 || i !== this.length - 1) { + out = zeros[6 - word.length] + word + out; + } else { + out = word + out; + } + } + if (carry !== 0) { + out = carry.toString(16) + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + if (base === (base | 0) && base >= 2 && base <= 36) { + // var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(base)); + var groupSize = groupSizes[base]; + // var groupBase = Math.pow(base, groupSize); + var groupBase = groupBases[base]; + out = ''; + var c = this.clone(); + c.negative = 0; + while (!c.isZero()) { + var r = c.modrn(groupBase).toString(base); + c = c.idivn(groupBase); + + if (!c.isZero()) { + out = zeros[groupSize - r.length] + r + out; + } else { + out = r + out; + } + } + if (this.isZero()) { + out = '0' + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + assert(false, 'Base should be between 2 and 36'); + }; + + BN.prototype.toNumber = function toNumber () { + var ret = this.words[0]; + if (this.length === 2) { + ret += this.words[1] * 0x4000000; + } else if (this.length === 3 && this.words[2] === 0x01) { + // NOTE: at this stage it is known that the top bit is set + ret += 0x10000000000000 + (this.words[1] * 0x4000000); + } else if (this.length > 2) { + assert(false, 'Number can only safely store up to 53 bits'); + } + return (this.negative !== 0) ? -ret : ret; + }; + + BN.prototype.toJSON = function toJSON () { + return this.toString(16, 2); + }; + + if (Buffer) { + BN.prototype.toBuffer = function toBuffer (endian, length) { + return this.toArrayLike(Buffer, endian, length); + }; + } + + BN.prototype.toArray = function toArray (endian, length) { + return this.toArrayLike(Array, endian, length); + }; + + var allocate = function allocate (ArrayType, size) { + if (ArrayType.allocUnsafe) { + return ArrayType.allocUnsafe(size); + } + return new ArrayType(size); + }; + + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + this._strip(); + + var byteLength = this.byteLength(); + var reqLength = length || Math.max(1, byteLength); + assert(byteLength <= reqLength, 'byte array longer than desired length'); + assert(reqLength > 0, 'Requested array length <= 0'); + + var res = allocate(ArrayType, reqLength); + var postfix = endian === 'le' ? 'LE' : 'BE'; + this['_toArrayLike' + postfix](res, byteLength); + return res; + }; + + BN.prototype._toArrayLikeLE = function _toArrayLikeLE (res, byteLength) { + var position = 0; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position++] = word & 0xff; + if (position < res.length) { + res[position++] = (word >> 8) & 0xff; + } + if (position < res.length) { + res[position++] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position < res.length) { + res[position++] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position < res.length) { + res[position++] = carry; + + while (position < res.length) { + res[position++] = 0; + } + } + }; + + BN.prototype._toArrayLikeBE = function _toArrayLikeBE (res, byteLength) { + var position = res.length - 1; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position--] = word & 0xff; + if (position >= 0) { + res[position--] = (word >> 8) & 0xff; + } + if (position >= 0) { + res[position--] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position >= 0) { + res[position--] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position >= 0) { + res[position--] = carry; + + while (position >= 0) { + res[position--] = 0; + } + } + }; + + if (Math.clz32) { + BN.prototype._countBits = function _countBits (w) { + return 32 - Math.clz32(w); + }; + } else { + BN.prototype._countBits = function _countBits (w) { + var t = w; + var r = 0; + if (t >= 0x1000) { + r += 13; + t >>>= 13; + } + if (t >= 0x40) { + r += 7; + t >>>= 7; + } + if (t >= 0x8) { + r += 4; + t >>>= 4; + } + if (t >= 0x02) { + r += 2; + t >>>= 2; + } + return r + t; + }; + } + + BN.prototype._zeroBits = function _zeroBits (w) { + // Short-cut + if (w === 0) return 26; + + var t = w; + var r = 0; + if ((t & 0x1fff) === 0) { + r += 13; + t >>>= 13; + } + if ((t & 0x7f) === 0) { + r += 7; + t >>>= 7; + } + if ((t & 0xf) === 0) { + r += 4; + t >>>= 4; + } + if ((t & 0x3) === 0) { + r += 2; + t >>>= 2; + } + if ((t & 0x1) === 0) { + r++; + } + return r; + }; + + // Return number of used bits in a BN + BN.prototype.bitLength = function bitLength () { + var w = this.words[this.length - 1]; + var hi = this._countBits(w); + return (this.length - 1) * 26 + hi; + }; + + function toBitArray (num) { + var w = new Array(num.bitLength()); + + for (var bit = 0; bit < w.length; bit++) { + var off = (bit / 26) | 0; + var wbit = bit % 26; + + w[bit] = (num.words[off] >>> wbit) & 0x01; + } + + return w; + } + + // Number of trailing zero bits + BN.prototype.zeroBits = function zeroBits () { + if (this.isZero()) return 0; + + var r = 0; + for (var i = 0; i < this.length; i++) { + var b = this._zeroBits(this.words[i]); + r += b; + if (b !== 26) break; + } + return r; + }; + + BN.prototype.byteLength = function byteLength () { + return Math.ceil(this.bitLength() / 8); + }; + + BN.prototype.toTwos = function toTwos (width) { + if (this.negative !== 0) { + return this.abs().inotn(width).iaddn(1); + } + return this.clone(); + }; + + BN.prototype.fromTwos = function fromTwos (width) { + if (this.testn(width - 1)) { + return this.notn(width).iaddn(1).ineg(); + } + return this.clone(); + }; + + BN.prototype.isNeg = function isNeg () { + return this.negative !== 0; + }; + + // Return negative clone of `this` + BN.prototype.neg = function neg () { + return this.clone().ineg(); + }; + + BN.prototype.ineg = function ineg () { + if (!this.isZero()) { + this.negative ^= 1; + } + + return this; + }; + + // Or `num` with `this` in-place + BN.prototype.iuor = function iuor (num) { + while (this.length < num.length) { + this.words[this.length++] = 0; + } + + for (var i = 0; i < num.length; i++) { + this.words[i] = this.words[i] | num.words[i]; + } + + return this._strip(); + }; + + BN.prototype.ior = function ior (num) { + assert((this.negative | num.negative) === 0); + return this.iuor(num); + }; + + // Or `num` with `this` + BN.prototype.or = function or (num) { + if (this.length > num.length) return this.clone().ior(num); + return num.clone().ior(this); + }; + + BN.prototype.uor = function uor (num) { + if (this.length > num.length) return this.clone().iuor(num); + return num.clone().iuor(this); + }; + + // And `num` with `this` in-place + BN.prototype.iuand = function iuand (num) { + // b = min-length(num, this) + var b; + if (this.length > num.length) { + b = num; + } else { + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = this.words[i] & num.words[i]; + } + + this.length = b.length; + + return this._strip(); + }; + + BN.prototype.iand = function iand (num) { + assert((this.negative | num.negative) === 0); + return this.iuand(num); + }; + + // And `num` with `this` + BN.prototype.and = function and (num) { + if (this.length > num.length) return this.clone().iand(num); + return num.clone().iand(this); + }; + + BN.prototype.uand = function uand (num) { + if (this.length > num.length) return this.clone().iuand(num); + return num.clone().iuand(this); + }; + + // Xor `num` with `this` in-place + BN.prototype.iuxor = function iuxor (num) { + // a.length > b.length + var a; + var b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = a.words[i] ^ b.words[i]; + } + + if (this !== a) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = a.length; + + return this._strip(); + }; + + BN.prototype.ixor = function ixor (num) { + assert((this.negative | num.negative) === 0); + return this.iuxor(num); + }; + + // Xor `num` with `this` + BN.prototype.xor = function xor (num) { + if (this.length > num.length) return this.clone().ixor(num); + return num.clone().ixor(this); + }; + + BN.prototype.uxor = function uxor (num) { + if (this.length > num.length) return this.clone().iuxor(num); + return num.clone().iuxor(this); + }; + + // Not ``this`` with ``width`` bitwidth + BN.prototype.inotn = function inotn (width) { + assert(typeof width === 'number' && width >= 0); + + var bytesNeeded = Math.ceil(width / 26) | 0; + var bitsLeft = width % 26; + + // Extend the buffer with leading zeroes + this._expand(bytesNeeded); + + if (bitsLeft > 0) { + bytesNeeded--; + } + + // Handle complete words + for (var i = 0; i < bytesNeeded; i++) { + this.words[i] = ~this.words[i] & 0x3ffffff; + } + + // Handle the residue + if (bitsLeft > 0) { + this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft)); + } + + // And remove leading zeroes + return this._strip(); + }; + + BN.prototype.notn = function notn (width) { + return this.clone().inotn(width); + }; + + // Set `bit` of `this` + BN.prototype.setn = function setn (bit, val) { + assert(typeof bit === 'number' && bit >= 0); + + var off = (bit / 26) | 0; + var wbit = bit % 26; + + this._expand(off + 1); + + if (val) { + this.words[off] = this.words[off] | (1 << wbit); + } else { + this.words[off] = this.words[off] & ~(1 << wbit); + } + + return this._strip(); + }; + + // Add `num` to `this` in-place + BN.prototype.iadd = function iadd (num) { + var r; + + // negative + positive + if (this.negative !== 0 && num.negative === 0) { + this.negative = 0; + r = this.isub(num); + this.negative ^= 1; + return this._normSign(); + + // positive + negative + } else if (this.negative === 0 && num.negative !== 0) { + num.negative = 0; + r = this.isub(num); + num.negative = 1; + return r._normSign(); + } + + // a.length > b.length + var a, b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) + (b.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + + this.length = a.length; + if (carry !== 0) { + this.words[this.length] = carry; + this.length++; + // Copy the rest of the words + } else if (a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + return this; + }; + + // Add `num` to `this` + BN.prototype.add = function add (num) { + var res; + if (num.negative !== 0 && this.negative === 0) { + num.negative = 0; + res = this.sub(num); + num.negative ^= 1; + return res; + } else if (num.negative === 0 && this.negative !== 0) { + this.negative = 0; + res = num.sub(this); + this.negative = 1; + return res; + } + + if (this.length > num.length) return this.clone().iadd(num); + + return num.clone().iadd(this); + }; + + // Subtract `num` from `this` in-place + BN.prototype.isub = function isub (num) { + // this - (-num) = this + num + if (num.negative !== 0) { + num.negative = 0; + var r = this.iadd(num); + num.negative = 1; + return r._normSign(); + + // -this - num = -(this + num) + } else if (this.negative !== 0) { + this.negative = 0; + this.iadd(num); + this.negative = 1; + return this._normSign(); + } + + // At this point both numbers are positive + var cmp = this.cmp(num); + + // Optimization - zeroify + if (cmp === 0) { + this.negative = 0; + this.length = 1; + this.words[0] = 0; + return this; + } + + // a > b + var a, b; + if (cmp > 0) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) - (b.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + + // Copy rest of the words + if (carry === 0 && i < a.length && a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = Math.max(this.length, i); + + if (a !== this) { + this.negative = 1; + } + + return this._strip(); + }; + + // Subtract `num` from `this` + BN.prototype.sub = function sub (num) { + return this.clone().isub(num); + }; + + function smallMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + var len = (self.length + num.length) | 0; + out.length = len; + len = (len - 1) | 0; + + // Peel one iteration (compiler can't do it, because of code complexity) + var a = self.words[0] | 0; + var b = num.words[0] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + var carry = (r / 0x4000000) | 0; + out.words[0] = lo; + + for (var k = 1; k < len; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = carry >>> 26; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = (k - j) | 0; + a = self.words[i] | 0; + b = num.words[j] | 0; + r = a * b + rword; + ncarry += (r / 0x4000000) | 0; + rword = r & 0x3ffffff; + } + out.words[k] = rword | 0; + carry = ncarry | 0; + } + if (carry !== 0) { + out.words[k] = carry | 0; + } else { + out.length--; + } + + return out._strip(); + } + + // TODO(indutny): it may be reasonable to omit it for users who don't need + // to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit + // multiplication (like elliptic secp256k1). + var comb10MulTo = function comb10MulTo (self, num, out) { + var a = self.words; + var b = num.words; + var o = out.words; + var c = 0; + var lo; + var mid; + var hi; + var a0 = a[0] | 0; + var al0 = a0 & 0x1fff; + var ah0 = a0 >>> 13; + var a1 = a[1] | 0; + var al1 = a1 & 0x1fff; + var ah1 = a1 >>> 13; + var a2 = a[2] | 0; + var al2 = a2 & 0x1fff; + var ah2 = a2 >>> 13; + var a3 = a[3] | 0; + var al3 = a3 & 0x1fff; + var ah3 = a3 >>> 13; + var a4 = a[4] | 0; + var al4 = a4 & 0x1fff; + var ah4 = a4 >>> 13; + var a5 = a[5] | 0; + var al5 = a5 & 0x1fff; + var ah5 = a5 >>> 13; + var a6 = a[6] | 0; + var al6 = a6 & 0x1fff; + var ah6 = a6 >>> 13; + var a7 = a[7] | 0; + var al7 = a7 & 0x1fff; + var ah7 = a7 >>> 13; + var a8 = a[8] | 0; + var al8 = a8 & 0x1fff; + var ah8 = a8 >>> 13; + var a9 = a[9] | 0; + var al9 = a9 & 0x1fff; + var ah9 = a9 >>> 13; + var b0 = b[0] | 0; + var bl0 = b0 & 0x1fff; + var bh0 = b0 >>> 13; + var b1 = b[1] | 0; + var bl1 = b1 & 0x1fff; + var bh1 = b1 >>> 13; + var b2 = b[2] | 0; + var bl2 = b2 & 0x1fff; + var bh2 = b2 >>> 13; + var b3 = b[3] | 0; + var bl3 = b3 & 0x1fff; + var bh3 = b3 >>> 13; + var b4 = b[4] | 0; + var bl4 = b4 & 0x1fff; + var bh4 = b4 >>> 13; + var b5 = b[5] | 0; + var bl5 = b5 & 0x1fff; + var bh5 = b5 >>> 13; + var b6 = b[6] | 0; + var bl6 = b6 & 0x1fff; + var bh6 = b6 >>> 13; + var b7 = b[7] | 0; + var bl7 = b7 & 0x1fff; + var bh7 = b7 >>> 13; + var b8 = b[8] | 0; + var bl8 = b8 & 0x1fff; + var bh8 = b8 >>> 13; + var b9 = b[9] | 0; + var bl9 = b9 & 0x1fff; + var bh9 = b9 >>> 13; + + out.negative = self.negative ^ num.negative; + out.length = 19; + /* k = 0 */ + lo = Math.imul(al0, bl0); + mid = Math.imul(al0, bh0); + mid = (mid + Math.imul(ah0, bl0)) | 0; + hi = Math.imul(ah0, bh0); + var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w0 >>> 26)) | 0; + w0 &= 0x3ffffff; + /* k = 1 */ + lo = Math.imul(al1, bl0); + mid = Math.imul(al1, bh0); + mid = (mid + Math.imul(ah1, bl0)) | 0; + hi = Math.imul(ah1, bh0); + lo = (lo + Math.imul(al0, bl1)) | 0; + mid = (mid + Math.imul(al0, bh1)) | 0; + mid = (mid + Math.imul(ah0, bl1)) | 0; + hi = (hi + Math.imul(ah0, bh1)) | 0; + var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w1 >>> 26)) | 0; + w1 &= 0x3ffffff; + /* k = 2 */ + lo = Math.imul(al2, bl0); + mid = Math.imul(al2, bh0); + mid = (mid + Math.imul(ah2, bl0)) | 0; + hi = Math.imul(ah2, bh0); + lo = (lo + Math.imul(al1, bl1)) | 0; + mid = (mid + Math.imul(al1, bh1)) | 0; + mid = (mid + Math.imul(ah1, bl1)) | 0; + hi = (hi + Math.imul(ah1, bh1)) | 0; + lo = (lo + Math.imul(al0, bl2)) | 0; + mid = (mid + Math.imul(al0, bh2)) | 0; + mid = (mid + Math.imul(ah0, bl2)) | 0; + hi = (hi + Math.imul(ah0, bh2)) | 0; + var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w2 >>> 26)) | 0; + w2 &= 0x3ffffff; + /* k = 3 */ + lo = Math.imul(al3, bl0); + mid = Math.imul(al3, bh0); + mid = (mid + Math.imul(ah3, bl0)) | 0; + hi = Math.imul(ah3, bh0); + lo = (lo + Math.imul(al2, bl1)) | 0; + mid = (mid + Math.imul(al2, bh1)) | 0; + mid = (mid + Math.imul(ah2, bl1)) | 0; + hi = (hi + Math.imul(ah2, bh1)) | 0; + lo = (lo + Math.imul(al1, bl2)) | 0; + mid = (mid + Math.imul(al1, bh2)) | 0; + mid = (mid + Math.imul(ah1, bl2)) | 0; + hi = (hi + Math.imul(ah1, bh2)) | 0; + lo = (lo + Math.imul(al0, bl3)) | 0; + mid = (mid + Math.imul(al0, bh3)) | 0; + mid = (mid + Math.imul(ah0, bl3)) | 0; + hi = (hi + Math.imul(ah0, bh3)) | 0; + var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w3 >>> 26)) | 0; + w3 &= 0x3ffffff; + /* k = 4 */ + lo = Math.imul(al4, bl0); + mid = Math.imul(al4, bh0); + mid = (mid + Math.imul(ah4, bl0)) | 0; + hi = Math.imul(ah4, bh0); + lo = (lo + Math.imul(al3, bl1)) | 0; + mid = (mid + Math.imul(al3, bh1)) | 0; + mid = (mid + Math.imul(ah3, bl1)) | 0; + hi = (hi + Math.imul(ah3, bh1)) | 0; + lo = (lo + Math.imul(al2, bl2)) | 0; + mid = (mid + Math.imul(al2, bh2)) | 0; + mid = (mid + Math.imul(ah2, bl2)) | 0; + hi = (hi + Math.imul(ah2, bh2)) | 0; + lo = (lo + Math.imul(al1, bl3)) | 0; + mid = (mid + Math.imul(al1, bh3)) | 0; + mid = (mid + Math.imul(ah1, bl3)) | 0; + hi = (hi + Math.imul(ah1, bh3)) | 0; + lo = (lo + Math.imul(al0, bl4)) | 0; + mid = (mid + Math.imul(al0, bh4)) | 0; + mid = (mid + Math.imul(ah0, bl4)) | 0; + hi = (hi + Math.imul(ah0, bh4)) | 0; + var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w4 >>> 26)) | 0; + w4 &= 0x3ffffff; + /* k = 5 */ + lo = Math.imul(al5, bl0); + mid = Math.imul(al5, bh0); + mid = (mid + Math.imul(ah5, bl0)) | 0; + hi = Math.imul(ah5, bh0); + lo = (lo + Math.imul(al4, bl1)) | 0; + mid = (mid + Math.imul(al4, bh1)) | 0; + mid = (mid + Math.imul(ah4, bl1)) | 0; + hi = (hi + Math.imul(ah4, bh1)) | 0; + lo = (lo + Math.imul(al3, bl2)) | 0; + mid = (mid + Math.imul(al3, bh2)) | 0; + mid = (mid + Math.imul(ah3, bl2)) | 0; + hi = (hi + Math.imul(ah3, bh2)) | 0; + lo = (lo + Math.imul(al2, bl3)) | 0; + mid = (mid + Math.imul(al2, bh3)) | 0; + mid = (mid + Math.imul(ah2, bl3)) | 0; + hi = (hi + Math.imul(ah2, bh3)) | 0; + lo = (lo + Math.imul(al1, bl4)) | 0; + mid = (mid + Math.imul(al1, bh4)) | 0; + mid = (mid + Math.imul(ah1, bl4)) | 0; + hi = (hi + Math.imul(ah1, bh4)) | 0; + lo = (lo + Math.imul(al0, bl5)) | 0; + mid = (mid + Math.imul(al0, bh5)) | 0; + mid = (mid + Math.imul(ah0, bl5)) | 0; + hi = (hi + Math.imul(ah0, bh5)) | 0; + var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w5 >>> 26)) | 0; + w5 &= 0x3ffffff; + /* k = 6 */ + lo = Math.imul(al6, bl0); + mid = Math.imul(al6, bh0); + mid = (mid + Math.imul(ah6, bl0)) | 0; + hi = Math.imul(ah6, bh0); + lo = (lo + Math.imul(al5, bl1)) | 0; + mid = (mid + Math.imul(al5, bh1)) | 0; + mid = (mid + Math.imul(ah5, bl1)) | 0; + hi = (hi + Math.imul(ah5, bh1)) | 0; + lo = (lo + Math.imul(al4, bl2)) | 0; + mid = (mid + Math.imul(al4, bh2)) | 0; + mid = (mid + Math.imul(ah4, bl2)) | 0; + hi = (hi + Math.imul(ah4, bh2)) | 0; + lo = (lo + Math.imul(al3, bl3)) | 0; + mid = (mid + Math.imul(al3, bh3)) | 0; + mid = (mid + Math.imul(ah3, bl3)) | 0; + hi = (hi + Math.imul(ah3, bh3)) | 0; + lo = (lo + Math.imul(al2, bl4)) | 0; + mid = (mid + Math.imul(al2, bh4)) | 0; + mid = (mid + Math.imul(ah2, bl4)) | 0; + hi = (hi + Math.imul(ah2, bh4)) | 0; + lo = (lo + Math.imul(al1, bl5)) | 0; + mid = (mid + Math.imul(al1, bh5)) | 0; + mid = (mid + Math.imul(ah1, bl5)) | 0; + hi = (hi + Math.imul(ah1, bh5)) | 0; + lo = (lo + Math.imul(al0, bl6)) | 0; + mid = (mid + Math.imul(al0, bh6)) | 0; + mid = (mid + Math.imul(ah0, bl6)) | 0; + hi = (hi + Math.imul(ah0, bh6)) | 0; + var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w6 >>> 26)) | 0; + w6 &= 0x3ffffff; + /* k = 7 */ + lo = Math.imul(al7, bl0); + mid = Math.imul(al7, bh0); + mid = (mid + Math.imul(ah7, bl0)) | 0; + hi = Math.imul(ah7, bh0); + lo = (lo + Math.imul(al6, bl1)) | 0; + mid = (mid + Math.imul(al6, bh1)) | 0; + mid = (mid + Math.imul(ah6, bl1)) | 0; + hi = (hi + Math.imul(ah6, bh1)) | 0; + lo = (lo + Math.imul(al5, bl2)) | 0; + mid = (mid + Math.imul(al5, bh2)) | 0; + mid = (mid + Math.imul(ah5, bl2)) | 0; + hi = (hi + Math.imul(ah5, bh2)) | 0; + lo = (lo + Math.imul(al4, bl3)) | 0; + mid = (mid + Math.imul(al4, bh3)) | 0; + mid = (mid + Math.imul(ah4, bl3)) | 0; + hi = (hi + Math.imul(ah4, bh3)) | 0; + lo = (lo + Math.imul(al3, bl4)) | 0; + mid = (mid + Math.imul(al3, bh4)) | 0; + mid = (mid + Math.imul(ah3, bl4)) | 0; + hi = (hi + Math.imul(ah3, bh4)) | 0; + lo = (lo + Math.imul(al2, bl5)) | 0; + mid = (mid + Math.imul(al2, bh5)) | 0; + mid = (mid + Math.imul(ah2, bl5)) | 0; + hi = (hi + Math.imul(ah2, bh5)) | 0; + lo = (lo + Math.imul(al1, bl6)) | 0; + mid = (mid + Math.imul(al1, bh6)) | 0; + mid = (mid + Math.imul(ah1, bl6)) | 0; + hi = (hi + Math.imul(ah1, bh6)) | 0; + lo = (lo + Math.imul(al0, bl7)) | 0; + mid = (mid + Math.imul(al0, bh7)) | 0; + mid = (mid + Math.imul(ah0, bl7)) | 0; + hi = (hi + Math.imul(ah0, bh7)) | 0; + var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w7 >>> 26)) | 0; + w7 &= 0x3ffffff; + /* k = 8 */ + lo = Math.imul(al8, bl0); + mid = Math.imul(al8, bh0); + mid = (mid + Math.imul(ah8, bl0)) | 0; + hi = Math.imul(ah8, bh0); + lo = (lo + Math.imul(al7, bl1)) | 0; + mid = (mid + Math.imul(al7, bh1)) | 0; + mid = (mid + Math.imul(ah7, bl1)) | 0; + hi = (hi + Math.imul(ah7, bh1)) | 0; + lo = (lo + Math.imul(al6, bl2)) | 0; + mid = (mid + Math.imul(al6, bh2)) | 0; + mid = (mid + Math.imul(ah6, bl2)) | 0; + hi = (hi + Math.imul(ah6, bh2)) | 0; + lo = (lo + Math.imul(al5, bl3)) | 0; + mid = (mid + Math.imul(al5, bh3)) | 0; + mid = (mid + Math.imul(ah5, bl3)) | 0; + hi = (hi + Math.imul(ah5, bh3)) | 0; + lo = (lo + Math.imul(al4, bl4)) | 0; + mid = (mid + Math.imul(al4, bh4)) | 0; + mid = (mid + Math.imul(ah4, bl4)) | 0; + hi = (hi + Math.imul(ah4, bh4)) | 0; + lo = (lo + Math.imul(al3, bl5)) | 0; + mid = (mid + Math.imul(al3, bh5)) | 0; + mid = (mid + Math.imul(ah3, bl5)) | 0; + hi = (hi + Math.imul(ah3, bh5)) | 0; + lo = (lo + Math.imul(al2, bl6)) | 0; + mid = (mid + Math.imul(al2, bh6)) | 0; + mid = (mid + Math.imul(ah2, bl6)) | 0; + hi = (hi + Math.imul(ah2, bh6)) | 0; + lo = (lo + Math.imul(al1, bl7)) | 0; + mid = (mid + Math.imul(al1, bh7)) | 0; + mid = (mid + Math.imul(ah1, bl7)) | 0; + hi = (hi + Math.imul(ah1, bh7)) | 0; + lo = (lo + Math.imul(al0, bl8)) | 0; + mid = (mid + Math.imul(al0, bh8)) | 0; + mid = (mid + Math.imul(ah0, bl8)) | 0; + hi = (hi + Math.imul(ah0, bh8)) | 0; + var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w8 >>> 26)) | 0; + w8 &= 0x3ffffff; + /* k = 9 */ + lo = Math.imul(al9, bl0); + mid = Math.imul(al9, bh0); + mid = (mid + Math.imul(ah9, bl0)) | 0; + hi = Math.imul(ah9, bh0); + lo = (lo + Math.imul(al8, bl1)) | 0; + mid = (mid + Math.imul(al8, bh1)) | 0; + mid = (mid + Math.imul(ah8, bl1)) | 0; + hi = (hi + Math.imul(ah8, bh1)) | 0; + lo = (lo + Math.imul(al7, bl2)) | 0; + mid = (mid + Math.imul(al7, bh2)) | 0; + mid = (mid + Math.imul(ah7, bl2)) | 0; + hi = (hi + Math.imul(ah7, bh2)) | 0; + lo = (lo + Math.imul(al6, bl3)) | 0; + mid = (mid + Math.imul(al6, bh3)) | 0; + mid = (mid + Math.imul(ah6, bl3)) | 0; + hi = (hi + Math.imul(ah6, bh3)) | 0; + lo = (lo + Math.imul(al5, bl4)) | 0; + mid = (mid + Math.imul(al5, bh4)) | 0; + mid = (mid + Math.imul(ah5, bl4)) | 0; + hi = (hi + Math.imul(ah5, bh4)) | 0; + lo = (lo + Math.imul(al4, bl5)) | 0; + mid = (mid + Math.imul(al4, bh5)) | 0; + mid = (mid + Math.imul(ah4, bl5)) | 0; + hi = (hi + Math.imul(ah4, bh5)) | 0; + lo = (lo + Math.imul(al3, bl6)) | 0; + mid = (mid + Math.imul(al3, bh6)) | 0; + mid = (mid + Math.imul(ah3, bl6)) | 0; + hi = (hi + Math.imul(ah3, bh6)) | 0; + lo = (lo + Math.imul(al2, bl7)) | 0; + mid = (mid + Math.imul(al2, bh7)) | 0; + mid = (mid + Math.imul(ah2, bl7)) | 0; + hi = (hi + Math.imul(ah2, bh7)) | 0; + lo = (lo + Math.imul(al1, bl8)) | 0; + mid = (mid + Math.imul(al1, bh8)) | 0; + mid = (mid + Math.imul(ah1, bl8)) | 0; + hi = (hi + Math.imul(ah1, bh8)) | 0; + lo = (lo + Math.imul(al0, bl9)) | 0; + mid = (mid + Math.imul(al0, bh9)) | 0; + mid = (mid + Math.imul(ah0, bl9)) | 0; + hi = (hi + Math.imul(ah0, bh9)) | 0; + var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w9 >>> 26)) | 0; + w9 &= 0x3ffffff; + /* k = 10 */ + lo = Math.imul(al9, bl1); + mid = Math.imul(al9, bh1); + mid = (mid + Math.imul(ah9, bl1)) | 0; + hi = Math.imul(ah9, bh1); + lo = (lo + Math.imul(al8, bl2)) | 0; + mid = (mid + Math.imul(al8, bh2)) | 0; + mid = (mid + Math.imul(ah8, bl2)) | 0; + hi = (hi + Math.imul(ah8, bh2)) | 0; + lo = (lo + Math.imul(al7, bl3)) | 0; + mid = (mid + Math.imul(al7, bh3)) | 0; + mid = (mid + Math.imul(ah7, bl3)) | 0; + hi = (hi + Math.imul(ah7, bh3)) | 0; + lo = (lo + Math.imul(al6, bl4)) | 0; + mid = (mid + Math.imul(al6, bh4)) | 0; + mid = (mid + Math.imul(ah6, bl4)) | 0; + hi = (hi + Math.imul(ah6, bh4)) | 0; + lo = (lo + Math.imul(al5, bl5)) | 0; + mid = (mid + Math.imul(al5, bh5)) | 0; + mid = (mid + Math.imul(ah5, bl5)) | 0; + hi = (hi + Math.imul(ah5, bh5)) | 0; + lo = (lo + Math.imul(al4, bl6)) | 0; + mid = (mid + Math.imul(al4, bh6)) | 0; + mid = (mid + Math.imul(ah4, bl6)) | 0; + hi = (hi + Math.imul(ah4, bh6)) | 0; + lo = (lo + Math.imul(al3, bl7)) | 0; + mid = (mid + Math.imul(al3, bh7)) | 0; + mid = (mid + Math.imul(ah3, bl7)) | 0; + hi = (hi + Math.imul(ah3, bh7)) | 0; + lo = (lo + Math.imul(al2, bl8)) | 0; + mid = (mid + Math.imul(al2, bh8)) | 0; + mid = (mid + Math.imul(ah2, bl8)) | 0; + hi = (hi + Math.imul(ah2, bh8)) | 0; + lo = (lo + Math.imul(al1, bl9)) | 0; + mid = (mid + Math.imul(al1, bh9)) | 0; + mid = (mid + Math.imul(ah1, bl9)) | 0; + hi = (hi + Math.imul(ah1, bh9)) | 0; + var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w10 >>> 26)) | 0; + w10 &= 0x3ffffff; + /* k = 11 */ + lo = Math.imul(al9, bl2); + mid = Math.imul(al9, bh2); + mid = (mid + Math.imul(ah9, bl2)) | 0; + hi = Math.imul(ah9, bh2); + lo = (lo + Math.imul(al8, bl3)) | 0; + mid = (mid + Math.imul(al8, bh3)) | 0; + mid = (mid + Math.imul(ah8, bl3)) | 0; + hi = (hi + Math.imul(ah8, bh3)) | 0; + lo = (lo + Math.imul(al7, bl4)) | 0; + mid = (mid + Math.imul(al7, bh4)) | 0; + mid = (mid + Math.imul(ah7, bl4)) | 0; + hi = (hi + Math.imul(ah7, bh4)) | 0; + lo = (lo + Math.imul(al6, bl5)) | 0; + mid = (mid + Math.imul(al6, bh5)) | 0; + mid = (mid + Math.imul(ah6, bl5)) | 0; + hi = (hi + Math.imul(ah6, bh5)) | 0; + lo = (lo + Math.imul(al5, bl6)) | 0; + mid = (mid + Math.imul(al5, bh6)) | 0; + mid = (mid + Math.imul(ah5, bl6)) | 0; + hi = (hi + Math.imul(ah5, bh6)) | 0; + lo = (lo + Math.imul(al4, bl7)) | 0; + mid = (mid + Math.imul(al4, bh7)) | 0; + mid = (mid + Math.imul(ah4, bl7)) | 0; + hi = (hi + Math.imul(ah4, bh7)) | 0; + lo = (lo + Math.imul(al3, bl8)) | 0; + mid = (mid + Math.imul(al3, bh8)) | 0; + mid = (mid + Math.imul(ah3, bl8)) | 0; + hi = (hi + Math.imul(ah3, bh8)) | 0; + lo = (lo + Math.imul(al2, bl9)) | 0; + mid = (mid + Math.imul(al2, bh9)) | 0; + mid = (mid + Math.imul(ah2, bl9)) | 0; + hi = (hi + Math.imul(ah2, bh9)) | 0; + var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w11 >>> 26)) | 0; + w11 &= 0x3ffffff; + /* k = 12 */ + lo = Math.imul(al9, bl3); + mid = Math.imul(al9, bh3); + mid = (mid + Math.imul(ah9, bl3)) | 0; + hi = Math.imul(ah9, bh3); + lo = (lo + Math.imul(al8, bl4)) | 0; + mid = (mid + Math.imul(al8, bh4)) | 0; + mid = (mid + Math.imul(ah8, bl4)) | 0; + hi = (hi + Math.imul(ah8, bh4)) | 0; + lo = (lo + Math.imul(al7, bl5)) | 0; + mid = (mid + Math.imul(al7, bh5)) | 0; + mid = (mid + Math.imul(ah7, bl5)) | 0; + hi = (hi + Math.imul(ah7, bh5)) | 0; + lo = (lo + Math.imul(al6, bl6)) | 0; + mid = (mid + Math.imul(al6, bh6)) | 0; + mid = (mid + Math.imul(ah6, bl6)) | 0; + hi = (hi + Math.imul(ah6, bh6)) | 0; + lo = (lo + Math.imul(al5, bl7)) | 0; + mid = (mid + Math.imul(al5, bh7)) | 0; + mid = (mid + Math.imul(ah5, bl7)) | 0; + hi = (hi + Math.imul(ah5, bh7)) | 0; + lo = (lo + Math.imul(al4, bl8)) | 0; + mid = (mid + Math.imul(al4, bh8)) | 0; + mid = (mid + Math.imul(ah4, bl8)) | 0; + hi = (hi + Math.imul(ah4, bh8)) | 0; + lo = (lo + Math.imul(al3, bl9)) | 0; + mid = (mid + Math.imul(al3, bh9)) | 0; + mid = (mid + Math.imul(ah3, bl9)) | 0; + hi = (hi + Math.imul(ah3, bh9)) | 0; + var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w12 >>> 26)) | 0; + w12 &= 0x3ffffff; + /* k = 13 */ + lo = Math.imul(al9, bl4); + mid = Math.imul(al9, bh4); + mid = (mid + Math.imul(ah9, bl4)) | 0; + hi = Math.imul(ah9, bh4); + lo = (lo + Math.imul(al8, bl5)) | 0; + mid = (mid + Math.imul(al8, bh5)) | 0; + mid = (mid + Math.imul(ah8, bl5)) | 0; + hi = (hi + Math.imul(ah8, bh5)) | 0; + lo = (lo + Math.imul(al7, bl6)) | 0; + mid = (mid + Math.imul(al7, bh6)) | 0; + mid = (mid + Math.imul(ah7, bl6)) | 0; + hi = (hi + Math.imul(ah7, bh6)) | 0; + lo = (lo + Math.imul(al6, bl7)) | 0; + mid = (mid + Math.imul(al6, bh7)) | 0; + mid = (mid + Math.imul(ah6, bl7)) | 0; + hi = (hi + Math.imul(ah6, bh7)) | 0; + lo = (lo + Math.imul(al5, bl8)) | 0; + mid = (mid + Math.imul(al5, bh8)) | 0; + mid = (mid + Math.imul(ah5, bl8)) | 0; + hi = (hi + Math.imul(ah5, bh8)) | 0; + lo = (lo + Math.imul(al4, bl9)) | 0; + mid = (mid + Math.imul(al4, bh9)) | 0; + mid = (mid + Math.imul(ah4, bl9)) | 0; + hi = (hi + Math.imul(ah4, bh9)) | 0; + var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w13 >>> 26)) | 0; + w13 &= 0x3ffffff; + /* k = 14 */ + lo = Math.imul(al9, bl5); + mid = Math.imul(al9, bh5); + mid = (mid + Math.imul(ah9, bl5)) | 0; + hi = Math.imul(ah9, bh5); + lo = (lo + Math.imul(al8, bl6)) | 0; + mid = (mid + Math.imul(al8, bh6)) | 0; + mid = (mid + Math.imul(ah8, bl6)) | 0; + hi = (hi + Math.imul(ah8, bh6)) | 0; + lo = (lo + Math.imul(al7, bl7)) | 0; + mid = (mid + Math.imul(al7, bh7)) | 0; + mid = (mid + Math.imul(ah7, bl7)) | 0; + hi = (hi + Math.imul(ah7, bh7)) | 0; + lo = (lo + Math.imul(al6, bl8)) | 0; + mid = (mid + Math.imul(al6, bh8)) | 0; + mid = (mid + Math.imul(ah6, bl8)) | 0; + hi = (hi + Math.imul(ah6, bh8)) | 0; + lo = (lo + Math.imul(al5, bl9)) | 0; + mid = (mid + Math.imul(al5, bh9)) | 0; + mid = (mid + Math.imul(ah5, bl9)) | 0; + hi = (hi + Math.imul(ah5, bh9)) | 0; + var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w14 >>> 26)) | 0; + w14 &= 0x3ffffff; + /* k = 15 */ + lo = Math.imul(al9, bl6); + mid = Math.imul(al9, bh6); + mid = (mid + Math.imul(ah9, bl6)) | 0; + hi = Math.imul(ah9, bh6); + lo = (lo + Math.imul(al8, bl7)) | 0; + mid = (mid + Math.imul(al8, bh7)) | 0; + mid = (mid + Math.imul(ah8, bl7)) | 0; + hi = (hi + Math.imul(ah8, bh7)) | 0; + lo = (lo + Math.imul(al7, bl8)) | 0; + mid = (mid + Math.imul(al7, bh8)) | 0; + mid = (mid + Math.imul(ah7, bl8)) | 0; + hi = (hi + Math.imul(ah7, bh8)) | 0; + lo = (lo + Math.imul(al6, bl9)) | 0; + mid = (mid + Math.imul(al6, bh9)) | 0; + mid = (mid + Math.imul(ah6, bl9)) | 0; + hi = (hi + Math.imul(ah6, bh9)) | 0; + var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w15 >>> 26)) | 0; + w15 &= 0x3ffffff; + /* k = 16 */ + lo = Math.imul(al9, bl7); + mid = Math.imul(al9, bh7); + mid = (mid + Math.imul(ah9, bl7)) | 0; + hi = Math.imul(ah9, bh7); + lo = (lo + Math.imul(al8, bl8)) | 0; + mid = (mid + Math.imul(al8, bh8)) | 0; + mid = (mid + Math.imul(ah8, bl8)) | 0; + hi = (hi + Math.imul(ah8, bh8)) | 0; + lo = (lo + Math.imul(al7, bl9)) | 0; + mid = (mid + Math.imul(al7, bh9)) | 0; + mid = (mid + Math.imul(ah7, bl9)) | 0; + hi = (hi + Math.imul(ah7, bh9)) | 0; + var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w16 >>> 26)) | 0; + w16 &= 0x3ffffff; + /* k = 17 */ + lo = Math.imul(al9, bl8); + mid = Math.imul(al9, bh8); + mid = (mid + Math.imul(ah9, bl8)) | 0; + hi = Math.imul(ah9, bh8); + lo = (lo + Math.imul(al8, bl9)) | 0; + mid = (mid + Math.imul(al8, bh9)) | 0; + mid = (mid + Math.imul(ah8, bl9)) | 0; + hi = (hi + Math.imul(ah8, bh9)) | 0; + var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w17 >>> 26)) | 0; + w17 &= 0x3ffffff; + /* k = 18 */ + lo = Math.imul(al9, bl9); + mid = Math.imul(al9, bh9); + mid = (mid + Math.imul(ah9, bl9)) | 0; + hi = Math.imul(ah9, bh9); + var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w18 >>> 26)) | 0; + w18 &= 0x3ffffff; + o[0] = w0; + o[1] = w1; + o[2] = w2; + o[3] = w3; + o[4] = w4; + o[5] = w5; + o[6] = w6; + o[7] = w7; + o[8] = w8; + o[9] = w9; + o[10] = w10; + o[11] = w11; + o[12] = w12; + o[13] = w13; + o[14] = w14; + o[15] = w15; + o[16] = w16; + o[17] = w17; + o[18] = w18; + if (c !== 0) { + o[19] = c; + out.length++; + } + return out; + }; + + // Polyfill comb + if (!Math.imul) { + comb10MulTo = smallMulTo; + } + + function bigMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + out.length = self.length + num.length; + + var carry = 0; + var hncarry = 0; + for (var k = 0; k < out.length - 1; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = hncarry; + hncarry = 0; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = k - j; + var a = self.words[i] | 0; + var b = num.words[j] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0; + lo = (lo + rword) | 0; + rword = lo & 0x3ffffff; + ncarry = (ncarry + (lo >>> 26)) | 0; + + hncarry += ncarry >>> 26; + ncarry &= 0x3ffffff; + } + out.words[k] = rword; + carry = ncarry; + ncarry = hncarry; + } + if (carry !== 0) { + out.words[k] = carry; + } else { + out.length--; + } + + return out._strip(); + } + + function jumboMulTo (self, num, out) { + // Temporary disable, see https://github.com/indutny/bn.js/issues/211 + // var fftm = new FFTM(); + // return fftm.mulp(self, num, out); + return bigMulTo(self, num, out); + } + + BN.prototype.mulTo = function mulTo (num, out) { + var res; + var len = this.length + num.length; + if (this.length === 10 && num.length === 10) { + res = comb10MulTo(this, num, out); + } else if (len < 63) { + res = smallMulTo(this, num, out); + } else if (len < 1024) { + res = bigMulTo(this, num, out); + } else { + res = jumboMulTo(this, num, out); + } + + return res; + }; + + // Cooley-Tukey algorithm for FFT + // slightly revisited to rely on looping instead of recursion + + function FFTM (x, y) { + this.x = x; + this.y = y; + } + + FFTM.prototype.makeRBT = function makeRBT (N) { + var t = new Array(N); + var l = BN.prototype._countBits(N) - 1; + for (var i = 0; i < N; i++) { + t[i] = this.revBin(i, l, N); + } + + return t; + }; + + // Returns binary-reversed representation of `x` + FFTM.prototype.revBin = function revBin (x, l, N) { + if (x === 0 || x === N - 1) return x; + + var rb = 0; + for (var i = 0; i < l; i++) { + rb |= (x & 1) << (l - i - 1); + x >>= 1; + } + + return rb; + }; + + // Performs "tweedling" phase, therefore 'emulating' + // behaviour of the recursive algorithm + FFTM.prototype.permute = function permute (rbt, rws, iws, rtws, itws, N) { + for (var i = 0; i < N; i++) { + rtws[i] = rws[rbt[i]]; + itws[i] = iws[rbt[i]]; + } + }; + + FFTM.prototype.transform = function transform (rws, iws, rtws, itws, N, rbt) { + this.permute(rbt, rws, iws, rtws, itws, N); + + for (var s = 1; s < N; s <<= 1) { + var l = s << 1; + + var rtwdf = Math.cos(2 * Math.PI / l); + var itwdf = Math.sin(2 * Math.PI / l); + + for (var p = 0; p < N; p += l) { + var rtwdf_ = rtwdf; + var itwdf_ = itwdf; + + for (var j = 0; j < s; j++) { + var re = rtws[p + j]; + var ie = itws[p + j]; + + var ro = rtws[p + j + s]; + var io = itws[p + j + s]; + + var rx = rtwdf_ * ro - itwdf_ * io; + + io = rtwdf_ * io + itwdf_ * ro; + ro = rx; + + rtws[p + j] = re + ro; + itws[p + j] = ie + io; + + rtws[p + j + s] = re - ro; + itws[p + j + s] = ie - io; + + /* jshint maxdepth : false */ + if (j !== l) { + rx = rtwdf * rtwdf_ - itwdf * itwdf_; + + itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_; + rtwdf_ = rx; + } + } + } + } + }; + + FFTM.prototype.guessLen13b = function guessLen13b (n, m) { + var N = Math.max(m, n) | 1; + var odd = N & 1; + var i = 0; + for (N = N / 2 | 0; N; N = N >>> 1) { + i++; + } + + return 1 << i + 1 + odd; + }; + + FFTM.prototype.conjugate = function conjugate (rws, iws, N) { + if (N <= 1) return; + + for (var i = 0; i < N / 2; i++) { + var t = rws[i]; + + rws[i] = rws[N - i - 1]; + rws[N - i - 1] = t; + + t = iws[i]; + + iws[i] = -iws[N - i - 1]; + iws[N - i - 1] = -t; + } + }; + + FFTM.prototype.normalize13b = function normalize13b (ws, N) { + var carry = 0; + for (var i = 0; i < N / 2; i++) { + var w = Math.round(ws[2 * i + 1] / N) * 0x2000 + + Math.round(ws[2 * i] / N) + + carry; + + ws[i] = w & 0x3ffffff; + + if (w < 0x4000000) { + carry = 0; + } else { + carry = w / 0x4000000 | 0; + } + } + + return ws; + }; + + FFTM.prototype.convert13b = function convert13b (ws, len, rws, N) { + var carry = 0; + for (var i = 0; i < len; i++) { + carry = carry + (ws[i] | 0); + + rws[2 * i] = carry & 0x1fff; carry = carry >>> 13; + rws[2 * i + 1] = carry & 0x1fff; carry = carry >>> 13; + } + + // Pad with zeroes + for (i = 2 * len; i < N; ++i) { + rws[i] = 0; + } + + assert(carry === 0); + assert((carry & ~0x1fff) === 0); + }; + + FFTM.prototype.stub = function stub (N) { + var ph = new Array(N); + for (var i = 0; i < N; i++) { + ph[i] = 0; + } + + return ph; + }; + + FFTM.prototype.mulp = function mulp (x, y, out) { + var N = 2 * this.guessLen13b(x.length, y.length); + + var rbt = this.makeRBT(N); + + var _ = this.stub(N); + + var rws = new Array(N); + var rwst = new Array(N); + var iwst = new Array(N); + + var nrws = new Array(N); + var nrwst = new Array(N); + var niwst = new Array(N); + + var rmws = out.words; + rmws.length = N; + + this.convert13b(x.words, x.length, rws, N); + this.convert13b(y.words, y.length, nrws, N); + + this.transform(rws, _, rwst, iwst, N, rbt); + this.transform(nrws, _, nrwst, niwst, N, rbt); + + for (var i = 0; i < N; i++) { + var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i]; + iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i]; + rwst[i] = rx; + } + + this.conjugate(rwst, iwst, N); + this.transform(rwst, iwst, rmws, _, N, rbt); + this.conjugate(rmws, _, N); + this.normalize13b(rmws, N); + + out.negative = x.negative ^ y.negative; + out.length = x.length + y.length; + return out._strip(); + }; + + // Multiply `this` by `num` + BN.prototype.mul = function mul (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return this.mulTo(num, out); + }; + + // Multiply employing FFT + BN.prototype.mulf = function mulf (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return jumboMulTo(this, num, out); + }; + + // In-place Multiplication + BN.prototype.imul = function imul (num) { + return this.clone().mulTo(num, this); + }; + + BN.prototype.imuln = function imuln (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(typeof num === 'number'); + assert(num < 0x4000000); + + // Carry + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = (this.words[i] | 0) * num; + var lo = (w & 0x3ffffff) + (carry & 0x3ffffff); + carry >>= 26; + carry += (w / 0x4000000) | 0; + // NOTE: lo is 27bit maximum + carry += lo >>> 26; + this.words[i] = lo & 0x3ffffff; + } + + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.muln = function muln (num) { + return this.clone().imuln(num); + }; + + // `this` * `this` + BN.prototype.sqr = function sqr () { + return this.mul(this); + }; + + // `this` * `this` in-place + BN.prototype.isqr = function isqr () { + return this.imul(this.clone()); + }; + + // Math.pow(`this`, `num`) + BN.prototype.pow = function pow (num) { + var w = toBitArray(num); + if (w.length === 0) return new BN(1); + + // Skip leading zeroes + var res = this; + for (var i = 0; i < w.length; i++, res = res.sqr()) { + if (w[i] !== 0) break; + } + + if (++i < w.length) { + for (var q = res.sqr(); i < w.length; i++, q = q.sqr()) { + if (w[i] === 0) continue; + + res = res.mul(q); + } + } + + return res; + }; + + // Shift-left in-place + BN.prototype.iushln = function iushln (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + var carryMask = (0x3ffffff >>> (26 - r)) << (26 - r); + var i; + + if (r !== 0) { + var carry = 0; + + for (i = 0; i < this.length; i++) { + var newCarry = this.words[i] & carryMask; + var c = ((this.words[i] | 0) - newCarry) << r; + this.words[i] = c | carry; + carry = newCarry >>> (26 - r); + } + + if (carry) { + this.words[i] = carry; + this.length++; + } + } + + if (s !== 0) { + for (i = this.length - 1; i >= 0; i--) { + this.words[i + s] = this.words[i]; + } + + for (i = 0; i < s; i++) { + this.words[i] = 0; + } + + this.length += s; + } + + return this._strip(); + }; + + BN.prototype.ishln = function ishln (bits) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushln(bits); + }; + + // Shift-right in-place + // NOTE: `hint` is a lowest bit before trailing zeroes + // NOTE: if `extended` is present - it will be filled with destroyed bits + BN.prototype.iushrn = function iushrn (bits, hint, extended) { + assert(typeof bits === 'number' && bits >= 0); + var h; + if (hint) { + h = (hint - (hint % 26)) / 26; + } else { + h = 0; + } + + var r = bits % 26; + var s = Math.min((bits - r) / 26, this.length); + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + var maskedWords = extended; + + h -= s; + h = Math.max(0, h); + + // Extended mode, copy masked part + if (maskedWords) { + for (var i = 0; i < s; i++) { + maskedWords.words[i] = this.words[i]; + } + maskedWords.length = s; + } + + if (s === 0) { + // No-op, we should not move anything at all + } else if (this.length > s) { + this.length -= s; + for (i = 0; i < this.length; i++) { + this.words[i] = this.words[i + s]; + } + } else { + this.words[0] = 0; + this.length = 1; + } + + var carry = 0; + for (i = this.length - 1; i >= 0 && (carry !== 0 || i >= h); i--) { + var word = this.words[i] | 0; + this.words[i] = (carry << (26 - r)) | (word >>> r); + carry = word & mask; + } + + // Push carried bits as a mask + if (maskedWords && carry !== 0) { + maskedWords.words[maskedWords.length++] = carry; + } + + if (this.length === 0) { + this.words[0] = 0; + this.length = 1; + } + + return this._strip(); + }; + + BN.prototype.ishrn = function ishrn (bits, hint, extended) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushrn(bits, hint, extended); + }; + + // Shift-left + BN.prototype.shln = function shln (bits) { + return this.clone().ishln(bits); + }; + + BN.prototype.ushln = function ushln (bits) { + return this.clone().iushln(bits); + }; + + // Shift-right + BN.prototype.shrn = function shrn (bits) { + return this.clone().ishrn(bits); + }; + + BN.prototype.ushrn = function ushrn (bits) { + return this.clone().iushrn(bits); + }; + + // Test if n bit is set + BN.prototype.testn = function testn (bit) { + assert(typeof bit === 'number' && bit >= 0); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) return false; + + // Check bit and return + var w = this.words[s]; + + return !!(w & q); + }; + + // Return only lowers bits of number (in-place) + BN.prototype.imaskn = function imaskn (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + + assert(this.negative === 0, 'imaskn works only with positive numbers'); + + if (this.length <= s) { + return this; + } + + if (r !== 0) { + s++; + } + this.length = Math.min(s, this.length); + + if (r !== 0) { + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + this.words[this.length - 1] &= mask; + } + + return this._strip(); + }; + + // Return only lowers bits of number + BN.prototype.maskn = function maskn (bits) { + return this.clone().imaskn(bits); + }; + + // Add plain number `num` to `this` + BN.prototype.iaddn = function iaddn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.isubn(-num); + + // Possible sign change + if (this.negative !== 0) { + if (this.length === 1 && (this.words[0] | 0) <= num) { + this.words[0] = num - (this.words[0] | 0); + this.negative = 0; + return this; + } + + this.negative = 0; + this.isubn(num); + this.negative = 1; + return this; + } + + // Add without checks + return this._iaddn(num); + }; + + BN.prototype._iaddn = function _iaddn (num) { + this.words[0] += num; + + // Carry + for (var i = 0; i < this.length && this.words[i] >= 0x4000000; i++) { + this.words[i] -= 0x4000000; + if (i === this.length - 1) { + this.words[i + 1] = 1; + } else { + this.words[i + 1]++; + } + } + this.length = Math.max(this.length, i + 1); + + return this; + }; + + // Subtract plain number `num` from `this` + BN.prototype.isubn = function isubn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.iaddn(-num); + + if (this.negative !== 0) { + this.negative = 0; + this.iaddn(num); + this.negative = 1; + return this; + } + + this.words[0] -= num; + + if (this.length === 1 && this.words[0] < 0) { + this.words[0] = -this.words[0]; + this.negative = 1; + } else { + // Carry + for (var i = 0; i < this.length && this.words[i] < 0; i++) { + this.words[i] += 0x4000000; + this.words[i + 1] -= 1; + } + } + + return this._strip(); + }; + + BN.prototype.addn = function addn (num) { + return this.clone().iaddn(num); + }; + + BN.prototype.subn = function subn (num) { + return this.clone().isubn(num); + }; + + BN.prototype.iabs = function iabs () { + this.negative = 0; + + return this; + }; + + BN.prototype.abs = function abs () { + return this.clone().iabs(); + }; + + BN.prototype._ishlnsubmul = function _ishlnsubmul (num, mul, shift) { + var len = num.length + shift; + var i; + + this._expand(len); + + var w; + var carry = 0; + for (i = 0; i < num.length; i++) { + w = (this.words[i + shift] | 0) + carry; + var right = (num.words[i] | 0) * mul; + w -= right & 0x3ffffff; + carry = (w >> 26) - ((right / 0x4000000) | 0); + this.words[i + shift] = w & 0x3ffffff; + } + for (; i < this.length - shift; i++) { + w = (this.words[i + shift] | 0) + carry; + carry = w >> 26; + this.words[i + shift] = w & 0x3ffffff; + } + + if (carry === 0) return this._strip(); + + // Subtraction overflow + assert(carry === -1); + carry = 0; + for (i = 0; i < this.length; i++) { + w = -(this.words[i] | 0) + carry; + carry = w >> 26; + this.words[i] = w & 0x3ffffff; + } + this.negative = 1; + + return this._strip(); + }; + + BN.prototype._wordDiv = function _wordDiv (num, mode) { + var shift = this.length - num.length; + + var a = this.clone(); + var b = num; + + // Normalize + var bhi = b.words[b.length - 1] | 0; + var bhiBits = this._countBits(bhi); + shift = 26 - bhiBits; + if (shift !== 0) { + b = b.ushln(shift); + a.iushln(shift); + bhi = b.words[b.length - 1] | 0; + } + + // Initialize quotient + var m = a.length - b.length; + var q; + + if (mode !== 'mod') { + q = new BN(null); + q.length = m + 1; + q.words = new Array(q.length); + for (var i = 0; i < q.length; i++) { + q.words[i] = 0; + } + } + + var diff = a.clone()._ishlnsubmul(b, 1, m); + if (diff.negative === 0) { + a = diff; + if (q) { + q.words[m] = 1; + } + } + + for (var j = m - 1; j >= 0; j--) { + var qj = (a.words[b.length + j] | 0) * 0x4000000 + + (a.words[b.length + j - 1] | 0); + + // NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max + // (0x7ffffff) + qj = Math.min((qj / bhi) | 0, 0x3ffffff); + + a._ishlnsubmul(b, qj, j); + while (a.negative !== 0) { + qj--; + a.negative = 0; + a._ishlnsubmul(b, 1, j); + if (!a.isZero()) { + a.negative ^= 1; + } + } + if (q) { + q.words[j] = qj; + } + } + if (q) { + q._strip(); + } + a._strip(); + + // Denormalize + if (mode !== 'div' && shift !== 0) { + a.iushrn(shift); + } + + return { + div: q || null, + mod: a + }; + }; + + // NOTE: 1) `mode` can be set to `mod` to request mod only, + // to `div` to request div only, or be absent to + // request both div & mod + // 2) `positive` is true if unsigned mod is requested + BN.prototype.divmod = function divmod (num, mode, positive) { + assert(!num.isZero()); + + if (this.isZero()) { + return { + div: new BN(0), + mod: new BN(0) + }; + } + + var div, mod, res; + if (this.negative !== 0 && num.negative === 0) { + res = this.neg().divmod(num, mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.iadd(num); + } + } + + return { + div: div, + mod: mod + }; + } + + if (this.negative === 0 && num.negative !== 0) { + res = this.divmod(num.neg(), mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + return { + div: div, + mod: res.mod + }; + } + + if ((this.negative & num.negative) !== 0) { + res = this.neg().divmod(num.neg(), mode); + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.isub(num); + } + } + + return { + div: res.div, + mod: mod + }; + } + + // Both numbers are positive at this point + + // Strip both numbers to approximate shift value + if (num.length > this.length || this.cmp(num) < 0) { + return { + div: new BN(0), + mod: this + }; + } + + // Very short reduction + if (num.length === 1) { + if (mode === 'div') { + return { + div: this.divn(num.words[0]), + mod: null + }; + } + + if (mode === 'mod') { + return { + div: null, + mod: new BN(this.modrn(num.words[0])) + }; + } + + return { + div: this.divn(num.words[0]), + mod: new BN(this.modrn(num.words[0])) + }; + } + + return this._wordDiv(num, mode); + }; + + // Find `this` / `num` + BN.prototype.div = function div (num) { + return this.divmod(num, 'div', false).div; + }; + + // Find `this` % `num` + BN.prototype.mod = function mod (num) { + return this.divmod(num, 'mod', false).mod; + }; + + BN.prototype.umod = function umod (num) { + return this.divmod(num, 'mod', true).mod; + }; + + // Find Round(`this` / `num`) + BN.prototype.divRound = function divRound (num) { + var dm = this.divmod(num); + + // Fast case - exact division + if (dm.mod.isZero()) return dm.div; + + var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod; + + var half = num.ushrn(1); + var r2 = num.andln(1); + var cmp = mod.cmp(half); + + // Round down + if (cmp < 0 || (r2 === 1 && cmp === 0)) return dm.div; + + // Round up + return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1); + }; + + BN.prototype.modrn = function modrn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + var p = (1 << 26) % num; + + var acc = 0; + for (var i = this.length - 1; i >= 0; i--) { + acc = (p * acc + (this.words[i] | 0)) % num; + } + + return isNegNum ? -acc : acc; + }; + + // WARNING: DEPRECATED + BN.prototype.modn = function modn (num) { + return this.modrn(num); + }; + + // In-place division by number + BN.prototype.idivn = function idivn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + + var carry = 0; + for (var i = this.length - 1; i >= 0; i--) { + var w = (this.words[i] | 0) + carry * 0x4000000; + this.words[i] = (w / num) | 0; + carry = w % num; + } + + this._strip(); + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.divn = function divn (num) { + return this.clone().idivn(num); + }; + + BN.prototype.egcd = function egcd (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var x = this; + var y = p.clone(); + + if (x.negative !== 0) { + x = x.umod(p); + } else { + x = x.clone(); + } + + // A * x + B * y = x + var A = new BN(1); + var B = new BN(0); + + // C * x + D * y = y + var C = new BN(0); + var D = new BN(1); + + var g = 0; + + while (x.isEven() && y.isEven()) { + x.iushrn(1); + y.iushrn(1); + ++g; + } + + var yp = y.clone(); + var xp = x.clone(); + + while (!x.isZero()) { + for (var i = 0, im = 1; (x.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + x.iushrn(i); + while (i-- > 0) { + if (A.isOdd() || B.isOdd()) { + A.iadd(yp); + B.isub(xp); + } + + A.iushrn(1); + B.iushrn(1); + } + } + + for (var j = 0, jm = 1; (y.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + y.iushrn(j); + while (j-- > 0) { + if (C.isOdd() || D.isOdd()) { + C.iadd(yp); + D.isub(xp); + } + + C.iushrn(1); + D.iushrn(1); + } + } + + if (x.cmp(y) >= 0) { + x.isub(y); + A.isub(C); + B.isub(D); + } else { + y.isub(x); + C.isub(A); + D.isub(B); + } + } + + return { + a: C, + b: D, + gcd: y.iushln(g) + }; + }; + + // This is reduced incarnation of the binary EEA + // above, designated to invert members of the + // _prime_ fields F(p) at a maximal speed + BN.prototype._invmp = function _invmp (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var a = this; + var b = p.clone(); + + if (a.negative !== 0) { + a = a.umod(p); + } else { + a = a.clone(); + } + + var x1 = new BN(1); + var x2 = new BN(0); + + var delta = b.clone(); + + while (a.cmpn(1) > 0 && b.cmpn(1) > 0) { + for (var i = 0, im = 1; (a.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + a.iushrn(i); + while (i-- > 0) { + if (x1.isOdd()) { + x1.iadd(delta); + } + + x1.iushrn(1); + } + } + + for (var j = 0, jm = 1; (b.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + b.iushrn(j); + while (j-- > 0) { + if (x2.isOdd()) { + x2.iadd(delta); + } + + x2.iushrn(1); + } + } + + if (a.cmp(b) >= 0) { + a.isub(b); + x1.isub(x2); + } else { + b.isub(a); + x2.isub(x1); + } + } + + var res; + if (a.cmpn(1) === 0) { + res = x1; + } else { + res = x2; + } + + if (res.cmpn(0) < 0) { + res.iadd(p); + } + + return res; + }; + + BN.prototype.gcd = function gcd (num) { + if (this.isZero()) return num.abs(); + if (num.isZero()) return this.abs(); + + var a = this.clone(); + var b = num.clone(); + a.negative = 0; + b.negative = 0; + + // Remove common factor of two + for (var shift = 0; a.isEven() && b.isEven(); shift++) { + a.iushrn(1); + b.iushrn(1); + } + + do { + while (a.isEven()) { + a.iushrn(1); + } + while (b.isEven()) { + b.iushrn(1); + } + + var r = a.cmp(b); + if (r < 0) { + // Swap `a` and `b` to make `a` always bigger than `b` + var t = a; + a = b; + b = t; + } else if (r === 0 || b.cmpn(1) === 0) { + break; + } + + a.isub(b); + } while (true); + + return b.iushln(shift); + }; + + // Invert number in the field F(num) + BN.prototype.invm = function invm (num) { + return this.egcd(num).a.umod(num); + }; + + BN.prototype.isEven = function isEven () { + return (this.words[0] & 1) === 0; + }; + + BN.prototype.isOdd = function isOdd () { + return (this.words[0] & 1) === 1; + }; + + // And first word and num + BN.prototype.andln = function andln (num) { + return this.words[0] & num; + }; + + // Increment at the bit position in-line + BN.prototype.bincn = function bincn (bit) { + assert(typeof bit === 'number'); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) { + this._expand(s + 1); + this.words[s] |= q; + return this; + } + + // Add bit and propagate, if needed + var carry = q; + for (var i = s; carry !== 0 && i < this.length; i++) { + var w = this.words[i] | 0; + w += carry; + carry = w >>> 26; + w &= 0x3ffffff; + this.words[i] = w; + } + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + return this; + }; + + BN.prototype.isZero = function isZero () { + return this.length === 1 && this.words[0] === 0; + }; + + BN.prototype.cmpn = function cmpn (num) { + var negative = num < 0; + + if (this.negative !== 0 && !negative) return -1; + if (this.negative === 0 && negative) return 1; + + this._strip(); + + var res; + if (this.length > 1) { + res = 1; + } else { + if (negative) { + num = -num; + } + + assert(num <= 0x3ffffff, 'Number is too big'); + + var w = this.words[0] | 0; + res = w === num ? 0 : w < num ? -1 : 1; + } + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Compare two numbers and return: + // 1 - if `this` > `num` + // 0 - if `this` == `num` + // -1 - if `this` < `num` + BN.prototype.cmp = function cmp (num) { + if (this.negative !== 0 && num.negative === 0) return -1; + if (this.negative === 0 && num.negative !== 0) return 1; + + var res = this.ucmp(num); + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Unsigned comparison + BN.prototype.ucmp = function ucmp (num) { + // At this point both numbers have the same sign + if (this.length > num.length) return 1; + if (this.length < num.length) return -1; + + var res = 0; + for (var i = this.length - 1; i >= 0; i--) { + var a = this.words[i] | 0; + var b = num.words[i] | 0; + + if (a === b) continue; + if (a < b) { + res = -1; + } else if (a > b) { + res = 1; + } + break; + } + return res; + }; + + BN.prototype.gtn = function gtn (num) { + return this.cmpn(num) === 1; + }; + + BN.prototype.gt = function gt (num) { + return this.cmp(num) === 1; + }; + + BN.prototype.gten = function gten (num) { + return this.cmpn(num) >= 0; + }; + + BN.prototype.gte = function gte (num) { + return this.cmp(num) >= 0; + }; + + BN.prototype.ltn = function ltn (num) { + return this.cmpn(num) === -1; + }; + + BN.prototype.lt = function lt (num) { + return this.cmp(num) === -1; + }; + + BN.prototype.lten = function lten (num) { + return this.cmpn(num) <= 0; + }; + + BN.prototype.lte = function lte (num) { + return this.cmp(num) <= 0; + }; + + BN.prototype.eqn = function eqn (num) { + return this.cmpn(num) === 0; + }; + + BN.prototype.eq = function eq (num) { + return this.cmp(num) === 0; + }; + + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + BN.red = function red (num) { + return new Red(num); + }; + + BN.prototype.toRed = function toRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + assert(this.negative === 0, 'red works only with positives'); + return ctx.convertTo(this)._forceRed(ctx); + }; + + BN.prototype.fromRed = function fromRed () { + assert(this.red, 'fromRed works only with numbers in reduction context'); + return this.red.convertFrom(this); + }; + + BN.prototype._forceRed = function _forceRed (ctx) { + this.red = ctx; + return this; + }; + + BN.prototype.forceRed = function forceRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + return this._forceRed(ctx); + }; + + BN.prototype.redAdd = function redAdd (num) { + assert(this.red, 'redAdd works only with red numbers'); + return this.red.add(this, num); + }; + + BN.prototype.redIAdd = function redIAdd (num) { + assert(this.red, 'redIAdd works only with red numbers'); + return this.red.iadd(this, num); + }; + + BN.prototype.redSub = function redSub (num) { + assert(this.red, 'redSub works only with red numbers'); + return this.red.sub(this, num); + }; + + BN.prototype.redISub = function redISub (num) { + assert(this.red, 'redISub works only with red numbers'); + return this.red.isub(this, num); + }; + + BN.prototype.redShl = function redShl (num) { + assert(this.red, 'redShl works only with red numbers'); + return this.red.shl(this, num); + }; + + BN.prototype.redMul = function redMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.mul(this, num); + }; + + BN.prototype.redIMul = function redIMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.imul(this, num); + }; + + BN.prototype.redSqr = function redSqr () { + assert(this.red, 'redSqr works only with red numbers'); + this.red._verify1(this); + return this.red.sqr(this); + }; + + BN.prototype.redISqr = function redISqr () { + assert(this.red, 'redISqr works only with red numbers'); + this.red._verify1(this); + return this.red.isqr(this); + }; + + // Square root over p + BN.prototype.redSqrt = function redSqrt () { + assert(this.red, 'redSqrt works only with red numbers'); + this.red._verify1(this); + return this.red.sqrt(this); + }; + + BN.prototype.redInvm = function redInvm () { + assert(this.red, 'redInvm works only with red numbers'); + this.red._verify1(this); + return this.red.invm(this); + }; + + // Return negative clone of `this` % `red modulo` + BN.prototype.redNeg = function redNeg () { + assert(this.red, 'redNeg works only with red numbers'); + this.red._verify1(this); + return this.red.neg(this); + }; + + BN.prototype.redPow = function redPow (num) { + assert(this.red && !num.red, 'redPow(normalNum)'); + this.red._verify1(this); + return this.red.pow(this, num); + }; + + // Prime numbers with efficient reduction + var primes = { + k256: null, + p224: null, + p192: null, + p25519: null + }; + + // Pseudo-Mersenne prime + function MPrime (name, p) { + // P = 2 ^ N - K + this.name = name; + this.p = new BN(p, 16); + this.n = this.p.bitLength(); + this.k = new BN(1).iushln(this.n).isub(this.p); + + this.tmp = this._tmp(); + } + + MPrime.prototype._tmp = function _tmp () { + var tmp = new BN(null); + tmp.words = new Array(Math.ceil(this.n / 13)); + return tmp; + }; + + MPrime.prototype.ireduce = function ireduce (num) { + // Assumes that `num` is less than `P^2` + // num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P) + var r = num; + var rlen; + + do { + this.split(r, this.tmp); + r = this.imulK(r); + r = r.iadd(this.tmp); + rlen = r.bitLength(); + } while (rlen > this.n); + + var cmp = rlen < this.n ? -1 : r.ucmp(this.p); + if (cmp === 0) { + r.words[0] = 0; + r.length = 1; + } else if (cmp > 0) { + r.isub(this.p); + } else { + if (r.strip !== undefined) { + // r is a BN v4 instance + r.strip(); + } else { + // r is a BN v5 instance + r._strip(); + } + } + + return r; + }; + + MPrime.prototype.split = function split (input, out) { + input.iushrn(this.n, 0, out); + }; + + MPrime.prototype.imulK = function imulK (num) { + return num.imul(this.k); + }; + + function K256 () { + MPrime.call( + this, + 'k256', + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f'); + } + inherits(K256, MPrime); + + K256.prototype.split = function split (input, output) { + // 256 = 9 * 26 + 22 + var mask = 0x3fffff; + + var outLen = Math.min(input.length, 9); + for (var i = 0; i < outLen; i++) { + output.words[i] = input.words[i]; + } + output.length = outLen; + + if (input.length <= 9) { + input.words[0] = 0; + input.length = 1; + return; + } + + // Shift by 9 limbs + var prev = input.words[9]; + output.words[output.length++] = prev & mask; + + for (i = 10; i < input.length; i++) { + var next = input.words[i] | 0; + input.words[i - 10] = ((next & mask) << 4) | (prev >>> 22); + prev = next; + } + prev >>>= 22; + input.words[i - 10] = prev; + if (prev === 0 && input.length > 10) { + input.length -= 10; + } else { + input.length -= 9; + } + }; + + K256.prototype.imulK = function imulK (num) { + // K = 0x1000003d1 = [ 0x40, 0x3d1 ] + num.words[num.length] = 0; + num.words[num.length + 1] = 0; + num.length += 2; + + // bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390 + var lo = 0; + for (var i = 0; i < num.length; i++) { + var w = num.words[i] | 0; + lo += w * 0x3d1; + num.words[i] = lo & 0x3ffffff; + lo = w * 0x40 + ((lo / 0x4000000) | 0); + } + + // Fast length reduction + if (num.words[num.length - 1] === 0) { + num.length--; + if (num.words[num.length - 1] === 0) { + num.length--; + } + } + return num; + }; + + function P224 () { + MPrime.call( + this, + 'p224', + 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001'); + } + inherits(P224, MPrime); + + function P192 () { + MPrime.call( + this, + 'p192', + 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff'); + } + inherits(P192, MPrime); + + function P25519 () { + // 2 ^ 255 - 19 + MPrime.call( + this, + '25519', + '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed'); + } + inherits(P25519, MPrime); + + P25519.prototype.imulK = function imulK (num) { + // K = 0x13 + var carry = 0; + for (var i = 0; i < num.length; i++) { + var hi = (num.words[i] | 0) * 0x13 + carry; + var lo = hi & 0x3ffffff; + hi >>>= 26; + + num.words[i] = lo; + carry = hi; + } + if (carry !== 0) { + num.words[num.length++] = carry; + } + return num; + }; + + // Exported mostly for testing purposes, use plain name instead + BN._prime = function prime (name) { + // Cached version of prime + if (primes[name]) return primes[name]; + + var prime; + if (name === 'k256') { + prime = new K256(); + } else if (name === 'p224') { + prime = new P224(); + } else if (name === 'p192') { + prime = new P192(); + } else if (name === 'p25519') { + prime = new P25519(); + } else { + throw new Error('Unknown prime ' + name); + } + primes[name] = prime; + + return prime; + }; + + // + // Base reduction engine + // + function Red (m) { + if (typeof m === 'string') { + var prime = BN._prime(m); + this.m = prime.p; + this.prime = prime; + } else { + assert(m.gtn(1), 'modulus must be greater than 1'); + this.m = m; + this.prime = null; + } + } + + Red.prototype._verify1 = function _verify1 (a) { + assert(a.negative === 0, 'red works only with positives'); + assert(a.red, 'red works only with red numbers'); + }; + + Red.prototype._verify2 = function _verify2 (a, b) { + assert((a.negative | b.negative) === 0, 'red works only with positives'); + assert(a.red && a.red === b.red, + 'red works only with red numbers'); + }; + + Red.prototype.imod = function imod (a) { + if (this.prime) return this.prime.ireduce(a)._forceRed(this); + + move(a, a.umod(this.m)._forceRed(this)); + return a; + }; + + Red.prototype.neg = function neg (a) { + if (a.isZero()) { + return a.clone(); + } + + return this.m.sub(a)._forceRed(this); + }; + + Red.prototype.add = function add (a, b) { + this._verify2(a, b); + + var res = a.add(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.iadd = function iadd (a, b) { + this._verify2(a, b); + + var res = a.iadd(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res; + }; + + Red.prototype.sub = function sub (a, b) { + this._verify2(a, b); + + var res = a.sub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.isub = function isub (a, b) { + this._verify2(a, b); + + var res = a.isub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res; + }; + + Red.prototype.shl = function shl (a, num) { + this._verify1(a); + return this.imod(a.ushln(num)); + }; + + Red.prototype.imul = function imul (a, b) { + this._verify2(a, b); + return this.imod(a.imul(b)); + }; + + Red.prototype.mul = function mul (a, b) { + this._verify2(a, b); + return this.imod(a.mul(b)); + }; + + Red.prototype.isqr = function isqr (a) { + return this.imul(a, a.clone()); + }; + + Red.prototype.sqr = function sqr (a) { + return this.mul(a, a); + }; + + Red.prototype.sqrt = function sqrt (a) { + if (a.isZero()) return a.clone(); + + var mod3 = this.m.andln(3); + assert(mod3 % 2 === 1); + + // Fast case + if (mod3 === 3) { + var pow = this.m.add(new BN(1)).iushrn(2); + return this.pow(a, pow); + } + + // Tonelli-Shanks algorithm (Totally unoptimized and slow) + // + // Find Q and S, that Q * 2 ^ S = (P - 1) + var q = this.m.subn(1); + var s = 0; + while (!q.isZero() && q.andln(1) === 0) { + s++; + q.iushrn(1); + } + assert(!q.isZero()); + + var one = new BN(1).toRed(this); + var nOne = one.redNeg(); + + // Find quadratic non-residue + // NOTE: Max is such because of generalized Riemann hypothesis. + var lpow = this.m.subn(1).iushrn(1); + var z = this.m.bitLength(); + z = new BN(2 * z * z).toRed(this); + + while (this.pow(z, lpow).cmp(nOne) !== 0) { + z.redIAdd(nOne); + } + + var c = this.pow(z, q); + var r = this.pow(a, q.addn(1).iushrn(1)); + var t = this.pow(a, q); + var m = s; + while (t.cmp(one) !== 0) { + var tmp = t; + for (var i = 0; tmp.cmp(one) !== 0; i++) { + tmp = tmp.redSqr(); + } + assert(i < m); + var b = this.pow(c, new BN(1).iushln(m - i - 1)); + + r = r.redMul(b); + c = b.redSqr(); + t = t.redMul(c); + m = i; + } + + return r; + }; + + Red.prototype.invm = function invm (a) { + var inv = a._invmp(this.m); + if (inv.negative !== 0) { + inv.negative = 0; + return this.imod(inv).redNeg(); + } else { + return this.imod(inv); + } + }; + + Red.prototype.pow = function pow (a, num) { + if (num.isZero()) return new BN(1).toRed(this); + if (num.cmpn(1) === 0) return a.clone(); + + var windowSize = 4; + var wnd = new Array(1 << windowSize); + wnd[0] = new BN(1).toRed(this); + wnd[1] = a; + for (var i = 2; i < wnd.length; i++) { + wnd[i] = this.mul(wnd[i - 1], a); + } + + var res = wnd[0]; + var current = 0; + var currentLen = 0; + var start = num.bitLength() % 26; + if (start === 0) { + start = 26; + } + + for (i = num.length - 1; i >= 0; i--) { + var word = num.words[i]; + for (var j = start - 1; j >= 0; j--) { + var bit = (word >> j) & 1; + if (res !== wnd[0]) { + res = this.sqr(res); + } + + if (bit === 0 && current === 0) { + currentLen = 0; + continue; + } + + current <<= 1; + current |= bit; + currentLen++; + if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue; + + res = this.mul(res, wnd[current]); + currentLen = 0; + current = 0; + } + start = 26; + } + + return res; + }; + + Red.prototype.convertTo = function convertTo (num) { + var r = num.umod(this.m); + + return r === num ? r.clone() : r; + }; + + Red.prototype.convertFrom = function convertFrom (num) { + var res = num.clone(); + res.red = null; + return res; + }; + + // + // Montgomery method engine + // + + BN.mont = function mont (num) { + return new Mont(num); + }; + + function Mont (m) { + Red.call(this, m); + + this.shift = this.m.bitLength(); + if (this.shift % 26 !== 0) { + this.shift += 26 - (this.shift % 26); + } + + this.r = new BN(1).iushln(this.shift); + this.r2 = this.imod(this.r.sqr()); + this.rinv = this.r._invmp(this.m); + + this.minv = this.rinv.mul(this.r).isubn(1).div(this.m); + this.minv = this.minv.umod(this.r); + this.minv = this.r.sub(this.minv); + } + inherits(Mont, Red); + + Mont.prototype.convertTo = function convertTo (num) { + return this.imod(num.ushln(this.shift)); + }; + + Mont.prototype.convertFrom = function convertFrom (num) { + var r = this.imod(num.mul(this.rinv)); + r.red = null; + return r; + }; + + Mont.prototype.imul = function imul (a, b) { + if (a.isZero() || b.isZero()) { + a.words[0] = 0; + a.length = 1; + return a; + } + + var t = a.imul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.mul = function mul (a, b) { + if (a.isZero() || b.isZero()) return new BN(0)._forceRed(this); + + var t = a.mul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.invm = function invm (a) { + // (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R + var res = this.imod(a._invmp(this.m).mul(this.r2)); + return res._forceRed(this); + }; +})(typeof module === 'undefined' || module, this); + +},{"buffer":20}],43:[function(require,module,exports){ +module.exports = require('./browser/algorithms.json') + +},{"./browser/algorithms.json":44}],44:[function(require,module,exports){ +module.exports={ + "sha224WithRSAEncryption": { + "sign": "rsa", + "hash": "sha224", + "id": "302d300d06096086480165030402040500041c" + }, + "RSA-SHA224": { + "sign": "ecdsa/rsa", + "hash": "sha224", + "id": "302d300d06096086480165030402040500041c" + }, + "sha256WithRSAEncryption": { + "sign": "rsa", + "hash": "sha256", + "id": "3031300d060960864801650304020105000420" + }, + "RSA-SHA256": { + "sign": "ecdsa/rsa", + "hash": "sha256", + "id": "3031300d060960864801650304020105000420" + }, + "sha384WithRSAEncryption": { + "sign": "rsa", + "hash": "sha384", + "id": "3041300d060960864801650304020205000430" + }, + "RSA-SHA384": { + "sign": "ecdsa/rsa", + "hash": "sha384", + "id": "3041300d060960864801650304020205000430" + }, + "sha512WithRSAEncryption": { + "sign": "rsa", + "hash": "sha512", + "id": "3051300d060960864801650304020305000440" + }, + "RSA-SHA512": { + "sign": "ecdsa/rsa", + "hash": "sha512", + "id": "3051300d060960864801650304020305000440" + }, + "RSA-SHA1": { + "sign": "rsa", + "hash": "sha1", + "id": "3021300906052b0e03021a05000414" + }, + "ecdsa-with-SHA1": { + "sign": "ecdsa", + "hash": "sha1", + "id": "" + }, + "sha256": { + "sign": "ecdsa", + "hash": "sha256", + "id": "" + }, + "sha224": { + "sign": "ecdsa", + "hash": "sha224", + "id": "" + }, + "sha384": { + "sign": "ecdsa", + "hash": "sha384", + "id": "" + }, + "sha512": { + "sign": "ecdsa", + "hash": "sha512", + "id": "" + }, + "DSA-SHA": { + "sign": "dsa", + "hash": "sha1", + "id": "" + }, + "DSA-SHA1": { + "sign": "dsa", + "hash": "sha1", + "id": "" + }, + "DSA": { + "sign": "dsa", + "hash": "sha1", + "id": "" + }, + "DSA-WITH-SHA224": { + "sign": "dsa", + "hash": "sha224", + "id": "" + }, + "DSA-SHA224": { + "sign": "dsa", + "hash": "sha224", + "id": "" + }, + "DSA-WITH-SHA256": { + "sign": "dsa", + "hash": "sha256", + "id": "" + }, + "DSA-SHA256": { + "sign": "dsa", + "hash": "sha256", + "id": "" + }, + "DSA-WITH-SHA384": { + "sign": "dsa", + "hash": "sha384", + "id": "" + }, + "DSA-SHA384": { + "sign": "dsa", + "hash": "sha384", + "id": "" + }, + "DSA-WITH-SHA512": { + "sign": "dsa", + "hash": "sha512", + "id": "" + }, + "DSA-SHA512": { + "sign": "dsa", + "hash": "sha512", + "id": "" + }, + "DSA-RIPEMD160": { + "sign": "dsa", + "hash": "rmd160", + "id": "" + }, + "ripemd160WithRSA": { + "sign": "rsa", + "hash": "rmd160", + "id": "3021300906052b2403020105000414" + }, + "RSA-RIPEMD160": { + "sign": "rsa", + "hash": "rmd160", + "id": "3021300906052b2403020105000414" + }, + "md5WithRSAEncryption": { + "sign": "rsa", + "hash": "md5", + "id": "3020300c06082a864886f70d020505000410" + }, + "RSA-MD5": { + "sign": "rsa", + "hash": "md5", + "id": "3020300c06082a864886f70d020505000410" + } +} + +},{}],45:[function(require,module,exports){ +module.exports={ + "1.3.132.0.10": "secp256k1", + "1.3.132.0.33": "p224", + "1.2.840.10045.3.1.1": "p192", + "1.2.840.10045.3.1.7": "p256", + "1.3.132.0.34": "p384", + "1.3.132.0.35": "p521" +} + +},{}],46:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer +var createHash = require('create-hash') +var stream = require('readable-stream') +var inherits = require('inherits') +var sign = require('./sign') +var verify = require('./verify') + +var algorithms = require('./algorithms.json') +Object.keys(algorithms).forEach(function (key) { + algorithms[key].id = Buffer.from(algorithms[key].id, 'hex') + algorithms[key.toLowerCase()] = algorithms[key] +}) + +function Sign (algorithm) { + stream.Writable.call(this) + + var data = algorithms[algorithm] + if (!data) throw new Error('Unknown message digest') + + this._hashType = data.hash + this._hash = createHash(data.hash) + this._tag = data.id + this._signType = data.sign +} +inherits(Sign, stream.Writable) + +Sign.prototype._write = function _write (data, _, done) { + this._hash.update(data) + done() +} + +Sign.prototype.update = function update (data, enc) { + if (typeof data === 'string') data = Buffer.from(data, enc) + + this._hash.update(data) + return this +} + +Sign.prototype.sign = function signMethod (key, enc) { + this.end() + var hash = this._hash.digest() + var sig = sign(hash, key, this._hashType, this._signType, this._tag) + + return enc ? sig.toString(enc) : sig +} + +function Verify (algorithm) { + stream.Writable.call(this) + + var data = algorithms[algorithm] + if (!data) throw new Error('Unknown message digest') + + this._hash = createHash(data.hash) + this._tag = data.id + this._signType = data.sign +} +inherits(Verify, stream.Writable) + +Verify.prototype._write = function _write (data, _, done) { + this._hash.update(data) + done() +} + +Verify.prototype.update = function update (data, enc) { + if (typeof data === 'string') data = Buffer.from(data, enc) + + this._hash.update(data) + return this +} + +Verify.prototype.verify = function verifyMethod (key, sig, enc) { + if (typeof sig === 'string') sig = Buffer.from(sig, enc) + + this.end() + var hash = this._hash.digest() + return verify(sig, hash, key, this._signType, this._tag) +} + +function createSign (algorithm) { + return new Sign(algorithm) +} + +function createVerify (algorithm) { + return new Verify(algorithm) +} + +module.exports = { + Sign: createSign, + Verify: createVerify, + createSign: createSign, + createVerify: createVerify +} + +},{"./algorithms.json":44,"./sign":47,"./verify":48,"create-hash":74,"inherits":146,"readable-stream":64,"safe-buffer":250}],47:[function(require,module,exports){ +// much of this based on https://github.com/indutny/self-signed/blob/gh-pages/lib/rsa.js +var Buffer = require('safe-buffer').Buffer +var createHmac = require('create-hmac') +var crt = require('browserify-rsa') +var EC = require('elliptic').ec +var BN = require('bn.js') +var parseKeys = require('parse-asn1') +var curves = require('./curves.json') + +function sign (hash, key, hashType, signType, tag) { + var priv = parseKeys(key) + if (priv.curve) { + // rsa keys can be interpreted as ecdsa ones in openssl + if (signType !== 'ecdsa' && signType !== 'ecdsa/rsa') throw new Error('wrong private key type') + return ecSign(hash, priv) + } else if (priv.type === 'dsa') { + if (signType !== 'dsa') throw new Error('wrong private key type') + return dsaSign(hash, priv, hashType) + } else { + if (signType !== 'rsa' && signType !== 'ecdsa/rsa') throw new Error('wrong private key type') + } + hash = Buffer.concat([tag, hash]) + var len = priv.modulus.byteLength() + var pad = [0, 1] + while (hash.length + pad.length + 1 < len) pad.push(0xff) + pad.push(0x00) + var i = -1 + while (++i < hash.length) pad.push(hash[i]) + + var out = crt(pad, priv) + return out +} + +function ecSign (hash, priv) { + var curveId = curves[priv.curve.join('.')] + if (!curveId) throw new Error('unknown curve ' + priv.curve.join('.')) + + var curve = new EC(curveId) + var key = curve.keyFromPrivate(priv.privateKey) + var out = key.sign(hash) + + return Buffer.from(out.toDER()) +} + +function dsaSign (hash, priv, algo) { + var x = priv.params.priv_key + var p = priv.params.p + var q = priv.params.q + var g = priv.params.g + var r = new BN(0) + var k + var H = bits2int(hash, q).mod(q) + var s = false + var kv = getKey(x, q, hash, algo) + while (s === false) { + k = makeKey(q, kv, algo) + r = makeR(g, k, p, q) + s = k.invm(q).imul(H.add(x.mul(r))).mod(q) + if (s.cmpn(0) === 0) { + s = false + r = new BN(0) + } + } + return toDER(r, s) +} + +function toDER (r, s) { + r = r.toArray() + s = s.toArray() + + // Pad values + if (r[0] & 0x80) r = [0].concat(r) + if (s[0] & 0x80) s = [0].concat(s) + + var total = r.length + s.length + 4 + var res = [0x30, total, 0x02, r.length] + res = res.concat(r, [0x02, s.length], s) + return Buffer.from(res) +} + +function getKey (x, q, hash, algo) { + x = Buffer.from(x.toArray()) + if (x.length < q.byteLength()) { + var zeros = Buffer.alloc(q.byteLength() - x.length) + x = Buffer.concat([zeros, x]) + } + var hlen = hash.length + var hbits = bits2octets(hash, q) + var v = Buffer.alloc(hlen) + v.fill(1) + var k = Buffer.alloc(hlen) + k = createHmac(algo, k).update(v).update(Buffer.from([0])).update(x).update(hbits).digest() + v = createHmac(algo, k).update(v).digest() + k = createHmac(algo, k).update(v).update(Buffer.from([1])).update(x).update(hbits).digest() + v = createHmac(algo, k).update(v).digest() + return { k: k, v: v } +} + +function bits2int (obits, q) { + var bits = new BN(obits) + var shift = (obits.length << 3) - q.bitLength() + if (shift > 0) bits.ishrn(shift) + return bits +} + +function bits2octets (bits, q) { + bits = bits2int(bits, q) + bits = bits.mod(q) + var out = Buffer.from(bits.toArray()) + if (out.length < q.byteLength()) { + var zeros = Buffer.alloc(q.byteLength() - out.length) + out = Buffer.concat([zeros, out]) + } + return out +} + +function makeKey (q, kv, algo) { + var t + var k + + do { + t = Buffer.alloc(0) + + while (t.length * 8 < q.bitLength()) { + kv.v = createHmac(algo, kv.k).update(kv.v).digest() + t = Buffer.concat([t, kv.v]) + } + + k = bits2int(t, q) + kv.k = createHmac(algo, kv.k).update(kv.v).update(Buffer.from([0])).digest() + kv.v = createHmac(algo, kv.k).update(kv.v).digest() + } while (k.cmp(q) !== -1) + + return k +} + +function makeR (g, k, p, q) { + return g.toRed(BN.mont(p)).redPow(k).fromRed().mod(q) +} + +module.exports = sign +module.exports.getKey = getKey +module.exports.makeKey = makeKey + +},{"./curves.json":45,"bn.js":49,"browserify-rsa":41,"create-hmac":76,"elliptic":89,"parse-asn1":230,"safe-buffer":250}],48:[function(require,module,exports){ +// much of this based on https://github.com/indutny/self-signed/blob/gh-pages/lib/rsa.js +var Buffer = require('safe-buffer').Buffer +var BN = require('bn.js') +var EC = require('elliptic').ec +var parseKeys = require('parse-asn1') +var curves = require('./curves.json') + +function verify (sig, hash, key, signType, tag) { + var pub = parseKeys(key) + if (pub.type === 'ec') { + // rsa keys can be interpreted as ecdsa ones in openssl + if (signType !== 'ecdsa' && signType !== 'ecdsa/rsa') throw new Error('wrong public key type') + return ecVerify(sig, hash, pub) + } else if (pub.type === 'dsa') { + if (signType !== 'dsa') throw new Error('wrong public key type') + return dsaVerify(sig, hash, pub) + } else { + if (signType !== 'rsa' && signType !== 'ecdsa/rsa') throw new Error('wrong public key type') + } + hash = Buffer.concat([tag, hash]) + var len = pub.modulus.byteLength() + var pad = [1] + var padNum = 0 + while (hash.length + pad.length + 2 < len) { + pad.push(0xff) + padNum++ + } + pad.push(0x00) + var i = -1 + while (++i < hash.length) { + pad.push(hash[i]) + } + pad = Buffer.from(pad) + var red = BN.mont(pub.modulus) + sig = new BN(sig).toRed(red) + + sig = sig.redPow(new BN(pub.publicExponent)) + sig = Buffer.from(sig.fromRed().toArray()) + var out = padNum < 8 ? 1 : 0 + len = Math.min(sig.length, pad.length) + if (sig.length !== pad.length) out = 1 + + i = -1 + while (++i < len) out |= sig[i] ^ pad[i] + return out === 0 +} + +function ecVerify (sig, hash, pub) { + var curveId = curves[pub.data.algorithm.curve.join('.')] + if (!curveId) throw new Error('unknown curve ' + pub.data.algorithm.curve.join('.')) + + var curve = new EC(curveId) + var pubkey = pub.data.subjectPrivateKey.data + + return curve.verify(hash, sig, pubkey) +} + +function dsaVerify (sig, hash, pub) { + var p = pub.data.p + var q = pub.data.q + var g = pub.data.g + var y = pub.data.pub_key + var unpacked = parseKeys.signature.decode(sig, 'der') + var s = unpacked.s + var r = unpacked.r + checkValue(s, q) + checkValue(r, q) + var montp = BN.mont(p) + var w = s.invm(q) + var v = g.toRed(montp) + .redPow(new BN(hash).mul(w).mod(q)) + .fromRed() + .mul(y.toRed(montp).redPow(r.mul(w).mod(q)).fromRed()) + .mod(p) + .mod(q) + return v.cmp(r) === 0 +} + +function checkValue (b, q) { + if (b.cmpn(0) <= 0) throw new Error('invalid sig') + if (b.cmp(q) >= q) throw new Error('invalid sig') +} + +module.exports = verify + +},{"./curves.json":45,"bn.js":49,"elliptic":89,"parse-asn1":230,"safe-buffer":250}],49:[function(require,module,exports){ +arguments[4][42][0].apply(exports,arguments) +},{"buffer":20,"dup":42}],50:[function(require,module,exports){ +'use strict'; + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } + +var codes = {}; + +function createErrorType(code, message, Base) { + if (!Base) { + Base = Error; + } + + function getMessage(arg1, arg2, arg3) { + if (typeof message === 'string') { + return message; + } else { + return message(arg1, arg2, arg3); + } + } + + var NodeError = + /*#__PURE__*/ + function (_Base) { + _inheritsLoose(NodeError, _Base); + + function NodeError(arg1, arg2, arg3) { + return _Base.call(this, getMessage(arg1, arg2, arg3)) || this; + } + + return NodeError; + }(Base); + + NodeError.prototype.name = Base.name; + NodeError.prototype.code = code; + codes[code] = NodeError; +} // https://github.com/nodejs/node/blob/v10.8.0/lib/internal/errors.js + + +function oneOf(expected, thing) { + if (Array.isArray(expected)) { + var len = expected.length; + expected = expected.map(function (i) { + return String(i); + }); + + if (len > 2) { + return "one of ".concat(thing, " ").concat(expected.slice(0, len - 1).join(', '), ", or ") + expected[len - 1]; + } else if (len === 2) { + return "one of ".concat(thing, " ").concat(expected[0], " or ").concat(expected[1]); + } else { + return "of ".concat(thing, " ").concat(expected[0]); + } + } else { + return "of ".concat(thing, " ").concat(String(expected)); + } +} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + + +function startsWith(str, search, pos) { + return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; +} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith + + +function endsWith(str, search, this_len) { + if (this_len === undefined || this_len > str.length) { + this_len = str.length; + } + + return str.substring(this_len - search.length, this_len) === search; +} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes + + +function includes(str, search, start) { + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > str.length) { + return false; + } else { + return str.indexOf(search, start) !== -1; + } +} + +createErrorType('ERR_INVALID_OPT_VALUE', function (name, value) { + return 'The value "' + value + '" is invalid for option "' + name + '"'; +}, TypeError); +createErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) { + // determiner: 'must be' or 'must not be' + var determiner; + + if (typeof expected === 'string' && startsWith(expected, 'not ')) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + var msg; + + if (endsWith(name, ' argument')) { + // For cases like 'first argument' + msg = "The ".concat(name, " ").concat(determiner, " ").concat(oneOf(expected, 'type')); + } else { + var type = includes(name, '.') ? 'property' : 'argument'; + msg = "The \"".concat(name, "\" ").concat(type, " ").concat(determiner, " ").concat(oneOf(expected, 'type')); + } + + msg += ". Received type ".concat(typeof actual); + return msg; +}, TypeError); +createErrorType('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF'); +createErrorType('ERR_METHOD_NOT_IMPLEMENTED', function (name) { + return 'The ' + name + ' method is not implemented'; +}); +createErrorType('ERR_STREAM_PREMATURE_CLOSE', 'Premature close'); +createErrorType('ERR_STREAM_DESTROYED', function (name) { + return 'Cannot call ' + name + ' after a stream was destroyed'; +}); +createErrorType('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times'); +createErrorType('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable'); +createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end'); +createErrorType('ERR_STREAM_NULL_VALUES', 'May not write null values to stream', TypeError); +createErrorType('ERR_UNKNOWN_ENCODING', function (arg) { + return 'Unknown encoding: ' + arg; +}, TypeError); +createErrorType('ERR_STREAM_UNSHIFT_AFTER_END_EVENT', 'stream.unshift() after end event'); +module.exports.codes = codes; + +},{}],51:[function(require,module,exports){ +(function (process){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. +'use strict'; +/**/ + +var objectKeys = Object.keys || function (obj) { + var keys = []; + + for (var key in obj) { + keys.push(key); + } + + return keys; +}; +/**/ + + +module.exports = Duplex; + +var Readable = require('./_stream_readable'); + +var Writable = require('./_stream_writable'); + +require('inherits')(Duplex, Readable); + +{ + // Allow the keys array to be GC'ed. + var keys = objectKeys(Writable.prototype); + + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + Readable.call(this, options); + Writable.call(this, options); + this.allowHalfOpen = true; + + if (options) { + if (options.readable === false) this.readable = false; + if (options.writable === false) this.writable = false; + + if (options.allowHalfOpen === false) { + this.allowHalfOpen = false; + this.once('end', onend); + } + } +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); +Object.defineProperty(Duplex.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); +Object.defineProperty(Duplex.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); // the no-half-open enforcer + +function onend() { + // If the writable side ended, then we're ok. + if (this._writableState.ended) return; // no more data can be written. + // But allow more writes to happen in this tick. + + process.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); +}).call(this)}).call(this,require('_process')) + +},{"./_stream_readable":53,"./_stream_writable":55,"_process":237,"inherits":146}],52:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +require('inherits')(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":54,"inherits":146}],53:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +'use strict'; + +module.exports = Readable; +/**/ + +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; +/**/ + +var EE = require('events').EventEmitter; + +var EElistenerCount = function EElistenerCount(emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ + + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ + + +var debugUtil = require('util'); + +var debug; + +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function debug() {}; +} +/**/ + + +var BufferList = require('./internal/streams/buffer_list'); + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_STREAM_PUSH_AFTER_EOF = _require$codes.ERR_STREAM_PUSH_AFTER_EOF, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_UNSHIFT_AFTER_END_EVENT = _require$codes.ERR_STREAM_UNSHIFT_AFTER_END_EVENT; // Lazy loaded to improve the startup performance. + + +var StringDecoder; +var createReadableStreamAsyncIterator; +var from; + +require('inherits')(Readable, Stream); + +var errorOrDestroy = destroyImpl.errorOrDestroy; +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (Array.isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + + this.highWaterMark = getHighWaterMark(this, options, 'readableHighWaterMark', isDuplex); // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + + this.sync = true; // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + this.paused = true; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'end' (and potentially 'finish') + + this.autoDestroy = !!options.autoDestroy; // has it been destroyed + + this.destroyed = false; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // the number of writers that are awaiting a drain event in .pipe()s + + this.awaitDrain = 0; // if true, a maybeReadMore has been scheduled + + this.readingMore = false; + this.decoder = null; + this.encoding = null; + + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + if (!(this instanceof Readable)) return new Readable(options); // Checking for a Stream.Duplex instance is faster here instead of inside + // the ReadableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + this._readableState = new ReadableState(options, this, isDuplex); // legacy + + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined) { + return false; + } + + return this._readableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + } +}); +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; + +Readable.prototype._destroy = function (err, cb) { + cb(err); +}; // Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. + + +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; // Unshift should *always* be something directly out of read() + + +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + debug('readableAddChunk', chunk); + var state = stream._readableState; + + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + + if (er) { + errorOrDestroy(stream, er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) errorOrDestroy(stream, new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());else addChunk(stream, state, chunk, true); + } else if (state.ended) { + errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF()); + } else if (state.destroyed) { + return false; + } else { + state.reading = false; + + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + maybeReadMore(stream, state); + } + } // We can push more data if we are below the highWaterMark. + // Also, if we have no data yet, we can stand some more bytes. + // This is to work around cases where hwm=0, such as the repl. + + + return !state.ended && (state.length < state.highWaterMark || state.length === 0); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + state.awaitDrain = 0; + stream.emit('data', chunk); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + if (state.needReadable) emitReadable(stream); + } + + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer', 'Uint8Array'], chunk); + } + + return er; +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; // backwards compatibility. + + +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + var decoder = new StringDecoder(enc); + this._readableState.decoder = decoder; // If setEncoding(null), decoder.encoding equals utf8 + + this._readableState.encoding = this._readableState.decoder.encoding; // Iterate over current buffer to convert already stored Buffers: + + var p = this._readableState.buffer.head; + var content = ''; + + while (p !== null) { + content += decoder.write(p.data); + p = p.next; + } + + this._readableState.buffer.clear(); + + if (content !== '') this._readableState.buffer.push(content); + this._readableState.length = content.length; + return this; +}; // Don't raise the hwm > 1GB + + +var MAX_HWM = 0x40000000; + +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + // TODO(ronag): Throw ERR_VALUE_OUT_OF_RANGE. + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + + return n; +} // This function is designed to be inlinable, so please take care when making +// changes to the function body. + + +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } // If we're asking for more than the current hwm, then raise the hwm. + + + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; // Don't have enough + + if (!state.ended) { + state.needReadable = true; + return 0; + } + + return state.length; +} // you can override either this method, or the async _read(n) below. + + +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + if (n !== 0) state.emittedReadable = false; // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + + if (n === 0 && state.needReadable && ((state.highWaterMark !== 0 ? state.length >= state.highWaterMark : state.length > 0) || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); // if we've ended, and we're now clear, then finish it up. + + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + // if we need a readable event, then we need to do some reading. + + + var doRead = state.needReadable; + debug('need readable', doRead); // if we currently have less than the highWaterMark, then also read some + + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + + + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; // if the length is currently zero, then we *need* a readable event. + + if (state.length === 0) state.needReadable = true; // call internal read method + + this._read(state.highWaterMark); + + state.sync = false; // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = state.length <= state.highWaterMark; + n = 0; + } else { + state.length -= n; + state.awaitDrain = 0; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; // If we tried to read() past the EOF, then emit end on the next tick. + + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + return ret; +}; + +function onEofChunk(stream, state) { + debug('onEofChunk'); + if (state.ended) return; + + if (state.decoder) { + var chunk = state.decoder.end(); + + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + + state.ended = true; + + if (state.sync) { + // if we are sync, wait until next tick to emit the data. + // Otherwise we risk emitting data in the flow() + // the readable code triggers during a read() call + emitReadable(stream); + } else { + // emit 'readable' now to make sure it gets picked up. + state.needReadable = false; + + if (!state.emittedReadable) { + state.emittedReadable = true; + emitReadable_(stream); + } + } +} // Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. + + +function emitReadable(stream) { + var state = stream._readableState; + debug('emitReadable', state.needReadable, state.emittedReadable); + state.needReadable = false; + + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + process.nextTick(emitReadable_, stream); + } +} + +function emitReadable_(stream) { + var state = stream._readableState; + debug('emitReadable_', state.destroyed, state.length, state.ended); + + if (!state.destroyed && (state.length || state.ended)) { + stream.emit('readable'); + state.emittedReadable = false; + } // The stream needs another readable event if + // 1. It is not flowing, as the flow mechanism will take + // care of it. + // 2. It is not ended. + // 3. It is below the highWaterMark, so we can schedule + // another readable later. + + + state.needReadable = !state.flowing && !state.ended && state.length <= state.highWaterMark; + flow(stream); +} // at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. + + +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + process.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + // Attempt to read more data if we should. + // + // The conditions for reading more data are (one of): + // - Not enough data buffered (state.length < state.highWaterMark). The loop + // is responsible for filling the buffer with enough data if such data + // is available. If highWaterMark is 0 and we are not in the flowing mode + // we should _not_ attempt to buffer any extra data. We'll get more data + // when the stream consumer calls read() instead. + // - No data in the buffer, and the stream is in flowing mode. In this mode + // the loop below is responsible for ensuring read() is called. Failing to + // call read here would abort the flow and there's no other mechanism for + // continuing the flow if the stream consumer has just subscribed to the + // 'data' event. + // + // In addition to the above conditions to keep reading data, the following + // conditions prevent the data from being read: + // - The stream has ended (state.ended). + // - There is already a pending 'read' operation (state.reading). This is a + // case where the the stream has called the implementation defined _read() + // method, but they are processing the call asynchronously and have _not_ + // called push() with new data. In this case we skip performing more + // read()s. The execution ends in this method again after the _read() ends + // up calling push() with more data. + while (!state.reading && !state.ended && (state.length < state.highWaterMark || state.flowing && state.length === 0)) { + var len = state.length; + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) // didn't get any data, stop spinning. + break; + } + + state.readingMore = false; +} // abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. + + +Readable.prototype._read = function (n) { + errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED('_read()')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + + case 1: + state.pipes = [state.pipes, dest]; + break; + + default: + state.pipes.push(dest); + break; + } + + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) process.nextTick(endFn);else src.once('end', endFn); + dest.on('unpipe', onunpipe); + + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + + + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + var cleanedUp = false; + + function cleanup() { + debug('cleanup'); // cleanup event handlers once the pipe is broken + + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + cleanedUp = true; // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + src.on('data', ondata); + + function ondata(chunk) { + debug('ondata'); + var ret = dest.write(chunk); + debug('dest.write', ret); + + if (ret === false) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', state.awaitDrain); + state.awaitDrain++; + } + + src.pause(); + } + } // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + + + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) errorOrDestroy(dest, er); + } // Make sure our error handler is attached before userland ones. + + + prependListener(dest, 'error', onerror); // Both close and finish should trigger unpipe, but only once. + + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + + dest.once('close', onclose); + + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } // tell the dest that it's being piped to + + + dest.emit('pipe', src); // start the flow if it hasn't been started already. + + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function pipeOnDrainFunctionResult() { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { + hasUnpiped: false + }; // if we're not piping anywhere, then do nothing. + + if (state.pipesCount === 0) return this; // just one destination. most common case. + + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + if (!dest) dest = state.pipes; // got a match. + + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } // slow case. multiple pipe destinations. + + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, { + hasUnpiped: false + }); + } + + return this; + } // try to find the right one. + + + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + dest.emit('unpipe', this, unpipeInfo); + return this; +}; // set up data events if they are asked for +// Ensure readable listeners eventually get something + + +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + var state = this._readableState; + + if (ev === 'data') { + // update readableListening so that resume() may be a no-op + // a few lines down. This is needed to support once('readable'). + state.readableListening = this.listenerCount('readable') > 0; // Try start flowing on next tick if stream isn't explicitly paused + + if (state.flowing !== false) this.resume(); + } else if (ev === 'readable') { + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.flowing = false; + state.emittedReadable = false; + debug('on readable', state.length, state.reading); + + if (state.length) { + emitReadable(this); + } else if (!state.reading) { + process.nextTick(nReadingNextTick, this); + } + } + } + + return res; +}; + +Readable.prototype.addListener = Readable.prototype.on; + +Readable.prototype.removeListener = function (ev, fn) { + var res = Stream.prototype.removeListener.call(this, ev, fn); + + if (ev === 'readable') { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +Readable.prototype.removeAllListeners = function (ev) { + var res = Stream.prototype.removeAllListeners.apply(this, arguments); + + if (ev === 'readable' || ev === undefined) { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +function updateReadableListening(self) { + var state = self._readableState; + state.readableListening = self.listenerCount('readable') > 0; + + if (state.resumeScheduled && !state.paused) { + // flowing needs to be set to true now, otherwise + // the upcoming resume will not flow. + state.flowing = true; // crude way to check if we should resume + } else if (self.listenerCount('data') > 0) { + self.resume(); + } +} + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} // pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. + + +Readable.prototype.resume = function () { + var state = this._readableState; + + if (!state.flowing) { + debug('resume'); // we flow only if there is no one listening + // for readable, but we still have to call + // resume() + + state.flowing = !state.readableListening; + resume(this, state); + } + + state.paused = false; + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + process.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + debug('resume', state.reading); + + if (!state.reading) { + stream.read(0); + } + + state.resumeScheduled = false; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + + if (this._readableState.flowing !== false) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + + this._readableState.paused = true; + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + + while (state.flowing && stream.read() !== null) { + ; + } +} // wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. + + +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + stream.on('end', function () { + debug('wrapped end'); + + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); // don't skip over falsy values in objectMode + + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + + if (!ret) { + paused = true; + stream.pause(); + } + }); // proxy all the other methods. + // important when wrapping filters and duplexes. + + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function methodWrap(method) { + return function methodWrapReturnFunction() { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } // proxy certain important events. + + + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } // when we try to consume some more bytes, simply unpause the + // underlying stream. + + + this._read = function (n) { + debug('wrapped _read', n); + + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +if (typeof Symbol === 'function') { + Readable.prototype[Symbol.asyncIterator] = function () { + if (createReadableStreamAsyncIterator === undefined) { + createReadableStreamAsyncIterator = require('./internal/streams/async_iterator'); + } + + return createReadableStreamAsyncIterator(this); + }; +} + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.highWaterMark; + } +}); +Object.defineProperty(Readable.prototype, 'readableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState && this._readableState.buffer; + } +}); +Object.defineProperty(Readable.prototype, 'readableFlowing', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.flowing; + }, + set: function set(state) { + if (this._readableState) { + this._readableState.flowing = state; + } + } +}); // exposed for testing purposes only. + +Readable._fromList = fromList; +Object.defineProperty(Readable.prototype, 'readableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.length; + } +}); // Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. + +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.first();else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = state.buffer.consume(n, state.decoder); + } + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + debug('endReadable', state.endEmitted); + + if (!state.endEmitted) { + state.ended = true; + process.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + debug('endReadableNT', state.endEmitted, state.length); // Check that we didn't get one last unshift. + + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the writable side is ready for autoDestroy as well + var wState = stream._writableState; + + if (!wState || wState.autoDestroy && wState.finished) { + stream.destroy(); + } + } + } +} + +if (typeof Symbol === 'function') { + Readable.from = function (iterable, opts) { + if (from === undefined) { + from = require('./internal/streams/from'); + } + + return from(Readable, iterable, opts); + }; +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + + return -1; +} +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":50,"./_stream_duplex":51,"./internal/streams/async_iterator":56,"./internal/streams/buffer_list":57,"./internal/streams/destroy":58,"./internal/streams/from":60,"./internal/streams/state":62,"./internal/streams/stream":63,"_process":237,"buffer":68,"events":105,"inherits":146,"string_decoder/":279,"util":20}],54:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. +'use strict'; + +module.exports = Transform; + +var _require$codes = require('../errors').codes, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK, + ERR_TRANSFORM_ALREADY_TRANSFORMING = _require$codes.ERR_TRANSFORM_ALREADY_TRANSFORMING, + ERR_TRANSFORM_WITH_LENGTH_0 = _require$codes.ERR_TRANSFORM_WITH_LENGTH_0; + +var Duplex = require('./_stream_duplex'); + +require('inherits')(Transform, Duplex); + +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; + var cb = ts.writecb; + + if (cb === null) { + return this.emit('error', new ERR_MULTIPLE_CALLBACK()); + } + + ts.writechunk = null; + ts.writecb = null; + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); + cb(er); + var rs = this._readableState; + rs.reading = false; + + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + Duplex.call(this, options); + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; // start out asking for a readable event once data is transformed. + + this._readableState.needReadable = true; // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + if (typeof options.flush === 'function') this._flush = options.flush; + } // When the writable side finishes, then flush out anything remaining. + + + this.on('prefinish', prefinish); +} + +function prefinish() { + var _this = this; + + if (typeof this._flush === 'function' && !this._readableState.destroyed) { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; // This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. + + +Transform.prototype._transform = function (chunk, encoding, cb) { + cb(new ERR_METHOD_NOT_IMPLEMENTED('_transform()')); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; // Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. + + +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && !ts.transforming) { + ts.transforming = true; + + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); // TODO(BridgeAR): Write a test for these two error cases + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + + if (stream._writableState.length) throw new ERR_TRANSFORM_WITH_LENGTH_0(); + if (stream._transformState.transforming) throw new ERR_TRANSFORM_ALREADY_TRANSFORMING(); + return stream.push(null); +} +},{"../errors":50,"./_stream_duplex":51,"inherits":146}],55:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. +'use strict'; + +module.exports = Writable; +/* */ + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} // It seems a linked list but it is not +// there will be only 2 of these for each stream + + +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ + + +var Duplex; +/**/ + +Writable.WritableState = WritableState; +/**/ + +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK, + ERR_STREAM_CANNOT_PIPE = _require$codes.ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES = _require$codes.ERR_STREAM_NULL_VALUES, + ERR_STREAM_WRITE_AFTER_END = _require$codes.ERR_STREAM_WRITE_AFTER_END, + ERR_UNKNOWN_ENCODING = _require$codes.ERR_UNKNOWN_ENCODING; + +var errorOrDestroy = destroyImpl.errorOrDestroy; + +require('inherits')(Writable, Stream); + +function nop() {} + +function WritableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream, + // e.g. options.readableObjectMode vs. options.writableObjectMode, etc. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag to indicate whether or not this stream + // contains buffers or objects. + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + + this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex); // if _final has been called + + this.finalCalled = false; // drain event flag. + + this.needDrain = false; // at the start of calling end() + + this.ending = false; // when end() has been called, and returned + + this.ended = false; // when 'finish' is emitted + + this.finished = false; // has it been destroyed + + this.destroyed = false; // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + + this.length = 0; // a flag to see when we're in the middle of a write. + + this.writing = false; // when true all writes will be buffered until .uncork() call + + this.corked = 0; // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + + this.sync = true; // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + + this.bufferProcessing = false; // the callback that's passed to _write(chunk,cb) + + this.onwrite = function (er) { + onwrite(stream, er); + }; // the callback that the user supplies to write(chunk,encoding,cb) + + + this.writecb = null; // the amount that is being written when _write is called. + + this.writelen = 0; + this.bufferedRequest = null; + this.lastBufferedRequest = null; // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + + this.pendingcb = 0; // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + + this.prefinished = false; // True if the error was already emitted and should not be thrown again + + this.errorEmitted = false; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'finish' (and potentially 'end') + + this.autoDestroy = !!options.autoDestroy; // count buffered requests + + this.bufferedRequestCount = 0; // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + + while (current) { + out.push(current); + current = current.next; + } + + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function writableStateBufferGetter() { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); // Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. + + +var realHasInstance; + +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function value(object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function realHasInstance(object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + // Checking for a Stream.Duplex instance is faster here instead of inside + // the WritableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options); + this._writableState = new WritableState(options, this, isDuplex); // legacy. + + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + if (typeof options.writev === 'function') this._writev = options.writev; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} // Otherwise people can pipe Writable streams, which is just wrong. + + +Writable.prototype.pipe = function () { + errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE()); +}; + +function writeAfterEnd(stream, cb) { + var er = new ERR_STREAM_WRITE_AFTER_END(); // TODO: defer error events consistently everywhere, not just the cb + + errorOrDestroy(stream, er); + process.nextTick(cb, er); +} // Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. + + +function validChunk(stream, state, chunk, cb) { + var er; + + if (chunk === null) { + er = new ERR_STREAM_NULL_VALUES(); + } else if (typeof chunk !== 'string' && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk); + } + + if (er) { + errorOrDestroy(stream, er); + process.nextTick(cb, er); + return false; + } + + return true; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + if (typeof cb !== 'function') cb = nop; + if (state.ending) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + return ret; +}; + +Writable.prototype.cork = function () { + this._writableState.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new ERR_UNKNOWN_ENCODING(encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); // if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. + +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + + var len = state.objectMode ? 1 : chunk.length; + state.length += len; + var ret = state.length < state.highWaterMark; // we must ensure that previous needDrain will not be reset to false. + + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (state.destroyed) state.onwrite(new ERR_STREAM_DESTROYED('write'));else if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + process.nextTick(cb, er); // this can emit finish, and it will always happen + // after error + + process.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); // this can emit finish, but finish must + // always follow error + + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + if (typeof cb !== 'function') throw new ERR_MULTIPLE_CALLBACK(); + onwriteStateUpdate(state); + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state) || stream.destroyed; + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + process.nextTick(afterWrite, stream, state, finished, cb); + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} // Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. + + +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} // if there's something in the buffer waiting, then process it + + +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + var count = 0; + var allBuffers = true; + + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + + buffer.allBuffers = allBuffers; + doWrite(stream, state, true, state.length, buffer, '', holder.finish); // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + + state.pendingcb++; + state.lastBufferedRequest = null; + + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new ERR_METHOD_NOT_IMPLEMENTED('_write()')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); // .end() fully uncorks + + if (state.corked) { + state.corked = 1; + this.uncork(); + } // ignore unnecessary end() calls. + + + if (!state.ending) endWritable(this, state, cb); + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} + +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + + if (err) { + errorOrDestroy(stream, err); + } + + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} + +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function' && !state.destroyed) { + state.pendingcb++; + state.finalCalled = true; + process.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + + if (need) { + prefinish(stream, state); + + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the readable side is ready for autoDestroy as well + var rState = stream._readableState; + + if (!rState || rState.autoDestroy && rState.endEmitted) { + stream.destroy(); + } + } + } + } + + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + + if (cb) { + if (state.finished) process.nextTick(cb);else stream.once('finish', cb); + } + + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } // reuse the free corkReq. + + + state.corkedRequestsFree.next = corkReq; +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._writableState === undefined) { + return false; + } + + return this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._writableState.destroyed = value; + } +}); +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; + +Writable.prototype._destroy = function (err, cb) { + cb(err); +}; +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":50,"./_stream_duplex":51,"./internal/streams/destroy":58,"./internal/streams/state":62,"./internal/streams/stream":63,"_process":237,"buffer":68,"inherits":146,"util-deprecate":283}],56:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; + +var _Object$setPrototypeO; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var finished = require('./end-of-stream'); + +var kLastResolve = Symbol('lastResolve'); +var kLastReject = Symbol('lastReject'); +var kError = Symbol('error'); +var kEnded = Symbol('ended'); +var kLastPromise = Symbol('lastPromise'); +var kHandlePromise = Symbol('handlePromise'); +var kStream = Symbol('stream'); + +function createIterResult(value, done) { + return { + value: value, + done: done + }; +} + +function readAndResolve(iter) { + var resolve = iter[kLastResolve]; + + if (resolve !== null) { + var data = iter[kStream].read(); // we defer if data is null + // we can be expecting either 'end' or + // 'error' + + if (data !== null) { + iter[kLastPromise] = null; + iter[kLastResolve] = null; + iter[kLastReject] = null; + resolve(createIterResult(data, false)); + } + } +} + +function onReadable(iter) { + // we wait for the next tick, because it might + // emit an error with process.nextTick + process.nextTick(readAndResolve, iter); +} + +function wrapForNext(lastPromise, iter) { + return function (resolve, reject) { + lastPromise.then(function () { + if (iter[kEnded]) { + resolve(createIterResult(undefined, true)); + return; + } + + iter[kHandlePromise](resolve, reject); + }, reject); + }; +} + +var AsyncIteratorPrototype = Object.getPrototypeOf(function () {}); +var ReadableStreamAsyncIteratorPrototype = Object.setPrototypeOf((_Object$setPrototypeO = { + get stream() { + return this[kStream]; + }, + + next: function next() { + var _this = this; + + // if we have detected an error in the meanwhile + // reject straight away + var error = this[kError]; + + if (error !== null) { + return Promise.reject(error); + } + + if (this[kEnded]) { + return Promise.resolve(createIterResult(undefined, true)); + } + + if (this[kStream].destroyed) { + // We need to defer via nextTick because if .destroy(err) is + // called, the error will be emitted via nextTick, and + // we cannot guarantee that there is no error lingering around + // waiting to be emitted. + return new Promise(function (resolve, reject) { + process.nextTick(function () { + if (_this[kError]) { + reject(_this[kError]); + } else { + resolve(createIterResult(undefined, true)); + } + }); + }); + } // if we have multiple next() calls + // we will wait for the previous Promise to finish + // this logic is optimized to support for await loops, + // where next() is only called once at a time + + + var lastPromise = this[kLastPromise]; + var promise; + + if (lastPromise) { + promise = new Promise(wrapForNext(lastPromise, this)); + } else { + // fast path needed to support multiple this.push() + // without triggering the next() queue + var data = this[kStream].read(); + + if (data !== null) { + return Promise.resolve(createIterResult(data, false)); + } + + promise = new Promise(this[kHandlePromise]); + } + + this[kLastPromise] = promise; + return promise; + } +}, _defineProperty(_Object$setPrototypeO, Symbol.asyncIterator, function () { + return this; +}), _defineProperty(_Object$setPrototypeO, "return", function _return() { + var _this2 = this; + + // destroy(err, cb) is a private API + // we can guarantee we have that here, because we control the + // Readable class this is attached to + return new Promise(function (resolve, reject) { + _this2[kStream].destroy(null, function (err) { + if (err) { + reject(err); + return; + } + + resolve(createIterResult(undefined, true)); + }); + }); +}), _Object$setPrototypeO), AsyncIteratorPrototype); + +var createReadableStreamAsyncIterator = function createReadableStreamAsyncIterator(stream) { + var _Object$create; + + var iterator = Object.create(ReadableStreamAsyncIteratorPrototype, (_Object$create = {}, _defineProperty(_Object$create, kStream, { + value: stream, + writable: true + }), _defineProperty(_Object$create, kLastResolve, { + value: null, + writable: true + }), _defineProperty(_Object$create, kLastReject, { + value: null, + writable: true + }), _defineProperty(_Object$create, kError, { + value: null, + writable: true + }), _defineProperty(_Object$create, kEnded, { + value: stream._readableState.endEmitted, + writable: true + }), _defineProperty(_Object$create, kHandlePromise, { + value: function value(resolve, reject) { + var data = iterator[kStream].read(); + + if (data) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(data, false)); + } else { + iterator[kLastResolve] = resolve; + iterator[kLastReject] = reject; + } + }, + writable: true + }), _Object$create)); + iterator[kLastPromise] = null; + finished(stream, function (err) { + if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { + var reject = iterator[kLastReject]; // reject if we are waiting for data in the Promise + // returned by next() and store the error + + if (reject !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + reject(err); + } + + iterator[kError] = err; + return; + } + + var resolve = iterator[kLastResolve]; + + if (resolve !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(undefined, true)); + } + + iterator[kEnded] = true; + }); + stream.on('readable', onReadable.bind(null, iterator)); + return iterator; +}; + +module.exports = createReadableStreamAsyncIterator; +}).call(this)}).call(this,require('_process')) + +},{"./end-of-stream":59,"_process":237}],57:[function(require,module,exports){ +'use strict'; + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var _require = require('buffer'), + Buffer = _require.Buffer; + +var _require2 = require('util'), + inspect = _require2.inspect; + +var custom = inspect && inspect.custom || 'inspect'; + +function copyBuffer(src, target, offset) { + Buffer.prototype.copy.call(src, target, offset); +} + +module.exports = +/*#__PURE__*/ +function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + _createClass(BufferList, [{ + key: "push", + value: function push(v) { + var entry = { + data: v, + next: null + }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + } + }, { + key: "unshift", + value: function unshift(v) { + var entry = { + data: v, + next: this.head + }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + } + }, { + key: "shift", + value: function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + } + }, { + key: "clear", + value: function clear() { + this.head = this.tail = null; + this.length = 0; + } + }, { + key: "join", + value: function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + + while (p = p.next) { + ret += s + p.data; + } + + return ret; + } + }, { + key: "concat", + value: function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + + return ret; + } // Consumes a specified amount of bytes or characters from the buffered data. + + }, { + key: "consume", + value: function consume(n, hasStrings) { + var ret; + + if (n < this.head.data.length) { + // `slice` is the same for buffers and strings. + ret = this.head.data.slice(0, n); + this.head.data = this.head.data.slice(n); + } else if (n === this.head.data.length) { + // First chunk is a perfect match. + ret = this.shift(); + } else { + // Result spans more than one buffer. + ret = hasStrings ? this._getString(n) : this._getBuffer(n); + } + + return ret; + } + }, { + key: "first", + value: function first() { + return this.head.data; + } // Consumes a specified amount of characters from the buffered data. + + }, { + key: "_getString", + value: function _getString(n) { + var p = this.head; + var c = 1; + var ret = p.data; + n -= ret.length; + + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) this.head = p.next;else this.head = this.tail = null; + } else { + this.head = p; + p.data = str.slice(nb); + } + + break; + } + + ++c; + } + + this.length -= c; + return ret; + } // Consumes a specified amount of bytes from the buffered data. + + }, { + key: "_getBuffer", + value: function _getBuffer(n) { + var ret = Buffer.allocUnsafe(n); + var p = this.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) this.head = p.next;else this.head = this.tail = null; + } else { + this.head = p; + p.data = buf.slice(nb); + } + + break; + } + + ++c; + } + + this.length -= c; + return ret; + } // Make sure the linked list only shows the minimal necessary information. + + }, { + key: custom, + value: function value(_, options) { + return inspect(this, _objectSpread({}, options, { + // Only inspect one level. + depth: 0, + // It should not recurse. + customInspect: false + })); + } + }]); + + return BufferList; +}(); +},{"buffer":68,"util":20}],58:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; // undocumented cb() API, needed for core, not for public API + +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err) { + if (!this._writableState) { + process.nextTick(emitErrorNT, this, err); + } else if (!this._writableState.errorEmitted) { + this._writableState.errorEmitted = true; + process.nextTick(emitErrorNT, this, err); + } + } + + return this; + } // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + + if (this._readableState) { + this._readableState.destroyed = true; + } // if this is a duplex stream mark the writable part as destroyed as well + + + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + if (!_this._writableState) { + process.nextTick(emitErrorAndCloseNT, _this, err); + } else if (!_this._writableState.errorEmitted) { + _this._writableState.errorEmitted = true; + process.nextTick(emitErrorAndCloseNT, _this, err); + } else { + process.nextTick(emitCloseNT, _this); + } + } else if (cb) { + process.nextTick(emitCloseNT, _this); + cb(err); + } else { + process.nextTick(emitCloseNT, _this); + } + }); + + return this; +} + +function emitErrorAndCloseNT(self, err) { + emitErrorNT(self, err); + emitCloseNT(self); +} + +function emitCloseNT(self) { + if (self._writableState && !self._writableState.emitClose) return; + if (self._readableState && !self._readableState.emitClose) return; + self.emit('close'); +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finalCalled = false; + this._writableState.prefinished = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +function errorOrDestroy(stream, err) { + // We have tests that rely on errors being emitted + // in the same tick, so changing this is semver major. + // For now when you opt-in to autoDestroy we allow + // the error to be emitted nextTick. In a future + // semver major update we should change the default to this. + var rState = stream._readableState; + var wState = stream._writableState; + if (rState && rState.autoDestroy || wState && wState.autoDestroy) stream.destroy(err);else stream.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy, + errorOrDestroy: errorOrDestroy +}; +}).call(this)}).call(this,require('_process')) + +},{"_process":237}],59:[function(require,module,exports){ +// Ported from https://github.com/mafintosh/end-of-stream with +// permission from the author, Mathias Buus (@mafintosh). +'use strict'; + +var ERR_STREAM_PREMATURE_CLOSE = require('../../../errors').codes.ERR_STREAM_PREMATURE_CLOSE; + +function once(callback) { + var called = false; + return function () { + if (called) return; + called = true; + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + callback.apply(this, args); + }; +} + +function noop() {} + +function isRequest(stream) { + return stream.setHeader && typeof stream.abort === 'function'; +} + +function eos(stream, opts, callback) { + if (typeof opts === 'function') return eos(stream, null, opts); + if (!opts) opts = {}; + callback = once(callback || noop); + var readable = opts.readable || opts.readable !== false && stream.readable; + var writable = opts.writable || opts.writable !== false && stream.writable; + + var onlegacyfinish = function onlegacyfinish() { + if (!stream.writable) onfinish(); + }; + + var writableEnded = stream._writableState && stream._writableState.finished; + + var onfinish = function onfinish() { + writable = false; + writableEnded = true; + if (!readable) callback.call(stream); + }; + + var readableEnded = stream._readableState && stream._readableState.endEmitted; + + var onend = function onend() { + readable = false; + readableEnded = true; + if (!writable) callback.call(stream); + }; + + var onerror = function onerror(err) { + callback.call(stream, err); + }; + + var onclose = function onclose() { + var err; + + if (readable && !readableEnded) { + if (!stream._readableState || !stream._readableState.ended) err = new ERR_STREAM_PREMATURE_CLOSE(); + return callback.call(stream, err); + } + + if (writable && !writableEnded) { + if (!stream._writableState || !stream._writableState.ended) err = new ERR_STREAM_PREMATURE_CLOSE(); + return callback.call(stream, err); + } + }; + + var onrequest = function onrequest() { + stream.req.on('finish', onfinish); + }; + + if (isRequest(stream)) { + stream.on('complete', onfinish); + stream.on('abort', onclose); + if (stream.req) onrequest();else stream.on('request', onrequest); + } else if (writable && !stream._writableState) { + // legacy streams + stream.on('end', onlegacyfinish); + stream.on('close', onlegacyfinish); + } + + stream.on('end', onend); + stream.on('finish', onfinish); + if (opts.error !== false) stream.on('error', onerror); + stream.on('close', onclose); + return function () { + stream.removeListener('complete', onfinish); + stream.removeListener('abort', onclose); + stream.removeListener('request', onrequest); + if (stream.req) stream.req.removeListener('finish', onfinish); + stream.removeListener('end', onlegacyfinish); + stream.removeListener('close', onlegacyfinish); + stream.removeListener('finish', onfinish); + stream.removeListener('end', onend); + stream.removeListener('error', onerror); + stream.removeListener('close', onclose); + }; +} + +module.exports = eos; +},{"../../../errors":50}],60:[function(require,module,exports){ +module.exports = function () { + throw new Error('Readable.from is not available in the browser') +}; + +},{}],61:[function(require,module,exports){ +// Ported from https://github.com/mafintosh/pump with +// permission from the author, Mathias Buus (@mafintosh). +'use strict'; + +var eos; + +function once(callback) { + var called = false; + return function () { + if (called) return; + called = true; + callback.apply(void 0, arguments); + }; +} + +var _require$codes = require('../../../errors').codes, + ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS, + ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED; + +function noop(err) { + // Rethrow the error if it exists to avoid swallowing it + if (err) throw err; +} + +function isRequest(stream) { + return stream.setHeader && typeof stream.abort === 'function'; +} + +function destroyer(stream, reading, writing, callback) { + callback = once(callback); + var closed = false; + stream.on('close', function () { + closed = true; + }); + if (eos === undefined) eos = require('./end-of-stream'); + eos(stream, { + readable: reading, + writable: writing + }, function (err) { + if (err) return callback(err); + closed = true; + callback(); + }); + var destroyed = false; + return function (err) { + if (closed) return; + if (destroyed) return; + destroyed = true; // request.destroy just do .end - .abort is what we want + + if (isRequest(stream)) return stream.abort(); + if (typeof stream.destroy === 'function') return stream.destroy(); + callback(err || new ERR_STREAM_DESTROYED('pipe')); + }; +} + +function call(fn) { + fn(); +} + +function pipe(from, to) { + return from.pipe(to); +} + +function popCallback(streams) { + if (!streams.length) return noop; + if (typeof streams[streams.length - 1] !== 'function') return noop; + return streams.pop(); +} + +function pipeline() { + for (var _len = arguments.length, streams = new Array(_len), _key = 0; _key < _len; _key++) { + streams[_key] = arguments[_key]; + } + + var callback = popCallback(streams); + if (Array.isArray(streams[0])) streams = streams[0]; + + if (streams.length < 2) { + throw new ERR_MISSING_ARGS('streams'); + } + + var error; + var destroys = streams.map(function (stream, i) { + var reading = i < streams.length - 1; + var writing = i > 0; + return destroyer(stream, reading, writing, function (err) { + if (!error) error = err; + if (err) destroys.forEach(call); + if (reading) return; + destroys.forEach(call); + callback(error); + }); + }); + return streams.reduce(pipe); +} + +module.exports = pipeline; +},{"../../../errors":50,"./end-of-stream":59}],62:[function(require,module,exports){ +'use strict'; + +var ERR_INVALID_OPT_VALUE = require('../../../errors').codes.ERR_INVALID_OPT_VALUE; + +function highWaterMarkFrom(options, isDuplex, duplexKey) { + return options.highWaterMark != null ? options.highWaterMark : isDuplex ? options[duplexKey] : null; +} + +function getHighWaterMark(state, options, duplexKey, isDuplex) { + var hwm = highWaterMarkFrom(options, isDuplex, duplexKey); + + if (hwm != null) { + if (!(isFinite(hwm) && Math.floor(hwm) === hwm) || hwm < 0) { + var name = isDuplex ? duplexKey : 'highWaterMark'; + throw new ERR_INVALID_OPT_VALUE(name, hwm); + } + + return Math.floor(hwm); + } // Default value + + + return state.objectMode ? 16 : 16 * 1024; +} + +module.exports = { + getHighWaterMark: getHighWaterMark +}; +},{"../../../errors":50}],63:[function(require,module,exports){ +module.exports = require('events').EventEmitter; + +},{"events":105}],64:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); +exports.finished = require('./lib/internal/streams/end-of-stream.js'); +exports.pipeline = require('./lib/internal/streams/pipeline.js'); + +},{"./lib/_stream_duplex.js":51,"./lib/_stream_passthrough.js":52,"./lib/_stream_readable.js":53,"./lib/_stream_transform.js":54,"./lib/_stream_writable.js":55,"./lib/internal/streams/end-of-stream.js":59,"./lib/internal/streams/pipeline.js":61}],65:[function(require,module,exports){ +const basex = require('base-x') +const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +module.exports = basex(ALPHABET) + +},{"base-x":66}],66:[function(require,module,exports){ +'use strict' +// base-x encoding / decoding +// Copyright (c) 2018 base-x contributors +// Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp) +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php. +function base (ALPHABET) { + if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') } + var BASE_MAP = new Uint8Array(256) + for (var j = 0; j < BASE_MAP.length; j++) { + BASE_MAP[j] = 255 + } + for (var i = 0; i < ALPHABET.length; i++) { + var x = ALPHABET.charAt(i) + var xc = x.charCodeAt(0) + if (BASE_MAP[xc] !== 255) { throw new TypeError(x + ' is ambiguous') } + BASE_MAP[xc] = i + } + var BASE = ALPHABET.length + var LEADER = ALPHABET.charAt(0) + var FACTOR = Math.log(BASE) / Math.log(256) // log(BASE) / log(256), rounded up + var iFACTOR = Math.log(256) / Math.log(BASE) // log(256) / log(BASE), rounded up + function encode (source) { + if (source instanceof Uint8Array) { + } else if (ArrayBuffer.isView(source)) { + source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength) + } else if (Array.isArray(source)) { + source = Uint8Array.from(source) + } + if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') } + if (source.length === 0) { return '' } + // Skip & count leading zeroes. + var zeroes = 0 + var length = 0 + var pbegin = 0 + var pend = source.length + while (pbegin !== pend && source[pbegin] === 0) { + pbegin++ + zeroes++ + } + // Allocate enough space in big-endian base58 representation. + var size = ((pend - pbegin) * iFACTOR + 1) >>> 0 + var b58 = new Uint8Array(size) + // Process the bytes. + while (pbegin !== pend) { + var carry = source[pbegin] + // Apply "b58 = b58 * 256 + ch". + var i = 0 + for (var it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) { + carry += (256 * b58[it1]) >>> 0 + b58[it1] = (carry % BASE) >>> 0 + carry = (carry / BASE) >>> 0 + } + if (carry !== 0) { throw new Error('Non-zero carry') } + length = i + pbegin++ + } + // Skip leading zeroes in base58 result. + var it2 = size - length + while (it2 !== size && b58[it2] === 0) { + it2++ + } + // Translate the result into a string. + var str = LEADER.repeat(zeroes) + for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]) } + return str + } + function decodeUnsafe (source) { + if (typeof source !== 'string') { throw new TypeError('Expected String') } + if (source.length === 0) { return new Uint8Array() } + var psz = 0 + // Skip and count leading '1's. + var zeroes = 0 + var length = 0 + while (source[psz] === LEADER) { + zeroes++ + psz++ + } + // Allocate enough space in big-endian base256 representation. + var size = (((source.length - psz) * FACTOR) + 1) >>> 0 // log(58) / log(256), rounded up. + var b256 = new Uint8Array(size) + // Process the characters. + while (source[psz]) { + // Decode character + var carry = BASE_MAP[source.charCodeAt(psz)] + // Invalid character + if (carry === 255) { return } + var i = 0 + for (var it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) { + carry += (BASE * b256[it3]) >>> 0 + b256[it3] = (carry % 256) >>> 0 + carry = (carry / 256) >>> 0 + } + if (carry !== 0) { throw new Error('Non-zero carry') } + length = i + psz++ + } + // Skip leading zeroes in b256. + var it4 = size - length + while (it4 !== size && b256[it4] === 0) { + it4++ + } + var vch = new Uint8Array(zeroes + (size - it4)) + var j = zeroes + while (it4 !== size) { + vch[j++] = b256[it4++] + } + return vch + } + function decode (string) { + var buffer = decodeUnsafe(string) + if (buffer) { return buffer } + throw new Error('Non-base' + BASE + ' character') + } + return { + encode: encode, + decodeUnsafe: decodeUnsafe, + decode: decode + } +} +module.exports = base + +},{}],67:[function(require,module,exports){ +(function (Buffer){(function (){ +module.exports = function xor (a, b) { + var length = Math.min(a.length, b.length) + var buffer = new Buffer(length) + + for (var i = 0; i < length; ++i) { + buffer[i] = a[i] ^ b[i] + } + + return buffer +} + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"buffer":68}],68:[function(require,module,exports){ +(function (Buffer){(function (){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } } + return arr.foo() === 42 + } catch (e) { + return false + } +} + +Object.defineProperty(Buffer.prototype, 'parent', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.buffer + } +}) + +Object.defineProperty(Buffer.prototype, 'offset', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.byteOffset + } +}) + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('The value "' + length + '" is invalid for option "size"') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new TypeError( + 'The "string" argument must be of type string. Received type number' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species != null && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + if (ArrayBuffer.isView(value)) { + return fromArrayLike(value) + } + + if (value == null) { + throw TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) + } + + if (isInstance(value, ArrayBuffer) || + (value && isInstance(value.buffer, ArrayBuffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'number') { + throw new TypeError( + 'The "value" argument must not be of type number. Received type number' + ) + } + + var valueOf = value.valueOf && value.valueOf() + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length) + } + + var b = fromObject(value) + if (b) return b + + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && + typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from( + value[Symbol.toPrimitive]('string'), encodingOrOffset, length + ) + } + + throw new TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true && + b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false +} + +Buffer.compare = function compare (a, b) { + if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) + if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' + ) + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (isInstance(buf, Uint8Array)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + throw new TypeError( + 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + + 'Received type ' + typeof string + ) + } + + var len = string.length + var mustMatch = (arguments.length > 2 && arguments[2] === true) + if (!mustMatch && len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 + } + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.toLocaleString = Buffer.prototype.toString + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim() + if (this.length > max) str += ' ... ' + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength) + } + if (!Buffer.isBuffer(target)) { + throw new TypeError( + 'The "target" argument must be one of type Buffer or Uint8Array. ' + + 'Received type ' + (typeof target) + ) + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + var strLen = string.length + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : Buffer.from(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass +// the `instanceof` check but they should be treated as of that type. +// See: https://github.com/feross/buffer/issues/166 +function isInstance (obj, type) { + return obj instanceof type || + (obj != null && obj.constructor != null && obj.constructor.name != null && + obj.constructor.name === type.name) +} +function numberIsNaN (obj) { + // For IE11 support + return obj !== obj // eslint-disable-line no-self-compare +} + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"base64-js":17,"buffer":68,"ieee754":145}],69:[function(require,module,exports){ +'use strict'; + +var GetIntrinsic = require('get-intrinsic'); + +var callBind = require('./'); + +var $indexOf = callBind(GetIntrinsic('String.prototype.indexOf')); + +module.exports = function callBoundIntrinsic(name, allowMissing) { + var intrinsic = GetIntrinsic(name, !!allowMissing); + if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) { + return callBind(intrinsic); + } + return intrinsic; +}; + +},{"./":70,"get-intrinsic":110}],70:[function(require,module,exports){ +'use strict'; + +var bind = require('function-bind'); +var GetIntrinsic = require('get-intrinsic'); + +var $apply = GetIntrinsic('%Function.prototype.apply%'); +var $call = GetIntrinsic('%Function.prototype.call%'); +var $reflectApply = GetIntrinsic('%Reflect.apply%', true) || bind.call($call, $apply); + +var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true); +var $defineProperty = GetIntrinsic('%Object.defineProperty%', true); +var $max = GetIntrinsic('%Math.max%'); + +if ($defineProperty) { + try { + $defineProperty({}, 'a', { value: 1 }); + } catch (e) { + // IE 8 has a broken defineProperty + $defineProperty = null; + } +} + +module.exports = function callBind(originalFunction) { + var func = $reflectApply(bind, $call, arguments); + if ($gOPD && $defineProperty) { + var desc = $gOPD(func, 'length'); + if (desc.configurable) { + // original length, plus the receiver, minus any additional arguments (after the receiver) + $defineProperty( + func, + 'length', + { value: 1 + $max(0, originalFunction.length - (arguments.length - 1)) } + ); + } + } + return func; +}; + +var applyBind = function applyBind() { + return $reflectApply(bind, $apply, arguments); +}; + +if ($defineProperty) { + $defineProperty(module.exports, 'apply', { value: applyBind }); +} else { + module.exports.apply = applyBind; +} + +},{"function-bind":109,"get-intrinsic":110}],71:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer +var Transform = require('stream').Transform +var StringDecoder = require('string_decoder').StringDecoder +var inherits = require('inherits') + +function CipherBase (hashMode) { + Transform.call(this) + this.hashMode = typeof hashMode === 'string' + if (this.hashMode) { + this[hashMode] = this._finalOrDigest + } else { + this.final = this._finalOrDigest + } + if (this._final) { + this.__final = this._final + this._final = null + } + this._decoder = null + this._encoding = null +} +inherits(CipherBase, Transform) + +CipherBase.prototype.update = function (data, inputEnc, outputEnc) { + if (typeof data === 'string') { + data = Buffer.from(data, inputEnc) + } + + var outData = this._update(data) + if (this.hashMode) return this + + if (outputEnc) { + outData = this._toString(outData, outputEnc) + } + + return outData +} + +CipherBase.prototype.setAutoPadding = function () {} +CipherBase.prototype.getAuthTag = function () { + throw new Error('trying to get auth tag in unsupported state') +} + +CipherBase.prototype.setAuthTag = function () { + throw new Error('trying to set auth tag in unsupported state') +} + +CipherBase.prototype.setAAD = function () { + throw new Error('trying to set aad in unsupported state') +} + +CipherBase.prototype._transform = function (data, _, next) { + var err + try { + if (this.hashMode) { + this._update(data) + } else { + this.push(this._update(data)) + } + } catch (e) { + err = e + } finally { + next(err) + } +} +CipherBase.prototype._flush = function (done) { + var err + try { + this.push(this.__final()) + } catch (e) { + err = e + } + + done(err) +} +CipherBase.prototype._finalOrDigest = function (outputEnc) { + var outData = this.__final() || Buffer.alloc(0) + if (outputEnc) { + outData = this._toString(outData, outputEnc, true) + } + return outData +} + +CipherBase.prototype._toString = function (value, enc, fin) { + if (!this._decoder) { + this._decoder = new StringDecoder(enc) + this._encoding = enc + } + + if (this._encoding !== enc) throw new Error('can\'t switch encodings') + + var out = this._decoder.write(value) + if (fin) { + out += this._decoder.end() + } + + return out +} + +module.exports = CipherBase + +},{"inherits":146,"safe-buffer":250,"stream":264,"string_decoder":279}],72:[function(require,module,exports){ +/*! + * content-type + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict' + +/** + * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 + * + * parameter = token "=" ( token / quoted-string ) + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + * obs-text = %x80-FF + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + */ +var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g // eslint-disable-line no-control-regex +var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex +var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/ + +/** + * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 + * + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + * obs-text = %x80-FF + */ +var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex + +/** + * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6 + */ +var QUOTE_REGEXP = /([\\"])/g + +/** + * RegExp to match type in RFC 7231 sec 3.1.1.1 + * + * media-type = type "/" subtype + * type = token + * subtype = token + */ +var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/ + +/** + * Module exports. + * @public + */ + +exports.format = format +exports.parse = parse + +/** + * Format object to media type. + * + * @param {object} obj + * @return {string} + * @public + */ + +function format (obj) { + if (!obj || typeof obj !== 'object') { + throw new TypeError('argument obj is required') + } + + var parameters = obj.parameters + var type = obj.type + + if (!type || !TYPE_REGEXP.test(type)) { + throw new TypeError('invalid type') + } + + var string = type + + // append parameters + if (parameters && typeof parameters === 'object') { + var param + var params = Object.keys(parameters).sort() + + for (var i = 0; i < params.length; i++) { + param = params[i] + + if (!TOKEN_REGEXP.test(param)) { + throw new TypeError('invalid parameter name') + } + + string += '; ' + param + '=' + qstring(parameters[param]) + } + } + + return string +} + +/** + * Parse media type to object. + * + * @param {string|object} string + * @return {Object} + * @public + */ + +function parse (string) { + if (!string) { + throw new TypeError('argument string is required') + } + + // support req/res-like objects as argument + var header = typeof string === 'object' + ? getcontenttype(string) + : string + + if (typeof header !== 'string') { + throw new TypeError('argument string is required to be a string') + } + + var index = header.indexOf(';') + var type = index !== -1 + ? header.slice(0, index).trim() + : header.trim() + + if (!TYPE_REGEXP.test(type)) { + throw new TypeError('invalid media type') + } + + var obj = new ContentType(type.toLowerCase()) + + // parse parameters + if (index !== -1) { + var key + var match + var value + + PARAM_REGEXP.lastIndex = index + + while ((match = PARAM_REGEXP.exec(header))) { + if (match.index !== index) { + throw new TypeError('invalid parameter format') + } + + index += match[0].length + key = match[1].toLowerCase() + value = match[2] + + if (value.charCodeAt(0) === 0x22 /* " */) { + // remove quotes + value = value.slice(1, -1) + + // remove escapes + if (value.indexOf('\\') !== -1) { + value = value.replace(QESC_REGEXP, '$1') + } + } + + obj.parameters[key] = value + } + + if (index !== header.length) { + throw new TypeError('invalid parameter format') + } + } + + return obj +} + +/** + * Get content-type from req/res objects. + * + * @param {object} + * @return {Object} + * @private + */ + +function getcontenttype (obj) { + var header + + if (typeof obj.getHeader === 'function') { + // res-like + header = obj.getHeader('content-type') + } else if (typeof obj.headers === 'object') { + // req-like + header = obj.headers && obj.headers['content-type'] + } + + if (typeof header !== 'string') { + throw new TypeError('content-type header is missing from object') + } + + return header +} + +/** + * Quote a string if necessary. + * + * @param {string} val + * @return {string} + * @private + */ + +function qstring (val) { + var str = String(val) + + // no need to quote tokens + if (TOKEN_REGEXP.test(str)) { + return str + } + + if (str.length > 0 && !TEXT_REGEXP.test(str)) { + throw new TypeError('invalid parameter value') + } + + return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"' +} + +/** + * Class to represent a content type. + * @private + */ +function ContentType (type) { + this.parameters = Object.create(null) + this.type = type +} + +},{}],73:[function(require,module,exports){ +(function (Buffer){(function (){ +var elliptic = require('elliptic') +var BN = require('bn.js') + +module.exports = function createECDH (curve) { + return new ECDH(curve) +} + +var aliases = { + secp256k1: { + name: 'secp256k1', + byteLength: 32 + }, + secp224r1: { + name: 'p224', + byteLength: 28 + }, + prime256v1: { + name: 'p256', + byteLength: 32 + }, + prime192v1: { + name: 'p192', + byteLength: 24 + }, + ed25519: { + name: 'ed25519', + byteLength: 32 + }, + secp384r1: { + name: 'p384', + byteLength: 48 + }, + secp521r1: { + name: 'p521', + byteLength: 66 + } +} + +aliases.p224 = aliases.secp224r1 +aliases.p256 = aliases.secp256r1 = aliases.prime256v1 +aliases.p192 = aliases.secp192r1 = aliases.prime192v1 +aliases.p384 = aliases.secp384r1 +aliases.p521 = aliases.secp521r1 + +function ECDH (curve) { + this.curveType = aliases[curve] + if (!this.curveType) { + this.curveType = { + name: curve + } + } + this.curve = new elliptic.ec(this.curveType.name) // eslint-disable-line new-cap + this.keys = void 0 +} + +ECDH.prototype.generateKeys = function (enc, format) { + this.keys = this.curve.genKeyPair() + return this.getPublicKey(enc, format) +} + +ECDH.prototype.computeSecret = function (other, inenc, enc) { + inenc = inenc || 'utf8' + if (!Buffer.isBuffer(other)) { + other = new Buffer(other, inenc) + } + var otherPub = this.curve.keyFromPublic(other).getPublic() + var out = otherPub.mul(this.keys.getPrivate()).getX() + return formatReturnValue(out, enc, this.curveType.byteLength) +} + +ECDH.prototype.getPublicKey = function (enc, format) { + var key = this.keys.getPublic(format === 'compressed', true) + if (format === 'hybrid') { + if (key[key.length - 1] % 2) { + key[0] = 7 + } else { + key[0] = 6 + } + } + return formatReturnValue(key, enc) +} + +ECDH.prototype.getPrivateKey = function (enc) { + return formatReturnValue(this.keys.getPrivate(), enc) +} + +ECDH.prototype.setPublicKey = function (pub, enc) { + enc = enc || 'utf8' + if (!Buffer.isBuffer(pub)) { + pub = new Buffer(pub, enc) + } + this.keys._importPublic(pub) + return this +} + +ECDH.prototype.setPrivateKey = function (priv, enc) { + enc = enc || 'utf8' + if (!Buffer.isBuffer(priv)) { + priv = new Buffer(priv, enc) + } + + var _priv = new BN(priv) + _priv = _priv.toString(16) + this.keys = this.curve.genKeyPair() + this.keys._importPrivate(_priv) + return this +} + +function formatReturnValue (bn, enc, len) { + if (!Array.isArray(bn)) { + bn = bn.toArray() + } + var buf = new Buffer(bn) + if (len && buf.length < len) { + var zeros = new Buffer(len - buf.length) + zeros.fill(0) + buf = Buffer.concat([zeros, buf]) + } + if (!enc) { + return buf + } else { + return buf.toString(enc) + } +} + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"bn.js":18,"buffer":68,"elliptic":89}],74:[function(require,module,exports){ +'use strict' +var inherits = require('inherits') +var MD5 = require('md5.js') +var RIPEMD160 = require('ripemd160') +var sha = require('sha.js') +var Base = require('cipher-base') + +function Hash (hash) { + Base.call(this, 'digest') + + this._hash = hash +} + +inherits(Hash, Base) + +Hash.prototype._update = function (data) { + this._hash.update(data) +} + +Hash.prototype._final = function () { + return this._hash.digest() +} + +module.exports = function createHash (alg) { + alg = alg.toLowerCase() + if (alg === 'md5') return new MD5() + if (alg === 'rmd160' || alg === 'ripemd160') return new RIPEMD160() + + return new Hash(sha(alg)) +} + +},{"cipher-base":71,"inherits":146,"md5.js":221,"ripemd160":249,"sha.js":257}],75:[function(require,module,exports){ +var MD5 = require('md5.js') + +module.exports = function (buffer) { + return new MD5().update(buffer).digest() +} + +},{"md5.js":221}],76:[function(require,module,exports){ +'use strict' +var inherits = require('inherits') +var Legacy = require('./legacy') +var Base = require('cipher-base') +var Buffer = require('safe-buffer').Buffer +var md5 = require('create-hash/md5') +var RIPEMD160 = require('ripemd160') + +var sha = require('sha.js') + +var ZEROS = Buffer.alloc(128) + +function Hmac (alg, key) { + Base.call(this, 'digest') + if (typeof key === 'string') { + key = Buffer.from(key) + } + + var blocksize = (alg === 'sha512' || alg === 'sha384') ? 128 : 64 + + this._alg = alg + this._key = key + if (key.length > blocksize) { + var hash = alg === 'rmd160' ? new RIPEMD160() : sha(alg) + key = hash.update(key).digest() + } else if (key.length < blocksize) { + key = Buffer.concat([key, ZEROS], blocksize) + } + + var ipad = this._ipad = Buffer.allocUnsafe(blocksize) + var opad = this._opad = Buffer.allocUnsafe(blocksize) + + for (var i = 0; i < blocksize; i++) { + ipad[i] = key[i] ^ 0x36 + opad[i] = key[i] ^ 0x5C + } + this._hash = alg === 'rmd160' ? new RIPEMD160() : sha(alg) + this._hash.update(ipad) +} + +inherits(Hmac, Base) + +Hmac.prototype._update = function (data) { + this._hash.update(data) +} + +Hmac.prototype._final = function () { + var h = this._hash.digest() + var hash = this._alg === 'rmd160' ? new RIPEMD160() : sha(this._alg) + return hash.update(this._opad).update(h).digest() +} + +module.exports = function createHmac (alg, key) { + alg = alg.toLowerCase() + if (alg === 'rmd160' || alg === 'ripemd160') { + return new Hmac('rmd160', key) + } + if (alg === 'md5') { + return new Legacy(md5, key) + } + return new Hmac(alg, key) +} + +},{"./legacy":77,"cipher-base":71,"create-hash/md5":75,"inherits":146,"ripemd160":249,"safe-buffer":250,"sha.js":257}],77:[function(require,module,exports){ +'use strict' +var inherits = require('inherits') +var Buffer = require('safe-buffer').Buffer + +var Base = require('cipher-base') + +var ZEROS = Buffer.alloc(128) +var blocksize = 64 + +function Hmac (alg, key) { + Base.call(this, 'digest') + if (typeof key === 'string') { + key = Buffer.from(key) + } + + this._alg = alg + this._key = key + + if (key.length > blocksize) { + key = alg(key) + } else if (key.length < blocksize) { + key = Buffer.concat([key, ZEROS], blocksize) + } + + var ipad = this._ipad = Buffer.allocUnsafe(blocksize) + var opad = this._opad = Buffer.allocUnsafe(blocksize) + + for (var i = 0; i < blocksize; i++) { + ipad[i] = key[i] ^ 0x36 + opad[i] = key[i] ^ 0x5C + } + + this._hash = [ipad] +} + +inherits(Hmac, Base) + +Hmac.prototype._update = function (data) { + this._hash.push(data) +} + +Hmac.prototype._final = function () { + var h = this._alg(Buffer.concat(this._hash)) + return this._alg(Buffer.concat([this._opad, h])) +} +module.exports = Hmac + +},{"cipher-base":71,"inherits":146,"safe-buffer":250}],78:[function(require,module,exports){ +'use strict' + +exports.randomBytes = exports.rng = exports.pseudoRandomBytes = exports.prng = require('randombytes') +exports.createHash = exports.Hash = require('create-hash') +exports.createHmac = exports.Hmac = require('create-hmac') + +var algos = require('browserify-sign/algos') +var algoKeys = Object.keys(algos) +var hashes = ['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'md5', 'rmd160'].concat(algoKeys) +exports.getHashes = function () { + return hashes +} + +var p = require('pbkdf2') +exports.pbkdf2 = p.pbkdf2 +exports.pbkdf2Sync = p.pbkdf2Sync + +var aes = require('browserify-cipher') + +exports.Cipher = aes.Cipher +exports.createCipher = aes.createCipher +exports.Cipheriv = aes.Cipheriv +exports.createCipheriv = aes.createCipheriv +exports.Decipher = aes.Decipher +exports.createDecipher = aes.createDecipher +exports.Decipheriv = aes.Decipheriv +exports.createDecipheriv = aes.createDecipheriv +exports.getCiphers = aes.getCiphers +exports.listCiphers = aes.listCiphers + +var dh = require('diffie-hellman') + +exports.DiffieHellmanGroup = dh.DiffieHellmanGroup +exports.createDiffieHellmanGroup = dh.createDiffieHellmanGroup +exports.getDiffieHellman = dh.getDiffieHellman +exports.createDiffieHellman = dh.createDiffieHellman +exports.DiffieHellman = dh.DiffieHellman + +var sign = require('browserify-sign') + +exports.createSign = sign.createSign +exports.Sign = sign.Sign +exports.createVerify = sign.createVerify +exports.Verify = sign.Verify + +exports.createECDH = require('create-ecdh') + +var publicEncrypt = require('public-encrypt') + +exports.publicEncrypt = publicEncrypt.publicEncrypt +exports.privateEncrypt = publicEncrypt.privateEncrypt +exports.publicDecrypt = publicEncrypt.publicDecrypt +exports.privateDecrypt = publicEncrypt.privateDecrypt + +// the least I can do is make error messages for the rest of the node.js/crypto api. +// ;[ +// 'createCredentials' +// ].forEach(function (name) { +// exports[name] = function () { +// throw new Error([ +// 'sorry, ' + name + ' is not implemented yet', +// 'we accept pull requests', +// 'https://github.com/crypto-browserify/crypto-browserify' +// ].join('\n')) +// } +// }) + +var rf = require('randomfill') + +exports.randomFill = rf.randomFill +exports.randomFillSync = rf.randomFillSync + +exports.createCredentials = function () { + throw new Error([ + 'sorry, createCredentials is not implemented yet', + 'we accept pull requests', + 'https://github.com/crypto-browserify/crypto-browserify' + ].join('\n')) +} + +exports.constants = { + 'DH_CHECK_P_NOT_SAFE_PRIME': 2, + 'DH_CHECK_P_NOT_PRIME': 1, + 'DH_UNABLE_TO_CHECK_GENERATOR': 4, + 'DH_NOT_SUITABLE_GENERATOR': 8, + 'NPN_ENABLED': 1, + 'ALPN_ENABLED': 1, + 'RSA_PKCS1_PADDING': 1, + 'RSA_SSLV23_PADDING': 2, + 'RSA_NO_PADDING': 3, + 'RSA_PKCS1_OAEP_PADDING': 4, + 'RSA_X931_PADDING': 5, + 'RSA_PKCS1_PSS_PADDING': 6, + 'POINT_CONVERSION_COMPRESSED': 2, + 'POINT_CONVERSION_UNCOMPRESSED': 4, + 'POINT_CONVERSION_HYBRID': 6 +} + +},{"browserify-cipher":38,"browserify-sign":46,"browserify-sign/algos":43,"create-ecdh":73,"create-hash":74,"create-hmac":76,"diffie-hellman":85,"pbkdf2":231,"public-encrypt":238,"randombytes":244,"randomfill":245}],79:[function(require,module,exports){ +'use strict'; + +exports.utils = require('./des/utils'); +exports.Cipher = require('./des/cipher'); +exports.DES = require('./des/des'); +exports.CBC = require('./des/cbc'); +exports.EDE = require('./des/ede'); + +},{"./des/cbc":80,"./des/cipher":81,"./des/des":82,"./des/ede":83,"./des/utils":84}],80:[function(require,module,exports){ +'use strict'; + +var assert = require('minimalistic-assert'); +var inherits = require('inherits'); + +var proto = {}; + +function CBCState(iv) { + assert.equal(iv.length, 8, 'Invalid IV length'); + + this.iv = new Array(8); + for (var i = 0; i < this.iv.length; i++) + this.iv[i] = iv[i]; +} + +function instantiate(Base) { + function CBC(options) { + Base.call(this, options); + this._cbcInit(); + } + inherits(CBC, Base); + + var keys = Object.keys(proto); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + CBC.prototype[key] = proto[key]; + } + + CBC.create = function create(options) { + return new CBC(options); + }; + + return CBC; +} + +exports.instantiate = instantiate; + +proto._cbcInit = function _cbcInit() { + var state = new CBCState(this.options.iv); + this._cbcState = state; +}; + +proto._update = function _update(inp, inOff, out, outOff) { + var state = this._cbcState; + var superProto = this.constructor.super_.prototype; + + var iv = state.iv; + if (this.type === 'encrypt') { + for (var i = 0; i < this.blockSize; i++) + iv[i] ^= inp[inOff + i]; + + superProto._update.call(this, iv, 0, out, outOff); + + for (var i = 0; i < this.blockSize; i++) + iv[i] = out[outOff + i]; + } else { + superProto._update.call(this, inp, inOff, out, outOff); + + for (var i = 0; i < this.blockSize; i++) + out[outOff + i] ^= iv[i]; + + for (var i = 0; i < this.blockSize; i++) + iv[i] = inp[inOff + i]; + } +}; + +},{"inherits":146,"minimalistic-assert":223}],81:[function(require,module,exports){ +'use strict'; + +var assert = require('minimalistic-assert'); + +function Cipher(options) { + this.options = options; + + this.type = this.options.type; + this.blockSize = 8; + this._init(); + + this.buffer = new Array(this.blockSize); + this.bufferOff = 0; +} +module.exports = Cipher; + +Cipher.prototype._init = function _init() { + // Might be overrided +}; + +Cipher.prototype.update = function update(data) { + if (data.length === 0) + return []; + + if (this.type === 'decrypt') + return this._updateDecrypt(data); + else + return this._updateEncrypt(data); +}; + +Cipher.prototype._buffer = function _buffer(data, off) { + // Append data to buffer + var min = Math.min(this.buffer.length - this.bufferOff, data.length - off); + for (var i = 0; i < min; i++) + this.buffer[this.bufferOff + i] = data[off + i]; + this.bufferOff += min; + + // Shift next + return min; +}; + +Cipher.prototype._flushBuffer = function _flushBuffer(out, off) { + this._update(this.buffer, 0, out, off); + this.bufferOff = 0; + return this.blockSize; +}; + +Cipher.prototype._updateEncrypt = function _updateEncrypt(data) { + var inputOff = 0; + var outputOff = 0; + + var count = ((this.bufferOff + data.length) / this.blockSize) | 0; + var out = new Array(count * this.blockSize); + + if (this.bufferOff !== 0) { + inputOff += this._buffer(data, inputOff); + + if (this.bufferOff === this.buffer.length) + outputOff += this._flushBuffer(out, outputOff); + } + + // Write blocks + var max = data.length - ((data.length - inputOff) % this.blockSize); + for (; inputOff < max; inputOff += this.blockSize) { + this._update(data, inputOff, out, outputOff); + outputOff += this.blockSize; + } + + // Queue rest + for (; inputOff < data.length; inputOff++, this.bufferOff++) + this.buffer[this.bufferOff] = data[inputOff]; + + return out; +}; + +Cipher.prototype._updateDecrypt = function _updateDecrypt(data) { + var inputOff = 0; + var outputOff = 0; + + var count = Math.ceil((this.bufferOff + data.length) / this.blockSize) - 1; + var out = new Array(count * this.blockSize); + + // TODO(indutny): optimize it, this is far from optimal + for (; count > 0; count--) { + inputOff += this._buffer(data, inputOff); + outputOff += this._flushBuffer(out, outputOff); + } + + // Buffer rest of the input + inputOff += this._buffer(data, inputOff); + + return out; +}; + +Cipher.prototype.final = function final(buffer) { + var first; + if (buffer) + first = this.update(buffer); + + var last; + if (this.type === 'encrypt') + last = this._finalEncrypt(); + else + last = this._finalDecrypt(); + + if (first) + return first.concat(last); + else + return last; +}; + +Cipher.prototype._pad = function _pad(buffer, off) { + if (off === 0) + return false; + + while (off < buffer.length) + buffer[off++] = 0; + + return true; +}; + +Cipher.prototype._finalEncrypt = function _finalEncrypt() { + if (!this._pad(this.buffer, this.bufferOff)) + return []; + + var out = new Array(this.blockSize); + this._update(this.buffer, 0, out, 0); + return out; +}; + +Cipher.prototype._unpad = function _unpad(buffer) { + return buffer; +}; + +Cipher.prototype._finalDecrypt = function _finalDecrypt() { + assert.equal(this.bufferOff, this.blockSize, 'Not enough data to decrypt'); + var out = new Array(this.blockSize); + this._flushBuffer(out, 0); + + return this._unpad(out); +}; + +},{"minimalistic-assert":223}],82:[function(require,module,exports){ +'use strict'; + +var assert = require('minimalistic-assert'); +var inherits = require('inherits'); + +var utils = require('./utils'); +var Cipher = require('./cipher'); + +function DESState() { + this.tmp = new Array(2); + this.keys = null; +} + +function DES(options) { + Cipher.call(this, options); + + var state = new DESState(); + this._desState = state; + + this.deriveKeys(state, options.key); +} +inherits(DES, Cipher); +module.exports = DES; + +DES.create = function create(options) { + return new DES(options); +}; + +var shiftTable = [ + 1, 1, 2, 2, 2, 2, 2, 2, + 1, 2, 2, 2, 2, 2, 2, 1 +]; + +DES.prototype.deriveKeys = function deriveKeys(state, key) { + state.keys = new Array(16 * 2); + + assert.equal(key.length, this.blockSize, 'Invalid key length'); + + var kL = utils.readUInt32BE(key, 0); + var kR = utils.readUInt32BE(key, 4); + + utils.pc1(kL, kR, state.tmp, 0); + kL = state.tmp[0]; + kR = state.tmp[1]; + for (var i = 0; i < state.keys.length; i += 2) { + var shift = shiftTable[i >>> 1]; + kL = utils.r28shl(kL, shift); + kR = utils.r28shl(kR, shift); + utils.pc2(kL, kR, state.keys, i); + } +}; + +DES.prototype._update = function _update(inp, inOff, out, outOff) { + var state = this._desState; + + var l = utils.readUInt32BE(inp, inOff); + var r = utils.readUInt32BE(inp, inOff + 4); + + // Initial Permutation + utils.ip(l, r, state.tmp, 0); + l = state.tmp[0]; + r = state.tmp[1]; + + if (this.type === 'encrypt') + this._encrypt(state, l, r, state.tmp, 0); + else + this._decrypt(state, l, r, state.tmp, 0); + + l = state.tmp[0]; + r = state.tmp[1]; + + utils.writeUInt32BE(out, l, outOff); + utils.writeUInt32BE(out, r, outOff + 4); +}; + +DES.prototype._pad = function _pad(buffer, off) { + var value = buffer.length - off; + for (var i = off; i < buffer.length; i++) + buffer[i] = value; + + return true; +}; + +DES.prototype._unpad = function _unpad(buffer) { + var pad = buffer[buffer.length - 1]; + for (var i = buffer.length - pad; i < buffer.length; i++) + assert.equal(buffer[i], pad); + + return buffer.slice(0, buffer.length - pad); +}; + +DES.prototype._encrypt = function _encrypt(state, lStart, rStart, out, off) { + var l = lStart; + var r = rStart; + + // Apply f() x16 times + for (var i = 0; i < state.keys.length; i += 2) { + var keyL = state.keys[i]; + var keyR = state.keys[i + 1]; + + // f(r, k) + utils.expand(r, state.tmp, 0); + + keyL ^= state.tmp[0]; + keyR ^= state.tmp[1]; + var s = utils.substitute(keyL, keyR); + var f = utils.permute(s); + + var t = r; + r = (l ^ f) >>> 0; + l = t; + } + + // Reverse Initial Permutation + utils.rip(r, l, out, off); +}; + +DES.prototype._decrypt = function _decrypt(state, lStart, rStart, out, off) { + var l = rStart; + var r = lStart; + + // Apply f() x16 times + for (var i = state.keys.length - 2; i >= 0; i -= 2) { + var keyL = state.keys[i]; + var keyR = state.keys[i + 1]; + + // f(r, k) + utils.expand(l, state.tmp, 0); + + keyL ^= state.tmp[0]; + keyR ^= state.tmp[1]; + var s = utils.substitute(keyL, keyR); + var f = utils.permute(s); + + var t = l; + l = (r ^ f) >>> 0; + r = t; + } + + // Reverse Initial Permutation + utils.rip(l, r, out, off); +}; + +},{"./cipher":81,"./utils":84,"inherits":146,"minimalistic-assert":223}],83:[function(require,module,exports){ +'use strict'; + +var assert = require('minimalistic-assert'); +var inherits = require('inherits'); + +var Cipher = require('./cipher'); +var DES = require('./des'); + +function EDEState(type, key) { + assert.equal(key.length, 24, 'Invalid key length'); + + var k1 = key.slice(0, 8); + var k2 = key.slice(8, 16); + var k3 = key.slice(16, 24); + + if (type === 'encrypt') { + this.ciphers = [ + DES.create({ type: 'encrypt', key: k1 }), + DES.create({ type: 'decrypt', key: k2 }), + DES.create({ type: 'encrypt', key: k3 }) + ]; + } else { + this.ciphers = [ + DES.create({ type: 'decrypt', key: k3 }), + DES.create({ type: 'encrypt', key: k2 }), + DES.create({ type: 'decrypt', key: k1 }) + ]; + } +} + +function EDE(options) { + Cipher.call(this, options); + + var state = new EDEState(this.type, this.options.key); + this._edeState = state; +} +inherits(EDE, Cipher); + +module.exports = EDE; + +EDE.create = function create(options) { + return new EDE(options); +}; + +EDE.prototype._update = function _update(inp, inOff, out, outOff) { + var state = this._edeState; + + state.ciphers[0]._update(inp, inOff, out, outOff); + state.ciphers[1]._update(out, outOff, out, outOff); + state.ciphers[2]._update(out, outOff, out, outOff); +}; + +EDE.prototype._pad = DES.prototype._pad; +EDE.prototype._unpad = DES.prototype._unpad; + +},{"./cipher":81,"./des":82,"inherits":146,"minimalistic-assert":223}],84:[function(require,module,exports){ +'use strict'; + +exports.readUInt32BE = function readUInt32BE(bytes, off) { + var res = (bytes[0 + off] << 24) | + (bytes[1 + off] << 16) | + (bytes[2 + off] << 8) | + bytes[3 + off]; + return res >>> 0; +}; + +exports.writeUInt32BE = function writeUInt32BE(bytes, value, off) { + bytes[0 + off] = value >>> 24; + bytes[1 + off] = (value >>> 16) & 0xff; + bytes[2 + off] = (value >>> 8) & 0xff; + bytes[3 + off] = value & 0xff; +}; + +exports.ip = function ip(inL, inR, out, off) { + var outL = 0; + var outR = 0; + + for (var i = 6; i >= 0; i -= 2) { + for (var j = 0; j <= 24; j += 8) { + outL <<= 1; + outL |= (inR >>> (j + i)) & 1; + } + for (var j = 0; j <= 24; j += 8) { + outL <<= 1; + outL |= (inL >>> (j + i)) & 1; + } + } + + for (var i = 6; i >= 0; i -= 2) { + for (var j = 1; j <= 25; j += 8) { + outR <<= 1; + outR |= (inR >>> (j + i)) & 1; + } + for (var j = 1; j <= 25; j += 8) { + outR <<= 1; + outR |= (inL >>> (j + i)) & 1; + } + } + + out[off + 0] = outL >>> 0; + out[off + 1] = outR >>> 0; +}; + +exports.rip = function rip(inL, inR, out, off) { + var outL = 0; + var outR = 0; + + for (var i = 0; i < 4; i++) { + for (var j = 24; j >= 0; j -= 8) { + outL <<= 1; + outL |= (inR >>> (j + i)) & 1; + outL <<= 1; + outL |= (inL >>> (j + i)) & 1; + } + } + for (var i = 4; i < 8; i++) { + for (var j = 24; j >= 0; j -= 8) { + outR <<= 1; + outR |= (inR >>> (j + i)) & 1; + outR <<= 1; + outR |= (inL >>> (j + i)) & 1; + } + } + + out[off + 0] = outL >>> 0; + out[off + 1] = outR >>> 0; +}; + +exports.pc1 = function pc1(inL, inR, out, off) { + var outL = 0; + var outR = 0; + + // 7, 15, 23, 31, 39, 47, 55, 63 + // 6, 14, 22, 30, 39, 47, 55, 63 + // 5, 13, 21, 29, 39, 47, 55, 63 + // 4, 12, 20, 28 + for (var i = 7; i >= 5; i--) { + for (var j = 0; j <= 24; j += 8) { + outL <<= 1; + outL |= (inR >> (j + i)) & 1; + } + for (var j = 0; j <= 24; j += 8) { + outL <<= 1; + outL |= (inL >> (j + i)) & 1; + } + } + for (var j = 0; j <= 24; j += 8) { + outL <<= 1; + outL |= (inR >> (j + i)) & 1; + } + + // 1, 9, 17, 25, 33, 41, 49, 57 + // 2, 10, 18, 26, 34, 42, 50, 58 + // 3, 11, 19, 27, 35, 43, 51, 59 + // 36, 44, 52, 60 + for (var i = 1; i <= 3; i++) { + for (var j = 0; j <= 24; j += 8) { + outR <<= 1; + outR |= (inR >> (j + i)) & 1; + } + for (var j = 0; j <= 24; j += 8) { + outR <<= 1; + outR |= (inL >> (j + i)) & 1; + } + } + for (var j = 0; j <= 24; j += 8) { + outR <<= 1; + outR |= (inL >> (j + i)) & 1; + } + + out[off + 0] = outL >>> 0; + out[off + 1] = outR >>> 0; +}; + +exports.r28shl = function r28shl(num, shift) { + return ((num << shift) & 0xfffffff) | (num >>> (28 - shift)); +}; + +var pc2table = [ + // inL => outL + 14, 11, 17, 4, 27, 23, 25, 0, + 13, 22, 7, 18, 5, 9, 16, 24, + 2, 20, 12, 21, 1, 8, 15, 26, + + // inR => outR + 15, 4, 25, 19, 9, 1, 26, 16, + 5, 11, 23, 8, 12, 7, 17, 0, + 22, 3, 10, 14, 6, 20, 27, 24 +]; + +exports.pc2 = function pc2(inL, inR, out, off) { + var outL = 0; + var outR = 0; + + var len = pc2table.length >>> 1; + for (var i = 0; i < len; i++) { + outL <<= 1; + outL |= (inL >>> pc2table[i]) & 0x1; + } + for (var i = len; i < pc2table.length; i++) { + outR <<= 1; + outR |= (inR >>> pc2table[i]) & 0x1; + } + + out[off + 0] = outL >>> 0; + out[off + 1] = outR >>> 0; +}; + +exports.expand = function expand(r, out, off) { + var outL = 0; + var outR = 0; + + outL = ((r & 1) << 5) | (r >>> 27); + for (var i = 23; i >= 15; i -= 4) { + outL <<= 6; + outL |= (r >>> i) & 0x3f; + } + for (var i = 11; i >= 3; i -= 4) { + outR |= (r >>> i) & 0x3f; + outR <<= 6; + } + outR |= ((r & 0x1f) << 1) | (r >>> 31); + + out[off + 0] = outL >>> 0; + out[off + 1] = outR >>> 0; +}; + +var sTable = [ + 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, + 3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, + 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, + 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13, + + 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, + 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, + 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, + 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9, + + 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, + 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, + 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, + 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12, + + 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, + 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, + 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, + 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14, + + 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, + 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, + 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, + 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3, + + 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, + 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, + 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, + 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13, + + 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, + 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, + 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, + 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12, + + 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, + 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, + 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, + 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 +]; + +exports.substitute = function substitute(inL, inR) { + var out = 0; + for (var i = 0; i < 4; i++) { + var b = (inL >>> (18 - i * 6)) & 0x3f; + var sb = sTable[i * 0x40 + b]; + + out <<= 4; + out |= sb; + } + for (var i = 0; i < 4; i++) { + var b = (inR >>> (18 - i * 6)) & 0x3f; + var sb = sTable[4 * 0x40 + i * 0x40 + b]; + + out <<= 4; + out |= sb; + } + return out >>> 0; +}; + +var permuteTable = [ + 16, 25, 12, 11, 3, 20, 4, 15, 31, 17, 9, 6, 27, 14, 1, 22, + 30, 24, 8, 18, 0, 5, 29, 23, 13, 19, 2, 26, 10, 21, 28, 7 +]; + +exports.permute = function permute(num) { + var out = 0; + for (var i = 0; i < permuteTable.length; i++) { + out <<= 1; + out |= (num >>> permuteTable[i]) & 0x1; + } + return out >>> 0; +}; + +exports.padSplit = function padSplit(num, size, group) { + var str = num.toString(2); + while (str.length < size) + str = '0' + str; + + var out = []; + for (var i = 0; i < size; i += group) + out.push(str.slice(i, i + group)); + return out.join(' '); +}; + +},{}],85:[function(require,module,exports){ +(function (Buffer){(function (){ +var generatePrime = require('./lib/generatePrime') +var primes = require('./lib/primes.json') + +var DH = require('./lib/dh') + +function getDiffieHellman (mod) { + var prime = new Buffer(primes[mod].prime, 'hex') + var gen = new Buffer(primes[mod].gen, 'hex') + + return new DH(prime, gen) +} + +var ENCODINGS = { + 'binary': true, 'hex': true, 'base64': true +} + +function createDiffieHellman (prime, enc, generator, genc) { + if (Buffer.isBuffer(enc) || ENCODINGS[enc] === undefined) { + return createDiffieHellman(prime, 'binary', enc, generator) + } + + enc = enc || 'binary' + genc = genc || 'binary' + generator = generator || new Buffer([2]) + + if (!Buffer.isBuffer(generator)) { + generator = new Buffer(generator, genc) + } + + if (typeof prime === 'number') { + return new DH(generatePrime(prime, generator), generator, true) + } + + if (!Buffer.isBuffer(prime)) { + prime = new Buffer(prime, enc) + } + + return new DH(prime, generator, true) +} + +exports.DiffieHellmanGroup = exports.createDiffieHellmanGroup = exports.getDiffieHellman = getDiffieHellman +exports.createDiffieHellman = exports.DiffieHellman = createDiffieHellman + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"./lib/dh":86,"./lib/generatePrime":87,"./lib/primes.json":88,"buffer":68}],86:[function(require,module,exports){ +(function (Buffer){(function (){ +var BN = require('bn.js'); +var MillerRabin = require('miller-rabin'); +var millerRabin = new MillerRabin(); +var TWENTYFOUR = new BN(24); +var ELEVEN = new BN(11); +var TEN = new BN(10); +var THREE = new BN(3); +var SEVEN = new BN(7); +var primes = require('./generatePrime'); +var randomBytes = require('randombytes'); +module.exports = DH; + +function setPublicKey(pub, enc) { + enc = enc || 'utf8'; + if (!Buffer.isBuffer(pub)) { + pub = new Buffer(pub, enc); + } + this._pub = new BN(pub); + return this; +} + +function setPrivateKey(priv, enc) { + enc = enc || 'utf8'; + if (!Buffer.isBuffer(priv)) { + priv = new Buffer(priv, enc); + } + this._priv = new BN(priv); + return this; +} + +var primeCache = {}; +function checkPrime(prime, generator) { + var gen = generator.toString('hex'); + var hex = [gen, prime.toString(16)].join('_'); + if (hex in primeCache) { + return primeCache[hex]; + } + var error = 0; + + if (prime.isEven() || + !primes.simpleSieve || + !primes.fermatTest(prime) || + !millerRabin.test(prime)) { + //not a prime so +1 + error += 1; + + if (gen === '02' || gen === '05') { + // we'd be able to check the generator + // it would fail so +8 + error += 8; + } else { + //we wouldn't be able to test the generator + // so +4 + error += 4; + } + primeCache[hex] = error; + return error; + } + if (!millerRabin.test(prime.shrn(1))) { + //not a safe prime + error += 2; + } + var rem; + switch (gen) { + case '02': + if (prime.mod(TWENTYFOUR).cmp(ELEVEN)) { + // unsuidable generator + error += 8; + } + break; + case '05': + rem = prime.mod(TEN); + if (rem.cmp(THREE) && rem.cmp(SEVEN)) { + // prime mod 10 needs to equal 3 or 7 + error += 8; + } + break; + default: + error += 4; + } + primeCache[hex] = error; + return error; +} + +function DH(prime, generator, malleable) { + this.setGenerator(generator); + this.__prime = new BN(prime); + this._prime = BN.mont(this.__prime); + this._primeLen = prime.length; + this._pub = undefined; + this._priv = undefined; + this._primeCode = undefined; + if (malleable) { + this.setPublicKey = setPublicKey; + this.setPrivateKey = setPrivateKey; + } else { + this._primeCode = 8; + } +} +Object.defineProperty(DH.prototype, 'verifyError', { + enumerable: true, + get: function () { + if (typeof this._primeCode !== 'number') { + this._primeCode = checkPrime(this.__prime, this.__gen); + } + return this._primeCode; + } +}); +DH.prototype.generateKeys = function () { + if (!this._priv) { + this._priv = new BN(randomBytes(this._primeLen)); + } + this._pub = this._gen.toRed(this._prime).redPow(this._priv).fromRed(); + return this.getPublicKey(); +}; + +DH.prototype.computeSecret = function (other) { + other = new BN(other); + other = other.toRed(this._prime); + var secret = other.redPow(this._priv).fromRed(); + var out = new Buffer(secret.toArray()); + var prime = this.getPrime(); + if (out.length < prime.length) { + var front = new Buffer(prime.length - out.length); + front.fill(0); + out = Buffer.concat([front, out]); + } + return out; +}; + +DH.prototype.getPublicKey = function getPublicKey(enc) { + return formatReturnValue(this._pub, enc); +}; + +DH.prototype.getPrivateKey = function getPrivateKey(enc) { + return formatReturnValue(this._priv, enc); +}; + +DH.prototype.getPrime = function (enc) { + return formatReturnValue(this.__prime, enc); +}; + +DH.prototype.getGenerator = function (enc) { + return formatReturnValue(this._gen, enc); +}; + +DH.prototype.setGenerator = function (gen, enc) { + enc = enc || 'utf8'; + if (!Buffer.isBuffer(gen)) { + gen = new Buffer(gen, enc); + } + this.__gen = gen; + this._gen = new BN(gen); + return this; +}; + +function formatReturnValue(bn, enc) { + var buf = new Buffer(bn.toArray()); + if (!enc) { + return buf; + } else { + return buf.toString(enc); + } +} + +}).call(this)}).call(this,require("buffer").Buffer) + +},{"./generatePrime":87,"bn.js":18,"buffer":68,"miller-rabin":222,"randombytes":244}],87:[function(require,module,exports){ +var randomBytes = require('randombytes'); +module.exports = findPrime; +findPrime.simpleSieve = simpleSieve; +findPrime.fermatTest = fermatTest; +var BN = require('bn.js'); +var TWENTYFOUR = new BN(24); +var MillerRabin = require('miller-rabin'); +var millerRabin = new MillerRabin(); +var ONE = new BN(1); +var TWO = new BN(2); +var FIVE = new BN(5); +var SIXTEEN = new BN(16); +var EIGHT = new BN(8); +var TEN = new BN(10); +var THREE = new BN(3); +var SEVEN = new BN(7); +var ELEVEN = new BN(11); +var FOUR = new BN(4); +var TWELVE = new BN(12); +var primes = null; + +function _getPrimes() { + if (primes !== null) + return primes; + + var limit = 0x100000; + var res = []; + res[0] = 2; + for (var i = 1, k = 3; k < limit; k += 2) { + var sqrt = Math.ceil(Math.sqrt(k)); + for (var j = 0; j < i && res[j] <= sqrt; j++) + if (k % res[j] === 0) + break; + + if (i !== j && res[j] <= sqrt) + continue; + + res[i++] = k; + } + primes = res; + return res; +} + +function simpleSieve(p) { + var primes = _getPrimes(); + + for (var i = 0; i < primes.length; i++) + if (p.modn(primes[i]) === 0) { + if (p.cmpn(primes[i]) === 0) { + return true; + } else { + return false; + } + } + + return true; +} + +function fermatTest(p) { + var red = BN.mont(p); + return TWO.toRed(red).redPow(p.subn(1)).fromRed().cmpn(1) === 0; +} + +function findPrime(bits, gen) { + if (bits < 16) { + // this is what openssl does + if (gen === 2 || gen === 5) { + return new BN([0x8c, 0x7b]); + } else { + return new BN([0x8c, 0x27]); + } + } + gen = new BN(gen); + + var num, n2; + + while (true) { + num = new BN(randomBytes(Math.ceil(bits / 8))); + while (num.bitLength() > bits) { + num.ishrn(1); + } + if (num.isEven()) { + num.iadd(ONE); + } + if (!num.testn(1)) { + num.iadd(TWO); + } + if (!gen.cmp(TWO)) { + while (num.mod(TWENTYFOUR).cmp(ELEVEN)) { + num.iadd(FOUR); + } + } else if (!gen.cmp(FIVE)) { + while (num.mod(TEN).cmp(THREE)) { + num.iadd(FOUR); + } + } + n2 = num.shrn(1); + if (simpleSieve(n2) && simpleSieve(num) && + fermatTest(n2) && fermatTest(num) && + millerRabin.test(n2) && millerRabin.test(num)) { + return num; + } + } + +} + +},{"bn.js":18,"miller-rabin":222,"randombytes":244}],88:[function(require,module,exports){ +module.exports={ + "modp1": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff" + }, + "modp2": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff" + }, + "modp5": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff" + }, + "modp14": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff" + }, + "modp15": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff" + }, + "modp16": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff" + }, + "modp17": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff" + }, + "modp18": { + "gen": "02", + "prime": "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff" + } +} +},{}],89:[function(require,module,exports){ +'use strict'; + +var elliptic = exports; + +elliptic.version = require('../package.json').version; +elliptic.utils = require('./elliptic/utils'); +elliptic.rand = require('brorand'); +elliptic.curve = require('./elliptic/curve'); +elliptic.curves = require('./elliptic/curves'); + +// Protocols +elliptic.ec = require('./elliptic/ec'); +elliptic.eddsa = require('./elliptic/eddsa'); + +},{"../package.json":104,"./elliptic/curve":92,"./elliptic/curves":95,"./elliptic/ec":96,"./elliptic/eddsa":99,"./elliptic/utils":103,"brorand":19}],90:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); +var utils = require('../utils'); +var getNAF = utils.getNAF; +var getJSF = utils.getJSF; +var assert = utils.assert; + +function BaseCurve(type, conf) { + this.type = type; + this.p = new BN(conf.p, 16); + + // Use Montgomery, when there is no fast reduction for the prime + this.red = conf.prime ? BN.red(conf.prime) : BN.mont(this.p); + + // Useful for many curves + this.zero = new BN(0).toRed(this.red); + this.one = new BN(1).toRed(this.red); + this.two = new BN(2).toRed(this.red); + + // Curve configuration, optional + this.n = conf.n && new BN(conf.n, 16); + this.g = conf.g && this.pointFromJSON(conf.g, conf.gRed); + + // Temporary arrays + this._wnafT1 = new Array(4); + this._wnafT2 = new Array(4); + this._wnafT3 = new Array(4); + this._wnafT4 = new Array(4); + + this._bitLength = this.n ? this.n.bitLength() : 0; + + // Generalized Greg Maxwell's trick + var adjustCount = this.n && this.p.div(this.n); + if (!adjustCount || adjustCount.cmpn(100) > 0) { + this.redN = null; + } else { + this._maxwellTrick = true; + this.redN = this.n.toRed(this.red); + } +} +module.exports = BaseCurve; + +BaseCurve.prototype.point = function point() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype.validate = function validate() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype._fixedNafMul = function _fixedNafMul(p, k) { + assert(p.precomputed); + var doubles = p._getDoubles(); + + var naf = getNAF(k, 1, this._bitLength); + var I = (1 << (doubles.step + 1)) - (doubles.step % 2 === 0 ? 2 : 1); + I /= 3; + + // Translate into more windowed form + var repr = []; + var j; + var nafW; + for (j = 0; j < naf.length; j += doubles.step) { + nafW = 0; + for (var l = j + doubles.step - 1; l >= j; l--) + nafW = (nafW << 1) + naf[l]; + repr.push(nafW); + } + + var a = this.jpoint(null, null, null); + var b = this.jpoint(null, null, null); + for (var i = I; i > 0; i--) { + for (j = 0; j < repr.length; j++) { + nafW = repr[j]; + if (nafW === i) + b = b.mixedAdd(doubles.points[j]); + else if (nafW === -i) + b = b.mixedAdd(doubles.points[j].neg()); + } + a = a.add(b); + } + return a.toP(); +}; + +BaseCurve.prototype._wnafMul = function _wnafMul(p, k) { + var w = 4; + + // Precompute window + var nafPoints = p._getNAFPoints(w); + w = nafPoints.wnd; + var wnd = nafPoints.points; + + // Get NAF form + var naf = getNAF(k, w, this._bitLength); + + // Add `this`*(N+1) for every w-NAF index + var acc = this.jpoint(null, null, null); + for (var i = naf.length - 1; i >= 0; i--) { + // Count zeroes + for (var l = 0; i >= 0 && naf[i] === 0; i--) + l++; + if (i >= 0) + l++; + acc = acc.dblp(l); + + if (i < 0) + break; + var z = naf[i]; + assert(z !== 0); + if (p.type === 'affine') { + // J +- P + if (z > 0) + acc = acc.mixedAdd(wnd[(z - 1) >> 1]); + else + acc = acc.mixedAdd(wnd[(-z - 1) >> 1].neg()); + } else { + // J +- J + if (z > 0) + acc = acc.add(wnd[(z - 1) >> 1]); + else + acc = acc.add(wnd[(-z - 1) >> 1].neg()); + } + } + return p.type === 'affine' ? acc.toP() : acc; +}; + +BaseCurve.prototype._wnafMulAdd = function _wnafMulAdd(defW, + points, + coeffs, + len, + jacobianResult) { + var wndWidth = this._wnafT1; + var wnd = this._wnafT2; + var naf = this._wnafT3; + + // Fill all arrays + var max = 0; + var i; + var j; + var p; + for (i = 0; i < len; i++) { + p = points[i]; + var nafPoints = p._getNAFPoints(defW); + wndWidth[i] = nafPoints.wnd; + wnd[i] = nafPoints.points; + } + + // Comb small window NAFs + for (i = len - 1; i >= 1; i -= 2) { + var a = i - 1; + var b = i; + if (wndWidth[a] !== 1 || wndWidth[b] !== 1) { + naf[a] = getNAF(coeffs[a], wndWidth[a], this._bitLength); + naf[b] = getNAF(coeffs[b], wndWidth[b], this._bitLength); + max = Math.max(naf[a].length, max); + max = Math.max(naf[b].length, max); + continue; + } + + var comb = [ + points[a], /* 1 */ + null, /* 3 */ + null, /* 5 */ + points[b], /* 7 */ + ]; + + // Try to avoid Projective points, if possible + if (points[a].y.cmp(points[b].y) === 0) { + comb[1] = points[a].add(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } else if (points[a].y.cmp(points[b].y.redNeg()) === 0) { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].add(points[b].neg()); + } else { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } + + var index = [ + -3, /* -1 -1 */ + -1, /* -1 0 */ + -5, /* -1 1 */ + -7, /* 0 -1 */ + 0, /* 0 0 */ + 7, /* 0 1 */ + 5, /* 1 -1 */ + 1, /* 1 0 */ + 3, /* 1 1 */ + ]; + + var jsf = getJSF(coeffs[a], coeffs[b]); + max = Math.max(jsf[0].length, max); + naf[a] = new Array(max); + naf[b] = new Array(max); + for (j = 0; j < max; j++) { + var ja = jsf[0][j] | 0; + var jb = jsf[1][j] | 0; + + naf[a][j] = index[(ja + 1) * 3 + (jb + 1)]; + naf[b][j] = 0; + wnd[a] = comb; + } + } + + var acc = this.jpoint(null, null, null); + var tmp = this._wnafT4; + for (i = max; i >= 0; i--) { + var k = 0; + + while (i >= 0) { + var zero = true; + for (j = 0; j < len; j++) { + tmp[j] = naf[j][i] | 0; + if (tmp[j] !== 0) + zero = false; + } + if (!zero) + break; + k++; + i--; + } + if (i >= 0) + k++; + acc = acc.dblp(k); + if (i < 0) + break; + + for (j = 0; j < len; j++) { + var z = tmp[j]; + p; + if (z === 0) + continue; + else if (z > 0) + p = wnd[j][(z - 1) >> 1]; + else if (z < 0) + p = wnd[j][(-z - 1) >> 1].neg(); + + if (p.type === 'affine') + acc = acc.mixedAdd(p); + else + acc = acc.add(p); + } + } + // Zeroify references + for (i = 0; i < len; i++) + wnd[i] = null; + + if (jacobianResult) + return acc; + else + return acc.toP(); +}; + +function BasePoint(curve, type) { + this.curve = curve; + this.type = type; + this.precomputed = null; +} +BaseCurve.BasePoint = BasePoint; + +BasePoint.prototype.eq = function eq(/*other*/) { + throw new Error('Not implemented'); +}; + +BasePoint.prototype.validate = function validate() { + return this.curve.validate(this); +}; + +BaseCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + bytes = utils.toArray(bytes, enc); + + var len = this.p.byteLength(); + + // uncompressed, hybrid-odd, hybrid-even + if ((bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) && + bytes.length - 1 === 2 * len) { + if (bytes[0] === 0x06) + assert(bytes[bytes.length - 1] % 2 === 0); + else if (bytes[0] === 0x07) + assert(bytes[bytes.length - 1] % 2 === 1); + + var res = this.point(bytes.slice(1, 1 + len), + bytes.slice(1 + len, 1 + 2 * len)); + + return res; + } else if ((bytes[0] === 0x02 || bytes[0] === 0x03) && + bytes.length - 1 === len) { + return this.pointFromX(bytes.slice(1, 1 + len), bytes[0] === 0x03); + } + throw new Error('Unknown point format'); +}; + +BasePoint.prototype.encodeCompressed = function encodeCompressed(enc) { + return this.encode(enc, true); +}; + +BasePoint.prototype._encode = function _encode(compact) { + var len = this.curve.p.byteLength(); + var x = this.getX().toArray('be', len); + + if (compact) + return [ this.getY().isEven() ? 0x02 : 0x03 ].concat(x); + + return [ 0x04 ].concat(x, this.getY().toArray('be', len)); +}; + +BasePoint.prototype.encode = function encode(enc, compact) { + return utils.encode(this._encode(compact), enc); +}; + +BasePoint.prototype.precompute = function precompute(power) { + if (this.precomputed) + return this; + + var precomputed = { + doubles: null, + naf: null, + beta: null, + }; + precomputed.naf = this._getNAFPoints(8); + precomputed.doubles = this._getDoubles(4, power); + precomputed.beta = this._getBeta(); + this.precomputed = precomputed; + + return this; +}; + +BasePoint.prototype._hasDoubles = function _hasDoubles(k) { + if (!this.precomputed) + return false; + + var doubles = this.precomputed.doubles; + if (!doubles) + return false; + + return doubles.points.length >= Math.ceil((k.bitLength() + 1) / doubles.step); +}; + +BasePoint.prototype._getDoubles = function _getDoubles(step, power) { + if (this.precomputed && this.precomputed.doubles) + return this.precomputed.doubles; + + var doubles = [ this ]; + var acc = this; + for (var i = 0; i < power; i += step) { + for (var j = 0; j < step; j++) + acc = acc.dbl(); + doubles.push(acc); + } + return { + step: step, + points: doubles, + }; +}; + +BasePoint.prototype._getNAFPoints = function _getNAFPoints(wnd) { + if (this.precomputed && this.precomputed.naf) + return this.precomputed.naf; + + var res = [ this ]; + var max = (1 << wnd) - 1; + var dbl = max === 1 ? null : this.dbl(); + for (var i = 1; i < max; i++) + res[i] = res[i - 1].add(dbl); + return { + wnd: wnd, + points: res, + }; +}; + +BasePoint.prototype._getBeta = function _getBeta() { + return null; +}; + +BasePoint.prototype.dblp = function dblp(k) { + var r = this; + for (var i = 0; i < k; i++) + r = r.dbl(); + return r; +}; + +},{"../utils":103,"bn.js":18}],91:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var BN = require('bn.js'); +var inherits = require('inherits'); +var Base = require('./base'); + +var assert = utils.assert; + +function EdwardsCurve(conf) { + // NOTE: Important as we are creating point in Base.call() + this.twisted = (conf.a | 0) !== 1; + this.mOneA = this.twisted && (conf.a | 0) === -1; + this.extended = this.mOneA; + + Base.call(this, 'edwards', conf); + + this.a = new BN(conf.a, 16).umod(this.red.m); + this.a = this.a.toRed(this.red); + this.c = new BN(conf.c, 16).toRed(this.red); + this.c2 = this.c.redSqr(); + this.d = new BN(conf.d, 16).toRed(this.red); + this.dd = this.d.redAdd(this.d); + + assert(!this.twisted || this.c.fromRed().cmpn(1) === 0); + this.oneC = (conf.c | 0) === 1; +} +inherits(EdwardsCurve, Base); +module.exports = EdwardsCurve; + +EdwardsCurve.prototype._mulA = function _mulA(num) { + if (this.mOneA) + return num.redNeg(); + else + return this.a.redMul(num); +}; + +EdwardsCurve.prototype._mulC = function _mulC(num) { + if (this.oneC) + return num; + else + return this.c.redMul(num); +}; + +// Just for compatibility with Short curve +EdwardsCurve.prototype.jpoint = function jpoint(x, y, z, t) { + return this.point(x, y, z, t); +}; + +EdwardsCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var x2 = x.redSqr(); + var rhs = this.c2.redSub(this.a.redMul(x2)); + var lhs = this.one.redSub(this.c2.redMul(this.d).redMul(x2)); + + var y2 = rhs.redMul(lhs.redInvm()); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.pointFromY = function pointFromY(y, odd) { + y = new BN(y, 16); + if (!y.red) + y = y.toRed(this.red); + + // x^2 = (y^2 - c^2) / (c^2 d y^2 - a) + var y2 = y.redSqr(); + var lhs = y2.redSub(this.c2); + var rhs = y2.redMul(this.d).redMul(this.c2).redSub(this.a); + var x2 = lhs.redMul(rhs.redInvm()); + + if (x2.cmp(this.zero) === 0) { + if (odd) + throw new Error('invalid point'); + else + return this.point(this.zero, y); + } + + var x = x2.redSqrt(); + if (x.redSqr().redSub(x2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + if (x.fromRed().isOdd() !== odd) + x = x.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.validate = function validate(point) { + if (point.isInfinity()) + return true; + + // Curve: A * X^2 + Y^2 = C^2 * (1 + D * X^2 * Y^2) + point.normalize(); + + var x2 = point.x.redSqr(); + var y2 = point.y.redSqr(); + var lhs = x2.redMul(this.a).redAdd(y2); + var rhs = this.c2.redMul(this.one.redAdd(this.d.redMul(x2).redMul(y2))); + + return lhs.cmp(rhs) === 0; +}; + +function Point(curve, x, y, z, t) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && y === null && z === null) { + this.x = this.curve.zero; + this.y = this.curve.one; + this.z = this.curve.one; + this.t = this.curve.zero; + this.zOne = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = z ? new BN(z, 16) : this.curve.one; + this.t = t && new BN(t, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + if (this.t && !this.t.red) + this.t = this.t.toRed(this.curve.red); + this.zOne = this.z === this.curve.one; + + // Use extended coordinates + if (this.curve.extended && !this.t) { + this.t = this.x.redMul(this.y); + if (!this.zOne) + this.t = this.t.redMul(this.z.redInvm()); + } + } +} +inherits(Point, Base.BasePoint); + +EdwardsCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +EdwardsCurve.prototype.point = function point(x, y, z, t) { + return new Point(this, x, y, z, t); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1], obj[2]); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.x.cmpn(0) === 0 && + (this.y.cmp(this.z) === 0 || + (this.zOne && this.y.cmp(this.curve.c) === 0)); +}; + +Point.prototype._extDbl = function _extDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #doubling-dbl-2008-hwcd + // 4M + 4S + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = 2 * Z1^2 + var c = this.z.redSqr(); + c = c.redIAdd(c); + // D = a * A + var d = this.curve._mulA(a); + // E = (X1 + Y1)^2 - A - B + var e = this.x.redAdd(this.y).redSqr().redISub(a).redISub(b); + // G = D + B + var g = d.redAdd(b); + // F = G - C + var f = g.redSub(c); + // H = D - B + var h = d.redSub(b); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projDbl = function _projDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #doubling-dbl-2008-bbjlp + // #doubling-dbl-2007-bl + // and others + // Generally 3M + 4S or 2M + 4S + + // B = (X1 + Y1)^2 + var b = this.x.redAdd(this.y).redSqr(); + // C = X1^2 + var c = this.x.redSqr(); + // D = Y1^2 + var d = this.y.redSqr(); + + var nx; + var ny; + var nz; + var e; + var h; + var j; + if (this.curve.twisted) { + // E = a * C + e = this.curve._mulA(c); + // F = E + D + var f = e.redAdd(d); + if (this.zOne) { + // X3 = (B - C - D) * (F - 2) + nx = b.redSub(c).redSub(d).redMul(f.redSub(this.curve.two)); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F^2 - 2 * F + nz = f.redSqr().redSub(f).redSub(f); + } else { + // H = Z1^2 + h = this.z.redSqr(); + // J = F - 2 * H + j = f.redSub(h).redISub(h); + // X3 = (B-C-D)*J + nx = b.redSub(c).redISub(d).redMul(j); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F * J + nz = f.redMul(j); + } + } else { + // E = C + D + e = c.redAdd(d); + // H = (c * Z1)^2 + h = this.curve._mulC(this.z).redSqr(); + // J = E - 2 * H + j = e.redSub(h).redSub(h); + // X3 = c * (B - E) * J + nx = this.curve._mulC(b.redISub(e)).redMul(j); + // Y3 = c * E * (C - D) + ny = this.curve._mulC(e).redMul(c.redISub(d)); + // Z3 = E * J + nz = e.redMul(j); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + // Double in extended coordinates + if (this.curve.extended) + return this._extDbl(); + else + return this._projDbl(); +}; + +Point.prototype._extAdd = function _extAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #addition-add-2008-hwcd-3 + // 8M + + // A = (Y1 - X1) * (Y2 - X2) + var a = this.y.redSub(this.x).redMul(p.y.redSub(p.x)); + // B = (Y1 + X1) * (Y2 + X2) + var b = this.y.redAdd(this.x).redMul(p.y.redAdd(p.x)); + // C = T1 * k * T2 + var c = this.t.redMul(this.curve.dd).redMul(p.t); + // D = Z1 * 2 * Z2 + var d = this.z.redMul(p.z.redAdd(p.z)); + // E = B - A + var e = b.redSub(a); + // F = D - C + var f = d.redSub(c); + // G = D + C + var g = d.redAdd(c); + // H = B + A + var h = b.redAdd(a); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projAdd = function _projAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #addition-add-2008-bbjlp + // #addition-add-2007-bl + // 10M + 1S + + // A = Z1 * Z2 + var a = this.z.redMul(p.z); + // B = A^2 + var b = a.redSqr(); + // C = X1 * X2 + var c = this.x.redMul(p.x); + // D = Y1 * Y2 + var d = this.y.redMul(p.y); + // E = d * C * D + var e = this.curve.d.redMul(c).redMul(d); + // F = B - E + var f = b.redSub(e); + // G = B + E + var g = b.redAdd(e); + // X3 = A * F * ((X1 + Y1) * (X2 + Y2) - C - D) + var tmp = this.x.redAdd(this.y).redMul(p.x.redAdd(p.y)).redISub(c).redISub(d); + var nx = a.redMul(f).redMul(tmp); + var ny; + var nz; + if (this.curve.twisted) { + // Y3 = A * G * (D - a * C) + ny = a.redMul(g).redMul(d.redSub(this.curve._mulA(c))); + // Z3 = F * G + nz = f.redMul(g); + } else { + // Y3 = A * G * (D - C) + ny = a.redMul(g).redMul(d.redSub(c)); + // Z3 = c * F * G + nz = this.curve._mulC(f).redMul(g); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.add = function add(p) { + if (this.isInfinity()) + return p; + if (p.isInfinity()) + return this; + + if (this.curve.extended) + return this._extAdd(p); + else + return this._projAdd(p); +}; + +Point.prototype.mul = function mul(k) { + if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, false); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, true); +}; + +Point.prototype.normalize = function normalize() { + if (this.zOne) + return this; + + // Normalize coordinates + var zi = this.z.redInvm(); + this.x = this.x.redMul(zi); + this.y = this.y.redMul(zi); + if (this.t) + this.t = this.t.redMul(zi); + this.z = this.curve.one; + this.zOne = true; + return this; +}; + +Point.prototype.neg = function neg() { + return this.curve.point(this.x.redNeg(), + this.y, + this.z, + this.t && this.t.redNeg()); +}; + +Point.prototype.getX = function getX() { + this.normalize(); + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + this.normalize(); + return this.y.fromRed(); +}; + +Point.prototype.eq = function eq(other) { + return this === other || + this.getX().cmp(other.getX()) === 0 && + this.getY().cmp(other.getY()) === 0; +}; + +Point.prototype.eqXToP = function eqXToP(x) { + var rx = x.toRed(this.curve.red).redMul(this.z); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(this.z); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } +}; + +// Compatibility with BaseCurve +Point.prototype.toP = Point.prototype.normalize; +Point.prototype.mixedAdd = Point.prototype.add; + +},{"../utils":103,"./base":90,"bn.js":18,"inherits":146}],92:[function(require,module,exports){ +'use strict'; + +var curve = exports; + +curve.base = require('./base'); +curve.short = require('./short'); +curve.mont = require('./mont'); +curve.edwards = require('./edwards'); + +},{"./base":90,"./edwards":91,"./mont":93,"./short":94}],93:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); +var inherits = require('inherits'); +var Base = require('./base'); + +var utils = require('../utils'); + +function MontCurve(conf) { + Base.call(this, 'mont', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.i4 = new BN(4).toRed(this.red).redInvm(); + this.two = new BN(2).toRed(this.red); + this.a24 = this.i4.redMul(this.a.redAdd(this.two)); +} +inherits(MontCurve, Base); +module.exports = MontCurve; + +MontCurve.prototype.validate = function validate(point) { + var x = point.normalize().x; + var x2 = x.redSqr(); + var rhs = x2.redMul(x).redAdd(x2.redMul(this.a)).redAdd(x); + var y = rhs.redSqrt(); + + return y.redSqr().cmp(rhs) === 0; +}; + +function Point(curve, x, z) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && z === null) { + this.x = this.curve.one; + this.z = this.curve.zero; + } else { + this.x = new BN(x, 16); + this.z = new BN(z, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + } +} +inherits(Point, Base.BasePoint); + +MontCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + return this.point(utils.toArray(bytes, enc), 1); +}; + +MontCurve.prototype.point = function point(x, z) { + return new Point(this, x, z); +}; + +MontCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +Point.prototype.precompute = function precompute() { + // No-op +}; + +Point.prototype._encode = function _encode() { + return this.getX().toArray('be', this.curve.p.byteLength()); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1] || curve.one); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; + +Point.prototype.dbl = function dbl() { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#doubling-dbl-1987-m-3 + // 2M + 2S + 4A + + // A = X1 + Z1 + var a = this.x.redAdd(this.z); + // AA = A^2 + var aa = a.redSqr(); + // B = X1 - Z1 + var b = this.x.redSub(this.z); + // BB = B^2 + var bb = b.redSqr(); + // C = AA - BB + var c = aa.redSub(bb); + // X3 = AA * BB + var nx = aa.redMul(bb); + // Z3 = C * (BB + A24 * C) + var nz = c.redMul(bb.redAdd(this.curve.a24.redMul(c))); + return this.curve.point(nx, nz); +}; + +Point.prototype.add = function add() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.diffAdd = function diffAdd(p, diff) { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#diffadd-dadd-1987-m-3 + // 4M + 2S + 6A + + // A = X2 + Z2 + var a = this.x.redAdd(this.z); + // B = X2 - Z2 + var b = this.x.redSub(this.z); + // C = X3 + Z3 + var c = p.x.redAdd(p.z); + // D = X3 - Z3 + var d = p.x.redSub(p.z); + // DA = D * A + var da = d.redMul(a); + // CB = C * B + var cb = c.redMul(b); + // X5 = Z1 * (DA + CB)^2 + var nx = diff.z.redMul(da.redAdd(cb).redSqr()); + // Z5 = X1 * (DA - CB)^2 + var nz = diff.x.redMul(da.redISub(cb).redSqr()); + return this.curve.point(nx, nz); +}; + +Point.prototype.mul = function mul(k) { + var t = k.clone(); + var a = this; // (N / 2) * Q + Q + var b = this.curve.point(null, null); // (N / 2) * Q + var c = this; // Q + + for (var bits = []; t.cmpn(0) !== 0; t.iushrn(1)) + bits.push(t.andln(1)); + + for (var i = bits.length - 1; i >= 0; i--) { + if (bits[i] === 0) { + // N * Q + Q = ((N / 2) * Q + Q)) + (N / 2) * Q + a = a.diffAdd(b, c); + // N * Q = 2 * ((N / 2) * Q + Q)) + b = b.dbl(); + } else { + // N * Q = ((N / 2) * Q + Q) + ((N / 2) * Q) + b = a.diffAdd(b, c); + // N * Q + Q = 2 * ((N / 2) * Q + Q) + a = a.dbl(); + } + } + return b; +}; + +Point.prototype.mulAdd = function mulAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.jumlAdd = function jumlAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.eq = function eq(other) { + return this.getX().cmp(other.getX()) === 0; +}; + +Point.prototype.normalize = function normalize() { + this.x = this.x.redMul(this.z.redInvm()); + this.z = this.curve.one; + return this; +}; + +Point.prototype.getX = function getX() { + // Normalize coordinates + this.normalize(); + + return this.x.fromRed(); +}; + +},{"../utils":103,"./base":90,"bn.js":18,"inherits":146}],94:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var BN = require('bn.js'); +var inherits = require('inherits'); +var Base = require('./base'); + +var assert = utils.assert; + +function ShortCurve(conf) { + Base.call(this, 'short', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.tinv = this.two.redInvm(); + + this.zeroA = this.a.fromRed().cmpn(0) === 0; + this.threeA = this.a.fromRed().sub(this.p).cmpn(-3) === 0; + + // If the curve is endomorphic, precalculate beta and lambda + this.endo = this._getEndomorphism(conf); + this._endoWnafT1 = new Array(4); + this._endoWnafT2 = new Array(4); +} +inherits(ShortCurve, Base); +module.exports = ShortCurve; + +ShortCurve.prototype._getEndomorphism = function _getEndomorphism(conf) { + // No efficient endomorphism + if (!this.zeroA || !this.g || !this.n || this.p.modn(3) !== 1) + return; + + // Compute beta and lambda, that lambda * P = (beta * Px; Py) + var beta; + var lambda; + if (conf.beta) { + beta = new BN(conf.beta, 16).toRed(this.red); + } else { + var betas = this._getEndoRoots(this.p); + // Choose the smallest beta + beta = betas[0].cmp(betas[1]) < 0 ? betas[0] : betas[1]; + beta = beta.toRed(this.red); + } + if (conf.lambda) { + lambda = new BN(conf.lambda, 16); + } else { + // Choose the lambda that is matching selected beta + var lambdas = this._getEndoRoots(this.n); + if (this.g.mul(lambdas[0]).x.cmp(this.g.x.redMul(beta)) === 0) { + lambda = lambdas[0]; + } else { + lambda = lambdas[1]; + assert(this.g.mul(lambda).x.cmp(this.g.x.redMul(beta)) === 0); + } + } + + // Get basis vectors, used for balanced length-two representation + var basis; + if (conf.basis) { + basis = conf.basis.map(function(vec) { + return { + a: new BN(vec.a, 16), + b: new BN(vec.b, 16), + }; + }); + } else { + basis = this._getEndoBasis(lambda); + } + + return { + beta: beta, + lambda: lambda, + basis: basis, + }; +}; + +ShortCurve.prototype._getEndoRoots = function _getEndoRoots(num) { + // Find roots of for x^2 + x + 1 in F + // Root = (-1 +- Sqrt(-3)) / 2 + // + var red = num === this.p ? this.red : BN.mont(num); + var tinv = new BN(2).toRed(red).redInvm(); + var ntinv = tinv.redNeg(); + + var s = new BN(3).toRed(red).redNeg().redSqrt().redMul(tinv); + + var l1 = ntinv.redAdd(s).fromRed(); + var l2 = ntinv.redSub(s).fromRed(); + return [ l1, l2 ]; +}; + +ShortCurve.prototype._getEndoBasis = function _getEndoBasis(lambda) { + // aprxSqrt >= sqrt(this.n) + var aprxSqrt = this.n.ushrn(Math.floor(this.n.bitLength() / 2)); + + // 3.74 + // Run EGCD, until r(L + 1) < aprxSqrt + var u = lambda; + var v = this.n.clone(); + var x1 = new BN(1); + var y1 = new BN(0); + var x2 = new BN(0); + var y2 = new BN(1); + + // NOTE: all vectors are roots of: a + b * lambda = 0 (mod n) + var a0; + var b0; + // First vector + var a1; + var b1; + // Second vector + var a2; + var b2; + + var prevR; + var i = 0; + var r; + var x; + while (u.cmpn(0) !== 0) { + var q = v.div(u); + r = v.sub(q.mul(u)); + x = x2.sub(q.mul(x1)); + var y = y2.sub(q.mul(y1)); + + if (!a1 && r.cmp(aprxSqrt) < 0) { + a0 = prevR.neg(); + b0 = x1; + a1 = r.neg(); + b1 = x; + } else if (a1 && ++i === 2) { + break; + } + prevR = r; + + v = u; + u = r; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + a2 = r.neg(); + b2 = x; + + var len1 = a1.sqr().add(b1.sqr()); + var len2 = a2.sqr().add(b2.sqr()); + if (len2.cmp(len1) >= 0) { + a2 = a0; + b2 = b0; + } + + // Normalize signs + if (a1.negative) { + a1 = a1.neg(); + b1 = b1.neg(); + } + if (a2.negative) { + a2 = a2.neg(); + b2 = b2.neg(); + } + + return [ + { a: a1, b: b1 }, + { a: a2, b: b2 }, + ]; +}; + +ShortCurve.prototype._endoSplit = function _endoSplit(k) { + var basis = this.endo.basis; + var v1 = basis[0]; + var v2 = basis[1]; + + var c1 = v2.b.mul(k).divRound(this.n); + var c2 = v1.b.neg().mul(k).divRound(this.n); + + var p1 = c1.mul(v1.a); + var p2 = c2.mul(v2.a); + var q1 = c1.mul(v1.b); + var q2 = c2.mul(v2.b); + + // Calculate answer + var k1 = k.sub(p1).sub(p2); + var k2 = q1.add(q2).neg(); + return { k1: k1, k2: k2 }; +}; + +ShortCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var y2 = x.redSqr().redMul(x).redIAdd(x.redMul(this.a)).redIAdd(this.b); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + // XXX Is there any way to tell if the number is odd without converting it + // to non-red form? + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +ShortCurve.prototype.validate = function validate(point) { + if (point.inf) + return true; + + var x = point.x; + var y = point.y; + + var ax = this.a.redMul(x); + var rhs = x.redSqr().redMul(x).redIAdd(ax).redIAdd(this.b); + return y.redSqr().redISub(rhs).cmpn(0) === 0; +}; + +ShortCurve.prototype._endoWnafMulAdd = + function _endoWnafMulAdd(points, coeffs, jacobianResult) { + var npoints = this._endoWnafT1; + var ncoeffs = this._endoWnafT2; + for (var i = 0; i < points.length; i++) { + var split = this._endoSplit(coeffs[i]); + var p = points[i]; + var beta = p._getBeta(); + + if (split.k1.negative) { + split.k1.ineg(); + p = p.neg(true); + } + if (split.k2.negative) { + split.k2.ineg(); + beta = beta.neg(true); + } + + npoints[i * 2] = p; + npoints[i * 2 + 1] = beta; + ncoeffs[i * 2] = split.k1; + ncoeffs[i * 2 + 1] = split.k2; + } + var res = this._wnafMulAdd(1, npoints, ncoeffs, i * 2, jacobianResult); + + // Clean-up references to points and coefficients + for (var j = 0; j < i * 2; j++) { + npoints[j] = null; + ncoeffs[j] = null; + } + return res; + }; + +function Point(curve, x, y, isRed) { + Base.BasePoint.call(this, curve, 'affine'); + if (x === null && y === null) { + this.x = null; + this.y = null; + this.inf = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + // Force redgomery representation when loading from JSON + if (isRed) { + this.x.forceRed(this.curve.red); + this.y.forceRed(this.curve.red); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + this.inf = false; + } +} +inherits(Point, Base.BasePoint); + +ShortCurve.prototype.point = function point(x, y, isRed) { + return new Point(this, x, y, isRed); +}; + +ShortCurve.prototype.pointFromJSON = function pointFromJSON(obj, red) { + return Point.fromJSON(this, obj, red); +}; + +Point.prototype._getBeta = function _getBeta() { + if (!this.curve.endo) + return; + + var pre = this.precomputed; + if (pre && pre.beta) + return pre.beta; + + var beta = this.curve.point(this.x.redMul(this.curve.endo.beta), this.y); + if (pre) { + var curve = this.curve; + var endoMul = function(p) { + return curve.point(p.x.redMul(curve.endo.beta), p.y); + }; + pre.beta = beta; + beta.precomputed = { + beta: null, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(endoMul), + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(endoMul), + }, + }; + } + return beta; +}; + +Point.prototype.toJSON = function toJSON() { + if (!this.precomputed) + return [ this.x, this.y ]; + + return [ this.x, this.y, this.precomputed && { + doubles: this.precomputed.doubles && { + step: this.precomputed.doubles.step, + points: this.precomputed.doubles.points.slice(1), + }, + naf: this.precomputed.naf && { + wnd: this.precomputed.naf.wnd, + points: this.precomputed.naf.points.slice(1), + }, + } ]; +}; + +Point.fromJSON = function fromJSON(curve, obj, red) { + if (typeof obj === 'string') + obj = JSON.parse(obj); + var res = curve.point(obj[0], obj[1], red); + if (!obj[2]) + return res; + + function obj2point(obj) { + return curve.point(obj[0], obj[1], red); + } + + var pre = obj[2]; + res.precomputed = { + beta: null, + doubles: pre.doubles && { + step: pre.doubles.step, + points: [ res ].concat(pre.doubles.points.map(obj2point)), + }, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: [ res ].concat(pre.naf.points.map(obj2point)), + }, + }; + return res; +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + return this.inf; +}; + +Point.prototype.add = function add(p) { + // O + P = P + if (this.inf) + return p; + + // P + O = P + if (p.inf) + return this; + + // P + P = 2P + if (this.eq(p)) + return this.dbl(); + + // P + (-P) = O + if (this.neg().eq(p)) + return this.curve.point(null, null); + + // P + Q = O + if (this.x.cmp(p.x) === 0) + return this.curve.point(null, null); + + var c = this.y.redSub(p.y); + if (c.cmpn(0) !== 0) + c = c.redMul(this.x.redSub(p.x).redInvm()); + var nx = c.redSqr().redISub(this.x).redISub(p.x); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.dbl = function dbl() { + if (this.inf) + return this; + + // 2P = O + var ys1 = this.y.redAdd(this.y); + if (ys1.cmpn(0) === 0) + return this.curve.point(null, null); + + var a = this.curve.a; + + var x2 = this.x.redSqr(); + var dyinv = ys1.redInvm(); + var c = x2.redAdd(x2).redIAdd(x2).redIAdd(a).redMul(dyinv); + + var nx = c.redSqr().redISub(this.x.redAdd(this.x)); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.getX = function getX() { + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + return this.y.fromRed(); +}; + +Point.prototype.mul = function mul(k) { + k = new BN(k, 16); + if (this.isInfinity()) + return this; + else if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else if (this.curve.endo) + return this.curve._endoWnafMulAdd([ this ], [ k ]); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs, true); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2, true); +}; + +Point.prototype.eq = function eq(p) { + return this === p || + this.inf === p.inf && + (this.inf || this.x.cmp(p.x) === 0 && this.y.cmp(p.y) === 0); +}; + +Point.prototype.neg = function neg(_precompute) { + if (this.inf) + return this; + + var res = this.curve.point(this.x, this.y.redNeg()); + if (_precompute && this.precomputed) { + var pre = this.precomputed; + var negate = function(p) { + return p.neg(); + }; + res.precomputed = { + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(negate), + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(negate), + }, + }; + } + return res; +}; + +Point.prototype.toJ = function toJ() { + if (this.inf) + return this.curve.jpoint(null, null, null); + + var res = this.curve.jpoint(this.x, this.y, this.curve.one); + return res; +}; + +function JPoint(curve, x, y, z) { + Base.BasePoint.call(this, curve, 'jacobian'); + if (x === null && y === null && z === null) { + this.x = this.curve.one; + this.y = this.curve.one; + this.z = new BN(0); + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = new BN(z, 16); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + + this.zOne = this.z === this.curve.one; +} +inherits(JPoint, Base.BasePoint); + +ShortCurve.prototype.jpoint = function jpoint(x, y, z) { + return new JPoint(this, x, y, z); +}; + +JPoint.prototype.toP = function toP() { + if (this.isInfinity()) + return this.curve.point(null, null); + + var zinv = this.z.redInvm(); + var zinv2 = zinv.redSqr(); + var ax = this.x.redMul(zinv2); + var ay = this.y.redMul(zinv2).redMul(zinv); + + return this.curve.point(ax, ay); +}; + +JPoint.prototype.neg = function neg() { + return this.curve.jpoint(this.x, this.y.redNeg(), this.z); +}; + +JPoint.prototype.add = function add(p) { + // O + P = P + if (this.isInfinity()) + return p; + + // P + O = P + if (p.isInfinity()) + return this; + + // 12M + 4S + 7A + var pz2 = p.z.redSqr(); + var z2 = this.z.redSqr(); + var u1 = this.x.redMul(pz2); + var u2 = p.x.redMul(z2); + var s1 = this.y.redMul(pz2.redMul(p.z)); + var s2 = p.y.redMul(z2.redMul(this.z)); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(p.z).redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mixedAdd = function mixedAdd(p) { + // O + P = P + if (this.isInfinity()) + return p.toJ(); + + // P + O = P + if (p.isInfinity()) + return this; + + // 8M + 3S + 7A + var z2 = this.z.redSqr(); + var u1 = this.x; + var u2 = p.x.redMul(z2); + var s1 = this.y; + var s2 = p.y.redMul(z2).redMul(this.z); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.dblp = function dblp(pow) { + if (pow === 0) + return this; + if (this.isInfinity()) + return this; + if (!pow) + return this.dbl(); + + var i; + if (this.curve.zeroA || this.curve.threeA) { + var r = this; + for (i = 0; i < pow; i++) + r = r.dbl(); + return r; + } + + // 1M + 2S + 1A + N * (4S + 5M + 8A) + // N = 1 => 6M + 6S + 9A + var a = this.curve.a; + var tinv = this.curve.tinv; + + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + // Reuse results + var jyd = jy.redAdd(jy); + for (i = 0; i < pow; i++) { + var jx2 = jx.redSqr(); + var jyd2 = jyd.redSqr(); + var jyd4 = jyd2.redSqr(); + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var t1 = jx.redMul(jyd2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + var dny = c.redMul(t2); + dny = dny.redIAdd(dny).redISub(jyd4); + var nz = jyd.redMul(jz); + if (i + 1 < pow) + jz4 = jz4.redMul(jyd4); + + jx = nx; + jz = nz; + jyd = dny; + } + + return this.curve.jpoint(jx, jyd.redMul(tinv), jz); +}; + +JPoint.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + if (this.curve.zeroA) + return this._zeroDbl(); + else if (this.curve.threeA) + return this._threeDbl(); + else + return this._dbl(); +}; + +JPoint.prototype._zeroDbl = function _zeroDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 14A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // T = M ^ 2 - 2*S + var t = m.redSqr().redISub(s).redISub(s); + + // 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2*Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-dbl-2009-l + // 2M + 5S + 13A + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = B^2 + var c = b.redSqr(); + // D = 2 * ((X1 + B)^2 - A - C) + var d = this.x.redAdd(b).redSqr().redISub(a).redISub(c); + d = d.redIAdd(d); + // E = 3 * A + var e = a.redAdd(a).redIAdd(a); + // F = E^2 + var f = e.redSqr(); + + // 8 * C + var c8 = c.redIAdd(c); + c8 = c8.redIAdd(c8); + c8 = c8.redIAdd(c8); + + // X3 = F - 2 * D + nx = f.redISub(d).redISub(d); + // Y3 = E * (D - X3) - 8 * C + ny = e.redMul(d.redISub(nx)).redISub(c8); + // Z3 = 2 * Y1 * Z1 + nz = this.y.redMul(this.z); + nz = nz.redIAdd(nz); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._threeDbl = function _threeDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 15A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a + var m = xx.redAdd(xx).redIAdd(xx).redIAdd(this.curve.a); + // T = M^2 - 2 * S + var t = m.redSqr().redISub(s).redISub(s); + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2 * Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b + // 3M + 5S + + // delta = Z1^2 + var delta = this.z.redSqr(); + // gamma = Y1^2 + var gamma = this.y.redSqr(); + // beta = X1 * gamma + var beta = this.x.redMul(gamma); + // alpha = 3 * (X1 - delta) * (X1 + delta) + var alpha = this.x.redSub(delta).redMul(this.x.redAdd(delta)); + alpha = alpha.redAdd(alpha).redIAdd(alpha); + // X3 = alpha^2 - 8 * beta + var beta4 = beta.redIAdd(beta); + beta4 = beta4.redIAdd(beta4); + var beta8 = beta4.redAdd(beta4); + nx = alpha.redSqr().redISub(beta8); + // Z3 = (Y1 + Z1)^2 - gamma - delta + nz = this.y.redAdd(this.z).redSqr().redISub(gamma).redISub(delta); + // Y3 = alpha * (4 * beta - X3) - 8 * gamma^2 + var ggamma8 = gamma.redSqr(); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ny = alpha.redMul(beta4.redISub(nx)).redISub(ggamma8); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._dbl = function _dbl() { + var a = this.curve.a; + + // 4M + 6S + 10A + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + var jx2 = jx.redSqr(); + var jy2 = jy.redSqr(); + + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var jxd4 = jx.redAdd(jx); + jxd4 = jxd4.redIAdd(jxd4); + var t1 = jxd4.redMul(jy2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + + var jyd8 = jy2.redSqr(); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + var ny = c.redMul(t2).redISub(jyd8); + var nz = jy.redAdd(jy).redMul(jz); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.trpl = function trpl() { + if (!this.curve.zeroA) + return this.dbl().add(this); + + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#tripling-tpl-2007-bl + // 5M + 10S + ... + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // ZZ = Z1^2 + var zz = this.z.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // M = 3 * XX + a * ZZ2; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // MM = M^2 + var mm = m.redSqr(); + // E = 6 * ((X1 + YY)^2 - XX - YYYY) - MM + var e = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + e = e.redIAdd(e); + e = e.redAdd(e).redIAdd(e); + e = e.redISub(mm); + // EE = E^2 + var ee = e.redSqr(); + // T = 16*YYYY + var t = yyyy.redIAdd(yyyy); + t = t.redIAdd(t); + t = t.redIAdd(t); + t = t.redIAdd(t); + // U = (M + E)^2 - MM - EE - T + var u = m.redIAdd(e).redSqr().redISub(mm).redISub(ee).redISub(t); + // X3 = 4 * (X1 * EE - 4 * YY * U) + var yyu4 = yy.redMul(u); + yyu4 = yyu4.redIAdd(yyu4); + yyu4 = yyu4.redIAdd(yyu4); + var nx = this.x.redMul(ee).redISub(yyu4); + nx = nx.redIAdd(nx); + nx = nx.redIAdd(nx); + // Y3 = 8 * Y1 * (U * (T - U) - E * EE) + var ny = this.y.redMul(u.redMul(t.redISub(u)).redISub(e.redMul(ee))); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + // Z3 = (Z1 + E)^2 - ZZ - EE + var nz = this.z.redAdd(e).redSqr().redISub(zz).redISub(ee); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mul = function mul(k, kbase) { + k = new BN(k, kbase); + + return this.curve._wnafMul(this, k); +}; + +JPoint.prototype.eq = function eq(p) { + if (p.type === 'affine') + return this.eq(p.toJ()); + + if (this === p) + return true; + + // x1 * z2^2 == x2 * z1^2 + var z2 = this.z.redSqr(); + var pz2 = p.z.redSqr(); + if (this.x.redMul(pz2).redISub(p.x.redMul(z2)).cmpn(0) !== 0) + return false; + + // y1 * z2^3 == y2 * z1^3 + var z3 = z2.redMul(this.z); + var pz3 = pz2.redMul(p.z); + return this.y.redMul(pz3).redISub(p.y.redMul(z3)).cmpn(0) === 0; +}; + +JPoint.prototype.eqXToP = function eqXToP(x) { + var zs = this.z.redSqr(); + var rx = x.toRed(this.curve.red).redMul(zs); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(zs); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } +}; + +JPoint.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +JPoint.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; + +},{"../utils":103,"./base":90,"bn.js":18,"inherits":146}],95:[function(require,module,exports){ +'use strict'; + +var curves = exports; + +var hash = require('hash.js'); +var curve = require('./curve'); +var utils = require('./utils'); + +var assert = utils.assert; + +function PresetCurve(options) { + if (options.type === 'short') + this.curve = new curve.short(options); + else if (options.type === 'edwards') + this.curve = new curve.edwards(options); + else + this.curve = new curve.mont(options); + this.g = this.curve.g; + this.n = this.curve.n; + this.hash = options.hash; + + assert(this.g.validate(), 'Invalid curve'); + assert(this.g.mul(this.n).isInfinity(), 'Invalid curve, G*N != O'); +} +curves.PresetCurve = PresetCurve; + +function defineCurve(name, options) { + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + get: function() { + var curve = new PresetCurve(options); + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + value: curve, + }); + return curve; + }, + }); +} + +defineCurve('p192', { + type: 'short', + prime: 'p192', + p: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc', + b: '64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1', + n: 'ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831', + hash: hash.sha256, + gRed: false, + g: [ + '188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012', + '07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811', + ], +}); + +defineCurve('p224', { + type: 'short', + prime: 'p224', + p: 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe', + b: 'b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4', + n: 'ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d', + hash: hash.sha256, + gRed: false, + g: [ + 'b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21', + 'bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34', + ], +}); + +defineCurve('p256', { + type: 'short', + prime: null, + p: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff', + a: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc', + b: '5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b', + n: 'ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551', + hash: hash.sha256, + gRed: false, + g: [ + '6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296', + '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5', + ], +}); + +defineCurve('p384', { + type: 'short', + prime: null, + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 ffffffff', + a: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 fffffffc', + b: 'b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f ' + + '5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef', + n: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 ' + + 'f4372ddf 581a0db2 48b0a77a ecec196a ccc52973', + hash: hash.sha384, + gRed: false, + g: [ + 'aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 ' + + '5502f25d bf55296c 3a545e38 72760ab7', + '3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 ' + + '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f', + ], +}); + +defineCurve('p521', { + type: 'short', + prime: null, + p: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff', + a: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff fffffffc', + b: '00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b ' + + '99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd ' + + '3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00', + n: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 ' + + 'f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409', + hash: hash.sha512, + gRed: false, + g: [ + '000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 ' + + '053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 ' + + 'a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66', + '00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 ' + + '579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 ' + + '3fad0761 353c7086 a272c240 88be9476 9fd16650', + ], +}); + +defineCurve('curve25519', { + type: 'mont', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '76d06', + b: '1', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: hash.sha256, + gRed: false, + g: [ + '9', + ], +}); + +defineCurve('ed25519', { + type: 'edwards', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '-1', + c: '1', + // -121665 * (121666^(-1)) (mod P) + d: '52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: hash.sha256, + gRed: false, + g: [ + '216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a', + + // 4/5 + '6666666666666666666666666666666666666666666666666666666666666658', + ], +}); + +var pre; +try { + pre = require('./precomputed/secp256k1'); +} catch (e) { + pre = undefined; +} + +defineCurve('secp256k1', { + type: 'short', + prime: 'k256', + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f', + a: '0', + b: '7', + n: 'ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141', + h: '1', + hash: hash.sha256, + + // Precomputed endomorphism + beta: '7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee', + lambda: '5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72', + basis: [ + { + a: '3086d221a7d46bcde86c90e49284eb15', + b: '-e4437ed6010e88286f547fa90abfe4c3', + }, + { + a: '114ca50f7a8e2f3f657c1108d9d44cfd8', + b: '3086d221a7d46bcde86c90e49284eb15', + }, + ], + + gRed: false, + g: [ + '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', + '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', + pre, + ], +}); + +},{"./curve":92,"./precomputed/secp256k1":102,"./utils":103,"hash.js":132}],96:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); +var HmacDRBG = require('hmac-drbg'); +var utils = require('../utils'); +var curves = require('../curves'); +var rand = require('brorand'); +var assert = utils.assert; + +var KeyPair = require('./key'); +var Signature = require('./signature'); + +function EC(options) { + if (!(this instanceof EC)) + return new EC(options); + + // Shortcut `elliptic.ec(curve-name)` + if (typeof options === 'string') { + assert(Object.prototype.hasOwnProperty.call(curves, options), + 'Unknown curve ' + options); + + options = curves[options]; + } + + // Shortcut for `elliptic.ec(elliptic.curves.curveName)` + if (options instanceof curves.PresetCurve) + options = { curve: options }; + + this.curve = options.curve.curve; + this.n = this.curve.n; + this.nh = this.n.ushrn(1); + this.g = this.curve.g; + + // Point on curve + this.g = options.curve.g; + this.g.precompute(options.curve.n.bitLength() + 1); + + // Hash for function for DRBG + this.hash = options.hash || options.curve.hash; +} +module.exports = EC; + +EC.prototype.keyPair = function keyPair(options) { + return new KeyPair(this, options); +}; + +EC.prototype.keyFromPrivate = function keyFromPrivate(priv, enc) { + return KeyPair.fromPrivate(this, priv, enc); +}; + +EC.prototype.keyFromPublic = function keyFromPublic(pub, enc) { + return KeyPair.fromPublic(this, pub, enc); +}; + +EC.prototype.genKeyPair = function genKeyPair(options) { + if (!options) + options = {}; + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + pers: options.pers, + persEnc: options.persEnc || 'utf8', + entropy: options.entropy || rand(this.hash.hmacStrength), + entropyEnc: options.entropy && options.entropyEnc || 'utf8', + nonce: this.n.toArray(), + }); + + var bytes = this.n.byteLength(); + var ns2 = this.n.sub(new BN(2)); + for (;;) { + var priv = new BN(drbg.generate(bytes)); + if (priv.cmp(ns2) > 0) + continue; + + priv.iaddn(1); + return this.keyFromPrivate(priv); + } +}; + +EC.prototype._truncateToN = function _truncateToN(msg, truncOnly) { + var delta = msg.byteLength() * 8 - this.n.bitLength(); + if (delta > 0) + msg = msg.ushrn(delta); + if (!truncOnly && msg.cmp(this.n) >= 0) + return msg.sub(this.n); + else + return msg; +}; + +EC.prototype.sign = function sign(msg, key, enc, options) { + if (typeof enc === 'object') { + options = enc; + enc = null; + } + if (!options) + options = {}; + + key = this.keyFromPrivate(key, enc); + msg = this._truncateToN(new BN(msg, 16)); + + // Zero-extend key to provide enough entropy + var bytes = this.n.byteLength(); + var bkey = key.getPrivate().toArray('be', bytes); + + // Zero-extend nonce to have the same byte size as N + var nonce = msg.toArray('be', bytes); + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + entropy: bkey, + nonce: nonce, + pers: options.pers, + persEnc: options.persEnc || 'utf8', + }); + + // Number of bytes to generate + var ns1 = this.n.sub(new BN(1)); + + for (var iter = 0; ; iter++) { + var k = options.k ? + options.k(iter) : + new BN(drbg.generate(this.n.byteLength())); + k = this._truncateToN(k, true); + if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0) + continue; + + var kp = this.g.mul(k); + if (kp.isInfinity()) + continue; + + var kpX = kp.getX(); + var r = kpX.umod(this.n); + if (r.cmpn(0) === 0) + continue; + + var s = k.invm(this.n).mul(r.mul(key.getPrivate()).iadd(msg)); + s = s.umod(this.n); + if (s.cmpn(0) === 0) + continue; + + var recoveryParam = (kp.getY().isOdd() ? 1 : 0) | + (kpX.cmp(r) !== 0 ? 2 : 0); + + // Use complement of `s`, if it is > `n / 2` + if (options.canonical && s.cmp(this.nh) > 0) { + s = this.n.sub(s); + recoveryParam ^= 1; + } + + return new Signature({ r: r, s: s, recoveryParam: recoveryParam }); + } +}; + +EC.prototype.verify = function verify(msg, signature, key, enc) { + msg = this._truncateToN(new BN(msg, 16)); + key = this.keyFromPublic(key, enc); + signature = new Signature(signature, 'hex'); + + // Perform primitive values validation + var r = signature.r; + var s = signature.s; + if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0) + return false; + if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0) + return false; + + // Validate signature + var sinv = s.invm(this.n); + var u1 = sinv.mul(msg).umod(this.n); + var u2 = sinv.mul(r).umod(this.n); + var p; + + if (!this.curve._maxwellTrick) { + p = this.g.mulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + return p.getX().umod(this.n).cmp(r) === 0; + } + + // NOTE: Greg Maxwell's trick, inspired by: + // https://git.io/vad3K + + p = this.g.jmulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + // Compare `p.x` of Jacobian point with `r`, + // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the + // inverse of `p.z^2` + return p.eqXToP(r); +}; + +EC.prototype.recoverPubKey = function(msg, signature, j, enc) { + assert((3 & j) === j, 'The recovery param is more than two bits'); + signature = new Signature(signature, enc); + + var n = this.n; + var e = new BN(msg); + var r = signature.r; + var s = signature.s; + + // A set LSB signifies that the y-coordinate is odd + var isYOdd = j & 1; + var isSecondKey = j >> 1; + if (r.cmp(this.curve.p.umod(this.curve.n)) >= 0 && isSecondKey) + throw new Error('Unable to find sencond key candinate'); + + // 1.1. Let x = r + jn. + if (isSecondKey) + r = this.curve.pointFromX(r.add(this.curve.n), isYOdd); + else + r = this.curve.pointFromX(r, isYOdd); + + var rInv = signature.r.invm(n); + var s1 = n.sub(e).mul(rInv).umod(n); + var s2 = s.mul(rInv).umod(n); + + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + return this.g.mulAdd(s1, r, s2); +}; + +EC.prototype.getKeyRecoveryParam = function(e, signature, Q, enc) { + signature = new Signature(signature, enc); + if (signature.recoveryParam !== null) + return signature.recoveryParam; + + for (var i = 0; i < 4; i++) { + var Qprime; + try { + Qprime = this.recoverPubKey(e, signature, i); + } catch (e) { + continue; + } + + if (Qprime.eq(Q)) + return i; + } + throw new Error('Unable to find valid recovery factor'); +}; + +},{"../curves":95,"../utils":103,"./key":97,"./signature":98,"bn.js":18,"brorand":19,"hmac-drbg":144}],97:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); +var utils = require('../utils'); +var assert = utils.assert; + +function KeyPair(ec, options) { + this.ec = ec; + this.priv = null; + this.pub = null; + + // KeyPair(ec, { priv: ..., pub: ... }) + if (options.priv) + this._importPrivate(options.priv, options.privEnc); + if (options.pub) + this._importPublic(options.pub, options.pubEnc); +} +module.exports = KeyPair; + +KeyPair.fromPublic = function fromPublic(ec, pub, enc) { + if (pub instanceof KeyPair) + return pub; + + return new KeyPair(ec, { + pub: pub, + pubEnc: enc, + }); +}; + +KeyPair.fromPrivate = function fromPrivate(ec, priv, enc) { + if (priv instanceof KeyPair) + return priv; + + return new KeyPair(ec, { + priv: priv, + privEnc: enc, + }); +}; + +KeyPair.prototype.validate = function validate() { + var pub = this.getPublic(); + + if (pub.isInfinity()) + return { result: false, reason: 'Invalid public key' }; + if (!pub.validate()) + return { result: false, reason: 'Public key is not a point' }; + if (!pub.mul(this.ec.curve.n).isInfinity()) + return { result: false, reason: 'Public key * N != O' }; + + return { result: true, reason: null }; +}; + +KeyPair.prototype.getPublic = function getPublic(compact, enc) { + // compact is optional argument + if (typeof compact === 'string') { + enc = compact; + compact = null; + } + + if (!this.pub) + this.pub = this.ec.g.mul(this.priv); + + if (!enc) + return this.pub; + + return this.pub.encode(enc, compact); +}; + +KeyPair.prototype.getPrivate = function getPrivate(enc) { + if (enc === 'hex') + return this.priv.toString(16, 2); + else + return this.priv; +}; + +KeyPair.prototype._importPrivate = function _importPrivate(key, enc) { + this.priv = new BN(key, enc || 16); + + // Ensure that the priv won't be bigger than n, otherwise we may fail + // in fixed multiplication method + this.priv = this.priv.umod(this.ec.curve.n); +}; + +KeyPair.prototype._importPublic = function _importPublic(key, enc) { + if (key.x || key.y) { + // Montgomery points only have an `x` coordinate. + // Weierstrass/Edwards points on the other hand have both `x` and + // `y` coordinates. + if (this.ec.curve.type === 'mont') { + assert(key.x, 'Need x coordinate'); + } else if (this.ec.curve.type === 'short' || + this.ec.curve.type === 'edwards') { + assert(key.x && key.y, 'Need both x and y coordinate'); + } + this.pub = this.ec.curve.point(key.x, key.y); + return; + } + this.pub = this.ec.curve.decodePoint(key, enc); +}; + +// ECDH +KeyPair.prototype.derive = function derive(pub) { + if(!pub.validate()) { + assert(pub.validate(), 'public point not validated'); + } + return pub.mul(this.priv).getX(); +}; + +// ECDSA +KeyPair.prototype.sign = function sign(msg, enc, options) { + return this.ec.sign(msg, this, enc, options); +}; + +KeyPair.prototype.verify = function verify(msg, signature) { + return this.ec.verify(msg, signature, this); +}; + +KeyPair.prototype.inspect = function inspect() { + return ''; +}; + +},{"../utils":103,"bn.js":18}],98:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); + +var utils = require('../utils'); +var assert = utils.assert; + +function Signature(options, enc) { + if (options instanceof Signature) + return options; + + if (this._importDER(options, enc)) + return; + + assert(options.r && options.s, 'Signature without r or s'); + this.r = new BN(options.r, 16); + this.s = new BN(options.s, 16); + if (options.recoveryParam === undefined) + this.recoveryParam = null; + else + this.recoveryParam = options.recoveryParam; +} +module.exports = Signature; + +function Position() { + this.place = 0; +} + +function getLength(buf, p) { + var initial = buf[p.place++]; + if (!(initial & 0x80)) { + return initial; + } + var octetLen = initial & 0xf; + + // Indefinite length or overflow + if (octetLen === 0 || octetLen > 4) { + return false; + } + + var val = 0; + for (var i = 0, off = p.place; i < octetLen; i++, off++) { + val <<= 8; + val |= buf[off]; + val >>>= 0; + } + + // Leading zeroes + if (val <= 0x7f) { + return false; + } + + p.place = off; + return val; +} + +function rmPadding(buf) { + var i = 0; + var len = buf.length - 1; + while (!buf[i] && !(buf[i + 1] & 0x80) && i < len) { + i++; + } + if (i === 0) { + return buf; + } + return buf.slice(i); +} + +Signature.prototype._importDER = function _importDER(data, enc) { + data = utils.toArray(data, enc); + var p = new Position(); + if (data[p.place++] !== 0x30) { + return false; + } + var len = getLength(data, p); + if (len === false) { + return false; + } + if ((len + p.place) !== data.length) { + return false; + } + if (data[p.place++] !== 0x02) { + return false; + } + var rlen = getLength(data, p); + if (rlen === false) { + return false; + } + var r = data.slice(p.place, rlen + p.place); + p.place += rlen; + if (data[p.place++] !== 0x02) { + return false; + } + var slen = getLength(data, p); + if (slen === false) { + return false; + } + if (data.length !== slen + p.place) { + return false; + } + var s = data.slice(p.place, slen + p.place); + if (r[0] === 0) { + if (r[1] & 0x80) { + r = r.slice(1); + } else { + // Leading zeroes + return false; + } + } + if (s[0] === 0) { + if (s[1] & 0x80) { + s = s.slice(1); + } else { + // Leading zeroes + return false; + } + } + + this.r = new BN(r); + this.s = new BN(s); + this.recoveryParam = null; + + return true; +}; + +function constructLength(arr, len) { + if (len < 0x80) { + arr.push(len); + return; + } + var octets = 1 + (Math.log(len) / Math.LN2 >>> 3); + arr.push(octets | 0x80); + while (--octets) { + arr.push((len >>> (octets << 3)) & 0xff); + } + arr.push(len); +} + +Signature.prototype.toDER = function toDER(enc) { + var r = this.r.toArray(); + var s = this.s.toArray(); + + // Pad values + if (r[0] & 0x80) + r = [ 0 ].concat(r); + // Pad values + if (s[0] & 0x80) + s = [ 0 ].concat(s); + + r = rmPadding(r); + s = rmPadding(s); + + while (!s[0] && !(s[1] & 0x80)) { + s = s.slice(1); + } + var arr = [ 0x02 ]; + constructLength(arr, r.length); + arr = arr.concat(r); + arr.push(0x02); + constructLength(arr, s.length); + var backHalf = arr.concat(s); + var res = [ 0x30 ]; + constructLength(res, backHalf.length); + res = res.concat(backHalf); + return utils.encode(res, enc); +}; + +},{"../utils":103,"bn.js":18}],99:[function(require,module,exports){ +'use strict'; + +var hash = require('hash.js'); +var curves = require('../curves'); +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var KeyPair = require('./key'); +var Signature = require('./signature'); + +function EDDSA(curve) { + assert(curve === 'ed25519', 'only tested with ed25519 so far'); + + if (!(this instanceof EDDSA)) + return new EDDSA(curve); + + curve = curves[curve].curve; + this.curve = curve; + this.g = curve.g; + this.g.precompute(curve.n.bitLength() + 1); + + this.pointClass = curve.point().constructor; + this.encodingLength = Math.ceil(curve.n.bitLength() / 8); + this.hash = hash.sha512; +} + +module.exports = EDDSA; + +/** +* @param {Array|String} message - message bytes +* @param {Array|String|KeyPair} secret - secret bytes or a keypair +* @returns {Signature} - signature +*/ +EDDSA.prototype.sign = function sign(message, secret) { + message = parseBytes(message); + var key = this.keyFromSecret(secret); + var r = this.hashInt(key.messagePrefix(), message); + var R = this.g.mul(r); + var Rencoded = this.encodePoint(R); + var s_ = this.hashInt(Rencoded, key.pubBytes(), message) + .mul(key.priv()); + var S = r.add(s_).umod(this.curve.n); + return this.makeSignature({ R: R, S: S, Rencoded: Rencoded }); +}; + +/** +* @param {Array} message - message bytes +* @param {Array|String|Signature} sig - sig bytes +* @param {Array|String|Point|KeyPair} pub - public key +* @returns {Boolean} - true if public key matches sig of message +*/ +EDDSA.prototype.verify = function verify(message, sig, pub) { + message = parseBytes(message); + sig = this.makeSignature(sig); + var key = this.keyFromPublic(pub); + var h = this.hashInt(sig.Rencoded(), key.pubBytes(), message); + var SG = this.g.mul(sig.S()); + var RplusAh = sig.R().add(key.pub().mul(h)); + return RplusAh.eq(SG); +}; + +EDDSA.prototype.hashInt = function hashInt() { + var hash = this.hash(); + for (var i = 0; i < arguments.length; i++) + hash.update(arguments[i]); + return utils.intFromLE(hash.digest()).umod(this.curve.n); +}; + +EDDSA.prototype.keyFromPublic = function keyFromPublic(pub) { + return KeyPair.fromPublic(this, pub); +}; + +EDDSA.prototype.keyFromSecret = function keyFromSecret(secret) { + return KeyPair.fromSecret(this, secret); +}; + +EDDSA.prototype.makeSignature = function makeSignature(sig) { + if (sig instanceof Signature) + return sig; + return new Signature(this, sig); +}; + +/** +* * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03#section-5.2 +* +* EDDSA defines methods for encoding and decoding points and integers. These are +* helper convenience methods, that pass along to utility functions implied +* parameters. +* +*/ +EDDSA.prototype.encodePoint = function encodePoint(point) { + var enc = point.getY().toArray('le', this.encodingLength); + enc[this.encodingLength - 1] |= point.getX().isOdd() ? 0x80 : 0; + return enc; +}; + +EDDSA.prototype.decodePoint = function decodePoint(bytes) { + bytes = utils.parseBytes(bytes); + + var lastIx = bytes.length - 1; + var normed = bytes.slice(0, lastIx).concat(bytes[lastIx] & ~0x80); + var xIsOdd = (bytes[lastIx] & 0x80) !== 0; + + var y = utils.intFromLE(normed); + return this.curve.pointFromY(y, xIsOdd); +}; + +EDDSA.prototype.encodeInt = function encodeInt(num) { + return num.toArray('le', this.encodingLength); +}; + +EDDSA.prototype.decodeInt = function decodeInt(bytes) { + return utils.intFromLE(bytes); +}; + +EDDSA.prototype.isPoint = function isPoint(val) { + return val instanceof this.pointClass; +}; + +},{"../curves":95,"../utils":103,"./key":100,"./signature":101,"hash.js":132}],100:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var cachedProperty = utils.cachedProperty; + +/** +* @param {EDDSA} eddsa - instance +* @param {Object} params - public/private key parameters +* +* @param {Array} [params.secret] - secret seed bytes +* @param {Point} [params.pub] - public key point (aka `A` in eddsa terms) +* @param {Array} [params.pub] - public key point encoded as bytes +* +*/ +function KeyPair(eddsa, params) { + this.eddsa = eddsa; + this._secret = parseBytes(params.secret); + if (eddsa.isPoint(params.pub)) + this._pub = params.pub; + else + this._pubBytes = parseBytes(params.pub); +} + +KeyPair.fromPublic = function fromPublic(eddsa, pub) { + if (pub instanceof KeyPair) + return pub; + return new KeyPair(eddsa, { pub: pub }); +}; + +KeyPair.fromSecret = function fromSecret(eddsa, secret) { + if (secret instanceof KeyPair) + return secret; + return new KeyPair(eddsa, { secret: secret }); +}; + +KeyPair.prototype.secret = function secret() { + return this._secret; +}; + +cachedProperty(KeyPair, 'pubBytes', function pubBytes() { + return this.eddsa.encodePoint(this.pub()); +}); + +cachedProperty(KeyPair, 'pub', function pub() { + if (this._pubBytes) + return this.eddsa.decodePoint(this._pubBytes); + return this.eddsa.g.mul(this.priv()); +}); + +cachedProperty(KeyPair, 'privBytes', function privBytes() { + var eddsa = this.eddsa; + var hash = this.hash(); + var lastIx = eddsa.encodingLength - 1; + + var a = hash.slice(0, eddsa.encodingLength); + a[0] &= 248; + a[lastIx] &= 127; + a[lastIx] |= 64; + + return a; +}); + +cachedProperty(KeyPair, 'priv', function priv() { + return this.eddsa.decodeInt(this.privBytes()); +}); + +cachedProperty(KeyPair, 'hash', function hash() { + return this.eddsa.hash().update(this.secret()).digest(); +}); + +cachedProperty(KeyPair, 'messagePrefix', function messagePrefix() { + return this.hash().slice(this.eddsa.encodingLength); +}); + +KeyPair.prototype.sign = function sign(message) { + assert(this._secret, 'KeyPair can only verify'); + return this.eddsa.sign(message, this); +}; + +KeyPair.prototype.verify = function verify(message, sig) { + return this.eddsa.verify(message, sig, this); +}; + +KeyPair.prototype.getSecret = function getSecret(enc) { + assert(this._secret, 'KeyPair is public only'); + return utils.encode(this.secret(), enc); +}; + +KeyPair.prototype.getPublic = function getPublic(enc) { + return utils.encode(this.pubBytes(), enc); +}; + +module.exports = KeyPair; + +},{"../utils":103}],101:[function(require,module,exports){ +'use strict'; + +var BN = require('bn.js'); +var utils = require('../utils'); +var assert = utils.assert; +var cachedProperty = utils.cachedProperty; +var parseBytes = utils.parseBytes; + +/** +* @param {EDDSA} eddsa - eddsa instance +* @param {Array|Object} sig - +* @param {Array|Point} [sig.R] - R point as Point or bytes +* @param {Array|bn} [sig.S] - S scalar as bn or bytes +* @param {Array} [sig.Rencoded] - R point encoded +* @param {Array} [sig.Sencoded] - S scalar encoded +*/ +function Signature(eddsa, sig) { + this.eddsa = eddsa; + + if (typeof sig !== 'object') + sig = parseBytes(sig); + + if (Array.isArray(sig)) { + sig = { + R: sig.slice(0, eddsa.encodingLength), + S: sig.slice(eddsa.encodingLength), + }; + } + + assert(sig.R && sig.S, 'Signature without R or S'); + + if (eddsa.isPoint(sig.R)) + this._R = sig.R; + if (sig.S instanceof BN) + this._S = sig.S; + + this._Rencoded = Array.isArray(sig.R) ? sig.R : sig.Rencoded; + this._Sencoded = Array.isArray(sig.S) ? sig.S : sig.Sencoded; +} + +cachedProperty(Signature, 'S', function S() { + return this.eddsa.decodeInt(this.Sencoded()); +}); + +cachedProperty(Signature, 'R', function R() { + return this.eddsa.decodePoint(this.Rencoded()); +}); + +cachedProperty(Signature, 'Rencoded', function Rencoded() { + return this.eddsa.encodePoint(this.R()); +}); + +cachedProperty(Signature, 'Sencoded', function Sencoded() { + return this.eddsa.encodeInt(this.S()); +}); + +Signature.prototype.toBytes = function toBytes() { + return this.Rencoded().concat(this.Sencoded()); +}; + +Signature.prototype.toHex = function toHex() { + return utils.encode(this.toBytes(), 'hex').toUpperCase(); +}; + +module.exports = Signature; + +},{"../utils":103,"bn.js":18}],102:[function(require,module,exports){ +module.exports = { + doubles: { + step: 4, + points: [ + [ + 'e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a', + 'f7e3507399e595929db99f34f57937101296891e44d23f0be1f32cce69616821', + ], + [ + '8282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508', + '11f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf', + ], + [ + '175e159f728b865a72f99cc6c6fc846de0b93833fd2222ed73fce5b551e5b739', + 'd3506e0d9e3c79eba4ef97a51ff71f5eacb5955add24345c6efa6ffee9fed695', + ], + [ + '363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640', + '4e273adfc732221953b445397f3363145b9a89008199ecb62003c7f3bee9de9', + ], + [ + '8b4b5f165df3c2be8c6244b5b745638843e4a781a15bcd1b69f79a55dffdf80c', + '4aad0a6f68d308b4b3fbd7813ab0da04f9e336546162ee56b3eff0c65fd4fd36', + ], + [ + '723cbaa6e5db996d6bf771c00bd548c7b700dbffa6c0e77bcb6115925232fcda', + '96e867b5595cc498a921137488824d6e2660a0653779494801dc069d9eb39f5f', + ], + [ + 'eebfa4d493bebf98ba5feec812c2d3b50947961237a919839a533eca0e7dd7fa', + '5d9a8ca3970ef0f269ee7edaf178089d9ae4cdc3a711f712ddfd4fdae1de8999', + ], + [ + '100f44da696e71672791d0a09b7bde459f1215a29b3c03bfefd7835b39a48db0', + 'cdd9e13192a00b772ec8f3300c090666b7ff4a18ff5195ac0fbd5cd62bc65a09', + ], + [ + 'e1031be262c7ed1b1dc9227a4a04c017a77f8d4464f3b3852c8acde6e534fd2d', + '9d7061928940405e6bb6a4176597535af292dd419e1ced79a44f18f29456a00d', + ], + [ + 'feea6cae46d55b530ac2839f143bd7ec5cf8b266a41d6af52d5e688d9094696d', + 'e57c6b6c97dce1bab06e4e12bf3ecd5c981c8957cc41442d3155debf18090088', + ], + [ + 'da67a91d91049cdcb367be4be6ffca3cfeed657d808583de33fa978bc1ec6cb1', + '9bacaa35481642bc41f463f7ec9780e5dec7adc508f740a17e9ea8e27a68be1d', + ], + [ + '53904faa0b334cdda6e000935ef22151ec08d0f7bb11069f57545ccc1a37b7c0', + '5bc087d0bc80106d88c9eccac20d3c1c13999981e14434699dcb096b022771c8', + ], + [ + '8e7bcd0bd35983a7719cca7764ca906779b53a043a9b8bcaeff959f43ad86047', + '10b7770b2a3da4b3940310420ca9514579e88e2e47fd68b3ea10047e8460372a', + ], + [ + '385eed34c1cdff21e6d0818689b81bde71a7f4f18397e6690a841e1599c43862', + '283bebc3e8ea23f56701de19e9ebf4576b304eec2086dc8cc0458fe5542e5453', + ], + [ + '6f9d9b803ecf191637c73a4413dfa180fddf84a5947fbc9c606ed86c3fac3a7', + '7c80c68e603059ba69b8e2a30e45c4d47ea4dd2f5c281002d86890603a842160', + ], + [ + '3322d401243c4e2582a2147c104d6ecbf774d163db0f5e5313b7e0e742d0e6bd', + '56e70797e9664ef5bfb019bc4ddaf9b72805f63ea2873af624f3a2e96c28b2a0', + ], + [ + '85672c7d2de0b7da2bd1770d89665868741b3f9af7643397721d74d28134ab83', + '7c481b9b5b43b2eb6374049bfa62c2e5e77f17fcc5298f44c8e3094f790313a6', + ], + [ + '948bf809b1988a46b06c9f1919413b10f9226c60f668832ffd959af60c82a0a', + '53a562856dcb6646dc6b74c5d1c3418c6d4dff08c97cd2bed4cb7f88d8c8e589', + ], + [ + '6260ce7f461801c34f067ce0f02873a8f1b0e44dfc69752accecd819f38fd8e8', + 'bc2da82b6fa5b571a7f09049776a1ef7ecd292238051c198c1a84e95b2b4ae17', + ], + [ + 'e5037de0afc1d8d43d8348414bbf4103043ec8f575bfdc432953cc8d2037fa2d', + '4571534baa94d3b5f9f98d09fb990bddbd5f5b03ec481f10e0e5dc841d755bda', + ], + [ + 'e06372b0f4a207adf5ea905e8f1771b4e7e8dbd1c6a6c5b725866a0ae4fce725', + '7a908974bce18cfe12a27bb2ad5a488cd7484a7787104870b27034f94eee31dd', + ], + [ + '213c7a715cd5d45358d0bbf9dc0ce02204b10bdde2a3f58540ad6908d0559754', + '4b6dad0b5ae462507013ad06245ba190bb4850f5f36a7eeddff2c27534b458f2', + ], + [ + '4e7c272a7af4b34e8dbb9352a5419a87e2838c70adc62cddf0cc3a3b08fbd53c', + '17749c766c9d0b18e16fd09f6def681b530b9614bff7dd33e0b3941817dcaae6', + ], + [ + 'fea74e3dbe778b1b10f238ad61686aa5c76e3db2be43057632427e2840fb27b6', + '6e0568db9b0b13297cf674deccb6af93126b596b973f7b77701d3db7f23cb96f', + ], + [ + '76e64113f677cf0e10a2570d599968d31544e179b760432952c02a4417bdde39', + 'c90ddf8dee4e95cf577066d70681f0d35e2a33d2b56d2032b4b1752d1901ac01', + ], + [ + 'c738c56b03b2abe1e8281baa743f8f9a8f7cc643df26cbee3ab150242bcbb891', + '893fb578951ad2537f718f2eacbfbbbb82314eef7880cfe917e735d9699a84c3', + ], + [ + 'd895626548b65b81e264c7637c972877d1d72e5f3a925014372e9f6588f6c14b', + 'febfaa38f2bc7eae728ec60818c340eb03428d632bb067e179363ed75d7d991f', + ], + [ + 'b8da94032a957518eb0f6433571e8761ceffc73693e84edd49150a564f676e03', + '2804dfa44805a1e4d7c99cc9762808b092cc584d95ff3b511488e4e74efdf6e7', + ], + [ + 'e80fea14441fb33a7d8adab9475d7fab2019effb5156a792f1a11778e3c0df5d', + 'eed1de7f638e00771e89768ca3ca94472d155e80af322ea9fcb4291b6ac9ec78', + ], + [ + 'a301697bdfcd704313ba48e51d567543f2a182031efd6915ddc07bbcc4e16070', + '7370f91cfb67e4f5081809fa25d40f9b1735dbf7c0a11a130c0d1a041e177ea1', + ], + [ + '90ad85b389d6b936463f9d0512678de208cc330b11307fffab7ac63e3fb04ed4', + 'e507a3620a38261affdcbd9427222b839aefabe1582894d991d4d48cb6ef150', + ], + [ + '8f68b9d2f63b5f339239c1ad981f162ee88c5678723ea3351b7b444c9ec4c0da', + '662a9f2dba063986de1d90c2b6be215dbbea2cfe95510bfdf23cbf79501fff82', + ], + [ + 'e4f3fb0176af85d65ff99ff9198c36091f48e86503681e3e6686fd5053231e11', + '1e63633ad0ef4f1c1661a6d0ea02b7286cc7e74ec951d1c9822c38576feb73bc', + ], + [ + '8c00fa9b18ebf331eb961537a45a4266c7034f2f0d4e1d0716fb6eae20eae29e', + 'efa47267fea521a1a9dc343a3736c974c2fadafa81e36c54e7d2a4c66702414b', + ], + [ + 'e7a26ce69dd4829f3e10cec0a9e98ed3143d084f308b92c0997fddfc60cb3e41', + '2a758e300fa7984b471b006a1aafbb18d0a6b2c0420e83e20e8a9421cf2cfd51', + ], + [ + 'b6459e0ee3662ec8d23540c223bcbdc571cbcb967d79424f3cf29eb3de6b80ef', + '67c876d06f3e06de1dadf16e5661db3c4b3ae6d48e35b2ff30bf0b61a71ba45', + ], + [ + 'd68a80c8280bb840793234aa118f06231d6f1fc67e73c5a5deda0f5b496943e8', + 'db8ba9fff4b586d00c4b1f9177b0e28b5b0e7b8f7845295a294c84266b133120', + ], + [ + '324aed7df65c804252dc0270907a30b09612aeb973449cea4095980fc28d3d5d', + '648a365774b61f2ff130c0c35aec1f4f19213b0c7e332843967224af96ab7c84', + ], + [ + '4df9c14919cde61f6d51dfdbe5fee5dceec4143ba8d1ca888e8bd373fd054c96', + '35ec51092d8728050974c23a1d85d4b5d506cdc288490192ebac06cad10d5d', + ], + [ + '9c3919a84a474870faed8a9c1cc66021523489054d7f0308cbfc99c8ac1f98cd', + 'ddb84f0f4a4ddd57584f044bf260e641905326f76c64c8e6be7e5e03d4fc599d', + ], + [ + '6057170b1dd12fdf8de05f281d8e06bb91e1493a8b91d4cc5a21382120a959e5', + '9a1af0b26a6a4807add9a2daf71df262465152bc3ee24c65e899be932385a2a8', + ], + [ + 'a576df8e23a08411421439a4518da31880cef0fba7d4df12b1a6973eecb94266', + '40a6bf20e76640b2c92b97afe58cd82c432e10a7f514d9f3ee8be11ae1b28ec8', + ], + [ + '7778a78c28dec3e30a05fe9629de8c38bb30d1f5cf9a3a208f763889be58ad71', + '34626d9ab5a5b22ff7098e12f2ff580087b38411ff24ac563b513fc1fd9f43ac', + ], + [ + '928955ee637a84463729fd30e7afd2ed5f96274e5ad7e5cb09eda9c06d903ac', + 'c25621003d3f42a827b78a13093a95eeac3d26efa8a8d83fc5180e935bcd091f', + ], + [ + '85d0fef3ec6db109399064f3a0e3b2855645b4a907ad354527aae75163d82751', + '1f03648413a38c0be29d496e582cf5663e8751e96877331582c237a24eb1f962', + ], + [ + 'ff2b0dce97eece97c1c9b6041798b85dfdfb6d8882da20308f5404824526087e', + '493d13fef524ba188af4c4dc54d07936c7b7ed6fb90e2ceb2c951e01f0c29907', + ], + [ + '827fbbe4b1e880ea9ed2b2e6301b212b57f1ee148cd6dd28780e5e2cf856e241', + 'c60f9c923c727b0b71bef2c67d1d12687ff7a63186903166d605b68baec293ec', + ], + [ + 'eaa649f21f51bdbae7be4ae34ce6e5217a58fdce7f47f9aa7f3b58fa2120e2b3', + 'be3279ed5bbbb03ac69a80f89879aa5a01a6b965f13f7e59d47a5305ba5ad93d', + ], + [ + 'e4a42d43c5cf169d9391df6decf42ee541b6d8f0c9a137401e23632dda34d24f', + '4d9f92e716d1c73526fc99ccfb8ad34ce886eedfa8d8e4f13a7f7131deba9414', + ], + [ + '1ec80fef360cbdd954160fadab352b6b92b53576a88fea4947173b9d4300bf19', + 'aeefe93756b5340d2f3a4958a7abbf5e0146e77f6295a07b671cdc1cc107cefd', + ], + [ + '146a778c04670c2f91b00af4680dfa8bce3490717d58ba889ddb5928366642be', + 'b318e0ec3354028add669827f9d4b2870aaa971d2f7e5ed1d0b297483d83efd0', + ], + [ + 'fa50c0f61d22e5f07e3acebb1aa07b128d0012209a28b9776d76a8793180eef9', + '6b84c6922397eba9b72cd2872281a68a5e683293a57a213b38cd8d7d3f4f2811', + ], + [ + 'da1d61d0ca721a11b1a5bf6b7d88e8421a288ab5d5bba5220e53d32b5f067ec2', + '8157f55a7c99306c79c0766161c91e2966a73899d279b48a655fba0f1ad836f1', + ], + [ + 'a8e282ff0c9706907215ff98e8fd416615311de0446f1e062a73b0610d064e13', + '7f97355b8db81c09abfb7f3c5b2515888b679a3e50dd6bd6cef7c73111f4cc0c', + ], + [ + '174a53b9c9a285872d39e56e6913cab15d59b1fa512508c022f382de8319497c', + 'ccc9dc37abfc9c1657b4155f2c47f9e6646b3a1d8cb9854383da13ac079afa73', + ], + [ + '959396981943785c3d3e57edf5018cdbe039e730e4918b3d884fdff09475b7ba', + '2e7e552888c331dd8ba0386a4b9cd6849c653f64c8709385e9b8abf87524f2fd', + ], + [ + 'd2a63a50ae401e56d645a1153b109a8fcca0a43d561fba2dbb51340c9d82b151', + 'e82d86fb6443fcb7565aee58b2948220a70f750af484ca52d4142174dcf89405', + ], + [ + '64587e2335471eb890ee7896d7cfdc866bacbdbd3839317b3436f9b45617e073', + 'd99fcdd5bf6902e2ae96dd6447c299a185b90a39133aeab358299e5e9faf6589', + ], + [ + '8481bde0e4e4d885b3a546d3e549de042f0aa6cea250e7fd358d6c86dd45e458', + '38ee7b8cba5404dd84a25bf39cecb2ca900a79c42b262e556d64b1b59779057e', + ], + [ + '13464a57a78102aa62b6979ae817f4637ffcfed3c4b1ce30bcd6303f6caf666b', + '69be159004614580ef7e433453ccb0ca48f300a81d0942e13f495a907f6ecc27', + ], + [ + 'bc4a9df5b713fe2e9aef430bcc1dc97a0cd9ccede2f28588cada3a0d2d83f366', + 'd3a81ca6e785c06383937adf4b798caa6e8a9fbfa547b16d758d666581f33c1', + ], + [ + '8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa', + '40a30463a3305193378fedf31f7cc0eb7ae784f0451cb9459e71dc73cbef9482', + ], + [ + '8ea9666139527a8c1dd94ce4f071fd23c8b350c5a4bb33748c4ba111faccae0', + '620efabbc8ee2782e24e7c0cfb95c5d735b783be9cf0f8e955af34a30e62b945', + ], + [ + 'dd3625faef5ba06074669716bbd3788d89bdde815959968092f76cc4eb9a9787', + '7a188fa3520e30d461da2501045731ca941461982883395937f68d00c644a573', + ], + [ + 'f710d79d9eb962297e4f6232b40e8f7feb2bc63814614d692c12de752408221e', + 'ea98e67232d3b3295d3b535532115ccac8612c721851617526ae47a9c77bfc82', + ], + ], + }, + naf: { + wnd: 7, + points: [ + [ + 'f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9', + '388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672', + ], + [ + '2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4', + 'd8ac222636e5e3d6d4dba9dda6c9c426f788271bab0d6840dca87d3aa6ac62d6', + ], + [ + '5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc', + '6aebca40ba255960a3178d6d861a54dba813d0b813fde7b5a5082628087264da', + ], + [ + 'acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe', + 'cc338921b0a7d9fd64380971763b61e9add888a4375f8e0f05cc262ac64f9c37', + ], + [ + '774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb', + 'd984a032eb6b5e190243dd56d7b7b365372db1e2dff9d6a8301d74c9c953c61b', + ], + [ + 'f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8', + 'ab0902e8d880a89758212eb65cdaf473a1a06da521fa91f29b5cb52db03ed81', + ], + [ + 'd7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e', + '581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58', + ], + [ + 'defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34', + '4211ab0694635168e997b0ead2a93daeced1f4a04a95c0f6cfb199f69e56eb77', + ], + [ + '2b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c', + '85e89bc037945d93b343083b5a1c86131a01f60c50269763b570c854e5c09b7a', + ], + [ + '352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5', + '321eb4075348f534d59c18259dda3e1f4a1b3b2e71b1039c67bd3d8bcf81998c', + ], + [ + '2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f', + '2de1068295dd865b64569335bd5dd80181d70ecfc882648423ba76b532b7d67', + ], + [ + '9248279b09b4d68dab21a9b066edda83263c3d84e09572e269ca0cd7f5453714', + '73016f7bf234aade5d1aa71bdea2b1ff3fc0de2a887912ffe54a32ce97cb3402', + ], + [ + 'daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729', + 'a69dce4a7d6c98e8d4a1aca87ef8d7003f83c230f3afa726ab40e52290be1c55', + ], + [ + 'c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db', + '2119a460ce326cdc76c45926c982fdac0e106e861edf61c5a039063f0e0e6482', + ], + [ + '6a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4', + 'e022cf42c2bd4a708b3f5126f16a24ad8b33ba48d0423b6efd5e6348100d8a82', + ], + [ + '1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5', + 'b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396', + ], + [ + '605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479', + '2972d2de4f8d20681a78d93ec96fe23c26bfae84fb14db43b01e1e9056b8c49', + ], + [ + '62d14dab4150bf497402fdc45a215e10dcb01c354959b10cfe31c7e9d87ff33d', + '80fc06bd8cc5b01098088a1950eed0db01aa132967ab472235f5642483b25eaf', + ], + [ + '80c60ad0040f27dade5b4b06c408e56b2c50e9f56b9b8b425e555c2f86308b6f', + '1c38303f1cc5c30f26e66bad7fe72f70a65eed4cbe7024eb1aa01f56430bd57a', + ], + [ + '7a9375ad6167ad54aa74c6348cc54d344cc5dc9487d847049d5eabb0fa03c8fb', + 'd0e3fa9eca8726909559e0d79269046bdc59ea10c70ce2b02d499ec224dc7f7', + ], + [ + 'd528ecd9b696b54c907a9ed045447a79bb408ec39b68df504bb51f459bc3ffc9', + 'eecf41253136e5f99966f21881fd656ebc4345405c520dbc063465b521409933', + ], + [ + '49370a4b5f43412ea25f514e8ecdad05266115e4a7ecb1387231808f8b45963', + '758f3f41afd6ed428b3081b0512fd62a54c3f3afbb5b6764b653052a12949c9a', + ], + [ + '77f230936ee88cbbd73df930d64702ef881d811e0e1498e2f1c13eb1fc345d74', + '958ef42a7886b6400a08266e9ba1b37896c95330d97077cbbe8eb3c7671c60d6', + ], + [ + 'f2dac991cc4ce4b9ea44887e5c7c0bce58c80074ab9d4dbaeb28531b7739f530', + 'e0dedc9b3b2f8dad4da1f32dec2531df9eb5fbeb0598e4fd1a117dba703a3c37', + ], + [ + '463b3d9f662621fb1b4be8fbbe2520125a216cdfc9dae3debcba4850c690d45b', + '5ed430d78c296c3543114306dd8622d7c622e27c970a1de31cb377b01af7307e', + ], + [ + 'f16f804244e46e2a09232d4aff3b59976b98fac14328a2d1a32496b49998f247', + 'cedabd9b82203f7e13d206fcdf4e33d92a6c53c26e5cce26d6579962c4e31df6', + ], + [ + 'caf754272dc84563b0352b7a14311af55d245315ace27c65369e15f7151d41d1', + 'cb474660ef35f5f2a41b643fa5e460575f4fa9b7962232a5c32f908318a04476', + ], + [ + '2600ca4b282cb986f85d0f1709979d8b44a09c07cb86d7c124497bc86f082120', + '4119b88753c15bd6a693b03fcddbb45d5ac6be74ab5f0ef44b0be9475a7e4b40', + ], + [ + '7635ca72d7e8432c338ec53cd12220bc01c48685e24f7dc8c602a7746998e435', + '91b649609489d613d1d5e590f78e6d74ecfc061d57048bad9e76f302c5b9c61', + ], + [ + '754e3239f325570cdbbf4a87deee8a66b7f2b33479d468fbc1a50743bf56cc18', + '673fb86e5bda30fb3cd0ed304ea49a023ee33d0197a695d0c5d98093c536683', + ], + [ + 'e3e6bd1071a1e96aff57859c82d570f0330800661d1c952f9fe2694691d9b9e8', + '59c9e0bba394e76f40c0aa58379a3cb6a5a2283993e90c4167002af4920e37f5', + ], + [ + '186b483d056a033826ae73d88f732985c4ccb1f32ba35f4b4cc47fdcf04aa6eb', + '3b952d32c67cf77e2e17446e204180ab21fb8090895138b4a4a797f86e80888b', + ], + [ + 'df9d70a6b9876ce544c98561f4be4f725442e6d2b737d9c91a8321724ce0963f', + '55eb2dafd84d6ccd5f862b785dc39d4ab157222720ef9da217b8c45cf2ba2417', + ], + [ + '5edd5cc23c51e87a497ca815d5dce0f8ab52554f849ed8995de64c5f34ce7143', + 'efae9c8dbc14130661e8cec030c89ad0c13c66c0d17a2905cdc706ab7399a868', + ], + [ + '290798c2b6476830da12fe02287e9e777aa3fba1c355b17a722d362f84614fba', + 'e38da76dcd440621988d00bcf79af25d5b29c094db2a23146d003afd41943e7a', + ], + [ + 'af3c423a95d9f5b3054754efa150ac39cd29552fe360257362dfdecef4053b45', + 'f98a3fd831eb2b749a93b0e6f35cfb40c8cd5aa667a15581bc2feded498fd9c6', + ], + [ + '766dbb24d134e745cccaa28c99bf274906bb66b26dcf98df8d2fed50d884249a', + '744b1152eacbe5e38dcc887980da38b897584a65fa06cedd2c924f97cbac5996', + ], + [ + '59dbf46f8c94759ba21277c33784f41645f7b44f6c596a58ce92e666191abe3e', + 'c534ad44175fbc300f4ea6ce648309a042ce739a7919798cd85e216c4a307f6e', + ], + [ + 'f13ada95103c4537305e691e74e9a4a8dd647e711a95e73cb62dc6018cfd87b8', + 'e13817b44ee14de663bf4bc808341f326949e21a6a75c2570778419bdaf5733d', + ], + [ + '7754b4fa0e8aced06d4167a2c59cca4cda1869c06ebadfb6488550015a88522c', + '30e93e864e669d82224b967c3020b8fa8d1e4e350b6cbcc537a48b57841163a2', + ], + [ + '948dcadf5990e048aa3874d46abef9d701858f95de8041d2a6828c99e2262519', + 'e491a42537f6e597d5d28a3224b1bc25df9154efbd2ef1d2cbba2cae5347d57e', + ], + [ + '7962414450c76c1689c7b48f8202ec37fb224cf5ac0bfa1570328a8a3d7c77ab', + '100b610ec4ffb4760d5c1fc133ef6f6b12507a051f04ac5760afa5b29db83437', + ], + [ + '3514087834964b54b15b160644d915485a16977225b8847bb0dd085137ec47ca', + 'ef0afbb2056205448e1652c48e8127fc6039e77c15c2378b7e7d15a0de293311', + ], + [ + 'd3cc30ad6b483e4bc79ce2c9dd8bc54993e947eb8df787b442943d3f7b527eaf', + '8b378a22d827278d89c5e9be8f9508ae3c2ad46290358630afb34db04eede0a4', + ], + [ + '1624d84780732860ce1c78fcbfefe08b2b29823db913f6493975ba0ff4847610', + '68651cf9b6da903e0914448c6cd9d4ca896878f5282be4c8cc06e2a404078575', + ], + [ + '733ce80da955a8a26902c95633e62a985192474b5af207da6df7b4fd5fc61cd4', + 'f5435a2bd2badf7d485a4d8b8db9fcce3e1ef8e0201e4578c54673bc1dc5ea1d', + ], + [ + '15d9441254945064cf1a1c33bbd3b49f8966c5092171e699ef258dfab81c045c', + 'd56eb30b69463e7234f5137b73b84177434800bacebfc685fc37bbe9efe4070d', + ], + [ + 'a1d0fcf2ec9de675b612136e5ce70d271c21417c9d2b8aaaac138599d0717940', + 'edd77f50bcb5a3cab2e90737309667f2641462a54070f3d519212d39c197a629', + ], + [ + 'e22fbe15c0af8ccc5780c0735f84dbe9a790badee8245c06c7ca37331cb36980', + 'a855babad5cd60c88b430a69f53a1a7a38289154964799be43d06d77d31da06', + ], + [ + '311091dd9860e8e20ee13473c1155f5f69635e394704eaa74009452246cfa9b3', + '66db656f87d1f04fffd1f04788c06830871ec5a64feee685bd80f0b1286d8374', + ], + [ + '34c1fd04d301be89b31c0442d3e6ac24883928b45a9340781867d4232ec2dbdf', + '9414685e97b1b5954bd46f730174136d57f1ceeb487443dc5321857ba73abee', + ], + [ + 'f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63', + '4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1', + ], + [ + 'd7b8740f74a8fbaab1f683db8f45de26543a5490bca627087236912469a0b448', + 'fa77968128d9c92ee1010f337ad4717eff15db5ed3c049b3411e0315eaa4593b', + ], + [ + '32d31c222f8f6f0ef86f7c98d3a3335ead5bcd32abdd94289fe4d3091aa824bf', + '5f3032f5892156e39ccd3d7915b9e1da2e6dac9e6f26e961118d14b8462e1661', + ], + [ + '7461f371914ab32671045a155d9831ea8793d77cd59592c4340f86cbc18347b5', + '8ec0ba238b96bec0cbdddcae0aa442542eee1ff50c986ea6b39847b3cc092ff6', + ], + [ + 'ee079adb1df1860074356a25aa38206a6d716b2c3e67453d287698bad7b2b2d6', + '8dc2412aafe3be5c4c5f37e0ecc5f9f6a446989af04c4e25ebaac479ec1c8c1e', + ], + [ + '16ec93e447ec83f0467b18302ee620f7e65de331874c9dc72bfd8616ba9da6b5', + '5e4631150e62fb40d0e8c2a7ca5804a39d58186a50e497139626778e25b0674d', + ], + [ + 'eaa5f980c245f6f038978290afa70b6bd8855897f98b6aa485b96065d537bd99', + 'f65f5d3e292c2e0819a528391c994624d784869d7e6ea67fb18041024edc07dc', + ], + [ + '78c9407544ac132692ee1910a02439958ae04877151342ea96c4b6b35a49f51', + 'f3e0319169eb9b85d5404795539a5e68fa1fbd583c064d2462b675f194a3ddb4', + ], + [ + '494f4be219a1a77016dcd838431aea0001cdc8ae7a6fc688726578d9702857a5', + '42242a969283a5f339ba7f075e36ba2af925ce30d767ed6e55f4b031880d562c', + ], + [ + 'a598a8030da6d86c6bc7f2f5144ea549d28211ea58faa70ebf4c1e665c1fe9b5', + '204b5d6f84822c307e4b4a7140737aec23fc63b65b35f86a10026dbd2d864e6b', + ], + [ + 'c41916365abb2b5d09192f5f2dbeafec208f020f12570a184dbadc3e58595997', + '4f14351d0087efa49d245b328984989d5caf9450f34bfc0ed16e96b58fa9913', + ], + [ + '841d6063a586fa475a724604da03bc5b92a2e0d2e0a36acfe4c73a5514742881', + '73867f59c0659e81904f9a1c7543698e62562d6744c169ce7a36de01a8d6154', + ], + [ + '5e95bb399a6971d376026947f89bde2f282b33810928be4ded112ac4d70e20d5', + '39f23f366809085beebfc71181313775a99c9aed7d8ba38b161384c746012865', + ], + [ + '36e4641a53948fd476c39f8a99fd974e5ec07564b5315d8bf99471bca0ef2f66', + 'd2424b1b1abe4eb8164227b085c9aa9456ea13493fd563e06fd51cf5694c78fc', + ], + [ + '336581ea7bfbbb290c191a2f507a41cf5643842170e914faeab27c2c579f726', + 'ead12168595fe1be99252129b6e56b3391f7ab1410cd1e0ef3dcdcabd2fda224', + ], + [ + '8ab89816dadfd6b6a1f2634fcf00ec8403781025ed6890c4849742706bd43ede', + '6fdcef09f2f6d0a044e654aef624136f503d459c3e89845858a47a9129cdd24e', + ], + [ + '1e33f1a746c9c5778133344d9299fcaa20b0938e8acff2544bb40284b8c5fb94', + '60660257dd11b3aa9c8ed618d24edff2306d320f1d03010e33a7d2057f3b3b6', + ], + [ + '85b7c1dcb3cec1b7ee7f30ded79dd20a0ed1f4cc18cbcfcfa410361fd8f08f31', + '3d98a9cdd026dd43f39048f25a8847f4fcafad1895d7a633c6fed3c35e999511', + ], + [ + '29df9fbd8d9e46509275f4b125d6d45d7fbe9a3b878a7af872a2800661ac5f51', + 'b4c4fe99c775a606e2d8862179139ffda61dc861c019e55cd2876eb2a27d84b', + ], + [ + 'a0b1cae06b0a847a3fea6e671aaf8adfdfe58ca2f768105c8082b2e449fce252', + 'ae434102edde0958ec4b19d917a6a28e6b72da1834aff0e650f049503a296cf2', + ], + [ + '4e8ceafb9b3e9a136dc7ff67e840295b499dfb3b2133e4ba113f2e4c0e121e5', + 'cf2174118c8b6d7a4b48f6d534ce5c79422c086a63460502b827ce62a326683c', + ], + [ + 'd24a44e047e19b6f5afb81c7ca2f69080a5076689a010919f42725c2b789a33b', + '6fb8d5591b466f8fc63db50f1c0f1c69013f996887b8244d2cdec417afea8fa3', + ], + [ + 'ea01606a7a6c9cdd249fdfcfacb99584001edd28abbab77b5104e98e8e3b35d4', + '322af4908c7312b0cfbfe369f7a7b3cdb7d4494bc2823700cfd652188a3ea98d', + ], + [ + 'af8addbf2b661c8a6c6328655eb96651252007d8c5ea31be4ad196de8ce2131f', + '6749e67c029b85f52a034eafd096836b2520818680e26ac8f3dfbcdb71749700', + ], + [ + 'e3ae1974566ca06cc516d47e0fb165a674a3dabcfca15e722f0e3450f45889', + '2aeabe7e4531510116217f07bf4d07300de97e4874f81f533420a72eeb0bd6a4', + ], + [ + '591ee355313d99721cf6993ffed1e3e301993ff3ed258802075ea8ced397e246', + 'b0ea558a113c30bea60fc4775460c7901ff0b053d25ca2bdeee98f1a4be5d196', + ], + [ + '11396d55fda54c49f19aa97318d8da61fa8584e47b084945077cf03255b52984', + '998c74a8cd45ac01289d5833a7beb4744ff536b01b257be4c5767bea93ea57a4', + ], + [ + '3c5d2a1ba39c5a1790000738c9e0c40b8dcdfd5468754b6405540157e017aa7a', + 'b2284279995a34e2f9d4de7396fc18b80f9b8b9fdd270f6661f79ca4c81bd257', + ], + [ + 'cc8704b8a60a0defa3a99a7299f2e9c3fbc395afb04ac078425ef8a1793cc030', + 'bdd46039feed17881d1e0862db347f8cf395b74fc4bcdc4e940b74e3ac1f1b13', + ], + [ + 'c533e4f7ea8555aacd9777ac5cad29b97dd4defccc53ee7ea204119b2889b197', + '6f0a256bc5efdf429a2fb6242f1a43a2d9b925bb4a4b3a26bb8e0f45eb596096', + ], + [ + 'c14f8f2ccb27d6f109f6d08d03cc96a69ba8c34eec07bbcf566d48e33da6593', + 'c359d6923bb398f7fd4473e16fe1c28475b740dd098075e6c0e8649113dc3a38', + ], + [ + 'a6cbc3046bc6a450bac24789fa17115a4c9739ed75f8f21ce441f72e0b90e6ef', + '21ae7f4680e889bb130619e2c0f95a360ceb573c70603139862afd617fa9b9f', + ], + [ + '347d6d9a02c48927ebfb86c1359b1caf130a3c0267d11ce6344b39f99d43cc38', + '60ea7f61a353524d1c987f6ecec92f086d565ab687870cb12689ff1e31c74448', + ], + [ + 'da6545d2181db8d983f7dcb375ef5866d47c67b1bf31c8cf855ef7437b72656a', + '49b96715ab6878a79e78f07ce5680c5d6673051b4935bd897fea824b77dc208a', + ], + [ + 'c40747cc9d012cb1a13b8148309c6de7ec25d6945d657146b9d5994b8feb1111', + '5ca560753be2a12fc6de6caf2cb489565db936156b9514e1bb5e83037e0fa2d4', + ], + [ + '4e42c8ec82c99798ccf3a610be870e78338c7f713348bd34c8203ef4037f3502', + '7571d74ee5e0fb92a7a8b33a07783341a5492144cc54bcc40a94473693606437', + ], + [ + '3775ab7089bc6af823aba2e1af70b236d251cadb0c86743287522a1b3b0dedea', + 'be52d107bcfa09d8bcb9736a828cfa7fac8db17bf7a76a2c42ad961409018cf7', + ], + [ + 'cee31cbf7e34ec379d94fb814d3d775ad954595d1314ba8846959e3e82f74e26', + '8fd64a14c06b589c26b947ae2bcf6bfa0149ef0be14ed4d80f448a01c43b1c6d', + ], + [ + 'b4f9eaea09b6917619f6ea6a4eb5464efddb58fd45b1ebefcdc1a01d08b47986', + '39e5c9925b5a54b07433a4f18c61726f8bb131c012ca542eb24a8ac07200682a', + ], + [ + 'd4263dfc3d2df923a0179a48966d30ce84e2515afc3dccc1b77907792ebcc60e', + '62dfaf07a0f78feb30e30d6295853ce189e127760ad6cf7fae164e122a208d54', + ], + [ + '48457524820fa65a4f8d35eb6930857c0032acc0a4a2de422233eeda897612c4', + '25a748ab367979d98733c38a1fa1c2e7dc6cc07db2d60a9ae7a76aaa49bd0f77', + ], + [ + 'dfeeef1881101f2cb11644f3a2afdfc2045e19919152923f367a1767c11cceda', + 'ecfb7056cf1de042f9420bab396793c0c390bde74b4bbdff16a83ae09a9a7517', + ], + [ + '6d7ef6b17543f8373c573f44e1f389835d89bcbc6062ced36c82df83b8fae859', + 'cd450ec335438986dfefa10c57fea9bcc521a0959b2d80bbf74b190dca712d10', + ], + [ + 'e75605d59102a5a2684500d3b991f2e3f3c88b93225547035af25af66e04541f', + 'f5c54754a8f71ee540b9b48728473e314f729ac5308b06938360990e2bfad125', + ], + [ + 'eb98660f4c4dfaa06a2be453d5020bc99a0c2e60abe388457dd43fefb1ed620c', + '6cb9a8876d9cb8520609af3add26cd20a0a7cd8a9411131ce85f44100099223e', + ], + [ + '13e87b027d8514d35939f2e6892b19922154596941888336dc3563e3b8dba942', + 'fef5a3c68059a6dec5d624114bf1e91aac2b9da568d6abeb2570d55646b8adf1', + ], + [ + 'ee163026e9fd6fe017c38f06a5be6fc125424b371ce2708e7bf4491691e5764a', + '1acb250f255dd61c43d94ccc670d0f58f49ae3fa15b96623e5430da0ad6c62b2', + ], + [ + 'b268f5ef9ad51e4d78de3a750c2dc89b1e626d43505867999932e5db33af3d80', + '5f310d4b3c99b9ebb19f77d41c1dee018cf0d34fd4191614003e945a1216e423', + ], + [ + 'ff07f3118a9df035e9fad85eb6c7bfe42b02f01ca99ceea3bf7ffdba93c4750d', + '438136d603e858a3a5c440c38eccbaddc1d2942114e2eddd4740d098ced1f0d8', + ], + [ + '8d8b9855c7c052a34146fd20ffb658bea4b9f69e0d825ebec16e8c3ce2b526a1', + 'cdb559eedc2d79f926baf44fb84ea4d44bcf50fee51d7ceb30e2e7f463036758', + ], + [ + '52db0b5384dfbf05bfa9d472d7ae26dfe4b851ceca91b1eba54263180da32b63', + 'c3b997d050ee5d423ebaf66a6db9f57b3180c902875679de924b69d84a7b375', + ], + [ + 'e62f9490d3d51da6395efd24e80919cc7d0f29c3f3fa48c6fff543becbd43352', + '6d89ad7ba4876b0b22c2ca280c682862f342c8591f1daf5170e07bfd9ccafa7d', + ], + [ + '7f30ea2476b399b4957509c88f77d0191afa2ff5cb7b14fd6d8e7d65aaab1193', + 'ca5ef7d4b231c94c3b15389a5f6311e9daff7bb67b103e9880ef4bff637acaec', + ], + [ + '5098ff1e1d9f14fb46a210fada6c903fef0fb7b4a1dd1d9ac60a0361800b7a00', + '9731141d81fc8f8084d37c6e7542006b3ee1b40d60dfe5362a5b132fd17ddc0', + ], + [ + '32b78c7de9ee512a72895be6b9cbefa6e2f3c4ccce445c96b9f2c81e2778ad58', + 'ee1849f513df71e32efc3896ee28260c73bb80547ae2275ba497237794c8753c', + ], + [ + 'e2cb74fddc8e9fbcd076eef2a7c72b0ce37d50f08269dfc074b581550547a4f7', + 'd3aa2ed71c9dd2247a62df062736eb0baddea9e36122d2be8641abcb005cc4a4', + ], + [ + '8438447566d4d7bedadc299496ab357426009a35f235cb141be0d99cd10ae3a8', + 'c4e1020916980a4da5d01ac5e6ad330734ef0d7906631c4f2390426b2edd791f', + ], + [ + '4162d488b89402039b584c6fc6c308870587d9c46f660b878ab65c82c711d67e', + '67163e903236289f776f22c25fb8a3afc1732f2b84b4e95dbda47ae5a0852649', + ], + [ + '3fad3fa84caf0f34f0f89bfd2dcf54fc175d767aec3e50684f3ba4a4bf5f683d', + 'cd1bc7cb6cc407bb2f0ca647c718a730cf71872e7d0d2a53fa20efcdfe61826', + ], + [ + '674f2600a3007a00568c1a7ce05d0816c1fb84bf1370798f1c69532faeb1a86b', + '299d21f9413f33b3edf43b257004580b70db57da0b182259e09eecc69e0d38a5', + ], + [ + 'd32f4da54ade74abb81b815ad1fb3b263d82d6c692714bcff87d29bd5ee9f08f', + 'f9429e738b8e53b968e99016c059707782e14f4535359d582fc416910b3eea87', + ], + [ + '30e4e670435385556e593657135845d36fbb6931f72b08cb1ed954f1e3ce3ff6', + '462f9bce619898638499350113bbc9b10a878d35da70740dc695a559eb88db7b', + ], + [ + 'be2062003c51cc3004682904330e4dee7f3dcd10b01e580bf1971b04d4cad297', + '62188bc49d61e5428573d48a74e1c655b1c61090905682a0d5558ed72dccb9bc', + ], + [ + '93144423ace3451ed29e0fb9ac2af211cb6e84a601df5993c419859fff5df04a', + '7c10dfb164c3425f5c71a3f9d7992038f1065224f72bb9d1d902a6d13037b47c', + ], + [ + 'b015f8044f5fcbdcf21ca26d6c34fb8197829205c7b7d2a7cb66418c157b112c', + 'ab8c1e086d04e813744a655b2df8d5f83b3cdc6faa3088c1d3aea1454e3a1d5f', + ], + [ + 'd5e9e1da649d97d89e4868117a465a3a4f8a18de57a140d36b3f2af341a21b52', + '4cb04437f391ed73111a13cc1d4dd0db1693465c2240480d8955e8592f27447a', + ], + [ + 'd3ae41047dd7ca065dbf8ed77b992439983005cd72e16d6f996a5316d36966bb', + 'bd1aeb21ad22ebb22a10f0303417c6d964f8cdd7df0aca614b10dc14d125ac46', + ], + [ + '463e2763d885f958fc66cdd22800f0a487197d0a82e377b49f80af87c897b065', + 'bfefacdb0e5d0fd7df3a311a94de062b26b80c61fbc97508b79992671ef7ca7f', + ], + [ + '7985fdfd127c0567c6f53ec1bb63ec3158e597c40bfe747c83cddfc910641917', + '603c12daf3d9862ef2b25fe1de289aed24ed291e0ec6708703a5bd567f32ed03', + ], + [ + '74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9', + 'cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08', + ], + [ + '30682a50703375f602d416664ba19b7fc9bab42c72747463a71d0896b22f6da3', + '553e04f6b018b4fa6c8f39e7f311d3176290d0e0f19ca73f17714d9977a22ff8', + ], + [ + '9e2158f0d7c0d5f26c3791efefa79597654e7a2b2464f52b1ee6c1347769ef57', + '712fcdd1b9053f09003a3481fa7762e9ffd7c8ef35a38509e2fbf2629008373', + ], + [ + '176e26989a43c9cfeba4029c202538c28172e566e3c4fce7322857f3be327d66', + 'ed8cc9d04b29eb877d270b4878dc43c19aefd31f4eee09ee7b47834c1fa4b1c3', + ], + [ + '75d46efea3771e6e68abb89a13ad747ecf1892393dfc4f1b7004788c50374da8', + '9852390a99507679fd0b86fd2b39a868d7efc22151346e1a3ca4726586a6bed8', + ], + [ + '809a20c67d64900ffb698c4c825f6d5f2310fb0451c869345b7319f645605721', + '9e994980d9917e22b76b061927fa04143d096ccc54963e6a5ebfa5f3f8e286c1', + ], + [ + '1b38903a43f7f114ed4500b4eac7083fdefece1cf29c63528d563446f972c180', + '4036edc931a60ae889353f77fd53de4a2708b26b6f5da72ad3394119daf408f9', + ], + ], + }, +}; + +},{}],103:[function(require,module,exports){ +'use strict'; + +var utils = exports; +var BN = require('bn.js'); +var minAssert = require('minimalistic-assert'); +var minUtils = require('minimalistic-crypto-utils'); + +utils.assert = minAssert; +utils.toArray = minUtils.toArray; +utils.zero2 = minUtils.zero2; +utils.toHex = minUtils.toHex; +utils.encode = minUtils.encode; + +// Represent num in a w-NAF form +function getNAF(num, w, bits) { + var naf = new Array(Math.max(num.bitLength(), bits) + 1); + naf.fill(0); + + var ws = 1 << (w + 1); + var k = num.clone(); + + for (var i = 0; i < naf.length; i++) { + var z; + var mod = k.andln(ws - 1); + if (k.isOdd()) { + if (mod > (ws >> 1) - 1) + z = (ws >> 1) - mod; + else + z = mod; + k.isubn(z); + } else { + z = 0; + } + + naf[i] = z; + k.iushrn(1); + } + + return naf; +} +utils.getNAF = getNAF; + +// Represent k1, k2 in a Joint Sparse Form +function getJSF(k1, k2) { + var jsf = [ + [], + [], + ]; + + k1 = k1.clone(); + k2 = k2.clone(); + var d1 = 0; + var d2 = 0; + var m8; + while (k1.cmpn(-d1) > 0 || k2.cmpn(-d2) > 0) { + // First phase + var m14 = (k1.andln(3) + d1) & 3; + var m24 = (k2.andln(3) + d2) & 3; + if (m14 === 3) + m14 = -1; + if (m24 === 3) + m24 = -1; + var u1; + if ((m14 & 1) === 0) { + u1 = 0; + } else { + m8 = (k1.andln(7) + d1) & 7; + if ((m8 === 3 || m8 === 5) && m24 === 2) + u1 = -m14; + else + u1 = m14; + } + jsf[0].push(u1); + + var u2; + if ((m24 & 1) === 0) { + u2 = 0; + } else { + m8 = (k2.andln(7) + d2) & 7; + if ((m8 === 3 || m8 === 5) && m14 === 2) + u2 = -m24; + else + u2 = m24; + } + jsf[1].push(u2); + + // Second phase + if (2 * d1 === u1 + 1) + d1 = 1 - d1; + if (2 * d2 === u2 + 1) + d2 = 1 - d2; + k1.iushrn(1); + k2.iushrn(1); + } + + return jsf; +} +utils.getJSF = getJSF; + +function cachedProperty(obj, name, computer) { + var key = '_' + name; + obj.prototype[name] = function cachedProperty() { + return this[key] !== undefined ? this[key] : + this[key] = computer.call(this); + }; +} +utils.cachedProperty = cachedProperty; + +function parseBytes(bytes) { + return typeof bytes === 'string' ? utils.toArray(bytes, 'hex') : + bytes; +} +utils.parseBytes = parseBytes; + +function intFromLE(bytes) { + return new BN(bytes, 'hex', 'le'); +} +utils.intFromLE = intFromLE; + + +},{"bn.js":18,"minimalistic-assert":223,"minimalistic-crypto-utils":224}],104:[function(require,module,exports){ +module.exports={ + "name": "elliptic", + "version": "6.5.4", + "description": "EC cryptography", + "main": "lib/elliptic.js", + "files": [ + "lib" + ], + "scripts": { + "lint": "eslint lib test", + "lint:fix": "npm run lint -- --fix", + "unit": "istanbul test _mocha --reporter=spec test/index.js", + "test": "npm run lint && npm run unit", + "version": "grunt dist && git add dist/" + }, + "repository": { + "type": "git", + "url": "git@github.com:indutny/elliptic" + }, + "keywords": [ + "EC", + "Elliptic", + "curve", + "Cryptography" + ], + "author": "Fedor Indutny ", + "license": "MIT", + "bugs": { + "url": "https://github.com/indutny/elliptic/issues" + }, + "homepage": "https://github.com/indutny/elliptic", + "devDependencies": { + "brfs": "^2.0.2", + "coveralls": "^3.1.0", + "eslint": "^7.6.0", + "grunt": "^1.2.1", + "grunt-browserify": "^5.3.0", + "grunt-cli": "^1.3.2", + "grunt-contrib-connect": "^3.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-uglify": "^5.0.0", + "grunt-mocha-istanbul": "^5.0.2", + "grunt-saucelabs": "^9.0.1", + "istanbul": "^0.4.5", + "mocha": "^8.0.1" + }, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } +} + +},{}],105:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} +module.exports = EventEmitter; +module.exports.once = once; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +function checkListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } +} + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + checkListener(listener); + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function once(emitter, name) { + return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } + + function resolver() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener('error', errorListener); + } + resolve([].slice.call(arguments)); + }; + + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== 'error') { + addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + } + }); +} + +function addErrorHandlerIfEventEmitter(emitter, handler, flags) { + if (typeof emitter.on === 'function') { + eventTargetAgnosticAddListener(emitter, 'error', handler, flags); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === 'function') { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen for `error` events here. + emitter.addEventListener(name, function wrapListener(arg) { + // IE does not have builtin `{ once: true }` support so we + // have to do it manually. + if (flags.once) { + emitter.removeEventListener(name, wrapListener); + } + listener(arg); + }); + } else { + throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + } +} + +},{}],106:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer +var MD5 = require('md5.js') + +/* eslint-disable camelcase */ +function EVP_BytesToKey (password, salt, keyBits, ivLen) { + if (!Buffer.isBuffer(password)) password = Buffer.from(password, 'binary') + if (salt) { + if (!Buffer.isBuffer(salt)) salt = Buffer.from(salt, 'binary') + if (salt.length !== 8) throw new RangeError('salt should be Buffer with 8 byte length') + } + + var keyLen = keyBits / 8 + var key = Buffer.alloc(keyLen) + var iv = Buffer.alloc(ivLen || 0) + var tmp = Buffer.alloc(0) + + while (keyLen > 0 || ivLen > 0) { + var hash = new MD5() + hash.update(tmp) + hash.update(password) + if (salt) hash.update(salt) + tmp = hash.digest() + + var used = 0 + + if (keyLen > 0) { + var keyStart = key.length - keyLen + used = Math.min(keyLen, tmp.length) + tmp.copy(key, keyStart, 0, used) + keyLen -= used + } + + if (used < tmp.length && ivLen > 0) { + var ivStart = iv.length - ivLen + var length = Math.min(ivLen, tmp.length - used) + tmp.copy(iv, ivStart, used, used + length) + ivLen -= length + } + } + + tmp.fill(0) + return { key: key, iv: iv } +} + +module.exports = EVP_BytesToKey + +},{"md5.js":221,"safe-buffer":250}],107:[function(require,module,exports){ +'use strict'; + +var isCallable = require('is-callable'); + +var toStr = Object.prototype.toString; +var hasOwnProperty = Object.prototype.hasOwnProperty; + +var forEachArray = function forEachArray(array, iterator, receiver) { + for (var i = 0, len = array.length; i < len; i++) { + if (hasOwnProperty.call(array, i)) { + if (receiver == null) { + iterator(array[i], i, array); + } else { + iterator.call(receiver, array[i], i, array); + } + } + } +}; + +var forEachString = function forEachString(string, iterator, receiver) { + for (var i = 0, len = string.length; i < len; i++) { + // no such thing as a sparse string. + if (receiver == null) { + iterator(string.charAt(i), i, string); + } else { + iterator.call(receiver, string.charAt(i), i, string); + } + } +}; + +var forEachObject = function forEachObject(object, iterator, receiver) { + for (var k in object) { + if (hasOwnProperty.call(object, k)) { + if (receiver == null) { + iterator(object[k], k, object); + } else { + iterator.call(receiver, object[k], k, object); + } + } + } +}; + +var forEach = function forEach(list, iterator, thisArg) { + if (!isCallable(iterator)) { + throw new TypeError('iterator must be a function'); + } + + var receiver; + if (arguments.length >= 3) { + receiver = thisArg; + } + + if (toStr.call(list) === '[object Array]') { + forEachArray(list, iterator, receiver); + } else if (typeof list === 'string') { + forEachString(list, iterator, receiver); + } else { + forEachObject(list, iterator, receiver); + } +}; + +module.exports = forEach; + +},{"is-callable":148}],108:[function(require,module,exports){ +'use strict'; + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + +},{}],109:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":108}],110:[function(require,module,exports){ +'use strict'; + +var undefined; + +var $SyntaxError = SyntaxError; +var $Function = Function; +var $TypeError = TypeError; + +// eslint-disable-next-line consistent-return +var getEvalledConstructor = function (expressionSyntax) { + try { + return $Function('"use strict"; return (' + expressionSyntax + ').constructor;')(); + } catch (e) {} +}; + +var $gOPD = Object.getOwnPropertyDescriptor; +if ($gOPD) { + try { + $gOPD({}, ''); + } catch (e) { + $gOPD = null; // this is IE 8, which has a broken gOPD + } +} + +var throwTypeError = function () { + throw new $TypeError(); +}; +var ThrowTypeError = $gOPD + ? (function () { + try { + // eslint-disable-next-line no-unused-expressions, no-caller, no-restricted-properties + arguments.callee; // IE 8 does not throw here + return throwTypeError; + } catch (calleeThrows) { + try { + // IE 8 throws on Object.getOwnPropertyDescriptor(arguments, '') + return $gOPD(arguments, 'callee').get; + } catch (gOPDthrows) { + return throwTypeError; + } + } + }()) + : throwTypeError; + +var hasSymbols = require('has-symbols')(); + +var getProto = Object.getPrototypeOf || function (x) { return x.__proto__; }; // eslint-disable-line no-proto + +var needsEval = {}; + +var TypedArray = typeof Uint8Array === 'undefined' ? undefined : getProto(Uint8Array); + +var INTRINSICS = { + '%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError, + '%Array%': Array, + '%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer, + '%ArrayIteratorPrototype%': hasSymbols ? getProto([][Symbol.iterator]()) : undefined, + '%AsyncFromSyncIteratorPrototype%': undefined, + '%AsyncFunction%': needsEval, + '%AsyncGenerator%': needsEval, + '%AsyncGeneratorFunction%': needsEval, + '%AsyncIteratorPrototype%': needsEval, + '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics, + '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt, + '%BigInt64Array%': typeof BigInt64Array === 'undefined' ? undefined : BigInt64Array, + '%BigUint64Array%': typeof BigUint64Array === 'undefined' ? undefined : BigUint64Array, + '%Boolean%': Boolean, + '%DataView%': typeof DataView === 'undefined' ? undefined : DataView, + '%Date%': Date, + '%decodeURI%': decodeURI, + '%decodeURIComponent%': decodeURIComponent, + '%encodeURI%': encodeURI, + '%encodeURIComponent%': encodeURIComponent, + '%Error%': Error, + '%eval%': eval, // eslint-disable-line no-eval + '%EvalError%': EvalError, + '%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array, + '%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array, + '%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry, + '%Function%': $Function, + '%GeneratorFunction%': needsEval, + '%Int8Array%': typeof Int8Array === 'undefined' ? undefined : Int8Array, + '%Int16Array%': typeof Int16Array === 'undefined' ? undefined : Int16Array, + '%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array, + '%isFinite%': isFinite, + '%isNaN%': isNaN, + '%IteratorPrototype%': hasSymbols ? getProto(getProto([][Symbol.iterator]())) : undefined, + '%JSON%': typeof JSON === 'object' ? JSON : undefined, + '%Map%': typeof Map === 'undefined' ? undefined : Map, + '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols ? undefined : getProto(new Map()[Symbol.iterator]()), + '%Math%': Math, + '%Number%': Number, + '%Object%': Object, + '%parseFloat%': parseFloat, + '%parseInt%': parseInt, + '%Promise%': typeof Promise === 'undefined' ? undefined : Promise, + '%Proxy%': typeof Proxy === 'undefined' ? undefined : Proxy, + '%RangeError%': RangeError, + '%ReferenceError%': ReferenceError, + '%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect, + '%RegExp%': RegExp, + '%Set%': typeof Set === 'undefined' ? undefined : Set, + '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols ? undefined : getProto(new Set()[Symbol.iterator]()), + '%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer, + '%String%': String, + '%StringIteratorPrototype%': hasSymbols ? getProto(''[Symbol.iterator]()) : undefined, + '%Symbol%': hasSymbols ? Symbol : undefined, + '%SyntaxError%': $SyntaxError, + '%ThrowTypeError%': ThrowTypeError, + '%TypedArray%': TypedArray, + '%TypeError%': $TypeError, + '%Uint8Array%': typeof Uint8Array === 'undefined' ? undefined : Uint8Array, + '%Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined : Uint8ClampedArray, + '%Uint16Array%': typeof Uint16Array === 'undefined' ? undefined : Uint16Array, + '%Uint32Array%': typeof Uint32Array === 'undefined' ? undefined : Uint32Array, + '%URIError%': URIError, + '%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap, + '%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef, + '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet +}; + +try { + null.error; // eslint-disable-line no-unused-expressions +} catch (e) { + // https://github.com/tc39/proposal-shadowrealm/pull/384#issuecomment-1364264229 + var errorProto = getProto(getProto(e)); + INTRINSICS['%Error.prototype%'] = errorProto; +} + +var doEval = function doEval(name) { + var value; + if (name === '%AsyncFunction%') { + value = getEvalledConstructor('async function () {}'); + } else if (name === '%GeneratorFunction%') { + value = getEvalledConstructor('function* () {}'); + } else if (name === '%AsyncGeneratorFunction%') { + value = getEvalledConstructor('async function* () {}'); + } else if (name === '%AsyncGenerator%') { + var fn = doEval('%AsyncGeneratorFunction%'); + if (fn) { + value = fn.prototype; + } + } else if (name === '%AsyncIteratorPrototype%') { + var gen = doEval('%AsyncGenerator%'); + if (gen) { + value = getProto(gen.prototype); + } + } + + INTRINSICS[name] = value; + + return value; +}; + +var LEGACY_ALIASES = { + '%ArrayBufferPrototype%': ['ArrayBuffer', 'prototype'], + '%ArrayPrototype%': ['Array', 'prototype'], + '%ArrayProto_entries%': ['Array', 'prototype', 'entries'], + '%ArrayProto_forEach%': ['Array', 'prototype', 'forEach'], + '%ArrayProto_keys%': ['Array', 'prototype', 'keys'], + '%ArrayProto_values%': ['Array', 'prototype', 'values'], + '%AsyncFunctionPrototype%': ['AsyncFunction', 'prototype'], + '%AsyncGenerator%': ['AsyncGeneratorFunction', 'prototype'], + '%AsyncGeneratorPrototype%': ['AsyncGeneratorFunction', 'prototype', 'prototype'], + '%BooleanPrototype%': ['Boolean', 'prototype'], + '%DataViewPrototype%': ['DataView', 'prototype'], + '%DatePrototype%': ['Date', 'prototype'], + '%ErrorPrototype%': ['Error', 'prototype'], + '%EvalErrorPrototype%': ['EvalError', 'prototype'], + '%Float32ArrayPrototype%': ['Float32Array', 'prototype'], + '%Float64ArrayPrototype%': ['Float64Array', 'prototype'], + '%FunctionPrototype%': ['Function', 'prototype'], + '%Generator%': ['GeneratorFunction', 'prototype'], + '%GeneratorPrototype%': ['GeneratorFunction', 'prototype', 'prototype'], + '%Int8ArrayPrototype%': ['Int8Array', 'prototype'], + '%Int16ArrayPrototype%': ['Int16Array', 'prototype'], + '%Int32ArrayPrototype%': ['Int32Array', 'prototype'], + '%JSONParse%': ['JSON', 'parse'], + '%JSONStringify%': ['JSON', 'stringify'], + '%MapPrototype%': ['Map', 'prototype'], + '%NumberPrototype%': ['Number', 'prototype'], + '%ObjectPrototype%': ['Object', 'prototype'], + '%ObjProto_toString%': ['Object', 'prototype', 'toString'], + '%ObjProto_valueOf%': ['Object', 'prototype', 'valueOf'], + '%PromisePrototype%': ['Promise', 'prototype'], + '%PromiseProto_then%': ['Promise', 'prototype', 'then'], + '%Promise_all%': ['Promise', 'all'], + '%Promise_reject%': ['Promise', 'reject'], + '%Promise_resolve%': ['Promise', 'resolve'], + '%RangeErrorPrototype%': ['RangeError', 'prototype'], + '%ReferenceErrorPrototype%': ['ReferenceError', 'prototype'], + '%RegExpPrototype%': ['RegExp', 'prototype'], + '%SetPrototype%': ['Set', 'prototype'], + '%SharedArrayBufferPrototype%': ['SharedArrayBuffer', 'prototype'], + '%StringPrototype%': ['String', 'prototype'], + '%SymbolPrototype%': ['Symbol', 'prototype'], + '%SyntaxErrorPrototype%': ['SyntaxError', 'prototype'], + '%TypedArrayPrototype%': ['TypedArray', 'prototype'], + '%TypeErrorPrototype%': ['TypeError', 'prototype'], + '%Uint8ArrayPrototype%': ['Uint8Array', 'prototype'], + '%Uint8ClampedArrayPrototype%': ['Uint8ClampedArray', 'prototype'], + '%Uint16ArrayPrototype%': ['Uint16Array', 'prototype'], + '%Uint32ArrayPrototype%': ['Uint32Array', 'prototype'], + '%URIErrorPrototype%': ['URIError', 'prototype'], + '%WeakMapPrototype%': ['WeakMap', 'prototype'], + '%WeakSetPrototype%': ['WeakSet', 'prototype'] +}; + +var bind = require('function-bind'); +var hasOwn = require('has'); +var $concat = bind.call(Function.call, Array.prototype.concat); +var $spliceApply = bind.call(Function.apply, Array.prototype.splice); +var $replace = bind.call(Function.call, String.prototype.replace); +var $strSlice = bind.call(Function.call, String.prototype.slice); +var $exec = bind.call(Function.call, RegExp.prototype.exec); + +/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */ +var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g; +var reEscapeChar = /\\(\\)?/g; /** Used to match backslashes in property paths. */ +var stringToPath = function stringToPath(string) { + var first = $strSlice(string, 0, 1); + var last = $strSlice(string, -1); + if (first === '%' && last !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected closing `%`'); + } else if (last === '%' && first !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected opening `%`'); + } + var result = []; + $replace(string, rePropName, function (match, number, quote, subString) { + result[result.length] = quote ? $replace(subString, reEscapeChar, '$1') : number || match; + }); + return result; +}; +/* end adaptation */ + +var getBaseIntrinsic = function getBaseIntrinsic(name, allowMissing) { + var intrinsicName = name; + var alias; + if (hasOwn(LEGACY_ALIASES, intrinsicName)) { + alias = LEGACY_ALIASES[intrinsicName]; + intrinsicName = '%' + alias[0] + '%'; + } + + if (hasOwn(INTRINSICS, intrinsicName)) { + var value = INTRINSICS[intrinsicName]; + if (value === needsEval) { + value = doEval(intrinsicName); + } + if (typeof value === 'undefined' && !allowMissing) { + throw new $TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!'); + } + + return { + alias: alias, + name: intrinsicName, + value: value + }; + } + + throw new $SyntaxError('intrinsic ' + name + ' does not exist!'); +}; + +module.exports = function GetIntrinsic(name, allowMissing) { + if (typeof name !== 'string' || name.length === 0) { + throw new $TypeError('intrinsic name must be a non-empty string'); + } + if (arguments.length > 1 && typeof allowMissing !== 'boolean') { + throw new $TypeError('"allowMissing" argument must be a boolean'); + } + + if ($exec(/^%?[^%]*%?$/, name) === null) { + throw new $SyntaxError('`%` may not be present anywhere but at the beginning and end of the intrinsic name'); + } + var parts = stringToPath(name); + var intrinsicBaseName = parts.length > 0 ? parts[0] : ''; + + var intrinsic = getBaseIntrinsic('%' + intrinsicBaseName + '%', allowMissing); + var intrinsicRealName = intrinsic.name; + var value = intrinsic.value; + var skipFurtherCaching = false; + + var alias = intrinsic.alias; + if (alias) { + intrinsicBaseName = alias[0]; + $spliceApply(parts, $concat([0, 1], alias)); + } + + for (var i = 1, isOwn = true; i < parts.length; i += 1) { + var part = parts[i]; + var first = $strSlice(part, 0, 1); + var last = $strSlice(part, -1); + if ( + ( + (first === '"' || first === "'" || first === '`') + || (last === '"' || last === "'" || last === '`') + ) + && first !== last + ) { + throw new $SyntaxError('property names with quotes must have matching quotes'); + } + if (part === 'constructor' || !isOwn) { + skipFurtherCaching = true; + } + + intrinsicBaseName += '.' + part; + intrinsicRealName = '%' + intrinsicBaseName + '%'; + + if (hasOwn(INTRINSICS, intrinsicRealName)) { + value = INTRINSICS[intrinsicRealName]; + } else if (value != null) { + if (!(part in value)) { + if (!allowMissing) { + throw new $TypeError('base intrinsic for ' + name + ' exists, but the property is not available.'); + } + return void undefined; + } + if ($gOPD && (i + 1) >= parts.length) { + var desc = $gOPD(value, part); + isOwn = !!desc; + + // By convention, when a data property is converted to an accessor + // property to emulate a data property that does not suffer from + // the override mistake, that accessor's getter is marked with + // an `originalValue` property. Here, when we detect this, we + // uphold the illusion by pretending to see that original data + // property, i.e., returning the value rather than the getter + // itself. + if (isOwn && 'get' in desc && !('originalValue' in desc.get)) { + value = desc.get; + } else { + value = value[part]; + } + } else { + isOwn = hasOwn(value, part); + value = value[part]; + } + + if (isOwn && !skipFurtherCaching) { + INTRINSICS[intrinsicRealName] = value; + } + } + } + return value; +}; + +},{"function-bind":109,"has":115,"has-symbols":112}],111:[function(require,module,exports){ +'use strict'; + +var GetIntrinsic = require('get-intrinsic'); + +var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true); + +if ($gOPD) { + try { + $gOPD([], 'length'); + } catch (e) { + // IE 8 has a broken gOPD + $gOPD = null; + } +} + +module.exports = $gOPD; + +},{"get-intrinsic":110}],112:[function(require,module,exports){ +'use strict'; + +var origSymbol = typeof Symbol !== 'undefined' && Symbol; +var hasSymbolSham = require('./shams'); + +module.exports = function hasNativeSymbols() { + if (typeof origSymbol !== 'function') { return false; } + if (typeof Symbol !== 'function') { return false; } + if (typeof origSymbol('foo') !== 'symbol') { return false; } + if (typeof Symbol('bar') !== 'symbol') { return false; } + + return hasSymbolSham(); +}; + +},{"./shams":113}],113:[function(require,module,exports){ +'use strict'; + +/* eslint complexity: [2, 18], max-statements: [2, 33] */ +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } + + var obj = {}; + var sym = Symbol('test'); + var symObj = Object(sym); + if (typeof sym === 'string') { return false; } + + if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; } + if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; } + + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(symObj instanceof Symbol)) { return false; } + + // if (typeof Symbol.prototype.toString !== 'function') { return false; } + // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; } + + var symVal = 42; + obj[sym] = symVal; + for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } + + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } + + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } + + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } + + if (typeof Object.getOwnPropertyDescriptor === 'function') { + var descriptor = Object.getOwnPropertyDescriptor(obj, sym); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } + } + + return true; +}; + +},{}],114:[function(require,module,exports){ +'use strict'; + +var hasSymbols = require('has-symbols/shams'); + +module.exports = function hasToStringTagShams() { + return hasSymbols() && !!Symbol.toStringTag; +}; + +},{"has-symbols/shams":113}],115:[function(require,module,exports){ +'use strict'; + +var bind = require('function-bind'); + +module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty); + +},{"function-bind":109}],116:[function(require,module,exports){ +'use strict' +var Buffer = require('safe-buffer').Buffer +var Transform = require('readable-stream').Transform +var inherits = require('inherits') + +function throwIfNotStringOrBuffer (val, prefix) { + if (!Buffer.isBuffer(val) && typeof val !== 'string') { + throw new TypeError(prefix + ' must be a string or a buffer') + } +} + +function HashBase (blockSize) { + Transform.call(this) + + this._block = Buffer.allocUnsafe(blockSize) + this._blockSize = blockSize + this._blockOffset = 0 + this._length = [0, 0, 0, 0] + + this._finalized = false +} + +inherits(HashBase, Transform) + +HashBase.prototype._transform = function (chunk, encoding, callback) { + var error = null + try { + this.update(chunk, encoding) + } catch (err) { + error = err + } + + callback(error) +} + +HashBase.prototype._flush = function (callback) { + var error = null + try { + this.push(this.digest()) + } catch (err) { + error = err + } + + callback(error) +} + +HashBase.prototype.update = function (data, encoding) { + throwIfNotStringOrBuffer(data, 'Data') + if (this._finalized) throw new Error('Digest already called') + if (!Buffer.isBuffer(data)) data = Buffer.from(data, encoding) + + // consume data + var block = this._block + var offset = 0 + while (this._blockOffset + data.length - offset >= this._blockSize) { + for (var i = this._blockOffset; i < this._blockSize;) block[i++] = data[offset++] + this._update() + this._blockOffset = 0 + } + while (offset < data.length) block[this._blockOffset++] = data[offset++] + + // update length + for (var j = 0, carry = data.length * 8; carry > 0; ++j) { + this._length[j] += carry + carry = (this._length[j] / 0x0100000000) | 0 + if (carry > 0) this._length[j] -= 0x0100000000 * carry + } + + return this +} + +HashBase.prototype._update = function () { + throw new Error('_update is not implemented') +} + +HashBase.prototype.digest = function (encoding) { + if (this._finalized) throw new Error('Digest already called') + this._finalized = true + + var digest = this._digest() + if (encoding !== undefined) digest = digest.toString(encoding) + + // reset state + this._block.fill(0) + this._blockOffset = 0 + for (var i = 0; i < 4; ++i) this._length[i] = 0 + + return digest +} + +HashBase.prototype._digest = function () { + throw new Error('_digest is not implemented') +} + +module.exports = HashBase + +},{"inherits":146,"readable-stream":131,"safe-buffer":250}],117:[function(require,module,exports){ +arguments[4][50][0].apply(exports,arguments) +},{"dup":50}],118:[function(require,module,exports){ +(function (process){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. +'use strict'; +/**/ + +var objectKeys = Object.keys || function (obj) { + var keys = []; + + for (var key in obj) { + keys.push(key); + } + + return keys; +}; +/**/ + + +module.exports = Duplex; + +var Readable = require('./_stream_readable'); + +var Writable = require('./_stream_writable'); + +require('inherits')(Duplex, Readable); + +{ + // Allow the keys array to be GC'ed. + var keys = objectKeys(Writable.prototype); + + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + Readable.call(this, options); + Writable.call(this, options); + this.allowHalfOpen = true; + + if (options) { + if (options.readable === false) this.readable = false; + if (options.writable === false) this.writable = false; + + if (options.allowHalfOpen === false) { + this.allowHalfOpen = false; + this.once('end', onend); + } + } +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); +Object.defineProperty(Duplex.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); +Object.defineProperty(Duplex.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); // the no-half-open enforcer + +function onend() { + // If the writable side ended, then we're ok. + if (this._writableState.ended) return; // no more data can be written. + // But allow more writes to happen in this tick. + + process.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); +}).call(this)}).call(this,require('_process')) + +},{"./_stream_readable":120,"./_stream_writable":122,"_process":237,"inherits":146}],119:[function(require,module,exports){ +arguments[4][52][0].apply(exports,arguments) +},{"./_stream_transform":121,"dup":52,"inherits":146}],120:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +'use strict'; + +module.exports = Readable; +/**/ + +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; +/**/ + +var EE = require('events').EventEmitter; + +var EElistenerCount = function EElistenerCount(emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ + + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ + + +var debugUtil = require('util'); + +var debug; + +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function debug() {}; +} +/**/ + + +var BufferList = require('./internal/streams/buffer_list'); + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_STREAM_PUSH_AFTER_EOF = _require$codes.ERR_STREAM_PUSH_AFTER_EOF, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_UNSHIFT_AFTER_END_EVENT = _require$codes.ERR_STREAM_UNSHIFT_AFTER_END_EVENT; // Lazy loaded to improve the startup performance. + + +var StringDecoder; +var createReadableStreamAsyncIterator; +var from; + +require('inherits')(Readable, Stream); + +var errorOrDestroy = destroyImpl.errorOrDestroy; +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (Array.isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + + this.highWaterMark = getHighWaterMark(this, options, 'readableHighWaterMark', isDuplex); // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + + this.sync = true; // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + this.paused = true; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'end' (and potentially 'finish') + + this.autoDestroy = !!options.autoDestroy; // has it been destroyed + + this.destroyed = false; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // the number of writers that are awaiting a drain event in .pipe()s + + this.awaitDrain = 0; // if true, a maybeReadMore has been scheduled + + this.readingMore = false; + this.decoder = null; + this.encoding = null; + + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + if (!(this instanceof Readable)) return new Readable(options); // Checking for a Stream.Duplex instance is faster here instead of inside + // the ReadableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + this._readableState = new ReadableState(options, this, isDuplex); // legacy + + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined) { + return false; + } + + return this._readableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + } +}); +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; + +Readable.prototype._destroy = function (err, cb) { + cb(err); +}; // Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. + + +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; // Unshift should *always* be something directly out of read() + + +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + debug('readableAddChunk', chunk); + var state = stream._readableState; + + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + + if (er) { + errorOrDestroy(stream, er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) errorOrDestroy(stream, new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());else addChunk(stream, state, chunk, true); + } else if (state.ended) { + errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF()); + } else if (state.destroyed) { + return false; + } else { + state.reading = false; + + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + maybeReadMore(stream, state); + } + } // We can push more data if we are below the highWaterMark. + // Also, if we have no data yet, we can stand some more bytes. + // This is to work around cases where hwm=0, such as the repl. + + + return !state.ended && (state.length < state.highWaterMark || state.length === 0); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + state.awaitDrain = 0; + stream.emit('data', chunk); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + if (state.needReadable) emitReadable(stream); + } + + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer', 'Uint8Array'], chunk); + } + + return er; +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; // backwards compatibility. + + +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + var decoder = new StringDecoder(enc); + this._readableState.decoder = decoder; // If setEncoding(null), decoder.encoding equals utf8 + + this._readableState.encoding = this._readableState.decoder.encoding; // Iterate over current buffer to convert already stored Buffers: + + var p = this._readableState.buffer.head; + var content = ''; + + while (p !== null) { + content += decoder.write(p.data); + p = p.next; + } + + this._readableState.buffer.clear(); + + if (content !== '') this._readableState.buffer.push(content); + this._readableState.length = content.length; + return this; +}; // Don't raise the hwm > 1GB + + +var MAX_HWM = 0x40000000; + +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + // TODO(ronag): Throw ERR_VALUE_OUT_OF_RANGE. + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + + return n; +} // This function is designed to be inlinable, so please take care when making +// changes to the function body. + + +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } // If we're asking for more than the current hwm, then raise the hwm. + + + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; // Don't have enough + + if (!state.ended) { + state.needReadable = true; + return 0; + } + + return state.length; +} // you can override either this method, or the async _read(n) below. + + +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + if (n !== 0) state.emittedReadable = false; // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + + if (n === 0 && state.needReadable && ((state.highWaterMark !== 0 ? state.length >= state.highWaterMark : state.length > 0) || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); // if we've ended, and we're now clear, then finish it up. + + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + // if we need a readable event, then we need to do some reading. + + + var doRead = state.needReadable; + debug('need readable', doRead); // if we currently have less than the highWaterMark, then also read some + + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + + + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; // if the length is currently zero, then we *need* a readable event. + + if (state.length === 0) state.needReadable = true; // call internal read method + + this._read(state.highWaterMark); + + state.sync = false; // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = state.length <= state.highWaterMark; + n = 0; + } else { + state.length -= n; + state.awaitDrain = 0; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; // If we tried to read() past the EOF, then emit end on the next tick. + + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + return ret; +}; + +function onEofChunk(stream, state) { + debug('onEofChunk'); + if (state.ended) return; + + if (state.decoder) { + var chunk = state.decoder.end(); + + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + + state.ended = true; + + if (state.sync) { + // if we are sync, wait until next tick to emit the data. + // Otherwise we risk emitting data in the flow() + // the readable code triggers during a read() call + emitReadable(stream); + } else { + // emit 'readable' now to make sure it gets picked up. + state.needReadable = false; + + if (!state.emittedReadable) { + state.emittedReadable = true; + emitReadable_(stream); + } + } +} // Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. + + +function emitReadable(stream) { + var state = stream._readableState; + debug('emitReadable', state.needReadable, state.emittedReadable); + state.needReadable = false; + + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + process.nextTick(emitReadable_, stream); + } +} + +function emitReadable_(stream) { + var state = stream._readableState; + debug('emitReadable_', state.destroyed, state.length, state.ended); + + if (!state.destroyed && (state.length || state.ended)) { + stream.emit('readable'); + state.emittedReadable = false; + } // The stream needs another readable event if + // 1. It is not flowing, as the flow mechanism will take + // care of it. + // 2. It is not ended. + // 3. It is below the highWaterMark, so we can schedule + // another readable later. + + + state.needReadable = !state.flowing && !state.ended && state.length <= state.highWaterMark; + flow(stream); +} // at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. + + +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + process.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + // Attempt to read more data if we should. + // + // The conditions for reading more data are (one of): + // - Not enough data buffered (state.length < state.highWaterMark). The loop + // is responsible for filling the buffer with enough data if such data + // is available. If highWaterMark is 0 and we are not in the flowing mode + // we should _not_ attempt to buffer any extra data. We'll get more data + // when the stream consumer calls read() instead. + // - No data in the buffer, and the stream is in flowing mode. In this mode + // the loop below is responsible for ensuring read() is called. Failing to + // call read here would abort the flow and there's no other mechanism for + // continuing the flow if the stream consumer has just subscribed to the + // 'data' event. + // + // In addition to the above conditions to keep reading data, the following + // conditions prevent the data from being read: + // - The stream has ended (state.ended). + // - There is already a pending 'read' operation (state.reading). This is a + // case where the the stream has called the implementation defined _read() + // method, but they are processing the call asynchronously and have _not_ + // called push() with new data. In this case we skip performing more + // read()s. The execution ends in this method again after the _read() ends + // up calling push() with more data. + while (!state.reading && !state.ended && (state.length < state.highWaterMark || state.flowing && state.length === 0)) { + var len = state.length; + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) // didn't get any data, stop spinning. + break; + } + + state.readingMore = false; +} // abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. + + +Readable.prototype._read = function (n) { + errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED('_read()')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + + case 1: + state.pipes = [state.pipes, dest]; + break; + + default: + state.pipes.push(dest); + break; + } + + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) process.nextTick(endFn);else src.once('end', endFn); + dest.on('unpipe', onunpipe); + + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + + + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + var cleanedUp = false; + + function cleanup() { + debug('cleanup'); // cleanup event handlers once the pipe is broken + + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + cleanedUp = true; // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + src.on('data', ondata); + + function ondata(chunk) { + debug('ondata'); + var ret = dest.write(chunk); + debug('dest.write', ret); + + if (ret === false) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', state.awaitDrain); + state.awaitDrain++; + } + + src.pause(); + } + } // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + + + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) errorOrDestroy(dest, er); + } // Make sure our error handler is attached before userland ones. + + + prependListener(dest, 'error', onerror); // Both close and finish should trigger unpipe, but only once. + + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + + dest.once('close', onclose); + + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } // tell the dest that it's being piped to + + + dest.emit('pipe', src); // start the flow if it hasn't been started already. + + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function pipeOnDrainFunctionResult() { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { + hasUnpiped: false + }; // if we're not piping anywhere, then do nothing. + + if (state.pipesCount === 0) return this; // just one destination. most common case. + + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + if (!dest) dest = state.pipes; // got a match. + + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } // slow case. multiple pipe destinations. + + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, { + hasUnpiped: false + }); + } + + return this; + } // try to find the right one. + + + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + dest.emit('unpipe', this, unpipeInfo); + return this; +}; // set up data events if they are asked for +// Ensure readable listeners eventually get something + + +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + var state = this._readableState; + + if (ev === 'data') { + // update readableListening so that resume() may be a no-op + // a few lines down. This is needed to support once('readable'). + state.readableListening = this.listenerCount('readable') > 0; // Try start flowing on next tick if stream isn't explicitly paused + + if (state.flowing !== false) this.resume(); + } else if (ev === 'readable') { + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.flowing = false; + state.emittedReadable = false; + debug('on readable', state.length, state.reading); + + if (state.length) { + emitReadable(this); + } else if (!state.reading) { + process.nextTick(nReadingNextTick, this); + } + } + } + + return res; +}; + +Readable.prototype.addListener = Readable.prototype.on; + +Readable.prototype.removeListener = function (ev, fn) { + var res = Stream.prototype.removeListener.call(this, ev, fn); + + if (ev === 'readable') { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +Readable.prototype.removeAllListeners = function (ev) { + var res = Stream.prototype.removeAllListeners.apply(this, arguments); + + if (ev === 'readable' || ev === undefined) { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +function updateReadableListening(self) { + var state = self._readableState; + state.readableListening = self.listenerCount('readable') > 0; + + if (state.resumeScheduled && !state.paused) { + // flowing needs to be set to true now, otherwise + // the upcoming resume will not flow. + state.flowing = true; // crude way to check if we should resume + } else if (self.listenerCount('data') > 0) { + self.resume(); + } +} + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} // pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. + + +Readable.prototype.resume = function () { + var state = this._readableState; + + if (!state.flowing) { + debug('resume'); // we flow only if there is no one listening + // for readable, but we still have to call + // resume() + + state.flowing = !state.readableListening; + resume(this, state); + } + + state.paused = false; + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + process.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + debug('resume', state.reading); + + if (!state.reading) { + stream.read(0); + } + + state.resumeScheduled = false; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + + if (this._readableState.flowing !== false) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + + this._readableState.paused = true; + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + + while (state.flowing && stream.read() !== null) { + ; + } +} // wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. + + +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + stream.on('end', function () { + debug('wrapped end'); + + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); // don't skip over falsy values in objectMode + + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + + if (!ret) { + paused = true; + stream.pause(); + } + }); // proxy all the other methods. + // important when wrapping filters and duplexes. + + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function methodWrap(method) { + return function methodWrapReturnFunction() { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } // proxy certain important events. + + + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } // when we try to consume some more bytes, simply unpause the + // underlying stream. + + + this._read = function (n) { + debug('wrapped _read', n); + + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +if (typeof Symbol === 'function') { + Readable.prototype[Symbol.asyncIterator] = function () { + if (createReadableStreamAsyncIterator === undefined) { + createReadableStreamAsyncIterator = require('./internal/streams/async_iterator'); + } + + return createReadableStreamAsyncIterator(this); + }; +} + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.highWaterMark; + } +}); +Object.defineProperty(Readable.prototype, 'readableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState && this._readableState.buffer; + } +}); +Object.defineProperty(Readable.prototype, 'readableFlowing', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.flowing; + }, + set: function set(state) { + if (this._readableState) { + this._readableState.flowing = state; + } + } +}); // exposed for testing purposes only. + +Readable._fromList = fromList; +Object.defineProperty(Readable.prototype, 'readableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.length; + } +}); // Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. + +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.first();else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = state.buffer.consume(n, state.decoder); + } + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + debug('endReadable', state.endEmitted); + + if (!state.endEmitted) { + state.ended = true; + process.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + debug('endReadableNT', state.endEmitted, state.length); // Check that we didn't get one last unshift. + + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the writable side is ready for autoDestroy as well + var wState = stream._writableState; + + if (!wState || wState.autoDestroy && wState.finished) { + stream.destroy(); + } + } + } +} + +if (typeof Symbol === 'function') { + Readable.from = function (iterable, opts) { + if (from === undefined) { + from = require('./internal/streams/from'); + } + + return from(Readable, iterable, opts); + }; +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + + return -1; +} +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":117,"./_stream_duplex":118,"./internal/streams/async_iterator":123,"./internal/streams/buffer_list":124,"./internal/streams/destroy":125,"./internal/streams/from":127,"./internal/streams/state":129,"./internal/streams/stream":130,"_process":237,"buffer":68,"events":105,"inherits":146,"string_decoder/":279,"util":20}],121:[function(require,module,exports){ +arguments[4][54][0].apply(exports,arguments) +},{"../errors":117,"./_stream_duplex":118,"dup":54,"inherits":146}],122:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. +'use strict'; + +module.exports = Writable; +/* */ + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} // It seems a linked list but it is not +// there will be only 2 of these for each stream + + +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ + + +var Duplex; +/**/ + +Writable.WritableState = WritableState; +/**/ + +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK, + ERR_STREAM_CANNOT_PIPE = _require$codes.ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES = _require$codes.ERR_STREAM_NULL_VALUES, + ERR_STREAM_WRITE_AFTER_END = _require$codes.ERR_STREAM_WRITE_AFTER_END, + ERR_UNKNOWN_ENCODING = _require$codes.ERR_UNKNOWN_ENCODING; + +var errorOrDestroy = destroyImpl.errorOrDestroy; + +require('inherits')(Writable, Stream); + +function nop() {} + +function WritableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream, + // e.g. options.readableObjectMode vs. options.writableObjectMode, etc. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag to indicate whether or not this stream + // contains buffers or objects. + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + + this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex); // if _final has been called + + this.finalCalled = false; // drain event flag. + + this.needDrain = false; // at the start of calling end() + + this.ending = false; // when end() has been called, and returned + + this.ended = false; // when 'finish' is emitted + + this.finished = false; // has it been destroyed + + this.destroyed = false; // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + + this.length = 0; // a flag to see when we're in the middle of a write. + + this.writing = false; // when true all writes will be buffered until .uncork() call + + this.corked = 0; // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + + this.sync = true; // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + + this.bufferProcessing = false; // the callback that's passed to _write(chunk,cb) + + this.onwrite = function (er) { + onwrite(stream, er); + }; // the callback that the user supplies to write(chunk,encoding,cb) + + + this.writecb = null; // the amount that is being written when _write is called. + + this.writelen = 0; + this.bufferedRequest = null; + this.lastBufferedRequest = null; // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + + this.pendingcb = 0; // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + + this.prefinished = false; // True if the error was already emitted and should not be thrown again + + this.errorEmitted = false; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'finish' (and potentially 'end') + + this.autoDestroy = !!options.autoDestroy; // count buffered requests + + this.bufferedRequestCount = 0; // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + + while (current) { + out.push(current); + current = current.next; + } + + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function writableStateBufferGetter() { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); // Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. + + +var realHasInstance; + +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function value(object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function realHasInstance(object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + // Checking for a Stream.Duplex instance is faster here instead of inside + // the WritableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options); + this._writableState = new WritableState(options, this, isDuplex); // legacy. + + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + if (typeof options.writev === 'function') this._writev = options.writev; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} // Otherwise people can pipe Writable streams, which is just wrong. + + +Writable.prototype.pipe = function () { + errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE()); +}; + +function writeAfterEnd(stream, cb) { + var er = new ERR_STREAM_WRITE_AFTER_END(); // TODO: defer error events consistently everywhere, not just the cb + + errorOrDestroy(stream, er); + process.nextTick(cb, er); +} // Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. + + +function validChunk(stream, state, chunk, cb) { + var er; + + if (chunk === null) { + er = new ERR_STREAM_NULL_VALUES(); + } else if (typeof chunk !== 'string' && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk); + } + + if (er) { + errorOrDestroy(stream, er); + process.nextTick(cb, er); + return false; + } + + return true; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + if (typeof cb !== 'function') cb = nop; + if (state.ending) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + return ret; +}; + +Writable.prototype.cork = function () { + this._writableState.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new ERR_UNKNOWN_ENCODING(encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); // if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. + +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + + var len = state.objectMode ? 1 : chunk.length; + state.length += len; + var ret = state.length < state.highWaterMark; // we must ensure that previous needDrain will not be reset to false. + + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (state.destroyed) state.onwrite(new ERR_STREAM_DESTROYED('write'));else if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + process.nextTick(cb, er); // this can emit finish, and it will always happen + // after error + + process.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); // this can emit finish, but finish must + // always follow error + + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + if (typeof cb !== 'function') throw new ERR_MULTIPLE_CALLBACK(); + onwriteStateUpdate(state); + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state) || stream.destroyed; + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + process.nextTick(afterWrite, stream, state, finished, cb); + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} // Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. + + +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} // if there's something in the buffer waiting, then process it + + +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + var count = 0; + var allBuffers = true; + + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + + buffer.allBuffers = allBuffers; + doWrite(stream, state, true, state.length, buffer, '', holder.finish); // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + + state.pendingcb++; + state.lastBufferedRequest = null; + + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new ERR_METHOD_NOT_IMPLEMENTED('_write()')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); // .end() fully uncorks + + if (state.corked) { + state.corked = 1; + this.uncork(); + } // ignore unnecessary end() calls. + + + if (!state.ending) endWritable(this, state, cb); + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} + +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + + if (err) { + errorOrDestroy(stream, err); + } + + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} + +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function' && !state.destroyed) { + state.pendingcb++; + state.finalCalled = true; + process.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + + if (need) { + prefinish(stream, state); + + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the readable side is ready for autoDestroy as well + var rState = stream._readableState; + + if (!rState || rState.autoDestroy && rState.endEmitted) { + stream.destroy(); + } + } + } + } + + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + + if (cb) { + if (state.finished) process.nextTick(cb);else stream.once('finish', cb); + } + + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } // reuse the free corkReq. + + + state.corkedRequestsFree.next = corkReq; +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._writableState === undefined) { + return false; + } + + return this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._writableState.destroyed = value; + } +}); +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; + +Writable.prototype._destroy = function (err, cb) { + cb(err); +}; +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":117,"./_stream_duplex":118,"./internal/streams/destroy":125,"./internal/streams/state":129,"./internal/streams/stream":130,"_process":237,"buffer":68,"inherits":146,"util-deprecate":283}],123:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; + +var _Object$setPrototypeO; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var finished = require('./end-of-stream'); + +var kLastResolve = Symbol('lastResolve'); +var kLastReject = Symbol('lastReject'); +var kError = Symbol('error'); +var kEnded = Symbol('ended'); +var kLastPromise = Symbol('lastPromise'); +var kHandlePromise = Symbol('handlePromise'); +var kStream = Symbol('stream'); + +function createIterResult(value, done) { + return { + value: value, + done: done + }; +} + +function readAndResolve(iter) { + var resolve = iter[kLastResolve]; + + if (resolve !== null) { + var data = iter[kStream].read(); // we defer if data is null + // we can be expecting either 'end' or + // 'error' + + if (data !== null) { + iter[kLastPromise] = null; + iter[kLastResolve] = null; + iter[kLastReject] = null; + resolve(createIterResult(data, false)); + } + } +} + +function onReadable(iter) { + // we wait for the next tick, because it might + // emit an error with process.nextTick + process.nextTick(readAndResolve, iter); +} + +function wrapForNext(lastPromise, iter) { + return function (resolve, reject) { + lastPromise.then(function () { + if (iter[kEnded]) { + resolve(createIterResult(undefined, true)); + return; + } + + iter[kHandlePromise](resolve, reject); + }, reject); + }; +} + +var AsyncIteratorPrototype = Object.getPrototypeOf(function () {}); +var ReadableStreamAsyncIteratorPrototype = Object.setPrototypeOf((_Object$setPrototypeO = { + get stream() { + return this[kStream]; + }, + + next: function next() { + var _this = this; + + // if we have detected an error in the meanwhile + // reject straight away + var error = this[kError]; + + if (error !== null) { + return Promise.reject(error); + } + + if (this[kEnded]) { + return Promise.resolve(createIterResult(undefined, true)); + } + + if (this[kStream].destroyed) { + // We need to defer via nextTick because if .destroy(err) is + // called, the error will be emitted via nextTick, and + // we cannot guarantee that there is no error lingering around + // waiting to be emitted. + return new Promise(function (resolve, reject) { + process.nextTick(function () { + if (_this[kError]) { + reject(_this[kError]); + } else { + resolve(createIterResult(undefined, true)); + } + }); + }); + } // if we have multiple next() calls + // we will wait for the previous Promise to finish + // this logic is optimized to support for await loops, + // where next() is only called once at a time + + + var lastPromise = this[kLastPromise]; + var promise; + + if (lastPromise) { + promise = new Promise(wrapForNext(lastPromise, this)); + } else { + // fast path needed to support multiple this.push() + // without triggering the next() queue + var data = this[kStream].read(); + + if (data !== null) { + return Promise.resolve(createIterResult(data, false)); + } + + promise = new Promise(this[kHandlePromise]); + } + + this[kLastPromise] = promise; + return promise; + } +}, _defineProperty(_Object$setPrototypeO, Symbol.asyncIterator, function () { + return this; +}), _defineProperty(_Object$setPrototypeO, "return", function _return() { + var _this2 = this; + + // destroy(err, cb) is a private API + // we can guarantee we have that here, because we control the + // Readable class this is attached to + return new Promise(function (resolve, reject) { + _this2[kStream].destroy(null, function (err) { + if (err) { + reject(err); + return; + } + + resolve(createIterResult(undefined, true)); + }); + }); +}), _Object$setPrototypeO), AsyncIteratorPrototype); + +var createReadableStreamAsyncIterator = function createReadableStreamAsyncIterator(stream) { + var _Object$create; + + var iterator = Object.create(ReadableStreamAsyncIteratorPrototype, (_Object$create = {}, _defineProperty(_Object$create, kStream, { + value: stream, + writable: true + }), _defineProperty(_Object$create, kLastResolve, { + value: null, + writable: true + }), _defineProperty(_Object$create, kLastReject, { + value: null, + writable: true + }), _defineProperty(_Object$create, kError, { + value: null, + writable: true + }), _defineProperty(_Object$create, kEnded, { + value: stream._readableState.endEmitted, + writable: true + }), _defineProperty(_Object$create, kHandlePromise, { + value: function value(resolve, reject) { + var data = iterator[kStream].read(); + + if (data) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(data, false)); + } else { + iterator[kLastResolve] = resolve; + iterator[kLastReject] = reject; + } + }, + writable: true + }), _Object$create)); + iterator[kLastPromise] = null; + finished(stream, function (err) { + if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { + var reject = iterator[kLastReject]; // reject if we are waiting for data in the Promise + // returned by next() and store the error + + if (reject !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + reject(err); + } + + iterator[kError] = err; + return; + } + + var resolve = iterator[kLastResolve]; + + if (resolve !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(undefined, true)); + } + + iterator[kEnded] = true; + }); + stream.on('readable', onReadable.bind(null, iterator)); + return iterator; +}; + +module.exports = createReadableStreamAsyncIterator; +}).call(this)}).call(this,require('_process')) + +},{"./end-of-stream":126,"_process":237}],124:[function(require,module,exports){ +arguments[4][57][0].apply(exports,arguments) +},{"buffer":68,"dup":57,"util":20}],125:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; // undocumented cb() API, needed for core, not for public API + +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err) { + if (!this._writableState) { + process.nextTick(emitErrorNT, this, err); + } else if (!this._writableState.errorEmitted) { + this._writableState.errorEmitted = true; + process.nextTick(emitErrorNT, this, err); + } + } + + return this; + } // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + + if (this._readableState) { + this._readableState.destroyed = true; + } // if this is a duplex stream mark the writable part as destroyed as well + + + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + if (!_this._writableState) { + process.nextTick(emitErrorAndCloseNT, _this, err); + } else if (!_this._writableState.errorEmitted) { + _this._writableState.errorEmitted = true; + process.nextTick(emitErrorAndCloseNT, _this, err); + } else { + process.nextTick(emitCloseNT, _this); + } + } else if (cb) { + process.nextTick(emitCloseNT, _this); + cb(err); + } else { + process.nextTick(emitCloseNT, _this); + } + }); + + return this; +} + +function emitErrorAndCloseNT(self, err) { + emitErrorNT(self, err); + emitCloseNT(self); +} + +function emitCloseNT(self) { + if (self._writableState && !self._writableState.emitClose) return; + if (self._readableState && !self._readableState.emitClose) return; + self.emit('close'); +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finalCalled = false; + this._writableState.prefinished = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +function errorOrDestroy(stream, err) { + // We have tests that rely on errors being emitted + // in the same tick, so changing this is semver major. + // For now when you opt-in to autoDestroy we allow + // the error to be emitted nextTick. In a future + // semver major update we should change the default to this. + var rState = stream._readableState; + var wState = stream._writableState; + if (rState && rState.autoDestroy || wState && wState.autoDestroy) stream.destroy(err);else stream.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy, + errorOrDestroy: errorOrDestroy +}; +}).call(this)}).call(this,require('_process')) + +},{"_process":237}],126:[function(require,module,exports){ +arguments[4][59][0].apply(exports,arguments) +},{"../../../errors":117,"dup":59}],127:[function(require,module,exports){ +arguments[4][60][0].apply(exports,arguments) +},{"dup":60}],128:[function(require,module,exports){ +arguments[4][61][0].apply(exports,arguments) +},{"../../../errors":117,"./end-of-stream":126,"dup":61}],129:[function(require,module,exports){ +arguments[4][62][0].apply(exports,arguments) +},{"../../../errors":117,"dup":62}],130:[function(require,module,exports){ +arguments[4][63][0].apply(exports,arguments) +},{"dup":63,"events":105}],131:[function(require,module,exports){ +arguments[4][64][0].apply(exports,arguments) +},{"./lib/_stream_duplex.js":118,"./lib/_stream_passthrough.js":119,"./lib/_stream_readable.js":120,"./lib/_stream_transform.js":121,"./lib/_stream_writable.js":122,"./lib/internal/streams/end-of-stream.js":126,"./lib/internal/streams/pipeline.js":128,"dup":64}],132:[function(require,module,exports){ +var hash = exports; + +hash.utils = require('./hash/utils'); +hash.common = require('./hash/common'); +hash.sha = require('./hash/sha'); +hash.ripemd = require('./hash/ripemd'); +hash.hmac = require('./hash/hmac'); + +// Proxy hash functions to the main object +hash.sha1 = hash.sha.sha1; +hash.sha256 = hash.sha.sha256; +hash.sha224 = hash.sha.sha224; +hash.sha384 = hash.sha.sha384; +hash.sha512 = hash.sha.sha512; +hash.ripemd160 = hash.ripemd.ripemd160; + +},{"./hash/common":133,"./hash/hmac":134,"./hash/ripemd":135,"./hash/sha":136,"./hash/utils":143}],133:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var assert = require('minimalistic-assert'); + +function BlockHash() { + this.pending = null; + this.pendingTotal = 0; + this.blockSize = this.constructor.blockSize; + this.outSize = this.constructor.outSize; + this.hmacStrength = this.constructor.hmacStrength; + this.padLength = this.constructor.padLength / 8; + this.endian = 'big'; + + this._delta8 = this.blockSize / 8; + this._delta32 = this.blockSize / 32; +} +exports.BlockHash = BlockHash; + +BlockHash.prototype.update = function update(msg, enc) { + // Convert message to array, pad it, and join into 32bit blocks + msg = utils.toArray(msg, enc); + if (!this.pending) + this.pending = msg; + else + this.pending = this.pending.concat(msg); + this.pendingTotal += msg.length; + + // Enough data, try updating + if (this.pending.length >= this._delta8) { + msg = this.pending; + + // Process pending data in blocks + var r = msg.length % this._delta8; + this.pending = msg.slice(msg.length - r, msg.length); + if (this.pending.length === 0) + this.pending = null; + + msg = utils.join32(msg, 0, msg.length - r, this.endian); + for (var i = 0; i < msg.length; i += this._delta32) + this._update(msg, i, i + this._delta32); + } + + return this; +}; + +BlockHash.prototype.digest = function digest(enc) { + this.update(this._pad()); + assert(this.pending === null); + + return this._digest(enc); +}; + +BlockHash.prototype._pad = function pad() { + var len = this.pendingTotal; + var bytes = this._delta8; + var k = bytes - ((len + this.padLength) % bytes); + var res = new Array(k + this.padLength); + res[0] = 0x80; + for (var i = 1; i < k; i++) + res[i] = 0; + + // Append length + len <<= 3; + if (this.endian === 'big') { + for (var t = 8; t < this.padLength; t++) + res[i++] = 0; + + res[i++] = 0; + res[i++] = 0; + res[i++] = 0; + res[i++] = 0; + res[i++] = (len >>> 24) & 0xff; + res[i++] = (len >>> 16) & 0xff; + res[i++] = (len >>> 8) & 0xff; + res[i++] = len & 0xff; + } else { + res[i++] = len & 0xff; + res[i++] = (len >>> 8) & 0xff; + res[i++] = (len >>> 16) & 0xff; + res[i++] = (len >>> 24) & 0xff; + res[i++] = 0; + res[i++] = 0; + res[i++] = 0; + res[i++] = 0; + + for (t = 8; t < this.padLength; t++) + res[i++] = 0; + } + + return res; +}; + +},{"./utils":143,"minimalistic-assert":223}],134:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var assert = require('minimalistic-assert'); + +function Hmac(hash, key, enc) { + if (!(this instanceof Hmac)) + return new Hmac(hash, key, enc); + this.Hash = hash; + this.blockSize = hash.blockSize / 8; + this.outSize = hash.outSize / 8; + this.inner = null; + this.outer = null; + + this._init(utils.toArray(key, enc)); +} +module.exports = Hmac; + +Hmac.prototype._init = function init(key) { + // Shorten key, if needed + if (key.length > this.blockSize) + key = new this.Hash().update(key).digest(); + assert(key.length <= this.blockSize); + + // Add padding to key + for (var i = key.length; i < this.blockSize; i++) + key.push(0); + + for (i = 0; i < key.length; i++) + key[i] ^= 0x36; + this.inner = new this.Hash().update(key); + + // 0x36 ^ 0x5c = 0x6a + for (i = 0; i < key.length; i++) + key[i] ^= 0x6a; + this.outer = new this.Hash().update(key); +}; + +Hmac.prototype.update = function update(msg, enc) { + this.inner.update(msg, enc); + return this; +}; + +Hmac.prototype.digest = function digest(enc) { + this.outer.update(this.inner.digest()); + return this.outer.digest(enc); +}; + +},{"./utils":143,"minimalistic-assert":223}],135:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var common = require('./common'); + +var rotl32 = utils.rotl32; +var sum32 = utils.sum32; +var sum32_3 = utils.sum32_3; +var sum32_4 = utils.sum32_4; +var BlockHash = common.BlockHash; + +function RIPEMD160() { + if (!(this instanceof RIPEMD160)) + return new RIPEMD160(); + + BlockHash.call(this); + + this.h = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + this.endian = 'little'; +} +utils.inherits(RIPEMD160, BlockHash); +exports.ripemd160 = RIPEMD160; + +RIPEMD160.blockSize = 512; +RIPEMD160.outSize = 160; +RIPEMD160.hmacStrength = 192; +RIPEMD160.padLength = 64; + +RIPEMD160.prototype._update = function update(msg, start) { + var A = this.h[0]; + var B = this.h[1]; + var C = this.h[2]; + var D = this.h[3]; + var E = this.h[4]; + var Ah = A; + var Bh = B; + var Ch = C; + var Dh = D; + var Eh = E; + for (var j = 0; j < 80; j++) { + var T = sum32( + rotl32( + sum32_4(A, f(j, B, C, D), msg[r[j] + start], K(j)), + s[j]), + E); + A = E; + E = D; + D = rotl32(C, 10); + C = B; + B = T; + T = sum32( + rotl32( + sum32_4(Ah, f(79 - j, Bh, Ch, Dh), msg[rh[j] + start], Kh(j)), + sh[j]), + Eh); + Ah = Eh; + Eh = Dh; + Dh = rotl32(Ch, 10); + Ch = Bh; + Bh = T; + } + T = sum32_3(this.h[1], C, Dh); + this.h[1] = sum32_3(this.h[2], D, Eh); + this.h[2] = sum32_3(this.h[3], E, Ah); + this.h[3] = sum32_3(this.h[4], A, Bh); + this.h[4] = sum32_3(this.h[0], B, Ch); + this.h[0] = T; +}; + +RIPEMD160.prototype._digest = function digest(enc) { + if (enc === 'hex') + return utils.toHex32(this.h, 'little'); + else + return utils.split32(this.h, 'little'); +}; + +function f(j, x, y, z) { + if (j <= 15) + return x ^ y ^ z; + else if (j <= 31) + return (x & y) | ((~x) & z); + else if (j <= 47) + return (x | (~y)) ^ z; + else if (j <= 63) + return (x & z) | (y & (~z)); + else + return x ^ (y | (~z)); +} + +function K(j) { + if (j <= 15) + return 0x00000000; + else if (j <= 31) + return 0x5a827999; + else if (j <= 47) + return 0x6ed9eba1; + else if (j <= 63) + return 0x8f1bbcdc; + else + return 0xa953fd4e; +} + +function Kh(j) { + if (j <= 15) + return 0x50a28be6; + else if (j <= 31) + return 0x5c4dd124; + else if (j <= 47) + return 0x6d703ef3; + else if (j <= 63) + return 0x7a6d76e9; + else + return 0x00000000; +} + +var r = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +]; + +var rh = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +]; + +var s = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +]; + +var sh = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +]; + +},{"./common":133,"./utils":143}],136:[function(require,module,exports){ +'use strict'; + +exports.sha1 = require('./sha/1'); +exports.sha224 = require('./sha/224'); +exports.sha256 = require('./sha/256'); +exports.sha384 = require('./sha/384'); +exports.sha512 = require('./sha/512'); + +},{"./sha/1":137,"./sha/224":138,"./sha/256":139,"./sha/384":140,"./sha/512":141}],137:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var common = require('../common'); +var shaCommon = require('./common'); + +var rotl32 = utils.rotl32; +var sum32 = utils.sum32; +var sum32_5 = utils.sum32_5; +var ft_1 = shaCommon.ft_1; +var BlockHash = common.BlockHash; + +var sha1_K = [ + 0x5A827999, 0x6ED9EBA1, + 0x8F1BBCDC, 0xCA62C1D6 +]; + +function SHA1() { + if (!(this instanceof SHA1)) + return new SHA1(); + + BlockHash.call(this); + this.h = [ + 0x67452301, 0xefcdab89, 0x98badcfe, + 0x10325476, 0xc3d2e1f0 ]; + this.W = new Array(80); +} + +utils.inherits(SHA1, BlockHash); +module.exports = SHA1; + +SHA1.blockSize = 512; +SHA1.outSize = 160; +SHA1.hmacStrength = 80; +SHA1.padLength = 64; + +SHA1.prototype._update = function _update(msg, start) { + var W = this.W; + + for (var i = 0; i < 16; i++) + W[i] = msg[start + i]; + + for(; i < W.length; i++) + W[i] = rotl32(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); + + var a = this.h[0]; + var b = this.h[1]; + var c = this.h[2]; + var d = this.h[3]; + var e = this.h[4]; + + for (i = 0; i < W.length; i++) { + var s = ~~(i / 20); + var t = sum32_5(rotl32(a, 5), ft_1(s, b, c, d), e, W[i], sha1_K[s]); + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = t; + } + + this.h[0] = sum32(this.h[0], a); + this.h[1] = sum32(this.h[1], b); + this.h[2] = sum32(this.h[2], c); + this.h[3] = sum32(this.h[3], d); + this.h[4] = sum32(this.h[4], e); +}; + +SHA1.prototype._digest = function digest(enc) { + if (enc === 'hex') + return utils.toHex32(this.h, 'big'); + else + return utils.split32(this.h, 'big'); +}; + +},{"../common":133,"../utils":143,"./common":142}],138:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var SHA256 = require('./256'); + +function SHA224() { + if (!(this instanceof SHA224)) + return new SHA224(); + + SHA256.call(this); + this.h = [ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 ]; +} +utils.inherits(SHA224, SHA256); +module.exports = SHA224; + +SHA224.blockSize = 512; +SHA224.outSize = 224; +SHA224.hmacStrength = 192; +SHA224.padLength = 64; + +SHA224.prototype._digest = function digest(enc) { + // Just truncate output + if (enc === 'hex') + return utils.toHex32(this.h.slice(0, 7), 'big'); + else + return utils.split32(this.h.slice(0, 7), 'big'); +}; + + +},{"../utils":143,"./256":139}],139:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var common = require('../common'); +var shaCommon = require('./common'); +var assert = require('minimalistic-assert'); + +var sum32 = utils.sum32; +var sum32_4 = utils.sum32_4; +var sum32_5 = utils.sum32_5; +var ch32 = shaCommon.ch32; +var maj32 = shaCommon.maj32; +var s0_256 = shaCommon.s0_256; +var s1_256 = shaCommon.s1_256; +var g0_256 = shaCommon.g0_256; +var g1_256 = shaCommon.g1_256; + +var BlockHash = common.BlockHash; + +var sha256_K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +]; + +function SHA256() { + if (!(this instanceof SHA256)) + return new SHA256(); + + BlockHash.call(this); + this.h = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ]; + this.k = sha256_K; + this.W = new Array(64); +} +utils.inherits(SHA256, BlockHash); +module.exports = SHA256; + +SHA256.blockSize = 512; +SHA256.outSize = 256; +SHA256.hmacStrength = 192; +SHA256.padLength = 64; + +SHA256.prototype._update = function _update(msg, start) { + var W = this.W; + + for (var i = 0; i < 16; i++) + W[i] = msg[start + i]; + for (; i < W.length; i++) + W[i] = sum32_4(g1_256(W[i - 2]), W[i - 7], g0_256(W[i - 15]), W[i - 16]); + + var a = this.h[0]; + var b = this.h[1]; + var c = this.h[2]; + var d = this.h[3]; + var e = this.h[4]; + var f = this.h[5]; + var g = this.h[6]; + var h = this.h[7]; + + assert(this.k.length === W.length); + for (i = 0; i < W.length; i++) { + var T1 = sum32_5(h, s1_256(e), ch32(e, f, g), this.k[i], W[i]); + var T2 = sum32(s0_256(a), maj32(a, b, c)); + h = g; + g = f; + f = e; + e = sum32(d, T1); + d = c; + c = b; + b = a; + a = sum32(T1, T2); + } + + this.h[0] = sum32(this.h[0], a); + this.h[1] = sum32(this.h[1], b); + this.h[2] = sum32(this.h[2], c); + this.h[3] = sum32(this.h[3], d); + this.h[4] = sum32(this.h[4], e); + this.h[5] = sum32(this.h[5], f); + this.h[6] = sum32(this.h[6], g); + this.h[7] = sum32(this.h[7], h); +}; + +SHA256.prototype._digest = function digest(enc) { + if (enc === 'hex') + return utils.toHex32(this.h, 'big'); + else + return utils.split32(this.h, 'big'); +}; + +},{"../common":133,"../utils":143,"./common":142,"minimalistic-assert":223}],140:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); + +var SHA512 = require('./512'); + +function SHA384() { + if (!(this instanceof SHA384)) + return new SHA384(); + + SHA512.call(this); + this.h = [ + 0xcbbb9d5d, 0xc1059ed8, + 0x629a292a, 0x367cd507, + 0x9159015a, 0x3070dd17, + 0x152fecd8, 0xf70e5939, + 0x67332667, 0xffc00b31, + 0x8eb44a87, 0x68581511, + 0xdb0c2e0d, 0x64f98fa7, + 0x47b5481d, 0xbefa4fa4 ]; +} +utils.inherits(SHA384, SHA512); +module.exports = SHA384; + +SHA384.blockSize = 1024; +SHA384.outSize = 384; +SHA384.hmacStrength = 192; +SHA384.padLength = 128; + +SHA384.prototype._digest = function digest(enc) { + if (enc === 'hex') + return utils.toHex32(this.h.slice(0, 12), 'big'); + else + return utils.split32(this.h.slice(0, 12), 'big'); +}; + +},{"../utils":143,"./512":141}],141:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var common = require('../common'); +var assert = require('minimalistic-assert'); + +var rotr64_hi = utils.rotr64_hi; +var rotr64_lo = utils.rotr64_lo; +var shr64_hi = utils.shr64_hi; +var shr64_lo = utils.shr64_lo; +var sum64 = utils.sum64; +var sum64_hi = utils.sum64_hi; +var sum64_lo = utils.sum64_lo; +var sum64_4_hi = utils.sum64_4_hi; +var sum64_4_lo = utils.sum64_4_lo; +var sum64_5_hi = utils.sum64_5_hi; +var sum64_5_lo = utils.sum64_5_lo; + +var BlockHash = common.BlockHash; + +var sha512_K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, + 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, + 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, + 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, + 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, + 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, + 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, + 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, + 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, + 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, + 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, + 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, + 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, + 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +]; + +function SHA512() { + if (!(this instanceof SHA512)) + return new SHA512(); + + BlockHash.call(this); + this.h = [ + 0x6a09e667, 0xf3bcc908, + 0xbb67ae85, 0x84caa73b, + 0x3c6ef372, 0xfe94f82b, + 0xa54ff53a, 0x5f1d36f1, + 0x510e527f, 0xade682d1, + 0x9b05688c, 0x2b3e6c1f, + 0x1f83d9ab, 0xfb41bd6b, + 0x5be0cd19, 0x137e2179 ]; + this.k = sha512_K; + this.W = new Array(160); +} +utils.inherits(SHA512, BlockHash); +module.exports = SHA512; + +SHA512.blockSize = 1024; +SHA512.outSize = 512; +SHA512.hmacStrength = 192; +SHA512.padLength = 128; + +SHA512.prototype._prepareBlock = function _prepareBlock(msg, start) { + var W = this.W; + + // 32 x 32bit words + for (var i = 0; i < 32; i++) + W[i] = msg[start + i]; + for (; i < W.length; i += 2) { + var c0_hi = g1_512_hi(W[i - 4], W[i - 3]); // i - 2 + var c0_lo = g1_512_lo(W[i - 4], W[i - 3]); + var c1_hi = W[i - 14]; // i - 7 + var c1_lo = W[i - 13]; + var c2_hi = g0_512_hi(W[i - 30], W[i - 29]); // i - 15 + var c2_lo = g0_512_lo(W[i - 30], W[i - 29]); + var c3_hi = W[i - 32]; // i - 16 + var c3_lo = W[i - 31]; + + W[i] = sum64_4_hi( + c0_hi, c0_lo, + c1_hi, c1_lo, + c2_hi, c2_lo, + c3_hi, c3_lo); + W[i + 1] = sum64_4_lo( + c0_hi, c0_lo, + c1_hi, c1_lo, + c2_hi, c2_lo, + c3_hi, c3_lo); + } +}; + +SHA512.prototype._update = function _update(msg, start) { + this._prepareBlock(msg, start); + + var W = this.W; + + var ah = this.h[0]; + var al = this.h[1]; + var bh = this.h[2]; + var bl = this.h[3]; + var ch = this.h[4]; + var cl = this.h[5]; + var dh = this.h[6]; + var dl = this.h[7]; + var eh = this.h[8]; + var el = this.h[9]; + var fh = this.h[10]; + var fl = this.h[11]; + var gh = this.h[12]; + var gl = this.h[13]; + var hh = this.h[14]; + var hl = this.h[15]; + + assert(this.k.length === W.length); + for (var i = 0; i < W.length; i += 2) { + var c0_hi = hh; + var c0_lo = hl; + var c1_hi = s1_512_hi(eh, el); + var c1_lo = s1_512_lo(eh, el); + var c2_hi = ch64_hi(eh, el, fh, fl, gh, gl); + var c2_lo = ch64_lo(eh, el, fh, fl, gh, gl); + var c3_hi = this.k[i]; + var c3_lo = this.k[i + 1]; + var c4_hi = W[i]; + var c4_lo = W[i + 1]; + + var T1_hi = sum64_5_hi( + c0_hi, c0_lo, + c1_hi, c1_lo, + c2_hi, c2_lo, + c3_hi, c3_lo, + c4_hi, c4_lo); + var T1_lo = sum64_5_lo( + c0_hi, c0_lo, + c1_hi, c1_lo, + c2_hi, c2_lo, + c3_hi, c3_lo, + c4_hi, c4_lo); + + c0_hi = s0_512_hi(ah, al); + c0_lo = s0_512_lo(ah, al); + c1_hi = maj64_hi(ah, al, bh, bl, ch, cl); + c1_lo = maj64_lo(ah, al, bh, bl, ch, cl); + + var T2_hi = sum64_hi(c0_hi, c0_lo, c1_hi, c1_lo); + var T2_lo = sum64_lo(c0_hi, c0_lo, c1_hi, c1_lo); + + hh = gh; + hl = gl; + + gh = fh; + gl = fl; + + fh = eh; + fl = el; + + eh = sum64_hi(dh, dl, T1_hi, T1_lo); + el = sum64_lo(dl, dl, T1_hi, T1_lo); + + dh = ch; + dl = cl; + + ch = bh; + cl = bl; + + bh = ah; + bl = al; + + ah = sum64_hi(T1_hi, T1_lo, T2_hi, T2_lo); + al = sum64_lo(T1_hi, T1_lo, T2_hi, T2_lo); + } + + sum64(this.h, 0, ah, al); + sum64(this.h, 2, bh, bl); + sum64(this.h, 4, ch, cl); + sum64(this.h, 6, dh, dl); + sum64(this.h, 8, eh, el); + sum64(this.h, 10, fh, fl); + sum64(this.h, 12, gh, gl); + sum64(this.h, 14, hh, hl); +}; + +SHA512.prototype._digest = function digest(enc) { + if (enc === 'hex') + return utils.toHex32(this.h, 'big'); + else + return utils.split32(this.h, 'big'); +}; + +function ch64_hi(xh, xl, yh, yl, zh) { + var r = (xh & yh) ^ ((~xh) & zh); + if (r < 0) + r += 0x100000000; + return r; +} + +function ch64_lo(xh, xl, yh, yl, zh, zl) { + var r = (xl & yl) ^ ((~xl) & zl); + if (r < 0) + r += 0x100000000; + return r; +} + +function maj64_hi(xh, xl, yh, yl, zh) { + var r = (xh & yh) ^ (xh & zh) ^ (yh & zh); + if (r < 0) + r += 0x100000000; + return r; +} + +function maj64_lo(xh, xl, yh, yl, zh, zl) { + var r = (xl & yl) ^ (xl & zl) ^ (yl & zl); + if (r < 0) + r += 0x100000000; + return r; +} + +function s0_512_hi(xh, xl) { + var c0_hi = rotr64_hi(xh, xl, 28); + var c1_hi = rotr64_hi(xl, xh, 2); // 34 + var c2_hi = rotr64_hi(xl, xh, 7); // 39 + + var r = c0_hi ^ c1_hi ^ c2_hi; + if (r < 0) + r += 0x100000000; + return r; +} + +function s0_512_lo(xh, xl) { + var c0_lo = rotr64_lo(xh, xl, 28); + var c1_lo = rotr64_lo(xl, xh, 2); // 34 + var c2_lo = rotr64_lo(xl, xh, 7); // 39 + + var r = c0_lo ^ c1_lo ^ c2_lo; + if (r < 0) + r += 0x100000000; + return r; +} + +function s1_512_hi(xh, xl) { + var c0_hi = rotr64_hi(xh, xl, 14); + var c1_hi = rotr64_hi(xh, xl, 18); + var c2_hi = rotr64_hi(xl, xh, 9); // 41 + + var r = c0_hi ^ c1_hi ^ c2_hi; + if (r < 0) + r += 0x100000000; + return r; +} + +function s1_512_lo(xh, xl) { + var c0_lo = rotr64_lo(xh, xl, 14); + var c1_lo = rotr64_lo(xh, xl, 18); + var c2_lo = rotr64_lo(xl, xh, 9); // 41 + + var r = c0_lo ^ c1_lo ^ c2_lo; + if (r < 0) + r += 0x100000000; + return r; +} + +function g0_512_hi(xh, xl) { + var c0_hi = rotr64_hi(xh, xl, 1); + var c1_hi = rotr64_hi(xh, xl, 8); + var c2_hi = shr64_hi(xh, xl, 7); + + var r = c0_hi ^ c1_hi ^ c2_hi; + if (r < 0) + r += 0x100000000; + return r; +} + +function g0_512_lo(xh, xl) { + var c0_lo = rotr64_lo(xh, xl, 1); + var c1_lo = rotr64_lo(xh, xl, 8); + var c2_lo = shr64_lo(xh, xl, 7); + + var r = c0_lo ^ c1_lo ^ c2_lo; + if (r < 0) + r += 0x100000000; + return r; +} + +function g1_512_hi(xh, xl) { + var c0_hi = rotr64_hi(xh, xl, 19); + var c1_hi = rotr64_hi(xl, xh, 29); // 61 + var c2_hi = shr64_hi(xh, xl, 6); + + var r = c0_hi ^ c1_hi ^ c2_hi; + if (r < 0) + r += 0x100000000; + return r; +} + +function g1_512_lo(xh, xl) { + var c0_lo = rotr64_lo(xh, xl, 19); + var c1_lo = rotr64_lo(xl, xh, 29); // 61 + var c2_lo = shr64_lo(xh, xl, 6); + + var r = c0_lo ^ c1_lo ^ c2_lo; + if (r < 0) + r += 0x100000000; + return r; +} + +},{"../common":133,"../utils":143,"minimalistic-assert":223}],142:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var rotr32 = utils.rotr32; + +function ft_1(s, x, y, z) { + if (s === 0) + return ch32(x, y, z); + if (s === 1 || s === 3) + return p32(x, y, z); + if (s === 2) + return maj32(x, y, z); +} +exports.ft_1 = ft_1; + +function ch32(x, y, z) { + return (x & y) ^ ((~x) & z); +} +exports.ch32 = ch32; + +function maj32(x, y, z) { + return (x & y) ^ (x & z) ^ (y & z); +} +exports.maj32 = maj32; + +function p32(x, y, z) { + return x ^ y ^ z; +} +exports.p32 = p32; + +function s0_256(x) { + return rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22); +} +exports.s0_256 = s0_256; + +function s1_256(x) { + return rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25); +} +exports.s1_256 = s1_256; + +function g0_256(x) { + return rotr32(x, 7) ^ rotr32(x, 18) ^ (x >>> 3); +} +exports.g0_256 = g0_256; + +function g1_256(x) { + return rotr32(x, 17) ^ rotr32(x, 19) ^ (x >>> 10); +} +exports.g1_256 = g1_256; + +},{"../utils":143}],143:[function(require,module,exports){ +'use strict'; + +var assert = require('minimalistic-assert'); +var inherits = require('inherits'); + +exports.inherits = inherits; + +function isSurrogatePair(msg, i) { + if ((msg.charCodeAt(i) & 0xFC00) !== 0xD800) { + return false; + } + if (i < 0 || i + 1 >= msg.length) { + return false; + } + return (msg.charCodeAt(i + 1) & 0xFC00) === 0xDC00; +} + +function toArray(msg, enc) { + if (Array.isArray(msg)) + return msg.slice(); + if (!msg) + return []; + var res = []; + if (typeof msg === 'string') { + if (!enc) { + // Inspired by stringToUtf8ByteArray() in closure-library by Google + // https://github.com/google/closure-library/blob/8598d87242af59aac233270742c8984e2b2bdbe0/closure/goog/crypt/crypt.js#L117-L143 + // Apache License 2.0 + // https://github.com/google/closure-library/blob/master/LICENSE + var p = 0; + for (var i = 0; i < msg.length; i++) { + var c = msg.charCodeAt(i); + if (c < 128) { + res[p++] = c; + } else if (c < 2048) { + res[p++] = (c >> 6) | 192; + res[p++] = (c & 63) | 128; + } else if (isSurrogatePair(msg, i)) { + c = 0x10000 + ((c & 0x03FF) << 10) + (msg.charCodeAt(++i) & 0x03FF); + res[p++] = (c >> 18) | 240; + res[p++] = ((c >> 12) & 63) | 128; + res[p++] = ((c >> 6) & 63) | 128; + res[p++] = (c & 63) | 128; + } else { + res[p++] = (c >> 12) | 224; + res[p++] = ((c >> 6) & 63) | 128; + res[p++] = (c & 63) | 128; + } + } + } else if (enc === 'hex') { + msg = msg.replace(/[^a-z0-9]+/ig, ''); + if (msg.length % 2 !== 0) + msg = '0' + msg; + for (i = 0; i < msg.length; i += 2) + res.push(parseInt(msg[i] + msg[i + 1], 16)); + } + } else { + for (i = 0; i < msg.length; i++) + res[i] = msg[i] | 0; + } + return res; +} +exports.toArray = toArray; + +function toHex(msg) { + var res = ''; + for (var i = 0; i < msg.length; i++) + res += zero2(msg[i].toString(16)); + return res; +} +exports.toHex = toHex; + +function htonl(w) { + var res = (w >>> 24) | + ((w >>> 8) & 0xff00) | + ((w << 8) & 0xff0000) | + ((w & 0xff) << 24); + return res >>> 0; +} +exports.htonl = htonl; + +function toHex32(msg, endian) { + var res = ''; + for (var i = 0; i < msg.length; i++) { + var w = msg[i]; + if (endian === 'little') + w = htonl(w); + res += zero8(w.toString(16)); + } + return res; +} +exports.toHex32 = toHex32; + +function zero2(word) { + if (word.length === 1) + return '0' + word; + else + return word; +} +exports.zero2 = zero2; + +function zero8(word) { + if (word.length === 7) + return '0' + word; + else if (word.length === 6) + return '00' + word; + else if (word.length === 5) + return '000' + word; + else if (word.length === 4) + return '0000' + word; + else if (word.length === 3) + return '00000' + word; + else if (word.length === 2) + return '000000' + word; + else if (word.length === 1) + return '0000000' + word; + else + return word; +} +exports.zero8 = zero8; + +function join32(msg, start, end, endian) { + var len = end - start; + assert(len % 4 === 0); + var res = new Array(len / 4); + for (var i = 0, k = start; i < res.length; i++, k += 4) { + var w; + if (endian === 'big') + w = (msg[k] << 24) | (msg[k + 1] << 16) | (msg[k + 2] << 8) | msg[k + 3]; + else + w = (msg[k + 3] << 24) | (msg[k + 2] << 16) | (msg[k + 1] << 8) | msg[k]; + res[i] = w >>> 0; + } + return res; +} +exports.join32 = join32; + +function split32(msg, endian) { + var res = new Array(msg.length * 4); + for (var i = 0, k = 0; i < msg.length; i++, k += 4) { + var m = msg[i]; + if (endian === 'big') { + res[k] = m >>> 24; + res[k + 1] = (m >>> 16) & 0xff; + res[k + 2] = (m >>> 8) & 0xff; + res[k + 3] = m & 0xff; + } else { + res[k + 3] = m >>> 24; + res[k + 2] = (m >>> 16) & 0xff; + res[k + 1] = (m >>> 8) & 0xff; + res[k] = m & 0xff; + } + } + return res; +} +exports.split32 = split32; + +function rotr32(w, b) { + return (w >>> b) | (w << (32 - b)); +} +exports.rotr32 = rotr32; + +function rotl32(w, b) { + return (w << b) | (w >>> (32 - b)); +} +exports.rotl32 = rotl32; + +function sum32(a, b) { + return (a + b) >>> 0; +} +exports.sum32 = sum32; + +function sum32_3(a, b, c) { + return (a + b + c) >>> 0; +} +exports.sum32_3 = sum32_3; + +function sum32_4(a, b, c, d) { + return (a + b + c + d) >>> 0; +} +exports.sum32_4 = sum32_4; + +function sum32_5(a, b, c, d, e) { + return (a + b + c + d + e) >>> 0; +} +exports.sum32_5 = sum32_5; + +function sum64(buf, pos, ah, al) { + var bh = buf[pos]; + var bl = buf[pos + 1]; + + var lo = (al + bl) >>> 0; + var hi = (lo < al ? 1 : 0) + ah + bh; + buf[pos] = hi >>> 0; + buf[pos + 1] = lo; +} +exports.sum64 = sum64; + +function sum64_hi(ah, al, bh, bl) { + var lo = (al + bl) >>> 0; + var hi = (lo < al ? 1 : 0) + ah + bh; + return hi >>> 0; +} +exports.sum64_hi = sum64_hi; + +function sum64_lo(ah, al, bh, bl) { + var lo = al + bl; + return lo >>> 0; +} +exports.sum64_lo = sum64_lo; + +function sum64_4_hi(ah, al, bh, bl, ch, cl, dh, dl) { + var carry = 0; + var lo = al; + lo = (lo + bl) >>> 0; + carry += lo < al ? 1 : 0; + lo = (lo + cl) >>> 0; + carry += lo < cl ? 1 : 0; + lo = (lo + dl) >>> 0; + carry += lo < dl ? 1 : 0; + + var hi = ah + bh + ch + dh + carry; + return hi >>> 0; +} +exports.sum64_4_hi = sum64_4_hi; + +function sum64_4_lo(ah, al, bh, bl, ch, cl, dh, dl) { + var lo = al + bl + cl + dl; + return lo >>> 0; +} +exports.sum64_4_lo = sum64_4_lo; + +function sum64_5_hi(ah, al, bh, bl, ch, cl, dh, dl, eh, el) { + var carry = 0; + var lo = al; + lo = (lo + bl) >>> 0; + carry += lo < al ? 1 : 0; + lo = (lo + cl) >>> 0; + carry += lo < cl ? 1 : 0; + lo = (lo + dl) >>> 0; + carry += lo < dl ? 1 : 0; + lo = (lo + el) >>> 0; + carry += lo < el ? 1 : 0; + + var hi = ah + bh + ch + dh + eh + carry; + return hi >>> 0; +} +exports.sum64_5_hi = sum64_5_hi; + +function sum64_5_lo(ah, al, bh, bl, ch, cl, dh, dl, eh, el) { + var lo = al + bl + cl + dl + el; + + return lo >>> 0; +} +exports.sum64_5_lo = sum64_5_lo; + +function rotr64_hi(ah, al, num) { + var r = (al << (32 - num)) | (ah >>> num); + return r >>> 0; +} +exports.rotr64_hi = rotr64_hi; + +function rotr64_lo(ah, al, num) { + var r = (ah << (32 - num)) | (al >>> num); + return r >>> 0; +} +exports.rotr64_lo = rotr64_lo; + +function shr64_hi(ah, al, num) { + return ah >>> num; +} +exports.shr64_hi = shr64_hi; + +function shr64_lo(ah, al, num) { + var r = (ah << (32 - num)) | (al >>> num); + return r >>> 0; +} +exports.shr64_lo = shr64_lo; + +},{"inherits":146,"minimalistic-assert":223}],144:[function(require,module,exports){ +'use strict'; + +var hash = require('hash.js'); +var utils = require('minimalistic-crypto-utils'); +var assert = require('minimalistic-assert'); + +function HmacDRBG(options) { + if (!(this instanceof HmacDRBG)) + return new HmacDRBG(options); + this.hash = options.hash; + this.predResist = !!options.predResist; + + this.outLen = this.hash.outSize; + this.minEntropy = options.minEntropy || this.hash.hmacStrength; + + this._reseed = null; + this.reseedInterval = null; + this.K = null; + this.V = null; + + var entropy = utils.toArray(options.entropy, options.entropyEnc || 'hex'); + var nonce = utils.toArray(options.nonce, options.nonceEnc || 'hex'); + var pers = utils.toArray(options.pers, options.persEnc || 'hex'); + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + this._init(entropy, nonce, pers); +} +module.exports = HmacDRBG; + +HmacDRBG.prototype._init = function init(entropy, nonce, pers) { + var seed = entropy.concat(nonce).concat(pers); + + this.K = new Array(this.outLen / 8); + this.V = new Array(this.outLen / 8); + for (var i = 0; i < this.V.length; i++) { + this.K[i] = 0x00; + this.V[i] = 0x01; + } + + this._update(seed); + this._reseed = 1; + this.reseedInterval = 0x1000000000000; // 2^48 +}; + +HmacDRBG.prototype._hmac = function hmac() { + return new hash.hmac(this.hash, this.K); +}; + +HmacDRBG.prototype._update = function update(seed) { + var kmac = this._hmac() + .update(this.V) + .update([ 0x00 ]); + if (seed) + kmac = kmac.update(seed); + this.K = kmac.digest(); + this.V = this._hmac().update(this.V).digest(); + if (!seed) + return; + + this.K = this._hmac() + .update(this.V) + .update([ 0x01 ]) + .update(seed) + .digest(); + this.V = this._hmac().update(this.V).digest(); +}; + +HmacDRBG.prototype.reseed = function reseed(entropy, entropyEnc, add, addEnc) { + // Optional entropy enc + if (typeof entropyEnc !== 'string') { + addEnc = add; + add = entropyEnc; + entropyEnc = null; + } + + entropy = utils.toArray(entropy, entropyEnc); + add = utils.toArray(add, addEnc); + + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + + this._update(entropy.concat(add || [])); + this._reseed = 1; +}; + +HmacDRBG.prototype.generate = function generate(len, enc, add, addEnc) { + if (this._reseed > this.reseedInterval) + throw new Error('Reseed is required'); + + // Optional encoding + if (typeof enc !== 'string') { + addEnc = add; + add = enc; + enc = null; + } + + // Optional additional data + if (add) { + add = utils.toArray(add, addEnc || 'hex'); + this._update(add); + } + + var temp = []; + while (temp.length < len) { + this.V = this._hmac().update(this.V).digest(); + temp = temp.concat(this.V); + } + + var res = temp.slice(0, len); + this._update(add); + this._reseed++; + return utils.encode(res, enc); +}; + +},{"hash.js":132,"minimalistic-assert":223,"minimalistic-crypto-utils":224}],145:[function(require,module,exports){ +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],146:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } + } +} + +},{}],147:[function(require,module,exports){ +'use strict'; + +var hasToStringTag = require('has-tostringtag/shams')(); +var callBound = require('call-bind/callBound'); + +var $toString = callBound('Object.prototype.toString'); + +var isStandardArguments = function isArguments(value) { + if (hasToStringTag && value && typeof value === 'object' && Symbol.toStringTag in value) { + return false; + } + return $toString(value) === '[object Arguments]'; +}; + +var isLegacyArguments = function isArguments(value) { + if (isStandardArguments(value)) { + return true; + } + return value !== null && + typeof value === 'object' && + typeof value.length === 'number' && + value.length >= 0 && + $toString(value) !== '[object Array]' && + $toString(value.callee) === '[object Function]'; +}; + +var supportsStandardArguments = (function () { + return isStandardArguments(arguments); +}()); + +isStandardArguments.isLegacyArguments = isLegacyArguments; // for tests + +module.exports = supportsStandardArguments ? isStandardArguments : isLegacyArguments; + +},{"call-bind/callBound":69,"has-tostringtag/shams":114}],148:[function(require,module,exports){ +'use strict'; + +var fnToStr = Function.prototype.toString; +var reflectApply = typeof Reflect === 'object' && Reflect !== null && Reflect.apply; +var badArrayLike; +var isCallableMarker; +if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') { + try { + badArrayLike = Object.defineProperty({}, 'length', { + get: function () { + throw isCallableMarker; + } + }); + isCallableMarker = {}; + // eslint-disable-next-line no-throw-literal + reflectApply(function () { throw 42; }, null, badArrayLike); + } catch (_) { + if (_ !== isCallableMarker) { + reflectApply = null; + } + } +} else { + reflectApply = null; +} + +var constructorRegex = /^\s*class\b/; +var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; // not a function + } +}; + +var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { return false; } + fnToStr.call(value); + return true; + } catch (e) { + return false; + } +}; +var toStr = Object.prototype.toString; +var objectClass = '[object Object]'; +var fnClass = '[object Function]'; +var genClass = '[object GeneratorFunction]'; +var ddaClass = '[object HTMLAllCollection]'; // IE 11 +var ddaClass2 = '[object HTML document.all class]'; +var ddaClass3 = '[object HTMLCollection]'; // IE 9-10 +var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag` + +var isIE68 = !(0 in [,]); // eslint-disable-line no-sparse-arrays, comma-spacing + +var isDDA = function isDocumentDotAll() { return false; }; +if (typeof document === 'object') { + // Firefox 3 canonicalizes DDA to undefined when it's not accessed directly + var all = document.all; + if (toStr.call(all) === toStr.call(document.all)) { + isDDA = function isDocumentDotAll(value) { + /* globals document: false */ + // in IE 6-8, typeof document.all is "object" and it's truthy + if ((isIE68 || !value) && (typeof value === 'undefined' || typeof value === 'object')) { + try { + var str = toStr.call(value); + return ( + str === ddaClass + || str === ddaClass2 + || str === ddaClass3 // opera 12.16 + || str === objectClass // IE 6-8 + ) && value('') == null; // eslint-disable-line eqeqeq + } catch (e) { /**/ } + } + return false; + }; + } +} + +module.exports = reflectApply + ? function isCallable(value) { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + try { + reflectApply(value, null, badArrayLike); + } catch (e) { + if (e !== isCallableMarker) { return false; } + } + return !isES6ClassFn(value) && tryFunctionObject(value); + } + : function isCallable(value) { + if (isDDA(value)) { return true; } + if (!value) { return false; } + if (typeof value !== 'function' && typeof value !== 'object') { return false; } + if (hasToStringTag) { return tryFunctionObject(value); } + if (isES6ClassFn(value)) { return false; } + var strClass = toStr.call(value); + if (strClass !== fnClass && strClass !== genClass && !(/^\[object HTML/).test(strClass)) { return false; } + return tryFunctionObject(value); + }; + +},{}],149:[function(require,module,exports){ +'use strict'; + +var toStr = Object.prototype.toString; +var fnToStr = Function.prototype.toString; +var isFnRegex = /^\s*(?:function)?\*/; +var hasToStringTag = require('has-tostringtag/shams')(); +var getProto = Object.getPrototypeOf; +var getGeneratorFunc = function () { // eslint-disable-line consistent-return + if (!hasToStringTag) { + return false; + } + try { + return Function('return function*() {}')(); + } catch (e) { + } +}; +var GeneratorFunction; + +module.exports = function isGeneratorFunction(fn) { + if (typeof fn !== 'function') { + return false; + } + if (isFnRegex.test(fnToStr.call(fn))) { + return true; + } + if (!hasToStringTag) { + var str = toStr.call(fn); + return str === '[object GeneratorFunction]'; + } + if (!getProto) { + return false; + } + if (typeof GeneratorFunction === 'undefined') { + var generatorFunc = getGeneratorFunc(); + GeneratorFunction = generatorFunc ? getProto(generatorFunc) : false; + } + return getProto(fn) === GeneratorFunction; +}; + +},{"has-tostringtag/shams":114}],150:[function(require,module,exports){ +(function (global){(function (){ +'use strict'; + +var forEach = require('for-each'); +var availableTypedArrays = require('available-typed-arrays'); +var callBound = require('call-bind/callBound'); + +var $toString = callBound('Object.prototype.toString'); +var hasToStringTag = require('has-tostringtag/shams')(); +var gOPD = require('gopd'); + +var g = typeof globalThis === 'undefined' ? global : globalThis; +var typedArrays = availableTypedArrays(); + +var $indexOf = callBound('Array.prototype.indexOf', true) || function indexOf(array, value) { + for (var i = 0; i < array.length; i += 1) { + if (array[i] === value) { + return i; + } + } + return -1; +}; +var $slice = callBound('String.prototype.slice'); +var toStrTags = {}; +var getPrototypeOf = Object.getPrototypeOf; // require('getprototypeof'); +if (hasToStringTag && gOPD && getPrototypeOf) { + forEach(typedArrays, function (typedArray) { + var arr = new g[typedArray](); + if (Symbol.toStringTag in arr) { + var proto = getPrototypeOf(arr); + var descriptor = gOPD(proto, Symbol.toStringTag); + if (!descriptor) { + var superProto = getPrototypeOf(proto); + descriptor = gOPD(superProto, Symbol.toStringTag); + } + toStrTags[typedArray] = descriptor.get; + } + }); +} + +var tryTypedArrays = function tryAllTypedArrays(value) { + var anyTrue = false; + forEach(toStrTags, function (getter, typedArray) { + if (!anyTrue) { + try { + anyTrue = getter.call(value) === typedArray; + } catch (e) { /**/ } + } + }); + return anyTrue; +}; + +module.exports = function isTypedArray(value) { + if (!value || typeof value !== 'object') { return false; } + if (!hasToStringTag || !(Symbol.toStringTag in value)) { + var tag = $slice($toString(value), 8, -1); + return $indexOf(typedArrays, tag) > -1; + } + if (!gOPD) { return false; } + return tryTypedArrays(value); +}; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"available-typed-arrays":16,"call-bind/callBound":69,"for-each":107,"gopd":111,"has-tostringtag/shams":114}],151:[function(require,module,exports){ +/* +* loglevel - https://github.com/pimterry/loglevel +* +* Copyright (c) 2013 Tim Perry +* Licensed under the MIT license. +*/ +(function (root, definition) { + "use strict"; + if (typeof define === 'function' && define.amd) { + define(definition); + } else if (typeof module === 'object' && module.exports) { + module.exports = definition(); + } else { + root.log = definition(); + } +}(this, function () { + "use strict"; + + // Slightly dubious tricks to cut down minimized file size + var noop = function() {}; + var undefinedType = "undefined"; + var isIE = (typeof window !== undefinedType) && (typeof window.navigator !== undefinedType) && ( + /Trident\/|MSIE /.test(window.navigator.userAgent) + ); + + var logMethods = [ + "trace", + "debug", + "info", + "warn", + "error" + ]; + + // Cross-browser bind equivalent that works at least back to IE6 + function bindMethod(obj, methodName) { + var method = obj[methodName]; + if (typeof method.bind === 'function') { + return method.bind(obj); + } else { + try { + return Function.prototype.bind.call(method, obj); + } catch (e) { + // Missing bind shim or IE8 + Modernizr, fallback to wrapping + return function() { + return Function.prototype.apply.apply(method, [obj, arguments]); + }; + } + } + } + + // Trace() doesn't print the message in IE, so for that case we need to wrap it + function traceForIE() { + if (console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + // In old IE, native console methods themselves don't have apply(). + Function.prototype.apply.apply(console.log, [console, arguments]); + } + } + if (console.trace) console.trace(); + } + + // Build the best logging method possible for this env + // Wherever possible we want to bind, not wrap, to preserve stack traces + function realMethod(methodName) { + if (methodName === 'debug') { + methodName = 'log'; + } + + if (typeof console === undefinedType) { + return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives + } else if (methodName === 'trace' && isIE) { + return traceForIE; + } else if (console[methodName] !== undefined) { + return bindMethod(console, methodName); + } else if (console.log !== undefined) { + return bindMethod(console, 'log'); + } else { + return noop; + } + } + + // These private functions always need `this` to be set properly + + function replaceLoggingMethods(level, loggerName) { + /*jshint validthis:true */ + for (var i = 0; i < logMethods.length; i++) { + var methodName = logMethods[i]; + this[methodName] = (i < level) ? + noop : + this.methodFactory(methodName, level, loggerName); + } + + // Define log.log as an alias for log.debug + this.log = this.debug; + } + + // In old IE versions, the console isn't present until you first open it. + // We build realMethod() replacements here that regenerate logging methods + function enableLoggingWhenConsoleArrives(methodName, level, loggerName) { + return function () { + if (typeof console !== undefinedType) { + replaceLoggingMethods.call(this, level, loggerName); + this[methodName].apply(this, arguments); + } + }; + } + + // By default, we use closely bound real methods wherever possible, and + // otherwise we wait for a console to appear, and then try again. + function defaultMethodFactory(methodName, level, loggerName) { + /*jshint validthis:true */ + return realMethod(methodName) || + enableLoggingWhenConsoleArrives.apply(this, arguments); + } + + function Logger(name, defaultLevel, factory) { + var self = this; + var currentLevel; + defaultLevel = defaultLevel == null ? "WARN" : defaultLevel; + + var storageKey = "loglevel"; + if (typeof name === "string") { + storageKey += ":" + name; + } else if (typeof name === "symbol") { + storageKey = undefined; + } + + function persistLevelIfPossible(levelNum) { + var levelName = (logMethods[levelNum] || 'silent').toUpperCase(); + + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage[storageKey] = levelName; + return; + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = + encodeURIComponent(storageKey) + "=" + levelName + ";"; + } catch (ignore) {} + } + + function getPersistedLevel() { + var storedLevel; + + if (typeof window === undefinedType || !storageKey) return; + + try { + storedLevel = window.localStorage[storageKey]; + } catch (ignore) {} + + // Fallback to cookies if local storage gives us nothing + if (typeof storedLevel === undefinedType) { + try { + var cookie = window.document.cookie; + var location = cookie.indexOf( + encodeURIComponent(storageKey) + "="); + if (location !== -1) { + storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1]; + } + } catch (ignore) {} + } + + // If the stored level is not valid, treat it as if nothing was stored. + if (self.levels[storedLevel] === undefined) { + storedLevel = undefined; + } + + return storedLevel; + } + + function clearPersistedLevel() { + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage.removeItem(storageKey); + return; + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = + encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; + } catch (ignore) {} + } + + /* + * + * Public logger API - see https://github.com/pimterry/loglevel for details + * + */ + + self.name = name; + + self.levels = { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, + "ERROR": 4, "SILENT": 5}; + + self.methodFactory = factory || defaultMethodFactory; + + self.getLevel = function () { + return currentLevel; + }; + + self.setLevel = function (level, persist) { + if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) { + level = self.levels[level.toUpperCase()]; + } + if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) { + currentLevel = level; + if (persist !== false) { // defaults to true + persistLevelIfPossible(level); + } + replaceLoggingMethods.call(self, level, name); + if (typeof console === undefinedType && level < self.levels.SILENT) { + return "No console available for logging"; + } + } else { + throw "log.setLevel() called with invalid level: " + level; + } + }; + + self.setDefaultLevel = function (level) { + defaultLevel = level; + if (!getPersistedLevel()) { + self.setLevel(level, false); + } + }; + + self.resetLevel = function () { + self.setLevel(defaultLevel, false); + clearPersistedLevel(); + }; + + self.enableAll = function(persist) { + self.setLevel(self.levels.TRACE, persist); + }; + + self.disableAll = function(persist) { + self.setLevel(self.levels.SILENT, persist); + }; + + // Initialize with the right level + var initialLevel = getPersistedLevel(); + if (initialLevel == null) { + initialLevel = defaultLevel; + } + self.setLevel(initialLevel, false); + } + + /* + * + * Top-level API + * + */ + + var defaultLogger = new Logger(); + + var _loggersByName = {}; + defaultLogger.getLogger = function getLogger(name) { + if ((typeof name !== "symbol" && typeof name !== "string") || name === "") { + throw new TypeError("You must supply a name when creating a logger."); + } + + var logger = _loggersByName[name]; + if (!logger) { + logger = _loggersByName[name] = new Logger( + name, defaultLogger.getLevel(), defaultLogger.methodFactory); + } + return logger; + }; + + // Grab the current global log variable in case of overwrite + var _log = (typeof window !== undefinedType) ? window.log : undefined; + defaultLogger.noConflict = function() { + if (typeof window !== undefinedType && + window.log === defaultLogger) { + window.log = _log; + } + + return defaultLogger; + }; + + defaultLogger.getLoggers = function getLoggers() { + return _loggersByName; + }; + + // ES6 default export, for compatibility + defaultLogger['default'] = defaultLogger; + + return defaultLogger; +})); + +},{}],152:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ExtensibleEvents = void 0; + +var _NamespacedMap = require("./NamespacedMap"); + +var _InvalidEventError = require("./InvalidEventError"); + +var _MRoomMessage = require("./interpreters/legacy/MRoomMessage"); + +var _MMessage = require("./interpreters/modern/MMessage"); + +var _message_types = require("./events/message_types"); + +var _poll_types = require("./events/poll_types"); + +var _MPoll = require("./interpreters/modern/MPoll"); + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Utility class for parsing and identifying event types in a renderable form. An + * instance of this class can be created to change rendering preference depending + * on use-case. + */ +var ExtensibleEvents = /*#__PURE__*/function () { + function ExtensibleEvents() { + _classCallCheck(this, ExtensibleEvents); + + _defineProperty(this, "interpreters", new _NamespacedMap.NamespacedMap([// Remember to add your unit test when adding to this! ("known events" test description) + [_MRoomMessage.LEGACY_M_ROOM_MESSAGE, _MRoomMessage.parseMRoomMessage], [_message_types.M_MESSAGE, _MMessage.parseMMessage], [_message_types.M_EMOTE, _MMessage.parseMMessage], [_message_types.M_NOTICE, _MMessage.parseMMessage], [_poll_types.M_POLL_START, _MPoll.parseMPoll], [_poll_types.M_POLL_RESPONSE, _MPoll.parseMPoll], [_poll_types.M_POLL_END, _MPoll.parseMPoll]])); + + _defineProperty(this, "_unknownInterpretOrder", [_message_types.M_MESSAGE]); + } + /** + * Gets the default instance for all extensible event parsing. + */ + + + _createClass(ExtensibleEvents, [{ + key: "unknownInterpretOrder", + get: + /** + * Gets the order the internal processor will use for unknown primary + * event types. + */ + function get() { + var _this$_unknownInterpr; + + return (_this$_unknownInterpr = this._unknownInterpretOrder) !== null && _this$_unknownInterpr !== void 0 ? _this$_unknownInterpr : []; + } + /** + * Sets the order the internal processor will use for unknown primary + * event types. + * @param {NamespacedValue[]} val The parsing order. + */ + , + set: function set(val) { + this._unknownInterpretOrder = val; + } + /** + * Gets the order the internal processor will use for unknown primary + * event types. + */ + + }, { + key: "registerInterpreter", + value: + /** + * Registers a primary event type interpreter. Note that the interpreter might be + * called with non-primary events if the event is being parsed as a fallback. + * @param {NamespacedValue} wireEventType The event type. + * @param {EventInterpreter} interpreter The interpreter. + */ + function registerInterpreter(wireEventType, interpreter) { + this.interpreters.set(wireEventType, interpreter); + } + /** + * Registers a primary event type interpreter. Note that the interpreter might be + * called with non-primary events if the event is being parsed as a fallback. + * @param {NamespacedValue} wireEventType The event type. + * @param {EventInterpreter} interpreter The interpreter. + */ + + }, { + key: "parse", + value: + /** + * Parses an event, trying the primary event type first. If the primary type is not known + * then the content will be inspected to find the most suitable fallback. + * + * If the parsing failed or was a completely unknown type, this will return falsy. + * @param {IPartialEvent} wireFormat The event to parse. + * @returns {Optional} The parsed extensible event. + */ + function parse(wireFormat) { + try { + if (this.interpreters.hasNamespaced(wireFormat.type)) { + return this.interpreters.getNamespaced(wireFormat.type)(wireFormat); + } + + var _iterator = _createForOfIteratorHelper(this.unknownInterpretOrder), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var tryType = _step.value; + + if (this.interpreters.has(tryType)) { + var val = this.interpreters.get(tryType)(wireFormat); + if (val) return val; + } + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + + return null; // cannot be parsed + } catch (e) { + if (e instanceof _InvalidEventError.InvalidEventError) { + return null; // fail parsing and move on + } + + throw e; // re-throw everything else + } + } + /** + * Parses an event, trying the primary event type first. If the primary type is not known + * then the content will be inspected to find the most suitable fallback. + * + * If the parsing failed or was a completely unknown type, this will return falsy. + * @param {IPartialEvent} wireFormat The event to parse. + * @returns {Optional} The parsed extensible event. + */ + + }], [{ + key: "defaultInstance", + get: function get() { + return ExtensibleEvents._defaultInstance; + } + }, { + key: "unknownInterpretOrder", + get: function get() { + return ExtensibleEvents.defaultInstance.unknownInterpretOrder; + } + /** + * Sets the order the internal processor will use for unknown primary + * event types. + * @param {NamespacedValue[]} val The parsing order. + */ + , + set: function set(val) { + ExtensibleEvents.defaultInstance.unknownInterpretOrder = val; + } + }, { + key: "registerInterpreter", + value: function registerInterpreter(wireEventType, interpreter) { + ExtensibleEvents.defaultInstance.registerInterpreter(wireEventType, interpreter); + } + }, { + key: "parse", + value: function parse(wireFormat) { + return ExtensibleEvents.defaultInstance.parse(wireFormat); + } + }]); + + return ExtensibleEvents; +}(); + +exports.ExtensibleEvents = ExtensibleEvents; + +_defineProperty(ExtensibleEvents, "_defaultInstance", new ExtensibleEvents()); +},{"./InvalidEventError":154,"./NamespacedMap":155,"./events/message_types":164,"./events/poll_types":165,"./interpreters/legacy/MRoomMessage":168,"./interpreters/modern/MMessage":169,"./interpreters/modern/MPoll":170}],153:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +},{}],154:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.InvalidEventError = void 0; + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } + +function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Thrown when an event is unforgivably unparsable. + */ +var InvalidEventError = /*#__PURE__*/function (_Error) { + _inherits(InvalidEventError, _Error); + + var _super = _createSuper(InvalidEventError); + + function InvalidEventError(message) { + _classCallCheck(this, InvalidEventError); + + return _super.call(this, message); + } + + return _createClass(InvalidEventError); +}( /*#__PURE__*/_wrapNativeSuper(Error)); + +exports.InvalidEventError = InvalidEventError; +},{}],155:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.NamespacedMap = void 0; + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * A `Map` implementation which accepts a NamespacedValue as a key, and arbitrary value. The + * namespaced value must be a string type. + */ +var NamespacedMap = /*#__PURE__*/function () { + // protected to make tests happy for access + + /** + * Creates a new map with optional seed data. + * @param {Array<[NS, V]>} initial The seed data. + */ + function NamespacedMap(initial) { + _classCallCheck(this, NamespacedMap); + + _defineProperty(this, "internalMap", new Map()); + + if (initial) { + var _iterator = _createForOfIteratorHelper(initial), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var val = _step.value; + this.set(val[0], val[1]); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } + } + /** + * Gets a value from the map. If the value does not exist under + * either namespace option, falsy is returned. + * @param {NS} key The key. + * @returns {Optional} The value, or falsy. + */ + + + _createClass(NamespacedMap, [{ + key: "get", + value: function get(key) { + if (key.name && this.internalMap.has(key.name)) { + return this.internalMap.get(key.name); + } + + if (key.altName && this.internalMap.has(key.altName)) { + return this.internalMap.get(key.altName); + } + + return null; + } + /** + * Sets a value in the map. + * @param {NS} key The key. + * @param {V} val The value. + */ + + }, { + key: "set", + value: function set(key, val) { + if (key.name) { + this.internalMap.set(key.name, val); + } + + if (key.altName) { + this.internalMap.set(key.altName, val); + } + } + /** + * Determines if any of the valid namespaced values are present + * in the map. + * @param {NS} key The key. + * @returns {boolean} True if present. + */ + + }, { + key: "has", + value: function has(key) { + return !!this.get(key); + } + /** + * Removes all the namespaced values from the map. + * @param {NS} key The key. + */ + + }, { + key: "delete", + value: function _delete(key) { + if (key.name) { + this.internalMap["delete"](key.name); + } + + if (key.altName) { + this.internalMap["delete"](key.altName); + } + } + /** + * Determines if the map contains a specific namespaced value + * instead of the parent NS type. + * @param {string} key The key. + * @returns {boolean} True if present. + */ + + }, { + key: "hasNamespaced", + value: function hasNamespaced(key) { + return this.internalMap.has(key); + } + /** + * Gets a specific namespaced value from the map instead of the + * parent NS type. Returns falsy if not found. + * @param {string} key The key. + * @returns {Optional} The value, or falsy. + */ + + }, { + key: "getNamespaced", + value: function getNamespaced(key) { + return this.internalMap.get(key); + } + }]); + + return NamespacedMap; +}(); + +exports.NamespacedMap = NamespacedMap; +},{}],156:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.UnstableValue = exports.NamespacedValue = void 0; + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +/* +Copyright 2021 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Represents a simple Matrix namespaced value. This will assume that if a stable prefix + * is provided that the stable prefix should be used when representing the identifier. + */ +var NamespacedValue = /*#__PURE__*/function () { + // Stable is optional, but one of the two parameters is required, hence the weird-looking types. + // Goal is to have developers explicitly say there is no stable value (if applicable). + function NamespacedValue(stable, unstable) { + _classCallCheck(this, NamespacedValue); + + this.stable = stable; + this.unstable = unstable; + + if (!this.unstable && !this.stable) { + throw new Error("One of stable or unstable values must be supplied"); + } + } + + _createClass(NamespacedValue, [{ + key: "name", + get: function get() { + if (this.stable) { + return this.stable; + } + + return this.unstable; + } + }, { + key: "altName", + get: function get() { + if (!this.stable) { + return null; + } + + return this.unstable; + } + }, { + key: "matches", + value: function matches(val) { + return !!this.name && this.name === val || !!this.altName && this.altName === val; + } // this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class + // so we can instantiate `NamespacedValue` as a default type for that namespace. + + }, { + key: "findIn", + value: function findIn(obj) { + var val; + + if (this.name) { + val = obj === null || obj === void 0 ? void 0 : obj[this.name]; + } + + if (!val && this.altName) { + val = obj === null || obj === void 0 ? void 0 : obj[this.altName]; + } + + return val; + } + }, { + key: "includedIn", + value: function includedIn(arr) { + var included = false; + + if (this.name) { + included = arr.includes(this.name); + } + + if (!included && this.altName) { + included = arr.includes(this.altName); + } + + return included; + } + }]); + + return NamespacedValue; +}(); +/** + * Represents a namespaced value which prioritizes the unstable value over the stable + * value. + */ + + +exports.NamespacedValue = NamespacedValue; + +var UnstableValue = /*#__PURE__*/function (_NamespacedValue) { + _inherits(UnstableValue, _NamespacedValue); + + var _super = _createSuper(UnstableValue); + + // Note: Constructor difference is that `unstable` is *required*. + function UnstableValue(stable, unstable) { + var _this; + + _classCallCheck(this, UnstableValue); + + _this = _super.call(this, stable, unstable); + + if (!_this.unstable) { + throw new Error("Unstable value must be supplied"); + } + + return _this; + } + + _createClass(UnstableValue, [{ + key: "name", + get: function get() { + return this.unstable; + } + }, { + key: "altName", + get: function get() { + return this.stable; + } + }]); + + return UnstableValue; +}(NamespacedValue); + +exports.UnstableValue = UnstableValue; +},{}],157:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.EmoteEvent = void 0; + +var _MessageEvent2 = require("./MessageEvent"); + +var _message_types = require("./message_types"); + +var _events = require("../utility/events"); + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +// Emote events are just decorated message events + +/** + * Represents an emote. This is essentially a MessageEvent with + * emote characteristics considered. + */ +var EmoteEvent = /*#__PURE__*/function (_MessageEvent) { + _inherits(EmoteEvent, _MessageEvent); + + var _super = _createSuper(EmoteEvent); + + function EmoteEvent(wireFormat) { + _classCallCheck(this, EmoteEvent); + + return _super.call(this, wireFormat); + } + + _createClass(EmoteEvent, [{ + key: "isEmote", + get: function get() { + return true; // override + } + }, { + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _message_types.M_EMOTE) || _get(_getPrototypeOf(EmoteEvent.prototype), "isEquivalentTo", this).call(this, primaryEventType); + } + }, { + key: "serialize", + value: function serialize() { + var message = _get(_getPrototypeOf(EmoteEvent.prototype), "serialize", this).call(this); + + message.content['msgtype'] = "m.emote"; + return message; + } + /** + * Creates a new EmoteEvent from text and HTML. + * @param {string} text The text. + * @param {string} html Optional HTML. + * @returns {MessageEvent} The representative message event. + */ + + }], [{ + key: "from", + value: function from(text, html) { + var _content; + + return new EmoteEvent({ + type: _message_types.M_EMOTE.name, + content: (_content = {}, _defineProperty(_content, _message_types.M_TEXT.name, text), _defineProperty(_content, _message_types.M_HTML.name, html), _content) + }); + } + }]); + + return EmoteEvent; +}(_MessageEvent2.MessageEvent); + +exports.EmoteEvent = EmoteEvent; +},{"../utility/events":173,"./MessageEvent":159,"./message_types":164}],158:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ExtensibleEvent = void 0; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +/* +Copyright 2021 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Represents an Extensible Event in Matrix. + */ +var ExtensibleEvent = /*#__PURE__*/function () { + function ExtensibleEvent(wireFormat) { + _classCallCheck(this, ExtensibleEvent); + + this.wireFormat = wireFormat; + } + /** + * Shortcut to wireFormat.content + */ + + + _createClass(ExtensibleEvent, [{ + key: "wireContent", + get: function get() { + return this.wireFormat.content; + } + /** + * Serializes the event into a format which can be used to send the + * event to the room. + * @returns {IPartialEvent} The serialized event. + */ + + }]); + + return ExtensibleEvent; +}(); + +exports.ExtensibleEvent = ExtensibleEvent; +},{}],159:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MessageEvent = void 0; + +var _ExtensibleEvent2 = require("./ExtensibleEvent"); + +var _types = require("../types"); + +var _InvalidEventError = require("../InvalidEventError"); + +var _message_types = require("./message_types"); + +var _events = require("../utility/events"); + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Represents a message event. Message events are the simplest form of event with + * just text (optionally of different mimetypes, like HTML). + * + * Message events can additionally be an Emote or Notice, though typically those + * are represented as EmoteEvent and NoticeEvent respectively. + */ +var MessageEvent = /*#__PURE__*/function (_ExtensibleEvent) { + _inherits(MessageEvent, _ExtensibleEvent); + + var _super = _createSuper(MessageEvent); + + /** + * The default text for the event. + */ + + /** + * The default HTML for the event, if provided. + */ + + /** + * All the different renderings of the message. Note that this is the same + * format as an m.message body but may contain elements not found directly + * in the event content: this is because this is interpreted based off the + * other information available in the event. + */ + + /** + * Creates a new MessageEvent from a pure format. Note that the event is + * *not* parsed here: it will be treated as a literal m.message primary + * typed event. + * @param {IPartialEvent} wireFormat The event. + */ + function MessageEvent(wireFormat) { + var _this; + + _classCallCheck(this, MessageEvent); + + _this = _super.call(this, wireFormat); + + _defineProperty(_assertThisInitialized(_this), "text", void 0); + + _defineProperty(_assertThisInitialized(_this), "html", void 0); + + _defineProperty(_assertThisInitialized(_this), "renderings", void 0); + + var mmessage = _message_types.M_MESSAGE.findIn(_this.wireContent); + + var mtext = _message_types.M_TEXT.findIn(_this.wireContent); + + var mhtml = _message_types.M_HTML.findIn(_this.wireContent); + + if ((0, _types.isProvided)(mmessage)) { + if (!Array.isArray(mmessage)) { + throw new _InvalidEventError.InvalidEventError("m.message contents must be an array"); + } + + var text = mmessage.find(function (r) { + return !(0, _types.isProvided)(r.mimetype) || r.mimetype === "text/plain"; + }); + var html = mmessage.find(function (r) { + return r.mimetype === "text/html"; + }); + if (!text) throw new _InvalidEventError.InvalidEventError("m.message is missing a plain text representation"); + _this.text = text.body; + _this.html = html === null || html === void 0 ? void 0 : html.body; + _this.renderings = mmessage; + } else if ((0, _types.isOptionalAString)(mtext)) { + _this.text = mtext; + _this.html = mhtml; + _this.renderings = [{ + body: mtext, + mimetype: "text/plain" + }]; + + if (_this.html) { + _this.renderings.push({ + body: _this.html, + mimetype: "text/html" + }); + } + } else { + throw new _InvalidEventError.InvalidEventError("Missing textual representation for event"); + } + + return _this; + } + /** + * Gets whether this message is considered an "emote". Note that a message + * might be an emote and notice at the same time: while technically possible, + * the event should be interpreted as one or the other. + */ + + + _createClass(MessageEvent, [{ + key: "isEmote", + get: function get() { + return _message_types.M_EMOTE.matches(this.wireFormat.type) || (0, _types.isProvided)(_message_types.M_EMOTE.findIn(this.wireFormat.content)); + } + /** + * Gets whether this message is considered a "notice". Note that a message + * might be an emote and notice at the same time: while technically possible, + * the event should be interpreted as one or the other. + */ + + }, { + key: "isNotice", + get: function get() { + return _message_types.M_NOTICE.matches(this.wireFormat.type) || (0, _types.isProvided)(_message_types.M_NOTICE.findIn(this.wireFormat.content)); + } + }, { + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _message_types.M_MESSAGE); + } + }, { + key: "serializeMMessageOnly", + value: function serializeMMessageOnly() { + var messageRendering = _defineProperty({}, _message_types.M_MESSAGE.name, this.renderings); // Use the shorthand if it's just a simple text event + + + if (this.renderings.length === 1) { + var mime = this.renderings[0].mimetype; + + if (mime === undefined || mime === "text/plain") { + messageRendering = _defineProperty({}, _message_types.M_TEXT.name, this.renderings[0].body); + } + } + + return messageRendering; + } + }, { + key: "serialize", + value: function serialize() { + var _this$html; + + return { + type: "m.room.message", + content: _objectSpread(_objectSpread({}, this.serializeMMessageOnly()), {}, { + body: this.text, + msgtype: "m.text", + format: this.html ? "org.matrix.custom.html" : undefined, + formatted_body: (_this$html = this.html) !== null && _this$html !== void 0 ? _this$html : undefined + }) + }; + } + /** + * Creates a new MessageEvent from text and HTML. + * @param {string} text The text. + * @param {string} html Optional HTML. + * @returns {MessageEvent} The representative message event. + */ + + }], [{ + key: "from", + value: function from(text, html) { + var _content; + + return new MessageEvent({ + type: _message_types.M_MESSAGE.name, + content: (_content = {}, _defineProperty(_content, _message_types.M_TEXT.name, text), _defineProperty(_content, _message_types.M_HTML.name, html), _content) + }); + } + }]); + + return MessageEvent; +}(_ExtensibleEvent2.ExtensibleEvent); + +exports.MessageEvent = MessageEvent; +},{"../InvalidEventError":154,"../types":171,"../utility/events":173,"./ExtensibleEvent":158,"./message_types":164}],160:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.NoticeEvent = void 0; + +var _MessageEvent2 = require("./MessageEvent"); + +var _message_types = require("./message_types"); + +var _events = require("../utility/events"); + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +// Notice events are just decorated message events + +/** + * Represents a notice. This is essentially a MessageEvent with + * notice characteristics considered. + */ +var NoticeEvent = /*#__PURE__*/function (_MessageEvent) { + _inherits(NoticeEvent, _MessageEvent); + + var _super = _createSuper(NoticeEvent); + + function NoticeEvent(wireFormat) { + _classCallCheck(this, NoticeEvent); + + return _super.call(this, wireFormat); + } + + _createClass(NoticeEvent, [{ + key: "isNotice", + get: function get() { + return true; // override + } + }, { + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _message_types.M_NOTICE) || _get(_getPrototypeOf(NoticeEvent.prototype), "isEquivalentTo", this).call(this, primaryEventType); + } + }, { + key: "serialize", + value: function serialize() { + var message = _get(_getPrototypeOf(NoticeEvent.prototype), "serialize", this).call(this); + + message.content['msgtype'] = "m.notice"; + return message; + } + /** + * Creates a new NoticeEvent from text and HTML. + * @param {string} text The text. + * @param {string} html Optional HTML. + * @returns {MessageEvent} The representative message event. + */ + + }], [{ + key: "from", + value: function from(text, html) { + var _content; + + return new NoticeEvent({ + type: _message_types.M_NOTICE.name, + content: (_content = {}, _defineProperty(_content, _message_types.M_TEXT.name, text), _defineProperty(_content, _message_types.M_HTML.name, html), _content) + }); + } + }]); + + return NoticeEvent; +}(_MessageEvent2.MessageEvent); + +exports.NoticeEvent = NoticeEvent; +},{"../utility/events":173,"./MessageEvent":159,"./message_types":164}],161:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PollEndEvent = void 0; + +var _poll_types = require("./poll_types"); + +var _InvalidEventError = require("../InvalidEventError"); + +var _relationship_types = require("./relationship_types"); + +var _MessageEvent = require("./MessageEvent"); + +var _message_types = require("./message_types"); + +var _events = require("../utility/events"); + +var _ExtensibleEvent2 = require("./ExtensibleEvent"); + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Represents a poll end/closure event. + */ +var PollEndEvent = /*#__PURE__*/function (_ExtensibleEvent) { + _inherits(PollEndEvent, _ExtensibleEvent); + + var _super = _createSuper(PollEndEvent); + + /** + * The poll start event ID referenced by the response. + */ + + /** + * The closing message for the event. + */ + + /** + * Creates a new PollEndEvent from a pure format. Note that the event is *not* + * parsed here: it will be treated as a literal m.poll.response primary typed event. + * @param {IPartialEvent} wireFormat The event. + */ + function PollEndEvent(wireFormat) { + var _this; + + _classCallCheck(this, PollEndEvent); + + _this = _super.call(this, wireFormat); + + _defineProperty(_assertThisInitialized(_this), "pollEventId", void 0); + + _defineProperty(_assertThisInitialized(_this), "closingMessage", void 0); + + var rel = _this.wireContent["m.relates_to"]; + + if (!_relationship_types.REFERENCE_RELATION.matches(rel === null || rel === void 0 ? void 0 : rel.rel_type) || typeof (rel === null || rel === void 0 ? void 0 : rel.event_id) !== "string") { + throw new _InvalidEventError.InvalidEventError("Relationship must be a reference to an event"); + } + + _this.pollEventId = rel.event_id; + _this.closingMessage = new _MessageEvent.MessageEvent(_this.wireFormat); + return _this; + } + + _createClass(PollEndEvent, [{ + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _poll_types.M_POLL_END); + } + }, { + key: "serialize", + value: function serialize() { + return { + type: _poll_types.M_POLL_END.name, + content: _objectSpread(_defineProperty({ + "m.relates_to": { + rel_type: _relationship_types.REFERENCE_RELATION.name, + event_id: this.pollEventId + } + }, _poll_types.M_POLL_END.name, {}), this.closingMessage.serialize().content) + }; + } + /** + * Creates a new PollEndEvent from a poll event ID. + * @param {string} pollEventId The poll start event ID. + * @param {string} message A closing message, typically revealing the top answer. + * @returns {PollStartEvent} The representative poll closure event. + */ + + }], [{ + key: "from", + value: function from(pollEventId, message) { + var _content; + + return new PollEndEvent({ + type: _poll_types.M_POLL_END.name, + content: (_content = { + "m.relates_to": { + rel_type: _relationship_types.REFERENCE_RELATION.name, + event_id: pollEventId + } + }, _defineProperty(_content, _poll_types.M_POLL_END.name, {}), _defineProperty(_content, _message_types.M_TEXT.name, message), _content) + }); + } + }]); + + return PollEndEvent; +}(_ExtensibleEvent2.ExtensibleEvent); + +exports.PollEndEvent = PollEndEvent; +},{"../InvalidEventError":154,"../utility/events":173,"./ExtensibleEvent":158,"./MessageEvent":159,"./message_types":164,"./poll_types":165,"./relationship_types":166}],162:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PollResponseEvent = void 0; + +var _ExtensibleEvent2 = require("./ExtensibleEvent"); + +var _poll_types = require("./poll_types"); + +var _InvalidEventError = require("../InvalidEventError"); + +var _relationship_types = require("./relationship_types"); + +var _events = require("../utility/events"); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Represents a poll response event. + */ +var PollResponseEvent = /*#__PURE__*/function (_ExtensibleEvent) { + _inherits(PollResponseEvent, _ExtensibleEvent); + + var _super = _createSuper(PollResponseEvent); + + /** + * Creates a new PollResponseEvent from a pure format. Note that the event is *not* + * parsed here: it will be treated as a literal m.poll.response primary typed event. + * + * To validate the response against a poll, call `validateAgainst` after creation. + * @param {IPartialEvent} wireFormat The event. + */ + function PollResponseEvent(wireFormat) { + var _this; + + _classCallCheck(this, PollResponseEvent); + + _this = _super.call(this, wireFormat); + + _defineProperty(_assertThisInitialized(_this), "internalAnswerIds", void 0); + + _defineProperty(_assertThisInitialized(_this), "internalSpoiled", void 0); + + _defineProperty(_assertThisInitialized(_this), "pollEventId", void 0); + + var rel = _this.wireContent["m.relates_to"]; + + if (!_relationship_types.REFERENCE_RELATION.matches(rel === null || rel === void 0 ? void 0 : rel.rel_type) || typeof (rel === null || rel === void 0 ? void 0 : rel.event_id) !== "string") { + throw new _InvalidEventError.InvalidEventError("Relationship must be a reference to an event"); + } + + _this.pollEventId = rel.event_id; + + _this.validateAgainst(null); + + return _this; + } + /** + * Validates the poll response using the poll start event as a frame of reference. This + * is used to determine if the vote is spoiled, whether the answers are valid, etc. + * @param {PollStartEvent} poll The poll start event. + */ + + + _createClass(PollResponseEvent, [{ + key: "answerIds", + get: + /** + * The provided answers for the poll. Note that this may be falsy/unpredictable if + * the `spoiled` property is true. + */ + function get() { + return this.internalAnswerIds; + } + /** + * The poll start event ID referenced by the response. + */ + + }, { + key: "spoiled", + get: + /** + * Whether the vote is spoiled. + */ + function get() { + return this.internalSpoiled; + } + }, { + key: "validateAgainst", + value: function validateAgainst(poll) { + var response = _poll_types.M_POLL_RESPONSE.findIn(this.wireContent); + + if (!Array.isArray(response === null || response === void 0 ? void 0 : response.answers)) { + this.internalSpoiled = true; + this.internalAnswerIds = []; + return; + } + + var answers = response.answers; + + if (answers.some(function (a) { + return typeof a !== "string"; + }) || answers.length === 0) { + this.internalSpoiled = true; + this.internalAnswerIds = []; + return; + } + + if (poll) { + if (answers.some(function (a) { + return !poll.answers.some(function (pa) { + return pa.id === a; + }); + })) { + this.internalSpoiled = true; + this.internalAnswerIds = []; + return; + } + + answers = answers.slice(0, poll.maxSelections); + } + + this.internalAnswerIds = answers; + this.internalSpoiled = false; + } + }, { + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _poll_types.M_POLL_RESPONSE); + } + }, { + key: "serialize", + value: function serialize() { + return { + type: _poll_types.M_POLL_RESPONSE.name, + content: _defineProperty({ + "m.relates_to": { + rel_type: _relationship_types.REFERENCE_RELATION.name, + event_id: this.pollEventId + } + }, _poll_types.M_POLL_RESPONSE.name, { + answers: this.spoiled ? undefined : this.answerIds + }) + }; + } + /** + * Creates a new PollResponseEvent from a set of answers. To spoil the vote, pass an empty + * answers array. + * @param {string} answers The user's answers. Should be valid from a poll's answer IDs. + * @param {string} pollEventId The poll start event ID. + * @returns {PollStartEvent} The representative poll response event. + */ + + }], [{ + key: "from", + value: function from(answers, pollEventId) { + return new PollResponseEvent({ + type: _poll_types.M_POLL_RESPONSE.name, + content: _defineProperty({ + "m.relates_to": { + rel_type: _relationship_types.REFERENCE_RELATION.name, + event_id: pollEventId + } + }, _poll_types.M_POLL_RESPONSE.name, { + answers: answers + }) + }); + } + }]); + + return PollResponseEvent; +}(_ExtensibleEvent2.ExtensibleEvent); + +exports.PollResponseEvent = PollResponseEvent; +},{"../InvalidEventError":154,"../utility/events":173,"./ExtensibleEvent":158,"./poll_types":165,"./relationship_types":166}],163:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PollStartEvent = exports.PollAnswerSubevent = void 0; + +var _poll_types = require("./poll_types"); + +var _MessageEvent2 = require("./MessageEvent"); + +var _message_types = require("./message_types"); + +var _InvalidEventError = require("../InvalidEventError"); + +var _NamespacedValue = require("../NamespacedValue"); + +var _events = require("../utility/events"); + +var _ExtensibleEvent2 = require("./ExtensibleEvent"); + +function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } + +function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } + +function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Represents a poll answer. Note that this is represented as a subtype and is + * not registered as a parsable event - it is implied for usage exclusively + * within the PollStartEvent parsing. + */ +var PollAnswerSubevent = /*#__PURE__*/function (_MessageEvent) { + _inherits(PollAnswerSubevent, _MessageEvent); + + var _super = _createSuper(PollAnswerSubevent); + + /** + * The answer ID. + */ + function PollAnswerSubevent(wireFormat) { + var _this; + + _classCallCheck(this, PollAnswerSubevent); + + _this = _super.call(this, wireFormat); + + _defineProperty(_assertThisInitialized(_this), "id", void 0); + + var id = wireFormat.content.id; + + if (!id || typeof id !== "string") { + throw new _InvalidEventError.InvalidEventError("Answer ID must be a non-empty string"); + } + + _this.id = id; + return _this; + } + + _createClass(PollAnswerSubevent, [{ + key: "serialize", + value: function serialize() { + return { + type: "org.matrix.sdk.poll.answer", + content: _objectSpread({ + id: this.id + }, this.serializeMMessageOnly()) + }; + } + /** + * Creates a new PollAnswerSubevent from ID and text. + * @param {string} id The answer ID (unique within the poll). + * @param {string} text The text. + * @returns {PollAnswerSubevent} The representative answer. + */ + + }], [{ + key: "from", + value: function from(id, text) { + return new PollAnswerSubevent({ + type: "org.matrix.sdk.poll.answer", + content: _defineProperty({ + id: id + }, _message_types.M_TEXT.name, text) + }); + } + }]); + + return PollAnswerSubevent; +}(_MessageEvent2.MessageEvent); +/** + * Represents a poll start event. + */ + + +exports.PollAnswerSubevent = PollAnswerSubevent; + +var PollStartEvent = /*#__PURE__*/function (_ExtensibleEvent) { + _inherits(PollStartEvent, _ExtensibleEvent); + + var _super2 = _createSuper(PollStartEvent); + + /** + * The question being asked, as a MessageEvent node. + */ + + /** + * The interpreted kind of poll. Note that this will infer a value that is known to the + * SDK rather than verbatim - this means unknown types will be represented as undisclosed + * polls. + * + * To get the raw kind, use rawKind. + */ + + /** + * The true kind as provided by the event sender. Might not be valid. + */ + + /** + * The maximum number of selections a user is allowed to make. + */ + + /** + * The possible answers for the poll. + */ + + /** + * Creates a new PollStartEvent from a pure format. Note that the event is *not* + * parsed here: it will be treated as a literal m.poll.start primary typed event. + * @param {IPartialEvent} wireFormat The event. + */ + function PollStartEvent(wireFormat) { + var _this2; + + _classCallCheck(this, PollStartEvent); + + _this2 = _super2.call(this, wireFormat); + + _defineProperty(_assertThisInitialized(_this2), "question", void 0); + + _defineProperty(_assertThisInitialized(_this2), "kind", void 0); + + _defineProperty(_assertThisInitialized(_this2), "rawKind", void 0); + + _defineProperty(_assertThisInitialized(_this2), "maxSelections", void 0); + + _defineProperty(_assertThisInitialized(_this2), "answers", void 0); + + var poll = _poll_types.M_POLL_START.findIn(_this2.wireContent); + + if (!poll.question) { + throw new _InvalidEventError.InvalidEventError("A question is required"); + } + + _this2.question = new _MessageEvent2.MessageEvent({ + type: "org.matrix.sdk.poll.question", + content: poll.question + }); + _this2.rawKind = poll.kind; + + if (_poll_types.M_POLL_KIND_DISCLOSED.matches(_this2.rawKind)) { + _this2.kind = _poll_types.M_POLL_KIND_DISCLOSED; + } else { + _this2.kind = _poll_types.M_POLL_KIND_UNDISCLOSED; // default & assumed value + } + + _this2.maxSelections = Number.isFinite(poll.max_selections) && poll.max_selections > 0 ? poll.max_selections : 1; + + if (!Array.isArray(poll.answers)) { + throw new _InvalidEventError.InvalidEventError("Poll answers must be an array"); + } + + var answers = poll.answers.slice(0, 20).map(function (a) { + return new PollAnswerSubevent({ + type: "org.matrix.sdk.poll.answer", + content: a + }); + }); + + if (answers.length <= 0) { + throw new _InvalidEventError.InvalidEventError("No answers available"); + } + + _this2.answers = answers; + return _this2; + } + + _createClass(PollStartEvent, [{ + key: "isEquivalentTo", + value: function isEquivalentTo(primaryEventType) { + return (0, _events.isEventTypeSame)(primaryEventType, _poll_types.M_POLL_START); + } + }, { + key: "serialize", + value: function serialize() { + var _content2; + + return { + type: _poll_types.M_POLL_START.name, + content: (_content2 = {}, _defineProperty(_content2, _poll_types.M_POLL_START.name, { + question: this.question.serialize().content, + kind: this.rawKind, + max_selections: this.maxSelections, + answers: this.answers.map(function (a) { + return a.serialize().content; + }) + }), _defineProperty(_content2, _message_types.M_TEXT.name, "".concat(this.question.text, "\n").concat(this.answers.map(function (a, i) { + return "".concat(i + 1, ". ").concat(a.text); + }).join("\n"))), _content2) + }; + } + /** + * Creates a new PollStartEvent from question, answers, and metadata. + * @param {string} question The question to ask. + * @param {string} answers The answers. Should be unique within each other. + * @param {KNOWN_POLL_KIND|string} kind The kind of poll. + * @param {number} maxSelections The maximum number of selections. Must be 1 or higher. + * @returns {PollStartEvent} The representative poll start event. + */ + + }], [{ + key: "from", + value: function from(question, answers, kind) { + var _content3; + + var maxSelections = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; + return new PollStartEvent({ + type: _poll_types.M_POLL_START.name, + content: (_content3 = {}, _defineProperty(_content3, _message_types.M_TEXT.name, question), _defineProperty(_content3, _poll_types.M_POLL_START.name, { + question: _defineProperty({}, _message_types.M_TEXT.name, question), + kind: kind instanceof _NamespacedValue.NamespacedValue ? kind.name : kind, + max_selections: maxSelections, + answers: answers.map(function (a) { + return _defineProperty({ + id: makeId() + }, _message_types.M_TEXT.name, a); + }) + }), _content3) + }); + } + }]); + + return PollStartEvent; +}(_ExtensibleEvent2.ExtensibleEvent); + +exports.PollStartEvent = PollStartEvent; +var LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +function makeId() { + return _toConsumableArray(Array(16)).map(function () { + return LETTERS.charAt(Math.floor(Math.random() * LETTERS.length)); + }).join(''); +} +},{"../InvalidEventError":154,"../NamespacedValue":156,"../utility/events":173,"./ExtensibleEvent":158,"./MessageEvent":159,"./message_types":164,"./poll_types":165}],164:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.M_TEXT = exports.M_NOTICE = exports.M_MESSAGE = exports.M_HTML = exports.M_EMOTE = void 0; + +var _NamespacedValue = require("../NamespacedValue"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * The namespaced value for m.message + */ +var M_MESSAGE = new _NamespacedValue.UnstableValue("m.message", "org.matrix.msc1767.message"); +/** + * An m.message event rendering + */ + +exports.M_MESSAGE = M_MESSAGE; + +/** + * The namespaced value for m.text + */ +var M_TEXT = new _NamespacedValue.UnstableValue("m.text", "org.matrix.msc1767.text"); +/** + * The content for an m.text event + */ + +exports.M_TEXT = M_TEXT; + +/** + * The namespaced value for m.html + */ +var M_HTML = new _NamespacedValue.UnstableValue("m.html", "org.matrix.msc1767.html"); +/** + * The content for an m.html event + */ + +exports.M_HTML = M_HTML; + +/** + * The namespaced value for m.emote + */ +var M_EMOTE = new _NamespacedValue.UnstableValue("m.emote", "org.matrix.msc1767.emote"); +/** + * The event definition for an m.emote event (in content) + */ + +exports.M_EMOTE = M_EMOTE; + +/** + * The namespaced value for m.notice + */ +var M_NOTICE = new _NamespacedValue.UnstableValue("m.notice", "org.matrix.msc1767.notice"); +/** + * The event definition for an m.notice event (in content) + */ + +exports.M_NOTICE = M_NOTICE; +},{"../NamespacedValue":156}],165:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.M_POLL_START = exports.M_POLL_RESPONSE = exports.M_POLL_KIND_UNDISCLOSED = exports.M_POLL_KIND_DISCLOSED = exports.M_POLL_END = void 0; + +var _NamespacedValue = require("../NamespacedValue"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Identifier for a disclosed poll. + */ +var M_POLL_KIND_DISCLOSED = new _NamespacedValue.UnstableValue("m.poll.disclosed", "org.matrix.msc3381.poll.disclosed"); +/** + * Identifier for an undisclosed poll. + */ + +exports.M_POLL_KIND_DISCLOSED = M_POLL_KIND_DISCLOSED; +var M_POLL_KIND_UNDISCLOSED = new _NamespacedValue.UnstableValue("m.poll.undisclosed", "org.matrix.msc3381.poll.undisclosed"); +/** + * Any poll kind. + */ + +exports.M_POLL_KIND_UNDISCLOSED = M_POLL_KIND_UNDISCLOSED; + +/** + * The namespaced value for m.poll.start + */ +var M_POLL_START = new _NamespacedValue.UnstableValue("m.poll.start", "org.matrix.msc3381.poll.start"); +/** + * The m.poll.start type within event content + */ + +exports.M_POLL_START = M_POLL_START; + +/** + * The namespaced value for m.poll.response + */ +var M_POLL_RESPONSE = new _NamespacedValue.UnstableValue("m.poll.response", "org.matrix.msc3381.poll.response"); +/** + * The m.poll.response type within event content + */ + +exports.M_POLL_RESPONSE = M_POLL_RESPONSE; + +/** + * The namespaced value for m.poll.end + */ +var M_POLL_END = new _NamespacedValue.UnstableValue("m.poll.end", "org.matrix.msc3381.poll.end"); +/** + * The event definition for an m.poll.end event (in content) + */ + +exports.M_POLL_END = M_POLL_END; +},{"../NamespacedValue":156}],166:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.REFERENCE_RELATION = void 0; + +var _NamespacedValue = require("../NamespacedValue"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * The namespaced value for an m.reference relation + */ +var REFERENCE_RELATION = new _NamespacedValue.NamespacedValue("m.reference"); +/** + * Represents any relation type + */ + +exports.REFERENCE_RELATION = REFERENCE_RELATION; +},{"../NamespacedValue":156}],167:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _ExtensibleEvents = require("./ExtensibleEvents"); + +Object.keys(_ExtensibleEvents).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ExtensibleEvents[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ExtensibleEvents[key]; + } + }); +}); + +var _IPartialEvent = require("./IPartialEvent"); + +Object.keys(_IPartialEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IPartialEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IPartialEvent[key]; + } + }); +}); + +var _InvalidEventError = require("./InvalidEventError"); + +Object.keys(_InvalidEventError).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _InvalidEventError[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _InvalidEventError[key]; + } + }); +}); + +var _NamespacedValue = require("./NamespacedValue"); + +Object.keys(_NamespacedValue).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _NamespacedValue[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _NamespacedValue[key]; + } + }); +}); + +var _NamespacedMap = require("./NamespacedMap"); + +Object.keys(_NamespacedMap).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _NamespacedMap[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _NamespacedMap[key]; + } + }); +}); + +var _types = require("./types"); + +Object.keys(_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _types[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _types[key]; + } + }); +}); + +var _MessageMatchers = require("./utility/MessageMatchers"); + +Object.keys(_MessageMatchers).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _MessageMatchers[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _MessageMatchers[key]; + } + }); +}); + +var _events = require("./utility/events"); + +Object.keys(_events).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _events[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _events[key]; + } + }); +}); + +var _MRoomMessage = require("./interpreters/legacy/MRoomMessage"); + +Object.keys(_MRoomMessage).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _MRoomMessage[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _MRoomMessage[key]; + } + }); +}); + +var _MMessage = require("./interpreters/modern/MMessage"); + +Object.keys(_MMessage).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _MMessage[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _MMessage[key]; + } + }); +}); + +var _MPoll = require("./interpreters/modern/MPoll"); + +Object.keys(_MPoll).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _MPoll[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _MPoll[key]; + } + }); +}); + +var _relationship_types = require("./events/relationship_types"); + +Object.keys(_relationship_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _relationship_types[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _relationship_types[key]; + } + }); +}); + +var _ExtensibleEvent = require("./events/ExtensibleEvent"); + +Object.keys(_ExtensibleEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ExtensibleEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ExtensibleEvent[key]; + } + }); +}); + +var _message_types = require("./events/message_types"); + +Object.keys(_message_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _message_types[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _message_types[key]; + } + }); +}); + +var _MessageEvent = require("./events/MessageEvent"); + +Object.keys(_MessageEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _MessageEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _MessageEvent[key]; + } + }); +}); + +var _EmoteEvent = require("./events/EmoteEvent"); + +Object.keys(_EmoteEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _EmoteEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _EmoteEvent[key]; + } + }); +}); + +var _NoticeEvent = require("./events/NoticeEvent"); + +Object.keys(_NoticeEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _NoticeEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _NoticeEvent[key]; + } + }); +}); + +var _poll_types = require("./events/poll_types"); + +Object.keys(_poll_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _poll_types[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _poll_types[key]; + } + }); +}); + +var _PollStartEvent = require("./events/PollStartEvent"); + +Object.keys(_PollStartEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _PollStartEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _PollStartEvent[key]; + } + }); +}); + +var _PollResponseEvent = require("./events/PollResponseEvent"); + +Object.keys(_PollResponseEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _PollResponseEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _PollResponseEvent[key]; + } + }); +}); + +var _PollEndEvent = require("./events/PollEndEvent"); + +Object.keys(_PollEndEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _PollEndEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _PollEndEvent[key]; + } + }); +}); +},{"./ExtensibleEvents":152,"./IPartialEvent":153,"./InvalidEventError":154,"./NamespacedMap":155,"./NamespacedValue":156,"./events/EmoteEvent":157,"./events/ExtensibleEvent":158,"./events/MessageEvent":159,"./events/NoticeEvent":160,"./events/PollEndEvent":161,"./events/PollResponseEvent":162,"./events/PollStartEvent":163,"./events/message_types":164,"./events/poll_types":165,"./events/relationship_types":166,"./interpreters/legacy/MRoomMessage":168,"./interpreters/modern/MMessage":169,"./interpreters/modern/MPoll":170,"./types":171,"./utility/MessageMatchers":172,"./utility/events":173}],168:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LEGACY_M_ROOM_MESSAGE = void 0; +exports.parseMRoomMessage = parseMRoomMessage; + +var _MessageEvent = require("../../events/MessageEvent"); + +var _NoticeEvent = require("../../events/NoticeEvent"); + +var _EmoteEvent = require("../../events/EmoteEvent"); + +var _NamespacedValue = require("../../NamespacedValue"); + +var _message_types = require("../../events/message_types"); + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var LEGACY_M_ROOM_MESSAGE = new _NamespacedValue.NamespacedValue("m.room.message"); +exports.LEGACY_M_ROOM_MESSAGE = LEGACY_M_ROOM_MESSAGE; + +function parseMRoomMessage(wireEvent) { + var _wireEvent$content, _wireEvent$content2, _wireEvent$content3; + + if (_message_types.M_MESSAGE.findIn(wireEvent.content) || _message_types.M_TEXT.findIn(wireEvent.content)) { + // We know enough about the event to coerce it into the right type + return new _MessageEvent.MessageEvent(wireEvent); + } + + var msgtype = (_wireEvent$content = wireEvent.content) === null || _wireEvent$content === void 0 ? void 0 : _wireEvent$content.msgtype; + var text = (_wireEvent$content2 = wireEvent.content) === null || _wireEvent$content2 === void 0 ? void 0 : _wireEvent$content2.body; + var html = ((_wireEvent$content3 = wireEvent.content) === null || _wireEvent$content3 === void 0 ? void 0 : _wireEvent$content3.format) === "org.matrix.custom.html" ? wireEvent.content.formatted_body : null; + + if (msgtype === "m.text") { + var _objectSpread2; + + return new _MessageEvent.MessageEvent(_objectSpread(_objectSpread({}, wireEvent), {}, { + content: _objectSpread(_objectSpread({}, wireEvent.content), {}, (_objectSpread2 = {}, _defineProperty(_objectSpread2, _message_types.M_TEXT.name, text), _defineProperty(_objectSpread2, _message_types.M_HTML.name, html), _objectSpread2)) + })); + } else if (msgtype === "m.notice") { + var _objectSpread3; + + return new _NoticeEvent.NoticeEvent(_objectSpread(_objectSpread({}, wireEvent), {}, { + content: _objectSpread(_objectSpread({}, wireEvent.content), {}, (_objectSpread3 = {}, _defineProperty(_objectSpread3, _message_types.M_TEXT.name, text), _defineProperty(_objectSpread3, _message_types.M_HTML.name, html), _objectSpread3)) + })); + } else if (msgtype === "m.emote") { + var _objectSpread4; + + return new _EmoteEvent.EmoteEvent(_objectSpread(_objectSpread({}, wireEvent), {}, { + content: _objectSpread(_objectSpread({}, wireEvent.content), {}, (_objectSpread4 = {}, _defineProperty(_objectSpread4, _message_types.M_TEXT.name, text), _defineProperty(_objectSpread4, _message_types.M_HTML.name, html), _objectSpread4)) + })); + } else { + // TODO: Handle other types + return null; + } +} +},{"../../NamespacedValue":156,"../../events/EmoteEvent":157,"../../events/MessageEvent":159,"../../events/NoticeEvent":160,"../../events/message_types":164}],169:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.parseMMessage = parseMMessage; + +var _MessageEvent = require("../../events/MessageEvent"); + +var _message_types = require("../../events/message_types"); + +var _EmoteEvent = require("../../events/EmoteEvent"); + +var _NoticeEvent = require("../../events/NoticeEvent"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +function parseMMessage(wireEvent) { + if (_message_types.M_EMOTE.matches(wireEvent.type)) { + return new _EmoteEvent.EmoteEvent(wireEvent); + } else if (_message_types.M_NOTICE.matches(wireEvent.type)) { + return new _NoticeEvent.NoticeEvent(wireEvent); + } // default: return a generic message + + + return new _MessageEvent.MessageEvent(wireEvent); +} +},{"../../events/EmoteEvent":157,"../../events/MessageEvent":159,"../../events/NoticeEvent":160,"../../events/message_types":164}],170:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.parseMPoll = parseMPoll; + +var _poll_types = require("../../events/poll_types"); + +var _PollStartEvent = require("../../events/PollStartEvent"); + +var _PollResponseEvent = require("../../events/PollResponseEvent"); + +var _PollEndEvent = require("../../events/PollEndEvent"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +function parseMPoll(wireEvent) { + if (_poll_types.M_POLL_START.matches(wireEvent.type)) { + return new _PollStartEvent.PollStartEvent(wireEvent); + } else if (_poll_types.M_POLL_RESPONSE.matches(wireEvent.type)) { + return new _PollResponseEvent.PollResponseEvent(wireEvent); + } else if (_poll_types.M_POLL_END.matches(wireEvent.type)) { + return new _PollEndEvent.PollEndEvent(wireEvent); + } + + return null; // not a poll event +} +},{"../../events/PollEndEvent":161,"../../events/PollResponseEvent":162,"../../events/PollStartEvent":163,"../../events/poll_types":165}],171:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isOptionalAString = isOptionalAString; +exports.isProvided = isProvided; + +/* +Copyright 2021 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Represents an optional type: can either be T or a falsy value. + */ + +/** + * Determines if the given optional string is a defined string. + * @param {Optional} s The input string. + * @returns {boolean} True if the input is a defined string. + */ +function isOptionalAString(s) { + return isProvided(s) && typeof s === 'string'; +} +/** + * Determines if the given optional was provided a value. + * @param {Optional} s The optional to test. + * @returns {boolean} True if the value is defined. + */ + + +function isProvided(s) { + return s !== null && s !== undefined; +} +/** + * Represents either just T1, just T2, or T1 and T2 mixed. + */ +},{}],172:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LegacyMsgType = void 0; +exports.isEventLike = isEventLike; + +var _message_types = require("../events/message_types"); + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Represents a legacy m.room.message msgtype + */ +var LegacyMsgType; +/** + * Determines if the given partial event looks similar enough to the given legacy msgtype + * to count as that message type. + * @param {IPartialEvent>} event The event. + * @param {LegacyMsgType} msgtype The message type to compare for. + * @returns {boolean} True if the event appears to look similar enough to the msgtype. + */ + +exports.LegacyMsgType = LegacyMsgType; + +(function (LegacyMsgType) { + LegacyMsgType["Text"] = "m.text"; + LegacyMsgType["Notice"] = "m.notice"; + LegacyMsgType["Emote"] = "m.emote"; +})(LegacyMsgType || (exports.LegacyMsgType = LegacyMsgType = {})); + +function isEventLike(event, msgtype) { + var content = event.content; + + if (msgtype === LegacyMsgType.Text) { + return _message_types.M_MESSAGE.matches(event.type) || event.type === "m.room.message" && (content === null || content === void 0 ? void 0 : content['msgtype']) === "m.text"; + } else if (msgtype === LegacyMsgType.Emote) { + return _message_types.M_EMOTE.matches(event.type) || event.type === "m.room.message" && (content === null || content === void 0 ? void 0 : content['msgtype']) === "m.emote"; + } else if (msgtype === LegacyMsgType.Notice) { + return _message_types.M_NOTICE.matches(event.type) || event.type === "m.room.message" && (content === null || content === void 0 ? void 0 : content['msgtype']) === "m.notice"; + } + + return false; +} +},{"../events/message_types":164}],173:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isEventTypeSame = isEventTypeSame; + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +/** + * Represents a potentially namespaced event type. + */ + +/** + * Determines if two event types are the same, including namespaces. + * @param {EventType} given The given event type. This will be compared + * against the expected type. + * @param {EventType} expected The expected event type. + * @returns {boolean} True if the given type matches the expected type. + */ +function isEventTypeSame(given, expected) { + if (typeof given === "string") { + if (typeof expected === "string") { + return expected === given; + } else { + return expected.matches(given); + } + } else { + if (typeof expected === "string") { + return given.matches(expected); + } else { + var expectedNs = expected; + var givenNs = given; + return expectedNs.matches(givenNs.name) || expectedNs.matches(givenNs.altName); + } + } +} +},{}],174:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ClientWidgetApi = void 0; + +var _events = require("events"); + +var _PostmessageTransport = require("./transport/PostmessageTransport"); + +var _WidgetApiDirection = require("./interfaces/WidgetApiDirection"); + +var _WidgetApiAction = require("./interfaces/WidgetApiAction"); + +var _Capabilities = require("./interfaces/Capabilities"); + +var _ApiVersion = require("./interfaces/ApiVersion"); + +var _WidgetEventCapability = require("./models/WidgetEventCapability"); + +var _GetOpenIDAction = require("./interfaces/GetOpenIDAction"); + +var _SimpleObservable = require("./util/SimpleObservable"); + +var _Symbols = require("./Symbols"); + +function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return exports; }; var exports = {}, Op = Object.prototype, hasOwn = Op.hasOwnProperty, $Symbol = "function" == typeof Symbol ? Symbol : {}, iteratorSymbol = $Symbol.iterator || "@@iterator", asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function define(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }), obj[key]; } try { define({}, ""); } catch (err) { define = function define(obj, key, value) { return obj[key] = value; }; } function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, generator = Object.create(protoGenerator.prototype), context = new Context(tryLocsList || []); return generator._invoke = function (innerFn, self, context) { var state = "suspendedStart"; return function (method, arg) { if ("executing" === state) throw new Error("Generator is already running"); if ("completed" === state) { if ("throw" === method) throw arg; return doneResult(); } for (context.method = method, context.arg = arg;;) { var delegate = context.delegate; if (delegate) { var delegateResult = maybeInvokeDelegate(delegate, context); if (delegateResult) { if (delegateResult === ContinueSentinel) continue; return delegateResult; } } if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { if ("suspendedStart" === state) throw state = "completed", context.arg; context.dispatchException(context.arg); } else "return" === context.method && context.abrupt("return", context.arg); state = "executing"; var record = tryCatch(innerFn, self, context); if ("normal" === record.type) { if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; return { value: record.arg, done: context.done }; } "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } }; }(innerFn, self, context), generator; } function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } } exports.wrap = wrap; var ContinueSentinel = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var IteratorPrototype = {}; define(IteratorPrototype, iteratorSymbol, function () { return this; }); var getProto = Object.getPrototypeOf, NativeIteratorPrototype = getProto && getProto(getProto(values([]))); NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function (method) { define(prototype, method, function (arg) { return this._invoke(method, arg); }); }); } function AsyncIterator(generator, PromiseImpl) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); if ("throw" !== record.type) { var result = record.arg, value = result.value; return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { invoke("next", value, resolve, reject); }, function (err) { invoke("throw", err, resolve, reject); }) : PromiseImpl.resolve(value).then(function (unwrapped) { result.value = unwrapped, resolve(result); }, function (error) { return invoke("throw", error, resolve, reject); }); } reject(record.arg); } var previousPromise; this._invoke = function (method, arg) { function callInvokeWithMethodAndArg() { return new PromiseImpl(function (resolve, reject) { invoke(method, arg, resolve, reject); }); } return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); }; } function maybeInvokeDelegate(delegate, context) { var method = delegate.iterator[context.method]; if (undefined === method) { if (context.delegate = null, "throw" === context.method) { if (delegate.iterator["return"] && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method)) return ContinueSentinel; context.method = "throw", context.arg = new TypeError("The iterator does not provide a 'throw' method"); } return ContinueSentinel; } var record = tryCatch(method, delegate.iterator, context.arg); if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; var info = record.arg; return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); } function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); } function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal", delete record.arg, entry.completion = record; } function Context(tryLocsList) { this.tryEntries = [{ tryLoc: "root" }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); } function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) return iteratorMethod.call(iterable); if ("function" == typeof iterable.next) return iterable; if (!isNaN(iterable.length)) { var i = -1, next = function next() { for (; ++i < iterable.length;) { if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; } return next.value = undefined, next.done = !0, next; }; return next.next = next; } } return { next: doneResult }; } function doneResult() { return { value: undefined, done: !0 }; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, define(Gp, "constructor", GeneratorFunctionPrototype), define(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { var ctor = "function" == typeof genFun && genFun.constructor; return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); }, exports.mark = function (genFun) { return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; }, exports.awrap = function (arg) { return { __await: arg }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { return this; }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { void 0 === PromiseImpl && (PromiseImpl = Promise); var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { return result.done ? result.value : iter.next(); }); }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { return this; }), define(Gp, "toString", function () { return "[object Generator]"; }), exports.keys = function (object) { var keys = []; for (var key in object) { keys.push(key); } return keys.reverse(), function next() { for (; keys.length;) { var key = keys.pop(); if (key in object) return next.value = key, next.done = !1, next; } return next.done = !0, next; }; }, exports.values = values, Context.prototype = { constructor: Context, reset: function reset(skipTempReset) { if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) { "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); } }, stop: function stop() { this.done = !0; var rootRecord = this.tryEntries[0].completion; if ("throw" === rootRecord.type) throw rootRecord.arg; return this.rval; }, dispatchException: function dispatchException(exception) { if (this.done) throw exception; var context = this; function handle(loc, caught) { return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; } for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i], record = entry.completion; if ("root" === entry.tryLoc) return handle("end"); if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"), hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } else if (hasCatch) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); } else { if (!hasFinally) throw new Error("try statement without catch or finally"); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } } } }, abrupt: function abrupt(type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); var record = finallyEntry ? finallyEntry.completion : {}; return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); }, complete: function complete(record, afterLoc) { if ("throw" === record.type) throw record.arg; return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; }, finish: function finish(finallyLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; } }, "catch": function _catch(tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc === tryLoc) { var record = entry.completion; if ("throw" === record.type) { var thrown = record.arg; resetTryEntry(entry); } return thrown; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(iterable, resultName, nextLoc) { return this.delegate = { iterator: values(iterable), resultName: resultName, nextLoc: nextLoc }, "next" === this.method && (this.arg = undefined), ContinueSentinel; } }, exports; } + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } + +function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _asyncIterator(iterable) { var method, async, sync, retry = 2; for ("undefined" != typeof Symbol && (async = Symbol.asyncIterator, sync = Symbol.iterator); retry--;) { if (async && null != (method = iterable[async])) return method.call(iterable); if (sync && null != (method = iterable[sync])) return new AsyncFromSyncIterator(method.call(iterable)); async = "@@asyncIterator", sync = "@@iterator"; } throw new TypeError("Object is not async iterable"); } + +function AsyncFromSyncIterator(s) { function AsyncFromSyncIteratorContinuation(r) { if (Object(r) !== r) return Promise.reject(new TypeError(r + " is not an object.")); var done = r.done; return Promise.resolve(r.value).then(function (value) { return { value: value, done: done }; }); } return AsyncFromSyncIterator = function AsyncFromSyncIterator(s) { this.s = s, this.n = s.next; }, AsyncFromSyncIterator.prototype = { s: null, n: null, next: function next() { return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments)); }, "return": function _return(value) { var ret = this.s["return"]; return void 0 === ret ? Promise.resolve({ value: value, done: !0 }) : AsyncFromSyncIteratorContinuation(ret.apply(this.s, arguments)); }, "throw": function _throw(value) { var thr = this.s["return"]; return void 0 === thr ? Promise.reject(value) : AsyncFromSyncIteratorContinuation(thr.apply(this.s, arguments)); } }, new AsyncFromSyncIterator(s); } + +/** + * API handler for the client side of widgets. This raises events + * for each action received as `action:${action}` (eg: "action:screenshot"). + * Default handling can be prevented by using preventDefault() on the + * raised event. The default handling varies for each action: ones + * which the SDK can handle safely are acknowledged appropriately and + * ones which are unhandled (custom or require the client to do something) + * are rejected with an error. + * + * Events which are preventDefault()ed must reply using the transport. + * The events raised will have a default of an IWidgetApiRequest + * interface. + * + * When the ClientWidgetApi is ready to start sending requests, it will + * raise a "ready" CustomEvent. After the ready event fires, actions can + * be sent and the transport will be ready. + * + * When the widget has indicated it has loaded, this class raises a + * "preparing" CustomEvent. The preparing event does not indicate that + * the widget is ready to receive communications - that is signified by + * the ready event exclusively. + * + * This class only handles one widget at a time. + */ +var ClientWidgetApi = /*#__PURE__*/function (_EventEmitter) { + _inherits(ClientWidgetApi, _EventEmitter); + + var _super = _createSuper(ClientWidgetApi); + + // contentLoadedActionSent is used to check that only one ContentLoaded request is send. + + /** + * Creates a new client widget API. This will instantiate the transport + * and start everything. When the iframe is loaded under the widget's + * conditions, a "ready" event will be raised. + * @param {Widget} widget The widget to communicate with. + * @param {HTMLIFrameElement} iframe The iframe the widget is in. + * @param {WidgetDriver} driver The driver for this widget/client. + */ + function ClientWidgetApi(widget, iframe, driver) { + var _this; + + _classCallCheck(this, ClientWidgetApi); + + _this = _super.call(this); + _this.widget = widget; + _this.iframe = iframe; + _this.driver = driver; + + _defineProperty(_assertThisInitialized(_this), "transport", void 0); + + _defineProperty(_assertThisInitialized(_this), "contentLoadedActionSent", false); + + _defineProperty(_assertThisInitialized(_this), "allowedCapabilities", new Set()); + + _defineProperty(_assertThisInitialized(_this), "allowedEvents", []); + + _defineProperty(_assertThisInitialized(_this), "isStopped", false); + + _defineProperty(_assertThisInitialized(_this), "turnServers", null); + + if (!(iframe !== null && iframe !== void 0 && iframe.contentWindow)) { + throw new Error("No iframe supplied"); + } + + if (!widget) { + throw new Error("Invalid widget"); + } + + if (!driver) { + throw new Error("Invalid driver"); + } + + _this.transport = new _PostmessageTransport.PostmessageTransport(_WidgetApiDirection.WidgetApiDirection.ToWidget, widget.id, iframe.contentWindow, window); + _this.transport.targetOrigin = widget.origin; + + _this.transport.on("message", _this.handleMessage.bind(_assertThisInitialized(_this))); + + iframe.addEventListener("load", _this.onIframeLoad.bind(_assertThisInitialized(_this))); + + _this.transport.start(); + + return _this; + } + + _createClass(ClientWidgetApi, [{ + key: "hasCapability", + value: function hasCapability(capability) { + return this.allowedCapabilities.has(capability); + } + }, { + key: "canUseRoomTimeline", + value: function canUseRoomTimeline(roomId) { + return this.hasCapability("org.matrix.msc2762.timeline:".concat(_Symbols.Symbols.AnyRoom)) || this.hasCapability("org.matrix.msc2762.timeline:".concat(roomId)); + } + }, { + key: "canSendRoomEvent", + value: function canSendRoomEvent(eventType) { + var msgtype = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + return this.allowedEvents.some(function (e) { + return e.matchesAsRoomEvent(_WidgetEventCapability.EventDirection.Send, eventType, msgtype); + }); + } + }, { + key: "canSendStateEvent", + value: function canSendStateEvent(eventType, stateKey) { + return this.allowedEvents.some(function (e) { + return e.matchesAsStateEvent(_WidgetEventCapability.EventDirection.Send, eventType, stateKey); + }); + } + }, { + key: "canSendToDeviceEvent", + value: function canSendToDeviceEvent(eventType) { + return this.allowedEvents.some(function (e) { + return e.matchesAsToDeviceEvent(_WidgetEventCapability.EventDirection.Send, eventType); + }); + } + }, { + key: "canReceiveRoomEvent", + value: function canReceiveRoomEvent(eventType) { + var msgtype = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + return this.allowedEvents.some(function (e) { + return e.matchesAsRoomEvent(_WidgetEventCapability.EventDirection.Receive, eventType, msgtype); + }); + } + }, { + key: "canReceiveStateEvent", + value: function canReceiveStateEvent(eventType, stateKey) { + return this.allowedEvents.some(function (e) { + return e.matchesAsStateEvent(_WidgetEventCapability.EventDirection.Receive, eventType, stateKey); + }); + } + }, { + key: "canReceiveToDeviceEvent", + value: function canReceiveToDeviceEvent(eventType) { + return this.allowedEvents.some(function (e) { + return e.matchesAsToDeviceEvent(_WidgetEventCapability.EventDirection.Receive, eventType); + }); + } + }, { + key: "stop", + value: function stop() { + this.isStopped = true; + this.transport.stop(); + } + }, { + key: "beginCapabilities", + value: function beginCapabilities() { + var _this2 = this; + + // widget has loaded - tell all the listeners that + this.emit("preparing"); + var requestedCaps; + this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.Capabilities, {}).then(function (caps) { + requestedCaps = caps.capabilities; + return _this2.driver.validateCapabilities(new Set(caps.capabilities)); + }).then(function (allowedCaps) { + console.log("Widget ".concat(_this2.widget.id, " is allowed capabilities:"), Array.from(allowedCaps)); + _this2.allowedCapabilities = allowedCaps; + _this2.allowedEvents = _WidgetEventCapability.WidgetEventCapability.findEventCapabilities(allowedCaps); + + _this2.notifyCapabilities(requestedCaps); + + _this2.emit("ready"); + }); + } + }, { + key: "notifyCapabilities", + value: function notifyCapabilities(requested) { + var _this3 = this; + + this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.NotifyCapabilities, { + requested: requested, + approved: Array.from(this.allowedCapabilities) + })["catch"](function (e) { + console.warn("non-fatal error notifying widget of approved capabilities:", e); + }).then(function () { + _this3.emit("capabilitiesNotified"); + }); + } + }, { + key: "onIframeLoad", + value: function onIframeLoad(ev) { + if (this.widget.waitForIframeLoad) { + // If the widget is set to waitForIframeLoad the capabilities immediatly get setup after load. + // The client does not wait for the ContentLoaded action. + this.beginCapabilities(); + } else { + // Reaching this means, that the Iframe got reloaded/loaded and + // the clientApi is awaiting the FIRST ContentLoaded action. + this.contentLoadedActionSent = false; + } + } + }, { + key: "handleContentLoadedAction", + value: function handleContentLoadedAction(action) { + if (this.contentLoadedActionSent) { + throw new Error("Improper sequence: ContentLoaded Action can only be send once after the widget loaded " + "and should only be used if waitForIframeLoad is false (default=true)"); + } + + if (this.widget.waitForIframeLoad) { + this.transport.reply(action, { + error: { + message: "Improper sequence: not expecting ContentLoaded event if " + "waitForIframLoad is true (default=true)" + } + }); + } else { + this.transport.reply(action, {}); + this.beginCapabilities(); + } + + this.contentLoadedActionSent = true; + } + }, { + key: "replyVersions", + value: function replyVersions(request) { + this.transport.reply(request, { + supported_versions: _ApiVersion.CurrentApiVersions + }); + } + }, { + key: "handleCapabilitiesRenegotiate", + value: function handleCapabilitiesRenegotiate(request) { + var _request$data, + _this4 = this; + + // acknowledge first + this.transport.reply(request, {}); + var requested = ((_request$data = request.data) === null || _request$data === void 0 ? void 0 : _request$data.capabilities) || []; + var newlyRequested = new Set(requested.filter(function (r) { + return !_this4.hasCapability(r); + })); + + if (newlyRequested.size === 0) { + // Nothing to do - notify capabilities + return this.notifyCapabilities([]); + } + + this.driver.validateCapabilities(newlyRequested).then(function (allowed) { + allowed.forEach(function (c) { + return _this4.allowedCapabilities.add(c); + }); + + var allowedEvents = _WidgetEventCapability.WidgetEventCapability.findEventCapabilities(allowed); + + allowedEvents.forEach(function (c) { + return _this4.allowedEvents.push(c); + }); + return _this4.notifyCapabilities(Array.from(newlyRequested)); + }); + } + }, { + key: "handleNavigate", + value: function handleNavigate(request) { + var _request$data2, + _request$data3, + _this5 = this; + + if (!this.hasCapability(_Capabilities.MatrixCapabilities.MSC2931Navigate)) { + return this.transport.reply(request, { + error: { + message: "Missing capability" + } + }); + } + + if (!((_request$data2 = request.data) !== null && _request$data2 !== void 0 && _request$data2.uri) || !((_request$data3 = request.data) !== null && _request$data3 !== void 0 && _request$data3.uri.toString().startsWith("https://matrix.to/#"))) { + return this.transport.reply(request, { + error: { + message: "Invalid matrix.to URI" + } + }); + } + + var onErr = function onErr(e) { + console.error("[ClientWidgetApi] Failed to handle navigation: ", e); + return _this5.transport.reply(request, { + error: { + message: "Error handling navigation" + } + }); + }; + + try { + this.driver.navigate(request.data.uri.toString())["catch"](function (e) { + return onErr(e); + }).then(function () { + return _this5.transport.reply(request, {}); + }); + } catch (e) { + return onErr(e); + } + } + }, { + key: "handleOIDC", + value: function handleOIDC(request) { + var _this6 = this; + + var phase = 1; // 1 = initial request, 2 = after user manual confirmation + + var replyState = function replyState(state, credential) { + credential = credential || {}; + + if (phase > 1) { + return _this6.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.OpenIDCredentials, _objectSpread({ + state: state, + original_request_id: request.requestId + }, credential)); + } else { + return _this6.transport.reply(request, _objectSpread({ + state: state + }, credential)); + } + }; + + var replyError = function replyError(msg) { + console.error("[ClientWidgetApi] Failed to handle OIDC: ", msg); + + if (phase > 1) { + // We don't have a way to indicate that a random error happened in this flow, so + // just block the attempt. + return replyState(_GetOpenIDAction.OpenIDRequestState.Blocked); + } else { + return _this6.transport.reply(request, { + error: { + message: msg + } + }); + } + }; + + var observer = new _SimpleObservable.SimpleObservable(function (update) { + if (update.state === _GetOpenIDAction.OpenIDRequestState.PendingUserConfirmation && phase > 1) { + observer.close(); + return replyError("client provided out-of-phase response to OIDC flow"); + } + + if (update.state === _GetOpenIDAction.OpenIDRequestState.PendingUserConfirmation) { + replyState(update.state); + phase++; + return; + } + + if (update.state === _GetOpenIDAction.OpenIDRequestState.Allowed && !update.token) { + return replyError("client provided invalid OIDC token for an allowed request"); + } + + if (update.state === _GetOpenIDAction.OpenIDRequestState.Blocked) { + update.token = undefined; // just in case the client did something weird + } + + observer.close(); + return replyState(update.state, update.token); + }); + this.driver.askOpenID(observer); + } + }, { + key: "handleReadEvents", + value: function handleReadEvents(request) { + var _this7 = this; + + if (!request.data.type) { + return this.transport.reply(request, { + error: { + message: "Invalid request - missing event type" + } + }); + } + + if (request.data.limit !== undefined && (!request.data.limit || request.data.limit < 0)) { + return this.transport.reply(request, { + error: { + message: "Invalid request - limit out of range" + } + }); + } + + var askRoomIds = null; // null denotes current room only + + if (request.data.room_ids) { + askRoomIds = request.data.room_ids; + + if (!Array.isArray(askRoomIds)) { + askRoomIds = [askRoomIds]; + } + + var _iterator2 = _createForOfIteratorHelper(askRoomIds), + _step2; + + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var roomId = _step2.value; + + if (!this.canUseRoomTimeline(roomId)) { + return this.transport.reply(request, { + error: { + message: "Unable to access room timeline: ".concat(roomId) + } + }); + } + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } + } + + var limit = request.data.limit || 0; + var events = Promise.resolve([]); + + if (request.data.state_key !== undefined) { + var stateKey = request.data.state_key === true ? undefined : request.data.state_key.toString(); + + if (!this.canReceiveStateEvent(request.data.type, stateKey !== null && stateKey !== void 0 ? stateKey : null)) { + return this.transport.reply(request, { + error: { + message: "Cannot read state events of this type" + } + }); + } + + events = this.driver.readStateEvents(request.data.type, stateKey, limit, askRoomIds); + } else { + if (!this.canReceiveRoomEvent(request.data.type, request.data.msgtype)) { + return this.transport.reply(request, { + error: { + message: "Cannot read room events of this type" + } + }); + } + + events = this.driver.readRoomEvents(request.data.type, request.data.msgtype, limit, askRoomIds); + } + + return events.then(function (evs) { + return _this7.transport.reply(request, { + events: evs + }); + }); + } + }, { + key: "handleSendEvent", + value: function handleSendEvent(request) { + var _this8 = this; + + if (!request.data.type) { + return this.transport.reply(request, { + error: { + message: "Invalid request - missing event type" + } + }); + } + + if (!!request.data.room_id && !this.canUseRoomTimeline(request.data.room_id)) { + return this.transport.reply(request, { + error: { + message: "Unable to access room timeline: ".concat(request.data.room_id) + } + }); + } + + var isState = request.data.state_key !== null && request.data.state_key !== undefined; + var sendEventPromise; + + if (isState) { + if (!this.canSendStateEvent(request.data.type, request.data.state_key)) { + return this.transport.reply(request, { + error: { + message: "Cannot send state events of this type" + } + }); + } + + sendEventPromise = this.driver.sendEvent(request.data.type, request.data.content || {}, request.data.state_key, request.data.room_id); + } else { + var content = request.data.content || {}; + var msgtype = content['msgtype']; + + if (!this.canSendRoomEvent(request.data.type, msgtype)) { + return this.transport.reply(request, { + error: { + message: "Cannot send room events of this type" + } + }); + } + + sendEventPromise = this.driver.sendEvent(request.data.type, content, null, // not sending a state event + request.data.room_id); + } + + sendEventPromise.then(function (sentEvent) { + return _this8.transport.reply(request, { + room_id: sentEvent.roomId, + event_id: sentEvent.eventId + }); + })["catch"](function (e) { + console.error("error sending event: ", e); + return _this8.transport.reply(request, { + error: { + message: "Error sending event" + } + }); + }); + } + }, { + key: "handleSendToDevice", + value: function () { + var _handleSendToDevice = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(request) { + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (request.data.type) { + _context.next = 5; + break; + } + + _context.next = 3; + return this.transport.reply(request, { + error: { + message: "Invalid request - missing event type" + } + }); + + case 3: + _context.next = 32; + break; + + case 5: + if (request.data.messages) { + _context.next = 10; + break; + } + + _context.next = 8; + return this.transport.reply(request, { + error: { + message: "Invalid request - missing event contents" + } + }); + + case 8: + _context.next = 32; + break; + + case 10: + if (!(typeof request.data.encrypted !== "boolean")) { + _context.next = 15; + break; + } + + _context.next = 13; + return this.transport.reply(request, { + error: { + message: "Invalid request - missing encryption flag" + } + }); + + case 13: + _context.next = 32; + break; + + case 15: + if (this.canSendToDeviceEvent(request.data.type)) { + _context.next = 20; + break; + } + + _context.next = 18; + return this.transport.reply(request, { + error: { + message: "Cannot send to-device events of this type" + } + }); + + case 18: + _context.next = 32; + break; + + case 20: + _context.prev = 20; + _context.next = 23; + return this.driver.sendToDevice(request.data.type, request.data.encrypted, request.data.messages); + + case 23: + _context.next = 25; + return this.transport.reply(request, {}); + + case 25: + _context.next = 32; + break; + + case 27: + _context.prev = 27; + _context.t0 = _context["catch"](20); + console.error("error sending to-device event", _context.t0); + _context.next = 32; + return this.transport.reply(request, { + error: { + message: "Error sending event" + } + }); + + case 32: + case "end": + return _context.stop(); + } + } + }, _callee, this, [[20, 27]]); + })); + + function handleSendToDevice(_x) { + return _handleSendToDevice.apply(this, arguments); + } + + return handleSendToDevice; + }() + }, { + key: "pollTurnServers", + value: function () { + var _pollTurnServers = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(turnServers, initialServer) { + var _iteratorAbruptCompletion, _didIteratorError, _iteratorError, _iterator, _step, server; + + return _regeneratorRuntime().wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.prev = 0; + _context2.next = 3; + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.UpdateTurnServers, initialServer // it's compatible, but missing the index signature + ); + + case 3: + // Pick the generator up where we left off + _iteratorAbruptCompletion = false; + _didIteratorError = false; + _context2.prev = 5; + _iterator = _asyncIterator(turnServers); + + case 7: + _context2.next = 9; + return _iterator.next(); + + case 9: + if (!(_iteratorAbruptCompletion = !(_step = _context2.sent).done)) { + _context2.next = 16; + break; + } + + server = _step.value; + _context2.next = 13; + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.UpdateTurnServers, server // it's compatible, but missing the index signature + ); + + case 13: + _iteratorAbruptCompletion = false; + _context2.next = 7; + break; + + case 16: + _context2.next = 22; + break; + + case 18: + _context2.prev = 18; + _context2.t0 = _context2["catch"](5); + _didIteratorError = true; + _iteratorError = _context2.t0; + + case 22: + _context2.prev = 22; + _context2.prev = 23; + + if (!(_iteratorAbruptCompletion && _iterator["return"] != null)) { + _context2.next = 27; + break; + } + + _context2.next = 27; + return _iterator["return"](); + + case 27: + _context2.prev = 27; + + if (!_didIteratorError) { + _context2.next = 30; + break; + } + + throw _iteratorError; + + case 30: + return _context2.finish(27); + + case 31: + return _context2.finish(22); + + case 32: + _context2.next = 37; + break; + + case 34: + _context2.prev = 34; + _context2.t1 = _context2["catch"](0); + console.error("error polling for TURN servers", _context2.t1); + + case 37: + case "end": + return _context2.stop(); + } + } + }, _callee2, this, [[0, 34], [5, 18, 22, 32], [23,, 27, 31]]); + })); + + function pollTurnServers(_x2, _x3) { + return _pollTurnServers.apply(this, arguments); + } + + return pollTurnServers; + }() + }, { + key: "handleWatchTurnServers", + value: function () { + var _handleWatchTurnServers = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(request) { + var turnServers, _yield$turnServers$ne, done, value; + + return _regeneratorRuntime().wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + if (this.hasCapability(_Capabilities.MatrixCapabilities.MSC3846TurnServers)) { + _context3.next = 5; + break; + } + + _context3.next = 3; + return this.transport.reply(request, { + error: { + message: "Missing capability" + } + }); + + case 3: + _context3.next = 30; + break; + + case 5: + if (!this.turnServers) { + _context3.next = 10; + break; + } + + _context3.next = 8; + return this.transport.reply(request, {}); + + case 8: + _context3.next = 30; + break; + + case 10: + _context3.prev = 10; + turnServers = this.driver.getTurnServers(); // Peek at the first result, so we can at least verify that the + // client isn't banned from getting TURN servers entirely + + _context3.next = 14; + return turnServers.next(); + + case 14: + _yield$turnServers$ne = _context3.sent; + done = _yield$turnServers$ne.done; + value = _yield$turnServers$ne.value; + + if (!done) { + _context3.next = 19; + break; + } + + throw new Error("Client refuses to provide any TURN servers"); + + case 19: + _context3.next = 21; + return this.transport.reply(request, {}); + + case 21: + // Start the poll loop, sending the widget the initial result + this.pollTurnServers(turnServers, value); + this.turnServers = turnServers; + _context3.next = 30; + break; + + case 25: + _context3.prev = 25; + _context3.t0 = _context3["catch"](10); + console.error("error getting first TURN server results", _context3.t0); + _context3.next = 30; + return this.transport.reply(request, { + error: { + message: "TURN servers not available" + } + }); + + case 30: + case "end": + return _context3.stop(); + } + } + }, _callee3, this, [[10, 25]]); + })); + + function handleWatchTurnServers(_x4) { + return _handleWatchTurnServers.apply(this, arguments); + } + + return handleWatchTurnServers; + }() + }, { + key: "handleUnwatchTurnServers", + value: function () { + var _handleUnwatchTurnServers = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(request) { + return _regeneratorRuntime().wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + if (this.hasCapability(_Capabilities.MatrixCapabilities.MSC3846TurnServers)) { + _context4.next = 5; + break; + } + + _context4.next = 3; + return this.transport.reply(request, { + error: { + message: "Missing capability" + } + }); + + case 3: + _context4.next = 15; + break; + + case 5: + if (this.turnServers) { + _context4.next = 10; + break; + } + + _context4.next = 8; + return this.transport.reply(request, {}); + + case 8: + _context4.next = 15; + break; + + case 10: + _context4.next = 12; + return this.turnServers["return"](undefined); + + case 12: + this.turnServers = null; + _context4.next = 15; + return this.transport.reply(request, {}); + + case 15: + case "end": + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function handleUnwatchTurnServers(_x5) { + return _handleUnwatchTurnServers.apply(this, arguments); + } + + return handleUnwatchTurnServers; + }() + }, { + key: "handleReadRelations", + value: function () { + var _handleReadRelations = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(request) { + var _this9 = this; + + var result, chunk; + return _regeneratorRuntime().wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + if (request.data.event_id) { + _context5.next = 2; + break; + } + + return _context5.abrupt("return", this.transport.reply(request, { + error: { + message: "Invalid request - missing event ID" + } + })); + + case 2: + if (!(request.data.limit !== undefined && request.data.limit < 0)) { + _context5.next = 4; + break; + } + + return _context5.abrupt("return", this.transport.reply(request, { + error: { + message: "Invalid request - limit out of range" + } + })); + + case 4: + if (!(request.data.room_id !== undefined && !this.canUseRoomTimeline(request.data.room_id))) { + _context5.next = 6; + break; + } + + return _context5.abrupt("return", this.transport.reply(request, { + error: { + message: "Unable to access room timeline: ".concat(request.data.room_id) + } + })); + + case 6: + _context5.prev = 6; + _context5.next = 9; + return this.driver.readEventRelations(request.data.event_id, request.data.room_id, request.data.rel_type, request.data.event_type, request.data.from, request.data.to, request.data.limit, request.data.direction); + + case 9: + result = _context5.sent; + // only return events that the user has the permission to receive + chunk = result.chunk.filter(function (e) { + if (e.state_key !== undefined) { + return _this9.canReceiveStateEvent(e.type, e.state_key); + } else { + return _this9.canReceiveRoomEvent(e.type, e.content['msgtype']); + } + }); + return _context5.abrupt("return", this.transport.reply(request, { + chunk: chunk, + prev_batch: result.prevBatch, + next_batch: result.nextBatch + })); + + case 14: + _context5.prev = 14; + _context5.t0 = _context5["catch"](6); + console.error("error getting the relations", _context5.t0); + _context5.next = 19; + return this.transport.reply(request, { + error: { + message: "Unexpected error while reading relations" + } + }); + + case 19: + case "end": + return _context5.stop(); + } + } + }, _callee5, this, [[6, 14]]); + })); + + function handleReadRelations(_x6) { + return _handleReadRelations.apply(this, arguments); + } + + return handleReadRelations; + }() + }, { + key: "handleUserDirectorySearch", + value: function () { + var _handleUserDirectorySearch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(request) { + var result; + return _regeneratorRuntime().wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + if (this.hasCapability(_Capabilities.MatrixCapabilities.MSC3973UserDirectorySearch)) { + _context6.next = 2; + break; + } + + return _context6.abrupt("return", this.transport.reply(request, { + error: { + message: "Missing capability" + } + })); + + case 2: + if (!(typeof request.data.search_term !== 'string')) { + _context6.next = 4; + break; + } + + return _context6.abrupt("return", this.transport.reply(request, { + error: { + message: "Invalid request - missing search term" + } + })); + + case 4: + if (!(request.data.limit !== undefined && request.data.limit < 0)) { + _context6.next = 6; + break; + } + + return _context6.abrupt("return", this.transport.reply(request, { + error: { + message: "Invalid request - limit out of range" + } + })); + + case 6: + _context6.prev = 6; + _context6.next = 9; + return this.driver.searchUserDirectory(request.data.search_term, request.data.limit); + + case 9: + result = _context6.sent; + return _context6.abrupt("return", this.transport.reply(request, { + limited: result.limited, + results: result.results.map(function (r) { + return { + user_id: r.userId, + display_name: r.displayName, + avatar_url: r.avatarUrl + }; + }) + })); + + case 13: + _context6.prev = 13; + _context6.t0 = _context6["catch"](6); + console.error("error searching in the user directory", _context6.t0); + _context6.next = 18; + return this.transport.reply(request, { + error: { + message: "Unexpected error while searching in the user directory" + } + }); + + case 18: + case "end": + return _context6.stop(); + } + } + }, _callee6, this, [[6, 13]]); + })); + + function handleUserDirectorySearch(_x7) { + return _handleUserDirectorySearch.apply(this, arguments); + } + + return handleUserDirectorySearch; + }() + }, { + key: "handleMessage", + value: function handleMessage(ev) { + if (this.isStopped) return; + var actionEv = new CustomEvent("action:".concat(ev.detail.action), { + detail: ev.detail, + cancelable: true + }); + this.emit("action:".concat(ev.detail.action), actionEv); + + if (!actionEv.defaultPrevented) { + switch (ev.detail.action) { + case _WidgetApiAction.WidgetApiFromWidgetAction.ContentLoaded: + return this.handleContentLoadedAction(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.SupportedApiVersions: + return this.replyVersions(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.SendEvent: + return this.handleSendEvent(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.SendToDevice: + return this.handleSendToDevice(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.GetOpenIDCredentials: + return this.handleOIDC(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.MSC2931Navigate: + return this.handleNavigate(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities: + return this.handleCapabilitiesRenegotiate(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.MSC2876ReadEvents: + return this.handleReadEvents(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.WatchTurnServers: + return this.handleWatchTurnServers(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.UnwatchTurnServers: + return this.handleUnwatchTurnServers(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.MSC3869ReadRelations: + return this.handleReadRelations(ev.detail); + + case _WidgetApiAction.WidgetApiFromWidgetAction.MSC3973UserDirectorySearch: + return this.handleUserDirectorySearch(ev.detail); + + default: + return this.transport.reply(ev.detail, { + error: { + message: "Unknown or unsupported action: " + ev.detail.action + } + }); + } + } + } + /** + * Takes a screenshot of the widget. + * @returns Resolves to the widget's screenshot. + * @throws Throws if there is a problem. + */ + + }, { + key: "takeScreenshot", + value: function takeScreenshot() { + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.TakeScreenshot, {}); + } + /** + * Alerts the widget to whether or not it is currently visible. + * @param {boolean} isVisible Whether the widget is visible or not. + * @returns {Promise} Resolves when the widget acknowledges the update. + */ + + }, { + key: "updateVisibility", + value: function updateVisibility(isVisible) { + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.UpdateVisibility, { + visible: isVisible + }); + } + }, { + key: "sendWidgetConfig", + value: function sendWidgetConfig(data) { + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.WidgetConfig, data).then(); + } + }, { + key: "notifyModalWidgetButtonClicked", + value: function notifyModalWidgetButtonClicked(id) { + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.ButtonClicked, { + id: id + }).then(); + } + }, { + key: "notifyModalWidgetClose", + value: function notifyModalWidgetClose(data) { + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.CloseModalWidget, data).then(); + } + /** + * Feeds an event to the widget. If the widget is not able to accept the event due to + * permissions, this will no-op and return calmly. If the widget failed to handle the + * event, this will raise an error. + * @param {IRoomEvent} rawEvent The event to (try to) send to the widget. + * @param {string} currentViewedRoomId The room ID the user is currently interacting with. + * Not the room ID of the event. + * @returns {Promise} Resolves when complete, rejects if there was an error sending. + */ + + }, { + key: "feedEvent", + value: function () { + var _feedEvent = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(rawEvent, currentViewedRoomId) { + var _rawEvent$content; + + return _regeneratorRuntime().wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + if (!(rawEvent.room_id !== currentViewedRoomId && !this.canUseRoomTimeline(rawEvent.room_id))) { + _context7.next = 2; + break; + } + + return _context7.abrupt("return"); + + case 2: + if (!(rawEvent.state_key !== undefined && rawEvent.state_key !== null)) { + _context7.next = 7; + break; + } + + if (this.canReceiveStateEvent(rawEvent.type, rawEvent.state_key)) { + _context7.next = 5; + break; + } + + return _context7.abrupt("return"); + + case 5: + _context7.next = 9; + break; + + case 7: + if (this.canReceiveRoomEvent(rawEvent.type, (_rawEvent$content = rawEvent.content) === null || _rawEvent$content === void 0 ? void 0 : _rawEvent$content["msgtype"])) { + _context7.next = 9; + break; + } + + return _context7.abrupt("return"); + + case 9: + _context7.next = 11; + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.SendEvent, rawEvent // it's compatible, but missing the index signature + ); + + case 11: + case "end": + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function feedEvent(_x8, _x9) { + return _feedEvent.apply(this, arguments); + } + + return feedEvent; + }() + /** + * Feeds a to-device event to the widget. If the widget is not able to accept the + * event due to permissions, this will no-op and return calmly. If the widget failed + * to handle the event, this will raise an error. + * @param {IRoomEvent} rawEvent The event to (try to) send to the widget. + * @param {boolean} encrypted Whether the event contents were encrypted. + * @returns {Promise} Resolves when complete, rejects if there was an error sending. + */ + + }, { + key: "feedToDevice", + value: function () { + var _feedToDevice = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee8(rawEvent, encrypted) { + return _regeneratorRuntime().wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + if (!this.canReceiveToDeviceEvent(rawEvent.type)) { + _context8.next = 3; + break; + } + + _context8.next = 3; + return this.transport.send(_WidgetApiAction.WidgetApiToWidgetAction.SendToDevice, // it's compatible, but missing the index signature + _objectSpread(_objectSpread({}, rawEvent), {}, { + encrypted: encrypted + })); + + case 3: + case "end": + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function feedToDevice(_x10, _x11) { + return _feedToDevice.apply(this, arguments); + } + + return feedToDevice; + }() + }]); + + return ClientWidgetApi; +}(_events.EventEmitter); + +exports.ClientWidgetApi = ClientWidgetApi; +},{"./Symbols":175,"./interfaces/ApiVersion":179,"./interfaces/Capabilities":180,"./interfaces/GetOpenIDAction":183,"./interfaces/WidgetApiAction":207,"./interfaces/WidgetApiDirection":208,"./models/WidgetEventCapability":213,"./transport/PostmessageTransport":219,"./util/SimpleObservable":220,"events":105}],175:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Symbols = void 0; + +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var Symbols; +exports.Symbols = Symbols; + +(function (Symbols) { + Symbols["AnyRoom"] = "*"; +})(Symbols || (exports.Symbols = Symbols = {})); +},{}],176:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetApi = void 0; + +var _events = require("events"); + +var _WidgetApiDirection = require("./interfaces/WidgetApiDirection"); + +var _ApiVersion = require("./interfaces/ApiVersion"); + +var _PostmessageTransport = require("./transport/PostmessageTransport"); + +var _WidgetApiAction = require("./interfaces/WidgetApiAction"); + +var _GetOpenIDAction = require("./interfaces/GetOpenIDAction"); + +var _WidgetType = require("./interfaces/WidgetType"); + +var _ModalWidgetActions = require("./interfaces/ModalWidgetActions"); + +var _WidgetEventCapability = require("./models/WidgetEventCapability"); + +var _Symbols = require("./Symbols"); + +function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return exports; }; var exports = {}, Op = Object.prototype, hasOwn = Op.hasOwnProperty, $Symbol = "function" == typeof Symbol ? Symbol : {}, iteratorSymbol = $Symbol.iterator || "@@iterator", asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function define(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }), obj[key]; } try { define({}, ""); } catch (err) { define = function define(obj, key, value) { return obj[key] = value; }; } function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, generator = Object.create(protoGenerator.prototype), context = new Context(tryLocsList || []); return generator._invoke = function (innerFn, self, context) { var state = "suspendedStart"; return function (method, arg) { if ("executing" === state) throw new Error("Generator is already running"); if ("completed" === state) { if ("throw" === method) throw arg; return doneResult(); } for (context.method = method, context.arg = arg;;) { var delegate = context.delegate; if (delegate) { var delegateResult = maybeInvokeDelegate(delegate, context); if (delegateResult) { if (delegateResult === ContinueSentinel) continue; return delegateResult; } } if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { if ("suspendedStart" === state) throw state = "completed", context.arg; context.dispatchException(context.arg); } else "return" === context.method && context.abrupt("return", context.arg); state = "executing"; var record = tryCatch(innerFn, self, context); if ("normal" === record.type) { if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; return { value: record.arg, done: context.done }; } "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } }; }(innerFn, self, context), generator; } function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } } exports.wrap = wrap; var ContinueSentinel = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var IteratorPrototype = {}; define(IteratorPrototype, iteratorSymbol, function () { return this; }); var getProto = Object.getPrototypeOf, NativeIteratorPrototype = getProto && getProto(getProto(values([]))); NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function (method) { define(prototype, method, function (arg) { return this._invoke(method, arg); }); }); } function AsyncIterator(generator, PromiseImpl) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); if ("throw" !== record.type) { var result = record.arg, value = result.value; return value && "object" == _typeof(value) && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { invoke("next", value, resolve, reject); }, function (err) { invoke("throw", err, resolve, reject); }) : PromiseImpl.resolve(value).then(function (unwrapped) { result.value = unwrapped, resolve(result); }, function (error) { return invoke("throw", error, resolve, reject); }); } reject(record.arg); } var previousPromise; this._invoke = function (method, arg) { function callInvokeWithMethodAndArg() { return new PromiseImpl(function (resolve, reject) { invoke(method, arg, resolve, reject); }); } return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); }; } function maybeInvokeDelegate(delegate, context) { var method = delegate.iterator[context.method]; if (undefined === method) { if (context.delegate = null, "throw" === context.method) { if (delegate.iterator["return"] && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method)) return ContinueSentinel; context.method = "throw", context.arg = new TypeError("The iterator does not provide a 'throw' method"); } return ContinueSentinel; } var record = tryCatch(method, delegate.iterator, context.arg); if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; var info = record.arg; return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); } function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); } function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal", delete record.arg, entry.completion = record; } function Context(tryLocsList) { this.tryEntries = [{ tryLoc: "root" }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); } function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) return iteratorMethod.call(iterable); if ("function" == typeof iterable.next) return iterable; if (!isNaN(iterable.length)) { var i = -1, next = function next() { for (; ++i < iterable.length;) { if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; } return next.value = undefined, next.done = !0, next; }; return next.next = next; } } return { next: doneResult }; } function doneResult() { return { value: undefined, done: !0 }; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, define(Gp, "constructor", GeneratorFunctionPrototype), define(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { var ctor = "function" == typeof genFun && genFun.constructor; return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); }, exports.mark = function (genFun) { return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; }, exports.awrap = function (arg) { return { __await: arg }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { return this; }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { void 0 === PromiseImpl && (PromiseImpl = Promise); var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { return result.done ? result.value : iter.next(); }); }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { return this; }), define(Gp, "toString", function () { return "[object Generator]"; }), exports.keys = function (object) { var keys = []; for (var key in object) { keys.push(key); } return keys.reverse(), function next() { for (; keys.length;) { var key = keys.pop(); if (key in object) return next.value = key, next.done = !1, next; } return next.done = !0, next; }; }, exports.values = values, Context.prototype = { constructor: Context, reset: function reset(skipTempReset) { if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) { "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); } }, stop: function stop() { this.done = !0; var rootRecord = this.tryEntries[0].completion; if ("throw" === rootRecord.type) throw rootRecord.arg; return this.rval; }, dispatchException: function dispatchException(exception) { if (this.done) throw exception; var context = this; function handle(loc, caught) { return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; } for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i], record = entry.completion; if ("root" === entry.tryLoc) return handle("end"); if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"), hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } else if (hasCatch) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); } else { if (!hasFinally) throw new Error("try statement without catch or finally"); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } } } }, abrupt: function abrupt(type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); var record = finallyEntry ? finallyEntry.completion : {}; return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); }, complete: function complete(record, afterLoc) { if ("throw" === record.type) throw record.arg; return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; }, finish: function finish(finallyLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; } }, "catch": function _catch(tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc === tryLoc) { var record = entry.completion; if ("throw" === record.type) { var thrown = record.arg; resetTryEntry(entry); } return thrown; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(iterable, resultName, nextLoc) { return this.delegate = { iterator: values(iterable), resultName: resultName, nextLoc: nextLoc }, "next" === this.method && (this.arg = undefined), ContinueSentinel; } }, exports; } + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } + +function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _awaitAsyncGenerator(value) { return new _AwaitValue(value); } + +function _wrapAsyncGenerator(fn) { return function () { return new _AsyncGenerator(fn.apply(this, arguments)); }; } + +function _AsyncGenerator(gen) { var front, back; function send(key, arg) { return new Promise(function (resolve, reject) { var request = { key: key, arg: arg, resolve: resolve, reject: reject, next: null }; if (back) { back = back.next = request; } else { front = back = request; resume(key, arg); } }); } function resume(key, arg) { try { var result = gen[key](arg); var value = result.value; var wrappedAwait = value instanceof _AwaitValue; Promise.resolve(wrappedAwait ? value.wrapped : value).then(function (arg) { if (wrappedAwait) { resume(key === "return" ? "return" : "next", arg); return; } settle(result.done ? "return" : "normal", arg); }, function (err) { resume("throw", err); }); } catch (err) { settle("throw", err); } } function settle(type, value) { switch (type) { case "return": front.resolve({ value: value, done: true }); break; case "throw": front.reject(value); break; default: front.resolve({ value: value, done: false }); break; } front = front.next; if (front) { resume(front.key, front.arg); } else { back = null; } } this._invoke = send; if (typeof gen["return"] !== "function") { this["return"] = undefined; } } + +_AsyncGenerator.prototype[typeof Symbol === "function" && Symbol.asyncIterator || "@@asyncIterator"] = function () { return this; }; + +_AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); }; + +_AsyncGenerator.prototype["throw"] = function (arg) { return this._invoke("throw", arg); }; + +_AsyncGenerator.prototype["return"] = function (arg) { return this._invoke("return", arg); }; + +function _AwaitValue(value) { this.wrapped = value; } + +/** + * API handler for widgets. This raises events for each action + * received as `action:${action}` (eg: "action:screenshot"). + * Default handling can be prevented by using preventDefault() + * on the raised event. The default handling varies for each + * action: ones which the SDK can handle safely are acknowledged + * appropriately and ones which are unhandled (custom or require + * the widget to do something) are rejected with an error. + * + * Events which are preventDefault()ed must reply using the + * transport. The events raised will have a detail of an + * IWidgetApiRequest interface. + * + * When the WidgetApi is ready to start sending requests, it will + * raise a "ready" CustomEvent. After the ready event fires, actions + * can be sent and the transport will be ready. + */ +var WidgetApi = /*#__PURE__*/function (_EventEmitter) { + _inherits(WidgetApi, _EventEmitter); + + var _super = _createSuper(WidgetApi); + + /** + * Creates a new API handler for the given widget. + * @param {string} widgetId The widget ID to listen for. If not supplied then + * the API will use the widget ID from the first valid request it receives. + * @param {string} clientOrigin The origin of the client, or null if not known. + */ + function WidgetApi() { + var _this2; + + var widgetId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var clientOrigin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + + _classCallCheck(this, WidgetApi); + + _this2 = _super.call(this); + _this2.clientOrigin = clientOrigin; + + _defineProperty(_assertThisInitialized(_this2), "transport", void 0); + + _defineProperty(_assertThisInitialized(_this2), "capabilitiesFinished", false); + + _defineProperty(_assertThisInitialized(_this2), "supportsMSC2974Renegotiate", false); + + _defineProperty(_assertThisInitialized(_this2), "requestedCapabilities", []); + + _defineProperty(_assertThisInitialized(_this2), "approvedCapabilities", void 0); + + _defineProperty(_assertThisInitialized(_this2), "cachedClientVersions", void 0); + + _defineProperty(_assertThisInitialized(_this2), "turnServerWatchers", 0); + + if (!window.parent) { + throw new Error("No parent window. This widget doesn't appear to be embedded properly."); + } + + _this2.transport = new _PostmessageTransport.PostmessageTransport(_WidgetApiDirection.WidgetApiDirection.FromWidget, widgetId, window.parent, window); + _this2.transport.targetOrigin = clientOrigin; + + _this2.transport.on("message", _this2.handleMessage.bind(_assertThisInitialized(_this2))); + + return _this2; + } + /** + * Determines if the widget was granted a particular capability. Note that on + * clients where the capabilities are not fed back to the widget this function + * will rely on requested capabilities instead. + * @param {Capability} capability The capability to check for approval of. + * @returns {boolean} True if the widget has approval for the given capability. + */ + + + _createClass(WidgetApi, [{ + key: "hasCapability", + value: function hasCapability(capability) { + if (Array.isArray(this.approvedCapabilities)) { + return this.approvedCapabilities.includes(capability); + } + + return this.requestedCapabilities.includes(capability); + } + /** + * Request a capability from the client. It is not guaranteed to be allowed, + * but will be asked for. + * @param {Capability} capability The capability to request. + * @throws Throws if the capabilities negotiation has already started and the + * widget is unable to request additional capabilities. + */ + + }, { + key: "requestCapability", + value: function requestCapability(capability) { + if (this.capabilitiesFinished && !this.supportsMSC2974Renegotiate) { + throw new Error("Capabilities have already been negotiated"); + } + + this.requestedCapabilities.push(capability); + } + /** + * Request capabilities from the client. They are not guaranteed to be allowed, + * but will be asked for if the negotiation has not already happened. + * @param {Capability[]} capabilities The capabilities to request. + * @throws Throws if the capabilities negotiation has already started. + */ + + }, { + key: "requestCapabilities", + value: function requestCapabilities(capabilities) { + var _this3 = this; + + capabilities.forEach(function (cap) { + return _this3.requestCapability(cap); + }); + } + /** + * Requests the capability to interact with rooms other than the user's currently + * viewed room. Applies to event receiving and sending. + * @param {string | Symbols.AnyRoom} roomId The room ID, or `Symbols.AnyRoom` to + * denote all known rooms. + */ + + }, { + key: "requestCapabilityForRoomTimeline", + value: function requestCapabilityForRoomTimeline(roomId) { + this.requestCapability("org.matrix.msc2762.timeline:".concat(roomId)); + } + /** + * Requests the capability to send a given state event with optional explicit + * state key. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} eventType The state event type to ask for. + * @param {string} stateKey If specified, the specific state key to request. + * Otherwise all state keys will be requested. + */ + + }, { + key: "requestCapabilityToSendState", + value: function requestCapabilityToSendState(eventType, stateKey) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forStateEvent(_WidgetEventCapability.EventDirection.Send, eventType, stateKey).raw); + } + /** + * Requests the capability to receive a given state event with optional explicit + * state key. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} eventType The state event type to ask for. + * @param {string} stateKey If specified, the specific state key to request. + * Otherwise all state keys will be requested. + */ + + }, { + key: "requestCapabilityToReceiveState", + value: function requestCapabilityToReceiveState(eventType, stateKey) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forStateEvent(_WidgetEventCapability.EventDirection.Receive, eventType, stateKey).raw); + } + /** + * Requests the capability to send a given to-device event. It is not + * guaranteed to be allowed, but will be asked for if the negotiation has + * not already happened. + * @param {string} eventType The room event type to ask for. + */ + + }, { + key: "requestCapabilityToSendToDevice", + value: function requestCapabilityToSendToDevice(eventType) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forToDeviceEvent(_WidgetEventCapability.EventDirection.Send, eventType).raw); + } + /** + * Requests the capability to receive a given to-device event. It is not + * guaranteed to be allowed, but will be asked for if the negotiation has + * not already happened. + * @param {string} eventType The room event type to ask for. + */ + + }, { + key: "requestCapabilityToReceiveToDevice", + value: function requestCapabilityToReceiveToDevice(eventType) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forToDeviceEvent(_WidgetEventCapability.EventDirection.Receive, eventType).raw); + } + /** + * Requests the capability to send a given room event. It is not guaranteed to be + * allowed, but will be asked for if the negotiation has not already happened. + * @param {string} eventType The room event type to ask for. + */ + + }, { + key: "requestCapabilityToSendEvent", + value: function requestCapabilityToSendEvent(eventType) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forRoomEvent(_WidgetEventCapability.EventDirection.Send, eventType).raw); + } + /** + * Requests the capability to receive a given room event. It is not guaranteed to be + * allowed, but will be asked for if the negotiation has not already happened. + * @param {string} eventType The room event type to ask for. + */ + + }, { + key: "requestCapabilityToReceiveEvent", + value: function requestCapabilityToReceiveEvent(eventType) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forRoomEvent(_WidgetEventCapability.EventDirection.Receive, eventType).raw); + } + /** + * Requests the capability to send a given message event with optional explicit + * `msgtype`. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} msgtype If specified, the specific msgtype to request. + * Otherwise all message types will be requested. + */ + + }, { + key: "requestCapabilityToSendMessage", + value: function requestCapabilityToSendMessage(msgtype) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forRoomMessageEvent(_WidgetEventCapability.EventDirection.Send, msgtype).raw); + } + /** + * Requests the capability to receive a given message event with optional explicit + * `msgtype`. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} msgtype If specified, the specific msgtype to request. + * Otherwise all message types will be requested. + */ + + }, { + key: "requestCapabilityToReceiveMessage", + value: function requestCapabilityToReceiveMessage(msgtype) { + this.requestCapability(_WidgetEventCapability.WidgetEventCapability.forRoomMessageEvent(_WidgetEventCapability.EventDirection.Receive, msgtype).raw); + } + /** + * Requests an OpenID Connect token from the client for the currently logged in + * user. This token can be validated server-side with the federation API. Note + * that the widget is responsible for validating the token and caching any results + * it needs. + * @returns {Promise} Resolves to a token for verification. + * @throws Throws if the user rejected the request or the request failed. + */ + + }, { + key: "requestOpenIDConnectToken", + value: function requestOpenIDConnectToken() { + var _this4 = this; + + return new Promise(function (resolve, reject) { + _this4.transport.sendComplete(_WidgetApiAction.WidgetApiFromWidgetAction.GetOpenIDCredentials, {}).then(function (response) { + var rdata = response.response; + + if (rdata.state === _GetOpenIDAction.OpenIDRequestState.Allowed) { + resolve(rdata); + } else if (rdata.state === _GetOpenIDAction.OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + } else if (rdata.state === _GetOpenIDAction.OpenIDRequestState.PendingUserConfirmation) { + var handlerFn = function handlerFn(ev) { + ev.preventDefault(); + var request = ev.detail; + if (request.data.original_request_id !== response.requestId) return; + + if (request.data.state === _GetOpenIDAction.OpenIDRequestState.Allowed) { + resolve(request.data); + + _this4.transport.reply(request, {}); // ack + + } else if (request.data.state === _GetOpenIDAction.OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + + _this4.transport.reply(request, {}); // ack + + } else { + reject(new Error("Invalid state on reply: " + rdata.state)); + + _this4.transport.reply(request, { + error: { + message: "Invalid state" + } + }); + } + + _this4.off("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.OpenIDCredentials), handlerFn); + }; + + _this4.on("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.OpenIDCredentials), handlerFn); + } else { + reject(new Error("Invalid state: " + rdata.state)); + } + })["catch"](reject); + }); + } + /** + * Asks the client for additional capabilities. Capabilities can be queued for this + * request with the requestCapability() functions. + * @returns {Promise} Resolves when complete. Note that the promise resolves when + * the capabilities request has gone through, not when the capabilities are approved/denied. + * Use the WidgetApiToWidgetAction.NotifyCapabilities action to detect changes. + */ + + }, { + key: "updateRequestedCapabilities", + value: function updateRequestedCapabilities() { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities, { + capabilities: this.requestedCapabilities + }).then(); + } + /** + * Tell the client that the content has been loaded. + * @returns {Promise} Resolves when the client acknowledges the request. + */ + + }, { + key: "sendContentLoaded", + value: function sendContentLoaded() { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.ContentLoaded, {}).then(); + } + /** + * Sends a sticker to the client. + * @param {IStickerActionRequestData} sticker The sticker to send. + * @returns {Promise} Resolves when the client acknowledges the request. + */ + + }, { + key: "sendSticker", + value: function sendSticker(sticker) { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SendSticker, sticker).then(); + } + /** + * Asks the client to set the always-on-screen status for this widget. + * @param {boolean} value The new state to request. + * @returns {Promise} Resolve with true if the client was able to fulfill + * the request, resolves to false otherwise. Rejects if an error occurred. + */ + + }, { + key: "setAlwaysOnScreen", + value: function setAlwaysOnScreen(value) { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, { + value: value + }).then(function (res) { + return res.success; + }); + } + /** + * Opens a modal widget. + * @param {string} url The URL to the modal widget. + * @param {string} name The name of the widget. + * @param {IModalWidgetOpenRequestDataButton[]} buttons The buttons to have on the widget. + * @param {IModalWidgetCreateData} data Data to supply to the modal widget. + * @param {WidgetType} type The type of modal widget. + * @returns {Promise} Resolves when the modal widget has been opened. + */ + + }, { + key: "openModalWidget", + value: function openModalWidget(url, name) { + var buttons = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; + var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var type = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : _WidgetType.MatrixWidgetType.Custom; + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.OpenModalWidget, { + type: type, + url: url, + name: name, + buttons: buttons, + data: data + }).then(); + } + /** + * Closes the modal widget. The widget's session will be terminated shortly after. + * @param {IModalWidgetReturnData} data Optional data to close the modal widget with. + * @returns {Promise} Resolves when complete. + */ + + }, { + key: "closeModalWidget", + value: function closeModalWidget() { + var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.CloseModalWidget, data).then(); + } + }, { + key: "sendRoomEvent", + value: function sendRoomEvent(eventType, content, roomId) { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SendEvent, { + type: eventType, + content: content, + room_id: roomId + }); + } + }, { + key: "sendStateEvent", + value: function sendStateEvent(eventType, stateKey, content, roomId) { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SendEvent, { + type: eventType, + content: content, + state_key: stateKey, + room_id: roomId + }); + } + /** + * Sends a to-device event. + * @param {string} eventType The type of events being sent. + * @param {boolean} encrypted Whether to encrypt the message contents. + * @param {Object} contentMap A map from user IDs to device IDs to message contents. + * @returns {Promise} Resolves when complete. + */ + + }, { + key: "sendToDevice", + value: function sendToDevice(eventType, encrypted, contentMap) { + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SendToDevice, { + type: eventType, + encrypted: encrypted, + messages: contentMap + }); + } + }, { + key: "readRoomEvents", + value: function readRoomEvents(eventType, limit, msgtype, roomIds) { + var data = { + type: eventType, + msgtype: msgtype + }; + + if (limit !== undefined) { + data.limit = limit; + } + + if (roomIds) { + if (roomIds.includes(_Symbols.Symbols.AnyRoom)) { + data.room_ids = _Symbols.Symbols.AnyRoom; + } else { + data.room_ids = roomIds; + } + } + + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC2876ReadEvents, data).then(function (r) { + return r.events; + }); + } + /** + * Reads all related events given a known eventId. + * @param eventId The id of the parent event to be read. + * @param roomId The room to look within. When undefined, the user's currently + * viewed room. + * @param relationType The relationship type of child events to search for. + * When undefined, all relations are returned. + * @param eventType The event type of child events to search for. When undefined, + * all related events are returned. + * @param limit The maximum number of events to retrieve per room. If not + * supplied, the server will apply a default limit. + * @param from The pagination token to start returning results from, as + * received from a previous call. If not supplied, results start at the most + * recent topological event known to the server. + * @param to The pagination token to stop returning results at. If not + * supplied, results continue up to limit or until there are no more events. + * @param direction The direction to search for according to MSC3715. + * @returns Resolves to the room relations. + */ + + }, { + key: "readEventRelations", + value: function () { + var _readEventRelations = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(eventId, roomId, relationType, eventType, limit, from, to, direction) { + var versions, data; + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return this.getClientVersions(); + + case 2: + versions = _context.sent; + + if (versions.includes(_ApiVersion.UnstableApiVersion.MSC3869)) { + _context.next = 5; + break; + } + + throw new Error("The read_relations action is not supported by the client."); + + case 5: + data = { + event_id: eventId, + rel_type: relationType, + event_type: eventType, + room_id: roomId, + to: to, + from: from, + limit: limit, + direction: direction + }; + return _context.abrupt("return", this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC3869ReadRelations, data)); + + case 7: + case "end": + return _context.stop(); + } + } + }, _callee, this); + })); + + function readEventRelations(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8) { + return _readEventRelations.apply(this, arguments); + } + + return readEventRelations; + }() + }, { + key: "readStateEvents", + value: function readStateEvents(eventType, limit, stateKey, roomIds) { + var data = { + type: eventType, + state_key: stateKey === undefined ? true : stateKey + }; + + if (limit !== undefined) { + data.limit = limit; + } + + if (roomIds) { + if (roomIds.includes(_Symbols.Symbols.AnyRoom)) { + data.room_ids = _Symbols.Symbols.AnyRoom; + } else { + data.room_ids = roomIds; + } + } + + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC2876ReadEvents, data).then(function (r) { + return r.events; + }); + } + /** + * Sets a button as disabled or enabled on the modal widget. Buttons are enabled by default. + * @param {ModalButtonID} buttonId The button ID to enable/disable. + * @param {boolean} isEnabled Whether or not the button is enabled. + * @returns {Promise} Resolves when complete. + * @throws Throws if the button cannot be disabled, or the client refuses to disable the button. + */ + + }, { + key: "setModalButtonEnabled", + value: function setModalButtonEnabled(buttonId, isEnabled) { + if (buttonId === _ModalWidgetActions.BuiltInModalButtonID.Close) { + throw new Error("The close button cannot be disabled"); + } + + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SetModalButtonEnabled, { + button: buttonId, + enabled: isEnabled + }).then(); + } + /** + * Attempts to navigate the client to the given URI. This can only be called with Matrix URIs + * (currently only matrix.to, but in future a Matrix URI scheme will be defined). + * @param {string} uri The URI to navigate to. + * @returns {Promise} Resolves when complete. + * @throws Throws if the URI is invalid or cannot be processed. + * @deprecated This currently relies on an unstable MSC (MSC2931). + */ + + }, { + key: "navigateTo", + value: function navigateTo(uri) { + if (!uri || !uri.startsWith("https://matrix.to/#")) { + throw new Error("Invalid matrix.to URI"); + } + + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC2931Navigate, { + uri: uri + }).then(); + } + /** + * Starts watching for TURN servers, yielding an initial set of credentials as soon as possible, + * and thereafter yielding new credentials whenever the previous ones expire. + * @yields {ITurnServer} The TURN server URIs and credentials currently available to the widget. + */ + + }, { + key: "getTurnServers", + value: function getTurnServers() { + var _this = this; + + return _wrapAsyncGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() { + var setTurnServer, onUpdateTurnServers; + return _regeneratorRuntime().wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + onUpdateTurnServers = /*#__PURE__*/function () { + var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(ev) { + return _regeneratorRuntime().wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + ev.preventDefault(); + setTurnServer(ev.detail.data); + _context2.next = 4; + return _this.transport.reply(ev.detail, {}); + + case 4: + case "end": + return _context2.stop(); + } + } + }, _callee2); + })); + + return function onUpdateTurnServers(_x9) { + return _ref.apply(this, arguments); + }; + }(); // Start listening for updates before we even start watching, to catch + // TURN data that is sent immediately + + + _this.on("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.UpdateTurnServers), onUpdateTurnServers); // Only send the 'watch' action if we aren't already watching + + + if (!(_this.turnServerWatchers === 0)) { + _context3.next = 12; + break; + } + + _context3.prev = 3; + _context3.next = 6; + return _awaitAsyncGenerator(_this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.WatchTurnServers, {})); + + case 6: + _context3.next = 12; + break; + + case 8: + _context3.prev = 8; + _context3.t0 = _context3["catch"](3); + + _this.off("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.UpdateTurnServers), onUpdateTurnServers); + + throw _context3.t0; + + case 12: + _this.turnServerWatchers++; + _context3.prev = 13; + + case 14: + if (!true) { + _context3.next = 21; + break; + } + + _context3.next = 17; + return _awaitAsyncGenerator(new Promise(function (resolve) { + return setTurnServer = resolve; + })); + + case 17: + _context3.next = 19; + return _context3.sent; + + case 19: + _context3.next = 14; + break; + + case 21: + _context3.prev = 21; + + // The loop was broken by the caller - clean up + _this.off("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.UpdateTurnServers), onUpdateTurnServers); // Since sending the 'unwatch' action will end updates for all other + // consumers, only send it if we're the only consumer remaining + + + _this.turnServerWatchers--; + + if (!(_this.turnServerWatchers === 0)) { + _context3.next = 27; + break; + } + + _context3.next = 27; + return _awaitAsyncGenerator(_this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.UnwatchTurnServers, {})); + + case 27: + return _context3.finish(21); + + case 28: + case "end": + return _context3.stop(); + } + } + }, _callee3, null, [[3, 8], [13,, 21, 28]]); + }))(); + } + /** + * Search for users in the user directory. + * @param searchTerm The term to search for. + * @param limit The maximum number of results to return. If not supplied, the + * @returns Resolves to the search results. + */ + + }, { + key: "searchUserDirectory", + value: function () { + var _searchUserDirectory = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(searchTerm, limit) { + var versions, data; + return _regeneratorRuntime().wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return this.getClientVersions(); + + case 2: + versions = _context4.sent; + + if (versions.includes(_ApiVersion.UnstableApiVersion.MSC3973)) { + _context4.next = 5; + break; + } + + throw new Error("The user_directory_search action is not supported by the client."); + + case 5: + data = { + search_term: searchTerm, + limit: limit + }; + return _context4.abrupt("return", this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data)); + + case 7: + case "end": + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function searchUserDirectory(_x10, _x11) { + return _searchUserDirectory.apply(this, arguments); + } + + return searchUserDirectory; + }() + /** + * Starts the communication channel. This should be done early to ensure + * that messages are not missed. Communication can only be stopped by the client. + */ + + }, { + key: "start", + value: function start() { + var _this5 = this; + + this.transport.start(); + this.getClientVersions().then(function (v) { + if (v.includes(_ApiVersion.UnstableApiVersion.MSC2974)) { + _this5.supportsMSC2974Renegotiate = true; + } + }); + } + }, { + key: "handleMessage", + value: function handleMessage(ev) { + var actionEv = new CustomEvent("action:".concat(ev.detail.action), { + detail: ev.detail, + cancelable: true + }); + this.emit("action:".concat(ev.detail.action), actionEv); + + if (!actionEv.defaultPrevented) { + switch (ev.detail.action) { + case _WidgetApiAction.WidgetApiToWidgetAction.SupportedApiVersions: + return this.replyVersions(ev.detail); + + case _WidgetApiAction.WidgetApiToWidgetAction.Capabilities: + return this.handleCapabilities(ev.detail); + + case _WidgetApiAction.WidgetApiToWidgetAction.UpdateVisibility: + return this.transport.reply(ev.detail, {}); + // ack to avoid error spam + + case _WidgetApiAction.WidgetApiToWidgetAction.NotifyCapabilities: + return this.transport.reply(ev.detail, {}); + // ack to avoid error spam + + default: + return this.transport.reply(ev.detail, { + error: { + message: "Unknown or unsupported action: " + ev.detail.action + } + }); + } + } + } + }, { + key: "replyVersions", + value: function replyVersions(request) { + this.transport.reply(request, { + supported_versions: _ApiVersion.CurrentApiVersions + }); + } + }, { + key: "getClientVersions", + value: function getClientVersions() { + var _this6 = this; + + if (Array.isArray(this.cachedClientVersions)) { + return Promise.resolve(this.cachedClientVersions); + } + + return this.transport.send(_WidgetApiAction.WidgetApiFromWidgetAction.SupportedApiVersions, {}).then(function (r) { + _this6.cachedClientVersions = r.supported_versions; + return r.supported_versions; + })["catch"](function (e) { + console.warn("non-fatal error getting supported client versions: ", e); + return []; + }); + } + }, { + key: "handleCapabilities", + value: function handleCapabilities(request) { + var _this7 = this; + + if (this.capabilitiesFinished) { + return this.transport.reply(request, { + error: { + message: "Capability negotiation already completed" + } + }); + } // See if we can expect a capabilities notification or not + + + return this.getClientVersions().then(function (v) { + if (v.includes(_ApiVersion.UnstableApiVersion.MSC2871)) { + _this7.once("action:".concat(_WidgetApiAction.WidgetApiToWidgetAction.NotifyCapabilities), function (ev) { + _this7.approvedCapabilities = ev.detail.data.approved; + + _this7.emit("ready"); + }); + } else { + // if we can't expect notification, we're as done as we can be + _this7.emit("ready"); + } // in either case, reply to that capabilities request + + + _this7.capabilitiesFinished = true; + return _this7.transport.reply(request, { + capabilities: _this7.requestedCapabilities + }); + }); + } + }]); + + return WidgetApi; +}(_events.EventEmitter); + +exports.WidgetApi = WidgetApi; +},{"./Symbols":175,"./interfaces/ApiVersion":179,"./interfaces/GetOpenIDAction":183,"./interfaces/ModalWidgetActions":193,"./interfaces/WidgetApiAction":207,"./interfaces/WidgetApiDirection":208,"./interfaces/WidgetType":211,"./models/WidgetEventCapability":213,"./transport/PostmessageTransport":219,"events":105}],177:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetDriver = void 0; + +var _ = require(".."); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +/** + * Represents the functions and behaviour the widget-api is unable to + * do, such as prompting the user for information or interacting with + * the UI. Clients are expected to implement this class and override + * any functions they need/want to support. + * + * This class assumes the client will have a context of a Widget + * instance already. + */ +var WidgetDriver = /*#__PURE__*/function () { + function WidgetDriver() { + _classCallCheck(this, WidgetDriver); + } + + _createClass(WidgetDriver, [{ + key: "validateCapabilities", + value: + /** + * Verifies the widget's requested capabilities, returning the ones + * it is approved to use. Mutating the requested capabilities will + * have no effect. + * + * This SHOULD result in the user being prompted to approve/deny + * capabilities. + * + * By default this rejects all capabilities (returns an empty set). + * @param {Set} requested The set of requested capabilities. + * @returns {Promise>} Resolves to the allowed capabilities. + */ + function validateCapabilities(requested) { + return Promise.resolve(new Set()); + } + /** + * Sends an event into a room. If `roomId` is falsy, the client should send the event + * into the room the user is currently looking at. The widget API will have already + * verified that the widget is capable of sending the event to that room. + * @param {string} eventType The event type to be sent. + * @param {*} content The content for the event. + * @param {string|null} stateKey The state key if this is a state event, otherwise null. + * May be an empty string. + * @param {string|null} roomId The room ID to send the event to. If falsy, the room the + * user is currently looking at. + * @returns {Promise} Resolves when the event has been sent with + * details of that event. + * @throws Rejected when the event could not be sent. + */ + + }, { + key: "sendEvent", + value: function sendEvent(eventType, content) { + var stateKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + var roomId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + return Promise.reject(new Error("Failed to override function")); + } + /** + * Sends a to-device event. The widget API will have already verified that the widget + * is capable of sending the event. + * @param {string} eventType The event type to be sent. + * @param {boolean} encrypted Whether to encrypt the message contents. + * @param {Object} contentMap A map from user ID and device ID to event content. + * @returns {Promise} Resolves when the event has been sent. + * @throws Rejected when the event could not be sent. + */ + + }, { + key: "sendToDevice", + value: function sendToDevice(eventType, encrypted, contentMap) { + return Promise.reject(new Error("Failed to override function")); + } + /** + * Reads all events of the given type, and optionally `msgtype` (if applicable/defined), + * the user has access to. The widget API will have already verified that the widget is + * capable of receiving the events. Less events than the limit are allowed to be returned, + * but not more. If `roomIds` is supplied, it may contain `Symbols.AnyRoom` to denote that + * `limit` in each of the client's known rooms should be returned. When `null`, only the + * room the user is currently looking at should be considered. + * @param eventType The event type to be read. + * @param msgtype The msgtype of the events to be read, if applicable/defined. + * @param limit The maximum number of events to retrieve per room. Will be zero to denote "as many + * as possible". + * @param roomIds When null, the user's currently viewed room. Otherwise, the list of room IDs + * to look within, possibly containing Symbols.AnyRoom to denote all known rooms. + * @returns {Promise} Resolves to the room events, or an empty array. + */ + + }, { + key: "readRoomEvents", + value: function readRoomEvents(eventType, msgtype, limit) { + var roomIds = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + return Promise.resolve([]); + } + /** + * Reads all events of the given type, and optionally state key (if applicable/defined), + * the user has access to. The widget API will have already verified that the widget is + * capable of receiving the events. Less events than the limit are allowed to be returned, + * but not more. If `roomIds` is supplied, it may contain `Symbols.AnyRoom` to denote that + * `limit` in each of the client's known rooms should be returned. When `null`, only the + * room the user is currently looking at should be considered. + * @param eventType The event type to be read. + * @param stateKey The state key of the events to be read, if applicable/defined. + * @param limit The maximum number of events to retrieve. Will be zero to denote "as many + * as possible". + * @param roomIds When null, the user's currently viewed room. Otherwise, the list of room IDs + * to look within, possibly containing Symbols.AnyRoom to denote all known rooms. + * @returns {Promise} Resolves to the state events, or an empty array. + */ + + }, { + key: "readStateEvents", + value: function readStateEvents(eventType, stateKey, limit) { + var roomIds = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + return Promise.resolve([]); + } + /** + * Reads all events that are related to a given event. The widget API will + * have already verified that the widget is capable of receiving the event, + * or will make sure to reject access to events which are returned from this + * function, but are not capable of receiving. If `relationType` or `eventType` + * are set, the returned events should already be filtered. Less events than + * the limit are allowed to be returned, but not more. + * @param eventId The id of the parent event to be read. + * @param roomId The room to look within. When undefined, the user's + * currently viewed room. + * @param relationType The relationship type of child events to search for. + * When undefined, all relations are returned. + * @param eventType The event type of child events to search for. When undefined, + * all related events are returned. + * @param from The pagination token to start returning results from, as + * received from a previous call. If not supplied, results start at the most + * recent topological event known to the server. + * @param to The pagination token to stop returning results at. If not + * supplied, results continue up to limit or until there are no more events. + * @param limit The maximum number of events to retrieve per room. If not + * supplied, the server will apply a default limit. + * @param direction The direction to search for according to MSC3715 + * @returns Resolves to the room relations. + */ + + }, { + key: "readEventRelations", + value: function readEventRelations(eventId, roomId, relationType, eventType, from, to, limit, direction) { + return Promise.resolve({ + chunk: [] + }); + } + /** + * Asks the user for permission to validate their identity through OpenID Connect. The + * interface for this function is an observable which accepts the state machine of the + * OIDC exchange flow. For example, if the client/user blocks the request then it would + * feed back a `{state: Blocked}` into the observable. Similarly, if the user already + * approved the widget then a `{state: Allowed}` would be fed into the observable alongside + * the token itself. If the client is asking for permission, it should feed in a + * `{state: PendingUserConfirmation}` followed by the relevant Allowed or Blocked state. + * + * The widget API will reject the widget's request with an error if this contract is not + * met properly. By default, the widget driver will block all OIDC requests. + * @param {SimpleObservable} observer The observable to feed updates into. + */ + + }, { + key: "askOpenID", + value: function askOpenID(observer) { + observer.update({ + state: _.OpenIDRequestState.Blocked + }); + } + /** + * Navigates the client with a matrix.to URI. In future this function will also be provided + * with the Matrix URIs once matrix.to is replaced. The given URI will have already been + * lightly checked to ensure it looks like a valid URI, though the implementation is recommended + * to do further checks on the URI. + * @param {string} uri The URI to navigate to. + * @returns {Promise} Resolves when complete. + * @throws Throws if there's a problem with the navigation, such as invalid format. + */ + + }, { + key: "navigate", + value: function navigate(uri) { + throw new Error("Navigation is not implemented"); + } + /** + * Polls for TURN server data, yielding an initial set of credentials as soon as possible, and + * thereafter yielding new credentials whenever the previous ones expire. The widget API will + * have already verified that the widget has permission to access TURN servers. + * @yields {ITurnServer} The TURN server URIs and credentials currently available to the client. + */ + + }, { + key: "getTurnServers", + value: function getTurnServers() { + throw new Error("TURN server support is not implemented"); + } + /** + * Search for users in the user directory. + * @param searchTerm The term to search for. + * @param limit The maximum number of results to return. If not supplied, the + * @returns Resolves to the search results. + */ + + }, { + key: "searchUserDirectory", + value: function searchUserDirectory(searchTerm, limit) { + return Promise.resolve({ + limited: false, + results: [] + }); + } + }]); + + return WidgetDriver; +}(); + +exports.WidgetDriver = WidgetDriver; +},{"..":178}],178:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _WidgetApi = require("./WidgetApi"); + +Object.keys(_WidgetApi).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetApi[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetApi[key]; + } + }); +}); + +var _ClientWidgetApi = require("./ClientWidgetApi"); + +Object.keys(_ClientWidgetApi).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ClientWidgetApi[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ClientWidgetApi[key]; + } + }); +}); + +var _Symbols = require("./Symbols"); + +Object.keys(_Symbols).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _Symbols[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _Symbols[key]; + } + }); +}); + +var _ITransport = require("./transport/ITransport"); + +Object.keys(_ITransport).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ITransport[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ITransport[key]; + } + }); +}); + +var _PostmessageTransport = require("./transport/PostmessageTransport"); + +Object.keys(_PostmessageTransport).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _PostmessageTransport[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _PostmessageTransport[key]; + } + }); +}); + +var _ICustomWidgetData = require("./interfaces/ICustomWidgetData"); + +Object.keys(_ICustomWidgetData).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ICustomWidgetData[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ICustomWidgetData[key]; + } + }); +}); + +var _IJitsiWidgetData = require("./interfaces/IJitsiWidgetData"); + +Object.keys(_IJitsiWidgetData).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IJitsiWidgetData[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IJitsiWidgetData[key]; + } + }); +}); + +var _IStickerpickerWidgetData = require("./interfaces/IStickerpickerWidgetData"); + +Object.keys(_IStickerpickerWidgetData).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IStickerpickerWidgetData[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IStickerpickerWidgetData[key]; + } + }); +}); + +var _IWidget = require("./interfaces/IWidget"); + +Object.keys(_IWidget).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IWidget[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IWidget[key]; + } + }); +}); + +var _WidgetType = require("./interfaces/WidgetType"); + +Object.keys(_WidgetType).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetType[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetType[key]; + } + }); +}); + +var _IWidgetApiErrorResponse = require("./interfaces/IWidgetApiErrorResponse"); + +Object.keys(_IWidgetApiErrorResponse).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IWidgetApiErrorResponse[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IWidgetApiErrorResponse[key]; + } + }); +}); + +var _IWidgetApiRequest = require("./interfaces/IWidgetApiRequest"); + +Object.keys(_IWidgetApiRequest).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IWidgetApiRequest[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IWidgetApiRequest[key]; + } + }); +}); + +var _IWidgetApiResponse = require("./interfaces/IWidgetApiResponse"); + +Object.keys(_IWidgetApiResponse).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IWidgetApiResponse[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IWidgetApiResponse[key]; + } + }); +}); + +var _WidgetApiAction = require("./interfaces/WidgetApiAction"); + +Object.keys(_WidgetApiAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetApiAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetApiAction[key]; + } + }); +}); + +var _WidgetApiDirection = require("./interfaces/WidgetApiDirection"); + +Object.keys(_WidgetApiDirection).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetApiDirection[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetApiDirection[key]; + } + }); +}); + +var _ApiVersion = require("./interfaces/ApiVersion"); + +Object.keys(_ApiVersion).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ApiVersion[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ApiVersion[key]; + } + }); +}); + +var _Capabilities = require("./interfaces/Capabilities"); + +Object.keys(_Capabilities).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _Capabilities[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _Capabilities[key]; + } + }); +}); + +var _CapabilitiesAction = require("./interfaces/CapabilitiesAction"); + +Object.keys(_CapabilitiesAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _CapabilitiesAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _CapabilitiesAction[key]; + } + }); +}); + +var _ContentLoadedAction = require("./interfaces/ContentLoadedAction"); + +Object.keys(_ContentLoadedAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ContentLoadedAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ContentLoadedAction[key]; + } + }); +}); + +var _ScreenshotAction = require("./interfaces/ScreenshotAction"); + +Object.keys(_ScreenshotAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ScreenshotAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ScreenshotAction[key]; + } + }); +}); + +var _StickerAction = require("./interfaces/StickerAction"); + +Object.keys(_StickerAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _StickerAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _StickerAction[key]; + } + }); +}); + +var _StickyAction = require("./interfaces/StickyAction"); + +Object.keys(_StickyAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _StickyAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _StickyAction[key]; + } + }); +}); + +var _SupportedVersionsAction = require("./interfaces/SupportedVersionsAction"); + +Object.keys(_SupportedVersionsAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _SupportedVersionsAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _SupportedVersionsAction[key]; + } + }); +}); + +var _VisibilityAction = require("./interfaces/VisibilityAction"); + +Object.keys(_VisibilityAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _VisibilityAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _VisibilityAction[key]; + } + }); +}); + +var _GetOpenIDAction = require("./interfaces/GetOpenIDAction"); + +Object.keys(_GetOpenIDAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _GetOpenIDAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _GetOpenIDAction[key]; + } + }); +}); + +var _OpenIDCredentialsAction = require("./interfaces/OpenIDCredentialsAction"); + +Object.keys(_OpenIDCredentialsAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _OpenIDCredentialsAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _OpenIDCredentialsAction[key]; + } + }); +}); + +var _WidgetKind = require("./interfaces/WidgetKind"); + +Object.keys(_WidgetKind).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetKind[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetKind[key]; + } + }); +}); + +var _ModalButtonKind = require("./interfaces/ModalButtonKind"); + +Object.keys(_ModalButtonKind).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ModalButtonKind[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ModalButtonKind[key]; + } + }); +}); + +var _ModalWidgetActions = require("./interfaces/ModalWidgetActions"); + +Object.keys(_ModalWidgetActions).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ModalWidgetActions[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ModalWidgetActions[key]; + } + }); +}); + +var _SetModalButtonEnabledAction = require("./interfaces/SetModalButtonEnabledAction"); + +Object.keys(_SetModalButtonEnabledAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _SetModalButtonEnabledAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _SetModalButtonEnabledAction[key]; + } + }); +}); + +var _WidgetConfigAction = require("./interfaces/WidgetConfigAction"); + +Object.keys(_WidgetConfigAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetConfigAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetConfigAction[key]; + } + }); +}); + +var _SendEventAction = require("./interfaces/SendEventAction"); + +Object.keys(_SendEventAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _SendEventAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _SendEventAction[key]; + } + }); +}); + +var _SendToDeviceAction = require("./interfaces/SendToDeviceAction"); + +Object.keys(_SendToDeviceAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _SendToDeviceAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _SendToDeviceAction[key]; + } + }); +}); + +var _ReadEventAction = require("./interfaces/ReadEventAction"); + +Object.keys(_ReadEventAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ReadEventAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ReadEventAction[key]; + } + }); +}); + +var _IRoomEvent = require("./interfaces/IRoomEvent"); + +Object.keys(_IRoomEvent).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _IRoomEvent[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _IRoomEvent[key]; + } + }); +}); + +var _NavigateAction = require("./interfaces/NavigateAction"); + +Object.keys(_NavigateAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _NavigateAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _NavigateAction[key]; + } + }); +}); + +var _TurnServerActions = require("./interfaces/TurnServerActions"); + +Object.keys(_TurnServerActions).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _TurnServerActions[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _TurnServerActions[key]; + } + }); +}); + +var _ReadRelationsAction = require("./interfaces/ReadRelationsAction"); + +Object.keys(_ReadRelationsAction).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _ReadRelationsAction[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _ReadRelationsAction[key]; + } + }); +}); + +var _WidgetEventCapability = require("./models/WidgetEventCapability"); + +Object.keys(_WidgetEventCapability).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetEventCapability[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetEventCapability[key]; + } + }); +}); + +var _url = require("./models/validation/url"); + +Object.keys(_url).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _url[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _url[key]; + } + }); +}); + +var _utils = require("./models/validation/utils"); + +Object.keys(_utils).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _utils[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _utils[key]; + } + }); +}); + +var _Widget = require("./models/Widget"); + +Object.keys(_Widget).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _Widget[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _Widget[key]; + } + }); +}); + +var _WidgetParser = require("./models/WidgetParser"); + +Object.keys(_WidgetParser).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetParser[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetParser[key]; + } + }); +}); + +var _urlTemplate = require("./templating/url-template"); + +Object.keys(_urlTemplate).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _urlTemplate[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _urlTemplate[key]; + } + }); +}); + +var _SimpleObservable = require("./util/SimpleObservable"); + +Object.keys(_SimpleObservable).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _SimpleObservable[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _SimpleObservable[key]; + } + }); +}); + +var _WidgetDriver = require("./driver/WidgetDriver"); + +Object.keys(_WidgetDriver).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _WidgetDriver[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _WidgetDriver[key]; + } + }); +}); +},{"./ClientWidgetApi":174,"./Symbols":175,"./WidgetApi":176,"./driver/WidgetDriver":177,"./interfaces/ApiVersion":179,"./interfaces/Capabilities":180,"./interfaces/CapabilitiesAction":181,"./interfaces/ContentLoadedAction":182,"./interfaces/GetOpenIDAction":183,"./interfaces/ICustomWidgetData":184,"./interfaces/IJitsiWidgetData":185,"./interfaces/IRoomEvent":186,"./interfaces/IStickerpickerWidgetData":187,"./interfaces/IWidget":188,"./interfaces/IWidgetApiErrorResponse":189,"./interfaces/IWidgetApiRequest":190,"./interfaces/IWidgetApiResponse":191,"./interfaces/ModalButtonKind":192,"./interfaces/ModalWidgetActions":193,"./interfaces/NavigateAction":194,"./interfaces/OpenIDCredentialsAction":195,"./interfaces/ReadEventAction":196,"./interfaces/ReadRelationsAction":197,"./interfaces/ScreenshotAction":198,"./interfaces/SendEventAction":199,"./interfaces/SendToDeviceAction":200,"./interfaces/SetModalButtonEnabledAction":201,"./interfaces/StickerAction":202,"./interfaces/StickyAction":203,"./interfaces/SupportedVersionsAction":204,"./interfaces/TurnServerActions":205,"./interfaces/VisibilityAction":206,"./interfaces/WidgetApiAction":207,"./interfaces/WidgetApiDirection":208,"./interfaces/WidgetConfigAction":209,"./interfaces/WidgetKind":210,"./interfaces/WidgetType":211,"./models/Widget":212,"./models/WidgetEventCapability":213,"./models/WidgetParser":214,"./models/validation/url":215,"./models/validation/utils":216,"./templating/url-template":217,"./transport/ITransport":218,"./transport/PostmessageTransport":219,"./util/SimpleObservable":220}],179:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.UnstableApiVersion = exports.MatrixApiVersion = exports.CurrentApiVersions = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var MatrixApiVersion; +exports.MatrixApiVersion = MatrixApiVersion; + +(function (MatrixApiVersion) { + MatrixApiVersion["Prerelease1"] = "0.0.1"; + MatrixApiVersion["Prerelease2"] = "0.0.2"; +})(MatrixApiVersion || (exports.MatrixApiVersion = MatrixApiVersion = {})); + +var UnstableApiVersion; +exports.UnstableApiVersion = UnstableApiVersion; + +(function (UnstableApiVersion) { + UnstableApiVersion["MSC2762"] = "org.matrix.msc2762"; + UnstableApiVersion["MSC2871"] = "org.matrix.msc2871"; + UnstableApiVersion["MSC2931"] = "org.matrix.msc2931"; + UnstableApiVersion["MSC2974"] = "org.matrix.msc2974"; + UnstableApiVersion["MSC2876"] = "org.matrix.msc2876"; + UnstableApiVersion["MSC3819"] = "org.matrix.msc3819"; + UnstableApiVersion["MSC3846"] = "town.robin.msc3846"; + UnstableApiVersion["MSC3869"] = "org.matrix.msc3869"; + UnstableApiVersion["MSC3973"] = "org.matrix.msc3973"; +})(UnstableApiVersion || (exports.UnstableApiVersion = UnstableApiVersion = {})); + +var CurrentApiVersions = [MatrixApiVersion.Prerelease1, MatrixApiVersion.Prerelease2, //MatrixApiVersion.V010, +UnstableApiVersion.MSC2762, UnstableApiVersion.MSC2871, UnstableApiVersion.MSC2931, UnstableApiVersion.MSC2974, UnstableApiVersion.MSC2876, UnstableApiVersion.MSC3819, UnstableApiVersion.MSC3846, UnstableApiVersion.MSC3869, UnstableApiVersion.MSC3973]; +exports.CurrentApiVersions = CurrentApiVersions; +},{}],180:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VideoConferenceCapabilities = exports.StickerpickerCapabilities = exports.MatrixCapabilities = void 0; +exports.getTimelineRoomIDFromCapability = getTimelineRoomIDFromCapability; +exports.isTimelineCapability = isTimelineCapability; +exports.isTimelineCapabilityFor = isTimelineCapabilityFor; + +/* + * Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var MatrixCapabilities; +exports.MatrixCapabilities = MatrixCapabilities; + +(function (MatrixCapabilities) { + MatrixCapabilities["Screenshots"] = "m.capability.screenshot"; + MatrixCapabilities["StickerSending"] = "m.sticker"; + MatrixCapabilities["AlwaysOnScreen"] = "m.always_on_screen"; + MatrixCapabilities["RequiresClient"] = "io.element.requires_client"; + MatrixCapabilities["MSC2931Navigate"] = "org.matrix.msc2931.navigate"; + MatrixCapabilities["MSC3846TurnServers"] = "town.robin.msc3846.turn_servers"; + MatrixCapabilities["MSC3973UserDirectorySearch"] = "org.matrix.msc3973.user_directory_search"; +})(MatrixCapabilities || (exports.MatrixCapabilities = MatrixCapabilities = {})); + +var StickerpickerCapabilities = [MatrixCapabilities.StickerSending]; +exports.StickerpickerCapabilities = StickerpickerCapabilities; +var VideoConferenceCapabilities = [MatrixCapabilities.AlwaysOnScreen]; +/** + * Determines if a capability is a capability for a timeline. + * @param {Capability} capability The capability to test. + * @returns {boolean} True if a timeline capability, false otherwise. + */ + +exports.VideoConferenceCapabilities = VideoConferenceCapabilities; + +function isTimelineCapability(capability) { + // TODO: Change when MSC2762 becomes stable. + return capability === null || capability === void 0 ? void 0 : capability.startsWith("org.matrix.msc2762.timeline:"); +} +/** + * Determines if a capability is a timeline capability for the given room. + * @param {Capability} capability The capability to test. + * @param {string | Symbols.AnyRoom} roomId The room ID, or `Symbols.AnyRoom` for that designation. + * @returns {boolean} True if a matching capability, false otherwise. + */ + + +function isTimelineCapabilityFor(capability, roomId) { + return capability === "org.matrix.msc2762.timeline:".concat(roomId); +} +/** + * Gets the room ID described by a timeline capability. + * @param {string} capability The capability to parse. + * @returns {string} The room ID. + */ + + +function getTimelineRoomIDFromCapability(capability) { + return capability.substring(capability.indexOf(":") + 1); +} +},{}],181:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],182:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],183:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.OpenIDRequestState = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var OpenIDRequestState; +exports.OpenIDRequestState = OpenIDRequestState; + +(function (OpenIDRequestState) { + OpenIDRequestState["Allowed"] = "allowed"; + OpenIDRequestState["Blocked"] = "blocked"; + OpenIDRequestState["PendingUserConfirmation"] = "request"; +})(OpenIDRequestState || (exports.OpenIDRequestState = OpenIDRequestState = {})); +},{}],184:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],185:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],186:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],187:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],188:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],189:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isErrorResponse = isErrorResponse; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +function isErrorResponse(responseData) { + if ("error" in responseData) { + var err = responseData; + return !!err.error.message; + } + + return false; +} +},{}],190:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],191:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],192:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ModalButtonKind = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var ModalButtonKind; +exports.ModalButtonKind = ModalButtonKind; + +(function (ModalButtonKind) { + ModalButtonKind["Primary"] = "m.primary"; + ModalButtonKind["Secondary"] = "m.secondary"; + ModalButtonKind["Warning"] = "m.warning"; + ModalButtonKind["Danger"] = "m.danger"; + ModalButtonKind["Link"] = "m.link"; +})(ModalButtonKind || (exports.ModalButtonKind = ModalButtonKind = {})); +},{}],193:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.BuiltInModalButtonID = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var BuiltInModalButtonID; +exports.BuiltInModalButtonID = BuiltInModalButtonID; + +(function (BuiltInModalButtonID) { + BuiltInModalButtonID["Close"] = "m.close"; +})(BuiltInModalButtonID || (exports.BuiltInModalButtonID = BuiltInModalButtonID = {})); +},{}],194:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],195:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],196:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],197:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],198:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],199:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],200:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],201:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],202:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],203:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],204:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],205:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],206:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],207:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetApiToWidgetAction = exports.WidgetApiFromWidgetAction = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var WidgetApiToWidgetAction; +exports.WidgetApiToWidgetAction = WidgetApiToWidgetAction; + +(function (WidgetApiToWidgetAction) { + WidgetApiToWidgetAction["SupportedApiVersions"] = "supported_api_versions"; + WidgetApiToWidgetAction["Capabilities"] = "capabilities"; + WidgetApiToWidgetAction["NotifyCapabilities"] = "notify_capabilities"; + WidgetApiToWidgetAction["TakeScreenshot"] = "screenshot"; + WidgetApiToWidgetAction["UpdateVisibility"] = "visibility"; + WidgetApiToWidgetAction["OpenIDCredentials"] = "openid_credentials"; + WidgetApiToWidgetAction["WidgetConfig"] = "widget_config"; + WidgetApiToWidgetAction["CloseModalWidget"] = "close_modal"; + WidgetApiToWidgetAction["ButtonClicked"] = "button_clicked"; + WidgetApiToWidgetAction["SendEvent"] = "send_event"; + WidgetApiToWidgetAction["SendToDevice"] = "send_to_device"; + WidgetApiToWidgetAction["UpdateTurnServers"] = "update_turn_servers"; +})(WidgetApiToWidgetAction || (exports.WidgetApiToWidgetAction = WidgetApiToWidgetAction = {})); + +var WidgetApiFromWidgetAction; +exports.WidgetApiFromWidgetAction = WidgetApiFromWidgetAction; + +(function (WidgetApiFromWidgetAction) { + WidgetApiFromWidgetAction["SupportedApiVersions"] = "supported_api_versions"; + WidgetApiFromWidgetAction["ContentLoaded"] = "content_loaded"; + WidgetApiFromWidgetAction["SendSticker"] = "m.sticker"; + WidgetApiFromWidgetAction["UpdateAlwaysOnScreen"] = "set_always_on_screen"; + WidgetApiFromWidgetAction["GetOpenIDCredentials"] = "get_openid"; + WidgetApiFromWidgetAction["CloseModalWidget"] = "close_modal"; + WidgetApiFromWidgetAction["OpenModalWidget"] = "open_modal"; + WidgetApiFromWidgetAction["SetModalButtonEnabled"] = "set_button_enabled"; + WidgetApiFromWidgetAction["SendEvent"] = "send_event"; + WidgetApiFromWidgetAction["SendToDevice"] = "send_to_device"; + WidgetApiFromWidgetAction["WatchTurnServers"] = "watch_turn_servers"; + WidgetApiFromWidgetAction["UnwatchTurnServers"] = "unwatch_turn_servers"; + WidgetApiFromWidgetAction["MSC2876ReadEvents"] = "org.matrix.msc2876.read_events"; + WidgetApiFromWidgetAction["MSC2931Navigate"] = "org.matrix.msc2931.navigate"; + WidgetApiFromWidgetAction["MSC2974RenegotiateCapabilities"] = "org.matrix.msc2974.request_capabilities"; + WidgetApiFromWidgetAction["MSC3869ReadRelations"] = "org.matrix.msc3869.read_relations"; + WidgetApiFromWidgetAction["MSC3973UserDirectorySearch"] = "org.matrix.msc3973.user_directory_search"; +})(WidgetApiFromWidgetAction || (exports.WidgetApiFromWidgetAction = WidgetApiFromWidgetAction = {})); +},{}],208:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetApiDirection = void 0; +exports.invertedDirection = invertedDirection; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var WidgetApiDirection; +exports.WidgetApiDirection = WidgetApiDirection; + +(function (WidgetApiDirection) { + WidgetApiDirection["ToWidget"] = "toWidget"; + WidgetApiDirection["FromWidget"] = "fromWidget"; +})(WidgetApiDirection || (exports.WidgetApiDirection = WidgetApiDirection = {})); + +function invertedDirection(dir) { + if (dir === WidgetApiDirection.ToWidget) { + return WidgetApiDirection.FromWidget; + } else if (dir === WidgetApiDirection.FromWidget) { + return WidgetApiDirection.ToWidget; + } else { + throw new Error("Invalid direction"); + } +} +},{}],209:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],210:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetKind = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var WidgetKind; +exports.WidgetKind = WidgetKind; + +(function (WidgetKind) { + WidgetKind["Room"] = "room"; + WidgetKind["Account"] = "account"; + WidgetKind["Modal"] = "modal"; +})(WidgetKind || (exports.WidgetKind = WidgetKind = {})); +},{}],211:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MatrixWidgetType = void 0; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var MatrixWidgetType; +exports.MatrixWidgetType = MatrixWidgetType; + +(function (MatrixWidgetType) { + MatrixWidgetType["Custom"] = "m.custom"; + MatrixWidgetType["JitsiMeet"] = "m.jitsi"; + MatrixWidgetType["Stickerpicker"] = "m.stickerpicker"; +})(MatrixWidgetType || (exports.MatrixWidgetType = MatrixWidgetType = {})); +},{}],212:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Widget = void 0; + +var _utils = require("./validation/utils"); + +var _ = require(".."); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +/** + * Represents the barest form of widget. + */ +var Widget = /*#__PURE__*/function () { + function Widget(definition) { + _classCallCheck(this, Widget); + + this.definition = definition; + if (!this.definition) throw new Error("Definition is required"); + (0, _utils.assertPresent)(definition, "id"); + (0, _utils.assertPresent)(definition, "creatorUserId"); + (0, _utils.assertPresent)(definition, "type"); + (0, _utils.assertPresent)(definition, "url"); + } + /** + * The user ID who created the widget. + */ + + + _createClass(Widget, [{ + key: "creatorUserId", + get: function get() { + return this.definition.creatorUserId; + } + /** + * The type of widget. + */ + + }, { + key: "type", + get: function get() { + return this.definition.type; + } + /** + * The ID of the widget. + */ + + }, { + key: "id", + get: function get() { + return this.definition.id; + } + /** + * The name of the widget, or null if not set. + */ + + }, { + key: "name", + get: function get() { + return this.definition.name || null; + } + /** + * The title for the widget, or null if not set. + */ + + }, { + key: "title", + get: function get() { + return this.rawData.title || null; + } + /** + * The templated URL for the widget. + */ + + }, { + key: "templateUrl", + get: function get() { + return this.definition.url; + } + /** + * The origin for this widget. + */ + + }, { + key: "origin", + get: function get() { + return new URL(this.templateUrl).origin; + } + /** + * Whether or not the client should wait for the iframe to load. Defaults + * to true. + */ + + }, { + key: "waitForIframeLoad", + get: function get() { + if (this.definition.waitForIframeLoad === false) return false; + if (this.definition.waitForIframeLoad === true) return true; + return true; // default true + } + /** + * The raw data for the widget. This will always be defined, though + * may be empty. + */ + + }, { + key: "rawData", + get: function get() { + return this.definition.data || {}; + } + /** + * Gets a complete widget URL for the client to render. + * @param {ITemplateParams} params The template parameters. + * @returns {string} A templated URL. + */ + + }, { + key: "getCompleteUrl", + value: function getCompleteUrl(params) { + return (0, _.runTemplate)(this.templateUrl, this.definition, params); + } + }]); + + return Widget; +}(); + +exports.Widget = Widget; +},{"..":178,"./validation/utils":216}],213:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetEventCapability = exports.EventKind = exports.EventDirection = void 0; + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var EventKind; +exports.EventKind = EventKind; + +(function (EventKind) { + EventKind["Event"] = "event"; + EventKind["State"] = "state_event"; + EventKind["ToDevice"] = "to_device"; +})(EventKind || (exports.EventKind = EventKind = {})); + +var EventDirection; +exports.EventDirection = EventDirection; + +(function (EventDirection) { + EventDirection["Send"] = "send"; + EventDirection["Receive"] = "receive"; +})(EventDirection || (exports.EventDirection = EventDirection = {})); + +var WidgetEventCapability = /*#__PURE__*/function () { + function WidgetEventCapability(direction, eventType, kind, keyStr, raw) { + _classCallCheck(this, WidgetEventCapability); + + this.direction = direction; + this.eventType = eventType; + this.kind = kind; + this.keyStr = keyStr; + this.raw = raw; + } + + _createClass(WidgetEventCapability, [{ + key: "matchesAsStateEvent", + value: function matchesAsStateEvent(direction, eventType, stateKey) { + if (this.kind !== EventKind.State) return false; // not a state event + + if (this.direction !== direction) return false; // direction mismatch + + if (this.eventType !== eventType) return false; // event type mismatch + + if (this.keyStr === null) return true; // all state keys are allowed + + if (this.keyStr === stateKey) return true; // this state key is allowed + // Default not allowed + + return false; + } + }, { + key: "matchesAsToDeviceEvent", + value: function matchesAsToDeviceEvent(direction, eventType) { + if (this.kind !== EventKind.ToDevice) return false; // not a to-device event + + if (this.direction !== direction) return false; // direction mismatch + + if (this.eventType !== eventType) return false; // event type mismatch + // Checks passed, the event is allowed + + return true; + } + }, { + key: "matchesAsRoomEvent", + value: function matchesAsRoomEvent(direction, eventType) { + var msgtype = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + if (this.kind !== EventKind.Event) return false; // not a room event + + if (this.direction !== direction) return false; // direction mismatch + + if (this.eventType !== eventType) return false; // event type mismatch + + if (this.eventType === "m.room.message") { + if (this.keyStr === null) return true; // all message types are allowed + + if (this.keyStr === msgtype) return true; // this message type is allowed + } else { + return true; // already passed the check for if the event is allowed + } // Default not allowed + + + return false; + } + }], [{ + key: "forStateEvent", + value: function forStateEvent(direction, eventType, stateKey) { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + eventType = eventType.replace(/#/g, '\\#'); + stateKey = stateKey !== null && stateKey !== undefined ? "#".concat(stateKey) : ''; + var str = "org.matrix.msc2762.".concat(direction, ".state_event:").concat(eventType).concat(stateKey); // cheat by sending it through the processor + + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + }, { + key: "forToDeviceEvent", + value: function forToDeviceEvent(direction, eventType) { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/56 + var str = "org.matrix.msc3819.".concat(direction, ".to_device:").concat(eventType); // cheat by sending it through the processor + + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + }, { + key: "forRoomEvent", + value: function forRoomEvent(direction, eventType) { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + var str = "org.matrix.msc2762.".concat(direction, ".event:").concat(eventType); // cheat by sending it through the processor + + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + }, { + key: "forRoomMessageEvent", + value: function forRoomMessageEvent(direction, msgtype) { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + msgtype = msgtype === null || msgtype === undefined ? '' : msgtype; + var str = "org.matrix.msc2762.".concat(direction, ".event:m.room.message#").concat(msgtype); // cheat by sending it through the processor + + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + /** + * Parses a capabilities request to find all the event capability requests. + * @param {Iterable} capabilities The capabilities requested/to parse. + * @returns {WidgetEventCapability[]} An array of event capability requests. May be empty, but never null. + */ + + }, { + key: "findEventCapabilities", + value: function findEventCapabilities(capabilities) { + var parsed = []; + + var _iterator = _createForOfIteratorHelper(capabilities), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var cap = _step.value; + var _direction = null; + var eventSegment = void 0; + var _kind = null; // TODO: Enable support for m.* namespace once the MSCs land. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + // https://github.com/matrix-org/matrix-widget-api/issues/56 + + if (cap.startsWith("org.matrix.msc2762.send.event:")) { + _direction = EventDirection.Send; + _kind = EventKind.Event; + eventSegment = cap.substring("org.matrix.msc2762.send.event:".length); + } else if (cap.startsWith("org.matrix.msc2762.send.state_event:")) { + _direction = EventDirection.Send; + _kind = EventKind.State; + eventSegment = cap.substring("org.matrix.msc2762.send.state_event:".length); + } else if (cap.startsWith("org.matrix.msc3819.send.to_device:")) { + _direction = EventDirection.Send; + _kind = EventKind.ToDevice; + eventSegment = cap.substring("org.matrix.msc3819.send.to_device:".length); + } else if (cap.startsWith("org.matrix.msc2762.receive.event:")) { + _direction = EventDirection.Receive; + _kind = EventKind.Event; + eventSegment = cap.substring("org.matrix.msc2762.receive.event:".length); + } else if (cap.startsWith("org.matrix.msc2762.receive.state_event:")) { + _direction = EventDirection.Receive; + _kind = EventKind.State; + eventSegment = cap.substring("org.matrix.msc2762.receive.state_event:".length); + } else if (cap.startsWith("org.matrix.msc3819.receive.to_device:")) { + _direction = EventDirection.Receive; + _kind = EventKind.ToDevice; + eventSegment = cap.substring("org.matrix.msc3819.receive.to_device:".length); + } + + if (_direction === null || _kind === null || eventSegment === undefined) continue; // The capability uses `#` as a separator between event type and state key/msgtype, + // so we split on that. However, a # is also valid in either one of those so we + // join accordingly. + // Eg: `m.room.message##m.text` is "m.room.message" event with msgtype "#m.text". + + var expectingKeyStr = eventSegment.startsWith("m.room.message#") || _kind === EventKind.State; + + var _keyStr = null; + + if (eventSegment.includes('#') && expectingKeyStr) { + // Dev note: regex is difficult to write, so instead the rules are manually written + // out. This is probably just as understandable as a boring regex though, so win-win? + // Test cases: + // str eventSegment keyStr + // ------------------------------------------------------------- + // m.room.message# m.room.message + // m.room.message#test m.room.message test + // m.room.message\# m.room.message# test + // m.room.message##test m.room.message #test + // m.room.message\##test m.room.message# test + // m.room.message\\##test m.room.message\# test + // m.room.message\\###test m.room.message\# #test + // First step: explode the string + var parts = eventSegment.split('#'); // To form the eventSegment, we'll keep finding parts of the exploded string until + // there's one that doesn't end with the escape character (\). We'll then join those + // segments together with the exploding character. We have to remember to consume the + // escape character as well. + + var idx = parts.findIndex(function (p) { + return !p.endsWith("\\"); + }); + eventSegment = parts.slice(0, idx + 1).map(function (p) { + return p.endsWith('\\') ? p.substring(0, p.length - 1) : p; + }).join('#'); // The keyStr is whatever is left over. + + _keyStr = parts.slice(idx + 1).join('#'); + } + + parsed.push(new WidgetEventCapability(_direction, eventSegment, _kind, _keyStr, cap)); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + + return parsed; + } + }]); + + return WidgetEventCapability; +}(); + +exports.WidgetEventCapability = WidgetEventCapability; +},{}],214:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.WidgetParser = void 0; + +var _Widget = require("./Widget"); + +var _url = require("./validation/url"); + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +var WidgetParser = /*#__PURE__*/function () { + function WidgetParser() {// private constructor because this is a util class + + _classCallCheck(this, WidgetParser); + } + /** + * Parses widgets from the "m.widgets" account data event. This will always + * return an array, though may be empty if no valid widgets were found. + * @param {IAccountDataWidgets} content The content of the "m.widgets" account data. + * @returns {Widget[]} The widgets in account data, or an empty array. + */ + + + _createClass(WidgetParser, null, [{ + key: "parseAccountData", + value: function parseAccountData(content) { + if (!content) return []; + var result = []; + + for (var _i = 0, _Object$keys = Object.keys(content); _i < _Object$keys.length; _i++) { + var _widgetId = _Object$keys[_i]; + var roughWidget = content[_widgetId]; + if (!roughWidget) continue; + if (roughWidget.type !== "m.widget" && roughWidget.type !== "im.vector.modular.widgets") continue; + if (!roughWidget.sender) continue; + var probableWidgetId = roughWidget.state_key || roughWidget.id; + if (probableWidgetId !== _widgetId) continue; + var asStateEvent = { + content: roughWidget.content, + sender: roughWidget.sender, + type: "m.widget", + state_key: _widgetId, + event_id: "$example", + room_id: "!example", + origin_server_ts: 1 + }; + var widget = WidgetParser.parseRoomWidget(asStateEvent); + if (widget) result.push(widget); + } + + return result; + } + /** + * Parses all the widgets possible in the given array. This will always return + * an array, though may be empty if no widgets could be parsed. + * @param {IStateEvent[]} currentState The room state to parse. + * @returns {Widget[]} The widgets in the state, or an empty array. + */ + + }, { + key: "parseWidgetsFromRoomState", + value: function parseWidgetsFromRoomState(currentState) { + if (!currentState) return []; + var result = []; + + var _iterator = _createForOfIteratorHelper(currentState), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var state = _step.value; + var widget = WidgetParser.parseRoomWidget(state); + if (widget) result.push(widget); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + + return result; + } + /** + * Parses a state event into a widget. If the state event does not represent + * a widget (wrong event type, invalid widget, etc) then null is returned. + * @param {IStateEvent} stateEvent The state event. + * @returns {Widget|null} The widget, or null if invalid + */ + + }, { + key: "parseRoomWidget", + value: function parseRoomWidget(stateEvent) { + if (!stateEvent) return null; // TODO: [Legacy] Remove legacy support + + if (stateEvent.type !== "m.widget" && stateEvent.type !== "im.vector.modular.widgets") { + return null; + } // Dev note: Throughout this function we have null safety to ensure that + // if the caller did not supply something useful that we don't error. This + // is done against the requirements of the interface because not everyone + // will have an interface to validate against. + + + var content = stateEvent.content || {}; // Form our best approximation of a widget with the information we have + + var estimatedWidget = { + id: stateEvent.state_key, + creatorUserId: content['creatorUserId'] || stateEvent.sender, + name: content['name'], + type: content['type'], + url: content['url'], + waitForIframeLoad: content['waitForIframeLoad'], + data: content['data'] + }; // Finally, process that widget + + return WidgetParser.processEstimatedWidget(estimatedWidget); + } + }, { + key: "processEstimatedWidget", + value: function processEstimatedWidget(widget) { + // Validate that the widget has the best chance of passing as a widget + if (!widget.id || !widget.creatorUserId || !widget.type) { + return null; + } + + if (!(0, _url.isValidUrl)(widget.url)) { + return null; + } // TODO: Validate data for known widget types + + + return new _Widget.Widget(widget); + } + }]); + + return WidgetParser; +}(); + +exports.WidgetParser = WidgetParser; +},{"./Widget":212,"./validation/url":215}],215:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isValidUrl = isValidUrl; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +function isValidUrl(val) { + if (!val) return false; // easy: not valid if not present + + try { + var parsed = new URL(val); + + if (parsed.protocol !== "http" && parsed.protocol !== "https") { + return false; + } + + return true; + } catch (e) { + if (e instanceof TypeError) { + return false; + } + + throw e; + } +} +},{}],216:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.assertPresent = assertPresent; + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +function assertPresent(obj, key) { + if (!obj[key]) { + throw new Error("".concat(key, " is required")); + } +} +},{}],217:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.runTemplate = runTemplate; +exports.toString = toString; + +/* + * Copyright 2020, 2021 The Matrix.org Foundation C.I.C. + * + * 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. + */ +function runTemplate(url, widget, params) { + // Always apply the supplied params over top of data to ensure the data can't lie about them. + var variables = Object.assign({}, widget.data, { + 'matrix_room_id': params.widgetRoomId || "", + 'matrix_user_id': params.currentUserId, + 'matrix_display_name': params.userDisplayName || params.currentUserId, + 'matrix_avatar_url': params.userHttpAvatarUrl || "", + 'matrix_widget_id': widget.id, + // TODO: Convert to stable (https://github.com/matrix-org/matrix-doc/pull/2873) + 'org.matrix.msc2873.client_id': params.clientId || "", + 'org.matrix.msc2873.client_theme': params.clientTheme || "", + 'org.matrix.msc2873.client_language': params.clientLanguage || "" + }); + var result = url; + + for (var _i = 0, _Object$keys = Object.keys(variables); _i < _Object$keys.length; _i++) { + var key = _Object$keys[_i]; + // Regex escape from https://stackoverflow.com/a/6969486/7037379 + var pattern = "$".concat(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + + var rexp = new RegExp(pattern, 'g'); // This is technically not what we're supposed to do for a couple of reasons: + // 1. We are assuming that there won't later be a $key match after we replace a variable. + // 2. We are assuming that the variable is in a place where it can be escaped (eg: path or query string). + + result = result.replace(rexp, encodeURIComponent(toString(variables[key]))); + } + + return result; +} + +function toString(a) { + if (a === null || a === undefined) { + return "".concat(a); + } + + return String(a); +} +},{}],218:[function(require,module,exports){ +arguments[4][153][0].apply(exports,arguments) +},{"dup":153}],219:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PostmessageTransport = void 0; + +var _events = require("events"); + +var _ = require(".."); + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/** + * Transport for the Widget API over postMessage. + */ +var PostmessageTransport = /*#__PURE__*/function (_EventEmitter) { + _inherits(PostmessageTransport, _EventEmitter); + + var _super = _createSuper(PostmessageTransport); + + function PostmessageTransport(sendDirection, initialWidgetId, transportWindow, inboundWindow) { + var _this; + + _classCallCheck(this, PostmessageTransport); + + _this = _super.call(this); + _this.sendDirection = sendDirection; + _this.initialWidgetId = initialWidgetId; + _this.transportWindow = transportWindow; + _this.inboundWindow = inboundWindow; + + _defineProperty(_assertThisInitialized(_this), "strictOriginCheck", false); + + _defineProperty(_assertThisInitialized(_this), "targetOrigin", "*"); + + _defineProperty(_assertThisInitialized(_this), "timeoutSeconds", 10); + + _defineProperty(_assertThisInitialized(_this), "_ready", false); + + _defineProperty(_assertThisInitialized(_this), "_widgetId", null); + + _defineProperty(_assertThisInitialized(_this), "outboundRequests", new Map()); + + _defineProperty(_assertThisInitialized(_this), "stopController", new AbortController()); + + _this._widgetId = initialWidgetId; + return _this; + } + + _createClass(PostmessageTransport, [{ + key: "ready", + get: function get() { + return this._ready; + } + }, { + key: "widgetId", + get: function get() { + return this._widgetId || null; + } + }, { + key: "nextRequestId", + get: function get() { + var idBase = "widgetapi-".concat(Date.now()); + var index = 0; + var id = idBase; + + while (this.outboundRequests.has(id)) { + id = "".concat(idBase, "-").concat(index++); + } // reserve the ID + + + this.outboundRequests.set(id, null); + return id; + } + }, { + key: "sendInternal", + value: function sendInternal(message) { + console.log("[PostmessageTransport] Sending object to ".concat(this.targetOrigin, ": "), message); + this.transportWindow.postMessage(message, this.targetOrigin); + } + }, { + key: "reply", + value: function reply(request, responseData) { + return this.sendInternal(_objectSpread(_objectSpread({}, request), {}, { + response: responseData + })); + } + }, { + key: "send", + value: function send(action, data) { + return this.sendComplete(action, data).then(function (r) { + return r.response; + }); + } + }, { + key: "sendComplete", + value: function sendComplete(action, data) { + var _this2 = this; + + if (!this.ready || !this.widgetId) { + return Promise.reject(new Error("Not ready or unknown widget ID")); + } + + var request = { + api: this.sendDirection, + widgetId: this.widgetId, + requestId: this.nextRequestId, + action: action, + data: data + }; + + if (action === _.WidgetApiToWidgetAction.UpdateVisibility) { + request['visible'] = data['visible']; + } + + return new Promise(function (prResolve, prReject) { + var resolve = function resolve(response) { + cleanUp(); + prResolve(response); + }; + + var reject = function reject(err) { + cleanUp(); + prReject(err); + }; + + var timerId = setTimeout(function () { + return reject(new Error("Request timed out")); + }, (_this2.timeoutSeconds || 1) * 1000); + + var onStop = function onStop() { + return reject(new Error("Transport stopped")); + }; + + _this2.stopController.signal.addEventListener("abort", onStop); + + var cleanUp = function cleanUp() { + _this2.outboundRequests["delete"](request.requestId); + + clearTimeout(timerId); + + _this2.stopController.signal.removeEventListener("abort", onStop); + }; + + _this2.outboundRequests.set(request.requestId, { + request: request, + resolve: resolve, + reject: reject + }); + + _this2.sendInternal(request); + }); + } + }, { + key: "start", + value: function start() { + var _this3 = this; + + this.inboundWindow.addEventListener("message", function (ev) { + _this3.handleMessage(ev); + }); + this._ready = true; + } + }, { + key: "stop", + value: function stop() { + this._ready = false; + this.stopController.abort(); + } + }, { + key: "handleMessage", + value: function handleMessage(ev) { + if (this.stopController.signal.aborted) return; + if (!ev.data) return; // invalid event + + if (this.strictOriginCheck && ev.origin !== window.origin) return; // bad origin + // treat the message as a response first, then downgrade to a request + + var response = ev.data; + if (!response.action || !response.requestId || !response.widgetId) return; // invalid request/response + + if (!response.response) { + // it's a request + var request = response; + if (request.api !== (0, _.invertedDirection)(this.sendDirection)) return; // wrong direction + + this.handleRequest(request); + } else { + // it's a response + if (response.api !== this.sendDirection) return; // wrong direction + + this.handleResponse(response); + } + } + }, { + key: "handleRequest", + value: function handleRequest(request) { + if (this.widgetId) { + if (this.widgetId !== request.widgetId) return; // wrong widget + } else { + this._widgetId = request.widgetId; + } + + this.emit("message", new CustomEvent("message", { + detail: request + })); + } + }, { + key: "handleResponse", + value: function handleResponse(response) { + if (response.widgetId !== this.widgetId) return; // wrong widget + + var req = this.outboundRequests.get(response.requestId); + if (!req) return; // response to an unknown request + + if ((0, _.isErrorResponse)(response.response)) { + var _err = response.response; + req.reject(new Error(_err.error.message)); + } else { + req.resolve(response); + } + } + }]); + + return PostmessageTransport; +}(_events.EventEmitter); + +exports.PostmessageTransport = PostmessageTransport; +},{"..":178,"events":105}],220:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SimpleObservable = void 0; + +function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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. + */ +var SimpleObservable = /*#__PURE__*/function () { + function SimpleObservable(initialFn) { + _classCallCheck(this, SimpleObservable); + + _defineProperty(this, "listeners", []); + + if (initialFn) this.listeners.push(initialFn); + } + + _createClass(SimpleObservable, [{ + key: "onUpdate", + value: function onUpdate(fn) { + this.listeners.push(fn); + } + }, { + key: "update", + value: function update(val) { + var _iterator = _createForOfIteratorHelper(this.listeners), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var listener = _step.value; + listener(val); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } + }, { + key: "close", + value: function close() { + this.listeners = []; // reset + } + }]); + + return SimpleObservable; +}(); + +exports.SimpleObservable = SimpleObservable; +},{}],221:[function(require,module,exports){ +'use strict' +var inherits = require('inherits') +var HashBase = require('hash-base') +var Buffer = require('safe-buffer').Buffer + +var ARRAY16 = new Array(16) + +function MD5 () { + HashBase.call(this, 64) + + // state + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 +} + +inherits(MD5, HashBase) + +MD5.prototype._update = function () { + var M = ARRAY16 + for (var i = 0; i < 16; ++i) M[i] = this._block.readInt32LE(i * 4) + + var a = this._a + var b = this._b + var c = this._c + var d = this._d + + a = fnF(a, b, c, d, M[0], 0xd76aa478, 7) + d = fnF(d, a, b, c, M[1], 0xe8c7b756, 12) + c = fnF(c, d, a, b, M[2], 0x242070db, 17) + b = fnF(b, c, d, a, M[3], 0xc1bdceee, 22) + a = fnF(a, b, c, d, M[4], 0xf57c0faf, 7) + d = fnF(d, a, b, c, M[5], 0x4787c62a, 12) + c = fnF(c, d, a, b, M[6], 0xa8304613, 17) + b = fnF(b, c, d, a, M[7], 0xfd469501, 22) + a = fnF(a, b, c, d, M[8], 0x698098d8, 7) + d = fnF(d, a, b, c, M[9], 0x8b44f7af, 12) + c = fnF(c, d, a, b, M[10], 0xffff5bb1, 17) + b = fnF(b, c, d, a, M[11], 0x895cd7be, 22) + a = fnF(a, b, c, d, M[12], 0x6b901122, 7) + d = fnF(d, a, b, c, M[13], 0xfd987193, 12) + c = fnF(c, d, a, b, M[14], 0xa679438e, 17) + b = fnF(b, c, d, a, M[15], 0x49b40821, 22) + + a = fnG(a, b, c, d, M[1], 0xf61e2562, 5) + d = fnG(d, a, b, c, M[6], 0xc040b340, 9) + c = fnG(c, d, a, b, M[11], 0x265e5a51, 14) + b = fnG(b, c, d, a, M[0], 0xe9b6c7aa, 20) + a = fnG(a, b, c, d, M[5], 0xd62f105d, 5) + d = fnG(d, a, b, c, M[10], 0x02441453, 9) + c = fnG(c, d, a, b, M[15], 0xd8a1e681, 14) + b = fnG(b, c, d, a, M[4], 0xe7d3fbc8, 20) + a = fnG(a, b, c, d, M[9], 0x21e1cde6, 5) + d = fnG(d, a, b, c, M[14], 0xc33707d6, 9) + c = fnG(c, d, a, b, M[3], 0xf4d50d87, 14) + b = fnG(b, c, d, a, M[8], 0x455a14ed, 20) + a = fnG(a, b, c, d, M[13], 0xa9e3e905, 5) + d = fnG(d, a, b, c, M[2], 0xfcefa3f8, 9) + c = fnG(c, d, a, b, M[7], 0x676f02d9, 14) + b = fnG(b, c, d, a, M[12], 0x8d2a4c8a, 20) + + a = fnH(a, b, c, d, M[5], 0xfffa3942, 4) + d = fnH(d, a, b, c, M[8], 0x8771f681, 11) + c = fnH(c, d, a, b, M[11], 0x6d9d6122, 16) + b = fnH(b, c, d, a, M[14], 0xfde5380c, 23) + a = fnH(a, b, c, d, M[1], 0xa4beea44, 4) + d = fnH(d, a, b, c, M[4], 0x4bdecfa9, 11) + c = fnH(c, d, a, b, M[7], 0xf6bb4b60, 16) + b = fnH(b, c, d, a, M[10], 0xbebfbc70, 23) + a = fnH(a, b, c, d, M[13], 0x289b7ec6, 4) + d = fnH(d, a, b, c, M[0], 0xeaa127fa, 11) + c = fnH(c, d, a, b, M[3], 0xd4ef3085, 16) + b = fnH(b, c, d, a, M[6], 0x04881d05, 23) + a = fnH(a, b, c, d, M[9], 0xd9d4d039, 4) + d = fnH(d, a, b, c, M[12], 0xe6db99e5, 11) + c = fnH(c, d, a, b, M[15], 0x1fa27cf8, 16) + b = fnH(b, c, d, a, M[2], 0xc4ac5665, 23) + + a = fnI(a, b, c, d, M[0], 0xf4292244, 6) + d = fnI(d, a, b, c, M[7], 0x432aff97, 10) + c = fnI(c, d, a, b, M[14], 0xab9423a7, 15) + b = fnI(b, c, d, a, M[5], 0xfc93a039, 21) + a = fnI(a, b, c, d, M[12], 0x655b59c3, 6) + d = fnI(d, a, b, c, M[3], 0x8f0ccc92, 10) + c = fnI(c, d, a, b, M[10], 0xffeff47d, 15) + b = fnI(b, c, d, a, M[1], 0x85845dd1, 21) + a = fnI(a, b, c, d, M[8], 0x6fa87e4f, 6) + d = fnI(d, a, b, c, M[15], 0xfe2ce6e0, 10) + c = fnI(c, d, a, b, M[6], 0xa3014314, 15) + b = fnI(b, c, d, a, M[13], 0x4e0811a1, 21) + a = fnI(a, b, c, d, M[4], 0xf7537e82, 6) + d = fnI(d, a, b, c, M[11], 0xbd3af235, 10) + c = fnI(c, d, a, b, M[2], 0x2ad7d2bb, 15) + b = fnI(b, c, d, a, M[9], 0xeb86d391, 21) + + this._a = (this._a + a) | 0 + this._b = (this._b + b) | 0 + this._c = (this._c + c) | 0 + this._d = (this._d + d) | 0 +} + +MD5.prototype._digest = function () { + // create padding and handle blocks + this._block[this._blockOffset++] = 0x80 + if (this._blockOffset > 56) { + this._block.fill(0, this._blockOffset, 64) + this._update() + this._blockOffset = 0 + } + + this._block.fill(0, this._blockOffset, 56) + this._block.writeUInt32LE(this._length[0], 56) + this._block.writeUInt32LE(this._length[1], 60) + this._update() + + // produce result + var buffer = Buffer.allocUnsafe(16) + buffer.writeInt32LE(this._a, 0) + buffer.writeInt32LE(this._b, 4) + buffer.writeInt32LE(this._c, 8) + buffer.writeInt32LE(this._d, 12) + return buffer +} + +function rotl (x, n) { + return (x << n) | (x >>> (32 - n)) +} + +function fnF (a, b, c, d, m, k, s) { + return (rotl((a + ((b & c) | ((~b) & d)) + m + k) | 0, s) + b) | 0 +} + +function fnG (a, b, c, d, m, k, s) { + return (rotl((a + ((b & d) | (c & (~d))) + m + k) | 0, s) + b) | 0 +} + +function fnH (a, b, c, d, m, k, s) { + return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + b) | 0 +} + +function fnI (a, b, c, d, m, k, s) { + return (rotl((a + ((c ^ (b | (~d)))) + m + k) | 0, s) + b) | 0 +} + +module.exports = MD5 + +},{"hash-base":116,"inherits":146,"safe-buffer":250}],222:[function(require,module,exports){ +var bn = require('bn.js'); +var brorand = require('brorand'); + +function MillerRabin(rand) { + this.rand = rand || new brorand.Rand(); +} +module.exports = MillerRabin; + +MillerRabin.create = function create(rand) { + return new MillerRabin(rand); +}; + +MillerRabin.prototype._randbelow = function _randbelow(n) { + var len = n.bitLength(); + var min_bytes = Math.ceil(len / 8); + + // Generage random bytes until a number less than n is found. + // This ensures that 0..n-1 have an equal probability of being selected. + do + var a = new bn(this.rand.generate(min_bytes)); + while (a.cmp(n) >= 0); + + return a; +}; + +MillerRabin.prototype._randrange = function _randrange(start, stop) { + // Generate a random number greater than or equal to start and less than stop. + var size = stop.sub(start); + return start.add(this._randbelow(size)); +}; + +MillerRabin.prototype.test = function test(n, k, cb) { + var len = n.bitLength(); + var red = bn.mont(n); + var rone = new bn(1).toRed(red); + + if (!k) + k = Math.max(1, (len / 48) | 0); + + // Find d and s, (n - 1) = (2 ^ s) * d; + var n1 = n.subn(1); + for (var s = 0; !n1.testn(s); s++) {} + var d = n.shrn(s); + + var rn1 = n1.toRed(red); + + var prime = true; + for (; k > 0; k--) { + var a = this._randrange(new bn(2), n1); + if (cb) + cb(a); + + var x = a.toRed(red).redPow(d); + if (x.cmp(rone) === 0 || x.cmp(rn1) === 0) + continue; + + for (var i = 1; i < s; i++) { + x = x.redSqr(); + + if (x.cmp(rone) === 0) + return false; + if (x.cmp(rn1) === 0) + break; + } + + if (i === s) + return false; + } + + return prime; +}; + +MillerRabin.prototype.getDivisor = function getDivisor(n, k) { + var len = n.bitLength(); + var red = bn.mont(n); + var rone = new bn(1).toRed(red); + + if (!k) + k = Math.max(1, (len / 48) | 0); + + // Find d and s, (n - 1) = (2 ^ s) * d; + var n1 = n.subn(1); + for (var s = 0; !n1.testn(s); s++) {} + var d = n.shrn(s); + + var rn1 = n1.toRed(red); + + for (; k > 0; k--) { + var a = this._randrange(new bn(2), n1); + + var g = n.gcd(a); + if (g.cmpn(1) !== 0) + return g; + + var x = a.toRed(red).redPow(d); + if (x.cmp(rone) === 0 || x.cmp(rn1) === 0) + continue; + + for (var i = 1; i < s; i++) { + x = x.redSqr(); + + if (x.cmp(rone) === 0) + return x.fromRed().subn(1).gcd(n); + if (x.cmp(rn1) === 0) + break; + } + + if (i === s) { + x = x.redSqr(); + return x.fromRed().subn(1).gcd(n); + } + } + + return false; +}; + +},{"bn.js":18,"brorand":19}],223:[function(require,module,exports){ +module.exports = assert; + +function assert(val, msg) { + if (!val) + throw new Error(msg || 'Assertion failed'); +} + +assert.equal = function assertEqual(l, r, msg) { + if (l != r) + throw new Error(msg || ('Assertion failed: ' + l + ' != ' + r)); +}; + +},{}],224:[function(require,module,exports){ +'use strict'; + +var utils = exports; + +function toArray(msg, enc) { + if (Array.isArray(msg)) + return msg.slice(); + if (!msg) + return []; + var res = []; + if (typeof msg !== 'string') { + for (var i = 0; i < msg.length; i++) + res[i] = msg[i] | 0; + return res; + } + if (enc === 'hex') { + msg = msg.replace(/[^a-z0-9]+/ig, ''); + if (msg.length % 2 !== 0) + msg = '0' + msg; + for (var i = 0; i < msg.length; i += 2) + res.push(parseInt(msg[i] + msg[i + 1], 16)); + } else { + for (var i = 0; i < msg.length; i++) { + var c = msg.charCodeAt(i); + var hi = c >> 8; + var lo = c & 0xff; + if (hi) + res.push(hi, lo); + else + res.push(lo); + } + } + return res; +} +utils.toArray = toArray; + +function zero2(word) { + if (word.length === 1) + return '0' + word; + else + return word; +} +utils.zero2 = zero2; + +function toHex(msg) { + var res = ''; + for (var i = 0; i < msg.length; i++) + res += zero2(msg[i].toString(16)); + return res; +} +utils.toHex = toHex; + +utils.encode = function encode(arr, enc) { + if (enc === 'hex') + return toHex(arr); + else + return arr; +}; + +},{}],225:[function(require,module,exports){ +'use strict'; +const retry = require('retry'); + +const networkErrorMsgs = [ + 'Failed to fetch', // Chrome + 'NetworkError when attempting to fetch resource.', // Firefox + 'The Internet connection appears to be offline.', // Safari + 'Network request failed' // `cross-fetch` +]; + +class AbortError extends Error { + constructor(message) { + super(); + + if (message instanceof Error) { + this.originalError = message; + ({message} = message); + } else { + this.originalError = new Error(message); + this.originalError.stack = this.stack; + } + + this.name = 'AbortError'; + this.message = message; + } +} + +const decorateErrorWithCounts = (error, attemptNumber, options) => { + // Minus 1 from attemptNumber because the first attempt does not count as a retry + const retriesLeft = options.retries - (attemptNumber - 1); + + error.attemptNumber = attemptNumber; + error.retriesLeft = retriesLeft; + return error; +}; + +const isNetworkError = errorMessage => networkErrorMsgs.includes(errorMessage); + +const pRetry = (input, options) => new Promise((resolve, reject) => { + options = { + onFailedAttempt: () => {}, + retries: 10, + ...options + }; + + const operation = retry.operation(options); + + operation.attempt(async attemptNumber => { + try { + resolve(await input(attemptNumber)); + } catch (error) { + if (!(error instanceof Error)) { + reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`)); + return; + } + + if (error instanceof AbortError) { + operation.stop(); + reject(error.originalError); + } else if (error instanceof TypeError && !isNetworkError(error.message)) { + operation.stop(); + reject(error); + } else { + decorateErrorWithCounts(error, attemptNumber, options); + + try { + await options.onFailedAttempt(error); + } catch (error) { + reject(error); + return; + } + + if (!operation.retry(error)) { + reject(operation.mainError()); + } + } + } + }); +}); + +module.exports = pRetry; +// TODO: remove this in the next major version +module.exports.default = pRetry; + +module.exports.AbortError = AbortError; + +},{"retry":246}],226:[function(require,module,exports){ +module.exports={"2.16.840.1.101.3.4.1.1": "aes-128-ecb", +"2.16.840.1.101.3.4.1.2": "aes-128-cbc", +"2.16.840.1.101.3.4.1.3": "aes-128-ofb", +"2.16.840.1.101.3.4.1.4": "aes-128-cfb", +"2.16.840.1.101.3.4.1.21": "aes-192-ecb", +"2.16.840.1.101.3.4.1.22": "aes-192-cbc", +"2.16.840.1.101.3.4.1.23": "aes-192-ofb", +"2.16.840.1.101.3.4.1.24": "aes-192-cfb", +"2.16.840.1.101.3.4.1.41": "aes-256-ecb", +"2.16.840.1.101.3.4.1.42": "aes-256-cbc", +"2.16.840.1.101.3.4.1.43": "aes-256-ofb", +"2.16.840.1.101.3.4.1.44": "aes-256-cfb" +} +},{}],227:[function(require,module,exports){ +// from https://github.com/indutny/self-signed/blob/gh-pages/lib/asn1.js +// Fedor, you are amazing. +'use strict' + +var asn1 = require('asn1.js') + +exports.certificate = require('./certificate') + +var RSAPrivateKey = asn1.define('RSAPrivateKey', function () { + this.seq().obj( + this.key('version').int(), + this.key('modulus').int(), + this.key('publicExponent').int(), + this.key('privateExponent').int(), + this.key('prime1').int(), + this.key('prime2').int(), + this.key('exponent1').int(), + this.key('exponent2').int(), + this.key('coefficient').int() + ) +}) +exports.RSAPrivateKey = RSAPrivateKey + +var RSAPublicKey = asn1.define('RSAPublicKey', function () { + this.seq().obj( + this.key('modulus').int(), + this.key('publicExponent').int() + ) +}) +exports.RSAPublicKey = RSAPublicKey + +var PublicKey = asn1.define('SubjectPublicKeyInfo', function () { + this.seq().obj( + this.key('algorithm').use(AlgorithmIdentifier), + this.key('subjectPublicKey').bitstr() + ) +}) +exports.PublicKey = PublicKey + +var AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () { + this.seq().obj( + this.key('algorithm').objid(), + this.key('none').null_().optional(), + this.key('curve').objid().optional(), + this.key('params').seq().obj( + this.key('p').int(), + this.key('q').int(), + this.key('g').int() + ).optional() + ) +}) + +var PrivateKeyInfo = asn1.define('PrivateKeyInfo', function () { + this.seq().obj( + this.key('version').int(), + this.key('algorithm').use(AlgorithmIdentifier), + this.key('subjectPrivateKey').octstr() + ) +}) +exports.PrivateKey = PrivateKeyInfo +var EncryptedPrivateKeyInfo = asn1.define('EncryptedPrivateKeyInfo', function () { + this.seq().obj( + this.key('algorithm').seq().obj( + this.key('id').objid(), + this.key('decrypt').seq().obj( + this.key('kde').seq().obj( + this.key('id').objid(), + this.key('kdeparams').seq().obj( + this.key('salt').octstr(), + this.key('iters').int() + ) + ), + this.key('cipher').seq().obj( + this.key('algo').objid(), + this.key('iv').octstr() + ) + ) + ), + this.key('subjectPrivateKey').octstr() + ) +}) + +exports.EncryptedPrivateKey = EncryptedPrivateKeyInfo + +var DSAPrivateKey = asn1.define('DSAPrivateKey', function () { + this.seq().obj( + this.key('version').int(), + this.key('p').int(), + this.key('q').int(), + this.key('g').int(), + this.key('pub_key').int(), + this.key('priv_key').int() + ) +}) +exports.DSAPrivateKey = DSAPrivateKey + +exports.DSAparam = asn1.define('DSAparam', function () { + this.int() +}) + +var ECPrivateKey = asn1.define('ECPrivateKey', function () { + this.seq().obj( + this.key('version').int(), + this.key('privateKey').octstr(), + this.key('parameters').optional().explicit(0).use(ECParameters), + this.key('publicKey').optional().explicit(1).bitstr() + ) +}) +exports.ECPrivateKey = ECPrivateKey + +var ECParameters = asn1.define('ECParameters', function () { + this.choice({ + namedCurve: this.objid() + }) +}) + +exports.signature = asn1.define('signature', function () { + this.seq().obj( + this.key('r').int(), + this.key('s').int() + ) +}) + +},{"./certificate":228,"asn1.js":2}],228:[function(require,module,exports){ +// from https://github.com/Rantanen/node-dtls/blob/25a7dc861bda38cfeac93a723500eea4f0ac2e86/Certificate.js +// thanks to @Rantanen + +'use strict' + +var asn = require('asn1.js') + +var Time = asn.define('Time', function () { + this.choice({ + utcTime: this.utctime(), + generalTime: this.gentime() + }) +}) + +var AttributeTypeValue = asn.define('AttributeTypeValue', function () { + this.seq().obj( + this.key('type').objid(), + this.key('value').any() + ) +}) + +var AlgorithmIdentifier = asn.define('AlgorithmIdentifier', function () { + this.seq().obj( + this.key('algorithm').objid(), + this.key('parameters').optional(), + this.key('curve').objid().optional() + ) +}) + +var SubjectPublicKeyInfo = asn.define('SubjectPublicKeyInfo', function () { + this.seq().obj( + this.key('algorithm').use(AlgorithmIdentifier), + this.key('subjectPublicKey').bitstr() + ) +}) + +var RelativeDistinguishedName = asn.define('RelativeDistinguishedName', function () { + this.setof(AttributeTypeValue) +}) + +var RDNSequence = asn.define('RDNSequence', function () { + this.seqof(RelativeDistinguishedName) +}) + +var Name = asn.define('Name', function () { + this.choice({ + rdnSequence: this.use(RDNSequence) + }) +}) + +var Validity = asn.define('Validity', function () { + this.seq().obj( + this.key('notBefore').use(Time), + this.key('notAfter').use(Time) + ) +}) + +var Extension = asn.define('Extension', function () { + this.seq().obj( + this.key('extnID').objid(), + this.key('critical').bool().def(false), + this.key('extnValue').octstr() + ) +}) + +var TBSCertificate = asn.define('TBSCertificate', function () { + this.seq().obj( + this.key('version').explicit(0).int().optional(), + this.key('serialNumber').int(), + this.key('signature').use(AlgorithmIdentifier), + this.key('issuer').use(Name), + this.key('validity').use(Validity), + this.key('subject').use(Name), + this.key('subjectPublicKeyInfo').use(SubjectPublicKeyInfo), + this.key('issuerUniqueID').implicit(1).bitstr().optional(), + this.key('subjectUniqueID').implicit(2).bitstr().optional(), + this.key('extensions').explicit(3).seqof(Extension).optional() + ) +}) + +var X509Certificate = asn.define('X509Certificate', function () { + this.seq().obj( + this.key('tbsCertificate').use(TBSCertificate), + this.key('signatureAlgorithm').use(AlgorithmIdentifier), + this.key('signatureValue').bitstr() + ) +}) + +module.exports = X509Certificate + +},{"asn1.js":2}],229:[function(require,module,exports){ +// adapted from https://github.com/apatil/pemstrip +var findProc = /Proc-Type: 4,ENCRYPTED[\n\r]+DEK-Info: AES-((?:128)|(?:192)|(?:256))-CBC,([0-9A-H]+)[\n\r]+([0-9A-z\n\r+/=]+)[\n\r]+/m +var startRegex = /^-----BEGIN ((?:.*? KEY)|CERTIFICATE)-----/m +var fullRegex = /^-----BEGIN ((?:.*? KEY)|CERTIFICATE)-----([0-9A-z\n\r+/=]+)-----END \1-----$/m +var evp = require('evp_bytestokey') +var ciphers = require('browserify-aes') +var Buffer = require('safe-buffer').Buffer +module.exports = function (okey, password) { + var key = okey.toString() + var match = key.match(findProc) + var decrypted + if (!match) { + var match2 = key.match(fullRegex) + decrypted = Buffer.from(match2[2].replace(/[\r\n]/g, ''), 'base64') + } else { + var suite = 'aes' + match[1] + var iv = Buffer.from(match[2], 'hex') + var cipherText = Buffer.from(match[3].replace(/[\r\n]/g, ''), 'base64') + var cipherKey = evp(password, iv.slice(0, 8), parseInt(match[1], 10)).key + var out = [] + var cipher = ciphers.createDecipheriv(suite, cipherKey, iv) + out.push(cipher.update(cipherText)) + out.push(cipher.final()) + decrypted = Buffer.concat(out) + } + var tag = key.match(startRegex)[1] + return { + tag: tag, + data: decrypted + } +} + +},{"browserify-aes":23,"evp_bytestokey":106,"safe-buffer":250}],230:[function(require,module,exports){ +var asn1 = require('./asn1') +var aesid = require('./aesid.json') +var fixProc = require('./fixProc') +var ciphers = require('browserify-aes') +var compat = require('pbkdf2') +var Buffer = require('safe-buffer').Buffer +module.exports = parseKeys + +function parseKeys (buffer) { + var password + if (typeof buffer === 'object' && !Buffer.isBuffer(buffer)) { + password = buffer.passphrase + buffer = buffer.key + } + if (typeof buffer === 'string') { + buffer = Buffer.from(buffer) + } + + var stripped = fixProc(buffer, password) + + var type = stripped.tag + var data = stripped.data + var subtype, ndata + switch (type) { + case 'CERTIFICATE': + ndata = asn1.certificate.decode(data, 'der').tbsCertificate.subjectPublicKeyInfo + // falls through + case 'PUBLIC KEY': + if (!ndata) { + ndata = asn1.PublicKey.decode(data, 'der') + } + subtype = ndata.algorithm.algorithm.join('.') + switch (subtype) { + case '1.2.840.113549.1.1.1': + return asn1.RSAPublicKey.decode(ndata.subjectPublicKey.data, 'der') + case '1.2.840.10045.2.1': + ndata.subjectPrivateKey = ndata.subjectPublicKey + return { + type: 'ec', + data: ndata + } + case '1.2.840.10040.4.1': + ndata.algorithm.params.pub_key = asn1.DSAparam.decode(ndata.subjectPublicKey.data, 'der') + return { + type: 'dsa', + data: ndata.algorithm.params + } + default: throw new Error('unknown key id ' + subtype) + } + // throw new Error('unknown key type ' + type) + case 'ENCRYPTED PRIVATE KEY': + data = asn1.EncryptedPrivateKey.decode(data, 'der') + data = decrypt(data, password) + // falls through + case 'PRIVATE KEY': + ndata = asn1.PrivateKey.decode(data, 'der') + subtype = ndata.algorithm.algorithm.join('.') + switch (subtype) { + case '1.2.840.113549.1.1.1': + return asn1.RSAPrivateKey.decode(ndata.subjectPrivateKey, 'der') + case '1.2.840.10045.2.1': + return { + curve: ndata.algorithm.curve, + privateKey: asn1.ECPrivateKey.decode(ndata.subjectPrivateKey, 'der').privateKey + } + case '1.2.840.10040.4.1': + ndata.algorithm.params.priv_key = asn1.DSAparam.decode(ndata.subjectPrivateKey, 'der') + return { + type: 'dsa', + params: ndata.algorithm.params + } + default: throw new Error('unknown key id ' + subtype) + } + // throw new Error('unknown key type ' + type) + case 'RSA PUBLIC KEY': + return asn1.RSAPublicKey.decode(data, 'der') + case 'RSA PRIVATE KEY': + return asn1.RSAPrivateKey.decode(data, 'der') + case 'DSA PRIVATE KEY': + return { + type: 'dsa', + params: asn1.DSAPrivateKey.decode(data, 'der') + } + case 'EC PRIVATE KEY': + data = asn1.ECPrivateKey.decode(data, 'der') + return { + curve: data.parameters.value, + privateKey: data.privateKey + } + default: throw new Error('unknown key type ' + type) + } +} +parseKeys.signature = asn1.signature +function decrypt (data, password) { + var salt = data.algorithm.decrypt.kde.kdeparams.salt + var iters = parseInt(data.algorithm.decrypt.kde.kdeparams.iters.toString(), 10) + var algo = aesid[data.algorithm.decrypt.cipher.algo.join('.')] + var iv = data.algorithm.decrypt.cipher.iv + var cipherText = data.subjectPrivateKey + var keylen = parseInt(algo.split('-')[1], 10) / 8 + var key = compat.pbkdf2Sync(password, salt, iters, keylen, 'sha1') + var cipher = ciphers.createDecipheriv(algo, key, iv) + var out = [] + out.push(cipher.update(cipherText)) + out.push(cipher.final()) + return Buffer.concat(out) +} + +},{"./aesid.json":226,"./asn1":227,"./fixProc":229,"browserify-aes":23,"pbkdf2":231,"safe-buffer":250}],231:[function(require,module,exports){ +exports.pbkdf2 = require('./lib/async') +exports.pbkdf2Sync = require('./lib/sync') + +},{"./lib/async":232,"./lib/sync":235}],232:[function(require,module,exports){ +(function (global){(function (){ +var Buffer = require('safe-buffer').Buffer + +var checkParameters = require('./precondition') +var defaultEncoding = require('./default-encoding') +var sync = require('./sync') +var toBuffer = require('./to-buffer') + +var ZERO_BUF +var subtle = global.crypto && global.crypto.subtle +var toBrowser = { + sha: 'SHA-1', + 'sha-1': 'SHA-1', + sha1: 'SHA-1', + sha256: 'SHA-256', + 'sha-256': 'SHA-256', + sha384: 'SHA-384', + 'sha-384': 'SHA-384', + 'sha-512': 'SHA-512', + sha512: 'SHA-512' +} +var checks = [] +function checkNative (algo) { + if (global.process && !global.process.browser) { + return Promise.resolve(false) + } + if (!subtle || !subtle.importKey || !subtle.deriveBits) { + return Promise.resolve(false) + } + if (checks[algo] !== undefined) { + return checks[algo] + } + ZERO_BUF = ZERO_BUF || Buffer.alloc(8) + var prom = browserPbkdf2(ZERO_BUF, ZERO_BUF, 10, 128, algo) + .then(function () { + return true + }).catch(function () { + return false + }) + checks[algo] = prom + return prom +} +var nextTick +function getNextTick () { + if (nextTick) { + return nextTick + } + if (global.process && global.process.nextTick) { + nextTick = global.process.nextTick + } else if (global.queueMicrotask) { + nextTick = global.queueMicrotask + } else if (global.setImmediate) { + nextTick = global.setImmediate + } else { + nextTick = global.setTimeout + } + return nextTick +} +function browserPbkdf2 (password, salt, iterations, length, algo) { + return subtle.importKey( + 'raw', password, { name: 'PBKDF2' }, false, ['deriveBits'] + ).then(function (key) { + return subtle.deriveBits({ + name: 'PBKDF2', + salt: salt, + iterations: iterations, + hash: { + name: algo + } + }, key, length << 3) + }).then(function (res) { + return Buffer.from(res) + }) +} + +function resolvePromise (promise, callback) { + promise.then(function (out) { + getNextTick()(function () { + callback(null, out) + }) + }, function (e) { + getNextTick()(function () { + callback(e) + }) + }) +} +module.exports = function (password, salt, iterations, keylen, digest, callback) { + if (typeof digest === 'function') { + callback = digest + digest = undefined + } + + digest = digest || 'sha1' + var algo = toBrowser[digest.toLowerCase()] + + if (!algo || typeof global.Promise !== 'function') { + getNextTick()(function () { + var out + try { + out = sync(password, salt, iterations, keylen, digest) + } catch (e) { + return callback(e) + } + callback(null, out) + }) + return + } + + checkParameters(iterations, keylen) + password = toBuffer(password, defaultEncoding, 'Password') + salt = toBuffer(salt, defaultEncoding, 'Salt') + if (typeof callback !== 'function') throw new Error('No callback provided to pbkdf2') + + resolvePromise(checkNative(algo).then(function (resp) { + if (resp) return browserPbkdf2(password, salt, iterations, keylen, algo) + + return sync(password, salt, iterations, keylen, digest) + }), callback) +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./default-encoding":233,"./precondition":234,"./sync":235,"./to-buffer":236,"safe-buffer":250}],233:[function(require,module,exports){ +(function (process,global){(function (){ +var defaultEncoding +/* istanbul ignore next */ +if (global.process && global.process.browser) { + defaultEncoding = 'utf-8' +} else if (global.process && global.process.version) { + var pVersionMajor = parseInt(process.version.split('.')[0].slice(1), 10) + + defaultEncoding = pVersionMajor >= 6 ? 'utf-8' : 'binary' +} else { + defaultEncoding = 'utf-8' +} +module.exports = defaultEncoding + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"_process":237}],234:[function(require,module,exports){ +var MAX_ALLOC = Math.pow(2, 30) - 1 // default in iojs + +module.exports = function (iterations, keylen) { + if (typeof iterations !== 'number') { + throw new TypeError('Iterations not a number') + } + + if (iterations < 0) { + throw new TypeError('Bad iterations') + } + + if (typeof keylen !== 'number') { + throw new TypeError('Key length not a number') + } + + if (keylen < 0 || keylen > MAX_ALLOC || keylen !== keylen) { /* eslint no-self-compare: 0 */ + throw new TypeError('Bad key length') + } +} + +},{}],235:[function(require,module,exports){ +var md5 = require('create-hash/md5') +var RIPEMD160 = require('ripemd160') +var sha = require('sha.js') +var Buffer = require('safe-buffer').Buffer + +var checkParameters = require('./precondition') +var defaultEncoding = require('./default-encoding') +var toBuffer = require('./to-buffer') + +var ZEROS = Buffer.alloc(128) +var sizes = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20 +} + +function Hmac (alg, key, saltLen) { + var hash = getDigest(alg) + var blocksize = (alg === 'sha512' || alg === 'sha384') ? 128 : 64 + + if (key.length > blocksize) { + key = hash(key) + } else if (key.length < blocksize) { + key = Buffer.concat([key, ZEROS], blocksize) + } + + var ipad = Buffer.allocUnsafe(blocksize + sizes[alg]) + var opad = Buffer.allocUnsafe(blocksize + sizes[alg]) + for (var i = 0; i < blocksize; i++) { + ipad[i] = key[i] ^ 0x36 + opad[i] = key[i] ^ 0x5C + } + + var ipad1 = Buffer.allocUnsafe(blocksize + saltLen + 4) + ipad.copy(ipad1, 0, 0, blocksize) + this.ipad1 = ipad1 + this.ipad2 = ipad + this.opad = opad + this.alg = alg + this.blocksize = blocksize + this.hash = hash + this.size = sizes[alg] +} + +Hmac.prototype.run = function (data, ipad) { + data.copy(ipad, this.blocksize) + var h = this.hash(ipad) + h.copy(this.opad, this.blocksize) + return this.hash(this.opad) +} + +function getDigest (alg) { + function shaFunc (data) { + return sha(alg).update(data).digest() + } + function rmd160Func (data) { + return new RIPEMD160().update(data).digest() + } + + if (alg === 'rmd160' || alg === 'ripemd160') return rmd160Func + if (alg === 'md5') return md5 + return shaFunc +} + +function pbkdf2 (password, salt, iterations, keylen, digest) { + checkParameters(iterations, keylen) + password = toBuffer(password, defaultEncoding, 'Password') + salt = toBuffer(salt, defaultEncoding, 'Salt') + + digest = digest || 'sha1' + + var hmac = new Hmac(digest, password, salt.length) + + var DK = Buffer.allocUnsafe(keylen) + var block1 = Buffer.allocUnsafe(salt.length + 4) + salt.copy(block1, 0, 0, salt.length) + + var destPos = 0 + var hLen = sizes[digest] + var l = Math.ceil(keylen / hLen) + + for (var i = 1; i <= l; i++) { + block1.writeUInt32BE(i, salt.length) + + var T = hmac.run(block1, hmac.ipad1) + var U = T + + for (var j = 1; j < iterations; j++) { + U = hmac.run(U, hmac.ipad2) + for (var k = 0; k < hLen; k++) T[k] ^= U[k] + } + + T.copy(DK, destPos) + destPos += hLen + } + + return DK +} + +module.exports = pbkdf2 + +},{"./default-encoding":233,"./precondition":234,"./to-buffer":236,"create-hash/md5":75,"ripemd160":249,"safe-buffer":250,"sha.js":257}],236:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer + +module.exports = function (thing, encoding, name) { + if (Buffer.isBuffer(thing)) { + return thing + } else if (typeof thing === 'string') { + return Buffer.from(thing, encoding) + } else if (ArrayBuffer.isView(thing)) { + return Buffer.from(thing.buffer) + } else { + throw new TypeError(name + ' must be a string, a Buffer, a typed array or a DataView') + } +} + +},{"safe-buffer":250}],237:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],238:[function(require,module,exports){ +exports.publicEncrypt = require('./publicEncrypt') +exports.privateDecrypt = require('./privateDecrypt') + +exports.privateEncrypt = function privateEncrypt (key, buf) { + return exports.publicEncrypt(key, buf, true) +} + +exports.publicDecrypt = function publicDecrypt (key, buf) { + return exports.privateDecrypt(key, buf, true) +} + +},{"./privateDecrypt":240,"./publicEncrypt":241}],239:[function(require,module,exports){ +var createHash = require('create-hash') +var Buffer = require('safe-buffer').Buffer + +module.exports = function (seed, len) { + var t = Buffer.alloc(0) + var i = 0 + var c + while (t.length < len) { + c = i2ops(i++) + t = Buffer.concat([t, createHash('sha1').update(seed).update(c).digest()]) + } + return t.slice(0, len) +} + +function i2ops (c) { + var out = Buffer.allocUnsafe(4) + out.writeUInt32BE(c, 0) + return out +} + +},{"create-hash":74,"safe-buffer":250}],240:[function(require,module,exports){ +var parseKeys = require('parse-asn1') +var mgf = require('./mgf') +var xor = require('./xor') +var BN = require('bn.js') +var crt = require('browserify-rsa') +var createHash = require('create-hash') +var withPublic = require('./withPublic') +var Buffer = require('safe-buffer').Buffer + +module.exports = function privateDecrypt (privateKey, enc, reverse) { + var padding + if (privateKey.padding) { + padding = privateKey.padding + } else if (reverse) { + padding = 1 + } else { + padding = 4 + } + + var key = parseKeys(privateKey) + var k = key.modulus.byteLength() + if (enc.length > k || new BN(enc).cmp(key.modulus) >= 0) { + throw new Error('decryption error') + } + var msg + if (reverse) { + msg = withPublic(new BN(enc), key) + } else { + msg = crt(enc, key) + } + var zBuffer = Buffer.alloc(k - msg.length) + msg = Buffer.concat([zBuffer, msg], k) + if (padding === 4) { + return oaep(key, msg) + } else if (padding === 1) { + return pkcs1(key, msg, reverse) + } else if (padding === 3) { + return msg + } else { + throw new Error('unknown padding') + } +} + +function oaep (key, msg) { + var k = key.modulus.byteLength() + var iHash = createHash('sha1').update(Buffer.alloc(0)).digest() + var hLen = iHash.length + if (msg[0] !== 0) { + throw new Error('decryption error') + } + var maskedSeed = msg.slice(1, hLen + 1) + var maskedDb = msg.slice(hLen + 1) + var seed = xor(maskedSeed, mgf(maskedDb, hLen)) + var db = xor(maskedDb, mgf(seed, k - hLen - 1)) + if (compare(iHash, db.slice(0, hLen))) { + throw new Error('decryption error') + } + var i = hLen + while (db[i] === 0) { + i++ + } + if (db[i++] !== 1) { + throw new Error('decryption error') + } + return db.slice(i) +} + +function pkcs1 (key, msg, reverse) { + var p1 = msg.slice(0, 2) + var i = 2 + var status = 0 + while (msg[i++] !== 0) { + if (i >= msg.length) { + status++ + break + } + } + var ps = msg.slice(2, i - 1) + + if ((p1.toString('hex') !== '0002' && !reverse) || (p1.toString('hex') !== '0001' && reverse)) { + status++ + } + if (ps.length < 8) { + status++ + } + if (status) { + throw new Error('decryption error') + } + return msg.slice(i) +} +function compare (a, b) { + a = Buffer.from(a) + b = Buffer.from(b) + var dif = 0 + var len = a.length + if (a.length !== b.length) { + dif++ + len = Math.min(a.length, b.length) + } + var i = -1 + while (++i < len) { + dif += (a[i] ^ b[i]) + } + return dif +} + +},{"./mgf":239,"./withPublic":242,"./xor":243,"bn.js":18,"browserify-rsa":41,"create-hash":74,"parse-asn1":230,"safe-buffer":250}],241:[function(require,module,exports){ +var parseKeys = require('parse-asn1') +var randomBytes = require('randombytes') +var createHash = require('create-hash') +var mgf = require('./mgf') +var xor = require('./xor') +var BN = require('bn.js') +var withPublic = require('./withPublic') +var crt = require('browserify-rsa') +var Buffer = require('safe-buffer').Buffer + +module.exports = function publicEncrypt (publicKey, msg, reverse) { + var padding + if (publicKey.padding) { + padding = publicKey.padding + } else if (reverse) { + padding = 1 + } else { + padding = 4 + } + var key = parseKeys(publicKey) + var paddedMsg + if (padding === 4) { + paddedMsg = oaep(key, msg) + } else if (padding === 1) { + paddedMsg = pkcs1(key, msg, reverse) + } else if (padding === 3) { + paddedMsg = new BN(msg) + if (paddedMsg.cmp(key.modulus) >= 0) { + throw new Error('data too long for modulus') + } + } else { + throw new Error('unknown padding') + } + if (reverse) { + return crt(paddedMsg, key) + } else { + return withPublic(paddedMsg, key) + } +} + +function oaep (key, msg) { + var k = key.modulus.byteLength() + var mLen = msg.length + var iHash = createHash('sha1').update(Buffer.alloc(0)).digest() + var hLen = iHash.length + var hLen2 = 2 * hLen + if (mLen > k - hLen2 - 2) { + throw new Error('message too long') + } + var ps = Buffer.alloc(k - mLen - hLen2 - 2) + var dblen = k - hLen - 1 + var seed = randomBytes(hLen) + var maskedDb = xor(Buffer.concat([iHash, ps, Buffer.alloc(1, 1), msg], dblen), mgf(seed, dblen)) + var maskedSeed = xor(seed, mgf(maskedDb, hLen)) + return new BN(Buffer.concat([Buffer.alloc(1), maskedSeed, maskedDb], k)) +} +function pkcs1 (key, msg, reverse) { + var mLen = msg.length + var k = key.modulus.byteLength() + if (mLen > k - 11) { + throw new Error('message too long') + } + var ps + if (reverse) { + ps = Buffer.alloc(k - mLen - 3, 0xff) + } else { + ps = nonZero(k - mLen - 3) + } + return new BN(Buffer.concat([Buffer.from([0, reverse ? 1 : 2]), ps, Buffer.alloc(1), msg], k)) +} +function nonZero (len) { + var out = Buffer.allocUnsafe(len) + var i = 0 + var cache = randomBytes(len * 2) + var cur = 0 + var num + while (i < len) { + if (cur === cache.length) { + cache = randomBytes(len * 2) + cur = 0 + } + num = cache[cur++] + if (num) { + out[i++] = num + } + } + return out +} + +},{"./mgf":239,"./withPublic":242,"./xor":243,"bn.js":18,"browserify-rsa":41,"create-hash":74,"parse-asn1":230,"randombytes":244,"safe-buffer":250}],242:[function(require,module,exports){ +var BN = require('bn.js') +var Buffer = require('safe-buffer').Buffer + +function withPublic (paddedMsg, key) { + return Buffer.from(paddedMsg + .toRed(BN.mont(key.modulus)) + .redPow(new BN(key.publicExponent)) + .fromRed() + .toArray()) +} + +module.exports = withPublic + +},{"bn.js":18,"safe-buffer":250}],243:[function(require,module,exports){ +module.exports = function xor (a, b) { + var len = a.length + var i = -1 + while (++i < len) { + a[i] ^= b[i] + } + return a +} + +},{}],244:[function(require,module,exports){ +(function (process,global){(function (){ +'use strict' + +// limit of Crypto.getRandomValues() +// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +var MAX_BYTES = 65536 + +// Node supports requesting up to this number of bytes +// https://github.com/nodejs/node/blob/master/lib/internal/crypto/random.js#L48 +var MAX_UINT32 = 4294967295 + +function oldBrowser () { + throw new Error('Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11') +} + +var Buffer = require('safe-buffer').Buffer +var crypto = global.crypto || global.msCrypto + +if (crypto && crypto.getRandomValues) { + module.exports = randomBytes +} else { + module.exports = oldBrowser +} + +function randomBytes (size, cb) { + // phantomjs needs to throw + if (size > MAX_UINT32) throw new RangeError('requested too many random bytes') + + var bytes = Buffer.allocUnsafe(size) + + if (size > 0) { // getRandomValues fails on IE if size == 0 + if (size > MAX_BYTES) { // this is the max bytes crypto.getRandomValues + // can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues + for (var generated = 0; generated < size; generated += MAX_BYTES) { + // buffer.slice automatically checks if the end is past the end of + // the buffer so we don't have to here + crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES)) + } + } else { + crypto.getRandomValues(bytes) + } + } + + if (typeof cb === 'function') { + return process.nextTick(function () { + cb(null, bytes) + }) + } + + return bytes +} + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"_process":237,"safe-buffer":250}],245:[function(require,module,exports){ +(function (process,global){(function (){ +'use strict' + +function oldBrowser () { + throw new Error('secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11') +} +var safeBuffer = require('safe-buffer') +var randombytes = require('randombytes') +var Buffer = safeBuffer.Buffer +var kBufferMaxLength = safeBuffer.kMaxLength +var crypto = global.crypto || global.msCrypto +var kMaxUint32 = Math.pow(2, 32) - 1 +function assertOffset (offset, length) { + if (typeof offset !== 'number' || offset !== offset) { // eslint-disable-line no-self-compare + throw new TypeError('offset must be a number') + } + + if (offset > kMaxUint32 || offset < 0) { + throw new TypeError('offset must be a uint32') + } + + if (offset > kBufferMaxLength || offset > length) { + throw new RangeError('offset out of range') + } +} + +function assertSize (size, offset, length) { + if (typeof size !== 'number' || size !== size) { // eslint-disable-line no-self-compare + throw new TypeError('size must be a number') + } + + if (size > kMaxUint32 || size < 0) { + throw new TypeError('size must be a uint32') + } + + if (size + offset > length || size > kBufferMaxLength) { + throw new RangeError('buffer too small') + } +} +if ((crypto && crypto.getRandomValues) || !process.browser) { + exports.randomFill = randomFill + exports.randomFillSync = randomFillSync +} else { + exports.randomFill = oldBrowser + exports.randomFillSync = oldBrowser +} +function randomFill (buf, offset, size, cb) { + if (!Buffer.isBuffer(buf) && !(buf instanceof global.Uint8Array)) { + throw new TypeError('"buf" argument must be a Buffer or Uint8Array') + } + + if (typeof offset === 'function') { + cb = offset + offset = 0 + size = buf.length + } else if (typeof size === 'function') { + cb = size + size = buf.length - offset + } else if (typeof cb !== 'function') { + throw new TypeError('"cb" argument must be a function') + } + assertOffset(offset, buf.length) + assertSize(size, offset, buf.length) + return actualFill(buf, offset, size, cb) +} + +function actualFill (buf, offset, size, cb) { + if (process.browser) { + var ourBuf = buf.buffer + var uint = new Uint8Array(ourBuf, offset, size) + crypto.getRandomValues(uint) + if (cb) { + process.nextTick(function () { + cb(null, buf) + }) + return + } + return buf + } + if (cb) { + randombytes(size, function (err, bytes) { + if (err) { + return cb(err) + } + bytes.copy(buf, offset) + cb(null, buf) + }) + return + } + var bytes = randombytes(size) + bytes.copy(buf, offset) + return buf +} +function randomFillSync (buf, offset, size) { + if (typeof offset === 'undefined') { + offset = 0 + } + if (!Buffer.isBuffer(buf) && !(buf instanceof global.Uint8Array)) { + throw new TypeError('"buf" argument must be a Buffer or Uint8Array') + } + + assertOffset(offset, buf.length) + + if (size === undefined) size = buf.length - offset + + assertSize(size, offset, buf.length) + + return actualFill(buf, offset, size) +} + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"_process":237,"randombytes":244,"safe-buffer":250}],246:[function(require,module,exports){ +module.exports = require('./lib/retry'); +},{"./lib/retry":247}],247:[function(require,module,exports){ +var RetryOperation = require('./retry_operation'); + +exports.operation = function(options) { + var timeouts = exports.timeouts(options); + return new RetryOperation(timeouts, { + forever: options && (options.forever || options.retries === Infinity), + unref: options && options.unref, + maxRetryTime: options && options.maxRetryTime + }); +}; + +exports.timeouts = function(options) { + if (options instanceof Array) { + return [].concat(options); + } + + var opts = { + retries: 10, + factor: 2, + minTimeout: 1 * 1000, + maxTimeout: Infinity, + randomize: false + }; + for (var key in options) { + opts[key] = options[key]; + } + + if (opts.minTimeout > opts.maxTimeout) { + throw new Error('minTimeout is greater than maxTimeout'); + } + + var timeouts = []; + for (var i = 0; i < opts.retries; i++) { + timeouts.push(this.createTimeout(i, opts)); + } + + if (options && options.forever && !timeouts.length) { + timeouts.push(this.createTimeout(i, opts)); + } + + // sort the array numerically ascending + timeouts.sort(function(a,b) { + return a - b; + }); + + return timeouts; +}; + +exports.createTimeout = function(attempt, opts) { + var random = (opts.randomize) + ? (Math.random() + 1) + : 1; + + var timeout = Math.round(random * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt)); + timeout = Math.min(timeout, opts.maxTimeout); + + return timeout; +}; + +exports.wrap = function(obj, options, methods) { + if (options instanceof Array) { + methods = options; + options = null; + } + + if (!methods) { + methods = []; + for (var key in obj) { + if (typeof obj[key] === 'function') { + methods.push(key); + } + } + } + + for (var i = 0; i < methods.length; i++) { + var method = methods[i]; + var original = obj[method]; + + obj[method] = function retryWrapper(original) { + var op = exports.operation(options); + var args = Array.prototype.slice.call(arguments, 1); + var callback = args.pop(); + + args.push(function(err) { + if (op.retry(err)) { + return; + } + if (err) { + arguments[0] = op.mainError(); + } + callback.apply(this, arguments); + }); + + op.attempt(function() { + original.apply(obj, args); + }); + }.bind(obj, original); + obj[method].options = options; + } +}; + +},{"./retry_operation":248}],248:[function(require,module,exports){ +function RetryOperation(timeouts, options) { + // Compatibility for the old (timeouts, retryForever) signature + if (typeof options === 'boolean') { + options = { forever: options }; + } + + this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); + this._timeouts = timeouts; + this._options = options || {}; + this._maxRetryTime = options && options.maxRetryTime || Infinity; + this._fn = null; + this._errors = []; + this._attempts = 1; + this._operationTimeout = null; + this._operationTimeoutCb = null; + this._timeout = null; + this._operationStart = null; + this._timer = null; + + if (this._options.forever) { + this._cachedTimeouts = this._timeouts.slice(0); + } +} +module.exports = RetryOperation; + +RetryOperation.prototype.reset = function() { + this._attempts = 1; + this._timeouts = this._originalTimeouts.slice(0); +} + +RetryOperation.prototype.stop = function() { + if (this._timeout) { + clearTimeout(this._timeout); + } + if (this._timer) { + clearTimeout(this._timer); + } + + this._timeouts = []; + this._cachedTimeouts = null; +}; + +RetryOperation.prototype.retry = function(err) { + if (this._timeout) { + clearTimeout(this._timeout); + } + + if (!err) { + return false; + } + var currentTime = new Date().getTime(); + if (err && currentTime - this._operationStart >= this._maxRetryTime) { + this._errors.push(err); + this._errors.unshift(new Error('RetryOperation timeout occurred')); + return false; + } + + this._errors.push(err); + + var timeout = this._timeouts.shift(); + if (timeout === undefined) { + if (this._cachedTimeouts) { + // retry forever, only keep last error + this._errors.splice(0, this._errors.length - 1); + timeout = this._cachedTimeouts.slice(-1); + } else { + return false; + } + } + + var self = this; + this._timer = setTimeout(function() { + self._attempts++; + + if (self._operationTimeoutCb) { + self._timeout = setTimeout(function() { + self._operationTimeoutCb(self._attempts); + }, self._operationTimeout); + + if (self._options.unref) { + self._timeout.unref(); + } + } + + self._fn(self._attempts); + }, timeout); + + if (this._options.unref) { + this._timer.unref(); + } + + return true; +}; + +RetryOperation.prototype.attempt = function(fn, timeoutOps) { + this._fn = fn; + + if (timeoutOps) { + if (timeoutOps.timeout) { + this._operationTimeout = timeoutOps.timeout; + } + if (timeoutOps.cb) { + this._operationTimeoutCb = timeoutOps.cb; + } + } + + var self = this; + if (this._operationTimeoutCb) { + this._timeout = setTimeout(function() { + self._operationTimeoutCb(); + }, self._operationTimeout); + } + + this._operationStart = new Date().getTime(); + + this._fn(this._attempts); +}; + +RetryOperation.prototype.try = function(fn) { + console.log('Using RetryOperation.try() is deprecated'); + this.attempt(fn); +}; + +RetryOperation.prototype.start = function(fn) { + console.log('Using RetryOperation.start() is deprecated'); + this.attempt(fn); +}; + +RetryOperation.prototype.start = RetryOperation.prototype.try; + +RetryOperation.prototype.errors = function() { + return this._errors; +}; + +RetryOperation.prototype.attempts = function() { + return this._attempts; +}; + +RetryOperation.prototype.mainError = function() { + if (this._errors.length === 0) { + return null; + } + + var counts = {}; + var mainError = null; + var mainErrorCount = 0; + + for (var i = 0; i < this._errors.length; i++) { + var error = this._errors[i]; + var message = error.message; + var count = (counts[message] || 0) + 1; + + counts[message] = count; + + if (count >= mainErrorCount) { + mainError = error; + mainErrorCount = count; + } + } + + return mainError; +}; + +},{}],249:[function(require,module,exports){ +'use strict' +var Buffer = require('buffer').Buffer +var inherits = require('inherits') +var HashBase = require('hash-base') + +var ARRAY16 = new Array(16) + +var zl = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +var zr = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +var sl = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +var sr = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +var hl = [0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] +var hr = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000] + +function RIPEMD160 () { + HashBase.call(this, 64) + + // state + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 + this._e = 0xc3d2e1f0 +} + +inherits(RIPEMD160, HashBase) + +RIPEMD160.prototype._update = function () { + var words = ARRAY16 + for (var j = 0; j < 16; ++j) words[j] = this._block.readInt32LE(j * 4) + + var al = this._a | 0 + var bl = this._b | 0 + var cl = this._c | 0 + var dl = this._d | 0 + var el = this._e | 0 + + var ar = this._a | 0 + var br = this._b | 0 + var cr = this._c | 0 + var dr = this._d | 0 + var er = this._e | 0 + + // computation + for (var i = 0; i < 80; i += 1) { + var tl + var tr + if (i < 16) { + tl = fn1(al, bl, cl, dl, el, words[zl[i]], hl[0], sl[i]) + tr = fn5(ar, br, cr, dr, er, words[zr[i]], hr[0], sr[i]) + } else if (i < 32) { + tl = fn2(al, bl, cl, dl, el, words[zl[i]], hl[1], sl[i]) + tr = fn4(ar, br, cr, dr, er, words[zr[i]], hr[1], sr[i]) + } else if (i < 48) { + tl = fn3(al, bl, cl, dl, el, words[zl[i]], hl[2], sl[i]) + tr = fn3(ar, br, cr, dr, er, words[zr[i]], hr[2], sr[i]) + } else if (i < 64) { + tl = fn4(al, bl, cl, dl, el, words[zl[i]], hl[3], sl[i]) + tr = fn2(ar, br, cr, dr, er, words[zr[i]], hr[3], sr[i]) + } else { // if (i<80) { + tl = fn5(al, bl, cl, dl, el, words[zl[i]], hl[4], sl[i]) + tr = fn1(ar, br, cr, dr, er, words[zr[i]], hr[4], sr[i]) + } + + al = el + el = dl + dl = rotl(cl, 10) + cl = bl + bl = tl + + ar = er + er = dr + dr = rotl(cr, 10) + cr = br + br = tr + } + + // update state + var t = (this._b + cl + dr) | 0 + this._b = (this._c + dl + er) | 0 + this._c = (this._d + el + ar) | 0 + this._d = (this._e + al + br) | 0 + this._e = (this._a + bl + cr) | 0 + this._a = t +} + +RIPEMD160.prototype._digest = function () { + // create padding and handle blocks + this._block[this._blockOffset++] = 0x80 + if (this._blockOffset > 56) { + this._block.fill(0, this._blockOffset, 64) + this._update() + this._blockOffset = 0 + } + + this._block.fill(0, this._blockOffset, 56) + this._block.writeUInt32LE(this._length[0], 56) + this._block.writeUInt32LE(this._length[1], 60) + this._update() + + // produce result + var buffer = Buffer.alloc ? Buffer.alloc(20) : new Buffer(20) + buffer.writeInt32LE(this._a, 0) + buffer.writeInt32LE(this._b, 4) + buffer.writeInt32LE(this._c, 8) + buffer.writeInt32LE(this._d, 12) + buffer.writeInt32LE(this._e, 16) + return buffer +} + +function rotl (x, n) { + return (x << n) | (x >>> (32 - n)) +} + +function fn1 (a, b, c, d, e, m, k, s) { + return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + e) | 0 +} + +function fn2 (a, b, c, d, e, m, k, s) { + return (rotl((a + ((b & c) | ((~b) & d)) + m + k) | 0, s) + e) | 0 +} + +function fn3 (a, b, c, d, e, m, k, s) { + return (rotl((a + ((b | (~c)) ^ d) + m + k) | 0, s) + e) | 0 +} + +function fn4 (a, b, c, d, e, m, k, s) { + return (rotl((a + ((b & d) | (c & (~d))) + m + k) | 0, s) + e) | 0 +} + +function fn5 (a, b, c, d, e, m, k, s) { + return (rotl((a + (b ^ (c | (~d))) + m + k) | 0, s) + e) | 0 +} + +module.exports = RIPEMD160 + +},{"buffer":68,"hash-base":116,"inherits":146}],250:[function(require,module,exports){ +/*! safe-buffer. MIT License. Feross Aboukhadijeh */ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.prototype = Object.create(Buffer.prototype) + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} + +},{"buffer":68}],251:[function(require,module,exports){ +(function (process){(function (){ +/* eslint-disable node/no-deprecated-api */ + +'use strict' + +var buffer = require('buffer') +var Buffer = buffer.Buffer + +var safer = {} + +var key + +for (key in buffer) { + if (!buffer.hasOwnProperty(key)) continue + if (key === 'SlowBuffer' || key === 'Buffer') continue + safer[key] = buffer[key] +} + +var Safer = safer.Buffer = {} +for (key in Buffer) { + if (!Buffer.hasOwnProperty(key)) continue + if (key === 'allocUnsafe' || key === 'allocUnsafeSlow') continue + Safer[key] = Buffer[key] +} + +safer.Buffer.prototype = Buffer.prototype + +if (!Safer.from || Safer.from === Uint8Array.from) { + Safer.from = function (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('The "value" argument must not be of type number. Received type ' + typeof value) + } + if (value && typeof value.length === 'undefined') { + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ' + typeof value) + } + return Buffer(value, encodingOrOffset, length) + } +} + +if (!Safer.alloc) { + Safer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('The "size" argument must be of type number. Received type ' + typeof size) + } + if (size < 0 || size >= 2 * (1 << 30)) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } + var buf = Buffer(size) + if (!fill || fill.length === 0) { + buf.fill(0) + } else if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + return buf + } +} + +if (!safer.kStringMaxLength) { + try { + safer.kStringMaxLength = process.binding('buffer').kStringMaxLength + } catch (e) { + // we can't determine kStringMaxLength in environments where process.binding + // is unsupported, so let's not set it + } +} + +if (!safer.constants) { + safer.constants = { + MAX_LENGTH: safer.kMaxLength + } + if (safer.kStringMaxLength) { + safer.constants.MAX_STRING_LENGTH = safer.kStringMaxLength + } +} + +module.exports = safer + +}).call(this)}).call(this,require('_process')) + +},{"_process":237,"buffer":68}],252:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ + // o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: '%s %s %d %s IP%d %s' + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly... + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + // k: [{}], // outdated thing ignored + t: [{ + // t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: '%d %d' + }], + c: [{ + // c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: 'IN IP%d %s' + }], + b: [{ + // b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: '%s:%s' + }], + m: [{ + // m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: '%s %d %s %s' + }], + a: [ + { + // a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) + ? 'rtpmap:%d %s/%s/%s' + : o.rate + ? 'rtpmap:%d %s/%s' + : 'rtpmap:%d %s'; + } + }, + { + // a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + // a=fmtp:111 minptime=10; useinbandfec=1 + push: 'fmtp', + reg: /^fmtp:(\d*) ([\S| ]*)/, + names: ['payload', 'config'], + format: 'fmtp:%d %s' + }, + { + // a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: 'control:%s' + }, + { + // a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) + ? 'rtcp:%d %s IP%d %s' + : 'rtcp:%d'; + } + }, + { + // a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: 'rtcp-fb:%s trr-int %d' + }, + { + // a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) + ? 'rtcp-fb:%s %s %s' + : 'rtcp-fb:%s %s'; + } + }, + { + // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + // a=extmap:1/recvonly URI-gps-string + // a=extmap:3 urn:ietf:params:rtp-hdrext:encrypt urn:ietf:params:rtp-hdrext:smpte-tc 25@600/24 + push: 'ext', + reg: /^extmap:(\d+)(?:\/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?/, + names: ['value', 'direction', 'encrypt-uri', 'uri', 'config'], + format: function (o) { + return ( + 'extmap:%d' + + (o.direction ? '/%s' : '%v') + + (o['encrypt-uri'] ? ' %s' : '%v') + + ' %s' + + (o.config ? ' %s' : '') + ); + } + }, + { + // a=extmap-allow-mixed + name: 'extmapAllowMixed', + reg: /^(extmap-allow-mixed)/ + }, + { + // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) + ? 'crypto:%d %s %s %s' + : 'crypto:%d %s %s'; + } + }, + { + // a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: 'setup:%s' + }, + { + // a=connection:new + name: 'connectionType', + reg: /^connection:(new|existing)/, + format: 'connection:%s' + }, + { + // a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: 'mid:%s' + }, + { + // a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: 'msid:%s' + }, + { + // a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*(?:\.\d*)*)/, + format: 'ptime:%d' + }, + { + // a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*(?:\.\d*)*)/, + format: 'maxptime:%d' + }, + { + // a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { + // a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { + // a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: 'ice-ufrag:%s' + }, + { + // a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: 'ice-pwd:%s' + }, + { + // a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: 'fingerprint:%s %s' + }, + { + // a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + // a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10 + // a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10 + // a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10 + // a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'], + format: function (o) { + var str = 'candidate:%s %d %s %d %s %d typ %s'; + + str += (o.raddr != null) ? ' raddr %s rport %d' : '%v%v'; + + // NB: candidate has three optional chunks, so %void middles one if it's missing + str += (o.tcptype != null) ? ' tcptype %s' : '%v'; + + if (o.generation != null) { + str += ' generation %d'; + } + + str += (o['network-id'] != null) ? ' network-id %d' : '%v'; + str += (o['network-cost'] != null) ? ' network-cost %d' : '%v'; + return str; + } + }, + { + // a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { + // a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: 'remote-candidates:%s' + }, + { + // a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: 'ice-options:%s' + }, + { + // a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: 'ssrcs', + reg: /^ssrc:(\d*) ([\w_-]*)(?::(.*))?/, + names: ['id', 'attribute', 'value'], + format: function (o) { + var str = 'ssrc:%d'; + if (o.attribute != null) { + str += ' %s'; + if (o.value != null) { + str += ':%s'; + } + } + return str; + } + }, + { + // a=ssrc-group:FEC 1 2 + // a=ssrc-group:FEC-FR 3004364195 1080772241 + push: 'ssrcGroups', + // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E + reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/, + names: ['semantics', 'ssrcs'], + format: 'ssrc-group:%s %s' + }, + { + // a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: 'msidSemantic', + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: 'msid-semantic: %s %s' // space after ':' is not accidental + }, + { + // a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: 'group:%s %s' + }, + { + // a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { + // a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { + // a=sctpmap:5000 webrtc-datachannel 1024 + name: 'sctpmap', + reg: /^sctpmap:([\w_/]*) (\S*)(?: (\S*))?/, + names: ['sctpmapNumber', 'app', 'maxMessageSize'], + format: function (o) { + return (o.maxMessageSize != null) + ? 'sctpmap:%s %s %s' + : 'sctpmap:%s %s'; + } + }, + { + // a=x-google-flag:conference + name: 'xGoogleFlag', + reg: /^x-google-flag:([^\s]*)/, + format: 'x-google-flag:%s' + }, + { + // a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0 + push: 'rids', + reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/, + names: ['id', 'direction', 'params'], + format: function (o) { + return (o.params) ? 'rid:%s %s %s' : 'rid:%s %s'; + } + }, + { + // a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250] + // a=imageattr:* send [x=800,y=640] recv * + // a=imageattr:100 recv [x=320,y=240] + push: 'imageattrs', + reg: new RegExp( + // a=imageattr:97 + '^imageattr:(\\d+|\\*)' + + // send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] + '[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' + + // recv [x=330,y=250] + '(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?' + ), + names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'], + format: function (o) { + return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : ''); + } + }, + { + // a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8 + // a=simulcast:recv 1;4,5 send 6;7 + name: 'simulcast', + reg: new RegExp( + // a=simulcast: + '^simulcast:' + + // send 1,2,3;~4,~5 + '(send|recv) ([a-zA-Z0-9\\-_~;,]+)' + + // space + recv 6;~7,~8 + '(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' + + // end + '$' + ), + names: ['dir1', 'list1', 'dir2', 'list2'], + format: function (o) { + return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : ''); + } + }, + { + // old simulcast draft 03 (implemented by Firefox) + // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03 + // a=simulcast: recv pt=97;98 send pt=97 + // a=simulcast: send rid=5;6;7 paused=6,7 + name: 'simulcast_03', + reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/, + names: ['value'], + format: 'simulcast: %s' + }, + { + // a=framerate:25 + // a=framerate:29.97 + name: 'framerate', + reg: /^framerate:(\d+(?:$|\.\d+))/, + format: 'framerate:%s' + }, + { + // RFC4570 + // a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5 + name: 'sourceFilter', + reg: /^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)/, + names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'], + format: 'source-filter: %s %s %s %s %s' + }, + { + // a=bundle-only + name: 'bundleOnly', + reg: /^(bundle-only)/ + }, + { + // a=label:1 + name: 'label', + reg: /^label:(.+)/, + format: 'label:%s' + }, + { + // RFC version 26 for SCTP over DTLS + // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-5 + name: 'sctpPort', + reg: /^sctp-port:(\d+)$/, + format: 'sctp-port:%s' + }, + { + // RFC version 26 for SCTP over DTLS + // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6 + name: 'maxMessageSize', + reg: /^max-message-size:(\d+)$/, + format: 'max-message-size:%s' + }, + { + // RFC7273 + // a=ts-refclk:ptp=IEEE1588-2008:39-A7-94-FF-FE-07-CB-D0:37 + push:'tsRefClocks', + reg: /^ts-refclk:([^\s=]*)(?:=(\S*))?/, + names: ['clksrc', 'clksrcExt'], + format: function (o) { + return 'ts-refclk:%s' + (o.clksrcExt != null ? '=%s' : ''); + } + }, + { + // RFC7273 + // a=mediaclk:direct=963214424 + name:'mediaClk', + reg: /^mediaclk:(?:id=(\S*))? *([^\s=]*)(?:=(\S*))?(?: *rate=(\d+)\/(\d+))?/, + names: ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'], + format: function (o) { + var str = 'mediaclk:'; + str += (o.id != null ? 'id=%s %s' : '%v%s'); + str += (o.mediaClockValue != null ? '=%s' : ''); + str += (o.rateNumerator != null ? ' rate=%s' : ''); + str += (o.rateDenominator != null ? '/%s' : ''); + return str; + } + }, + { + // a=keywds:keywords + name: 'keywords', + reg: /^keywds:(.+)$/, + format: 'keywds:%s' + }, + { + // a=content:main + name: 'content', + reg: /^content:(.+)/, + format: 'content:%s' + }, + // BFCP https://tools.ietf.org/html/rfc4583 + { + // a=floorctrl:c-s + name: 'bfcpFloorCtrl', + reg: /^floorctrl:(c-only|s-only|c-s)/, + format: 'floorctrl:%s' + }, + { + // a=confid:1 + name: 'bfcpConfId', + reg: /^confid:(\d+)/, + format: 'confid:%s' + }, + { + // a=userid:1 + name: 'bfcpUserId', + reg: /^userid:(\d+)/, + format: 'userid:%s' + }, + { + // a=floorid:1 + name: 'bfcpFloorId', + reg: /^floorid:(.+) (?:m-stream|mstrm):(.+)/, + names: ['id', 'mStream'], + format: 'floorid:%s mstrm:%s' + }, + { + // any a= that we don't understand is kept verbatim on media.invalid + push: 'invalid', + names: ['value'] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = '%s'; + } + }); +}); + +},{}],253:[function(require,module,exports){ +var parser = require('./parser'); +var writer = require('./writer'); + +exports.write = writer; +exports.parse = parser.parse; +exports.parseParams = parser.parseParams; +exports.parseFmtpConfig = parser.parseFmtpConfig; // Alias of parseParams(). +exports.parsePayloads = parser.parsePayloads; +exports.parseRemoteCandidates = parser.parseRemoteCandidates; +exports.parseImageAttributes = parser.parseImageAttributes; +exports.parseSimulcastStreamList = parser.parseSimulcastStreamList; + +},{"./parser":254,"./writer":255}],254:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var paramReducer = function (acc, expr) { + var s = expr.split(/=(.+)/, 2); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } else if (s.length === 1 && expr.length > 1) { + acc[s[0]] = undefined; + } + return acc; +}; + +exports.parseParams = function (str) { + return str.split(/;\s?/).reduce(paramReducer, {}); +}; + +// For backward compatibility - alias will be removed in 3.0.0 +exports.parseFmtpConfig = exports.parseParams; + +exports.parsePayloads = function (str) { + return str.toString().split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +exports.parseImageAttributes = function (str) { + return str.split(' ').map(function (item) { + return item.substring(1, item.length-1).split(',').reduce(paramReducer, {}); + }); +}; + +exports.parseSimulcastStreamList = function (str) { + return str.split(';').map(function (stream) { + return stream.split(',').map(function (format) { + var scid, paused = false; + + if (format[0] !== '~') { + scid = toIntIfInt(format); + } else { + scid = toIntIfInt(format.substring(1, format.length)); + paused = true; + } + + return { + scid: scid, + paused: paused + }; + }); + }); +}; + +},{"./grammar":252}],255:[function(require,module,exports){ +var grammar = require('./grammar'); + +// customized util.format - discards excess arguments and can void middle ones +var formatRegExp = /%[sdv%]/g; +var format = function (formatStr) { + var i = 1; + var args = arguments; + var len = args.length; + return formatStr.replace(formatRegExp, function (x) { + if (i >= len) { + return x; // missing argument + } + var arg = args[i]; + i += 1; + switch (x) { + case '%%': + return '%'; + case '%s': + return String(arg); + case '%d': + return Number(arg); + case '%v': + return ''; + } + }); + // NB: we discard excess arguments - they are typically undefined from makeLine +}; + +var makeLine = function (type, obj, location) { + var str = obj.format instanceof Function ? + (obj.format(obj.push ? location : location[obj.name])) : + obj.format; + + var args = [type + '=' + str]; + if (obj.names) { + for (var i = 0; i < obj.names.length; i += 1) { + var n = obj.names[i]; + if (obj.name) { + args.push(location[obj.name][n]); + } + else { // for mLine and push attributes + args.push(location[obj.names[i]]); + } + } + } + else { + args.push(location[obj.name]); + } + return format.apply(null, args); +}; + +// RFC specified order +// TODO: extend this with all the rest +var defaultOuterOrder = [ + 'v', 'o', 's', 'i', + 'u', 'e', 'p', 'c', + 'b', 't', 'r', 'z', 'a' +]; +var defaultInnerOrder = ['i', 'c', 'b', 'a']; + + +module.exports = function (session, opts) { + opts = opts || {}; + // ensure certain properties exist + if (session.version == null) { + session.version = 0; // 'v=0' must be there (only defined version atm) + } + if (session.name == null) { + session.name = ' '; // 's= ' must be there if no meaningful name set + } + session.media.forEach(function (mLine) { + if (mLine.payloads == null) { + mLine.payloads = ''; + } + }); + + var outerOrder = opts.outerOrder || defaultOuterOrder; + var innerOrder = opts.innerOrder || defaultInnerOrder; + var sdp = []; + + // loop through outerOrder for matching properties on session + outerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in session && session[obj.name] != null) { + sdp.push(makeLine(type, obj, session)); + } + else if (obj.push in session && session[obj.push] != null) { + session[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + + // then for each media line, follow the innerOrder + session.media.forEach(function (mLine) { + sdp.push(makeLine('m', grammar.m[0], mLine)); + + innerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in mLine && mLine[obj.name] != null) { + sdp.push(makeLine(type, obj, mLine)); + } + else if (obj.push in mLine && mLine[obj.push] != null) { + mLine[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + }); + + return sdp.join('\r\n') + '\r\n'; +}; + +},{"./grammar":252}],256:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer + +// prototype class for hash functions +function Hash (blockSize, finalSize) { + this._block = Buffer.alloc(blockSize) + this._finalSize = finalSize + this._blockSize = blockSize + this._len = 0 +} + +Hash.prototype.update = function (data, enc) { + if (typeof data === 'string') { + enc = enc || 'utf8' + data = Buffer.from(data, enc) + } + + var block = this._block + var blockSize = this._blockSize + var length = data.length + var accum = this._len + + for (var offset = 0; offset < length;) { + var assigned = accum % blockSize + var remainder = Math.min(length - offset, blockSize - assigned) + + for (var i = 0; i < remainder; i++) { + block[assigned + i] = data[offset + i] + } + + accum += remainder + offset += remainder + + if ((accum % blockSize) === 0) { + this._update(block) + } + } + + this._len += length + return this +} + +Hash.prototype.digest = function (enc) { + var rem = this._len % this._blockSize + + this._block[rem] = 0x80 + + // zero (rem + 1) trailing bits, where (rem + 1) is the smallest + // non-negative solution to the equation (length + 1 + (rem + 1)) === finalSize mod blockSize + this._block.fill(0, rem + 1) + + if (rem >= this._finalSize) { + this._update(this._block) + this._block.fill(0) + } + + var bits = this._len * 8 + + // uint32 + if (bits <= 0xffffffff) { + this._block.writeUInt32BE(bits, this._blockSize - 4) + + // uint64 + } else { + var lowBits = (bits & 0xffffffff) >>> 0 + var highBits = (bits - lowBits) / 0x100000000 + + this._block.writeUInt32BE(highBits, this._blockSize - 8) + this._block.writeUInt32BE(lowBits, this._blockSize - 4) + } + + this._update(this._block) + var hash = this._hash() + + return enc ? hash.toString(enc) : hash +} + +Hash.prototype._update = function () { + throw new Error('_update must be implemented by subclass') +} + +module.exports = Hash + +},{"safe-buffer":250}],257:[function(require,module,exports){ +var exports = module.exports = function SHA (algorithm) { + algorithm = algorithm.toLowerCase() + + var Algorithm = exports[algorithm] + if (!Algorithm) throw new Error(algorithm + ' is not supported (we accept pull requests)') + + return new Algorithm() +} + +exports.sha = require('./sha') +exports.sha1 = require('./sha1') +exports.sha224 = require('./sha224') +exports.sha256 = require('./sha256') +exports.sha384 = require('./sha384') +exports.sha512 = require('./sha512') + +},{"./sha":258,"./sha1":259,"./sha224":260,"./sha256":261,"./sha384":262,"./sha512":263}],258:[function(require,module,exports){ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-0, as defined + * in FIPS PUB 180-1 + * This source code is derived from sha1.js of the same repository. + * The difference between SHA-0 and SHA-1 is just a bitwise rotate left + * operation was added. + */ + +var inherits = require('inherits') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var K = [ + 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc | 0, 0xca62c1d6 | 0 +] + +var W = new Array(80) + +function Sha () { + this.init() + this._w = W + + Hash.call(this, 64, 56) +} + +inherits(Sha, Hash) + +Sha.prototype.init = function () { + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 + this._e = 0xc3d2e1f0 + + return this +} + +function rotl5 (num) { + return (num << 5) | (num >>> 27) +} + +function rotl30 (num) { + return (num << 30) | (num >>> 2) +} + +function ft (s, b, c, d) { + if (s === 0) return (b & c) | ((~b) & d) + if (s === 2) return (b & c) | (b & d) | (c & d) + return b ^ c ^ d +} + +Sha.prototype._update = function (M) { + var W = this._w + + var a = this._a | 0 + var b = this._b | 0 + var c = this._c | 0 + var d = this._d | 0 + var e = this._e | 0 + + for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4) + for (; i < 80; ++i) W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16] + + for (var j = 0; j < 80; ++j) { + var s = ~~(j / 20) + var t = (rotl5(a) + ft(s, b, c, d) + e + W[j] + K[s]) | 0 + + e = d + d = c + c = rotl30(b) + b = a + a = t + } + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 +} + +Sha.prototype._hash = function () { + var H = Buffer.allocUnsafe(20) + + H.writeInt32BE(this._a | 0, 0) + H.writeInt32BE(this._b | 0, 4) + H.writeInt32BE(this._c | 0, 8) + H.writeInt32BE(this._d | 0, 12) + H.writeInt32BE(this._e | 0, 16) + + return H +} + +module.exports = Sha + +},{"./hash":256,"inherits":146,"safe-buffer":250}],259:[function(require,module,exports){ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +var inherits = require('inherits') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var K = [ + 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc | 0, 0xca62c1d6 | 0 +] + +var W = new Array(80) + +function Sha1 () { + this.init() + this._w = W + + Hash.call(this, 64, 56) +} + +inherits(Sha1, Hash) + +Sha1.prototype.init = function () { + this._a = 0x67452301 + this._b = 0xefcdab89 + this._c = 0x98badcfe + this._d = 0x10325476 + this._e = 0xc3d2e1f0 + + return this +} + +function rotl1 (num) { + return (num << 1) | (num >>> 31) +} + +function rotl5 (num) { + return (num << 5) | (num >>> 27) +} + +function rotl30 (num) { + return (num << 30) | (num >>> 2) +} + +function ft (s, b, c, d) { + if (s === 0) return (b & c) | ((~b) & d) + if (s === 2) return (b & c) | (b & d) | (c & d) + return b ^ c ^ d +} + +Sha1.prototype._update = function (M) { + var W = this._w + + var a = this._a | 0 + var b = this._b | 0 + var c = this._c | 0 + var d = this._d | 0 + var e = this._e | 0 + + for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4) + for (; i < 80; ++i) W[i] = rotl1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]) + + for (var j = 0; j < 80; ++j) { + var s = ~~(j / 20) + var t = (rotl5(a) + ft(s, b, c, d) + e + W[j] + K[s]) | 0 + + e = d + d = c + c = rotl30(b) + b = a + a = t + } + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 +} + +Sha1.prototype._hash = function () { + var H = Buffer.allocUnsafe(20) + + H.writeInt32BE(this._a | 0, 0) + H.writeInt32BE(this._b | 0, 4) + H.writeInt32BE(this._c | 0, 8) + H.writeInt32BE(this._d | 0, 12) + H.writeInt32BE(this._e | 0, 16) + + return H +} + +module.exports = Sha1 + +},{"./hash":256,"inherits":146,"safe-buffer":250}],260:[function(require,module,exports){ +/** + * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined + * in FIPS 180-2 + * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * + */ + +var inherits = require('inherits') +var Sha256 = require('./sha256') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var W = new Array(64) + +function Sha224 () { + this.init() + + this._w = W // new Array(64) + + Hash.call(this, 64, 56) +} + +inherits(Sha224, Sha256) + +Sha224.prototype.init = function () { + this._a = 0xc1059ed8 + this._b = 0x367cd507 + this._c = 0x3070dd17 + this._d = 0xf70e5939 + this._e = 0xffc00b31 + this._f = 0x68581511 + this._g = 0x64f98fa7 + this._h = 0xbefa4fa4 + + return this +} + +Sha224.prototype._hash = function () { + var H = Buffer.allocUnsafe(28) + + H.writeInt32BE(this._a, 0) + H.writeInt32BE(this._b, 4) + H.writeInt32BE(this._c, 8) + H.writeInt32BE(this._d, 12) + H.writeInt32BE(this._e, 16) + H.writeInt32BE(this._f, 20) + H.writeInt32BE(this._g, 24) + + return H +} + +module.exports = Sha224 + +},{"./hash":256,"./sha256":261,"inherits":146,"safe-buffer":250}],261:[function(require,module,exports){ +/** + * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined + * in FIPS 180-2 + * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * + */ + +var inherits = require('inherits') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var K = [ + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 +] + +var W = new Array(64) + +function Sha256 () { + this.init() + + this._w = W // new Array(64) + + Hash.call(this, 64, 56) +} + +inherits(Sha256, Hash) + +Sha256.prototype.init = function () { + this._a = 0x6a09e667 + this._b = 0xbb67ae85 + this._c = 0x3c6ef372 + this._d = 0xa54ff53a + this._e = 0x510e527f + this._f = 0x9b05688c + this._g = 0x1f83d9ab + this._h = 0x5be0cd19 + + return this +} + +function ch (x, y, z) { + return z ^ (x & (y ^ z)) +} + +function maj (x, y, z) { + return (x & y) | (z & (x | y)) +} + +function sigma0 (x) { + return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10) +} + +function sigma1 (x) { + return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7) +} + +function gamma0 (x) { + return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ (x >>> 3) +} + +function gamma1 (x) { + return (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13) ^ (x >>> 10) +} + +Sha256.prototype._update = function (M) { + var W = this._w + + var a = this._a | 0 + var b = this._b | 0 + var c = this._c | 0 + var d = this._d | 0 + var e = this._e | 0 + var f = this._f | 0 + var g = this._g | 0 + var h = this._h | 0 + + for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4) + for (; i < 64; ++i) W[i] = (gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]) | 0 + + for (var j = 0; j < 64; ++j) { + var T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W[j]) | 0 + var T2 = (sigma0(a) + maj(a, b, c)) | 0 + + h = g + g = f + f = e + e = (d + T1) | 0 + d = c + c = b + b = a + a = (T1 + T2) | 0 + } + + this._a = (a + this._a) | 0 + this._b = (b + this._b) | 0 + this._c = (c + this._c) | 0 + this._d = (d + this._d) | 0 + this._e = (e + this._e) | 0 + this._f = (f + this._f) | 0 + this._g = (g + this._g) | 0 + this._h = (h + this._h) | 0 +} + +Sha256.prototype._hash = function () { + var H = Buffer.allocUnsafe(32) + + H.writeInt32BE(this._a, 0) + H.writeInt32BE(this._b, 4) + H.writeInt32BE(this._c, 8) + H.writeInt32BE(this._d, 12) + H.writeInt32BE(this._e, 16) + H.writeInt32BE(this._f, 20) + H.writeInt32BE(this._g, 24) + H.writeInt32BE(this._h, 28) + + return H +} + +module.exports = Sha256 + +},{"./hash":256,"inherits":146,"safe-buffer":250}],262:[function(require,module,exports){ +var inherits = require('inherits') +var SHA512 = require('./sha512') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var W = new Array(160) + +function Sha384 () { + this.init() + this._w = W + + Hash.call(this, 128, 112) +} + +inherits(Sha384, SHA512) + +Sha384.prototype.init = function () { + this._ah = 0xcbbb9d5d + this._bh = 0x629a292a + this._ch = 0x9159015a + this._dh = 0x152fecd8 + this._eh = 0x67332667 + this._fh = 0x8eb44a87 + this._gh = 0xdb0c2e0d + this._hh = 0x47b5481d + + this._al = 0xc1059ed8 + this._bl = 0x367cd507 + this._cl = 0x3070dd17 + this._dl = 0xf70e5939 + this._el = 0xffc00b31 + this._fl = 0x68581511 + this._gl = 0x64f98fa7 + this._hl = 0xbefa4fa4 + + return this +} + +Sha384.prototype._hash = function () { + var H = Buffer.allocUnsafe(48) + + function writeInt64BE (h, l, offset) { + H.writeInt32BE(h, offset) + H.writeInt32BE(l, offset + 4) + } + + writeInt64BE(this._ah, this._al, 0) + writeInt64BE(this._bh, this._bl, 8) + writeInt64BE(this._ch, this._cl, 16) + writeInt64BE(this._dh, this._dl, 24) + writeInt64BE(this._eh, this._el, 32) + writeInt64BE(this._fh, this._fl, 40) + + return H +} + +module.exports = Sha384 + +},{"./hash":256,"./sha512":263,"inherits":146,"safe-buffer":250}],263:[function(require,module,exports){ +var inherits = require('inherits') +var Hash = require('./hash') +var Buffer = require('safe-buffer').Buffer + +var K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, + 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, + 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, + 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, + 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, + 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, + 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, + 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, + 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, + 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, + 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, + 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, + 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, + 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +] + +var W = new Array(160) + +function Sha512 () { + this.init() + this._w = W + + Hash.call(this, 128, 112) +} + +inherits(Sha512, Hash) + +Sha512.prototype.init = function () { + this._ah = 0x6a09e667 + this._bh = 0xbb67ae85 + this._ch = 0x3c6ef372 + this._dh = 0xa54ff53a + this._eh = 0x510e527f + this._fh = 0x9b05688c + this._gh = 0x1f83d9ab + this._hh = 0x5be0cd19 + + this._al = 0xf3bcc908 + this._bl = 0x84caa73b + this._cl = 0xfe94f82b + this._dl = 0x5f1d36f1 + this._el = 0xade682d1 + this._fl = 0x2b3e6c1f + this._gl = 0xfb41bd6b + this._hl = 0x137e2179 + + return this +} + +function Ch (x, y, z) { + return z ^ (x & (y ^ z)) +} + +function maj (x, y, z) { + return (x & y) | (z & (x | y)) +} + +function sigma0 (x, xl) { + return (x >>> 28 | xl << 4) ^ (xl >>> 2 | x << 30) ^ (xl >>> 7 | x << 25) +} + +function sigma1 (x, xl) { + return (x >>> 14 | xl << 18) ^ (x >>> 18 | xl << 14) ^ (xl >>> 9 | x << 23) +} + +function Gamma0 (x, xl) { + return (x >>> 1 | xl << 31) ^ (x >>> 8 | xl << 24) ^ (x >>> 7) +} + +function Gamma0l (x, xl) { + return (x >>> 1 | xl << 31) ^ (x >>> 8 | xl << 24) ^ (x >>> 7 | xl << 25) +} + +function Gamma1 (x, xl) { + return (x >>> 19 | xl << 13) ^ (xl >>> 29 | x << 3) ^ (x >>> 6) +} + +function Gamma1l (x, xl) { + return (x >>> 19 | xl << 13) ^ (xl >>> 29 | x << 3) ^ (x >>> 6 | xl << 26) +} + +function getCarry (a, b) { + return (a >>> 0) < (b >>> 0) ? 1 : 0 +} + +Sha512.prototype._update = function (M) { + var W = this._w + + var ah = this._ah | 0 + var bh = this._bh | 0 + var ch = this._ch | 0 + var dh = this._dh | 0 + var eh = this._eh | 0 + var fh = this._fh | 0 + var gh = this._gh | 0 + var hh = this._hh | 0 + + var al = this._al | 0 + var bl = this._bl | 0 + var cl = this._cl | 0 + var dl = this._dl | 0 + var el = this._el | 0 + var fl = this._fl | 0 + var gl = this._gl | 0 + var hl = this._hl | 0 + + for (var i = 0; i < 32; i += 2) { + W[i] = M.readInt32BE(i * 4) + W[i + 1] = M.readInt32BE(i * 4 + 4) + } + for (; i < 160; i += 2) { + var xh = W[i - 15 * 2] + var xl = W[i - 15 * 2 + 1] + var gamma0 = Gamma0(xh, xl) + var gamma0l = Gamma0l(xl, xh) + + xh = W[i - 2 * 2] + xl = W[i - 2 * 2 + 1] + var gamma1 = Gamma1(xh, xl) + var gamma1l = Gamma1l(xl, xh) + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7h = W[i - 7 * 2] + var Wi7l = W[i - 7 * 2 + 1] + + var Wi16h = W[i - 16 * 2] + var Wi16l = W[i - 16 * 2 + 1] + + var Wil = (gamma0l + Wi7l) | 0 + var Wih = (gamma0 + Wi7h + getCarry(Wil, gamma0l)) | 0 + Wil = (Wil + gamma1l) | 0 + Wih = (Wih + gamma1 + getCarry(Wil, gamma1l)) | 0 + Wil = (Wil + Wi16l) | 0 + Wih = (Wih + Wi16h + getCarry(Wil, Wi16l)) | 0 + + W[i] = Wih + W[i + 1] = Wil + } + + for (var j = 0; j < 160; j += 2) { + Wih = W[j] + Wil = W[j + 1] + + var majh = maj(ah, bh, ch) + var majl = maj(al, bl, cl) + + var sigma0h = sigma0(ah, al) + var sigma0l = sigma0(al, ah) + var sigma1h = sigma1(eh, el) + var sigma1l = sigma1(el, eh) + + // t1 = h + sigma1 + ch + K[j] + W[j] + var Kih = K[j] + var Kil = K[j + 1] + + var chh = Ch(eh, fh, gh) + var chl = Ch(el, fl, gl) + + var t1l = (hl + sigma1l) | 0 + var t1h = (hh + sigma1h + getCarry(t1l, hl)) | 0 + t1l = (t1l + chl) | 0 + t1h = (t1h + chh + getCarry(t1l, chl)) | 0 + t1l = (t1l + Kil) | 0 + t1h = (t1h + Kih + getCarry(t1l, Kil)) | 0 + t1l = (t1l + Wil) | 0 + t1h = (t1h + Wih + getCarry(t1l, Wil)) | 0 + + // t2 = sigma0 + maj + var t2l = (sigma0l + majl) | 0 + var t2h = (sigma0h + majh + getCarry(t2l, sigma0l)) | 0 + + hh = gh + hl = gl + gh = fh + gl = fl + fh = eh + fl = el + el = (dl + t1l) | 0 + eh = (dh + t1h + getCarry(el, dl)) | 0 + dh = ch + dl = cl + ch = bh + cl = bl + bh = ah + bl = al + al = (t1l + t2l) | 0 + ah = (t1h + t2h + getCarry(al, t1l)) | 0 + } + + this._al = (this._al + al) | 0 + this._bl = (this._bl + bl) | 0 + this._cl = (this._cl + cl) | 0 + this._dl = (this._dl + dl) | 0 + this._el = (this._el + el) | 0 + this._fl = (this._fl + fl) | 0 + this._gl = (this._gl + gl) | 0 + this._hl = (this._hl + hl) | 0 + + this._ah = (this._ah + ah + getCarry(this._al, al)) | 0 + this._bh = (this._bh + bh + getCarry(this._bl, bl)) | 0 + this._ch = (this._ch + ch + getCarry(this._cl, cl)) | 0 + this._dh = (this._dh + dh + getCarry(this._dl, dl)) | 0 + this._eh = (this._eh + eh + getCarry(this._el, el)) | 0 + this._fh = (this._fh + fh + getCarry(this._fl, fl)) | 0 + this._gh = (this._gh + gh + getCarry(this._gl, gl)) | 0 + this._hh = (this._hh + hh + getCarry(this._hl, hl)) | 0 +} + +Sha512.prototype._hash = function () { + var H = Buffer.allocUnsafe(64) + + function writeInt64BE (h, l, offset) { + H.writeInt32BE(h, offset) + H.writeInt32BE(l, offset + 4) + } + + writeInt64BE(this._ah, this._al, 0) + writeInt64BE(this._bh, this._bl, 8) + writeInt64BE(this._ch, this._cl, 16) + writeInt64BE(this._dh, this._dl, 24) + writeInt64BE(this._eh, this._el, 32) + writeInt64BE(this._fh, this._fl, 40) + writeInt64BE(this._gh, this._gl, 48) + writeInt64BE(this._hh, this._hl, 56) + + return H +} + +module.exports = Sha512 + +},{"./hash":256,"inherits":146,"safe-buffer":250}],264:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/lib/_stream_readable.js'); +Stream.Writable = require('readable-stream/lib/_stream_writable.js'); +Stream.Duplex = require('readable-stream/lib/_stream_duplex.js'); +Stream.Transform = require('readable-stream/lib/_stream_transform.js'); +Stream.PassThrough = require('readable-stream/lib/_stream_passthrough.js'); +Stream.finished = require('readable-stream/lib/internal/streams/end-of-stream.js') +Stream.pipeline = require('readable-stream/lib/internal/streams/pipeline.js') + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":105,"inherits":146,"readable-stream/lib/_stream_duplex.js":266,"readable-stream/lib/_stream_passthrough.js":267,"readable-stream/lib/_stream_readable.js":268,"readable-stream/lib/_stream_transform.js":269,"readable-stream/lib/_stream_writable.js":270,"readable-stream/lib/internal/streams/end-of-stream.js":274,"readable-stream/lib/internal/streams/pipeline.js":276}],265:[function(require,module,exports){ +arguments[4][50][0].apply(exports,arguments) +},{"dup":50}],266:[function(require,module,exports){ +(function (process){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. +'use strict'; +/**/ + +var objectKeys = Object.keys || function (obj) { + var keys = []; + + for (var key in obj) { + keys.push(key); + } + + return keys; +}; +/**/ + + +module.exports = Duplex; + +var Readable = require('./_stream_readable'); + +var Writable = require('./_stream_writable'); + +require('inherits')(Duplex, Readable); + +{ + // Allow the keys array to be GC'ed. + var keys = objectKeys(Writable.prototype); + + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + Readable.call(this, options); + Writable.call(this, options); + this.allowHalfOpen = true; + + if (options) { + if (options.readable === false) this.readable = false; + if (options.writable === false) this.writable = false; + + if (options.allowHalfOpen === false) { + this.allowHalfOpen = false; + this.once('end', onend); + } + } +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); +Object.defineProperty(Duplex.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); +Object.defineProperty(Duplex.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); // the no-half-open enforcer + +function onend() { + // If the writable side ended, then we're ok. + if (this._writableState.ended) return; // no more data can be written. + // But allow more writes to happen in this tick. + + process.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); +}).call(this)}).call(this,require('_process')) + +},{"./_stream_readable":268,"./_stream_writable":270,"_process":237,"inherits":146}],267:[function(require,module,exports){ +arguments[4][52][0].apply(exports,arguments) +},{"./_stream_transform":269,"dup":52,"inherits":146}],268:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +'use strict'; + +module.exports = Readable; +/**/ + +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; +/**/ + +var EE = require('events').EventEmitter; + +var EElistenerCount = function EElistenerCount(emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ + + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ + + +var debugUtil = require('util'); + +var debug; + +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function debug() {}; +} +/**/ + + +var BufferList = require('./internal/streams/buffer_list'); + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_STREAM_PUSH_AFTER_EOF = _require$codes.ERR_STREAM_PUSH_AFTER_EOF, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_UNSHIFT_AFTER_END_EVENT = _require$codes.ERR_STREAM_UNSHIFT_AFTER_END_EVENT; // Lazy loaded to improve the startup performance. + + +var StringDecoder; +var createReadableStreamAsyncIterator; +var from; + +require('inherits')(Readable, Stream); + +var errorOrDestroy = destroyImpl.errorOrDestroy; +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (Array.isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + + this.highWaterMark = getHighWaterMark(this, options, 'readableHighWaterMark', isDuplex); // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + + this.sync = true; // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + this.paused = true; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'end' (and potentially 'finish') + + this.autoDestroy = !!options.autoDestroy; // has it been destroyed + + this.destroyed = false; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // the number of writers that are awaiting a drain event in .pipe()s + + this.awaitDrain = 0; // if true, a maybeReadMore has been scheduled + + this.readingMore = false; + this.decoder = null; + this.encoding = null; + + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + if (!(this instanceof Readable)) return new Readable(options); // Checking for a Stream.Duplex instance is faster here instead of inside + // the ReadableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + this._readableState = new ReadableState(options, this, isDuplex); // legacy + + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._readableState === undefined) { + return false; + } + + return this._readableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._readableState.destroyed = value; + } +}); +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; + +Readable.prototype._destroy = function (err, cb) { + cb(err); +}; // Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. + + +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; // Unshift should *always* be something directly out of read() + + +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + debug('readableAddChunk', chunk); + var state = stream._readableState; + + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + + if (er) { + errorOrDestroy(stream, er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) errorOrDestroy(stream, new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());else addChunk(stream, state, chunk, true); + } else if (state.ended) { + errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF()); + } else if (state.destroyed) { + return false; + } else { + state.reading = false; + + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + maybeReadMore(stream, state); + } + } // We can push more data if we are below the highWaterMark. + // Also, if we have no data yet, we can stand some more bytes. + // This is to work around cases where hwm=0, such as the repl. + + + return !state.ended && (state.length < state.highWaterMark || state.length === 0); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + state.awaitDrain = 0; + stream.emit('data', chunk); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + if (state.needReadable) emitReadable(stream); + } + + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer', 'Uint8Array'], chunk); + } + + return er; +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; // backwards compatibility. + + +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + var decoder = new StringDecoder(enc); + this._readableState.decoder = decoder; // If setEncoding(null), decoder.encoding equals utf8 + + this._readableState.encoding = this._readableState.decoder.encoding; // Iterate over current buffer to convert already stored Buffers: + + var p = this._readableState.buffer.head; + var content = ''; + + while (p !== null) { + content += decoder.write(p.data); + p = p.next; + } + + this._readableState.buffer.clear(); + + if (content !== '') this._readableState.buffer.push(content); + this._readableState.length = content.length; + return this; +}; // Don't raise the hwm > 1GB + + +var MAX_HWM = 0x40000000; + +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + // TODO(ronag): Throw ERR_VALUE_OUT_OF_RANGE. + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + + return n; +} // This function is designed to be inlinable, so please take care when making +// changes to the function body. + + +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } // If we're asking for more than the current hwm, then raise the hwm. + + + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; // Don't have enough + + if (!state.ended) { + state.needReadable = true; + return 0; + } + + return state.length; +} // you can override either this method, or the async _read(n) below. + + +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + if (n !== 0) state.emittedReadable = false; // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + + if (n === 0 && state.needReadable && ((state.highWaterMark !== 0 ? state.length >= state.highWaterMark : state.length > 0) || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); // if we've ended, and we're now clear, then finish it up. + + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + // if we need a readable event, then we need to do some reading. + + + var doRead = state.needReadable; + debug('need readable', doRead); // if we currently have less than the highWaterMark, then also read some + + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + + + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; // if the length is currently zero, then we *need* a readable event. + + if (state.length === 0) state.needReadable = true; // call internal read method + + this._read(state.highWaterMark); + + state.sync = false; // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = state.length <= state.highWaterMark; + n = 0; + } else { + state.length -= n; + state.awaitDrain = 0; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; // If we tried to read() past the EOF, then emit end on the next tick. + + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + return ret; +}; + +function onEofChunk(stream, state) { + debug('onEofChunk'); + if (state.ended) return; + + if (state.decoder) { + var chunk = state.decoder.end(); + + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + + state.ended = true; + + if (state.sync) { + // if we are sync, wait until next tick to emit the data. + // Otherwise we risk emitting data in the flow() + // the readable code triggers during a read() call + emitReadable(stream); + } else { + // emit 'readable' now to make sure it gets picked up. + state.needReadable = false; + + if (!state.emittedReadable) { + state.emittedReadable = true; + emitReadable_(stream); + } + } +} // Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. + + +function emitReadable(stream) { + var state = stream._readableState; + debug('emitReadable', state.needReadable, state.emittedReadable); + state.needReadable = false; + + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + process.nextTick(emitReadable_, stream); + } +} + +function emitReadable_(stream) { + var state = stream._readableState; + debug('emitReadable_', state.destroyed, state.length, state.ended); + + if (!state.destroyed && (state.length || state.ended)) { + stream.emit('readable'); + state.emittedReadable = false; + } // The stream needs another readable event if + // 1. It is not flowing, as the flow mechanism will take + // care of it. + // 2. It is not ended. + // 3. It is below the highWaterMark, so we can schedule + // another readable later. + + + state.needReadable = !state.flowing && !state.ended && state.length <= state.highWaterMark; + flow(stream); +} // at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. + + +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + process.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + // Attempt to read more data if we should. + // + // The conditions for reading more data are (one of): + // - Not enough data buffered (state.length < state.highWaterMark). The loop + // is responsible for filling the buffer with enough data if such data + // is available. If highWaterMark is 0 and we are not in the flowing mode + // we should _not_ attempt to buffer any extra data. We'll get more data + // when the stream consumer calls read() instead. + // - No data in the buffer, and the stream is in flowing mode. In this mode + // the loop below is responsible for ensuring read() is called. Failing to + // call read here would abort the flow and there's no other mechanism for + // continuing the flow if the stream consumer has just subscribed to the + // 'data' event. + // + // In addition to the above conditions to keep reading data, the following + // conditions prevent the data from being read: + // - The stream has ended (state.ended). + // - There is already a pending 'read' operation (state.reading). This is a + // case where the the stream has called the implementation defined _read() + // method, but they are processing the call asynchronously and have _not_ + // called push() with new data. In this case we skip performing more + // read()s. The execution ends in this method again after the _read() ends + // up calling push() with more data. + while (!state.reading && !state.ended && (state.length < state.highWaterMark || state.flowing && state.length === 0)) { + var len = state.length; + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) // didn't get any data, stop spinning. + break; + } + + state.readingMore = false; +} // abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. + + +Readable.prototype._read = function (n) { + errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED('_read()')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + + case 1: + state.pipes = [state.pipes, dest]; + break; + + default: + state.pipes.push(dest); + break; + } + + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) process.nextTick(endFn);else src.once('end', endFn); + dest.on('unpipe', onunpipe); + + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + + + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + var cleanedUp = false; + + function cleanup() { + debug('cleanup'); // cleanup event handlers once the pipe is broken + + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + cleanedUp = true; // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + src.on('data', ondata); + + function ondata(chunk) { + debug('ondata'); + var ret = dest.write(chunk); + debug('dest.write', ret); + + if (ret === false) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', state.awaitDrain); + state.awaitDrain++; + } + + src.pause(); + } + } // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + + + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) errorOrDestroy(dest, er); + } // Make sure our error handler is attached before userland ones. + + + prependListener(dest, 'error', onerror); // Both close and finish should trigger unpipe, but only once. + + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + + dest.once('close', onclose); + + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } // tell the dest that it's being piped to + + + dest.emit('pipe', src); // start the flow if it hasn't been started already. + + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function pipeOnDrainFunctionResult() { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { + hasUnpiped: false + }; // if we're not piping anywhere, then do nothing. + + if (state.pipesCount === 0) return this; // just one destination. most common case. + + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + if (!dest) dest = state.pipes; // got a match. + + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } // slow case. multiple pipe destinations. + + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, { + hasUnpiped: false + }); + } + + return this; + } // try to find the right one. + + + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + dest.emit('unpipe', this, unpipeInfo); + return this; +}; // set up data events if they are asked for +// Ensure readable listeners eventually get something + + +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + var state = this._readableState; + + if (ev === 'data') { + // update readableListening so that resume() may be a no-op + // a few lines down. This is needed to support once('readable'). + state.readableListening = this.listenerCount('readable') > 0; // Try start flowing on next tick if stream isn't explicitly paused + + if (state.flowing !== false) this.resume(); + } else if (ev === 'readable') { + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.flowing = false; + state.emittedReadable = false; + debug('on readable', state.length, state.reading); + + if (state.length) { + emitReadable(this); + } else if (!state.reading) { + process.nextTick(nReadingNextTick, this); + } + } + } + + return res; +}; + +Readable.prototype.addListener = Readable.prototype.on; + +Readable.prototype.removeListener = function (ev, fn) { + var res = Stream.prototype.removeListener.call(this, ev, fn); + + if (ev === 'readable') { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +Readable.prototype.removeAllListeners = function (ev) { + var res = Stream.prototype.removeAllListeners.apply(this, arguments); + + if (ev === 'readable' || ev === undefined) { + // We need to check if there is someone still listening to + // readable and reset the state. However this needs to happen + // after readable has been emitted but before I/O (nextTick) to + // support once('readable', fn) cycles. This means that calling + // resume within the same tick will have no + // effect. + process.nextTick(updateReadableListening, this); + } + + return res; +}; + +function updateReadableListening(self) { + var state = self._readableState; + state.readableListening = self.listenerCount('readable') > 0; + + if (state.resumeScheduled && !state.paused) { + // flowing needs to be set to true now, otherwise + // the upcoming resume will not flow. + state.flowing = true; // crude way to check if we should resume + } else if (self.listenerCount('data') > 0) { + self.resume(); + } +} + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} // pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. + + +Readable.prototype.resume = function () { + var state = this._readableState; + + if (!state.flowing) { + debug('resume'); // we flow only if there is no one listening + // for readable, but we still have to call + // resume() + + state.flowing = !state.readableListening; + resume(this, state); + } + + state.paused = false; + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + process.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + debug('resume', state.reading); + + if (!state.reading) { + stream.read(0); + } + + state.resumeScheduled = false; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + + if (this._readableState.flowing !== false) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + + this._readableState.paused = true; + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + + while (state.flowing && stream.read() !== null) { + ; + } +} // wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. + + +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + stream.on('end', function () { + debug('wrapped end'); + + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); // don't skip over falsy values in objectMode + + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + + if (!ret) { + paused = true; + stream.pause(); + } + }); // proxy all the other methods. + // important when wrapping filters and duplexes. + + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function methodWrap(method) { + return function methodWrapReturnFunction() { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } // proxy certain important events. + + + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } // when we try to consume some more bytes, simply unpause the + // underlying stream. + + + this._read = function (n) { + debug('wrapped _read', n); + + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +if (typeof Symbol === 'function') { + Readable.prototype[Symbol.asyncIterator] = function () { + if (createReadableStreamAsyncIterator === undefined) { + createReadableStreamAsyncIterator = require('./internal/streams/async_iterator'); + } + + return createReadableStreamAsyncIterator(this); + }; +} + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.highWaterMark; + } +}); +Object.defineProperty(Readable.prototype, 'readableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState && this._readableState.buffer; + } +}); +Object.defineProperty(Readable.prototype, 'readableFlowing', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.flowing; + }, + set: function set(state) { + if (this._readableState) { + this._readableState.flowing = state; + } + } +}); // exposed for testing purposes only. + +Readable._fromList = fromList; +Object.defineProperty(Readable.prototype, 'readableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._readableState.length; + } +}); // Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. + +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.first();else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = state.buffer.consume(n, state.decoder); + } + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + debug('endReadable', state.endEmitted); + + if (!state.endEmitted) { + state.ended = true; + process.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + debug('endReadableNT', state.endEmitted, state.length); // Check that we didn't get one last unshift. + + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the writable side is ready for autoDestroy as well + var wState = stream._writableState; + + if (!wState || wState.autoDestroy && wState.finished) { + stream.destroy(); + } + } + } +} + +if (typeof Symbol === 'function') { + Readable.from = function (iterable, opts) { + if (from === undefined) { + from = require('./internal/streams/from'); + } + + return from(Readable, iterable, opts); + }; +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + + return -1; +} +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":265,"./_stream_duplex":266,"./internal/streams/async_iterator":271,"./internal/streams/buffer_list":272,"./internal/streams/destroy":273,"./internal/streams/from":275,"./internal/streams/state":277,"./internal/streams/stream":278,"_process":237,"buffer":68,"events":105,"inherits":146,"string_decoder/":279,"util":20}],269:[function(require,module,exports){ +arguments[4][54][0].apply(exports,arguments) +},{"../errors":265,"./_stream_duplex":266,"dup":54,"inherits":146}],270:[function(require,module,exports){ +(function (process,global){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. +'use strict'; + +module.exports = Writable; +/* */ + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} // It seems a linked list but it is not +// there will be only 2 of these for each stream + + +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ + + +var Duplex; +/**/ + +Writable.WritableState = WritableState; +/**/ + +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ + +var Stream = require('./internal/streams/stream'); +/**/ + + +var Buffer = require('buffer').Buffer; + +var OurUint8Array = global.Uint8Array || function () {}; + +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} + +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +var destroyImpl = require('./internal/streams/destroy'); + +var _require = require('./internal/streams/state'), + getHighWaterMark = _require.getHighWaterMark; + +var _require$codes = require('../errors').codes, + ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, + ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED, + ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK, + ERR_STREAM_CANNOT_PIPE = _require$codes.ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES = _require$codes.ERR_STREAM_NULL_VALUES, + ERR_STREAM_WRITE_AFTER_END = _require$codes.ERR_STREAM_WRITE_AFTER_END, + ERR_UNKNOWN_ENCODING = _require$codes.ERR_UNKNOWN_ENCODING; + +var errorOrDestroy = destroyImpl.errorOrDestroy; + +require('inherits')(Writable, Stream); + +function nop() {} + +function WritableState(options, stream, isDuplex) { + Duplex = Duplex || require('./_stream_duplex'); + options = options || {}; // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream, + // e.g. options.readableObjectMode vs. options.writableObjectMode, etc. + + if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex; // object stream flag to indicate whether or not this stream + // contains buffers or objects. + + this.objectMode = !!options.objectMode; + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + + this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex); // if _final has been called + + this.finalCalled = false; // drain event flag. + + this.needDrain = false; // at the start of calling end() + + this.ending = false; // when end() has been called, and returned + + this.ended = false; // when 'finish' is emitted + + this.finished = false; // has it been destroyed + + this.destroyed = false; // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + + this.defaultEncoding = options.defaultEncoding || 'utf8'; // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + + this.length = 0; // a flag to see when we're in the middle of a write. + + this.writing = false; // when true all writes will be buffered until .uncork() call + + this.corked = 0; // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + + this.sync = true; // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + + this.bufferProcessing = false; // the callback that's passed to _write(chunk,cb) + + this.onwrite = function (er) { + onwrite(stream, er); + }; // the callback that the user supplies to write(chunk,encoding,cb) + + + this.writecb = null; // the amount that is being written when _write is called. + + this.writelen = 0; + this.bufferedRequest = null; + this.lastBufferedRequest = null; // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + + this.pendingcb = 0; // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + + this.prefinished = false; // True if the error was already emitted and should not be thrown again + + this.errorEmitted = false; // Should close be emitted on destroy. Defaults to true. + + this.emitClose = options.emitClose !== false; // Should .destroy() be called after 'finish' (and potentially 'end') + + this.autoDestroy = !!options.autoDestroy; // count buffered requests + + this.bufferedRequestCount = 0; // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + + while (current) { + out.push(current); + current = current.next; + } + + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function writableStateBufferGetter() { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); // Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. + + +var realHasInstance; + +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function value(object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function realHasInstance(object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + // Checking for a Stream.Duplex instance is faster here instead of inside + // the WritableState constructor, at least with V8 6.5 + + var isDuplex = this instanceof Duplex; + if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options); + this._writableState = new WritableState(options, this, isDuplex); // legacy. + + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + if (typeof options.writev === 'function') this._writev = options.writev; + if (typeof options.destroy === 'function') this._destroy = options.destroy; + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} // Otherwise people can pipe Writable streams, which is just wrong. + + +Writable.prototype.pipe = function () { + errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE()); +}; + +function writeAfterEnd(stream, cb) { + var er = new ERR_STREAM_WRITE_AFTER_END(); // TODO: defer error events consistently everywhere, not just the cb + + errorOrDestroy(stream, er); + process.nextTick(cb, er); +} // Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. + + +function validChunk(stream, state, chunk, cb) { + var er; + + if (chunk === null) { + er = new ERR_STREAM_NULL_VALUES(); + } else if (typeof chunk !== 'string' && !state.objectMode) { + er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk); + } + + if (er) { + errorOrDestroy(stream, er); + process.nextTick(cb, er); + return false; + } + + return true; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + if (typeof cb !== 'function') cb = nop; + if (state.ending) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + return ret; +}; + +Writable.prototype.cork = function () { + this._writableState.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new ERR_UNKNOWN_ENCODING(encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableBuffer', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState && this._writableState.getBuffer(); + } +}); + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.highWaterMark; + } +}); // if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. + +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + + var len = state.objectMode ? 1 : chunk.length; + state.length += len; + var ret = state.length < state.highWaterMark; // we must ensure that previous needDrain will not be reset to false. + + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (state.destroyed) state.onwrite(new ERR_STREAM_DESTROYED('write'));else if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + process.nextTick(cb, er); // this can emit finish, and it will always happen + // after error + + process.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + errorOrDestroy(stream, er); // this can emit finish, but finish must + // always follow error + + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + if (typeof cb !== 'function') throw new ERR_MULTIPLE_CALLBACK(); + onwriteStateUpdate(state); + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state) || stream.destroyed; + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + process.nextTick(afterWrite, stream, state, finished, cb); + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} // Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. + + +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} // if there's something in the buffer waiting, then process it + + +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + var count = 0; + var allBuffers = true; + + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + + buffer.allBuffers = allBuffers; + doWrite(stream, state, true, state.length, buffer, '', holder.finish); // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + + state.pendingcb++; + state.lastBufferedRequest = null; + + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new ERR_METHOD_NOT_IMPLEMENTED('_write()')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); // .end() fully uncorks + + if (state.corked) { + state.corked = 1; + this.uncork(); + } // ignore unnecessary end() calls. + + + if (!state.ending) endWritable(this, state, cb); + return this; +}; + +Object.defineProperty(Writable.prototype, 'writableLength', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + return this._writableState.length; + } +}); + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} + +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + + if (err) { + errorOrDestroy(stream, err); + } + + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} + +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function' && !state.destroyed) { + state.pendingcb++; + state.finalCalled = true; + process.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + + if (need) { + prefinish(stream, state); + + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + + if (state.autoDestroy) { + // In case of duplex streams we need a way to detect + // if the readable side is ready for autoDestroy as well + var rState = stream._readableState; + + if (!rState || rState.autoDestroy && rState.endEmitted) { + stream.destroy(); + } + } + } + } + + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + + if (cb) { + if (state.finished) process.nextTick(cb);else stream.once('finish', cb); + } + + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } // reuse the free corkReq. + + + state.corkedRequestsFree.next = corkReq; +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function get() { + if (this._writableState === undefined) { + return false; + } + + return this._writableState.destroyed; + }, + set: function set(value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } // backward compatibility, the user is explicitly + // managing destroyed + + + this._writableState.destroyed = value; + } +}); +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; + +Writable.prototype._destroy = function (err, cb) { + cb(err); +}; +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../errors":265,"./_stream_duplex":266,"./internal/streams/destroy":273,"./internal/streams/state":277,"./internal/streams/stream":278,"_process":237,"buffer":68,"inherits":146,"util-deprecate":283}],271:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; + +var _Object$setPrototypeO; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var finished = require('./end-of-stream'); + +var kLastResolve = Symbol('lastResolve'); +var kLastReject = Symbol('lastReject'); +var kError = Symbol('error'); +var kEnded = Symbol('ended'); +var kLastPromise = Symbol('lastPromise'); +var kHandlePromise = Symbol('handlePromise'); +var kStream = Symbol('stream'); + +function createIterResult(value, done) { + return { + value: value, + done: done + }; +} + +function readAndResolve(iter) { + var resolve = iter[kLastResolve]; + + if (resolve !== null) { + var data = iter[kStream].read(); // we defer if data is null + // we can be expecting either 'end' or + // 'error' + + if (data !== null) { + iter[kLastPromise] = null; + iter[kLastResolve] = null; + iter[kLastReject] = null; + resolve(createIterResult(data, false)); + } + } +} + +function onReadable(iter) { + // we wait for the next tick, because it might + // emit an error with process.nextTick + process.nextTick(readAndResolve, iter); +} + +function wrapForNext(lastPromise, iter) { + return function (resolve, reject) { + lastPromise.then(function () { + if (iter[kEnded]) { + resolve(createIterResult(undefined, true)); + return; + } + + iter[kHandlePromise](resolve, reject); + }, reject); + }; +} + +var AsyncIteratorPrototype = Object.getPrototypeOf(function () {}); +var ReadableStreamAsyncIteratorPrototype = Object.setPrototypeOf((_Object$setPrototypeO = { + get stream() { + return this[kStream]; + }, + + next: function next() { + var _this = this; + + // if we have detected an error in the meanwhile + // reject straight away + var error = this[kError]; + + if (error !== null) { + return Promise.reject(error); + } + + if (this[kEnded]) { + return Promise.resolve(createIterResult(undefined, true)); + } + + if (this[kStream].destroyed) { + // We need to defer via nextTick because if .destroy(err) is + // called, the error will be emitted via nextTick, and + // we cannot guarantee that there is no error lingering around + // waiting to be emitted. + return new Promise(function (resolve, reject) { + process.nextTick(function () { + if (_this[kError]) { + reject(_this[kError]); + } else { + resolve(createIterResult(undefined, true)); + } + }); + }); + } // if we have multiple next() calls + // we will wait for the previous Promise to finish + // this logic is optimized to support for await loops, + // where next() is only called once at a time + + + var lastPromise = this[kLastPromise]; + var promise; + + if (lastPromise) { + promise = new Promise(wrapForNext(lastPromise, this)); + } else { + // fast path needed to support multiple this.push() + // without triggering the next() queue + var data = this[kStream].read(); + + if (data !== null) { + return Promise.resolve(createIterResult(data, false)); + } + + promise = new Promise(this[kHandlePromise]); + } + + this[kLastPromise] = promise; + return promise; + } +}, _defineProperty(_Object$setPrototypeO, Symbol.asyncIterator, function () { + return this; +}), _defineProperty(_Object$setPrototypeO, "return", function _return() { + var _this2 = this; + + // destroy(err, cb) is a private API + // we can guarantee we have that here, because we control the + // Readable class this is attached to + return new Promise(function (resolve, reject) { + _this2[kStream].destroy(null, function (err) { + if (err) { + reject(err); + return; + } + + resolve(createIterResult(undefined, true)); + }); + }); +}), _Object$setPrototypeO), AsyncIteratorPrototype); + +var createReadableStreamAsyncIterator = function createReadableStreamAsyncIterator(stream) { + var _Object$create; + + var iterator = Object.create(ReadableStreamAsyncIteratorPrototype, (_Object$create = {}, _defineProperty(_Object$create, kStream, { + value: stream, + writable: true + }), _defineProperty(_Object$create, kLastResolve, { + value: null, + writable: true + }), _defineProperty(_Object$create, kLastReject, { + value: null, + writable: true + }), _defineProperty(_Object$create, kError, { + value: null, + writable: true + }), _defineProperty(_Object$create, kEnded, { + value: stream._readableState.endEmitted, + writable: true + }), _defineProperty(_Object$create, kHandlePromise, { + value: function value(resolve, reject) { + var data = iterator[kStream].read(); + + if (data) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(data, false)); + } else { + iterator[kLastResolve] = resolve; + iterator[kLastReject] = reject; + } + }, + writable: true + }), _Object$create)); + iterator[kLastPromise] = null; + finished(stream, function (err) { + if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { + var reject = iterator[kLastReject]; // reject if we are waiting for data in the Promise + // returned by next() and store the error + + if (reject !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + reject(err); + } + + iterator[kError] = err; + return; + } + + var resolve = iterator[kLastResolve]; + + if (resolve !== null) { + iterator[kLastPromise] = null; + iterator[kLastResolve] = null; + iterator[kLastReject] = null; + resolve(createIterResult(undefined, true)); + } + + iterator[kEnded] = true; + }); + stream.on('readable', onReadable.bind(null, iterator)); + return iterator; +}; + +module.exports = createReadableStreamAsyncIterator; +}).call(this)}).call(this,require('_process')) + +},{"./end-of-stream":274,"_process":237}],272:[function(require,module,exports){ +arguments[4][57][0].apply(exports,arguments) +},{"buffer":68,"dup":57,"util":20}],273:[function(require,module,exports){ +(function (process){(function (){ +'use strict'; // undocumented cb() API, needed for core, not for public API + +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err) { + if (!this._writableState) { + process.nextTick(emitErrorNT, this, err); + } else if (!this._writableState.errorEmitted) { + this._writableState.errorEmitted = true; + process.nextTick(emitErrorNT, this, err); + } + } + + return this; + } // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + + if (this._readableState) { + this._readableState.destroyed = true; + } // if this is a duplex stream mark the writable part as destroyed as well + + + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + if (!_this._writableState) { + process.nextTick(emitErrorAndCloseNT, _this, err); + } else if (!_this._writableState.errorEmitted) { + _this._writableState.errorEmitted = true; + process.nextTick(emitErrorAndCloseNT, _this, err); + } else { + process.nextTick(emitCloseNT, _this); + } + } else if (cb) { + process.nextTick(emitCloseNT, _this); + cb(err); + } else { + process.nextTick(emitCloseNT, _this); + } + }); + + return this; +} + +function emitErrorAndCloseNT(self, err) { + emitErrorNT(self, err); + emitCloseNT(self); +} + +function emitCloseNT(self) { + if (self._writableState && !self._writableState.emitClose) return; + if (self._readableState && !self._readableState.emitClose) return; + self.emit('close'); +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finalCalled = false; + this._writableState.prefinished = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +function errorOrDestroy(stream, err) { + // We have tests that rely on errors being emitted + // in the same tick, so changing this is semver major. + // For now when you opt-in to autoDestroy we allow + // the error to be emitted nextTick. In a future + // semver major update we should change the default to this. + var rState = stream._readableState; + var wState = stream._writableState; + if (rState && rState.autoDestroy || wState && wState.autoDestroy) stream.destroy(err);else stream.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy, + errorOrDestroy: errorOrDestroy +}; +}).call(this)}).call(this,require('_process')) + +},{"_process":237}],274:[function(require,module,exports){ +arguments[4][59][0].apply(exports,arguments) +},{"../../../errors":265,"dup":59}],275:[function(require,module,exports){ +arguments[4][60][0].apply(exports,arguments) +},{"dup":60}],276:[function(require,module,exports){ +arguments[4][61][0].apply(exports,arguments) +},{"../../../errors":265,"./end-of-stream":274,"dup":61}],277:[function(require,module,exports){ +arguments[4][62][0].apply(exports,arguments) +},{"../../../errors":265,"dup":62}],278:[function(require,module,exports){ +arguments[4][63][0].apply(exports,arguments) +},{"dup":63,"events":105}],279:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var Buffer = require('safe-buffer').Buffer; +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} +},{"safe-buffer":250}],280:[function(require,module,exports){ +(function (setImmediate,clearImmediate){(function (){ +var nextTick = require('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate) + +},{"process/browser.js":237,"timers":280}],281:[function(require,module,exports){ +module.exports={ + "0": "O", + "1": "l", + "֭": "֖", + "֮": "֘", + "֨": "֙", + "֤": "֚", + "᪴": "ۛ", + "⃛": "ۛ", + "ؙ": "̓", + "ࣳ": "̓", + "̓": "̓", + "̕": "̓", + "ُ": "̓", + "ٝ": "̔", + "֜": "́", + "֝": "́", + "ؘ": "́", + "݇": "́", + "́": "́", + "॔": "́", + "َ": "́", + "̀": "̀", + "॓": "̀", + "̌": "̆", + "꙼": "̆", + "٘": "̆", + "ٚ": "̆", + "ͮ": "̆", + "ۨ": "̆̇", + "̐": "̆̇", + "ँ": "̆̇", + "ঁ": "̆̇", + "ઁ": "̆̇", + "ଁ": "̆̇", + "ఀ": "̆̇", + "ಁ": "̆̇", + "ഁ": "̆̇", + "𑒿": "̆̇", + "᳐": "̂", + "̑": "̂", + "ٛ": "̂", + "߮": "̂", + "꛰": "̂", + "֯": "̊", + "۟": "̊", + "៓": "̊", + "゚": "̊", + "ْ": "̊", + "ஂ": "̊", + "ံ": "̊", + "ំ": "̊", + "𑌀": "̊", + "ํ": "̊", + "ໍ": "̊", + "ͦ": "̊", + "ⷪ": "̊", + "࣫": "̈", + "߳": "̈", + "ً": "̋", + "ࣰ": "̋", + "͂": "̃", + "ٓ": "̃", + "ׄ": "̇", + "۬": "̇", + "݀": "̇", + "࣪": "̇", + "݁": "̇", + "͘": "̇", + "ֹ": "̇", + "ֺ": "̇", + "ׂ": "̇", + "ׁ": "̇", + "߭": "̇", + "ं": "̇", + "ਂ": "̇", + "ં": "̇", + "்": "̇", + "̷": "̸", + "᪷": "̨", + "̢": "̨", + "ͅ": "̨", + "᳒": "̄", + "̅": "̄", + "ٙ": "̄", + "߫": "̄", + "꛱": "̄", + "᳚": "̎", + "ٗ": "̒", + "͗": "͐", + "ࣿ": "͐", + "ࣸ": "͐", + "ऀ": "͒", + "᳭": "̖", + "᳜": "̩", + "ٖ": "̩", + "᳕": "̫", + "͇": "̳", + "ࣹ": "͔", + "ࣺ": "͕", + "゛": "゙", + "゜": "゚", + "̶": "̵", + "〬": "̉", + "ׅ": "̣", + "࣭": "̣", + "᳝": "̣", + "ִ": "̣", + "ٜ": "̣", + "़": "̣", + "়": "̣", + "਼": "̣", + "઼": "̣", + "଼": "̣", + "𑇊": "̣", + "𑓃": "̣", + "𐨺": "̣", + "࣮": "̤", + "᳞": "̤", + "༷": "̥", + "〭": "̥", + "̧": "̦", + "̡": "̦", + "̹": "̦", + "᳙": "̭", + "᳘": "̮", + "॒": "̱", + "̠": "̱", + "ࣱ": "ٌ", + "ࣨ": "ٌ", + "ࣥ": "ٌ", + "ﱞ": "ﹲّ", + "ࣲ": "ٍ", + "ﱟ": "ﹴّ", + "ﳲ": "ﹷّ", + "ﱠ": "ﹶّ", + "ﳳ": "ﹹّ", + "ﱡ": "ﹸّ", + "ؚ": "ِ", + "̗": "ِ", + "ﳴ": "ﹻّ", + "ﱢ": "ﹺّ", + "ﱣ": "ﹼٰ", + "ٟ": "ٕ", + "̍": "ٰ", + "݂": "ܼ", + "ਃ": "ঃ", + "ః": "ঃ", + "ಃ": "ঃ", + "ഃ": "ঃ", + "ඃ": "ঃ", + "း": "ঃ", + "𑓁": "ঃ", + "់": "่", + "່": "่", + "້": "้", + "໊": "๊", + "໋": "๋", + "꙯": "⃩", + "\u2028": " ", + "\u2029": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + " ": " ", + "ߺ": "_", + "﹍": "_", + "﹎": "_", + "﹏": "_", + "‐": "-", + "‑": "-", + "‒": "-", + "–": "-", + "﹘": "-", + "۔": "-", + "⁃": "-", + "˗": "-", + "−": "-", + "➖": "-", + "Ⲻ": "-", + "⨩": "-̓", + "⸚": "-̈", + "﬩": "-̇", + "∸": "-̇", + "⨪": "-̣", + "꓾": "-.", + "~": "〜", + "؍": ",", + "٫": ",", + "‚": ",", + "¸": ",", + "ꓹ": ",", + "⸲": "،", + "٬": "،", + ";": ";", + "⸵": "؛", + "ः": ":", + "ઃ": ":", + ":": ":", + "։": ":", + "܃": ":", + "܄": ":", + "᛬": ":", + "︰": ":", + "᠃": ":", + "᠉": ":", + "⁚": ":", + "׃": ":", + "˸": ":", + "꞉": ":", + "∶": ":", + "ː": ":", + "ꓽ": ":", + "⩴": "::=", + "⧴": ":→", + "!": "!", + "ǃ": "!", + "ⵑ": "!", + "‼": "!!", + "⁉": "!?", + "ʔ": "?", + "Ɂ": "?", + "ॽ": "?", + "Ꭾ": "?", + "ꛫ": "?", + "⁈": "?!", + "⁇": "??", + "⸮": "؟", + "𝅭": ".", + "․": ".", + "܁": ".", + "܂": ".", + "꘎": ".", + "𐩐": ".", + "٠": ".", + "۰": ".", + "ꓸ": ".", + "ꓻ": ".,", + "‥": "..", + "ꓺ": "..", + "…": "...", + "꛴": "꛳꛳", + "・": "·", + "・": "·", + "᛫": "·", + "·": "·", + "⸱": "·", + "𐄁": "·", + "•": "·", + "‧": "·", + "∙": "·", + "⋅": "·", + "ꞏ": "·", + "ᐧ": "·", + "⋯": "···", + "ⵈ": "···", + "ᑄ": "·<", + "⋗": "·>", + "ᐷ": "·>", + "ᑀ": "·>", + "ᔯ": "·4", + "ᑾ": "·b", + "ᒀ": "·ḃ", + "ᑺ": "·d", + "ᒘ": "·J", + "ᒶ": "·L", + "ᑶ": "·P", + "ᑗ": "·U", + "ᐺ": "·V", + "ᐼ": "·Ʌ", + "ᒮ": "·Γ", + "ᐎ": "·Δ", + "ᑙ": "·Ո", + "ᐌ": "·ᐁ", + "ᐐ": "·ᐄ", + "ᐒ": "·ᐅ", + "ᐔ": "·ᐆ", + "ᐗ": "·ᐊ", + "ᐙ": "·ᐋ", + "ᐾ": "·ᐲ", + "ᑂ": "·ᐴ", + "ᑆ": "·ᐹ", + "ᑛ": "·ᑏ", + "ᑔ": "·ᑐ", + "ᑝ": "·ᑐ", + "ᑟ": "·ᑑ", + "ᑡ": "·ᑕ", + "ᑣ": "·ᑖ", + "ᑴ": "·ᑫ", + "ᑸ": "·ᑮ", + "ᑼ": "·ᑰ", + "ᒒ": "·ᒉ", + "ᒔ": "·ᒋ", + "ᒖ": "·ᒌ", + "ᒚ": "·ᒎ", + "ᒜ": "·ᒐ", + "ᒞ": "·ᒑ", + "ᒬ": "·ᒣ", + "ᒰ": "·ᒦ", + "ᒲ": "·ᒧ", + "ᒴ": "·ᒨ", + "ᒸ": "·ᒫ", + "ᓉ": "·ᓀ", + "ᣆ": "·ᓂ", + "ᣈ": "·ᓃ", + "ᣊ": "·ᓄ", + "ᣌ": "·ᓅ", + "ᓋ": "·ᓇ", + "ᓍ": "·ᓈ", + "ᓜ": "·ᓓ", + "ᓞ": "·ᓕ", + "ᓠ": "·ᓖ", + "ᓢ": "·ᓗ", + "ᓤ": "·ᓘ", + "ᓦ": "·ᓚ", + "ᓨ": "·ᓛ", + "ᓶ": "·ᓭ", + "ᓸ": "·ᓯ", + "ᓺ": "·ᓰ", + "ᓼ": "·ᓱ", + "ᓾ": "·ᓲ", + "ᔀ": "·ᓴ", + "ᔂ": "·ᓵ", + "ᔗ": "·ᔐ", + "ᔙ": "·ᔑ", + "ᔛ": "·ᔒ", + "ᔝ": "·ᔓ", + "ᔟ": "·ᔔ", + "ᔡ": "·ᔕ", + "ᔣ": "·ᔖ", + "ᔱ": "·ᔨ", + "ᔳ": "·ᔩ", + "ᔵ": "·ᔪ", + "ᔷ": "·ᔫ", + "ᔹ": "·ᔭ", + "ᔻ": "·ᔮ", + "ᣎ": "·ᕃ", + "ᣏ": "·ᕆ", + "ᣐ": "·ᕇ", + "ᣑ": "·ᕈ", + "ᣒ": "·ᕉ", + "ᣓ": "·ᕋ", + "ᕎ": "·ᕌ", + "ᕛ": "·ᕚ", + "ᕨ": "·ᕧ", + "ᢳ": "·ᢱ", + "ᢶ": "·ᢴ", + "ᢹ": "·ᢸ", + "ᣂ": "·ᣀ", + "꠰": "।", + "॥": "।।", + "᰼": "᰻᰻", + "။": "၊၊", + "᪩": "᪨᪨", + "᪫": "᪪᪨", + "᭟": "᭞᭞", + "𐩗": "𐩖𐩖", + "𑑌": "𑑋𑑋", + "𑙂": "𑙁𑙁", + "𑱂": "𑱁𑱁", + "᱿": "᱾᱾", + "՝": "'", + "'": "'", + "‘": "'", + "’": "'", + "‛": "'", + "′": "'", + "‵": "'", + "՚": "'", + "׳": "'", + "`": "'", + "`": "'", + "`": "'", + "´": "'", + "΄": "'", + "´": "'", + "᾽": "'", + "᾿": "'", + "῾": "'", + "ʹ": "'", + "ʹ": "'", + "ˈ": "'", + "ˊ": "'", + "ˋ": "'", + "˴": "'", + "ʻ": "'", + "ʽ": "'", + "ʼ": "'", + "ʾ": "'", + "ꞌ": "'", + "י": "'", + "ߴ": "'", + "ߵ": "'", + "ᑊ": "'", + "ᛌ": "'", + "𖽑": "'", + "𖽒": "'", + "᳓": "''", + "\"": "''", + """: "''", + "“": "''", + "”": "''", + "‟": "''", + "″": "''", + "‶": "''", + "〃": "''", + "״": "''", + "˝": "''", + "ʺ": "''", + "˶": "''", + "ˮ": "''", + "ײ": "''", + "‴": "'''", + "‷": "'''", + "⁗": "''''", + "Ɓ": "'B", + "Ɗ": "'D", + "ʼn": "'n", + "Ƥ": "'P", + "Ƭ": "'T", + "Ƴ": "'Y", + "[": "(", + "❨": "(", + "❲": "(", + "〔": "(", + "﴾": "(", + "⸨": "((", + "㈠": "(ー)", + "⑵": "(2)", + "⒇": "(2O)", + "⑶": "(3)", + "⑷": "(4)", + "⑸": "(5)", + "⑹": "(6)", + "⑺": "(7)", + "⑻": "(8)", + "⑼": "(9)", + "⒜": "(a)", + "🄐": "(A)", + "⒝": "(b)", + "🄑": "(B)", + "⒞": "(c)", + "🄒": "(C)", + "⒟": "(d)", + "🄓": "(D)", + "⒠": "(e)", + "🄔": "(E)", + "⒡": "(f)", + "🄕": "(F)", + "⒢": "(g)", + "🄖": "(G)", + "⒣": "(h)", + "🄗": "(H)", + "⒤": "(i)", + "⒥": "(j)", + "🄙": "(J)", + "⒦": "(k)", + "🄚": "(K)", + "⑴": "(l)", + "🄘": "(l)", + "⒧": "(l)", + "🄛": "(L)", + "⑿": "(l2)", + "⒀": "(l3)", + "⒁": "(l4)", + "⒂": "(l5)", + "⒃": "(l6)", + "⒄": "(l7)", + "⒅": "(l8)", + "⒆": "(l9)", + "⑾": "(ll)", + "⑽": "(lO)", + "🄜": "(M)", + "⒩": "(n)", + "🄝": "(N)", + "⒪": "(o)", + "🄞": "(O)", + "⒫": "(p)", + "🄟": "(P)", + "⒬": "(q)", + "🄠": "(Q)", + "⒭": "(r)", + "🄡": "(R)", + "⒨": "(rn)", + "⒮": "(s)", + "🄢": "(S)", + "🄪": "(S)", + "⒯": "(t)", + "🄣": "(T)", + "⒰": "(u)", + "🄤": "(U)", + "⒱": "(v)", + "🄥": "(V)", + "⒲": "(w)", + "🄦": "(W)", + "⒳": "(x)", + "🄧": "(X)", + "⒴": "(y)", + "🄨": "(Y)", + "⒵": "(z)", + "🄩": "(Z)", + "㈀": "(ᄀ)", + "㈎": "(가)", + "㈁": "(ᄂ)", + "㈏": "(나)", + "㈂": "(ᄃ)", + "㈐": "(다)", + "㈃": "(ᄅ)", + "㈑": "(라)", + "㈄": "(ᄆ)", + "㈒": "(마)", + "㈅": "(ᄇ)", + "㈓": "(바)", + "㈆": "(ᄉ)", + "㈔": "(사)", + "㈇": "(ᄋ)", + "㈕": "(아)", + "㈝": "(오전)", + "㈞": "(오후)", + "㈈": "(ᄌ)", + "㈖": "(자)", + "㈜": "(주)", + "㈉": "(ᄎ)", + "㈗": "(차)", + "㈊": "(ᄏ)", + "㈘": "(카)", + "㈋": "(ᄐ)", + "㈙": "(타)", + "㈌": "(ᄑ)", + "㈚": "(파)", + "㈍": "(ᄒ)", + "㈛": "(하)", + "㈦": "(七)", + "㈢": "(三)", + "🉁": "(三)", + "㈨": "(九)", + "㈡": "(二)", + "🉂": "(二)", + "㈤": "(五)", + "㈹": "(代)", + "㈽": "(企)", + "㉁": "(休)", + "㈧": "(八)", + "㈥": "(六)", + "㈸": "(労)", + "🉇": "(勝)", + "㈩": "(十)", + "㈿": "(協)", + "㈴": "(名)", + "㈺": "(呼)", + "㈣": "(四)", + "㈯": "(土)", + "㈻": "(学)", + "🉃": "(安)", + "🉅": "(打)", + "🉈": "(敗)", + "㈰": "(日)", + "㈪": "(月)", + "㈲": "(有)", + "㈭": "(木)", + "🉀": "(本)", + "㈱": "(株)", + "㈬": "(水)", + "㈫": "(火)", + "🉄": "(点)", + "㈵": "(特)", + "🉆": "(盗)", + "㈼": "(監)", + "㈳": "(社)", + "㈷": "(祝)", + "㉀": "(祭)", + "㉂": "(自)", + "㉃": "(至)", + "㈶": "(財)", + "㈾": "(資)", + "㈮": "(金)", + "]": ")", + "❩": ")", + "❳": ")", + "〕": ")", + "﴿": ")", + "⸩": "))", + "❴": "{", + "𝄔": "{", + "❵": "}", + "〚": "⟦", + "〛": "⟧", + "⟨": "❬", + "〈": "❬", + "〈": "❬", + "㇛": "❬", + "く": "❬", + "𡿨": "❬", + "⟩": "❭", + "〉": "❭", + "〉": "❭", + "^": "︿", + "⸿": "¶", + "⁎": "*", + "٭": "*", + "∗": "*", + "𐌟": "*", + "᜵": "/", + "⁁": "/", + "∕": "/", + "⁄": "/", + "╱": "/", + "⟋": "/", + "⧸": "/", + "𝈺": "/", + "㇓": "/", + "〳": "/", + "Ⳇ": "/", + "ノ": "/", + "丿": "/", + "⼃": "/", + "⧶": "/̄", + "⫽": "//", + "⫻": "///", + "\": "\\", + "﹨": "\\", + "∖": "\\", + "⟍": "\\", + "⧵": "\\", + "⧹": "\\", + "𝈏": "\\", + "𝈻": "\\", + "㇔": "\\", + "丶": "\\", + "⼂": "\\", + "⳹": "\\\\", + "⑊": "\\\\", + "⟈": "\\ᑕ", + "ꝸ": "&", + "૰": "॰", + "𑂻": "॰", + "𑇇": "॰", + "⚬": "॰", + "𑇛": "꣼", + "៙": "๏", + "៕": "๚", + "៚": "๛", + "༌": "་", + "༎": "།།", + "˄": "^", + "ˆ": "^", + "꙾": "ˇ", + "˘": "ˇ", + "‾": "ˉ", + "﹉": "ˉ", + "﹊": "ˉ", + "﹋": "ˉ", + "﹌": "ˉ", + "¯": "ˉ", + " ̄": "ˉ", + "▔": "ˉ", + "ъ": "ˉb", + "ꙑ": "ˉbi", + "͵": "ˏ", + "˻": "˪", + "꜖": "˪", + "꜔": "˫", + "。": "˳", + "⸰": "°", + "˚": "°", + "∘": "°", + "○": "°", + "◦": "°", + "⍜": "°̲", + "⍤": "°̈", + "℃": "°C", + "℉": "°F", + "௵": "௳", + "༛": "༚༚", + "༟": "༚༝", + "࿎": "༝༚", + "༞": "༝༝", + "Ⓒ": "©", + "Ⓡ": "®", + "Ⓟ": "℗", + "𝈛": "⅄", + "⯬": "↞", + "⯭": "↟", + "⯮": "↠", + "⯯": "↡", + "↵": "↲", + "⥥": "⇃⇂", + "⥯": "⇃ᛚ", + "𝛛": "∂", + "𝜕": "∂", + "𝝏": "∂", + "𝞉": "∂", + "𝟃": "∂", + "𞣌": "∂", + "𞣍": "∂̵", + "ð": "∂̵", + "⌀": "∅", + "𝛁": "∇", + "𝛻": "∇", + "𝜵": "∇", + "𝝯": "∇", + "𝞩": "∇", + "𑢨": "∇", + "⍢": "∇̈", + "⍫": "∇̴", + "█": "∎", + "■": "∎", + "⨿": "∐", + "᛭": "+", + "➕": "+", + "𐊛": "+", + "⨣": "+̂", + "⨢": "+̊", + "⨤": "+̃", + "∔": "+̇", + "⨥": "+̣", + "⨦": "+̰", + "⨧": "+₂", + "➗": "÷", + "‹": "<", + "❮": "<", + "˂": "<", + "𝈶": "<", + "ᐸ": "<", + "ᚲ": "<", + "⋖": "<·", + "Ⲵ": "<·", + "ᑅ": "<·", + "≪": "<<", + "⋘": "<<<", + "᐀": "=", + "⹀": "=", + "゠": "=", + "꓿": "=", + "≚": "=̆", + "≙": "=̂", + "≗": "=̊", + "≐": "=̇", + "≑": "=̣̇", + "⩮": "=⃰", + "⩵": "==", + "⩶": "===", + "≞": "=ͫ", + "›": ">", + "❯": ">", + "˃": ">", + "𝈷": ">", + "ᐳ": ">", + "𖼿": ">", + "ᑁ": ">·", + "⪥": "><", + "≫": ">>", + "⨠": ">>", + "⋙": ">>>", + "⁓": "~", + "˜": "~", + "῀": "~", + "∼": "~", + "⍨": "~̈", + "⸞": "~̇", + "⩪": "~̇", + "⸟": "~̣", + "𞣈": "∠", + "⋀": "∧", + "∯": "∮∮", + "∰": "∮∮∮", + "⸫": "∴", + "⸪": "∵", + "⸬": "∷", + "𑇞": "≈", + "♎": "≏", + "🝞": "≏", + "≣": "≡", + "⨃": "⊍", + "⨄": "⊎", + "𝈸": "⊏", + "𝈹": "⊐", + "⨅": "⊓", + "⨆": "⊔", + "⨂": "⊗", + "⍟": "⊛", + "🝱": "⊠", + "🝕": "⊡", + "◁": "⊲", + "▷": "⊳", + "⍣": "⋆̈", + "︴": "⌇", + "◠": "⌒", + "⨽": "⌙", + "⌥": "⌤", + "⧇": "⌻", + "◎": "⌾", + "⦾": "⌾", + "⧅": "⍂", + "⦰": "⍉", + "⏃": "⍋", + "⏂": "⍎", + "⏁": "⍕", + "⏆": "⍭", + "☸": "⎈", + "︵": "⏜", + "︶": "⏝", + "︷": "⏞", + "︸": "⏟", + "︹": "⏠", + "︺": "⏡", + "▱": "⏥", + "⏼": "⏻", + "︱": "│", + "|": "│", + "┃": "│", + "┏": "┌", + "┣": "├", + "▐": "▌", + "▗": "▖", + "▝": "▘", + "☐": "□", + "■": "▪", + "▸": "▶", + "►": "▶", + "⳩": "☧", + "🜊": "☩", + "🌒": "☽", + "🌙": "☽", + "⏾": "☾", + "🌘": "☾", + "⧙": "⦚", + "🜺": "⧟", + "⨾": "⨟", + "𐆠": "⳨", + "♩": "𝅘𝅥", + "♪": "𝅘𝅥𝅮", + "⓪": "🄍", + "↺": "🄎", + "˙": "ॱ", + "ൎ": "ॱ", + "-": "ー", + "—": "ー", + "―": "ー", + "─": "ー", + "━": "ー", + "㇐": "ー", + "ꟷ": "ー", + "ᅳ": "ー", + "ㅡ": "ー", + "一": "ー", + "⼀": "ー", + "ᆖ": "ーー", + "ힹ": "ーᅡ", + "ힺ": "ーᅥ", + "ힻ": "ーᅥ丨", + "ힼ": "ーᅩ", + "ᆕ": "ーᅮ", + "ᅴ": "ー丨", + "ㅢ": "ー丨", + "ᆗ": "ー丨ᅮ", + "🄏": "$⃠", + "₤": "£", + "〒": "₸", + "〶": "₸", + "᭜": "᭐", + "꧆": "꧐", + "𑓑": "১", + "೧": "౧", + "ၥ": "၁", + "①": "➀", + "⑩": "➉", + "⏨": "₁₀", + "𝟐": "2", + "𝟚": "2", + "𝟤": "2", + "𝟮": "2", + "𝟸": "2", + "🯲": "2", + "Ꝛ": "2", + "Ƨ": "2", + "Ϩ": "2", + "Ꙅ": "2", + "ᒿ": "2", + "ꛯ": "2", + "ꧏ": "٢", + "۲": "٢", + "૨": "२", + "𑓒": "২", + "೨": "౨", + "②": "➁", + "ƻ": "2̵", + "🄃": "2,", + "⒉": "2.", + "㏵": "22日", + "㍮": "22点", + "㏶": "23日", + "㍯": "23点", + "㏷": "24日", + "㍰": "24点", + "㏸": "25日", + "㏹": "26日", + "㏺": "27日", + "㏻": "28日", + "㏼": "29日", + "㏴": "2l日", + "㍭": "2l点", + "⒛": "2O.", + "㏳": "2O日", + "㍬": "2O点", + "෩": "෨ා", + "෯": "෨ී", + "㏡": "2日", + "㋁": "2月", + "㍚": "2点", + "𝈆": "3", + "𝟑": "3", + "𝟛": "3", + "𝟥": "3", + "𝟯": "3", + "𝟹": "3", + "🯳": "3", + "Ɜ": "3", + "Ȝ": "3", + "Ʒ": "3", + "Ꝫ": "3", + "Ⳍ": "3", + "З": "3", + "Ӡ": "3", + "𖼻": "3", + "𑣊": "3", + "۳": "٣", + "𞣉": "٣", + "૩": "३", + "③": "➂", + "Ҙ": "3̦", + "🄄": "3,", + "⒊": "3.", + "㏾": "3l日", + "㏽": "3O日", + "㏢": "3日", + "㋂": "3月", + "㍛": "3点", + "𝟒": "4", + "𝟜": "4", + "𝟦": "4", + "𝟰": "4", + "𝟺": "4", + "🯴": "4", + "Ꮞ": "4", + "𑢯": "4", + "۴": "٤", + "૪": "४", + "④": "➃", + "🄅": "4,", + "⒋": "4.", + "ᔰ": "4·", + "㏣": "4日", + "㋃": "4月", + "㍜": "4点", + "𝟓": "5", + "𝟝": "5", + "𝟧": "5", + "𝟱": "5", + "𝟻": "5", + "🯵": "5", + "Ƽ": "5", + "𑢻": "5", + "⑤": "➄", + "🄆": "5,", + "⒌": "5.", + "㏤": "5日", + "㋄": "5月", + "㍝": "5点", + "𝟔": "6", + "𝟞": "6", + "𝟨": "6", + "𝟲": "6", + "𝟼": "6", + "🯶": "6", + "Ⳓ": "6", + "б": "6", + "Ꮾ": "6", + "𑣕": "6", + "۶": "٦", + "𑓖": "৬", + "⑥": "➅", + "🄇": "6,", + "⒍": "6.", + "㏥": "6日", + "㋅": "6月", + "㍞": "6点", + "𝈒": "7", + "𝟕": "7", + "𝟟": "7", + "𝟩": "7", + "𝟳": "7", + "𝟽": "7", + "🯷": "7", + "𐓒": "7", + "𑣆": "7", + "⑦": "➆", + "🄈": "7,", + "⒎": "7.", + "㏦": "7日", + "㋆": "7月", + "㍟": "7点", + "ଃ": "8", + "৪": "8", + "੪": "8", + "𞣋": "8", + "𝟖": "8", + "𝟠": "8", + "𝟪": "8", + "𝟴": "8", + "𝟾": "8", + "🯸": "8", + "ȣ": "8", + "Ȣ": "8", + "𐌚": "8", + "૮": "८", + "⑧": "➇", + "🄉": "8,", + "⒏": "8.", + "㏧": "8日", + "㋇": "8月", + "㍠": "8点", + "੧": "9", + "୨": "9", + "৭": "9", + "൭": "9", + "𝟗": "9", + "𝟡": "9", + "𝟫": "9", + "𝟵": "9", + "𝟿": "9", + "🯹": "9", + "Ꝯ": "9", + "Ⳋ": "9", + "𑣌": "9", + "𑢬": "9", + "𑣖": "9", + "१": "٩", + "𑣤": "٩", + "۹": "٩", + "೯": "౯", + "⑨": "➈", + "🄊": "9,", + "⒐": "9.", + "㏨": "9日", + "㋈": "9月", + "㍡": "9点", + "⍺": "a", + "a": "a", + "𝐚": "a", + "𝑎": "a", + "𝒂": "a", + "𝒶": "a", + "𝓪": "a", + "𝔞": "a", + "𝕒": "a", + "𝖆": "a", + "𝖺": "a", + "𝗮": "a", + "𝘢": "a", + "𝙖": "a", + "𝚊": "a", + "ɑ": "a", + "α": "a", + "𝛂": "a", + "𝛼": "a", + "𝜶": "a", + "𝝰": "a", + "𝞪": "a", + "а": "a", + "ⷶ": "ͣ", + "A": "A", + "𝐀": "A", + "𝐴": "A", + "𝑨": "A", + "𝒜": "A", + "𝓐": "A", + "𝔄": "A", + "𝔸": "A", + "𝕬": "A", + "𝖠": "A", + "𝗔": "A", + "𝘈": "A", + "𝘼": "A", + "𝙰": "A", + "Α": "A", + "𝚨": "A", + "𝛢": "A", + "𝜜": "A", + "𝝖": "A", + "𝞐": "A", + "А": "A", + "Ꭺ": "A", + "ᗅ": "A", + "ꓮ": "A", + "𖽀": "A", + "𐊠": "A", + "⍶": "a̲", + "ǎ": "ă", + "Ǎ": "Ă", + "ȧ": "å", + "Ȧ": "Å", + "ẚ": "ả", + "℀": "a/c", + "℁": "a/s", + "ꜳ": "aa", + "Ꜳ": "AA", + "æ": "ae", + "ӕ": "ae", + "Æ": "AE", + "Ӕ": "AE", + "ꜵ": "ao", + "Ꜵ": "AO", + "🜇": "AR", + "ꜷ": "au", + "Ꜷ": "AU", + "ꜹ": "av", + "ꜻ": "av", + "Ꜹ": "AV", + "Ꜻ": "AV", + "ꜽ": "ay", + "Ꜽ": "AY", + "ꭺ": "ᴀ", + "∀": "Ɐ", + "𝈗": "Ɐ", + "ᗄ": "Ɐ", + "ꓯ": "Ɐ", + "𐐟": "Ɒ", + "𝐛": "b", + "𝑏": "b", + "𝒃": "b", + "𝒷": "b", + "𝓫": "b", + "𝔟": "b", + "𝕓": "b", + "𝖇": "b", + "𝖻": "b", + "𝗯": "b", + "𝘣": "b", + "𝙗": "b", + "𝚋": "b", + "Ƅ": "b", + "Ь": "b", + "Ꮟ": "b", + "ᑲ": "b", + "ᖯ": "b", + "B": "B", + "ℬ": "B", + "𝐁": "B", + "𝐵": "B", + "𝑩": "B", + "𝓑": "B", + "𝔅": "B", + "𝔹": "B", + "𝕭": "B", + "𝖡": "B", + "𝗕": "B", + "𝘉": "B", + "𝘽": "B", + "𝙱": "B", + "Ꞵ": "B", + "Β": "B", + "𝚩": "B", + "𝛣": "B", + "𝜝": "B", + "𝝗": "B", + "𝞑": "B", + "В": "B", + "Ᏼ": "B", + "ᗷ": "B", + "ꓐ": "B", + "𐊂": "B", + "𐊡": "B", + "𐌁": "B", + "ɓ": "b̔", + "ᑳ": "ḃ", + "ƃ": "b̄", + "Ƃ": "b̄", + "Б": "b̄", + "ƀ": "b̵", + "ҍ": "b̵", + "Ҍ": "b̵", + "ѣ": "b̵", + "Ѣ": "b̵", + "ᑿ": "b·", + "ᒁ": "ḃ·", + "ᒈ": "b'", + "Ы": "bl", + "в": "ʙ", + "ᏼ": "ʙ", + "c": "c", + "ⅽ": "c", + "𝐜": "c", + "𝑐": "c", + "𝒄": "c", + "𝒸": "c", + "𝓬": "c", + "𝔠": "c", + "𝕔": "c", + "𝖈": "c", + "𝖼": "c", + "𝗰": "c", + "𝘤": "c", + "𝙘": "c", + "𝚌": "c", + "ᴄ": "c", + "ϲ": "c", + "ⲥ": "c", + "с": "c", + "ꮯ": "c", + "𐐽": "c", + "ⷭ": "ͨ", + "🝌": "C", + "𑣲": "C", + "𑣩": "C", + "C": "C", + "Ⅽ": "C", + "ℂ": "C", + "ℭ": "C", + "𝐂": "C", + "𝐶": "C", + "𝑪": "C", + "𝒞": "C", + "𝓒": "C", + "𝕮": "C", + "𝖢": "C", + "𝗖": "C", + "𝘊": "C", + "𝘾": "C", + "𝙲": "C", + "Ϲ": "C", + "Ⲥ": "C", + "С": "C", + "Ꮯ": "C", + "ꓚ": "C", + "𐊢": "C", + "𐌂": "C", + "𐐕": "C", + "𐔜": "C", + "¢": "c̸", + "ȼ": "c̸", + "₡": "C⃫", + "🅮": "C⃠", + "ç": "c̦", + "ҫ": "c̦", + "Ç": "C̦", + "Ҫ": "C̦", + "Ƈ": "C'", + "℅": "c/o", + "℆": "c/u", + "🅭": "㏄\t⃝", + "⋴": "ꞓ", + "ɛ": "ꞓ", + "ε": "ꞓ", + "ϵ": "ꞓ", + "𝛆": "ꞓ", + "𝛜": "ꞓ", + "𝜀": "ꞓ", + "𝜖": "ꞓ", + "𝜺": "ꞓ", + "𝝐": "ꞓ", + "𝝴": "ꞓ", + "𝞊": "ꞓ", + "𝞮": "ꞓ", + "𝟄": "ꞓ", + "ⲉ": "ꞓ", + "є": "ꞓ", + "ԑ": "ꞓ", + "ꮛ": "ꞓ", + "𑣎": "ꞓ", + "𐐩": "ꞓ", + "€": "Ꞓ", + "Ⲉ": "Ꞓ", + "Є": "Ꞓ", + "⍷": "ꞓ̲", + "ͽ": "ꜿ", + "Ͽ": "Ꜿ", + "ⅾ": "d", + "ⅆ": "d", + "𝐝": "d", + "𝑑": "d", + "𝒅": "d", + "𝒹": "d", + "𝓭": "d", + "𝔡": "d", + "𝕕": "d", + "𝖉": "d", + "𝖽": "d", + "𝗱": "d", + "𝘥": "d", + "𝙙": "d", + "𝚍": "d", + "ԁ": "d", + "Ꮷ": "d", + "ᑯ": "d", + "ꓒ": "d", + "Ⅾ": "D", + "ⅅ": "D", + "𝐃": "D", + "𝐷": "D", + "𝑫": "D", + "𝒟": "D", + "𝓓": "D", + "𝔇": "D", + "𝔻": "D", + "𝕯": "D", + "𝖣": "D", + "𝗗": "D", + "𝘋": "D", + "𝘿": "D", + "𝙳": "D", + "Ꭰ": "D", + "ᗞ": "D", + "ᗪ": "D", + "ꓓ": "D", + "ɗ": "d̔", + "ɖ": "d̨", + "ƌ": "d̄", + "đ": "d̵", + "Đ": "D̵", + "Ð": "D̵", + "Ɖ": "D̵", + "₫": "ḏ̵", + "ꝺ": "Ꝺ", + "ᑻ": "d·", + "ᒇ": "d'", + "ʤ": "dȝ", + "dz": "dz", + "ʣ": "dz", + "Dz": "Dz", + "DZ": "DZ", + "dž": "dž", + "Dž": "Dž", + "DŽ": "DŽ", + "ʥ": "dʑ", + "ꭰ": "ᴅ", + "⸹": "ẟ", + "δ": "ẟ", + "𝛅": "ẟ", + "𝛿": "ẟ", + "𝜹": "ẟ", + "𝝳": "ẟ", + "𝞭": "ẟ", + "ծ": "ẟ", + "ᕷ": "ẟ", + "℮": "e", + "e": "e", + "ℯ": "e", + "ⅇ": "e", + "𝐞": "e", + "𝑒": "e", + "𝒆": "e", + "𝓮": "e", + "𝔢": "e", + "𝕖": "e", + "𝖊": "e", + "𝖾": "e", + "𝗲": "e", + "𝘦": "e", + "𝙚": "e", + "𝚎": "e", + "ꬲ": "e", + "е": "e", + "ҽ": "e", + "ⷷ": "ͤ", + "⋿": "E", + "E": "E", + "ℰ": "E", + "𝐄": "E", + "𝐸": "E", + "𝑬": "E", + "𝓔": "E", + "𝔈": "E", + "𝔼": "E", + "𝕰": "E", + "𝖤": "E", + "𝗘": "E", + "𝘌": "E", + "𝙀": "E", + "𝙴": "E", + "Ε": "E", + "𝚬": "E", + "𝛦": "E", + "𝜠": "E", + "𝝚": "E", + "𝞔": "E", + "Е": "E", + "ⴹ": "E", + "Ꭼ": "E", + "ꓰ": "E", + "𑢦": "E", + "𑢮": "E", + "𐊆": "E", + "ě": "ĕ", + "Ě": "Ĕ", + "ɇ": "e̸", + "Ɇ": "E̸", + "ҿ": "ę", + "ꭼ": "ᴇ", + "ə": "ǝ", + "ә": "ǝ", + "∃": "Ǝ", + "ⴺ": "Ǝ", + "ꓱ": "Ǝ", + "ɚ": "ǝ˞", + "ᴔ": "ǝo", + "ꭁ": "ǝo̸", + "ꭂ": "ǝo̵", + "Ә": "Ə", + "𝈡": "Ɛ", + "ℇ": "Ɛ", + "Ԑ": "Ɛ", + "Ꮛ": "Ɛ", + "𖼭": "Ɛ", + "𐐁": "Ɛ", + "ᶟ": "ᵋ", + "ᴈ": "ɜ", + "з": "ɜ", + "ҙ": "ɜ̦", + "𐑂": "ɞ", + "ꞝ": "ʚ", + "𐐪": "ʚ", + "𝐟": "f", + "𝑓": "f", + "𝒇": "f", + "𝒻": "f", + "𝓯": "f", + "𝔣": "f", + "𝕗": "f", + "𝖋": "f", + "𝖿": "f", + "𝗳": "f", + "𝘧": "f", + "𝙛": "f", + "𝚏": "f", + "ꬵ": "f", + "ꞙ": "f", + "ſ": "f", + "ẝ": "f", + "ք": "f", + "𝈓": "F", + "ℱ": "F", + "𝐅": "F", + "𝐹": "F", + "𝑭": "F", + "𝓕": "F", + "𝔉": "F", + "𝔽": "F", + "𝕱": "F", + "𝖥": "F", + "𝗙": "F", + "𝘍": "F", + "𝙁": "F", + "𝙵": "F", + "Ꞙ": "F", + "Ϝ": "F", + "𝟊": "F", + "ᖴ": "F", + "ꓝ": "F", + "𑣂": "F", + "𑢢": "F", + "𐊇": "F", + "𐊥": "F", + "𐔥": "F", + "ƒ": "f̦", + "Ƒ": "F̦", + "ᵮ": "f̴", + "℻": "FAX", + "ff": "ff", + "ffi": "ffi", + "ffl": "ffl", + "fi": "fi", + "fl": "fl", + "ʩ": "fŋ", + "ᖵ": "Ⅎ", + "ꓞ": "Ⅎ", + "𝈰": "ꟻ", + "ᖷ": "ꟻ", + "g": "g", + "ℊ": "g", + "𝐠": "g", + "𝑔": "g", + "𝒈": "g", + "𝓰": "g", + "𝔤": "g", + "𝕘": "g", + "𝖌": "g", + "𝗀": "g", + "𝗴": "g", + "𝘨": "g", + "𝙜": "g", + "𝚐": "g", + "ɡ": "g", + "ᶃ": "g", + "ƍ": "g", + "ց": "g", + "𝐆": "G", + "𝐺": "G", + "𝑮": "G", + "𝒢": "G", + "𝓖": "G", + "𝔊": "G", + "𝔾": "G", + "𝕲": "G", + "𝖦": "G", + "𝗚": "G", + "𝘎": "G", + "𝙂": "G", + "𝙶": "G", + "Ԍ": "G", + "Ꮐ": "G", + "Ᏻ": "G", + "ꓖ": "G", + "ᶢ": "ᵍ", + "ɠ": "g̔", + "ǧ": "ğ", + "Ǧ": "Ğ", + "ǵ": "ģ", + "ǥ": "g̵", + "Ǥ": "G̵", + "Ɠ": "G'", + "ԍ": "ɢ", + "ꮐ": "ɢ", + "ᏻ": "ɢ", + "h": "h", + "ℎ": "h", + "𝐡": "h", + "𝒉": "h", + "𝒽": "h", + "𝓱": "h", + "𝔥": "h", + "𝕙": "h", + "𝖍": "h", + "𝗁": "h", + "𝗵": "h", + "𝘩": "h", + "𝙝": "h", + "𝚑": "h", + "һ": "h", + "հ": "h", + "Ꮒ": "h", + "H": "H", + "ℋ": "H", + "ℌ": "H", + "ℍ": "H", + "𝐇": "H", + "𝐻": "H", + "𝑯": "H", + "𝓗": "H", + "𝕳": "H", + "𝖧": "H", + "𝗛": "H", + "𝘏": "H", + "𝙃": "H", + "𝙷": "H", + "Η": "H", + "𝚮": "H", + "𝛨": "H", + "𝜢": "H", + "𝝜": "H", + "𝞖": "H", + "Ⲏ": "H", + "Н": "H", + "Ꮋ": "H", + "ᕼ": "H", + "ꓧ": "H", + "𐋏": "H", + "ᵸ": "ᴴ", + "ɦ": "h̔", + "ꚕ": "h̔", + "Ᏺ": "h̔", + "Ⱨ": "H̩", + "Ң": "H̩", + "ħ": "h̵", + "ℏ": "h̵", + "ћ": "h̵", + "Ħ": "H̵", + "Ӊ": "H̦", + "Ӈ": "H̦", + "н": "ʜ", + "ꮋ": "ʜ", + "ң": "ʜ̩", + "ӊ": "ʜ̦", + "ӈ": "ʜ̦", + "Ԋ": "Ƕ", + "ꮀ": "ⱶ", + "Ͱ": "Ⱶ", + "Ꭸ": "Ⱶ", + "Ꮀ": "Ⱶ", + "ꚱ": "Ⱶ", + "ꞕ": "ꜧ", + "˛": "i", + "⍳": "i", + "i": "i", + "ⅰ": "i", + "ℹ": "i", + "ⅈ": "i", + "𝐢": "i", + "𝑖": "i", + "𝒊": "i", + "𝒾": "i", + "𝓲": "i", + "𝔦": "i", + "𝕚": "i", + "𝖎": "i", + "𝗂": "i", + "𝗶": "i", + "𝘪": "i", + "𝙞": "i", + "𝚒": "i", + "ı": "i", + "𝚤": "i", + "ɪ": "i", + "ɩ": "i", + "ι": "i", + "ι": "i", + "ͺ": "i", + "𝛊": "i", + "𝜄": "i", + "𝜾": "i", + "𝝸": "i", + "𝞲": "i", + "і": "i", + "ꙇ": "i", + "ӏ": "i", + "ꭵ": "i", + "Ꭵ": "i", + "𑣃": "i", + "ⓛ": "Ⓘ", + "⍸": "i̲", + "ǐ": "ĭ", + "Ǐ": "Ĭ", + "ɨ": "i̵", + "ᵻ": "i̵", + "ᵼ": "i̵", + "ⅱ": "ii", + "ⅲ": "iii", + "ij": "ij", + "ⅳ": "iv", + "ⅸ": "ix", + "j": "j", + "ⅉ": "j", + "𝐣": "j", + "𝑗": "j", + "𝒋": "j", + "𝒿": "j", + "𝓳": "j", + "𝔧": "j", + "𝕛": "j", + "𝖏": "j", + "𝗃": "j", + "𝗷": "j", + "𝘫": "j", + "𝙟": "j", + "𝚓": "j", + "ϳ": "j", + "ј": "j", + "J": "J", + "𝐉": "J", + "𝐽": "J", + "𝑱": "J", + "𝒥": "J", + "𝓙": "J", + "𝔍": "J", + "𝕁": "J", + "𝕵": "J", + "𝖩": "J", + "𝗝": "J", + "𝘑": "J", + "𝙅": "J", + "𝙹": "J", + "Ʝ": "J", + "Ϳ": "J", + "Ј": "J", + "Ꭻ": "J", + "ᒍ": "J", + "ꓙ": "J", + "ɉ": "j̵", + "Ɉ": "J̵", + "ᒙ": "J·", + "𝚥": "ȷ", + "յ": "ȷ", + "ꭻ": "ᴊ", + "𝐤": "k", + "𝑘": "k", + "𝒌": "k", + "𝓀": "k", + "𝓴": "k", + "𝔨": "k", + "𝕜": "k", + "𝖐": "k", + "𝗄": "k", + "𝗸": "k", + "𝘬": "k", + "𝙠": "k", + "𝚔": "k", + "K": "K", + "K": "K", + "𝐊": "K", + "𝐾": "K", + "𝑲": "K", + "𝒦": "K", + "𝓚": "K", + "𝔎": "K", + "𝕂": "K", + "𝕶": "K", + "𝖪": "K", + "𝗞": "K", + "𝘒": "K", + "𝙆": "K", + "𝙺": "K", + "Κ": "K", + "𝚱": "K", + "𝛫": "K", + "𝜥": "K", + "𝝟": "K", + "𝞙": "K", + "Ⲕ": "K", + "К": "K", + "Ꮶ": "K", + "ᛕ": "K", + "ꓗ": "K", + "𐔘": "K", + "ƙ": "k̔", + "Ⱪ": "K̩", + "Қ": "K̩", + "₭": "K̵", + "Ꝁ": "K̵", + "Ҟ": "K̵", + "Ƙ": "K'", + "׀": "l", + "|": "l", + "∣": "l", + "⏽": "l", + "│": "l", + "١": "l", + "۱": "l", + "𐌠": "l", + "𞣇": "l", + "𝟏": "l", + "𝟙": "l", + "𝟣": "l", + "𝟭": "l", + "𝟷": "l", + "🯱": "l", + "I": "l", + "I": "l", + "Ⅰ": "l", + "ℐ": "l", + "ℑ": "l", + "𝐈": "l", + "𝐼": "l", + "𝑰": "l", + "𝓘": "l", + "𝕀": "l", + "𝕴": "l", + "𝖨": "l", + "𝗜": "l", + "𝘐": "l", + "𝙄": "l", + "𝙸": "l", + "Ɩ": "l", + "l": "l", + "ⅼ": "l", + "ℓ": "l", + "𝐥": "l", + "𝑙": "l", + "𝒍": "l", + "𝓁": "l", + "𝓵": "l", + "𝔩": "l", + "𝕝": "l", + "𝖑": "l", + "𝗅": "l", + "𝗹": "l", + "𝘭": "l", + "𝙡": "l", + "𝚕": "l", + "ǀ": "l", + "Ι": "l", + "𝚰": "l", + "𝛪": "l", + "𝜤": "l", + "𝝞": "l", + "𝞘": "l", + "Ⲓ": "l", + "І": "l", + "Ӏ": "l", + "ו": "l", + "ן": "l", + "ا": "l", + "𞸀": "l", + "𞺀": "l", + "ﺎ": "l", + "ﺍ": "l", + "ߊ": "l", + "ⵏ": "l", + "ᛁ": "l", + "ꓲ": "l", + "𖼨": "l", + "𐊊": "l", + "𐌉": "l", + "𝈪": "L", + "Ⅼ": "L", + "ℒ": "L", + "𝐋": "L", + "𝐿": "L", + "𝑳": "L", + "𝓛": "L", + "𝔏": "L", + "𝕃": "L", + "𝕷": "L", + "𝖫": "L", + "𝗟": "L", + "𝘓": "L", + "𝙇": "L", + "𝙻": "L", + "Ⳑ": "L", + "Ꮮ": "L", + "ᒪ": "L", + "ꓡ": "L", + "𖼖": "L", + "𑢣": "L", + "𑢲": "L", + "𐐛": "L", + "𐔦": "L", + "ﴼ": "l̋", + "ﴽ": "l̋", + "ł": "l̸", + "Ł": "L̸", + "ɭ": "l̨", + "Ɨ": "l̵", + "ƚ": "l̵", + "ɫ": "l̴", + "إ": "lٕ", + "ﺈ": "lٕ", + "ﺇ": "lٕ", + "ٳ": "lٕ", + "ŀ": "l·", + "Ŀ": "l·", + "ᒷ": "l·", + "🄂": "l,", + "⒈": "l.", + "ױ": "l'", + "⒓": "l2.", + "㏫": "l2日", + "㋋": "l2月", + "㍤": "l2点", + "⒔": "l3.", + "㏬": "l3日", + "㍥": "l3点", + "⒕": "l4.", + "㏭": "l4日", + "㍦": "l4点", + "⒖": "l5.", + "㏮": "l5日", + "㍧": "l5点", + "⒗": "l6.", + "㏯": "l6日", + "㍨": "l6点", + "⒘": "l7.", + "㏰": "l7日", + "㍩": "l7点", + "⒙": "l8.", + "㏱": "l8日", + "㍪": "l8点", + "⒚": "l9.", + "㏲": "l9日", + "㍫": "l9点", + "lj": "lj", + "IJ": "lJ", + "Lj": "Lj", + "LJ": "LJ", + "‖": "ll", + "∥": "ll", + "Ⅱ": "ll", + "ǁ": "ll", + "װ": "ll", + "𐆙": "l̵l̵", + "⒒": "ll.", + "Ⅲ": "lll", + "𐆘": "l̵l̵S̵", + "㏪": "ll日", + "㋊": "ll月", + "㍣": "ll点", + "Ю": "lO", + "⒑": "lO.", + "㏩": "lO日", + "㋉": "lO月", + "㍢": "lO点", + "ʪ": "ls", + "₶": "lt", + "Ⅳ": "lV", + "Ⅸ": "lX", + "ɮ": "lȝ", + "ʫ": "lz", + "أ": "lٴ", + "ﺄ": "lٴ", + "ﺃ": "lٴ", + "ٲ": "lٴ", + "ٵ": "lٴ", + "ﷳ": "lكبر", + "ﷲ": "lللّٰo", + "㏠": "l日", + "㋀": "l月", + "㍙": "l点", + "ⳑ": "ʟ", + "ꮮ": "ʟ", + "𐑃": "ʟ", + "M": "M", + "Ⅿ": "M", + "ℳ": "M", + "𝐌": "M", + "𝑀": "M", + "𝑴": "M", + "𝓜": "M", + "𝔐": "M", + "𝕄": "M", + "𝕸": "M", + "𝖬": "M", + "𝗠": "M", + "𝘔": "M", + "𝙈": "M", + "𝙼": "M", + "Μ": "M", + "𝚳": "M", + "𝛭": "M", + "𝜧": "M", + "𝝡": "M", + "𝞛": "M", + "Ϻ": "M", + "Ⲙ": "M", + "М": "M", + "Ꮇ": "M", + "ᗰ": "M", + "ᛖ": "M", + "ꓟ": "M", + "𐊰": "M", + "𐌑": "M", + "Ӎ": "M̦", + "🝫": "MB", + "ⷨ": "ᷟ", + "𝐧": "n", + "𝑛": "n", + "𝒏": "n", + "𝓃": "n", + "𝓷": "n", + "𝔫": "n", + "𝕟": "n", + "𝖓": "n", + "𝗇": "n", + "𝗻": "n", + "𝘯": "n", + "𝙣": "n", + "𝚗": "n", + "ո": "n", + "ռ": "n", + "N": "N", + "ℕ": "N", + "𝐍": "N", + "𝑁": "N", + "𝑵": "N", + "𝒩": "N", + "𝓝": "N", + "𝔑": "N", + "𝕹": "N", + "𝖭": "N", + "𝗡": "N", + "𝘕": "N", + "𝙉": "N", + "𝙽": "N", + "Ν": "N", + "𝚴": "N", + "𝛮": "N", + "𝜨": "N", + "𝝢": "N", + "𝞜": "N", + "Ⲛ": "N", + "ꓠ": "N", + "𐔓": "N", + "𐆎": "N̊", + "ɳ": "n̨", + "ƞ": "n̩", + "η": "n̩", + "𝛈": "n̩", + "𝜂": "n̩", + "𝜼": "n̩", + "𝝶": "n̩", + "𝞰": "n̩", + "Ɲ": "N̦", + "ᵰ": "n̴", + "nj": "nj", + "Nj": "Nj", + "NJ": "NJ", + "№": "No", + "ͷ": "ᴎ", + "и": "ᴎ", + "𐑍": "ᴎ", + "ņ": "ɲ", + "ం": "o", + "ಂ": "o", + "ം": "o", + "ං": "o", + "०": "o", + "੦": "o", + "૦": "o", + "௦": "o", + "౦": "o", + "೦": "o", + "൦": "o", + "๐": "o", + "໐": "o", + "၀": "o", + "٥": "o", + "۵": "o", + "o": "o", + "ℴ": "o", + "𝐨": "o", + "𝑜": "o", + "𝒐": "o", + "𝓸": "o", + "𝔬": "o", + "𝕠": "o", + "𝖔": "o", + "𝗈": "o", + "𝗼": "o", + "𝘰": "o", + "𝙤": "o", + "𝚘": "o", + "ᴏ": "o", + "ᴑ": "o", + "ꬽ": "o", + "ο": "o", + "𝛐": "o", + "𝜊": "o", + "𝝄": "o", + "𝝾": "o", + "𝞸": "o", + "σ": "o", + "𝛔": "o", + "𝜎": "o", + "𝝈": "o", + "𝞂": "o", + "𝞼": "o", + "ⲟ": "o", + "о": "o", + "ჿ": "o", + "օ": "o", + "ס": "o", + "ه": "o", + "𞸤": "o", + "𞹤": "o", + "𞺄": "o", + "ﻫ": "o", + "ﻬ": "o", + "ﻪ": "o", + "ﻩ": "o", + "ھ": "o", + "ﮬ": "o", + "ﮭ": "o", + "ﮫ": "o", + "ﮪ": "o", + "ہ": "o", + "ﮨ": "o", + "ﮩ": "o", + "ﮧ": "o", + "ﮦ": "o", + "ە": "o", + "ഠ": "o", + "ဝ": "o", + "𐓪": "o", + "𑣈": "o", + "𑣗": "o", + "𐐬": "o", + "߀": "O", + "০": "O", + "୦": "O", + "〇": "O", + "𑓐": "O", + "𑣠": "O", + "𝟎": "O", + "𝟘": "O", + "𝟢": "O", + "𝟬": "O", + "𝟶": "O", + "🯰": "O", + "O": "O", + "𝐎": "O", + "𝑂": "O", + "𝑶": "O", + "𝒪": "O", + "𝓞": "O", + "𝔒": "O", + "𝕆": "O", + "𝕺": "O", + "𝖮": "O", + "𝗢": "O", + "𝘖": "O", + "𝙊": "O", + "𝙾": "O", + "Ο": "O", + "𝚶": "O", + "𝛰": "O", + "𝜪": "O", + "𝝤": "O", + "𝞞": "O", + "Ⲟ": "O", + "О": "O", + "Օ": "O", + "ⵔ": "O", + "ዐ": "O", + "ଠ": "O", + "𐓂": "O", + "ꓳ": "O", + "𑢵": "O", + "𐊒": "O", + "𐊫": "O", + "𐐄": "O", + "𐔖": "O", + "⁰": "º", + "ᵒ": "º", + "ǒ": "ŏ", + "Ǒ": "Ŏ", + "ۿ": "ô", + "Ő": "Ö", + "ø": "o̸", + "ꬾ": "o̸", + "Ø": "O̸", + "ⵁ": "O̸", + "Ǿ": "Ó̸", + "ɵ": "o̵", + "ꝋ": "o̵", + "ө": "o̵", + "ѳ": "o̵", + "ꮎ": "o̵", + "ꮻ": "o̵", + "⊖": "O̵", + "⊝": "O̵", + "⍬": "O̵", + "𝈚": "O̵", + "🜔": "O̵", + "Ɵ": "O̵", + "Ꝋ": "O̵", + "θ": "O̵", + "ϑ": "O̵", + "𝛉": "O̵", + "𝛝": "O̵", + "𝜃": "O̵", + "𝜗": "O̵", + "𝜽": "O̵", + "𝝑": "O̵", + "𝝷": "O̵", + "𝞋": "O̵", + "𝞱": "O̵", + "𝟅": "O̵", + "Θ": "O̵", + "ϴ": "O̵", + "𝚯": "O̵", + "𝚹": "O̵", + "𝛩": "O̵", + "𝛳": "O̵", + "𝜣": "O̵", + "𝜭": "O̵", + "𝝝": "O̵", + "𝝧": "O̵", + "𝞗": "O̵", + "𝞡": "O̵", + "Ө": "O̵", + "Ѳ": "O̵", + "ⴱ": "O̵", + "Ꮎ": "O̵", + "Ꮻ": "O̵", + "ꭴ": "ơ", + "ﳙ": "oٰ", + "🄁": "O,", + "🄀": "O.", + "ơ": "o'", + "Ơ": "O'", + "Ꭴ": "O'", + "%": "º/₀", + "٪": "º/₀", + "⁒": "º/₀", + "‰": "º/₀₀", + "؉": "º/₀₀", + "‱": "º/₀₀₀", + "؊": "º/₀₀₀", + "œ": "oe", + "Œ": "OE", + "ɶ": "oᴇ", + "∞": "oo", + "ꝏ": "oo", + "ꚙ": "oo", + "Ꝏ": "OO", + "Ꚙ": "OO", + "ﳗ": "oج", + "ﱑ": "oج", + "ﳘ": "oم", + "ﱒ": "oم", + "ﶓ": "oمج", + "ﶔ": "oمم", + "ﱓ": "oى", + "ﱔ": "oى", + "ൟ": "oരo", + "တ": "oာ", + "㍘": "O点", + "ↄ": "ɔ", + "ᴐ": "ɔ", + "ͻ": "ɔ", + "𐑋": "ɔ", + "Ↄ": "Ɔ", + "Ͻ": "Ɔ", + "ꓛ": "Ɔ", + "𐐣": "Ɔ", + "ꬿ": "ɔ̸", + "ꭢ": "ɔe", + "𐐿": "ɷ", + "⍴": "p", + "p": "p", + "𝐩": "p", + "𝑝": "p", + "𝒑": "p", + "𝓅": "p", + "𝓹": "p", + "𝔭": "p", + "𝕡": "p", + "𝖕": "p", + "𝗉": "p", + "𝗽": "p", + "𝘱": "p", + "𝙥": "p", + "𝚙": "p", + "ρ": "p", + "ϱ": "p", + "𝛒": "p", + "𝛠": "p", + "𝜌": "p", + "𝜚": "p", + "𝝆": "p", + "𝝔": "p", + "𝞀": "p", + "𝞎": "p", + "𝞺": "p", + "𝟈": "p", + "ⲣ": "p", + "р": "p", + "P": "P", + "ℙ": "P", + "𝐏": "P", + "𝑃": "P", + "𝑷": "P", + "𝒫": "P", + "𝓟": "P", + "𝔓": "P", + "𝕻": "P", + "𝖯": "P", + "𝗣": "P", + "𝘗": "P", + "𝙋": "P", + "𝙿": "P", + "Ρ": "P", + "𝚸": "P", + "𝛲": "P", + "𝜬": "P", + "𝝦": "P", + "𝞠": "P", + "Ⲣ": "P", + "Р": "P", + "Ꮲ": "P", + "ᑭ": "P", + "ꓑ": "P", + "𐊕": "P", + "ƥ": "p̔", + "ᵽ": "p̵", + "ᑷ": "p·", + "ᒆ": "P'", + "ᴩ": "ᴘ", + "ꮲ": "ᴘ", + "φ": "ɸ", + "ϕ": "ɸ", + "𝛗": "ɸ", + "𝛟": "ɸ", + "𝜑": "ɸ", + "𝜙": "ɸ", + "𝝋": "ɸ", + "𝝓": "ɸ", + "𝞅": "ɸ", + "𝞍": "ɸ", + "𝞿": "ɸ", + "𝟇": "ɸ", + "ⲫ": "ɸ", + "ф": "ɸ", + "𝐪": "q", + "𝑞": "q", + "𝒒": "q", + "𝓆": "q", + "𝓺": "q", + "𝔮": "q", + "𝕢": "q", + "𝖖": "q", + "𝗊": "q", + "𝗾": "q", + "𝘲": "q", + "𝙦": "q", + "𝚚": "q", + "ԛ": "q", + "գ": "q", + "զ": "q", + "ℚ": "Q", + "𝐐": "Q", + "𝑄": "Q", + "𝑸": "Q", + "𝒬": "Q", + "𝓠": "Q", + "𝔔": "Q", + "𝕼": "Q", + "𝖰": "Q", + "𝗤": "Q", + "𝘘": "Q", + "𝙌": "Q", + "𝚀": "Q", + "ⵕ": "Q", + "ʠ": "q̔", + "🜀": "QE", + "ᶐ": "ɋ", + "ᴋ": "ĸ", + "κ": "ĸ", + "ϰ": "ĸ", + "𝛋": "ĸ", + "𝛞": "ĸ", + "𝜅": "ĸ", + "𝜘": "ĸ", + "𝜿": "ĸ", + "𝝒": "ĸ", + "𝝹": "ĸ", + "𝞌": "ĸ", + "𝞳": "ĸ", + "𝟆": "ĸ", + "ⲕ": "ĸ", + "к": "ĸ", + "ꮶ": "ĸ", + "қ": "ĸ̩", + "ҟ": "ĸ̵", + "𝐫": "r", + "𝑟": "r", + "𝒓": "r", + "𝓇": "r", + "𝓻": "r", + "𝔯": "r", + "𝕣": "r", + "𝖗": "r", + "𝗋": "r", + "𝗿": "r", + "𝘳": "r", + "𝙧": "r", + "𝚛": "r", + "ꭇ": "r", + "ꭈ": "r", + "ᴦ": "r", + "ⲅ": "r", + "г": "r", + "ꮁ": "r", + "𝈖": "R", + "ℛ": "R", + "ℜ": "R", + "ℝ": "R", + "𝐑": "R", + "𝑅": "R", + "𝑹": "R", + "𝓡": "R", + "𝕽": "R", + "𝖱": "R", + "𝗥": "R", + "𝘙": "R", + "𝙍": "R", + "𝚁": "R", + "Ʀ": "R", + "Ꭱ": "R", + "Ꮢ": "R", + "𐒴": "R", + "ᖇ": "R", + "ꓣ": "R", + "𖼵": "R", + "ɽ": "r̨", + "ɼ": "r̩", + "ɍ": "r̵", + "ғ": "r̵", + "ᵲ": "r̴", + "ґ": "r'", + "𑣣": "rn", + "m": "rn", + "ⅿ": "rn", + "𝐦": "rn", + "𝑚": "rn", + "𝒎": "rn", + "𝓂": "rn", + "𝓶": "rn", + "𝔪": "rn", + "𝕞": "rn", + "𝖒": "rn", + "𝗆": "rn", + "𝗺": "rn", + "𝘮": "rn", + "𝙢": "rn", + "𝚖": "rn", + "𑜀": "rn", + "₥": "rn̸", + "ɱ": "rn̦", + "ᵯ": "rn̴", + "₨": "Rs", + "ꭱ": "ʀ", + "ꮢ": "ʀ", + "я": "ᴙ", + "ᵳ": "ɾ̴", + "℩": "ɿ", + "s": "s", + "𝐬": "s", + "𝑠": "s", + "𝒔": "s", + "𝓈": "s", + "𝓼": "s", + "𝔰": "s", + "𝕤": "s", + "𝖘": "s", + "𝗌": "s", + "𝘀": "s", + "𝘴": "s", + "𝙨": "s", + "𝚜": "s", + "ꜱ": "s", + "ƽ": "s", + "ѕ": "s", + "ꮪ": "s", + "𑣁": "s", + "𐑈": "s", + "S": "S", + "𝐒": "S", + "𝑆": "S", + "𝑺": "S", + "𝒮": "S", + "𝓢": "S", + "𝔖": "S", + "𝕊": "S", + "𝕾": "S", + "𝖲": "S", + "𝗦": "S", + "𝘚": "S", + "𝙎": "S", + "𝚂": "S", + "Ѕ": "S", + "Տ": "S", + "Ꮥ": "S", + "Ꮪ": "S", + "ꓢ": "S", + "𖼺": "S", + "𐊖": "S", + "𐐠": "S", + "ʂ": "s̨", + "ᵴ": "s̴", + "ꞵ": "ß", + "β": "ß", + "ϐ": "ß", + "𝛃": "ß", + "𝛽": "ß", + "𝜷": "ß", + "𝝱": "ß", + "𝞫": "ß", + "Ᏸ": "ß", + "🝜": "sss", + "st": "st", + "∫": "ʃ", + "ꭍ": "ʃ", + "∑": "Ʃ", + "⅀": "Ʃ", + "Σ": "Ʃ", + "𝚺": "Ʃ", + "𝛴": "Ʃ", + "𝜮": "Ʃ", + "𝝨": "Ʃ", + "𝞢": "Ʃ", + "ⵉ": "Ʃ", + "∬": "ʃʃ", + "∭": "ʃʃʃ", + "⨌": "ʃʃʃʃ", + "𝐭": "t", + "𝑡": "t", + "𝒕": "t", + "𝓉": "t", + "𝓽": "t", + "𝔱": "t", + "𝕥": "t", + "𝖙": "t", + "𝗍": "t", + "𝘁": "t", + "𝘵": "t", + "𝙩": "t", + "𝚝": "t", + "⊤": "T", + "⟙": "T", + "🝨": "T", + "T": "T", + "𝐓": "T", + "𝑇": "T", + "𝑻": "T", + "𝒯": "T", + "𝓣": "T", + "𝔗": "T", + "𝕋": "T", + "𝕿": "T", + "𝖳": "T", + "𝗧": "T", + "𝘛": "T", + "𝙏": "T", + "𝚃": "T", + "Τ": "T", + "𝚻": "T", + "𝛵": "T", + "𝜯": "T", + "𝝩": "T", + "𝞣": "T", + "Ⲧ": "T", + "Т": "T", + "Ꭲ": "T", + "ꓔ": "T", + "𖼊": "T", + "𑢼": "T", + "𐊗": "T", + "𐊱": "T", + "𐌕": "T", + "ƭ": "t̔", + "⍡": "T̈", + "Ⱦ": "T̸", + "Ț": "Ţ", + "Ʈ": "T̨", + "Ҭ": "T̩", + "₮": "T⃫", + "ŧ": "t̵", + "Ŧ": "T̵", + "ᵵ": "t̴", + "Ⴀ": "Ꞇ", + "Ꜩ": "T3", + "ʨ": "tɕ", + "℡": "TEL", + "ꝷ": "tf", + "ʦ": "ts", + "ʧ": "tʃ", + "ꜩ": "tȝ", + "τ": "ᴛ", + "𝛕": "ᴛ", + "𝜏": "ᴛ", + "𝝉": "ᴛ", + "𝞃": "ᴛ", + "𝞽": "ᴛ", + "т": "ᴛ", + "ꭲ": "ᴛ", + "ҭ": "ᴛ̩", + "ţ": "ƫ", + "ț": "ƫ", + "Ꮏ": "ƫ", + "𝐮": "u", + "𝑢": "u", + "𝒖": "u", + "𝓊": "u", + "𝓾": "u", + "𝔲": "u", + "𝕦": "u", + "𝖚": "u", + "𝗎": "u", + "𝘂": "u", + "𝘶": "u", + "𝙪": "u", + "𝚞": "u", + "ꞟ": "u", + "ᴜ": "u", + "ꭎ": "u", + "ꭒ": "u", + "ʋ": "u", + "υ": "u", + "𝛖": "u", + "𝜐": "u", + "𝝊": "u", + "𝞄": "u", + "𝞾": "u", + "ս": "u", + "𐓶": "u", + "𑣘": "u", + "∪": "U", + "⋃": "U", + "𝐔": "U", + "𝑈": "U", + "𝑼": "U", + "𝒰": "U", + "𝓤": "U", + "𝔘": "U", + "𝕌": "U", + "𝖀": "U", + "𝖴": "U", + "𝗨": "U", + "𝘜": "U", + "𝙐": "U", + "𝚄": "U", + "Ս": "U", + "ሀ": "U", + "𐓎": "U", + "ᑌ": "U", + "ꓴ": "U", + "𖽂": "U", + "𑢸": "U", + "ǔ": "ŭ", + "Ǔ": "Ŭ", + "ᵾ": "u̵", + "ꮜ": "u̵", + "Ʉ": "U̵", + "Ꮜ": "U̵", + "ᑘ": "U·", + "ᑧ": "U'", + "ᵫ": "ue", + "ꭣ": "uo", + "ṃ": "ꭑ", + "պ": "ɰ", + "ሣ": "ɰ", + "℧": "Ʊ", + "ᘮ": "Ʊ", + "ᘴ": "Ʊ", + "ᵿ": "ʊ̵", + "∨": "v", + "⋁": "v", + "v": "v", + "ⅴ": "v", + "𝐯": "v", + "𝑣": "v", + "𝒗": "v", + "𝓋": "v", + "𝓿": "v", + "𝔳": "v", + "𝕧": "v", + "𝖛": "v", + "𝗏": "v", + "𝘃": "v", + "𝘷": "v", + "𝙫": "v", + "𝚟": "v", + "ᴠ": "v", + "ν": "v", + "𝛎": "v", + "𝜈": "v", + "𝝂": "v", + "𝝼": "v", + "𝞶": "v", + "ѵ": "v", + "ט": "v", + "𑜆": "v", + "ꮩ": "v", + "𑣀": "v", + "𝈍": "V", + "٧": "V", + "۷": "V", + "Ⅴ": "V", + "𝐕": "V", + "𝑉": "V", + "𝑽": "V", + "𝒱": "V", + "𝓥": "V", + "𝔙": "V", + "𝕍": "V", + "𝖁": "V", + "𝖵": "V", + "𝗩": "V", + "𝘝": "V", + "𝙑": "V", + "𝚅": "V", + "Ѵ": "V", + "ⴸ": "V", + "Ꮩ": "V", + "ᐯ": "V", + "ꛟ": "V", + "ꓦ": "V", + "𖼈": "V", + "𑢠": "V", + "𐔝": "V", + "𐆗": "V̵", + "ᐻ": "V·", + "🝬": "VB", + "ⅵ": "vi", + "ⅶ": "vii", + "ⅷ": "viii", + "Ⅵ": "Vl", + "Ⅶ": "Vll", + "Ⅷ": "Vlll", + "🜈": "Vᷤ", + "ᴧ": "ʌ", + "𐓘": "ʌ", + "٨": "Ʌ", + "۸": "Ʌ", + "Λ": "Ʌ", + "𝚲": "Ʌ", + "𝛬": "Ʌ", + "𝜦": "Ʌ", + "𝝠": "Ʌ", + "𝞚": "Ʌ", + "Л": "Ʌ", + "ⴷ": "Ʌ", + "𐒰": "Ʌ", + "ᐱ": "Ʌ", + "ꛎ": "Ʌ", + "ꓥ": "Ʌ", + "𖼽": "Ʌ", + "𐊍": "Ʌ", + "Ӆ": "Ʌ̦", + "ᐽ": "Ʌ·", + "ɯ": "w", + "𝐰": "w", + "𝑤": "w", + "𝒘": "w", + "𝓌": "w", + "𝔀": "w", + "𝔴": "w", + "𝕨": "w", + "𝖜": "w", + "𝗐": "w", + "𝘄": "w", + "𝘸": "w", + "𝙬": "w", + "𝚠": "w", + "ᴡ": "w", + "ѡ": "w", + "ԝ": "w", + "ա": "w", + "𑜊": "w", + "𑜎": "w", + "𑜏": "w", + "ꮃ": "w", + "𑣯": "W", + "𑣦": "W", + "𝐖": "W", + "𝑊": "W", + "𝑾": "W", + "𝒲": "W", + "𝓦": "W", + "𝔚": "W", + "𝕎": "W", + "𝖂": "W", + "𝖶": "W", + "𝗪": "W", + "𝘞": "W", + "𝙒": "W", + "𝚆": "W", + "Ԝ": "W", + "Ꮃ": "W", + "Ꮤ": "W", + "ꓪ": "W", + "ѽ": "w҆҇", + "𑓅": "ẇ", + "₩": "W̵", + "ꝡ": "w̦", + "ᴍ": "ʍ", + "м": "ʍ", + "ꮇ": "ʍ", + "ӎ": "ʍ̦", + "᙮": "x", + "×": "x", + "⤫": "x", + "⤬": "x", + "⨯": "x", + "x": "x", + "ⅹ": "x", + "𝐱": "x", + "𝑥": "x", + "𝒙": "x", + "𝓍": "x", + "𝔁": "x", + "𝔵": "x", + "𝕩": "x", + "𝖝": "x", + "𝗑": "x", + "𝘅": "x", + "𝘹": "x", + "𝙭": "x", + "𝚡": "x", + "х": "x", + "ᕁ": "x", + "ᕽ": "x", + "ⷯ": "ͯ", + "᙭": "X", + "╳": "X", + "𐌢": "X", + "𑣬": "X", + "X": "X", + "Ⅹ": "X", + "𝐗": "X", + "𝑋": "X", + "𝑿": "X", + "𝒳": "X", + "𝓧": "X", + "𝔛": "X", + "𝕏": "X", + "𝖃": "X", + "𝖷": "X", + "𝗫": "X", + "𝘟": "X", + "𝙓": "X", + "𝚇": "X", + "Ꭓ": "X", + "Χ": "X", + "𝚾": "X", + "𝛸": "X", + "𝜲": "X", + "𝝬": "X", + "𝞦": "X", + "Ⲭ": "X", + "Х": "X", + "ⵝ": "X", + "ᚷ": "X", + "ꓫ": "X", + "𐊐": "X", + "𐊴": "X", + "𐌗": "X", + "𐔧": "X", + "⨰": "ẋ", + "Ҳ": "X̩", + "𐆖": "X̵", + "ⅺ": "xi", + "ⅻ": "xii", + "Ⅺ": "Xl", + "Ⅻ": "Xll", + "ɣ": "y", + "ᶌ": "y", + "y": "y", + "𝐲": "y", + "𝑦": "y", + "𝒚": "y", + "𝓎": "y", + "𝔂": "y", + "𝔶": "y", + "𝕪": "y", + "𝖞": "y", + "𝗒": "y", + "𝘆": "y", + "𝘺": "y", + "𝙮": "y", + "𝚢": "y", + "ʏ": "y", + "ỿ": "y", + "ꭚ": "y", + "γ": "y", + "ℽ": "y", + "𝛄": "y", + "𝛾": "y", + "𝜸": "y", + "𝝲": "y", + "𝞬": "y", + "у": "y", + "ү": "y", + "ყ": "y", + "𑣜": "y", + "Y": "Y", + "𝐘": "Y", + "𝑌": "Y", + "𝒀": "Y", + "𝒴": "Y", + "𝓨": "Y", + "𝔜": "Y", + "𝕐": "Y", + "𝖄": "Y", + "𝖸": "Y", + "𝗬": "Y", + "𝘠": "Y", + "𝙔": "Y", + "𝚈": "Y", + "Υ": "Y", + "ϒ": "Y", + "𝚼": "Y", + "𝛶": "Y", + "𝜰": "Y", + "𝝪": "Y", + "𝞤": "Y", + "Ⲩ": "Y", + "У": "Y", + "Ү": "Y", + "Ꭹ": "Y", + "Ꮍ": "Y", + "ꓬ": "Y", + "𖽃": "Y", + "𑢤": "Y", + "𐊲": "Y", + "ƴ": "y̔", + "ɏ": "y̵", + "ұ": "y̵", + "¥": "Y̵", + "Ɏ": "Y̵", + "Ұ": "Y̵", + "ʒ": "ȝ", + "ꝫ": "ȝ", + "ⳍ": "ȝ", + "ӡ": "ȝ", + "ჳ": "ȝ", + "𝐳": "z", + "𝑧": "z", + "𝒛": "z", + "𝓏": "z", + "𝔃": "z", + "𝔷": "z", + "𝕫": "z", + "𝖟": "z", + "𝗓": "z", + "𝘇": "z", + "𝘻": "z", + "𝙯": "z", + "𝚣": "z", + "ᴢ": "z", + "ꮓ": "z", + "𑣄": "z", + "𐋵": "Z", + "𑣥": "Z", + "Z": "Z", + "ℤ": "Z", + "ℨ": "Z", + "𝐙": "Z", + "𝑍": "Z", + "𝒁": "Z", + "𝒵": "Z", + "𝓩": "Z", + "𝖅": "Z", + "𝖹": "Z", + "𝗭": "Z", + "𝘡": "Z", + "𝙕": "Z", + "𝚉": "Z", + "Ζ": "Z", + "𝚭": "Z", + "𝛧": "Z", + "𝜡": "Z", + "𝝛": "Z", + "𝞕": "Z", + "Ꮓ": "Z", + "ꓜ": "Z", + "𑢩": "Z", + "ʐ": "z̨", + "ƶ": "z̵", + "Ƶ": "Z̵", + "ȥ": "z̦", + "Ȥ": "Z̦", + "ᵶ": "z̴", + "ƿ": "þ", + "ϸ": "þ", + "Ϸ": "Þ", + "𐓄": "Þ", + "⁹": "ꝰ", + "ᴤ": "ƨ", + "ϩ": "ƨ", + "ꙅ": "ƨ", + "ь": "ƅ", + "ꮟ": "ƅ", + "ы": "ƅi", + "ꭾ": "ɂ", + "ˤ": "ˁ", + "ꛍ": "ʡ", + "⊙": "ʘ", + "☉": "ʘ", + "⨀": "ʘ", + "Ꙩ": "ʘ", + "ⵙ": "ʘ", + "𐓃": "ʘ", + "ℾ": "Γ", + "𝚪": "Γ", + "𝛤": "Γ", + "𝜞": "Γ", + "𝝘": "Γ", + "𝞒": "Γ", + "Ⲅ": "Γ", + "Г": "Γ", + "Ꮁ": "Γ", + "ᒥ": "Γ", + "𖼇": "Γ", + "Ғ": "Γ̵", + "ᒯ": "Γ·", + "Ґ": "Γ'", + "∆": "Δ", + "△": "Δ", + "🜂": "Δ", + "𝚫": "Δ", + "𝛥": "Δ", + "𝜟": "Δ", + "𝝙": "Δ", + "𝞓": "Δ", + "Ⲇ": "Δ", + "ⵠ": "Δ", + "ᐃ": "Δ", + "𖼚": "Δ", + "𐊅": "Δ", + "𐊣": "Δ", + "⍙": "Δ̲", + "ᐏ": "Δ·", + "ᐬ": "Δᐠ", + "𝟋": "ϝ", + "𝛇": "ζ", + "𝜁": "ζ", + "𝜻": "ζ", + "𝝵": "ζ", + "𝞯": "ζ", + "ⳤ": "ϗ", + "𝛌": "λ", + "𝜆": "λ", + "𝝀": "λ", + "𝝺": "λ", + "𝞴": "λ", + "Ⲗ": "λ", + "𐓛": "λ", + "µ": "μ", + "𝛍": "μ", + "𝜇": "μ", + "𝝁": "μ", + "𝝻": "μ", + "𝞵": "μ", + "𝛏": "ξ", + "𝜉": "ξ", + "𝝃": "ξ", + "𝝽": "ξ", + "𝞷": "ξ", + "𝚵": "Ξ", + "𝛯": "Ξ", + "𝜩": "Ξ", + "𝝣": "Ξ", + "𝞝": "Ξ", + "ϖ": "π", + "ℼ": "π", + "𝛑": "π", + "𝛡": "π", + "𝜋": "π", + "𝜛": "π", + "𝝅": "π", + "𝝕": "π", + "𝝿": "π", + "𝞏": "π", + "𝞹": "π", + "𝟉": "π", + "ᴨ": "π", + "п": "π", + "∏": "Π", + "ℿ": "Π", + "𝚷": "Π", + "𝛱": "Π", + "𝜫": "Π", + "𝝥": "Π", + "𝞟": "Π", + "Ⲡ": "Π", + "П": "Π", + "ꛛ": "Π", + "𐊭": "Ϙ", + "𐌒": "Ϙ", + "ϛ": "ς", + "𝛓": "ς", + "𝜍": "ς", + "𝝇": "ς", + "𝞁": "ς", + "𝞻": "ς", + "𝚽": "Φ", + "𝛷": "Φ", + "𝜱": "Φ", + "𝝫": "Φ", + "𝞥": "Φ", + "Ⲫ": "Φ", + "Ф": "Φ", + "Փ": "Φ", + "ቀ": "Φ", + "ᛰ": "Φ", + "𐊳": "Φ", + "ꭓ": "χ", + "ꭕ": "χ", + "𝛘": "χ", + "𝜒": "χ", + "𝝌": "χ", + "𝞆": "χ", + "𝟀": "χ", + "ⲭ": "χ", + "𝛙": "ψ", + "𝜓": "ψ", + "𝝍": "ψ", + "𝞇": "ψ", + "𝟁": "ψ", + "ѱ": "ψ", + "𐓹": "ψ", + "𝚿": "Ψ", + "𝛹": "Ψ", + "𝜳": "Ψ", + "𝝭": "Ψ", + "𝞧": "Ψ", + "Ⲯ": "Ψ", + "Ѱ": "Ψ", + "𐓑": "Ψ", + "ᛘ": "Ψ", + "𐊵": "Ψ", + "⍵": "ω", + "ꞷ": "ω", + "𝛚": "ω", + "𝜔": "ω", + "𝝎": "ω", + "𝞈": "ω", + "𝟂": "ω", + "ⲱ": "ω", + "ꙍ": "ω", + "Ω": "Ω", + "𝛀": "Ω", + "𝛺": "Ω", + "𝜴": "Ω", + "𝝮": "Ω", + "𝞨": "Ω", + "ᘯ": "Ω", + "ᘵ": "Ω", + "𐊶": "Ω", + "⍹": "ω̲", + "ώ": "ῴ", + "☰": "Ⲷ", + "Ⳝ": "Ϭ", + "җ": "ж̩", + "Җ": "Ж̩", + "𝈋": "И", + "Ͷ": "И", + "ꚡ": "И", + "𐐥": "И", + "Й": "Ѝ", + "Ҋ": "Ѝ̦", + "ѝ": "й", + "ҋ": "й̦", + "𐒼": "Ӄ", + "ᴫ": "л", + "ӆ": "л̦", + "ꭠ": "љ", + "𐓫": "ꙩ", + "ᷮ": "ⷬ", + "𐓍": "Ћ", + "𝈂": "Ӿ", + "𝈢": "Ѡ", + "Ꮗ": "Ѡ", + "ᗯ": "Ѡ", + "Ѽ": "Ѡ҆҇", + "ᣭ": "Ѡ·", + "Ꞷ": "Ꙍ", + "ӌ": "ҷ", + "Ӌ": "Ҷ", + "Ҿ": "Ҽ̨", + "ⲽ": "ш", + "Ⲽ": "Ш", + "Ꙑ": "Ъl", + "℈": "Э", + "🜁": "Ꙙ", + "𖼜": "Ꙙ", + "ꦒ": "ⰿ", + "և": "եւ", + "ኔ": "ձ", + "ﬔ": "մե", + "ﬕ": "մի", + "ﬗ": "մխ", + "ﬓ": "մն", + "∩": "Ո", + "⋂": "Ո", + "𝉅": "Ո", + "በ": "Ո", + "ᑎ": "Ո", + "ꓵ": "Ո", + "ᑚ": "Ո·", + "ᑨ": "Ո'", + "ﬖ": "վն", + "₽": "Ք", + "˓": "ՙ", + "ʿ": "ՙ", + "ℵ": "א", + "ﬡ": "א", + "אָ": "אַ", + "אּ": "אַ", + "ﭏ": "אל", + "ℶ": "ב", + "ℷ": "ג", + "ℸ": "ד", + "ﬢ": "ד", + "ﬣ": "ה", + "יּ": "יִ", + "ﬤ": "כ", + "ﬥ": "ל", + "ﬦ": "ם", + "ﬠ": "ע", + "ﬧ": "ר", + "שׂ": "שׁ", + "שּ": "שׁ", + "שּׂ": "שּׁ", + "ﬨ": "ת", + "ﺀ": "ء", + "۽": "ء͈", + "ﺂ": "آ", + "ﺁ": "آ", + "ﭑ": "ٱ", + "ﭐ": "ٱ", + "𞸁": "ب", + "𞸡": "ب", + "𞹡": "ب", + "𞺁": "ب", + "𞺡": "ب", + "ﺑ": "ب", + "ﺒ": "ب", + "ﺐ": "ب", + "ﺏ": "ب", + "ݑ": "بۛ", + "ࢶ": "بۢ", + "ࢡ": "بٔ", + "ﲠ": "بo", + "ﳢ": "بo", + "ﲜ": "بج", + "ﰅ": "بج", + "ﲝ": "بح", + "ﰆ": "بح", + "ﷂ": "بحى", + "ﲞ": "بخ", + "ﰇ": "بخ", + "ﳒ": "بخ", + "ﱋ": "بخ", + "ﶞ": "بخى", + "ﱪ": "بر", + "ﱫ": "بز", + "ﲟ": "بم", + "ﳡ": "بم", + "ﱬ": "بم", + "ﰈ": "بم", + "ﱭ": "بن", + "ﱮ": "بى", + "ﰉ": "بى", + "ﱯ": "بى", + "ﰊ": "بى", + "ﭔ": "ٻ", + "ﭕ": "ٻ", + "ﭓ": "ٻ", + "ﭒ": "ٻ", + "ې": "ٻ", + "ﯦ": "ٻ", + "ﯧ": "ٻ", + "ﯥ": "ٻ", + "ﯤ": "ٻ", + "ﭜ": "ڀ", + "ﭝ": "ڀ", + "ﭛ": "ڀ", + "ﭚ": "ڀ", + "ࢩ": "ݔ", + "ݧ": "ݔ", + "⍥": "ة", + "ö": "ة", + "ﺔ": "ة", + "ﺓ": "ة", + "ۃ": "ة", + "𞸕": "ت", + "𞸵": "ت", + "𞹵": "ت", + "𞺕": "ت", + "𞺵": "ت", + "ﺗ": "ت", + "ﺘ": "ت", + "ﺖ": "ت", + "ﺕ": "ت", + "ﲥ": "تo", + "ﳤ": "تo", + "ﲡ": "تج", + "ﰋ": "تج", + "ﵐ": "تجم", + "ﶠ": "تجى", + "ﶟ": "تجى", + "ﲢ": "تح", + "ﰌ": "تح", + "ﵒ": "تحج", + "ﵑ": "تحج", + "ﵓ": "تحم", + "ﲣ": "تخ", + "ﰍ": "تخ", + "ﵔ": "تخم", + "ﶢ": "تخى", + "ﶡ": "تخى", + "ﱰ": "تر", + "ﱱ": "تز", + "ﲤ": "تم", + "ﳣ": "تم", + "ﱲ": "تم", + "ﰎ": "تم", + "ﵕ": "تمج", + "ﵖ": "تمح", + "ﵗ": "تمخ", + "ﶤ": "تمى", + "ﶣ": "تمى", + "ﱳ": "تن", + "ﱴ": "تى", + "ﰏ": "تى", + "ﱵ": "تى", + "ﰐ": "تى", + "ﭠ": "ٺ", + "ﭡ": "ٺ", + "ﭟ": "ٺ", + "ﭞ": "ٺ", + "ﭤ": "ٿ", + "ﭥ": "ٿ", + "ﭣ": "ٿ", + "ﭢ": "ٿ", + "𞸂": "ج", + "𞸢": "ج", + "𞹂": "ج", + "𞹢": "ج", + "𞺂": "ج", + "𞺢": "ج", + "ﺟ": "ج", + "ﺠ": "ج", + "ﺞ": "ج", + "ﺝ": "ج", + "ﲧ": "جح", + "ﰕ": "جح", + "ﶦ": "جحى", + "ﶾ": "جحى", + "ﷻ": "جل جلlلo", + "ﲨ": "جم", + "ﰖ": "جم", + "ﵙ": "جمح", + "ﵘ": "جمح", + "ﶧ": "جمى", + "ﶥ": "جمى", + "ﴝ": "جى", + "ﴁ": "جى", + "ﴞ": "جى", + "ﴂ": "جى", + "ﭸ": "ڃ", + "ﭹ": "ڃ", + "ﭷ": "ڃ", + "ﭶ": "ڃ", + "ﭴ": "ڄ", + "ﭵ": "ڄ", + "ﭳ": "ڄ", + "ﭲ": "ڄ", + "ﭼ": "چ", + "ﭽ": "چ", + "ﭻ": "چ", + "ﭺ": "چ", + "ﮀ": "ڇ", + "ﮁ": "ڇ", + "ﭿ": "ڇ", + "ﭾ": "ڇ", + "𞸇": "ح", + "𞸧": "ح", + "𞹇": "ح", + "𞹧": "ح", + "𞺇": "ح", + "𞺧": "ح", + "ﺣ": "ح", + "ﺤ": "ح", + "ﺢ": "ح", + "ﺡ": "ح", + "څ": "حۛ", + "ځ": "حٔ", + "ݲ": "حٔ", + "ﲩ": "حج", + "ﰗ": "حج", + "ﶿ": "حجى", + "ﲪ": "حم", + "ﰘ": "حم", + "ﵛ": "حمى", + "ﵚ": "حمى", + "ﴛ": "حى", + "ﳿ": "حى", + "ﴜ": "حى", + "ﴀ": "حى", + "𞸗": "خ", + "𞸷": "خ", + "𞹗": "خ", + "𞹷": "خ", + "𞺗": "خ", + "𞺷": "خ", + "ﺧ": "خ", + "ﺨ": "خ", + "ﺦ": "خ", + "ﺥ": "خ", + "ﲫ": "خج", + "ﰙ": "خج", + "ﰚ": "خح", + "ﲬ": "خم", + "ﰛ": "خم", + "ﴟ": "خى", + "ﴃ": "خى", + "ﴠ": "خى", + "ﴄ": "خى", + "𐋡": "د", + "𞸃": "د", + "𞺃": "د", + "𞺣": "د", + "ﺪ": "د", + "ﺩ": "د", + "ڈ": "دؕ", + "ﮉ": "دؕ", + "ﮈ": "دؕ", + "ڎ": "دۛ", + "ﮇ": "دۛ", + "ﮆ": "دۛ", + "ۮ": "د̂", + "ࢮ": "د̤̣", + "𞸘": "ذ", + "𞺘": "ذ", + "𞺸": "ذ", + "ﺬ": "ذ", + "ﺫ": "ذ", + "ﱛ": "ذٰ", + "ڋ": "ڊؕ", + "ﮅ": "ڌ", + "ﮄ": "ڌ", + "ﮃ": "ڍ", + "ﮂ": "ڍ", + "𞸓": "ر", + "𞺓": "ر", + "𞺳": "ر", + "ﺮ": "ر", + "ﺭ": "ر", + "ڑ": "رؕ", + "ﮍ": "رؕ", + "ﮌ": "رؕ", + "ژ": "رۛ", + "ﮋ": "رۛ", + "ﮊ": "رۛ", + "ڒ": "ر̆", + "ࢹ": "ر̆̇", + "ۯ": "ر̂", + "ݬ": "رٔ", + "ﱜ": "رٰ", + "ﷶ": "رسول", + "﷼": "رىlل", + "𞸆": "ز", + "𞺆": "ز", + "𞺦": "ز", + "ﺰ": "ز", + "ﺯ": "ز", + "ࢲ": "ز̂", + "ݱ": "ڗؕ", + "𞸎": "س", + "𞸮": "س", + "𞹎": "س", + "𞹮": "س", + "𞺎": "س", + "𞺮": "س", + "ﺳ": "س", + "ﺴ": "س", + "ﺲ": "س", + "ﺱ": "س", + "ش": "سۛ", + "𞸔": "سۛ", + "𞸴": "سۛ", + "𞹔": "سۛ", + "𞹴": "سۛ", + "𞺔": "سۛ", + "𞺴": "سۛ", + "ﺷ": "سۛ", + "ﺸ": "سۛ", + "ﺶ": "سۛ", + "ﺵ": "سۛ", + "ݾ": "س̂", + "ﴱ": "سo", + "ﳨ": "سo", + "ﴲ": "سۛo", + "ﳪ": "سۛo", + "ﲭ": "سج", + "ﴴ": "سج", + "ﰜ": "سج", + "ﴭ": "سۛج", + "ﴷ": "سۛج", + "ﴥ": "سۛج", + "ﴉ": "سۛج", + "ﵝ": "سجح", + "ﵞ": "سجى", + "ﵩ": "سۛجى", + "ﲮ": "سح", + "ﴵ": "سح", + "ﰝ": "سح", + "ﴮ": "سۛح", + "ﴸ": "سۛح", + "ﴦ": "سۛح", + "ﴊ": "سۛح", + "ﵜ": "سحج", + "ﵨ": "سۛحم", + "ﵧ": "سۛحم", + "ﶪ": "سۛحى", + "ﲯ": "سخ", + "ﴶ": "سخ", + "ﰞ": "سخ", + "ﴯ": "سۛخ", + "ﴹ": "سۛخ", + "ﴧ": "سۛخ", + "ﴋ": "سۛخ", + "ﶨ": "سخى", + "ﷆ": "سخى", + "ﴪ": "سر", + "ﴎ": "سر", + "ﴩ": "سۛر", + "ﴍ": "سۛر", + "ﲰ": "سم", + "ﳧ": "سم", + "ﰟ": "سم", + "ﴰ": "سۛم", + "ﳩ": "سۛم", + "ﴨ": "سۛم", + "ﴌ": "سۛم", + "ﵡ": "سمج", + "ﵠ": "سمح", + "ﵟ": "سمح", + "ﵫ": "سۛمخ", + "ﵪ": "سۛمخ", + "ﵣ": "سمم", + "ﵢ": "سمم", + "ﵭ": "سۛمم", + "ﵬ": "سۛمم", + "ﴗ": "سى", + "ﳻ": "سى", + "ﴘ": "سى", + "ﳼ": "سى", + "ﴙ": "سۛى", + "ﳽ": "سۛى", + "ﴚ": "سۛى", + "ﳾ": "سۛى", + "𐋲": "ص", + "𞸑": "ص", + "𞸱": "ص", + "𞹑": "ص", + "𞹱": "ص", + "𞺑": "ص", + "𞺱": "ص", + "ﺻ": "ص", + "ﺼ": "ص", + "ﺺ": "ص", + "ﺹ": "ص", + "ڞ": "صۛ", + "ࢯ": "ص̤̣", + "ﲱ": "صح", + "ﰠ": "صح", + "ﵥ": "صحح", + "ﵤ": "صحح", + "ﶩ": "صحى", + "ﲲ": "صخ", + "ﴫ": "صر", + "ﴏ": "صر", + "ﷵ": "صلعم", + "ﷹ": "صلى", + "ﷰ": "صلى", + "ﷺ": "صلى lللo علىo وسلم", + "ﲳ": "صم", + "ﰡ": "صم", + "ﷅ": "صمم", + "ﵦ": "صمم", + "ﴡ": "صى", + "ﴅ": "صى", + "ﴢ": "صى", + "ﴆ": "صى", + "𞸙": "ض", + "𞸹": "ض", + "𞹙": "ض", + "𞹹": "ض", + "𞺙": "ض", + "𞺹": "ض", + "ﺿ": "ض", + "ﻀ": "ض", + "ﺾ": "ض", + "ﺽ": "ض", + "ﲴ": "ضج", + "ﰢ": "ضج", + "ﲵ": "ضح", + "ﰣ": "ضح", + "ﵮ": "ضحى", + "ﶫ": "ضحى", + "ﲶ": "ضخ", + "ﰤ": "ضخ", + "ﵰ": "ضخم", + "ﵯ": "ضخم", + "ﴬ": "ضر", + "ﴐ": "ضر", + "ﲷ": "ضم", + "ﰥ": "ضم", + "ﴣ": "ضى", + "ﴇ": "ضى", + "ﴤ": "ضى", + "ﴈ": "ضى", + "𐋨": "ط", + "𞸈": "ط", + "𞹨": "ط", + "𞺈": "ط", + "𞺨": "ط", + "ﻃ": "ط", + "ﻄ": "ط", + "ﻂ": "ط", + "ﻁ": "ط", + "ڟ": "طۛ", + "ﲸ": "طح", + "ﰦ": "طح", + "ﴳ": "طم", + "ﴺ": "طم", + "ﰧ": "طم", + "ﵲ": "طمح", + "ﵱ": "طمح", + "ﵳ": "طمم", + "ﵴ": "طمى", + "ﴑ": "طى", + "ﳵ": "طى", + "ﴒ": "طى", + "ﳶ": "طى", + "𞸚": "ظ", + "𞹺": "ظ", + "𞺚": "ظ", + "𞺺": "ظ", + "ﻇ": "ظ", + "ﻈ": "ظ", + "ﻆ": "ظ", + "ﻅ": "ظ", + "ﲹ": "ظم", + "ﴻ": "ظم", + "ﰨ": "ظم", + "؏": "ع", + "𞸏": "ع", + "𞸯": "ع", + "𞹏": "ع", + "𞹯": "ع", + "𞺏": "ع", + "𞺯": "ع", + "ﻋ": "ع", + "ﻌ": "ع", + "ﻊ": "ع", + "ﻉ": "ع", + "ﲺ": "عج", + "ﰩ": "عج", + "ﷄ": "عجم", + "ﵵ": "عجم", + "ﷷ": "علىo", + "ﲻ": "عم", + "ﰪ": "عم", + "ﵷ": "عمم", + "ﵶ": "عمم", + "ﵸ": "عمى", + "ﶶ": "عمى", + "ﴓ": "عى", + "ﳷ": "عى", + "ﴔ": "عى", + "ﳸ": "عى", + "𞸛": "غ", + "𞸻": "غ", + "𞹛": "غ", + "𞹻": "غ", + "𞺛": "غ", + "𞺻": "غ", + "ﻏ": "غ", + "ﻐ": "غ", + "ﻎ": "غ", + "ﻍ": "غ", + "ﲼ": "غج", + "ﰫ": "غج", + "ﲽ": "غم", + "ﰬ": "غم", + "ﵹ": "غمم", + "ﵻ": "غمى", + "ﵺ": "غمى", + "ﴕ": "غى", + "ﳹ": "غى", + "ﴖ": "غى", + "ﳺ": "غى", + "𞸐": "ف", + "𞸰": "ف", + "𞹰": "ف", + "𞺐": "ف", + "𞺰": "ف", + "ﻓ": "ف", + "ﻔ": "ف", + "ﻒ": "ف", + "ﻑ": "ف", + "ڧ": "ف", + "ﲾ": "فج", + "ﰭ": "فج", + "ﲿ": "فح", + "ﰮ": "فح", + "ﳀ": "فخ", + "ﰯ": "فخ", + "ﵽ": "فخم", + "ﵼ": "فخم", + "ﳁ": "فم", + "ﰰ": "فم", + "ﷁ": "فمى", + "ﱼ": "فى", + "ﰱ": "فى", + "ﱽ": "فى", + "ﰲ": "فى", + "𞸞": "ڡ", + "𞹾": "ڡ", + "ࢻ": "ڡ", + "ٯ": "ڡ", + "𞸟": "ڡ", + "𞹟": "ڡ", + "ࢼ": "ڡ", + "ڤ": "ڡۛ", + "ﭬ": "ڡۛ", + "ﭭ": "ڡۛ", + "ﭫ": "ڡۛ", + "ﭪ": "ڡۛ", + "ڨ": "ڡۛ", + "ࢤ": "ڢۛ", + "ﭰ": "ڦ", + "ﭱ": "ڦ", + "ﭯ": "ڦ", + "ﭮ": "ڦ", + "𞸒": "ق", + "𞸲": "ق", + "𞹒": "ق", + "𞹲": "ق", + "𞺒": "ق", + "𞺲": "ق", + "ﻗ": "ق", + "ﻘ": "ق", + "ﻖ": "ق", + "ﻕ": "ق", + "ﳂ": "قح", + "ﰳ": "قح", + "ﷱ": "قلى", + "ﳃ": "قم", + "ﰴ": "قم", + "ﶴ": "قمح", + "ﵾ": "قمح", + "ﵿ": "قمم", + "ﶲ": "قمى", + "ﱾ": "قى", + "ﰵ": "قى", + "ﱿ": "قى", + "ﰶ": "قى", + "𞸊": "ك", + "𞸪": "ك", + "𞹪": "ك", + "ﻛ": "ك", + "ﻜ": "ك", + "ﻚ": "ك", + "ﻙ": "ك", + "ک": "ك", + "ﮐ": "ك", + "ﮑ": "ك", + "ﮏ": "ك", + "ﮎ": "ك", + "ڪ": "ك", + "ڭ": "كۛ", + "ﯕ": "كۛ", + "ﯖ": "كۛ", + "ﯔ": "كۛ", + "ﯓ": "كۛ", + "ݣ": "كۛ", + "ﲀ": "كl", + "ﰷ": "كl", + "ﳄ": "كج", + "ﰸ": "كج", + "ﳅ": "كح", + "ﰹ": "كح", + "ﳆ": "كخ", + "ﰺ": "كخ", + "ﳇ": "كل", + "ﳫ": "كل", + "ﲁ": "كل", + "ﰻ": "كل", + "ﳈ": "كم", + "ﳬ": "كم", + "ﲂ": "كم", + "ﰼ": "كم", + "ﷃ": "كمم", + "ﶻ": "كمم", + "ﶷ": "كمى", + "ﲃ": "كى", + "ﰽ": "كى", + "ﲄ": "كى", + "ﰾ": "كى", + "ݢ": "ڬ", + "ﮔ": "گ", + "ﮕ": "گ", + "ﮓ": "گ", + "ﮒ": "گ", + "ࢰ": "گ", + "ڴ": "گۛ", + "ﮜ": "ڱ", + "ﮝ": "ڱ", + "ﮛ": "ڱ", + "ﮚ": "ڱ", + "ﮘ": "ڳ", + "ﮙ": "ڳ", + "ﮗ": "ڳ", + "ﮖ": "ڳ", + "𞸋": "ل", + "𞸫": "ل", + "𞹋": "ل", + "𞺋": "ل", + "𞺫": "ل", + "ﻟ": "ل", + "ﻠ": "ل", + "ﻞ": "ل", + "ﻝ": "ل", + "ڷ": "لۛ", + "ڵ": "ل̆", + "ﻼ": "لl", + "ﻻ": "لl", + "ﻺ": "لlٕ", + "ﻹ": "لlٕ", + "ﻸ": "لlٴ", + "ﻷ": "لlٴ", + "ﳍ": "لo", + "ﻶ": "لآ", + "ﻵ": "لآ", + "ﳉ": "لج", + "ﰿ": "لج", + "ﶃ": "لجج", + "ﶄ": "لجج", + "ﶺ": "لجم", + "ﶼ": "لجم", + "ﶬ": "لجى", + "ﳊ": "لح", + "ﱀ": "لح", + "ﶵ": "لحم", + "ﶀ": "لحم", + "ﶂ": "لحى", + "ﶁ": "لحى", + "ﳋ": "لخ", + "ﱁ": "لخ", + "ﶆ": "لخم", + "ﶅ": "لخم", + "ﳌ": "لم", + "ﳭ": "لم", + "ﲅ": "لم", + "ﱂ": "لم", + "ﶈ": "لمح", + "ﶇ": "لمح", + "ﶭ": "لمى", + "ﲆ": "لى", + "ﱃ": "لى", + "ﲇ": "لى", + "ﱄ": "لى", + "𞸌": "م", + "𞸬": "م", + "𞹬": "م", + "𞺌": "م", + "𞺬": "م", + "ﻣ": "م", + "ﻤ": "م", + "ﻢ": "م", + "ﻡ": "م", + "ࢧ": "مۛ", + "۾": "م͈", + "ﲈ": "مl", + "ﳎ": "مج", + "ﱅ": "مج", + "ﶌ": "مجح", + "ﶒ": "مجخ", + "ﶍ": "مجم", + "ﷀ": "مجى", + "ﳏ": "مح", + "ﱆ": "مح", + "ﶉ": "محج", + "ﶊ": "محم", + "ﷴ": "محمد", + "ﶋ": "محى", + "ﳐ": "مخ", + "ﱇ": "مخ", + "ﶎ": "مخج", + "ﶏ": "مخم", + "ﶹ": "مخى", + "ﳑ": "مم", + "ﲉ": "مم", + "ﱈ": "مم", + "ﶱ": "ممى", + "ﱉ": "مى", + "ﱊ": "مى", + "𞸍": "ن", + "𞸭": "ن", + "𞹍": "ن", + "𞹭": "ن", + "𞺍": "ن", + "𞺭": "ن", + "ﻧ": "ن", + "ﻨ": "ن", + "ﻦ": "ن", + "ﻥ": "ن", + "ݨ": "نؕ", + "ݩ": "ن̆", + "ﳖ": "نo", + "ﳯ": "نo", + "ﶸ": "نجح", + "ﶽ": "نجح", + "ﶘ": "نجم", + "ﶗ": "نجم", + "ﶙ": "نجى", + "ﷇ": "نجى", + "ﳓ": "نح", + "ﱌ": "نح", + "ﶕ": "نحم", + "ﶖ": "نحى", + "ﶳ": "نحى", + "ﳔ": "نخ", + "ﱍ": "نخ", + "ﲊ": "نر", + "ﲋ": "نز", + "ﳕ": "نم", + "ﳮ": "نم", + "ﲌ": "نم", + "ﱎ": "نم", + "ﶛ": "نمى", + "ﶚ": "نمى", + "ﲍ": "نن", + "ﲎ": "نى", + "ﱏ": "نى", + "ﲏ": "نى", + "ﱐ": "نى", + "ۂ": "ۀ", + "ﮥ": "ۀ", + "ﮤ": "ۀ", + "𐋤": "و", + "𞸅": "و", + "𞺅": "و", + "𞺥": "و", + "ﻮ": "و", + "ﻭ": "و", + "ࢱ": "و", + "ۋ": "وۛ", + "ﯟ": "وۛ", + "ﯞ": "وۛ", + "ۇ": "و̓", + "ﯘ": "و̓", + "ﯗ": "و̓", + "ۆ": "و̆", + "ﯚ": "و̆", + "ﯙ": "و̆", + "ۉ": "و̂", + "ﯣ": "و̂", + "ﯢ": "و̂", + "ۈ": "وٰ", + "ﯜ": "وٰ", + "ﯛ": "وٰ", + "ؤ": "وٴ", + "ﺆ": "وٴ", + "ﺅ": "وٴ", + "ٶ": "وٴ", + "ٷ": "و̓ٴ", + "ﯝ": "و̓ٴ", + "ﷸ": "وسلم", + "ﯡ": "ۅ", + "ﯠ": "ۅ", + "ٮ": "ى", + "𞸜": "ى", + "𞹼": "ى", + "ں": "ى", + "𞸝": "ى", + "𞹝": "ى", + "ﮟ": "ى", + "ﮞ": "ى", + "ࢽ": "ى", + "ﯨ": "ى", + "ﯩ": "ى", + "ﻰ": "ى", + "ﻯ": "ى", + "ي": "ى", + "𞸉": "ى", + "𞸩": "ى", + "𞹉": "ى", + "𞹩": "ى", + "𞺉": "ى", + "𞺩": "ى", + "ﻳ": "ى", + "ﻴ": "ى", + "ﻲ": "ى", + "ﻱ": "ى", + "ی": "ى", + "ﯾ": "ى", + "ﯿ": "ى", + "ﯽ": "ى", + "ﯼ": "ى", + "ے": "ى", + "ﮯ": "ى", + "ﮮ": "ى", + "ٹ": "ىؕ", + "ﭨ": "ىؕ", + "ﭩ": "ىؕ", + "ﭧ": "ىؕ", + "ﭦ": "ىؕ", + "ڻ": "ىؕ", + "ﮢ": "ىؕ", + "ﮣ": "ىؕ", + "ﮡ": "ىؕ", + "ﮠ": "ىؕ", + "پ": "ىۛ", + "ﭘ": "ىۛ", + "ﭙ": "ىۛ", + "ﭗ": "ىۛ", + "ﭖ": "ىۛ", + "ث": "ىۛ", + "𞸖": "ىۛ", + "𞸶": "ىۛ", + "𞹶": "ىۛ", + "𞺖": "ىۛ", + "𞺶": "ىۛ", + "ﺛ": "ىۛ", + "ﺜ": "ىۛ", + "ﺚ": "ىۛ", + "ﺙ": "ىۛ", + "ڽ": "ىۛ", + "ۑ": "ىۛ", + "ؿ": "ىۛ", + "ࢷ": "ىۛۢ", + "ݖ": "ى̆", + "ێ": "ى̆", + "ࢺ": "ى̆̇", + "ؽ": "ى̂", + "ࢨ": "ىٔ", + "ﲐ": "ىٰ", + "ﱝ": "ىٰ", + "ﳞ": "ىo", + "ﳱ": "ىo", + "ﳦ": "ىۛo", + "ئ": "ىٴ", + "ﺋ": "ىٴ", + "ﺌ": "ىٴ", + "ﺊ": "ىٴ", + "ﺉ": "ىٴ", + "ٸ": "ىٴ", + "ﯫ": "ىٴl", + "ﯪ": "ىٴl", + "ﲛ": "ىٴo", + "ﳠ": "ىٴo", + "ﯭ": "ىٴo", + "ﯬ": "ىٴo", + "ﯸ": "ىٴٻ", + "ﯷ": "ىٴٻ", + "ﯶ": "ىٴٻ", + "ﲗ": "ىٴج", + "ﰀ": "ىٴج", + "ﲘ": "ىٴح", + "ﰁ": "ىٴح", + "ﲙ": "ىٴخ", + "ﱤ": "ىٴر", + "ﱥ": "ىٴز", + "ﲚ": "ىٴم", + "ﳟ": "ىٴم", + "ﱦ": "ىٴم", + "ﰂ": "ىٴم", + "ﱧ": "ىٴن", + "ﯯ": "ىٴو", + "ﯮ": "ىٴو", + "ﯱ": "ىٴو̓", + "ﯰ": "ىٴو̓", + "ﯳ": "ىٴو̆", + "ﯲ": "ىٴو̆", + "ﯵ": "ىٴوٰ", + "ﯴ": "ىٴوٰ", + "ﯻ": "ىٴى", + "ﯺ": "ىٴى", + "ﱨ": "ىٴى", + "ﯹ": "ىٴى", + "ﰃ": "ىٴى", + "ﱩ": "ىٴى", + "ﰄ": "ىٴى", + "ﳚ": "ىج", + "ﱕ": "ىج", + "ﰑ": "ىۛج", + "ﶯ": "ىجى", + "ﳛ": "ىح", + "ﱖ": "ىح", + "ﶮ": "ىحى", + "ﳜ": "ىخ", + "ﱗ": "ىخ", + "ﲑ": "ىر", + "ﱶ": "ىۛر", + "ﲒ": "ىز", + "ﱷ": "ىۛز", + "ﳝ": "ىم", + "ﳰ": "ىم", + "ﲓ": "ىم", + "ﱘ": "ىم", + "ﲦ": "ىۛم", + "ﳥ": "ىۛم", + "ﱸ": "ىۛم", + "ﰒ": "ىۛم", + "ﶝ": "ىمم", + "ﶜ": "ىمم", + "ﶰ": "ىمى", + "ﲔ": "ىن", + "ﱹ": "ىۛن", + "ﲕ": "ىى", + "ﱙ": "ىى", + "ﲖ": "ىى", + "ﱚ": "ىى", + "ﱺ": "ىۛى", + "ﰓ": "ىۛى", + "ﱻ": "ىۛى", + "ﰔ": "ىۛى", + "ﮱ": "ۓ", + "ﮰ": "ۓ", + "𐊸": "ⵀ", + "⁞": "ⵂ", + "⸽": "ⵂ", + "⦙": "ⵂ", + "︙": "ⵗ", + "⁝": "ⵗ", + "⋮": "ⵗ", + "Մ": "ሆ", + "Ռ": "ቡ", + "Ի": "ኮ", + "Պ": "ጣ", + "आ": "अा", + "ऒ": "अाॆ", + "ओ": "अाे", + "औ": "अाै", + "ऄ": "अॆ", + "ऑ": "अॉ", + "ऍ": "एॅ", + "ऎ": "एॆ", + "ऐ": "एे", + "ई": "र्इ", + "ઽ": "ऽ", + "𑇜": "ꣻ", + "𑇋": "ऺ", + "ુ": "ु", + "ૂ": "ू", + "ੋ": "ॆ", + "੍": "्", + "્": "्", + "আ": "অা", + "ৠ": "ঋৃ", + "ৡ": "ঋৃ", + "𑒒": "ঘ", + "𑒔": "চ", + "𑒖": "জ", + "𑒘": "ঞ", + "𑒙": "ট", + "𑒛": "ড", + "𑒪": "ণ", + "𑒞": "ত", + "𑒟": "থ", + "𑒠": "দ", + "𑒡": "ধ", + "𑒢": "ন", + "𑒣": "প", + "𑒩": "ব", + "𑒧": "ম", + "𑒨": "য", + "𑒫": "র", + "𑒝": "ল", + "𑒭": "ষ", + "𑒮": "স", + "𑓄": "ঽ", + "𑒰": "া", + "𑒱": "ি", + "𑒹": "ে", + "𑒼": "ো", + "𑒾": "ৌ", + "𑓂": "্", + "𑒽": "ৗ", + "ਉ": "ੳੁ", + "ਊ": "ੳੂ", + "ਆ": "ਅਾ", + "ਐ": "ਅੈ", + "ਔ": "ਅੌ", + "ਇ": "ੲਿ", + "ਈ": "ੲੀ", + "ਏ": "ੲੇ", + "આ": "અા", + "ઑ": "અાૅ", + "ઓ": "અાે", + "ઔ": "અાૈ", + "ઍ": "અૅ", + "એ": "અે", + "ઐ": "અૈ", + "ଆ": "ଅା", + "௮": "அ", + "ர": "ஈ", + "ா": "ஈ", + "௫": "ஈு", + "௨": "உ", + "ഉ": "உ", + "ஊ": "உள", + "ഊ": "உൗ", + "௭": "எ", + "௷": "எவ", + "ஜ": "ஐ", + "ജ": "ஐ", + "௧": "க", + "௪": "ச", + "௬": "சு", + "௲": "சூ", + "ഺ": "டி", + "ണ": "ண", + "௺": "நீ", + "௴": "மீ", + "௰": "ய", + "ഴ": "ழ", + "ௗ": "ள", + "ை": "ன", + "ശ": "ஶ", + "௸": "ஷ", + "ി": "ி", + "ീ": "ி", + "ொ": "ெஈ", + "ௌ": "ெள", + "ோ": "ேஈ", + "ಅ": "అ", + "ಆ": "ఆ", + "ಇ": "ఇ", + "ౠ": "ఋా", + "ౡ": "ఌా", + "ಒ": "ఒ", + "ఔ": "ఒౌ", + "ಔ": "ఒౌ", + "ఓ": "ఒౕ", + "ಓ": "ఒౕ", + "ಜ": "జ", + "ಞ": "ఞ", + "ఢ": "డ̣", + "ಣ": "ణ", + "థ": "ధּ", + "భ": "బ̣", + "ಯ": "య", + "ఠ": "రּ", + "ಱ": "ఱ", + "ಲ": "ల", + "ష": "వ̣", + "హ": "వా", + "మ": "వు", + "ూ": "ుా", + "ౄ": "ృా", + "ೡ": "ಌಾ", + "ഈ": "ഇൗ", + "ഐ": "എെ", + "ഓ": "ഒാ", + "ഔ": "ഒൗ", + "ൡ": "ഞ", + "൫": "ദ്ര", + "൹": "നു", + "ഌ": "നു", + "ങ": "നു", + "൯": "ന്", + "ൻ": "ന്", + "൬": "ന്ന", + "൚": "ന്മ", + "റ": "ര", + "൪": "ര്", + "ർ": "ര്", + "൮": "വ്ര", + "൶": "ഹ്മ", + "ൂ": "ു", + "ൃ": "ു", + "ൈ": "െെ", + "෪": "ජ", + "෫": "ද", + "𑐓": "𑐴𑑂𑐒", + "𑐙": "𑐴𑑂𑐘", + "𑐤": "𑐴𑑂𑐣", + "𑐪": "𑐴𑑂𑐩", + "𑐭": "𑐴𑑂𑐬", + "𑐯": "𑐴𑑂𑐮", + "𑗘": "𑖂", + "𑗙": "𑖂", + "𑗚": "𑖃", + "𑗛": "𑖄", + "𑗜": "𑖲", + "𑗝": "𑖳", + "ฃ": "ข", + "ด": "ค", + "ต": "ค", + "ม": "ฆ", + "ຈ": "จ", + "ซ": "ช", + "ฏ": "ฎ", + "ท": "ฑ", + "ບ": "บ", + "ປ": "ป", + "ຝ": "ฝ", + "ພ": "พ", + "ຟ": "ฟ", + "ฦ": "ภ", + "ຍ": "ย", + "។": "ฯ", + "ๅ": "า", + "ำ": "̊า", + "ិ": "ิ", + "ី": "ี", + "ឹ": "ึ", + "ឺ": "ื", + "ຸ": "ุ", + "ູ": "ู", + "แ": "เเ", + "ໜ": "ຫນ", + "ໝ": "ຫມ", + "ຳ": "̊າ", + "༂": "འུྂཿ", + "༃": "འུྂ༔", + "ཪ": "ར", + "ༀ": "ཨོཾ", + "ཷ": "ྲཱྀ", + "ཹ": "ླཱྀ", + "𑲲": "𑲪", + "ႁ": "ဂှ", + "က": "ဂာ", + "ၰ": "ဃှ", + "ၦ": "ပှ", + "ဟ": "ပာ", + "ၯ": "ပာှ", + "ၾ": "ၽှ", + "ဩ": "သြ", + "ဪ": "သြော်", + "႞": "ႃ̊", + "ឣ": "អ", + "᧐": "ᦞ", + "᧑": "ᦱ", + "᪀": "ᩅ", + "᪐": "ᩅ", + "꩓": "ꨁ", + "꩖": "ꨣ", + "᭒": "ᬍ", + "᭓": "ᬑ", + "᭘": "ᬨ", + "ꦣ": "ꦝ", + "ᢖ": "ᡜ", + "ᡕ": "ᠵ", + "ῶ": "Ꮿ", + "ᐍ": "ᐁ·", + "ᐫ": "ᐁᐠ", + "ᐑ": "ᐄ·", + "ᐓ": "ᐅ·", + "ᐭ": "ᐅᐠ", + "ᐕ": "ᐆ·", + "ᐘ": "ᐊ·", + "ᐮ": "ᐊᐠ", + "ᐚ": "ᐋ·", + "ᣝ": "ᐞᣟ", + "ᓑ": "ᐡ", + "ᕀ": "ᐩ", + "ᐿ": "ᐲ·", + "ᑃ": "ᐴ·", + "⍩": "ᐵ", + "ᑇ": "ᐹ·", + "ᑜ": "ᑏ·", + "⸧": "ᑐ", + "⊃": "ᑐ", + "ᑞ": "ᑐ·", + "ᑩ": "ᑐ'", + "⟉": "ᑐ/", + "⫗": "ᑐᑕ", + "ᑠ": "ᑑ·", + "⸦": "ᑕ", + "⊂": "ᑕ", + "ᑢ": "ᑕ·", + "ᑪ": "ᑕ'", + "ᑤ": "ᑖ·", + "ᑵ": "ᑫ·", + "ᒅ": "ᑫ'", + "ᑹ": "ᑮ·", + "ᑽ": "ᑰ·", + "ᘃ": "ᒉ", + "ᒓ": "ᒉ·", + "ᒕ": "ᒋ·", + "ᒗ": "ᒌ·", + "ᒛ": "ᒎ·", + "ᘂ": "ᒐ", + "ᒝ": "ᒐ·", + "ᒟ": "ᒑ·", + "ᒭ": "ᒣ·", + "ᒱ": "ᒦ·", + "ᒳ": "ᒧ·", + "ᒵ": "ᒨ·", + "ᒹ": "ᒫ·", + "ᓊ": "ᓀ·", + "ᣇ": "ᓂ·", + "ᣉ": "ᓃ·", + "ᣋ": "ᓄ·", + "ᣍ": "ᓅ·", + "ᓌ": "ᓇ·", + "ᓎ": "ᓈ·", + "ᘄ": "ᓓ", + "ᓝ": "ᓓ·", + "ᓟ": "ᓕ·", + "ᓡ": "ᓖ·", + "ᓣ": "ᓗ·", + "ᓥ": "ᓘ·", + "ᘇ": "ᓚ", + "ᓧ": "ᓚ·", + "ᓩ": "ᓛ·", + "ᓷ": "ᓭ·", + "ᓹ": "ᓯ·", + "ᓻ": "ᓰ·", + "ᓽ": "ᓱ·", + "ᓿ": "ᓲ·", + "ᔁ": "ᓴ·", + "ᔃ": "ᓵ·", + "ᔌ": "ᔋ<", + "ᔎ": "ᔋb", + "ᔍ": "ᔋᑕ", + "ᔏ": "ᔋᒐ", + "ᔘ": "ᔐ·", + "ᔚ": "ᔑ·", + "ᔜ": "ᔒ·", + "ᔞ": "ᔓ·", + "ᔠ": "ᔔ·", + "ᔢ": "ᔕ·", + "ᔤ": "ᔖ·", + "ᔲ": "ᔨ·", + "ᔴ": "ᔩ·", + "ᔶ": "ᔪ·", + "ᔸ": "ᔫ·", + "ᔺ": "ᔭ·", + "ᔼ": "ᔮ·", + "ᘢ": "ᕃ", + "ᣠ": "ᕃ·", + "ᘣ": "ᕆ", + "ᘤ": "ᕊ", + "ᕏ": "ᕌ·", + "ᖃ": "ᕐb", + "ᖄ": "ᕐḃ", + "ᖁ": "ᕐd", + "ᕿ": "ᕐP", + "ᙯ": "ᕐᑫ", + "ᕾ": "ᕐᑬ", + "ᖀ": "ᕐᑮ", + "ᖂ": "ᕐᑰ", + "ᖅ": "ᕐᒃ", + "ᕜ": "ᕚ·", + "ᣣ": "ᕞ·", + "ᣤ": "ᕦ·", + "ᕩ": "ᕧ·", + "ᣥ": "ᕫ·", + "ᣨ": "ᖆ·", + "ᖑ": "ᖕJ", + "ᙰ": "ᖕᒉ", + "ᖎ": "ᖕᒊ", + "ᖏ": "ᖕᒋ", + "ᖐ": "ᖕᒌ", + "ᖒ": "ᖕᒎ", + "ᖓ": "ᖕᒐ", + "ᖔ": "ᖕᒑ", + "ᙳ": "ᖖJ", + "ᙱ": "ᖖᒋ", + "ᙲ": "ᖖᒌ", + "ᙴ": "ᖖᒎ", + "ᙵ": "ᖖᒐ", + "ᙶ": "ᖖᒑ", + "ᣪ": "ᖗ·", + "ᙷ": "ᖧ·", + "ᙸ": "ᖨ·", + "ᙹ": "ᖩ·", + "ᙺ": "ᖪ·", + "ᙻ": "ᖫ·", + "ᙼ": "ᖬ·", + "ᙽ": "ᖭ·", + "⪫": "ᗒ", + "⪪": "ᗕ", + "ꓷ": "ᗡ", + "ᣰ": "ᗴ·", + "ᣲ": "ᘛ·", + "ᶻ": "ᙆ", + "ꓭ": "ᙠ", + "ᶺ": "ᣔ", + "ᴾ": "ᣖ", + "ᣜ": "ᣟᐞ", + "ˡ": "ᣳ", + "ʳ": "ᣴ", + "ˢ": "ᣵ", + "ᣛ": "ᣵ", + "ꚰ": "ᚹ", + "ᛡ": "ᚼ", + "⍿": "ᚽ", + "ᛂ": "ᚽ", + "𝈿": "ᛋ", + "↑": "ᛏ", + "↿": "ᛐ", + "⥮": "ᛐ⇂", + "⥣": "ᛐᛚ", + "ⵣ": "ᛯ", + "↾": "ᛚ", + "⨡": "ᛚ", + "⋄": "ᛜ", + "◇": "ᛜ", + "◊": "ᛜ", + "♢": "ᛜ", + "🝔": "ᛜ", + "𑢷": "ᛜ", + "𐊔": "ᛜ", + "⍚": "ᛜ̲", + "⋈": "ᛞ", + "⨝": "ᛞ", + "𐓐": "ᛦ", + "↕": "ᛨ", + "𐳼": "𐲂", + "𐳺": "𐲥", + "ㄱ": "ᄀ", + "ᆨ": "ᄀ", + "ᄁ": "ᄀᄀ", + "ㄲ": "ᄀᄀ", + "ᆩ": "ᄀᄀ", + "ᇺ": "ᄀᄂ", + "ᅚ": "ᄀᄃ", + "ᇃ": "ᄀᄅ", + "ᇻ": "ᄀᄇ", + "ᆪ": "ᄀᄉ", + "ㄳ": "ᄀᄉ", + "ᇄ": "ᄀᄉᄀ", + "ᇼ": "ᄀᄎ", + "ᇽ": "ᄀᄏ", + "ᇾ": "ᄀᄒ", + "ㄴ": "ᄂ", + "ᆫ": "ᄂ", + "ᄓ": "ᄂᄀ", + "ᇅ": "ᄂᄀ", + "ᄔ": "ᄂᄂ", + "ㅥ": "ᄂᄂ", + "ᇿ": "ᄂᄂ", + "ᄕ": "ᄂᄃ", + "ㅦ": "ᄂᄃ", + "ᇆ": "ᄂᄃ", + "ퟋ": "ᄂᄅ", + "ᄖ": "ᄂᄇ", + "ᅛ": "ᄂᄉ", + "ᇇ": "ᄂᄉ", + "ㅧ": "ᄂᄉ", + "ᅜ": "ᄂᄌ", + "ᆬ": "ᄂᄌ", + "ㄵ": "ᄂᄌ", + "ퟌ": "ᄂᄎ", + "ᇉ": "ᄂᄐ", + "ᅝ": "ᄂᄒ", + "ᆭ": "ᄂᄒ", + "ㄶ": "ᄂᄒ", + "ᇈ": "ᄂᅀ", + "ㅨ": "ᄂᅀ", + "ㄷ": "ᄃ", + "ᆮ": "ᄃ", + "ᄗ": "ᄃᄀ", + "ᇊ": "ᄃᄀ", + "ᄄ": "ᄃᄃ", + "ㄸ": "ᄃᄃ", + "ퟍ": "ᄃᄃ", + "ퟎ": "ᄃᄃᄇ", + "ᅞ": "ᄃᄅ", + "ᇋ": "ᄃᄅ", + "ꥠ": "ᄃᄆ", + "ꥡ": "ᄃᄇ", + "ퟏ": "ᄃᄇ", + "ꥢ": "ᄃᄉ", + "ퟐ": "ᄃᄉ", + "ퟑ": "ᄃᄉᄀ", + "ꥣ": "ᄃᄌ", + "ퟒ": "ᄃᄌ", + "ퟓ": "ᄃᄎ", + "ퟔ": "ᄃᄐ", + "ㄹ": "ᄅ", + "ᆯ": "ᄅ", + "ꥤ": "ᄅᄀ", + "ᆰ": "ᄅᄀ", + "ㄺ": "ᄅᄀ", + "ꥥ": "ᄅᄀᄀ", + "ퟕ": "ᄅᄀᄀ", + "ᇌ": "ᄅᄀᄉ", + "ㅩ": "ᄅᄀᄉ", + "ퟖ": "ᄅᄀᄒ", + "ᄘ": "ᄅᄂ", + "ᇍ": "ᄅᄂ", + "ꥦ": "ᄅᄃ", + "ᇎ": "ᄅᄃ", + "ㅪ": "ᄅᄃ", + "ꥧ": "ᄅᄃᄃ", + "ᇏ": "ᄅᄃᄒ", + "ᄙ": "ᄅᄅ", + "ᇐ": "ᄅᄅ", + "ퟗ": "ᄅᄅᄏ", + "ꥨ": "ᄅᄆ", + "ᆱ": "ᄅᄆ", + "ㄻ": "ᄅᄆ", + "ᇑ": "ᄅᄆᄀ", + "ᇒ": "ᄅᄆᄉ", + "ퟘ": "ᄅᄆᄒ", + "ꥩ": "ᄅᄇ", + "ᆲ": "ᄅᄇ", + "ㄼ": "ᄅᄇ", + "ퟙ": "ᄅᄇᄃ", + "ꥪ": "ᄅᄇᄇ", + "ᇓ": "ᄅᄇᄉ", + "ㅫ": "ᄅᄇᄉ", + "ꥫ": "ᄅᄇᄋ", + "ᇕ": "ᄅᄇᄋ", + "ퟚ": "ᄅᄇᄑ", + "ᇔ": "ᄅᄇᄒ", + "ꥬ": "ᄅᄉ", + "ᆳ": "ᄅᄉ", + "ㄽ": "ᄅᄉ", + "ᇖ": "ᄅᄉᄉ", + "ᄛ": "ᄅᄋ", + "ퟝ": "ᄅᄋ", + "ꥭ": "ᄅᄌ", + "ꥮ": "ᄅᄏ", + "ᇘ": "ᄅᄏ", + "ᆴ": "ᄅᄐ", + "ㄾ": "ᄅᄐ", + "ᆵ": "ᄅᄑ", + "ㄿ": "ᄅᄑ", + "ᄚ": "ᄅᄒ", + "ㅀ": "ᄅᄒ", + "ᄻ": "ᄅᄒ", + "ᆶ": "ᄅᄒ", + "ퟲ": "ᄅᄒ", + "ᇗ": "ᄅᅀ", + "ㅬ": "ᄅᅀ", + "ퟛ": "ᄅᅌ", + "ᇙ": "ᄅᅙ", + "ㅭ": "ᄅᅙ", + "ퟜ": "ᄅᅙᄒ", + "ㅁ": "ᄆ", + "ᆷ": "ᄆ", + "ꥯ": "ᄆᄀ", + "ᇚ": "ᄆᄀ", + "ퟞ": "ᄆᄂ", + "ퟟ": "ᄆᄂᄂ", + "ꥰ": "ᄆᄃ", + "ᇛ": "ᄆᄅ", + "ퟠ": "ᄆᄆ", + "ᄜ": "ᄆᄇ", + "ㅮ": "ᄆᄇ", + "ᇜ": "ᄆᄇ", + "ퟡ": "ᄆᄇᄉ", + "ꥱ": "ᄆᄉ", + "ᇝ": "ᄆᄉ", + "ㅯ": "ᄆᄉ", + "ᇞ": "ᄆᄉᄉ", + "ᄝ": "ᄆᄋ", + "ㅱ": "ᄆᄋ", + "ᇢ": "ᄆᄋ", + "ퟢ": "ᄆᄌ", + "ᇠ": "ᄆᄎ", + "ᇡ": "ᄆᄒ", + "ᇟ": "ᄆᅀ", + "ㅰ": "ᄆᅀ", + "ㅂ": "ᄇ", + "ᆸ": "ᄇ", + "ᄞ": "ᄇᄀ", + "ㅲ": "ᄇᄀ", + "ᄟ": "ᄇᄂ", + "ᄠ": "ᄇᄃ", + "ㅳ": "ᄇᄃ", + "ퟣ": "ᄇᄃ", + "ᇣ": "ᄇᄅ", + "ퟤ": "ᄇᄅᄑ", + "ퟥ": "ᄇᄆ", + "ᄈ": "ᄇᄇ", + "ㅃ": "ᄇᄇ", + "ퟦ": "ᄇᄇ", + "ᄬ": "ᄇᄇᄋ", + "ㅹ": "ᄇᄇᄋ", + "ᄡ": "ᄇᄉ", + "ㅄ": "ᄇᄉ", + "ᆹ": "ᄇᄉ", + "ᄢ": "ᄇᄉᄀ", + "ㅴ": "ᄇᄉᄀ", + "ᄣ": "ᄇᄉᄃ", + "ㅵ": "ᄇᄉᄃ", + "ퟧ": "ᄇᄉᄃ", + "ᄤ": "ᄇᄉᄇ", + "ᄥ": "ᄇᄉᄉ", + "ᄦ": "ᄇᄉᄌ", + "ꥲ": "ᄇᄉᄐ", + "ᄫ": "ᄇᄋ", + "ㅸ": "ᄇᄋ", + "ᇦ": "ᄇᄋ", + "ᄧ": "ᄇᄌ", + "ㅶ": "ᄇᄌ", + "ퟨ": "ᄇᄌ", + "ᄨ": "ᄇᄎ", + "ퟩ": "ᄇᄎ", + "ꥳ": "ᄇᄏ", + "ᄩ": "ᄇᄐ", + "ㅷ": "ᄇᄐ", + "ᄪ": "ᄇᄑ", + "ᇤ": "ᄇᄑ", + "ꥴ": "ᄇᄒ", + "ᇥ": "ᄇᄒ", + "ㅅ": "ᄉ", + "ᆺ": "ᄉ", + "ᄭ": "ᄉᄀ", + "ㅺ": "ᄉᄀ", + "ᇧ": "ᄉᄀ", + "ᄮ": "ᄉᄂ", + "ㅻ": "ᄉᄂ", + "ᄯ": "ᄉᄃ", + "ㅼ": "ᄉᄃ", + "ᇨ": "ᄉᄃ", + "ᄰ": "ᄉᄅ", + "ᇩ": "ᄉᄅ", + "ᄱ": "ᄉᄆ", + "ퟪ": "ᄉᄆ", + "ᄲ": "ᄉᄇ", + "ㅽ": "ᄉᄇ", + "ᇪ": "ᄉᄇ", + "ᄳ": "ᄉᄇᄀ", + "ퟫ": "ᄉᄇᄋ", + "ᄊ": "ᄉᄉ", + "ㅆ": "ᄉᄉ", + "ᆻ": "ᄉᄉ", + "ퟬ": "ᄉᄉᄀ", + "ퟭ": "ᄉᄉᄃ", + "ꥵ": "ᄉᄉᄇ", + "ᄴ": "ᄉᄉᄉ", + "ᄵ": "ᄉᄋ", + "ᄶ": "ᄉᄌ", + "ㅾ": "ᄉᄌ", + "ퟯ": "ᄉᄌ", + "ᄷ": "ᄉᄎ", + "ퟰ": "ᄉᄎ", + "ᄸ": "ᄉᄏ", + "ᄹ": "ᄉᄐ", + "ퟱ": "ᄉᄐ", + "ᄺ": "ᄉᄑ", + "ퟮ": "ᄉᅀ", + "ㅇ": "ᄋ", + "ᆼ": "ᄋ", + "ᅁ": "ᄋᄀ", + "ᇬ": "ᄋᄀ", + "ᇭ": "ᄋᄀᄀ", + "ᅂ": "ᄋᄃ", + "ꥶ": "ᄋᄅ", + "ᅃ": "ᄋᄆ", + "ᅄ": "ᄋᄇ", + "ᅅ": "ᄋᄉ", + "ᇱ": "ᄋᄉ", + "ㆂ": "ᄋᄉ", + "ᅇ": "ᄋᄋ", + "ㆀ": "ᄋᄋ", + "ᇮ": "ᄋᄋ", + "ᅈ": "ᄋᄌ", + "ᅉ": "ᄋᄎ", + "ᇯ": "ᄋᄏ", + "ᅊ": "ᄋᄐ", + "ᅋ": "ᄋᄑ", + "ꥷ": "ᄋᄒ", + "ᅆ": "ᄋᅀ", + "ᇲ": "ᄋᅀ", + "ㆃ": "ᄋᅀ", + "ㅈ": "ᄌ", + "ᆽ": "ᄌ", + "ퟷ": "ᄌᄇ", + "ퟸ": "ᄌᄇᄇ", + "ᅍ": "ᄌᄋ", + "ᄍ": "ᄌᄌ", + "ㅉ": "ᄌᄌ", + "ퟹ": "ᄌᄌ", + "ꥸ": "ᄌᄌᄒ", + "ㅊ": "ᄎ", + "ᆾ": "ᄎ", + "ᅒ": "ᄎᄏ", + "ᅓ": "ᄎᄒ", + "ㅋ": "ᄏ", + "ᆿ": "ᄏ", + "ㅌ": "ᄐ", + "ᇀ": "ᄐ", + "ꥹ": "ᄐᄐ", + "ㅍ": "ᄑ", + "ᇁ": "ᄑ", + "ᅖ": "ᄑᄇ", + "ᇳ": "ᄑᄇ", + "ퟺ": "ᄑᄉ", + "ᅗ": "ᄑᄋ", + "ㆄ": "ᄑᄋ", + "ᇴ": "ᄑᄋ", + "ퟻ": "ᄑᄐ", + "ꥺ": "ᄑᄒ", + "ㅎ": "ᄒ", + "ᇂ": "ᄒ", + "ᇵ": "ᄒᄂ", + "ᇶ": "ᄒᄅ", + "ᇷ": "ᄒᄆ", + "ᇸ": "ᄒᄇ", + "ꥻ": "ᄒᄉ", + "ᅘ": "ᄒᄒ", + "ㆅ": "ᄒᄒ", + "ᄽ": "ᄼᄼ", + "ᄿ": "ᄾᄾ", + "ㅿ": "ᅀ", + "ᇫ": "ᅀ", + "ퟳ": "ᅀᄇ", + "ퟴ": "ᅀᄇᄋ", + "ㆁ": "ᅌ", + "ᇰ": "ᅌ", + "ퟵ": "ᅌᄆ", + "ퟶ": "ᅌᄒ", + "ᅏ": "ᅎᅎ", + "ᅑ": "ᅐᅐ", + "ㆆ": "ᅙ", + "ᇹ": "ᅙ", + "ꥼ": "ᅙᅙ", + "ㅤ": "ᅠ", + "ㅏ": "ᅡ", + "ᆣ": "ᅡー", + "ᅶ": "ᅡᅩ", + "ᅷ": "ᅡᅮ", + "ᅢ": "ᅡ丨", + "ㅐ": "ᅡ丨", + "ㅑ": "ᅣ", + "ᅸ": "ᅣᅩ", + "ᅹ": "ᅣᅭ", + "ᆤ": "ᅣᅮ", + "ᅤ": "ᅣ丨", + "ㅒ": "ᅣ丨", + "ㅓ": "ᅥ", + "ᅼ": "ᅥー", + "ᅺ": "ᅥᅩ", + "ᅻ": "ᅥᅮ", + "ᅦ": "ᅥ丨", + "ㅔ": "ᅥ丨", + "ㅕ": "ᅧ", + "ᆥ": "ᅧᅣ", + "ᅽ": "ᅧᅩ", + "ᅾ": "ᅧᅮ", + "ᅨ": "ᅧ丨", + "ㅖ": "ᅧ丨", + "ㅗ": "ᅩ", + "ᅪ": "ᅩᅡ", + "ㅘ": "ᅩᅡ", + "ᅫ": "ᅩᅡ丨", + "ㅙ": "ᅩᅡ丨", + "ᆦ": "ᅩᅣ", + "ᆧ": "ᅩᅣ丨", + "ᅿ": "ᅩᅥ", + "ᆀ": "ᅩᅥ丨", + "ힰ": "ᅩᅧ", + "ᆁ": "ᅩᅧ丨", + "ᆂ": "ᅩᅩ", + "ힱ": "ᅩᅩ丨", + "ᆃ": "ᅩᅮ", + "ᅬ": "ᅩ丨", + "ㅚ": "ᅩ丨", + "ㅛ": "ᅭ", + "ힲ": "ᅭᅡ", + "ힳ": "ᅭᅡ丨", + "ᆄ": "ᅭᅣ", + "ㆇ": "ᅭᅣ", + "ᆆ": "ᅭᅣ", + "ᆅ": "ᅭᅣ丨", + "ㆈ": "ᅭᅣ丨", + "ힴ": "ᅭᅥ", + "ᆇ": "ᅭᅩ", + "ᆈ": "ᅭ丨", + "ㆉ": "ᅭ丨", + "ㅜ": "ᅮ", + "ᆉ": "ᅮᅡ", + "ᆊ": "ᅮᅡ丨", + "ᅯ": "ᅮᅥ", + "ㅝ": "ᅮᅥ", + "ᆋ": "ᅮᅥー", + "ᅰ": "ᅮᅥ丨", + "ㅞ": "ᅮᅥ丨", + "ힵ": "ᅮᅧ", + "ᆌ": "ᅮᅧ丨", + "ᆍ": "ᅮᅮ", + "ᅱ": "ᅮ丨", + "ㅟ": "ᅮ丨", + "ힶ": "ᅮ丨丨", + "ㅠ": "ᅲ", + "ᆎ": "ᅲᅡ", + "ힷ": "ᅲᅡ丨", + "ᆏ": "ᅲᅥ", + "ᆐ": "ᅲᅥ丨", + "ᆑ": "ᅲᅧ", + "ㆊ": "ᅲᅧ", + "ᆒ": "ᅲᅧ丨", + "ㆋ": "ᅲᅧ丨", + "ힸ": "ᅲᅩ", + "ᆓ": "ᅲᅮ", + "ᆔ": "ᅲ丨", + "ㆌ": "ᅲ丨", + "ㆍ": "ᆞ", + "ퟅ": "ᆞᅡ", + "ᆟ": "ᆞᅥ", + "ퟆ": "ᆞᅥ丨", + "ᆠ": "ᆞᅮ", + "ᆢ": "ᆞᆞ", + "ᆡ": "ᆞ丨", + "ㆎ": "ᆞ丨", + "ヘ": "へ", + "⍁": "〼", + "⧄": "〼", + "꒞": "ꁊ", + "꒬": "ꁐ", + "꒜": "ꃀ", + "꒨": "ꄲ", + "꒿": "ꉙ", + "꒾": "ꊱ", + "꒔": "ꋍ", + "꓀": "ꎫ", + "꓂": "ꎵ", + "꒺": "ꎿ", + "꒰": "ꏂ", + "꒧": "ꑘ", + "⊥": "ꓕ", + "⟂": "ꓕ", + "𝈜": "ꓕ", + "Ʇ": "ꓕ", + "Ꞟ": "ꓤ", + "⅁": "ꓨ", + "⅂": "ꓶ", + "𝈕": "ꓶ", + "𝈫": "ꓶ", + "𖼦": "ꓶ", + "𐐑": "ꓶ", + "⅃": "𖼀", + "𑫦": "𑫥𑫯", + "𑫨": "𑫥𑫥", + "𑫩": "𑫥𑫥𑫯", + "𑫪": "𑫥𑫥𑫰", + "𑫧": "𑫥𑫰", + "𑫴": "𑫳𑫯", + "𑫶": "𑫳𑫳", + "𑫷": "𑫳𑫳𑫯", + "𑫸": "𑫳𑫳𑫰", + "𑫵": "𑫳𑫰", + "𑫬": "𑫫𑫯", + "𑫭": "𑫫𑫫", + "𑫮": "𑫫𑫫𑫯", + "⊕": "𐊨", + "⨁": "𐊨", + "🜨": "𐊨", + "Ꚛ": "𐊨", + "▽": "𐊼", + "𝈔": "𐊼", + "🜄": "𐊼", + "⧖": "𐋀", + "ꞛ": "𐐺", + "Ꞛ": "𐐒", + "𐒠": "𐒆", + "𐏑": "𐎂", + "𐏓": "𐎓", + "𒀸": "𐎚", + "☥": "𐦞", + "𓋹": "𐦞", + "〹": "卄", + "不": "不", + "丽": "丽", + "並": "並", + "⎜": "丨", + "⎟": "丨", + "⎢": "丨", + "⎥": "丨", + "⎪": "丨", + "⎮": "丨", + "㇑": "丨", + "ᅵ": "丨", + "ㅣ": "丨", + "⼁": "丨", + "ᆜ": "丨ー", + "ᆘ": "丨ᅡ", + "ᆙ": "丨ᅣ", + "ힽ": "丨ᅣᅩ", + "ힾ": "丨ᅣ丨", + "ힿ": "丨ᅧ", + "ퟀ": "丨ᅧ丨", + "ᆚ": "丨ᅩ", + "ퟁ": "丨ᅩ丨", + "ퟂ": "丨ᅭ", + "ᆛ": "丨ᅮ", + "ퟃ": "丨ᅲ", + "ᆝ": "丨ᆞ", + "ퟄ": "丨丨", + "串": "串", + "丸": "丸", + "丹": "丹", + "乁": "乁", + "㇠": "乙", + "⼄": "乙", + "㇟": "乚", + "⺃": "乚", + "㇖": "乛", + "⺂": "乛", + "⻲": "亀", + "亂": "亂", + "㇚": "亅", + "⼅": "亅", + "了": "了", + "ニ": "二", + "⼆": "二", + "𠄢": "𠄢", + "⼇": "亠", + "亮": "亮", + "⼈": "人", + "イ": "亻", + "⺅": "亻", + "什": "什", + "仌": "仌", + "令": "令", + "你": "你", + "倂": "併", + "倂": "併", + "侀": "侀", + "來": "來", + "例": "例", + "侮": "侮", + "侮": "侮", + "侻": "侻", + "便": "便", + "值": "値", + "倫": "倫", + "偺": "偺", + "備": "備", + "像": "像", + "僚": "僚", + "僧": "僧", + "僧": "僧", + "㒞": "㒞", + "⼉": "儿", + "兀": "兀", + "⺎": "兀", + "充": "充", + "免": "免", + "免": "免", + "兔": "兔", + "兤": "兤", + "⼊": "入", + "內": "內", + "全": "全", + "兩": "兩", + "ハ": "八", + "⼋": "八", + "六": "六", + "具": "具", + "𠔜": "𠔜", + "𠔥": "𠔥", + "冀": "冀", + "㒹": "㒹", + "⼌": "冂", + "再": "再", + "𠕋": "𠕋", + "冒": "冒", + "冕": "冕", + "㒻": "㒻", + "最": "最", + "⼍": "冖", + "冗": "冗", + "冤": "冤", + "⼎": "冫", + "冬": "冬", + "况": "况", + "况": "况", + "冷": "冷", + "凉": "凉", + "凌": "凌", + "凜": "凜", + "凞": "凞", + "⼏": "几", + "𠘺": "𠘺", + "凵": "凵", + "⼐": "凵", + "⼑": "刀", + "⺉": "刂", + "刃": "刃", + "切": "切", + "切": "切", + "列": "列", + "利": "利", + "㓟": "㓟", + "刺": "刺", + "刻": "刻", + "剆": "剆", + "割": "割", + "剷": "剷", + "劉": "劉", + "𠠄": "𠠄", + "カ": "力", + "力": "力", + "⼒": "力", + "劣": "劣", + "㔕": "㔕", + "劳": "劳", + "勇": "勇", + "勇": "勇", + "勉": "勉", + "勉": "勉", + "勒": "勒", + "勞": "勞", + "勤": "勤", + "勤": "勤", + "勵": "勵", + "⼓": "勹", + "勺": "勺", + "勺": "勺", + "包": "包", + "匆": "匆", + "𠣞": "𠣞", + "⼔": "匕", + "北": "北", + "北": "北", + "⼕": "匚", + "⼖": "匸", + "匿": "匿", + "⼗": "十", + "〸": "十", + "〺": "卅", + "卉": "卉", + "࿖": "卍", + "࿕": "卐", + "卑": "卑", + "卑": "卑", + "博": "博", + "ト": "卜", + "⼘": "卜", + "⼙": "卩", + "⺋": "㔾", + "即": "即", + "卵": "卵", + "卽": "卽", + "卿": "卿", + "卿": "卿", + "卿": "卿", + "⼚": "厂", + "𠨬": "𠨬", + "⼛": "厶", + "參": "參", + "⼜": "又", + "及": "及", + "叟": "叟", + "𠭣": "𠭣", + "ロ": "口", + "⼝": "口", + "囗": "口", + "⼞": "口", + "句": "句", + "叫": "叫", + "叱": "叱", + "吆": "吆", + "吏": "吏", + "吝": "吝", + "吸": "吸", + "呂": "呂", + "呈": "呈", + "周": "周", + "咞": "咞", + "咢": "咢", + "咽": "咽", + "䎛": "㖈", + "哶": "哶", + "唐": "唐", + "啓": "啓", + "啟": "啓", + "啕": "啕", + "啣": "啣", + "善": "善", + "善": "善", + "喇": "喇", + "喙": "喙", + "喙": "喙", + "喝": "喝", + "喝": "喝", + "喫": "喫", + "喳": "喳", + "嗀": "嗀", + "嗂": "嗂", + "嗢": "嗢", + "嘆": "嘆", + "嘆": "嘆", + "噑": "噑", + "噴": "噴", + "器": "器", + "囹": "囹", + "圖": "圖", + "圗": "圗", + "⼟": "土", + "士": "土", + "⼠": "土", + "型": "型", + "城": "城", + "㦳": "㘽", + "埴": "埴", + "堍": "堍", + "報": "報", + "堲": "堲", + "塀": "塀", + "塚": "塚", + "塚": "塚", + "塞": "塞", + "填": "塡", + "壿": "墫", + "墬": "墬", + "墳": "墳", + "壘": "壘", + "壟": "壟", + "𡓤": "𡓤", + "壮": "壮", + "売": "売", + "壷": "壷", + "⼡": "夂", + "夆": "夆", + "⼢": "夊", + "タ": "夕", + "⼣": "夕", + "多": "多", + "夢": "夢", + "⼤": "大", + "奄": "奄", + "奈": "奈", + "契": "契", + "奔": "奔", + "奢": "奢", + "女": "女", + "⼥": "女", + "𡚨": "𡚨", + "𡛪": "𡛪", + "姘": "姘", + "姬": "姬", + "娛": "娛", + "娧": "娧", + "婢": "婢", + "婦": "婦", + "嬀": "媯", + "㛮": "㛮", + "㛼": "㛼", + "媵": "媵", + "嬈": "嬈", + "嬨": "嬨", + "嬾": "嬾", + "嬾": "嬾", + "⼦": "子", + "⼧": "宀", + "宅": "宅", + "𡧈": "𡧈", + "寃": "寃", + "寘": "寘", + "寧": "寧", + "寧": "寧", + "寧": "寧", + "寮": "寮", + "寳": "寳", + "𡬘": "𡬘", + "⼨": "寸", + "寿": "寿", + "将": "将", + "⼩": "小", + "尢": "尢", + "⺐": "尢", + "⼪": "尢", + "⺏": "尣", + "㞁": "㞁", + "⼫": "尸", + "尿": "尿", + "屠": "屠", + "屢": "屢", + "層": "層", + "履": "履", + "屮": "屮", + "屮": "屮", + "⼬": "屮", + "𡴋": "𡴋", + "⼭": "山", + "峀": "峀", + "岍": "岍", + "𡷤": "𡷤", + "𡷦": "𡷦", + "崙": "崙", + "嵃": "嵃", + "嵐": "嵐", + "嵫": "嵫", + "嵮": "嵮", + "嵼": "嵼", + "嶲": "嶲", + "嶺": "嶺", + "⼮": "巛", + "巢": "巢", + "エ": "工", + "⼯": "工", + "⼰": "己", + "⺒": "巳", + "㠯": "㠯", + "巽": "巽", + "⼱": "巾", + "帲": "帡", + "帨": "帨", + "帽": "帽", + "幩": "幩", + "㡢": "㡢", + "𢆃": "𢆃", + "⼲": "干", + "年": "年", + "𢆟": "𢆟", + "⺓": "幺", + "⼳": "幺", + "⼴": "广", + "度": "度", + "㡼": "㡼", + "庰": "庰", + "庳": "庳", + "庶": "庶", + "廊": "廊", + "廊": "廊", + "廉": "廉", + "廒": "廒", + "廓": "廓", + "廙": "廙", + "廬": "廬", + "⼵": "廴", + "廾": "廾", + "⼶": "廾", + "𢌱": "𢌱", + "𢌱": "𢌱", + "弄": "弄", + "⼷": "弋", + "⼸": "弓", + "弢": "弢", + "弢": "弢", + "⼹": "彐", + "⺔": "彑", + "当": "当", + "㣇": "㣇", + "⼺": "彡", + "形": "形", + "彩": "彩", + "彫": "彫", + "⼻": "彳", + "律": "律", + "㣣": "㣣", + "徚": "徚", + "復": "復", + "徭": "徭", + "⼼": "心", + "⺖": "忄", + "⺗": "㣺", + "忍": "忍", + "志": "志", + "念": "念", + "忹": "忹", + "怒": "怒", + "怜": "怜", + "恵": "恵", + "㤜": "㤜", + "㤺": "㤺", + "悁": "悁", + "悔": "悔", + "悔": "悔", + "惇": "惇", + "惘": "惘", + "惡": "惡", + "𢛔": "𢛔", + "愈": "愈", + "慨": "慨", + "慄": "慄", + "慈": "慈", + "慌": "慌", + "慌": "慌", + "慎": "慎", + "慎": "慎", + "慠": "慠", + "慺": "慺", + "憎": "憎", + "憎": "憎", + "憎": "憎", + "憐": "憐", + "憤": "憤", + "憯": "憯", + "憲": "憲", + "𢡄": "𢡄", + "𢡊": "𢡊", + "懞": "懞", + "懲": "懲", + "懲": "懲", + "懲": "懲", + "懶": "懶", + "懶": "懶", + "戀": "戀", + "⼽": "戈", + "成": "成", + "戛": "戛", + "戮": "戮", + "戴": "戴", + "⼾": "戶", + "戸": "戶", + "⼿": "手", + "⺘": "扌", + "扝": "扝", + "抱": "抱", + "拉": "拉", + "拏": "拏", + "拓": "拓", + "拔": "拔", + "拼": "拼", + "拾": "拾", + "𢬌": "𢬌", + "挽": "挽", + "捐": "捐", + "捨": "捨", + "捻": "捻", + "掃": "掃", + "掠": "掠", + "掩": "掩", + "揄": "揄", + "揤": "揤", + "摒": "摒", + "𢯱": "𢯱", + "搜": "搜", + "搢": "搢", + "揅": "揅", + "摩": "摩", + "摷": "摷", + "摾": "摾", + "㨮": "㨮", + "搉": "㩁", + "撚": "撚", + "撝": "撝", + "擄": "擄", + "㩬": "㩬", + "⽀": "支", + "⽁": "攴", + "⺙": "攵", + "敏": "敏", + "敏": "敏", + "敖": "敖", + "敬": "敬", + "數": "數", + "𣀊": "𣀊", + "⽂": "文", + "⻫": "斉", + "⽃": "斗", + "料": "料", + "⽄": "斤", + "⽅": "方", + "旅": "旅", + "⽆": "无", + "⺛": "旡", + "既": "既", + "旣": "旣", + "⽇": "日", + "易": "易", + "曶": "㫚", + "㫤": "㫤", + "晉": "晉", + "晩": "晚", + "晴": "晴", + "晴": "晴", + "暑": "暑", + "暑": "暑", + "暈": "暈", + "㬈": "㬈", + "暜": "暜", + "暴": "暴", + "曆": "曆", + "㬙": "㬙", + "𣊸": "𣊸", + "⽈": "曰", + "更": "更", + "書": "書", + "⽉": "月", + "𣍟": "𣍟", + "肦": "朌", + "胐": "朏", + "胊": "朐", + "脁": "朓", + "胶": "㬵", + "朗": "朗", + "朗": "朗", + "朗": "朗", + "脧": "朘", + "望": "望", + "望": "望", + "幐": "㬺", + "䐠": "㬻", + "𣎓": "𣎓", + "膧": "朣", + "𣎜": "𣎜", + "⽊": "木", + "李": "李", + "杓": "杓", + "杖": "杖", + "杞": "杞", + "𣏃": "𣏃", + "柿": "杮", + "杻": "杻", + "枅": "枅", + "林": "林", + "㭉": "㭉", + "𣏕": "𣏕", + "柳": "柳", + "柺": "柺", + "栗": "栗", + "栟": "栟", + "桒": "桒", + "𣑭": "𣑭", + "梁": "梁", + "梅": "梅", + "梅": "梅", + "梎": "梎", + "梨": "梨", + "椔": "椔", + "楂": "楂", + "㮝": "㮝", + "㮝": "㮝", + "槩": "㮣", + "樧": "榝", + "榣": "榣", + "槪": "槪", + "樂": "樂", + "樂": "樂", + "樂": "樂", + "樓": "樓", + "𣚣": "𣚣", + "檨": "檨", + "櫓": "櫓", + "櫛": "櫛", + "欄": "欄", + "㰘": "㰘", + "⽋": "欠", + "次": "次", + "𣢧": "𣢧", + "歔": "歔", + "㱎": "㱎", + "⽌": "止", + "⻭": "歯", + "歲": "歲", + "歷": "歷", + "歹": "歹", + "⽍": "歹", + "⺞": "歺", + "殟": "殟", + "殮": "殮", + "⽎": "殳", + "殺": "殺", + "殺": "殺", + "殺": "殺", + "殻": "殻", + "𣪍": "𣪍", + "⽏": "毋", + "⺟": "母", + "𣫺": "𣫺", + "⽐": "比", + "⽑": "毛", + "⽒": "氏", + "⺠": "民", + "⽓": "气", + "⽔": "水", + "⺡": "氵", + "⺢": "氺", + "汎": "汎", + "汧": "汧", + "沈": "沈", + "沿": "沿", + "泌": "泌", + "泍": "泍", + "泥": "泥", + "𣲼": "𣲼", + "洛": "洛", + "洞": "洞", + "洴": "洴", + "派": "派", + "流": "流", + "流": "流", + "流": "流", + "洖": "洖", + "浩": "浩", + "浪": "浪", + "海": "海", + "海": "海", + "浸": "浸", + "涅": "涅", + "𣴞": "𣴞", + "淋": "淋", + "淚": "淚", + "淪": "淪", + "淹": "淹", + "渚": "渚", + "港": "港", + "湮": "湮", + "潙": "溈", + "滋": "滋", + "滋": "滋", + "溜": "溜", + "溺": "溺", + "滇": "滇", + "滑": "滑", + "滛": "滛", + "㴳": "㴳", + "漏": "漏", + "漢": "漢", + "漢": "漢", + "漣": "漣", + "𣻑": "𣻑", + "潮": "潮", + "𣽞": "𣽞", + "𣾎": "𣾎", + "濆": "濆", + "濫": "濫", + "濾": "濾", + "瀛": "瀛", + "瀞": "瀞", + "瀞": "瀞", + "瀹": "瀹", + "灊": "灊", + "㶖": "㶖", + "⽕": "火", + "⺣": "灬", + "灰": "灰", + "灷": "灷", + "災": "災", + "炙": "炙", + "炭": "炭", + "烈": "烈", + "烙": "烙", + "煮": "煮", + "煮": "煮", + "𤉣": "𤉣", + "煅": "煅", + "煉": "煉", + "𤋮": "𤋮", + "熜": "熜", + "燎": "燎", + "燐": "燐", + "𤎫": "𤎫", + "爐": "爐", + "爛": "爛", + "爨": "爨", + "⽖": "爪", + "爫": "爫", + "⺤": "爫", + "爵": "爵", + "爵": "爵", + "⽗": "父", + "⽘": "爻", + "⺦": "丬", + "⽙": "爿", + "⽚": "片", + "牐": "牐", + "⽛": "牙", + "𤘈": "𤘈", + "⽜": "牛", + "牢": "牢", + "犀": "犀", + "犕": "犕", + "⽝": "犬", + "⺨": "犭", + "犯": "犯", + "狀": "狀", + "𤜵": "𤜵", + "狼": "狼", + "猪": "猪", + "猪": "猪", + "𤠔": "𤠔", + "獵": "獵", + "獺": "獺", + "⽞": "玄", + "率": "率", + "率": "率", + "⽟": "玉", + "王": "王", + "㺬": "㺬", + "玥": "玥", + "玲": "玲", + "㺸": "㺸", + "㺸": "㺸", + "珞": "珞", + "琉": "琉", + "理": "理", + "琢": "琢", + "瑇": "瑇", + "瑜": "瑜", + "瑩": "瑩", + "瑱": "瑱", + "瑱": "瑱", + "璅": "璅", + "璉": "璉", + "璘": "璘", + "瓊": "瓊", + "⽠": "瓜", + "⽡": "瓦", + "㼛": "㼛", + "甆": "甆", + "⽢": "甘", + "⽣": "生", + "甤": "甤", + "⽤": "用", + "⽥": "田", + "画": "画", + "甾": "甾", + "𤰶": "𤰶", + "留": "留", + "略": "略", + "異": "異", + "異": "異", + "𤲒": "𤲒", + "⽦": "疋", + "⽧": "疒", + "痢": "痢", + "瘐": "瘐", + "瘟": "瘟", + "瘝": "瘝", + "療": "療", + "癩": "癩", + "⽨": "癶", + "⽩": "白", + "𤾡": "𤾡", + "𤾸": "𤾸", + "⽪": "皮", + "⽫": "皿", + "𥁄": "𥁄", + "㿼": "㿼", + "益": "益", + "益": "益", + "盛": "盛", + "盧": "盧", + "䀈": "䀈", + "⽬": "目", + "直": "直", + "直": "直", + "𥃲": "𥃲", + "𥃳": "𥃳", + "省": "省", + "䀘": "䀘", + "𥄙": "𥄙", + "眞": "眞", + "真": "真", + "真": "真", + "𥄳": "𥄳", + "着": "着", + "睊": "睊", + "睊": "睊", + "鿃": "䀹", + "䀹": "䀹", + "䀹": "䀹", + "晣": "䀿", + "䁆": "䁆", + "瞋": "瞋", + "𥉉": "𥉉", + "瞧": "瞧", + "⽭": "矛", + "⽮": "矢", + "⽯": "石", + "䂖": "䂖", + "𥐝": "𥐝", + "硏": "研", + "硎": "硎", + "硫": "硫", + "碌": "碌", + "碌": "碌", + "碑": "碑", + "磊": "磊", + "磌": "磌", + "磌": "磌", + "磻": "磻", + "䃣": "䃣", + "礪": "礪", + "⽰": "示", + "⺭": "礻", + "礼": "礼", + "社": "社", + "祈": "祈", + "祉": "祉", + "𥘦": "𥘦", + "祐": "祐", + "祖": "祖", + "祖": "祖", + "祝": "祝", + "神": "神", + "祥": "祥", + "視": "視", + "視": "視", + "祿": "祿", + "𥚚": "𥚚", + "禍": "禍", + "禎": "禎", + "福": "福", + "福": "福", + "𥛅": "𥛅", + "禮": "禮", + "⽱": "禸", + "⽲": "禾", + "秊": "秊", + "䄯": "䄯", + "秫": "秫", + "稜": "稜", + "穊": "穊", + "穀": "穀", + "穀": "穀", + "穏": "穏", + "⽳": "穴", + "突": "突", + "𥥼": "𥥼", + "窱": "窱", + "立": "立", + "⽴": "立", + "⻯": "竜", + "𥪧": "𥪧", + "𥪧": "𥪧", + "竮": "竮", + "⽵": "竹", + "笠": "笠", + "節": "節", + "節": "節", + "䈂": "䈂", + "𥮫": "𥮫", + "篆": "篆", + "䈧": "䈧", + "築": "築", + "𥲀": "𥲀", + "𥳐": "𥳐", + "簾": "簾", + "籠": "籠", + "⽶": "米", + "类": "类", + "粒": "粒", + "精": "精", + "糒": "糒", + "糖": "糖", + "糨": "糨", + "䊠": "䊠", + "糣": "糣", + "糧": "糧", + "⽷": "糸", + "⺯": "糹", + "𥾆": "𥾆", + "紀": "紀", + "紐": "紐", + "索": "索", + "累": "累", + "絶": "絕", + "絣": "絣", + "絛": "絛", + "綠": "綠", + "綾": "綾", + "緇": "緇", + "練": "練", + "練": "練", + "練": "練", + "縂": "縂", + "䌁": "䌁", + "縉": "縉", + "縷": "縷", + "繁": "繁", + "繅": "繅", + "𦇚": "𦇚", + "䌴": "䌴", + "⽸": "缶", + "𦈨": "𦈨", + "缾": "缾", + "𦉇": "𦉇", + "⽹": "网", + "⺫": "罒", + "⺲": "罒", + "⺱": "罓", + "䍙": "䍙", + "署": "署", + "𦋙": "𦋙", + "罹": "罹", + "罺": "罺", + "羅": "羅", + "𦌾": "𦌾", + "⽺": "羊", + "羕": "羕", + "羚": "羚", + "羽": "羽", + "⽻": "羽", + "翺": "翺", + "老": "老", + "⽼": "老", + "⺹": "耂", + "者": "者", + "者": "者", + "者": "者", + "⽽": "而", + "𦓚": "𦓚", + "⽾": "耒", + "𦔣": "𦔣", + "⽿": "耳", + "聆": "聆", + "聠": "聠", + "𦖨": "𦖨", + "聯": "聯", + "聰": "聰", + "聾": "聾", + "⾀": "聿", + "⺺": "肀", + "⾁": "肉", + "肋": "肋", + "肭": "肭", + "育": "育", + "䏕": "䏕", + "䏙": "䏙", + "腁": "胼", + "脃": "脃", + "脾": "脾", + "䐋": "䐋", + "朡": "朡", + "𦞧": "𦞧", + "𦞵": "𦞵", + "朦": "䑃", + "臘": "臘", + "⾂": "臣", + "臨": "臨", + "⾃": "自", + "臭": "臭", + "⾄": "至", + "⾅": "臼", + "舁": "舁", + "舁": "舁", + "舄": "舄", + "⾆": "舌", + "舘": "舘", + "⾇": "舛", + "⾈": "舟", + "䑫": "䑫", + "⾉": "艮", + "良": "良", + "⾊": "色", + "⾋": "艸", + "艹": "艹", + "艹": "艹", + "⺾": "艹", + "⺿": "艹", + "⻀": "艹", + "芋": "芋", + "芑": "芑", + "芝": "芝", + "花": "花", + "芳": "芳", + "芽": "芽", + "若": "若", + "若": "若", + "苦": "苦", + "𦬼": "𦬼", + "茶": "茶", + "荒": "荒", + "荣": "荣", + "茝": "茝", + "茣": "茣", + "莽": "莽", + "荓": "荓", + "菉": "菉", + "菊": "菊", + "菌": "菌", + "菜": "菜", + "菧": "菧", + "華": "華", + "菱": "菱", + "著": "著", + "著": "著", + "𦰶": "𦰶", + "莭": "莭", + "落": "落", + "葉": "葉", + "蔿": "蒍", + "𦳕": "𦳕", + "𦵫": "𦵫", + "蓮": "蓮", + "蓱": "蓱", + "蓳": "蓳", + "蓼": "蓼", + "蔖": "蔖", + "䔫": "䔫", + "蕤": "蕤", + "𦼬": "𦼬", + "藍": "藍", + "䕝": "䕝", + "𦾱": "𦾱", + "䕡": "䕡", + "藺": "藺", + "蘆": "蘆", + "䕫": "䕫", + "蘒": "蘒", + "蘭": "蘭", + "𧃒": "𧃒", + "虁": "蘷", + "蘿": "蘿", + "⾌": "虍", + "⻁": "虎", + "虐": "虐", + "虜": "虜", + "虜": "虜", + "虧": "虧", + "虩": "虩", + "⾍": "虫", + "蚩": "蚩", + "蚈": "蚈", + "蛢": "蛢", + "蜎": "蜎", + "蜨": "蜨", + "蝫": "蝫", + "蟡": "蟡", + "蝹": "蝹", + "蝹": "蝹", + "螆": "螆", + "䗗": "䗗", + "𧏊": "𧏊", + "螺": "螺", + "蠁": "蠁", + "䗹": "䗹", + "蠟": "蠟", + "⾎": "血", + "行": "行", + "⾏": "行", + "衠": "衠", + "衣": "衣", + "⾐": "衣", + "⻂": "衤", + "裂": "裂", + "𧙧": "𧙧", + "裏": "裏", + "裗": "裗", + "裞": "裞", + "裡": "裡", + "裸": "裸", + "裺": "裺", + "䘵": "䘵", + "褐": "褐", + "襁": "襁", + "襤": "襤", + "⾑": "襾", + "⻄": "西", + "⻃": "覀", + "覆": "覆", + "見": "見", + "⾒": "見", + "𧢮": "𧢮", + "⻅": "见", + "⾓": "角", + "⾔": "言", + "𧥦": "𧥦", + "詽": "訮", + "訞": "䚶", + "䚾": "䚾", + "䛇": "䛇", + "誠": "誠", + "說": "說", + "說": "說", + "調": "調", + "請": "請", + "諒": "諒", + "論": "論", + "諭": "諭", + "諭": "諭", + "諸": "諸", + "諸": "諸", + "諾": "諾", + "諾": "諾", + "謁": "謁", + "謁": "謁", + "謹": "謹", + "謹": "謹", + "識": "識", + "讀": "讀", + "讏": "讆", + "變": "變", + "變": "變", + "⻈": "讠", + "⾕": "谷", + "⾖": "豆", + "豈": "豈", + "豕": "豕", + "⾗": "豕", + "豣": "豜", + "⾘": "豸", + "𧲨": "𧲨", + "⾙": "貝", + "貫": "貫", + "賁": "賁", + "賂": "賂", + "賈": "賈", + "賓": "賓", + "贈": "贈", + "贈": "贈", + "贛": "贛", + "⻉": "贝", + "⾚": "赤", + "⾛": "走", + "起": "起", + "趆": "赿", + "𧻓": "𧻓", + "𧼯": "𧼯", + "⾜": "足", + "跋": "跋", + "趼": "趼", + "跺": "跥", + "路": "路", + "跰": "跰", + "躛": "躗", + "⾝": "身", + "車": "車", + "⾞": "車", + "軔": "軔", + "輧": "軿", + "輦": "輦", + "輪": "輪", + "輸": "輸", + "輸": "輸", + "輻": "輻", + "轢": "轢", + "⻋": "车", + "⾟": "辛", + "辞": "辞", + "辰": "辰", + "⾠": "辰", + "⾡": "辵", + "辶": "辶", + "⻌": "辶", + "⻍": "辶", + "巡": "巡", + "連": "連", + "逸": "逸", + "逸": "逸", + "遲": "遲", + "遼": "遼", + "𨗒": "𨗒", + "𨗭": "𨗭", + "邏": "邏", + "⾢": "邑", + "邔": "邔", + "郎": "郎", + "郞": "郎", + "郞": "郎", + "郱": "郱", + "都": "都", + "𨜮": "𨜮", + "鄑": "鄑", + "鄛": "鄛", + "⾣": "酉", + "酪": "酪", + "醙": "醙", + "醴": "醴", + "⾤": "釆", + "里": "里", + "⾥": "里", + "量": "量", + "金": "金", + "⾦": "金", + "鈴": "鈴", + "鈸": "鈸", + "鉶": "鉶", + "鋗": "鋗", + "鋘": "鋘", + "鉼": "鉼", + "錄": "錄", + "鍊": "鍊", + "鎮": "鎭", + "鏹": "鏹", + "鐕": "鐕", + "𨯺": "𨯺", + "⻐": "钅", + "⻑": "長", + "⾧": "長", + "⻒": "镸", + "⻓": "长", + "⾨": "門", + "開": "開", + "䦕": "䦕", + "閭": "閭", + "閷": "閷", + "𨵷": "𨵷", + "⻔": "门", + "⾩": "阜", + "⻏": "阝", + "⻖": "阝", + "阮": "阮", + "陋": "陋", + "降": "降", + "陵": "陵", + "陸": "陸", + "陼": "陼", + "隆": "隆", + "隣": "隣", + "䧦": "䧦", + "⾪": "隶", + "隷": "隷", + "隸": "隷", + "隸": "隷", + "⾫": "隹", + "雃": "雃", + "離": "離", + "難": "難", + "難": "難", + "⾬": "雨", + "零": "零", + "雷": "雷", + "霣": "霣", + "𩅅": "𩅅", + "露": "露", + "靈": "靈", + "⾭": "靑", + "⻘": "青", + "靖": "靖", + "靖": "靖", + "𩇟": "𩇟", + "⾮": "非", + "⾯": "面", + "𩈚": "𩈚", + "⾰": "革", + "䩮": "䩮", + "䩶": "䩶", + "⾱": "韋", + "韛": "韛", + "韠": "韠", + "⻙": "韦", + "⾲": "韭", + "𩐊": "𩐊", + "⾳": "音", + "響": "響", + "響": "響", + "⾴": "頁", + "䪲": "䪲", + "頋": "頋", + "頋": "頋", + "頋": "頋", + "領": "領", + "頩": "頩", + "𩒖": "𩒖", + "頻": "頻", + "頻": "頻", + "類": "類", + "⻚": "页", + "⾵": "風", + "𩖶": "𩖶", + "⻛": "风", + "⾶": "飛", + "⻜": "飞", + "⻝": "食", + "⾷": "食", + "⻟": "飠", + "飢": "飢", + "飯": "飯", + "飼": "飼", + "䬳": "䬳", + "館": "館", + "餩": "餩", + "⻠": "饣", + "⾸": "首", + "⾹": "香", + "馧": "馧", + "⾺": "馬", + "駂": "駂", + "駱": "駱", + "駾": "駾", + "驪": "驪", + "⻢": "马", + "⾻": "骨", + "䯎": "䯎", + "⾼": "高", + "⾽": "髟", + "𩬰": "𩬰", + "鬒": "鬒", + "鬒": "鬒", + "⾾": "鬥", + "⾿": "鬯", + "⿀": "鬲", + "⿁": "鬼", + "⻤": "鬼", + "⿂": "魚", + "魯": "魯", + "鱀": "鱀", + "鱗": "鱗", + "⻥": "鱼", + "⿃": "鳥", + "鳽": "鳽", + "䳎": "䳎", + "鵧": "鵧", + "䳭": "䳭", + "𪃎": "𪃎", + "鶴": "鶴", + "𪄅": "𪄅", + "䳸": "䳸", + "鷺": "鷺", + "𪈎": "𪈎", + "鸞": "鸞", + "鹃": "鹂", + "⿄": "鹵", + "鹿": "鹿", + "⿅": "鹿", + "𪊑": "𪊑", + "麗": "麗", + "麟": "麟", + "⿆": "麥", + "⻨": "麦", + "麻": "麻", + "⿇": "麻", + "𪎒": "𪎒", + "⿈": "黃", + "⻩": "黄", + "⿉": "黍", + "黎": "黎", + "䵖": "䵖", + "⿊": "黑", + "黒": "黑", + "墨": "墨", + "黹": "黹", + "⿋": "黹", + "⿌": "黽", + "鼅": "鼅", + "黾": "黾", + "⿍": "鼎", + "鼏": "鼏", + "⿎": "鼓", + "鼖": "鼖", + "⿏": "鼠", + "鼻": "鼻", + "⿐": "鼻", + "齃": "齃", + "⿑": "齊", + "⻬": "齐", + "⿒": "齒", + "𪘀": "𪘀", + "⻮": "齿", + "龍": "龍", + "⿓": "龍", + "龎": "龎", + "⻰": "龙", + "龜": "龜", + "龜": "龜", + "龜": "龜", + "⿔": "龜", + "⻳": "龟", + "⿕": "龠" +} +},{}],282:[function(require,module,exports){ +'use strict'; + + +var data = require('./data.json'); + +function escapeRegexp(str) { + return str.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); +} + +var REPLACE_RE = RegExp(Object.keys(data).map(escapeRegexp).join('|'), 'g'); + +function replace_fn(match) { + return data[match]; +} + +function unhomoglyph(str) { + return str.replace(REPLACE_RE, replace_fn); +} + +module.exports = unhomoglyph; + +},{"./data.json":281}],283:[function(require,module,exports){ +(function (global){(function (){ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],284:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],285:[function(require,module,exports){ +// Currently in sync with Node.js lib/internal/util/types.js +// https://github.com/nodejs/node/commit/112cc7c27551254aa2b17098fb774867f05ed0d9 + +'use strict'; + +var isArgumentsObject = require('is-arguments'); +var isGeneratorFunction = require('is-generator-function'); +var whichTypedArray = require('which-typed-array'); +var isTypedArray = require('is-typed-array'); + +function uncurryThis(f) { + return f.call.bind(f); +} + +var BigIntSupported = typeof BigInt !== 'undefined'; +var SymbolSupported = typeof Symbol !== 'undefined'; + +var ObjectToString = uncurryThis(Object.prototype.toString); + +var numberValue = uncurryThis(Number.prototype.valueOf); +var stringValue = uncurryThis(String.prototype.valueOf); +var booleanValue = uncurryThis(Boolean.prototype.valueOf); + +if (BigIntSupported) { + var bigIntValue = uncurryThis(BigInt.prototype.valueOf); +} + +if (SymbolSupported) { + var symbolValue = uncurryThis(Symbol.prototype.valueOf); +} + +function checkBoxedPrimitive(value, prototypeValueOf) { + if (typeof value !== 'object') { + return false; + } + try { + prototypeValueOf(value); + return true; + } catch(e) { + return false; + } +} + +exports.isArgumentsObject = isArgumentsObject; +exports.isGeneratorFunction = isGeneratorFunction; +exports.isTypedArray = isTypedArray; + +// Taken from here and modified for better browser support +// https://github.com/sindresorhus/p-is-promise/blob/cda35a513bda03f977ad5cde3a079d237e82d7ef/index.js +function isPromise(input) { + return ( + ( + typeof Promise !== 'undefined' && + input instanceof Promise + ) || + ( + input !== null && + typeof input === 'object' && + typeof input.then === 'function' && + typeof input.catch === 'function' + ) + ); +} +exports.isPromise = isPromise; + +function isArrayBufferView(value) { + if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView) { + return ArrayBuffer.isView(value); + } + + return ( + isTypedArray(value) || + isDataView(value) + ); +} +exports.isArrayBufferView = isArrayBufferView; + + +function isUint8Array(value) { + return whichTypedArray(value) === 'Uint8Array'; +} +exports.isUint8Array = isUint8Array; + +function isUint8ClampedArray(value) { + return whichTypedArray(value) === 'Uint8ClampedArray'; +} +exports.isUint8ClampedArray = isUint8ClampedArray; + +function isUint16Array(value) { + return whichTypedArray(value) === 'Uint16Array'; +} +exports.isUint16Array = isUint16Array; + +function isUint32Array(value) { + return whichTypedArray(value) === 'Uint32Array'; +} +exports.isUint32Array = isUint32Array; + +function isInt8Array(value) { + return whichTypedArray(value) === 'Int8Array'; +} +exports.isInt8Array = isInt8Array; + +function isInt16Array(value) { + return whichTypedArray(value) === 'Int16Array'; +} +exports.isInt16Array = isInt16Array; + +function isInt32Array(value) { + return whichTypedArray(value) === 'Int32Array'; +} +exports.isInt32Array = isInt32Array; + +function isFloat32Array(value) { + return whichTypedArray(value) === 'Float32Array'; +} +exports.isFloat32Array = isFloat32Array; + +function isFloat64Array(value) { + return whichTypedArray(value) === 'Float64Array'; +} +exports.isFloat64Array = isFloat64Array; + +function isBigInt64Array(value) { + return whichTypedArray(value) === 'BigInt64Array'; +} +exports.isBigInt64Array = isBigInt64Array; + +function isBigUint64Array(value) { + return whichTypedArray(value) === 'BigUint64Array'; +} +exports.isBigUint64Array = isBigUint64Array; + +function isMapToString(value) { + return ObjectToString(value) === '[object Map]'; +} +isMapToString.working = ( + typeof Map !== 'undefined' && + isMapToString(new Map()) +); + +function isMap(value) { + if (typeof Map === 'undefined') { + return false; + } + + return isMapToString.working + ? isMapToString(value) + : value instanceof Map; +} +exports.isMap = isMap; + +function isSetToString(value) { + return ObjectToString(value) === '[object Set]'; +} +isSetToString.working = ( + typeof Set !== 'undefined' && + isSetToString(new Set()) +); +function isSet(value) { + if (typeof Set === 'undefined') { + return false; + } + + return isSetToString.working + ? isSetToString(value) + : value instanceof Set; +} +exports.isSet = isSet; + +function isWeakMapToString(value) { + return ObjectToString(value) === '[object WeakMap]'; +} +isWeakMapToString.working = ( + typeof WeakMap !== 'undefined' && + isWeakMapToString(new WeakMap()) +); +function isWeakMap(value) { + if (typeof WeakMap === 'undefined') { + return false; + } + + return isWeakMapToString.working + ? isWeakMapToString(value) + : value instanceof WeakMap; +} +exports.isWeakMap = isWeakMap; + +function isWeakSetToString(value) { + return ObjectToString(value) === '[object WeakSet]'; +} +isWeakSetToString.working = ( + typeof WeakSet !== 'undefined' && + isWeakSetToString(new WeakSet()) +); +function isWeakSet(value) { + return isWeakSetToString(value); +} +exports.isWeakSet = isWeakSet; + +function isArrayBufferToString(value) { + return ObjectToString(value) === '[object ArrayBuffer]'; +} +isArrayBufferToString.working = ( + typeof ArrayBuffer !== 'undefined' && + isArrayBufferToString(new ArrayBuffer()) +); +function isArrayBuffer(value) { + if (typeof ArrayBuffer === 'undefined') { + return false; + } + + return isArrayBufferToString.working + ? isArrayBufferToString(value) + : value instanceof ArrayBuffer; +} +exports.isArrayBuffer = isArrayBuffer; + +function isDataViewToString(value) { + return ObjectToString(value) === '[object DataView]'; +} +isDataViewToString.working = ( + typeof ArrayBuffer !== 'undefined' && + typeof DataView !== 'undefined' && + isDataViewToString(new DataView(new ArrayBuffer(1), 0, 1)) +); +function isDataView(value) { + if (typeof DataView === 'undefined') { + return false; + } + + return isDataViewToString.working + ? isDataViewToString(value) + : value instanceof DataView; +} +exports.isDataView = isDataView; + +// Store a copy of SharedArrayBuffer in case it's deleted elsewhere +var SharedArrayBufferCopy = typeof SharedArrayBuffer !== 'undefined' ? SharedArrayBuffer : undefined; +function isSharedArrayBufferToString(value) { + return ObjectToString(value) === '[object SharedArrayBuffer]'; +} +function isSharedArrayBuffer(value) { + if (typeof SharedArrayBufferCopy === 'undefined') { + return false; + } + + if (typeof isSharedArrayBufferToString.working === 'undefined') { + isSharedArrayBufferToString.working = isSharedArrayBufferToString(new SharedArrayBufferCopy()); + } + + return isSharedArrayBufferToString.working + ? isSharedArrayBufferToString(value) + : value instanceof SharedArrayBufferCopy; +} +exports.isSharedArrayBuffer = isSharedArrayBuffer; + +function isAsyncFunction(value) { + return ObjectToString(value) === '[object AsyncFunction]'; +} +exports.isAsyncFunction = isAsyncFunction; + +function isMapIterator(value) { + return ObjectToString(value) === '[object Map Iterator]'; +} +exports.isMapIterator = isMapIterator; + +function isSetIterator(value) { + return ObjectToString(value) === '[object Set Iterator]'; +} +exports.isSetIterator = isSetIterator; + +function isGeneratorObject(value) { + return ObjectToString(value) === '[object Generator]'; +} +exports.isGeneratorObject = isGeneratorObject; + +function isWebAssemblyCompiledModule(value) { + return ObjectToString(value) === '[object WebAssembly.Module]'; +} +exports.isWebAssemblyCompiledModule = isWebAssemblyCompiledModule; + +function isNumberObject(value) { + return checkBoxedPrimitive(value, numberValue); +} +exports.isNumberObject = isNumberObject; + +function isStringObject(value) { + return checkBoxedPrimitive(value, stringValue); +} +exports.isStringObject = isStringObject; + +function isBooleanObject(value) { + return checkBoxedPrimitive(value, booleanValue); +} +exports.isBooleanObject = isBooleanObject; + +function isBigIntObject(value) { + return BigIntSupported && checkBoxedPrimitive(value, bigIntValue); +} +exports.isBigIntObject = isBigIntObject; + +function isSymbolObject(value) { + return SymbolSupported && checkBoxedPrimitive(value, symbolValue); +} +exports.isSymbolObject = isSymbolObject; + +function isBoxedPrimitive(value) { + return ( + isNumberObject(value) || + isStringObject(value) || + isBooleanObject(value) || + isBigIntObject(value) || + isSymbolObject(value) + ); +} +exports.isBoxedPrimitive = isBoxedPrimitive; + +function isAnyArrayBuffer(value) { + return typeof Uint8Array !== 'undefined' && ( + isArrayBuffer(value) || + isSharedArrayBuffer(value) + ); +} +exports.isAnyArrayBuffer = isAnyArrayBuffer; + +['isProxy', 'isExternal', 'isModuleNamespaceObject'].forEach(function(method) { + Object.defineProperty(exports, method, { + enumerable: false, + value: function() { + throw new Error(method + ' is not supported in userland'); + } + }); +}); + +},{"is-arguments":147,"is-generator-function":149,"is-typed-array":150,"which-typed-array":303}],286:[function(require,module,exports){ +(function (process){(function (){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || + function getOwnPropertyDescriptors(obj) { + var keys = Object.keys(obj); + var descriptors = {}; + for (var i = 0; i < keys.length; i++) { + descriptors[keys[i]] = Object.getOwnPropertyDescriptor(obj, keys[i]); + } + return descriptors; + }; + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + if (typeof process !== 'undefined' && process.noDeprecation === true) { + return fn; + } + + // Allow for deprecating things in the process of starting up. + if (typeof process === 'undefined') { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnvRegex = /^$/; + +if (process.env.NODE_DEBUG) { + var debugEnv = process.env.NODE_DEBUG; + debugEnv = debugEnv.replace(/[|\\{}()[\]^$+?.]/g, '\\$&') + .replace(/\*/g, '.*') + .replace(/,/g, '$|^') + .toUpperCase(); + debugEnvRegex = new RegExp('^' + debugEnv + '$', 'i'); +} +exports.debuglog = function(set) { + set = set.toUpperCase(); + if (!debugs[set]) { + if (debugEnvRegex.test(set)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').slice(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.slice(1, -1); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +exports.types = require('./support/types'); + +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; +exports.types.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; +exports.types.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; +exports.types.isNativeError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +var kCustomPromisifiedSymbol = typeof Symbol !== 'undefined' ? Symbol('util.promisify.custom') : undefined; + +exports.promisify = function promisify(original) { + if (typeof original !== 'function') + throw new TypeError('The "original" argument must be of type Function'); + + if (kCustomPromisifiedSymbol && original[kCustomPromisifiedSymbol]) { + var fn = original[kCustomPromisifiedSymbol]; + if (typeof fn !== 'function') { + throw new TypeError('The "util.promisify.custom" argument must be of type Function'); + } + Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, enumerable: false, writable: false, configurable: true + }); + return fn; + } + + function fn() { + var promiseResolve, promiseReject; + var promise = new Promise(function (resolve, reject) { + promiseResolve = resolve; + promiseReject = reject; + }); + + var args = []; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + args.push(function (err, value) { + if (err) { + promiseReject(err); + } else { + promiseResolve(value); + } + }); + + try { + original.apply(this, args); + } catch (err) { + promiseReject(err); + } + + return promise; + } + + Object.setPrototypeOf(fn, Object.getPrototypeOf(original)); + + if (kCustomPromisifiedSymbol) Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, enumerable: false, writable: false, configurable: true + }); + return Object.defineProperties( + fn, + getOwnPropertyDescriptors(original) + ); +} + +exports.promisify.custom = kCustomPromisifiedSymbol + +function callbackifyOnRejected(reason, cb) { + // `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M). + // Because `null` is a special error value in callbacks which means "no error + // occurred", we error-wrap so the callback consumer can distinguish between + // "the promise rejected with null" or "the promise fulfilled with undefined". + if (!reason) { + var newReason = new Error('Promise was rejected with a falsy value'); + newReason.reason = reason; + reason = newReason; + } + return cb(reason); +} + +function callbackify(original) { + if (typeof original !== 'function') { + throw new TypeError('The "original" argument must be of type Function'); + } + + // We DO NOT return the promise as it gives the user a false sense that + // the promise is actually somehow related to the callback's execution + // and that the callback throwing will reject the promise. + function callbackified() { + var args = []; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + + var maybeCb = args.pop(); + if (typeof maybeCb !== 'function') { + throw new TypeError('The last argument must be of type Function'); + } + var self = this; + var cb = function() { + return maybeCb.apply(self, arguments); + }; + // In true node style we process the callback on `nextTick` with all the + // implications (stack, `uncaughtException`, `async_hooks`) + original.apply(this, args) + .then(function(ret) { process.nextTick(cb.bind(null, null, ret)) }, + function(rej) { process.nextTick(callbackifyOnRejected.bind(null, rej, cb)) }); + } + + Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original)); + Object.defineProperties(callbackified, + getOwnPropertyDescriptors(original)); + return callbackified; +} +exports.callbackify = callbackify; + +}).call(this)}).call(this,require('_process')) + +},{"./support/isBuffer":284,"./support/types":285,"_process":237,"inherits":146}],287:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "NIL", { + enumerable: true, + get: function () { + return _nil.default; + } +}); +Object.defineProperty(exports, "parse", { + enumerable: true, + get: function () { + return _parse.default; + } +}); +Object.defineProperty(exports, "stringify", { + enumerable: true, + get: function () { + return _stringify.default; + } +}); +Object.defineProperty(exports, "v1", { + enumerable: true, + get: function () { + return _v.default; + } +}); +Object.defineProperty(exports, "v3", { + enumerable: true, + get: function () { + return _v2.default; + } +}); +Object.defineProperty(exports, "v4", { + enumerable: true, + get: function () { + return _v3.default; + } +}); +Object.defineProperty(exports, "v5", { + enumerable: true, + get: function () { + return _v4.default; + } +}); +Object.defineProperty(exports, "validate", { + enumerable: true, + get: function () { + return _validate.default; + } +}); +Object.defineProperty(exports, "version", { + enumerable: true, + get: function () { + return _version.default; + } +}); + +var _v = _interopRequireDefault(require("./v1.js")); + +var _v2 = _interopRequireDefault(require("./v3.js")); + +var _v3 = _interopRequireDefault(require("./v4.js")); + +var _v4 = _interopRequireDefault(require("./v5.js")); + +var _nil = _interopRequireDefault(require("./nil.js")); + +var _version = _interopRequireDefault(require("./version.js")); + +var _validate = _interopRequireDefault(require("./validate.js")); + +var _stringify = _interopRequireDefault(require("./stringify.js")); + +var _parse = _interopRequireDefault(require("./parse.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +},{"./nil.js":290,"./parse.js":291,"./stringify.js":295,"./v1.js":296,"./v3.js":297,"./v4.js":299,"./v5.js":300,"./validate.js":301,"./version.js":302}],288:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +/* + * Browser-compatible JavaScript MD5 + * + * Modification of JavaScript MD5 + * https://github.com/blueimp/JavaScript-MD5 + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + * + * Based on + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ +function md5(bytes) { + if (typeof bytes === 'string') { + const msg = unescape(encodeURIComponent(bytes)); // UTF8 escape + + bytes = new Uint8Array(msg.length); + + for (let i = 0; i < msg.length; ++i) { + bytes[i] = msg.charCodeAt(i); + } + } + + return md5ToHexEncodedArray(wordsToMd5(bytesToWords(bytes), bytes.length * 8)); +} +/* + * Convert an array of little-endian words to an array of bytes + */ + + +function md5ToHexEncodedArray(input) { + const output = []; + const length32 = input.length * 32; + const hexTab = '0123456789abcdef'; + + for (let i = 0; i < length32; i += 8) { + const x = input[i >> 5] >>> i % 32 & 0xff; + const hex = parseInt(hexTab.charAt(x >>> 4 & 0x0f) + hexTab.charAt(x & 0x0f), 16); + output.push(hex); + } + + return output; +} +/** + * Calculate output length with padding and bit length + */ + + +function getOutputLength(inputLength8) { + return (inputLength8 + 64 >>> 9 << 4) + 14 + 1; +} +/* + * Calculate the MD5 of an array of little-endian words, and a bit length. + */ + + +function wordsToMd5(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << len % 32; + x[getOutputLength(len) - 1] = len; + let a = 1732584193; + let b = -271733879; + let c = -1732584194; + let d = 271733878; + + for (let i = 0; i < x.length; i += 16) { + const olda = a; + const oldb = b; + const oldc = c; + const oldd = d; + a = md5ff(a, b, c, d, x[i], 7, -680876936); + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5gg(b, c, d, a, x[i], 20, -373897302); + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); + a = md5hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5hh(d, a, b, c, x[i], 11, -358537222); + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); + a = md5ii(a, b, c, d, x[i], 6, -198630844); + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); + a = safeAdd(a, olda); + b = safeAdd(b, oldb); + c = safeAdd(c, oldc); + d = safeAdd(d, oldd); + } + + return [a, b, c, d]; +} +/* + * Convert an array bytes to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + */ + + +function bytesToWords(input) { + if (input.length === 0) { + return []; + } + + const length8 = input.length * 8; + const output = new Uint32Array(getOutputLength(length8)); + + for (let i = 0; i < length8; i += 8) { + output[i >> 5] |= (input[i / 8] & 0xff) << i % 32; + } + + return output; +} +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + + +function safeAdd(x, y) { + const lsw = (x & 0xffff) + (y & 0xffff); + const msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return msw << 16 | lsw & 0xffff; +} +/* + * Bitwise rotate a 32-bit number to the left. + */ + + +function bitRotateLeft(num, cnt) { + return num << cnt | num >>> 32 - cnt; +} +/* + * These functions implement the four basic operations the algorithm uses. + */ + + +function md5cmn(q, a, b, x, s, t) { + return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); +} + +function md5ff(a, b, c, d, x, s, t) { + return md5cmn(b & c | ~b & d, a, b, x, s, t); +} + +function md5gg(a, b, c, d, x, s, t) { + return md5cmn(b & d | c & ~d, a, b, x, s, t); +} + +function md5hh(a, b, c, d, x, s, t) { + return md5cmn(b ^ c ^ d, a, b, x, s, t); +} + +function md5ii(a, b, c, d, x, s, t) { + return md5cmn(c ^ (b | ~d), a, b, x, s, t); +} + +var _default = md5; +exports.default = _default; +},{}],289:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto); +var _default = { + randomUUID +}; +exports.default = _default; +},{}],290:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = '00000000-0000-0000-0000-000000000000'; +exports.default = _default; +},{}],291:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(require("./validate.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function parse(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + let v; + const arr = new Uint8Array(16); // Parse ########-....-....-....-............ + + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = v >>> 16 & 0xff; + arr[2] = v >>> 8 & 0xff; + arr[3] = v & 0xff; // Parse ........-####-....-....-............ + + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; // Parse ........-....-####-....-............ + + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; // Parse ........-....-....-####-............ + + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + + arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; + arr[11] = v / 0x100000000 & 0xff; + arr[12] = v >>> 24 & 0xff; + arr[13] = v >>> 16 & 0xff; + arr[14] = v >>> 8 & 0xff; + arr[15] = v & 0xff; + return arr; +} + +var _default = parse; +exports.default = _default; +},{"./validate.js":301}],292:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +exports.default = _default; +},{}],293:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = rng; +// Unique ID creation requires a high quality random # generator. In the browser we therefore +// require the crypto API and do not support built-in fallback to lower quality random number +// generators (like Math.random()). +let getRandomValues; +const rnds8 = new Uint8Array(16); + +function rng() { + // lazy load so that environments that need to polyfill have a chance to do so + if (!getRandomValues) { + // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. + getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); + + if (!getRandomValues) { + throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); + } + } + + return getRandomValues(rnds8); +} +},{}],294:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +// Adapted from Chris Veness' SHA1 code at +// http://www.movable-type.co.uk/scripts/sha1.html +function f(s, x, y, z) { + switch (s) { + case 0: + return x & y ^ ~x & z; + + case 1: + return x ^ y ^ z; + + case 2: + return x & y ^ x & z ^ y & z; + + case 3: + return x ^ y ^ z; + } +} + +function ROTL(x, n) { + return x << n | x >>> 32 - n; +} + +function sha1(bytes) { + const K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6]; + const H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]; + + if (typeof bytes === 'string') { + const msg = unescape(encodeURIComponent(bytes)); // UTF8 escape + + bytes = []; + + for (let i = 0; i < msg.length; ++i) { + bytes.push(msg.charCodeAt(i)); + } + } else if (!Array.isArray(bytes)) { + // Convert Array-like to Array + bytes = Array.prototype.slice.call(bytes); + } + + bytes.push(0x80); + const l = bytes.length / 4 + 2; + const N = Math.ceil(l / 16); + const M = new Array(N); + + for (let i = 0; i < N; ++i) { + const arr = new Uint32Array(16); + + for (let j = 0; j < 16; ++j) { + arr[j] = bytes[i * 64 + j * 4] << 24 | bytes[i * 64 + j * 4 + 1] << 16 | bytes[i * 64 + j * 4 + 2] << 8 | bytes[i * 64 + j * 4 + 3]; + } + + M[i] = arr; + } + + M[N - 1][14] = (bytes.length - 1) * 8 / Math.pow(2, 32); + M[N - 1][14] = Math.floor(M[N - 1][14]); + M[N - 1][15] = (bytes.length - 1) * 8 & 0xffffffff; + + for (let i = 0; i < N; ++i) { + const W = new Uint32Array(80); + + for (let t = 0; t < 16; ++t) { + W[t] = M[i][t]; + } + + for (let t = 16; t < 80; ++t) { + W[t] = ROTL(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1); + } + + let a = H[0]; + let b = H[1]; + let c = H[2]; + let d = H[3]; + let e = H[4]; + + for (let t = 0; t < 80; ++t) { + const s = Math.floor(t / 20); + const T = ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t] >>> 0; + e = d; + d = c; + c = ROTL(b, 30) >>> 0; + b = a; + a = T; + } + + H[0] = H[0] + a >>> 0; + H[1] = H[1] + b >>> 0; + H[2] = H[2] + c >>> 0; + H[3] = H[3] + d >>> 0; + H[4] = H[4] + e >>> 0; + } + + return [H[0] >> 24 & 0xff, H[0] >> 16 & 0xff, H[0] >> 8 & 0xff, H[0] & 0xff, H[1] >> 24 & 0xff, H[1] >> 16 & 0xff, H[1] >> 8 & 0xff, H[1] & 0xff, H[2] >> 24 & 0xff, H[2] >> 16 & 0xff, H[2] >> 8 & 0xff, H[2] & 0xff, H[3] >> 24 & 0xff, H[3] >> 16 & 0xff, H[3] >> 8 & 0xff, H[3] & 0xff, H[4] >> 24 & 0xff, H[4] >> 16 & 0xff, H[4] >> 8 & 0xff, H[4] & 0xff]; +} + +var _default = sha1; +exports.default = _default; +},{}],295:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +exports.unsafeStringify = unsafeStringify; + +var _validate = _interopRequireDefault(require("./validate.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).slice(1)); +} + +function unsafeStringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); +} + +function stringify(arr, offset = 0) { + const uuid = unsafeStringify(arr, offset); // Consistency check for valid UUID. If this throws, it's likely due to one + // of the following: + // - One or more input array values don't map to a hex octet (leading to + // "undefined" in the uuid) + // - Invalid input values for the RFC `version` or `variant` fields + + if (!(0, _validate.default)(uuid)) { + throw TypeError('Stringified UUID is invalid'); + } + + return uuid; +} + +var _default = stringify; +exports.default = _default; +},{"./validate.js":301}],296:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _rng = _interopRequireDefault(require("./rng.js")); + +var _stringify = require("./stringify.js"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html +let _nodeId; + +let _clockseq; // Previous uuid creation time + + +let _lastMSecs = 0; +let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details + +function v1(options, buf, offset) { + let i = buf && offset || 0; + const b = buf || new Array(16); + options = options || {}; + let node = options.node || _nodeId; + let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not + // specified. We do this lazily to minimize issues related to insufficient + // system entropy. See #189 + + if (node == null || clockseq == null) { + const seedBytes = options.random || (options.rng || _rng.default)(); + + if (node == null) { + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]]; + } + + if (clockseq == null) { + // Per 4.2.2, randomize (14 bit) clockseq + clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; + } + } // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + + + let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + + let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) + + const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression + + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + + + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } // Per 4.2.1.2 Throw error if too many uuids are requested + + + if (nsecs >= 10000) { + throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + + msecs += 12219292800000; // `time_low` + + const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; // `time_mid` + + const tmh = msecs / 0x100000000 * 10000 & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; // `time_high_and_version` + + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + + b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + + b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low` + + b[i++] = clockseq & 0xff; // `node` + + for (let n = 0; n < 6; ++n) { + b[i + n] = node[n]; + } + + return buf || (0, _stringify.unsafeStringify)(b); +} + +var _default = v1; +exports.default = _default; +},{"./rng.js":293,"./stringify.js":295}],297:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(require("./v35.js")); + +var _md = _interopRequireDefault(require("./md5.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v3 = (0, _v.default)('v3', 0x30, _md.default); +var _default = v3; +exports.default = _default; +},{"./md5.js":288,"./v35.js":298}],298:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.URL = exports.DNS = void 0; +exports.default = v35; + +var _stringify = require("./stringify.js"); + +var _parse = _interopRequireDefault(require("./parse.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function stringToBytes(str) { + str = unescape(encodeURIComponent(str)); // UTF8 escape + + const bytes = []; + + for (let i = 0; i < str.length; ++i) { + bytes.push(str.charCodeAt(i)); + } + + return bytes; +} + +const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; +exports.DNS = DNS; +const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +exports.URL = URL; + +function v35(name, version, hashfunc) { + function generateUUID(value, namespace, buf, offset) { + var _namespace; + + if (typeof value === 'string') { + value = stringToBytes(value); + } + + if (typeof namespace === 'string') { + namespace = (0, _parse.default)(namespace); + } + + if (((_namespace = namespace) === null || _namespace === void 0 ? void 0 : _namespace.length) !== 16) { + throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); + } // Compute hash of namespace and value, Per 4.3 + // Future: Use spread syntax when supported on all platforms, e.g. `bytes = + // hashfunc([...namespace, ... value])` + + + let bytes = new Uint8Array(16 + value.length); + bytes.set(namespace); + bytes.set(value, namespace.length); + bytes = hashfunc(bytes); + bytes[6] = bytes[6] & 0x0f | version; + bytes[8] = bytes[8] & 0x3f | 0x80; + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = bytes[i]; + } + + return buf; + } + + return (0, _stringify.unsafeStringify)(bytes); + } // Function#name is not settable on some platforms (#270) + + + try { + generateUUID.name = name; // eslint-disable-next-line no-empty + } catch (err) {} // For CommonJS default export support + + + generateUUID.DNS = DNS; + generateUUID.URL = URL; + return generateUUID; +} +},{"./parse.js":291,"./stringify.js":295}],299:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _native = _interopRequireDefault(require("./native.js")); + +var _rng = _interopRequireDefault(require("./rng.js")); + +var _stringify = require("./stringify.js"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function v4(options, buf, offset) { + if (_native.default.randomUUID && !buf && !options) { + return _native.default.randomUUID(); + } + + options = options || {}; + + const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + + rnds[6] = rnds[6] & 0x0f | 0x40; + rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return (0, _stringify.unsafeStringify)(rnds); +} + +var _default = v4; +exports.default = _default; +},{"./native.js":289,"./rng.js":293,"./stringify.js":295}],300:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(require("./v35.js")); + +var _sha = _interopRequireDefault(require("./sha1.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v5 = (0, _v.default)('v5', 0x50, _sha.default); +var _default = v5; +exports.default = _default; +},{"./sha1.js":294,"./v35.js":298}],301:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _regex = _interopRequireDefault(require("./regex.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function validate(uuid) { + return typeof uuid === 'string' && _regex.default.test(uuid); +} + +var _default = validate; +exports.default = _default; +},{"./regex.js":292}],302:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(require("./validate.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function version(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + return parseInt(uuid.slice(14, 15), 16); +} + +var _default = version; +exports.default = _default; +},{"./validate.js":301}],303:[function(require,module,exports){ +(function (global){(function (){ +'use strict'; + +var forEach = require('for-each'); +var availableTypedArrays = require('available-typed-arrays'); +var callBound = require('call-bind/callBound'); +var gOPD = require('gopd'); + +var $toString = callBound('Object.prototype.toString'); +var hasToStringTag = require('has-tostringtag/shams')(); + +var g = typeof globalThis === 'undefined' ? global : globalThis; +var typedArrays = availableTypedArrays(); + +var $slice = callBound('String.prototype.slice'); +var toStrTags = {}; +var getPrototypeOf = Object.getPrototypeOf; // require('getprototypeof'); +if (hasToStringTag && gOPD && getPrototypeOf) { + forEach(typedArrays, function (typedArray) { + if (typeof g[typedArray] === 'function') { + var arr = new g[typedArray](); + if (Symbol.toStringTag in arr) { + var proto = getPrototypeOf(arr); + var descriptor = gOPD(proto, Symbol.toStringTag); + if (!descriptor) { + var superProto = getPrototypeOf(proto); + descriptor = gOPD(superProto, Symbol.toStringTag); + } + toStrTags[typedArray] = descriptor.get; + } + } + }); +} + +var tryTypedArrays = function tryAllTypedArrays(value) { + var foundName = false; + forEach(toStrTags, function (getter, typedArray) { + if (!foundName) { + try { + var name = getter.call(value); + if (name === typedArray) { + foundName = name; + } + } catch (e) {} + } + }); + return foundName; +}; + +var isTypedArray = require('is-typed-array'); + +module.exports = function whichTypedArray(value) { + if (!isTypedArray(value)) { return false; } + if (!hasToStringTag || !(Symbol.toStringTag in value)) { return $slice($toString(value), 8, -1); } + return tryTypedArrays(value); +}; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"available-typed-arrays":16,"call-bind/callBound":69,"for-each":107,"gopd":111,"has-tostringtag/shams":114,"is-typed-array":150}],304:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RuleId = exports.PushRuleKind = exports.ConditionKind = exports.isDmMemberCountCondition = exports.DMMemberCountCondition = exports.ConditionOperator = exports.TweakName = exports.PushRuleActionName = void 0; +// allow camelcase as these are things that go onto the wire +/* eslint-disable camelcase */ +var PushRuleActionName; +(function (PushRuleActionName) { + PushRuleActionName["DontNotify"] = "dont_notify"; + PushRuleActionName["Notify"] = "notify"; + PushRuleActionName["Coalesce"] = "coalesce"; +})(PushRuleActionName = exports.PushRuleActionName || (exports.PushRuleActionName = {})); +var TweakName; +(function (TweakName) { + TweakName["Highlight"] = "highlight"; + TweakName["Sound"] = "sound"; +})(TweakName = exports.TweakName || (exports.TweakName = {})); +var ConditionOperator; +(function (ConditionOperator) { + ConditionOperator["ExactEquals"] = "=="; + ConditionOperator["LessThan"] = "<"; + ConditionOperator["GreaterThan"] = ">"; + ConditionOperator["GreaterThanOrEqual"] = ">="; + ConditionOperator["LessThanOrEqual"] = "<="; +})(ConditionOperator = exports.ConditionOperator || (exports.ConditionOperator = {})); +exports.DMMemberCountCondition = "2"; +function isDmMemberCountCondition(condition) { + return condition === "==2" || condition === "2"; +} +exports.isDmMemberCountCondition = isDmMemberCountCondition; +var ConditionKind; +(function (ConditionKind) { + ConditionKind["EventMatch"] = "event_match"; + ConditionKind["EventPropertyIs"] = "event_property_is"; + ConditionKind["EventPropertyContains"] = "event_property_contains"; + ConditionKind["ContainsDisplayName"] = "contains_display_name"; + ConditionKind["RoomMemberCount"] = "room_member_count"; + ConditionKind["SenderNotificationPermission"] = "sender_notification_permission"; + ConditionKind["CallStarted"] = "call_started"; + ConditionKind["CallStartedPrefix"] = "org.matrix.msc3914.call_started"; +})(ConditionKind = exports.ConditionKind || (exports.ConditionKind = {})); +var PushRuleKind; +(function (PushRuleKind) { + PushRuleKind["Override"] = "override"; + PushRuleKind["ContentSpecific"] = "content"; + PushRuleKind["RoomSpecific"] = "room"; + PushRuleKind["SenderSpecific"] = "sender"; + PushRuleKind["Underride"] = "underride"; +})(PushRuleKind = exports.PushRuleKind || (exports.PushRuleKind = {})); +var RuleId; +(function (RuleId) { + RuleId["Master"] = ".m.rule.master"; + RuleId["IsUserMention"] = ".org.matrix.msc3952.is_user_mention"; + RuleId["IsRoomMention"] = ".org.matrix.msc3952.is_room_mention"; + RuleId["ContainsDisplayName"] = ".m.rule.contains_display_name"; + RuleId["ContainsUserName"] = ".m.rule.contains_user_name"; + RuleId["AtRoomNotification"] = ".m.rule.roomnotif"; + RuleId["DM"] = ".m.rule.room_one_to_one"; + RuleId["EncryptedDM"] = ".m.rule.encrypted_room_one_to_one"; + RuleId["Message"] = ".m.rule.message"; + RuleId["EncryptedMessage"] = ".m.rule.encrypted"; + RuleId["InviteToSelf"] = ".m.rule.invite_for_me"; + RuleId["MemberEvent"] = ".m.rule.member_event"; + RuleId["IncomingCall"] = ".m.rule.call"; + RuleId["SuppressNotices"] = ".m.rule.suppress_notices"; + RuleId["Tombstone"] = ".m.rule.tombstone"; + RuleId["PollStart"] = ".m.rule.poll_start"; + RuleId["PollStartUnstable"] = ".org.matrix.msc3930.rule.poll_start"; + RuleId["PollEnd"] = ".m.rule.poll_end"; + RuleId["PollEndUnstable"] = ".org.matrix.msc3930.rule.poll_end"; + RuleId["PollStartOneToOne"] = ".m.rule.poll_start_one_to_one"; + RuleId["PollStartOneToOneUnstable"] = ".org.matrix.msc3930.rule.poll_start_one_to_one"; + RuleId["PollEndOneToOne"] = ".m.rule.poll_end_one_to_one"; + RuleId["PollEndOneToOneUnstable"] = ".org.matrix.msc3930.rule.poll_end_one_to_one"; +})(RuleId = exports.RuleId || (exports.RuleId = {})); +/* eslint-enable camelcase */ + +},{}],305:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.M_BEACON = exports.M_BEACON_INFO = void 0; +const NamespacedValue_1 = require("../NamespacedValue"); +/** + * Beacon info and beacon event types as described in MSC3672 + * https://github.com/matrix-org/matrix-spec-proposals/pull/3672 + */ +/** + * Beacon info events are state events. + * We have two requirements for these events: + * 1. they can only be written by their owner + * 2. a user can have an arbitrary number of beacon_info events + * + * 1. is achieved by setting the state_key to the owners mxid. + * Event keys in room state are a combination of `type` + `state_key`. + * To achieve an arbitrary number of only owner-writable state events + * we introduce a variable suffix to the event type + * + * @example + * ``` + * { + * "type": "m.beacon_info.@matthew:matrix.org.1", + * "state_key": "@matthew:matrix.org", + * "content": { + * "m.beacon_info": { + * "description": "The Matthew Tracker", + * "timeout": 86400000, + * }, + * // more content as described below + * } + * }, + * { + * "type": "m.beacon_info.@matthew:matrix.org.2", + * "state_key": "@matthew:matrix.org", + * "content": { + * "m.beacon_info": { + * "description": "Another different Matthew tracker", + * "timeout": 400000, + * }, + * // more content as described below + * } + * } + * ``` + */ +/** + * Non-variable type for m.beacon_info event content + */ +exports.M_BEACON_INFO = new NamespacedValue_1.UnstableValue("m.beacon_info", "org.matrix.msc3672.beacon_info"); +exports.M_BEACON = new NamespacedValue_1.UnstableValue("m.beacon", "org.matrix.msc3672.beacon"); + +},{"../NamespacedValue":316}],306:[function(require,module,exports){ +"use strict"; +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LOCAL_NOTIFICATION_SETTINGS_PREFIX = exports.PUSHER_DEVICE_ID = exports.PUSHER_ENABLED = exports.EVENT_VISIBILITY_CHANGE_TYPE = exports.UNSTABLE_ELEMENT_FUNCTIONAL_USERS = exports.MSC3912_RELATION_BASED_REDACTIONS_PROP = exports.UNSTABLE_MSC2716_MARKER = exports.UNSTABLE_MSC3089_BRANCH = exports.UNSTABLE_MSC3089_LEAF = exports.UNSTABLE_MSC3089_TREE_SUBTYPE = exports.UNSTABLE_MSC3088_ENABLED = exports.UNSTABLE_MSC3088_PURPOSE = exports.ToDeviceMessageId = exports.RoomType = exports.RoomCreateTypeField = exports.MsgType = exports.RelationType = exports.EventType = void 0; +const NamespacedValue_1 = require("../NamespacedValue"); +var EventType; +(function (EventType) { + // Room state events + EventType["RoomCanonicalAlias"] = "m.room.canonical_alias"; + EventType["RoomCreate"] = "m.room.create"; + EventType["RoomJoinRules"] = "m.room.join_rules"; + EventType["RoomMember"] = "m.room.member"; + EventType["RoomThirdPartyInvite"] = "m.room.third_party_invite"; + EventType["RoomPowerLevels"] = "m.room.power_levels"; + EventType["RoomName"] = "m.room.name"; + EventType["RoomTopic"] = "m.room.topic"; + EventType["RoomAvatar"] = "m.room.avatar"; + EventType["RoomPinnedEvents"] = "m.room.pinned_events"; + EventType["RoomEncryption"] = "m.room.encryption"; + EventType["RoomHistoryVisibility"] = "m.room.history_visibility"; + EventType["RoomGuestAccess"] = "m.room.guest_access"; + EventType["RoomServerAcl"] = "m.room.server_acl"; + EventType["RoomTombstone"] = "m.room.tombstone"; + EventType["RoomPredecessor"] = "org.matrix.msc3946.room_predecessor"; + EventType["SpaceChild"] = "m.space.child"; + EventType["SpaceParent"] = "m.space.parent"; + // Room timeline events + EventType["RoomRedaction"] = "m.room.redaction"; + EventType["RoomMessage"] = "m.room.message"; + EventType["RoomMessageEncrypted"] = "m.room.encrypted"; + EventType["Sticker"] = "m.sticker"; + EventType["CallInvite"] = "m.call.invite"; + EventType["CallCandidates"] = "m.call.candidates"; + EventType["CallAnswer"] = "m.call.answer"; + EventType["CallHangup"] = "m.call.hangup"; + EventType["CallReject"] = "m.call.reject"; + EventType["CallSelectAnswer"] = "m.call.select_answer"; + EventType["CallNegotiate"] = "m.call.negotiate"; + EventType["CallSDPStreamMetadataChanged"] = "m.call.sdp_stream_metadata_changed"; + EventType["CallSDPStreamMetadataChangedPrefix"] = "org.matrix.call.sdp_stream_metadata_changed"; + EventType["CallReplaces"] = "m.call.replaces"; + EventType["CallAssertedIdentity"] = "m.call.asserted_identity"; + EventType["CallAssertedIdentityPrefix"] = "org.matrix.call.asserted_identity"; + EventType["KeyVerificationRequest"] = "m.key.verification.request"; + EventType["KeyVerificationStart"] = "m.key.verification.start"; + EventType["KeyVerificationCancel"] = "m.key.verification.cancel"; + EventType["KeyVerificationMac"] = "m.key.verification.mac"; + EventType["KeyVerificationDone"] = "m.key.verification.done"; + EventType["KeyVerificationKey"] = "m.key.verification.key"; + EventType["KeyVerificationAccept"] = "m.key.verification.accept"; + // Not used directly - see READY_TYPE in VerificationRequest. + EventType["KeyVerificationReady"] = "m.key.verification.ready"; + // use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback + EventType["RoomMessageFeedback"] = "m.room.message.feedback"; + EventType["Reaction"] = "m.reaction"; + EventType["PollStart"] = "org.matrix.msc3381.poll.start"; + // Room ephemeral events + EventType["Typing"] = "m.typing"; + EventType["Receipt"] = "m.receipt"; + EventType["Presence"] = "m.presence"; + // Room account_data events + EventType["FullyRead"] = "m.fully_read"; + EventType["Tag"] = "m.tag"; + EventType["SpaceOrder"] = "org.matrix.msc3230.space_order"; + // User account_data events + EventType["PushRules"] = "m.push_rules"; + EventType["Direct"] = "m.direct"; + EventType["IgnoredUserList"] = "m.ignored_user_list"; + // to_device events + EventType["RoomKey"] = "m.room_key"; + EventType["RoomKeyRequest"] = "m.room_key_request"; + EventType["ForwardedRoomKey"] = "m.forwarded_room_key"; + EventType["Dummy"] = "m.dummy"; + // Group call events + EventType["GroupCallPrefix"] = "org.matrix.msc3401.call"; + EventType["GroupCallMemberPrefix"] = "org.matrix.msc3401.call.member"; +})(EventType = exports.EventType || (exports.EventType = {})); +var RelationType; +(function (RelationType) { + RelationType["Annotation"] = "m.annotation"; + RelationType["Replace"] = "m.replace"; + RelationType["Reference"] = "m.reference"; + RelationType["Thread"] = "m.thread"; +})(RelationType = exports.RelationType || (exports.RelationType = {})); +var MsgType; +(function (MsgType) { + MsgType["Text"] = "m.text"; + MsgType["Emote"] = "m.emote"; + MsgType["Notice"] = "m.notice"; + MsgType["Image"] = "m.image"; + MsgType["File"] = "m.file"; + MsgType["Audio"] = "m.audio"; + MsgType["Location"] = "m.location"; + MsgType["Video"] = "m.video"; + MsgType["KeyVerificationRequest"] = "m.key.verification.request"; +})(MsgType = exports.MsgType || (exports.MsgType = {})); +exports.RoomCreateTypeField = "type"; +var RoomType; +(function (RoomType) { + RoomType["Space"] = "m.space"; + RoomType["UnstableCall"] = "org.matrix.msc3417.call"; + RoomType["ElementVideo"] = "io.element.video"; +})(RoomType = exports.RoomType || (exports.RoomType = {})); +exports.ToDeviceMessageId = "org.matrix.msgid"; +/** + * Identifier for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088) + * room purpose. Note that this reference is UNSTABLE and subject to breaking changes, + * including its eventual removal. + */ +exports.UNSTABLE_MSC3088_PURPOSE = new NamespacedValue_1.UnstableValue("m.room.purpose", "org.matrix.msc3088.purpose"); +/** + * Enabled flag for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088) + * room purpose. Note that this reference is UNSTABLE and subject to breaking changes, + * including its eventual removal. + */ +exports.UNSTABLE_MSC3088_ENABLED = new NamespacedValue_1.UnstableValue("m.enabled", "org.matrix.msc3088.enabled"); +/** + * Subtype for an [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. + * Note that this reference is UNSTABLE and subject to breaking changes, including its + * eventual removal. + */ +exports.UNSTABLE_MSC3089_TREE_SUBTYPE = new NamespacedValue_1.UnstableValue("m.data_tree", "org.matrix.msc3089.data_tree"); +/** + * Leaf type for an event in a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. + * Note that this reference is UNSTABLE and subject to breaking changes, including its + * eventual removal. + */ +exports.UNSTABLE_MSC3089_LEAF = new NamespacedValue_1.UnstableValue("m.leaf", "org.matrix.msc3089.leaf"); +/** + * Branch (Leaf Reference) type for the index approach in a + * [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. Note that this reference is + * UNSTABLE and subject to breaking changes, including its eventual removal. + */ +exports.UNSTABLE_MSC3089_BRANCH = new NamespacedValue_1.UnstableValue("m.branch", "org.matrix.msc3089.branch"); +/** + * Marker event type to point back at imported historical content in a room. See + * [MSC2716](https://github.com/matrix-org/matrix-spec-proposals/pull/2716). + * Note that this reference is UNSTABLE and subject to breaking changes, + * including its eventual removal. + */ +exports.UNSTABLE_MSC2716_MARKER = new NamespacedValue_1.UnstableValue("m.room.marker", "org.matrix.msc2716.marker"); +/** + * Name of the "with_relations" request property for relation based redactions. + * {@link https://github.com/matrix-org/matrix-spec-proposals/pull/3912} + */ +exports.MSC3912_RELATION_BASED_REDACTIONS_PROP = new NamespacedValue_1.UnstableValue("with_relations", "org.matrix.msc3912.with_relations"); +/** + * Functional members type for declaring a purpose of room members (e.g. helpful bots). + * Note that this reference is UNSTABLE and subject to breaking changes, including its + * eventual removal. + * + * Schema (TypeScript): + * ``` + * { + * service_members?: string[] + * } + * ``` + * + * @example + * ``` + * { + * "service_members": [ + * "@helperbot:localhost", + * "@reminderbot:alice.tdl" + * ] + * } + * ``` + */ +exports.UNSTABLE_ELEMENT_FUNCTIONAL_USERS = new NamespacedValue_1.UnstableValue("io.element.functional_members", "io.element.functional_members"); +/** + * A type of message that affects visibility of a message, + * as per https://github.com/matrix-org/matrix-doc/pull/3531 + * + * @experimental + */ +exports.EVENT_VISIBILITY_CHANGE_TYPE = new NamespacedValue_1.UnstableValue("m.visibility", "org.matrix.msc3531.visibility"); +/** + * https://github.com/matrix-org/matrix-doc/pull/3881 + * + * @experimental + */ +exports.PUSHER_ENABLED = new NamespacedValue_1.UnstableValue("enabled", "org.matrix.msc3881.enabled"); +/** + * https://github.com/matrix-org/matrix-doc/pull/3881 + * + * @experimental + */ +exports.PUSHER_DEVICE_ID = new NamespacedValue_1.UnstableValue("device_id", "org.matrix.msc3881.device_id"); +/** + * https://github.com/matrix-org/matrix-doc/pull/3890 + * + * @experimental + */ +exports.LOCAL_NOTIFICATION_SETTINGS_PREFIX = new NamespacedValue_1.UnstableValue("m.local_notification_settings", "org.matrix.msc3890.local_notification_settings"); + +},{"../NamespacedValue":316}],307:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isEventTypeSame = exports.REFERENCE_RELATION = exports.M_HTML = exports.M_TEXT = exports.M_MESSAGE = void 0; +const matrix_events_sdk_1 = require("matrix-events-sdk"); +const utilities_1 = require("../extensible_events_v1/utilities"); +/** + * The namespaced value for m.message + */ +exports.M_MESSAGE = new matrix_events_sdk_1.UnstableValue("m.message", "org.matrix.msc1767.message"); +/** + * The namespaced value for m.text + */ +exports.M_TEXT = new matrix_events_sdk_1.UnstableValue("m.text", "org.matrix.msc1767.text"); +/** + * The namespaced value for m.html + */ +exports.M_HTML = new matrix_events_sdk_1.UnstableValue("m.html", "org.matrix.msc1767.html"); +/** + * The namespaced value for an m.reference relation + */ +exports.REFERENCE_RELATION = new matrix_events_sdk_1.NamespacedValue("m.reference"); +/** + * Determines if two event types are the same, including namespaces. + * @param given - The given event type. This will be compared + * against the expected type. + * @param expected - The expected event type. + * @returns True if the given type matches the expected type. + */ +function isEventTypeSame(given, expected) { + if (typeof given === "string") { + if (typeof expected === "string") { + return expected === given; + } + else { + return expected.matches(given); + } + } + else { + if (typeof expected === "string") { + return given.matches(expected); + } + else { + const expectedNs = expected; + const givenNs = given; + return (expectedNs.matches(givenNs.name) || + ((0, utilities_1.isProvided)(givenNs.altName) && expectedNs.matches(givenNs.altName))); + } + } +} +exports.isEventTypeSame = isEventTypeSame; + +},{"../extensible_events_v1/utilities":361,"matrix-events-sdk":167}],308:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.M_LOCATION = exports.M_TIMESTAMP = exports.M_ASSET = exports.LocationAssetType = void 0; +const NamespacedValue_1 = require("../NamespacedValue"); +const extensible_events_1 = require("./extensible_events"); +var LocationAssetType; +(function (LocationAssetType) { + LocationAssetType["Self"] = "m.self"; + LocationAssetType["Pin"] = "m.pin"; +})(LocationAssetType = exports.LocationAssetType || (exports.LocationAssetType = {})); +exports.M_ASSET = new NamespacedValue_1.UnstableValue("m.asset", "org.matrix.msc3488.asset"); +exports.M_TIMESTAMP = new NamespacedValue_1.UnstableValue("m.ts", "org.matrix.msc3488.ts"); +exports.M_LOCATION = new NamespacedValue_1.UnstableValue("m.location", "org.matrix.msc3488.location"); + +},{"../NamespacedValue":316,"./extensible_events":307}],309:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.HistoryVisibility = exports.GuestAccess = exports.RestrictedAllowType = exports.JoinRule = exports.Preset = exports.Visibility = void 0; +var Visibility; +(function (Visibility) { + Visibility["Public"] = "public"; + Visibility["Private"] = "private"; +})(Visibility = exports.Visibility || (exports.Visibility = {})); +var Preset; +(function (Preset) { + Preset["PrivateChat"] = "private_chat"; + Preset["TrustedPrivateChat"] = "trusted_private_chat"; + Preset["PublicChat"] = "public_chat"; +})(Preset = exports.Preset || (exports.Preset = {})); +// Knock and private are reserved keywords which are not yet implemented. +var JoinRule; +(function (JoinRule) { + JoinRule["Public"] = "public"; + JoinRule["Invite"] = "invite"; + /** + * @deprecated Reserved keyword. Should not be used. Not yet implemented. + */ + JoinRule["Private"] = "private"; + JoinRule["Knock"] = "knock"; + JoinRule["Restricted"] = "restricted"; +})(JoinRule = exports.JoinRule || (exports.JoinRule = {})); +var RestrictedAllowType; +(function (RestrictedAllowType) { + RestrictedAllowType["RoomMembership"] = "m.room_membership"; +})(RestrictedAllowType = exports.RestrictedAllowType || (exports.RestrictedAllowType = {})); +var GuestAccess; +(function (GuestAccess) { + GuestAccess["CanJoin"] = "can_join"; + GuestAccess["Forbidden"] = "forbidden"; +})(GuestAccess = exports.GuestAccess || (exports.GuestAccess = {})); +var HistoryVisibility; +(function (HistoryVisibility) { + HistoryVisibility["Invited"] = "invited"; + HistoryVisibility["Joined"] = "joined"; + HistoryVisibility["Shared"] = "shared"; + HistoryVisibility["WorldReadable"] = "world_readable"; +})(HistoryVisibility = exports.HistoryVisibility || (exports.HistoryVisibility = {})); + +},{}],310:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.M_POLL_END = exports.M_POLL_RESPONSE = exports.M_POLL_START = exports.M_POLL_KIND_UNDISCLOSED = exports.M_POLL_KIND_DISCLOSED = void 0; +const matrix_events_sdk_1 = require("matrix-events-sdk"); +/** + * Identifier for a disclosed poll. + */ +exports.M_POLL_KIND_DISCLOSED = new matrix_events_sdk_1.UnstableValue("m.poll.disclosed", "org.matrix.msc3381.poll.disclosed"); +/** + * Identifier for an undisclosed poll. + */ +exports.M_POLL_KIND_UNDISCLOSED = new matrix_events_sdk_1.UnstableValue("m.poll.undisclosed", "org.matrix.msc3381.poll.undisclosed"); +/** + * The namespaced value for m.poll.start + */ +exports.M_POLL_START = new matrix_events_sdk_1.UnstableValue("m.poll.start", "org.matrix.msc3381.poll.start"); +/** + * The namespaced value for m.poll.response + */ +exports.M_POLL_RESPONSE = new matrix_events_sdk_1.UnstableValue("m.poll.response", "org.matrix.msc3381.poll.response"); +/** + * The namespaced value for m.poll.end + */ +exports.M_POLL_END = new matrix_events_sdk_1.UnstableValue("m.poll.end", "org.matrix.msc3381.poll.end"); + +},{"matrix-events-sdk":167}],311:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 Šimon Brandner + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MAIN_ROOM_TIMELINE = exports.ReceiptType = void 0; +var ReceiptType; +(function (ReceiptType) { + ReceiptType["Read"] = "m.read"; + ReceiptType["FullyRead"] = "m.fully_read"; + ReceiptType["ReadPrivate"] = "m.read.private"; +})(ReceiptType = exports.ReceiptType || (exports.ReceiptType = {})); +exports.MAIN_ROOM_TIMELINE = "main"; + +},{}],312:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +/* eslint-enable camelcase */ + +},{}],313:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SearchOrderBy = void 0; +var GroupKey; +(function (GroupKey) { + GroupKey["RoomId"] = "room_id"; + GroupKey["Sender"] = "sender"; +})(GroupKey || (GroupKey = {})); +var SearchOrderBy; +(function (SearchOrderBy) { + SearchOrderBy["Recent"] = "recent"; + SearchOrderBy["Rank"] = "rank"; +})(SearchOrderBy = exports.SearchOrderBy || (exports.SearchOrderBy = {})); +/* eslint-enable camelcase */ + +},{}],314:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UNREAD_THREAD_NOTIFICATIONS = void 0; +const NamespacedValue_1 = require("../NamespacedValue"); +/** + * https://github.com/matrix-org/matrix-doc/pull/3773 + * + * @experimental + */ +exports.UNREAD_THREAD_NOTIFICATIONS = new NamespacedValue_1.ServerControlledNamespacedValue("unread_thread_notifications", "org.matrix.msc3773.unread_thread_notifications"); + +},{"../NamespacedValue":316}],315:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.M_TOPIC = void 0; +const NamespacedValue_1 = require("../NamespacedValue"); +/** + * Extensible topic event type based on MSC3765 + * https://github.com/matrix-org/matrix-spec-proposals/pull/3765 + * + * @example + * ``` + * { + * "type": "m.room.topic, + * "state_key": "", + * "content": { + * "topic": "All about **pizza**", + * "m.topic": [{ + * "body": "All about **pizza**", + * "mimetype": "text/plain", + * }, { + * "body": "All about pizza", + * "mimetype": "text/html", + * }], + * } + * } + * ``` + */ +/** + * The event type for an m.topic event (in content) + */ +exports.M_TOPIC = new NamespacedValue_1.UnstableValue("m.topic", "org.matrix.msc3765.topic"); + +},{"../NamespacedValue":316}],316:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UnstableValue = exports.ServerControlledNamespacedValue = exports.NamespacedValue = void 0; +/** + * Represents a simple Matrix namespaced value. This will assume that if a stable prefix + * is provided that the stable prefix should be used when representing the identifier. + */ +class NamespacedValue { + constructor(stable, unstable) { + this.stable = stable; + this.unstable = unstable; + if (!this.unstable && !this.stable) { + throw new Error("One of stable or unstable values must be supplied"); + } + } + get name() { + if (this.stable) { + return this.stable; + } + return this.unstable; + } + get altName() { + if (!this.stable) { + return null; + } + return this.unstable; + } + get names() { + const names = [this.name]; + const altName = this.altName; + if (altName) + names.push(altName); + return names; + } + matches(val) { + return this.name === val || this.altName === val; + } + // this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class + // so we can instantiate `NamespacedValue` as a default type for that namespace. + findIn(obj) { + let val = undefined; + if (this.name) { + val = obj === null || obj === void 0 ? void 0 : obj[this.name]; + } + if (!val && this.altName) { + val = obj === null || obj === void 0 ? void 0 : obj[this.altName]; + } + return val; + } + includedIn(arr) { + let included = false; + if (this.name) { + included = arr.includes(this.name); + } + if (!included && this.altName) { + included = arr.includes(this.altName); + } + return included; + } +} +exports.NamespacedValue = NamespacedValue; +class ServerControlledNamespacedValue extends NamespacedValue { + constructor() { + super(...arguments); + this.preferUnstable = false; + } + setPreferUnstable(preferUnstable) { + this.preferUnstable = preferUnstable; + } + get name() { + if (this.stable && !this.preferUnstable) { + return this.stable; + } + return this.unstable; + } +} +exports.ServerControlledNamespacedValue = ServerControlledNamespacedValue; +/** + * Represents a namespaced value which prioritizes the unstable value over the stable + * value. + */ +class UnstableValue extends NamespacedValue { + // Note: Constructor difference is that `unstable` is *required*. + constructor(stable, unstable) { + super(stable, unstable); + if (!this.unstable) { + throw new Error("Unstable value must be supplied"); + } + } + get name() { + return this.unstable; + } + get altName() { + return this.stable; + } +} +exports.UnstableValue = UnstableValue; + +},{}],317:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2017 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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TypedReEmitter = exports.ReEmitter = void 0; +class ReEmitter { + constructor(target) { + this.target = target; + // Map from emitter to event name to re-emitter + this.reEmitters = new Map(); + } + reEmit(source, eventNames) { + let reEmittersByEvent = this.reEmitters.get(source); + if (!reEmittersByEvent) { + reEmittersByEvent = new Map(); + this.reEmitters.set(source, reEmittersByEvent); + } + for (const eventName of eventNames) { + // We include the source as the last argument for event handlers which may need it, + // such as read receipt listeners on the client class which won't have the context + // of the room. + const forSource = (...args) => { + // EventEmitter special cases 'error' to make the emit function throw if no + // handler is attached, which sort of makes sense for making sure that something + // handles an error, but for re-emitting, there could be a listener on the original + // source object so the test doesn't really work. We *could* try to replicate the + // same logic and throw if there is no listener on either the source or the target, + // but this behaviour is fairly undesireable for us anyway: the main place we throw + // 'error' events is for calls, where error events are usually emitted some time + // later by a different part of the code where 'emit' throwing because the app hasn't + // added an error handler isn't terribly helpful. (A better fix in retrospect may + // have been to just avoid using the event name 'error', but backwards compat...) + if (eventName === "error" && this.target.listenerCount("error") === 0) + return; + this.target.emit(eventName, ...args, source); + }; + source.on(eventName, forSource); + reEmittersByEvent.set(eventName, forSource); + } + } + stopReEmitting(source, eventNames) { + const reEmittersByEvent = this.reEmitters.get(source); + if (!reEmittersByEvent) + return; // We were never re-emitting these events in the first place + for (const eventName of eventNames) { + source.off(eventName, reEmittersByEvent.get(eventName)); + reEmittersByEvent.delete(eventName); + } + if (reEmittersByEvent.size === 0) + this.reEmitters.delete(source); + } +} +exports.ReEmitter = ReEmitter; +class TypedReEmitter extends ReEmitter { + constructor(target) { + super(target); + } + reEmit(source, eventNames) { + super.reEmit(source, eventNames); + } + stopReEmitting(source, eventNames) { + super.stopReEmitting(source, eventNames); + } +} +exports.TypedReEmitter = TypedReEmitter; + +},{}],318:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ToDeviceMessageQueue = void 0; +const event_1 = require("./@types/event"); +const logger_1 = require("./logger"); +const client_1 = require("./client"); +const scheduler_1 = require("./scheduler"); +const sync_1 = require("./sync"); +const utils_1 = require("./utils"); +const MAX_BATCH_SIZE = 20; +/** + * Maintains a queue of outgoing to-device messages, sending them + * as soon as the homeserver is reachable. + */ +class ToDeviceMessageQueue { + constructor(client) { + this.client = client; + this.sending = false; + this.running = true; + this.retryTimeout = null; + this.retryAttempts = 0; + this.sendQueue = () => __awaiter(this, void 0, void 0, function* () { + if (this.retryTimeout !== null) + clearTimeout(this.retryTimeout); + this.retryTimeout = null; + if (this.sending || !this.running) + return; + logger_1.logger.debug("Attempting to send queued to-device messages"); + this.sending = true; + let headBatch; + try { + while (this.running) { + headBatch = yield this.client.store.getOldestToDeviceBatch(); + if (headBatch === null) + break; + yield this.sendBatch(headBatch); + yield this.client.store.removeToDeviceBatch(headBatch.id); + this.retryAttempts = 0; + } + // Make sure we're still running after the async tasks: if not, stop. + if (!this.running) + return; + logger_1.logger.debug("All queued to-device messages sent"); + } + catch (e) { + ++this.retryAttempts; + // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line new-cap + const retryDelay = scheduler_1.MatrixScheduler.RETRY_BACKOFF_RATELIMIT(null, this.retryAttempts, e); + if (retryDelay === -1) { + // the scheduler function doesn't differentiate between fatal errors and just getting + // bored and giving up for now + if (Math.floor(e.httpStatus / 100) === 4) { + logger_1.logger.error("Fatal error when sending to-device message - dropping to-device batch!", e); + yield this.client.store.removeToDeviceBatch(headBatch.id); + } + else { + logger_1.logger.info("Automatic retry limit reached for to-device messages."); + } + return; + } + logger_1.logger.info(`Failed to send batch of to-device messages. Will retry in ${retryDelay}ms`, e); + this.retryTimeout = setTimeout(this.sendQueue, retryDelay); + } + finally { + this.sending = false; + } + }); + /** + * Listen to sync state changes and automatically resend any pending events + * once syncing is resumed + */ + this.onResumedSync = (state, oldState) => { + if (state === sync_1.SyncState.Syncing && oldState !== sync_1.SyncState.Syncing) { + logger_1.logger.info(`Resuming queue after resumed sync`); + this.sendQueue(); + } + }; + } + start() { + this.running = true; + this.sendQueue(); + this.client.on(client_1.ClientEvent.Sync, this.onResumedSync); + } + stop() { + this.running = false; + if (this.retryTimeout !== null) + clearTimeout(this.retryTimeout); + this.retryTimeout = null; + this.client.removeListener(client_1.ClientEvent.Sync, this.onResumedSync); + } + queueBatch(batch) { + return __awaiter(this, void 0, void 0, function* () { + const batches = []; + for (let i = 0; i < batch.batch.length; i += MAX_BATCH_SIZE) { + const batchWithTxnId = { + eventType: batch.eventType, + batch: batch.batch.slice(i, i + MAX_BATCH_SIZE), + txnId: this.client.makeTxnId(), + }; + batches.push(batchWithTxnId); + const msgmap = batchWithTxnId.batch.map((msg) => `${msg.userId}/${msg.deviceId} (msgid ${msg.payload[event_1.ToDeviceMessageId]})`); + logger_1.logger.info(`Enqueuing batch of to-device messages. type=${batch.eventType} txnid=${batchWithTxnId.txnId}`, msgmap); + } + yield this.client.store.saveToDeviceBatches(batches); + this.sendQueue(); + }); + } + /** + * Attempts to send a batch of to-device messages. + */ + sendBatch(batch) { + return __awaiter(this, void 0, void 0, function* () { + const contentMap = new utils_1.MapWithDefault(() => new Map()); + for (const item of batch.batch) { + contentMap.getOrCreate(item.userId).set(item.deviceId, item.payload); + } + logger_1.logger.info(`Sending batch of ${batch.batch.length} to-device messages with ID ${batch.id} and txnId ${batch.txnId}`); + yield this.client.sendToDevice(batch.eventType, contentMap, batch.txnId); + }); + } +} +exports.ToDeviceMessageQueue = ToDeviceMessageQueue; + +},{"./@types/event":306,"./client":321,"./logger":374,"./scheduler":403,"./sync":414,"./utils":416}],319:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2018 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AutoDiscovery = exports.AutoDiscoveryAction = void 0; +const logger_1 = require("./logger"); +const http_api_1 = require("./http-api"); +// Dev note: Auto discovery is part of the spec. +// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery +var AutoDiscoveryAction; +(function (AutoDiscoveryAction) { + AutoDiscoveryAction["SUCCESS"] = "SUCCESS"; + AutoDiscoveryAction["IGNORE"] = "IGNORE"; + AutoDiscoveryAction["PROMPT"] = "PROMPT"; + AutoDiscoveryAction["FAIL_PROMPT"] = "FAIL_PROMPT"; + AutoDiscoveryAction["FAIL_ERROR"] = "FAIL_ERROR"; +})(AutoDiscoveryAction = exports.AutoDiscoveryAction || (exports.AutoDiscoveryAction = {})); +var AutoDiscoveryError; +(function (AutoDiscoveryError) { + AutoDiscoveryError["Invalid"] = "Invalid homeserver discovery response"; + AutoDiscoveryError["GenericFailure"] = "Failed to get autodiscovery configuration from server"; + AutoDiscoveryError["InvalidHsBaseUrl"] = "Invalid base_url for m.homeserver"; + AutoDiscoveryError["InvalidHomeserver"] = "Homeserver URL does not appear to be a valid Matrix homeserver"; + AutoDiscoveryError["InvalidIsBaseUrl"] = "Invalid base_url for m.identity_server"; + AutoDiscoveryError["InvalidIdentityServer"] = "Identity server URL does not appear to be a valid identity server"; + AutoDiscoveryError["InvalidIs"] = "Invalid identity server discovery response"; + AutoDiscoveryError["MissingWellknown"] = "No .well-known JSON file found"; + AutoDiscoveryError["InvalidJson"] = "Invalid JSON"; +})(AutoDiscoveryError || (AutoDiscoveryError = {})); +/** + * Utilities for automatically discovery resources, such as homeservers + * for users to log in to. + */ +class AutoDiscovery { + /** + * Validates and verifies client configuration information for purposes + * of logging in. Such information includes the homeserver URL + * and identity server URL the client would want. Additional details + * may also be included, and will be transparently brought into the + * response object unaltered. + * @param wellknown - The configuration object itself, as returned + * by the .well-known auto-discovery endpoint. + * @returns Promise which resolves to the verified + * configuration, which may include error states. Rejects on unexpected + * failure, not when verification fails. + */ + static fromDiscoveryConfig(wellknown) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // Step 1 is to get the config, which is provided to us here. + // We default to an error state to make the first few checks easier to + // write. We'll update the properties of this object over the duration + // of this function. + const clientConfig = { + "m.homeserver": { + state: AutoDiscovery.FAIL_ERROR, + error: AutoDiscovery.ERROR_INVALID, + base_url: null, + }, + "m.identity_server": { + // Technically, we don't have a problem with the identity server + // config at this point. + state: AutoDiscovery.PROMPT, + error: null, + base_url: null, + }, + }; + if (!wellknown || !wellknown["m.homeserver"]) { + logger_1.logger.error("No m.homeserver key in config"); + clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT; + clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID; + return Promise.resolve(clientConfig); + } + if (!wellknown["m.homeserver"]["base_url"]) { + logger_1.logger.error("No m.homeserver base_url in config"); + clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT; + clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL; + return Promise.resolve(clientConfig); + } + // Step 2: Make sure the homeserver URL is valid *looking*. We'll make + // sure it points to a homeserver in Step 3. + const hsUrl = this.sanitizeWellKnownUrl(wellknown["m.homeserver"]["base_url"]); + if (!hsUrl) { + logger_1.logger.error("Invalid base_url for m.homeserver"); + clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL; + return Promise.resolve(clientConfig); + } + // Step 3: Make sure the homeserver URL points to a homeserver. + const hsVersions = yield this.fetchWellKnownObject(`${hsUrl}/_matrix/client/versions`); + if (!hsVersions || !((_a = hsVersions.raw) === null || _a === void 0 ? void 0 : _a["versions"])) { + logger_1.logger.error("Invalid /versions response"); + clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER; + // Supply the base_url to the caller because they may be ignoring liveliness + // errors, like this one. + clientConfig["m.homeserver"].base_url = hsUrl; + return Promise.resolve(clientConfig); + } + // Step 4: Now that the homeserver looks valid, update our client config. + clientConfig["m.homeserver"] = { + state: AutoDiscovery.SUCCESS, + error: null, + base_url: hsUrl, + }; + // Step 5: Try to pull out the identity server configuration + let isUrl = ""; + if (wellknown["m.identity_server"]) { + // We prepare a failing identity server response to save lines later + // in this branch. + const failingClientConfig = { + "m.homeserver": clientConfig["m.homeserver"], + "m.identity_server": { + state: AutoDiscovery.FAIL_PROMPT, + error: AutoDiscovery.ERROR_INVALID_IS, + base_url: null, + }, + }; + // Step 5a: Make sure the URL is valid *looking*. We'll make sure it + // points to an identity server in Step 5b. + isUrl = this.sanitizeWellKnownUrl(wellknown["m.identity_server"]["base_url"]); + if (!isUrl) { + logger_1.logger.error("Invalid base_url for m.identity_server"); + failingClientConfig["m.identity_server"].error = AutoDiscovery.ERROR_INVALID_IS_BASE_URL; + return Promise.resolve(failingClientConfig); + } + // Step 5b: Verify there is an identity server listening on the provided + // URL. + const isResponse = yield this.fetchWellKnownObject(`${isUrl}/_matrix/identity/v2`); + if (!(isResponse === null || isResponse === void 0 ? void 0 : isResponse.raw) || isResponse.action !== AutoDiscoveryAction.SUCCESS) { + logger_1.logger.error("Invalid /v2 response"); + failingClientConfig["m.identity_server"].error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER; + // Supply the base_url to the caller because they may be ignoring + // liveliness errors, like this one. + failingClientConfig["m.identity_server"].base_url = isUrl; + return Promise.resolve(failingClientConfig); + } + } + // Step 6: Now that the identity server is valid, or never existed, + // populate the IS section. + if (isUrl && isUrl.toString().length > 0) { + clientConfig["m.identity_server"] = { + state: AutoDiscovery.SUCCESS, + error: null, + base_url: isUrl, + }; + } + // Step 7: Copy any other keys directly into the clientConfig. This is for + // things like custom configuration of services. + Object.keys(wellknown).forEach((k) => { + if (k === "m.homeserver" || k === "m.identity_server") { + // Only copy selected parts of the config to avoid overwriting + // properties computed by the validation logic above. + const notProps = ["error", "state", "base_url"]; + for (const prop of Object.keys(wellknown[k])) { + if (notProps.includes(prop)) + continue; + // @ts-ignore - ts gets unhappy as we're mixing types here + clientConfig[k][prop] = wellknown[k][prop]; + } + } + else { + // Just copy the whole thing over otherwise + clientConfig[k] = wellknown[k]; + } + }); + // Step 8: Give the config to the caller (finally) + return Promise.resolve(clientConfig); + }); + } + /** + * Attempts to automatically discover client configuration information + * prior to logging in. Such information includes the homeserver URL + * and identity server URL the client would want. Additional details + * may also be discovered, and will be transparently included in the + * response object unaltered. + * @param domain - The homeserver domain to perform discovery + * on. For example, "matrix.org". + * @returns Promise which resolves to the discovered + * configuration, which may include error states. Rejects on unexpected + * failure, not when discovery fails. + */ + static findClientConfig(domain) { + return __awaiter(this, void 0, void 0, function* () { + if (!domain || typeof domain !== "string" || domain.length === 0) { + throw new Error("'domain' must be a string of non-zero length"); + } + // We use a .well-known lookup for all cases. According to the spec, we + // can do other discovery mechanisms if we want such as custom lookups + // however we won't bother with that here (mostly because the spec only + // supports .well-known right now). + // + // By using .well-known, we need to ensure we at least pull out a URL + // for the homeserver. We don't really need an identity server configuration + // but will return one anyways (with state PROMPT) to make development + // easier for clients. If we can't get a homeserver URL, all bets are + // off on the rest of the config and we'll assume it is invalid too. + // We default to an error state to make the first few checks easier to + // write. We'll update the properties of this object over the duration + // of this function. + const clientConfig = { + "m.homeserver": { + state: AutoDiscovery.FAIL_ERROR, + error: AutoDiscovery.ERROR_INVALID, + base_url: null, + }, + "m.identity_server": { + // Technically, we don't have a problem with the identity server + // config at this point. + state: AutoDiscovery.PROMPT, + error: null, + base_url: null, + }, + }; + // Step 1: Actually request the .well-known JSON file and make sure it + // at least has a homeserver definition. + const wellknown = yield this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`); + if (!wellknown || wellknown.action !== AutoDiscoveryAction.SUCCESS) { + logger_1.logger.error("No response or error when parsing .well-known"); + if (wellknown.reason) + logger_1.logger.error(wellknown.reason); + if (wellknown.action === AutoDiscoveryAction.IGNORE) { + clientConfig["m.homeserver"] = { + state: AutoDiscovery.PROMPT, + error: null, + base_url: null, + }; + } + else { + // this can only ever be FAIL_PROMPT at this point. + clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT; + clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID; + } + return Promise.resolve(clientConfig); + } + // Step 2: Validate and parse the config + return AutoDiscovery.fromDiscoveryConfig(wellknown.raw); + }); + } + /** + * Gets the raw discovery client configuration for the given domain name. + * Should only be used if there's no validation to be done on the resulting + * object, otherwise use findClientConfig(). + * @param domain - The domain to get the client config for. + * @returns Promise which resolves to the domain's client config. Can + * be an empty object. + */ + static getRawClientConfig(domain) { + return __awaiter(this, void 0, void 0, function* () { + if (!domain || typeof domain !== "string" || domain.length === 0) { + throw new Error("'domain' must be a string of non-zero length"); + } + const response = yield this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`); + if (!response) + return {}; + return response.raw || {}; + }); + } + /** + * Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and + * is suitable for the requirements laid out by .well-known auto discovery. + * If valid, the URL will also be stripped of any trailing slashes. + * @param url - The potentially invalid URL to sanitize. + * @returns The sanitized URL or a falsey value if the URL is invalid. + * @internal + */ + static sanitizeWellKnownUrl(url) { + if (!url) + return false; + try { + let parsed; + try { + parsed = new URL(url); + } + catch (e) { + logger_1.logger.error("Could not parse url", e); + } + if (!(parsed === null || parsed === void 0 ? void 0 : parsed.hostname)) + return false; + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") + return false; + const port = parsed.port ? `:${parsed.port}` : ""; + const path = parsed.pathname ? parsed.pathname : ""; + let saferUrl = `${parsed.protocol}//${parsed.hostname}${port}${path}`; + if (saferUrl.endsWith("/")) { + saferUrl = saferUrl.substring(0, saferUrl.length - 1); + } + return saferUrl; + } + catch (e) { + logger_1.logger.error(e); + return false; + } + } + static fetch(resource, options) { + if (this.fetchFn) { + return this.fetchFn(resource, options); + } + return global.fetch(resource, options); + } + static setFetchFn(fetchFn) { + AutoDiscovery.fetchFn = fetchFn; + } + /** + * Fetches a JSON object from a given URL, as expected by all .well-known + * related lookups. If the server gives a 404 then the `action` will be + * IGNORE. If the server returns something that isn't JSON, the `action` + * will be FAIL_PROMPT. For any other failure the `action` will be FAIL_PROMPT. + * + * The returned object will be a result of the call in object form with + * the following properties: + * raw: The JSON object returned by the server. + * action: One of SUCCESS, IGNORE, or FAIL_PROMPT. + * reason: Relatively human-readable description of what went wrong. + * error: The actual Error, if one exists. + * @param url - The URL to fetch a JSON object from. + * @returns Promise which resolves to the returned state. + * @internal + */ + static fetchWellKnownObject(url) { + return __awaiter(this, void 0, void 0, function* () { + let response; + try { + response = yield AutoDiscovery.fetch(url, { + method: http_api_1.Method.Get, + signal: (0, http_api_1.timeoutSignal)(5000), + }); + if (response.status === 404) { + return { + raw: {}, + action: AutoDiscoveryAction.IGNORE, + reason: AutoDiscovery.ERROR_MISSING_WELLKNOWN, + }; + } + if (!response.ok) { + return { + raw: {}, + action: AutoDiscoveryAction.FAIL_PROMPT, + reason: "General failure", + }; + } + } + catch (err) { + const error = err; + let reason = ""; + if (typeof error === "object") { + reason = error === null || error === void 0 ? void 0 : error.message; + } + return { + error, + raw: {}, + action: AutoDiscoveryAction.FAIL_PROMPT, + reason: reason || "General failure", + }; + } + try { + return { + raw: yield response.json(), + action: AutoDiscoveryAction.SUCCESS, + }; + } + catch (err) { + const error = err; + return { + error, + raw: {}, + action: AutoDiscoveryAction.FAIL_PROMPT, + reason: (error === null || error === void 0 ? void 0 : error.name) === "SyntaxError" + ? AutoDiscovery.ERROR_INVALID_JSON + : AutoDiscovery.ERROR_INVALID, + }; + } + }); + } +} +exports.AutoDiscovery = AutoDiscovery; +// Dev note: the constants defined here are related to but not +// exactly the same as those in the spec. This is to hopefully +// translate the meaning of the states in the spec, but also +// support our own if needed. +AutoDiscovery.ERROR_INVALID = AutoDiscoveryError.Invalid; +AutoDiscovery.ERROR_GENERIC_FAILURE = AutoDiscoveryError.GenericFailure; +AutoDiscovery.ERROR_INVALID_HS_BASE_URL = AutoDiscoveryError.InvalidHsBaseUrl; +AutoDiscovery.ERROR_INVALID_HOMESERVER = AutoDiscoveryError.InvalidHomeserver; +AutoDiscovery.ERROR_INVALID_IS_BASE_URL = AutoDiscoveryError.InvalidIsBaseUrl; +AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER = AutoDiscoveryError.InvalidIdentityServer; +AutoDiscovery.ERROR_INVALID_IS = AutoDiscoveryError.InvalidIs; +AutoDiscovery.ERROR_MISSING_WELLKNOWN = AutoDiscoveryError.MissingWellknown; +AutoDiscovery.ERROR_INVALID_JSON = AutoDiscoveryError.InvalidJson; +AutoDiscovery.ALL_ERRORS = Object.keys(AutoDiscoveryError); +/** + * The auto discovery failed. The client is expected to communicate + * the error to the user and refuse logging in. + */ +AutoDiscovery.FAIL_ERROR = AutoDiscoveryAction.FAIL_ERROR; +/** + * The auto discovery failed, however the client may still recover + * from the problem. The client is recommended to that the same + * action it would for PROMPT while also warning the user about + * what went wrong. The client may also treat this the same as + * a FAIL_ERROR state. + */ +AutoDiscovery.FAIL_PROMPT = AutoDiscoveryAction.FAIL_PROMPT; +/** + * The auto discovery didn't fail but did not find anything of + * interest. The client is expected to prompt the user for more + * information, or fail if it prefers. + */ +AutoDiscovery.PROMPT = AutoDiscoveryAction.PROMPT; +/** + * The auto discovery was successful. + */ +AutoDiscovery.SUCCESS = AutoDiscoveryAction.SUCCESS; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./http-api":367,"./logger":374}],320:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const matrixcs = __importStar(require("./matrix")); +if (global.__js_sdk_entrypoint) { + throw new Error("Multiple matrix-js-sdk entrypoints detected!"); +} +global.__js_sdk_entrypoint = true; +// just *accessing* indexedDB throws an exception in firefox with indexeddb disabled. +let indexedDB; +try { + indexedDB = global.indexedDB; +} +catch (e) { } +// if our browser (appears to) support indexeddb, use an indexeddb crypto store. +if (indexedDB) { + matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB, "matrix-js-sdk:crypto")); +} +// We export 3 things to make browserify happy as well as downstream projects. +// It's awkward, but required. +__exportStar(require("./matrix"), exports); +exports.default = matrixcs; // keep export for browserify package deps +global.matrixcs = matrixcs; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./matrix":375}],321:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2015-2023 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fixNotificationCountOnDecryption = exports.MatrixClient = exports.ClientEvent = exports.M_AUTHENTICATION = exports.RoomVersionStability = exports.PendingEventOrdering = exports.UNSTABLE_MSC3852_LAST_SEEN_UA = exports.CRYPTO_ENABLED = void 0; +const sync_1 = require("./sync"); +const event_1 = require("./models/event"); +const stub_1 = require("./store/stub"); +const call_1 = require("./webrtc/call"); +const filter_1 = require("./filter"); +const callEventHandler_1 = require("./webrtc/callEventHandler"); +const utils = __importStar(require("./utils")); +const utils_1 = require("./utils"); +const event_timeline_1 = require("./models/event-timeline"); +const pushprocessor_1 = require("./pushprocessor"); +const autodiscovery_1 = require("./autodiscovery"); +const olmlib = __importStar(require("./crypto/olmlib")); +const olmlib_1 = require("./crypto/olmlib"); +const ReEmitter_1 = require("./ReEmitter"); +const RoomList_1 = require("./crypto/RoomList"); +const logger_1 = require("./logger"); +const service_types_1 = require("./service-types"); +const http_api_1 = require("./http-api"); +const crypto_1 = require("./crypto"); +const recoverykey_1 = require("./crypto/recoverykey"); +const key_passphrase_1 = require("./crypto/key_passphrase"); +const user_1 = require("./models/user"); +const content_repo_1 = require("./content-repo"); +const search_result_1 = require("./models/search-result"); +const dehydration_1 = require("./crypto/dehydration"); +const api_1 = require("./crypto/api"); +const ContentHelpers = __importStar(require("./content-helpers")); +const room_1 = require("./models/room"); +const room_member_1 = require("./models/room-member"); +const event_2 = require("./@types/event"); +const partials_1 = require("./@types/partials"); +const event_mapper_1 = require("./event-mapper"); +const randomstring_1 = require("./randomstring"); +const backup_1 = require("./crypto/backup"); +const MSC3089TreeSpace_1 = require("./models/MSC3089TreeSpace"); +const search_1 = require("./@types/search"); +const PushRules_1 = require("./@types/PushRules"); +const groupCall_1 = require("./webrtc/groupCall"); +const mediaHandler_1 = require("./webrtc/mediaHandler"); +const groupCallEventHandler_1 = require("./webrtc/groupCallEventHandler"); +const typed_event_emitter_1 = require("./models/typed-event-emitter"); +const read_receipts_1 = require("./@types/read_receipts"); +const sliding_sync_sdk_1 = require("./sliding-sync-sdk"); +const thread_1 = require("./models/thread"); +const beacon_1 = require("./@types/beacon"); +const NamespacedValue_1 = require("./NamespacedValue"); +const ToDeviceMessageQueue_1 = require("./ToDeviceMessageQueue"); +const invites_ignorer_1 = require("./models/invites-ignorer"); +const feature_1 = require("./feature"); +const constants_1 = require("./rust-crypto/constants"); +const SCROLLBACK_DELAY_MS = 3000; +exports.CRYPTO_ENABLED = (0, crypto_1.isCryptoAvailable)(); +const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value +const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes +exports.UNSTABLE_MSC3852_LAST_SEEN_UA = new NamespacedValue_1.UnstableValue("last_seen_user_agent", "org.matrix.msc3852.last_seen_user_agent"); +var PendingEventOrdering; +(function (PendingEventOrdering) { + PendingEventOrdering["Chronological"] = "chronological"; + PendingEventOrdering["Detached"] = "detached"; +})(PendingEventOrdering = exports.PendingEventOrdering || (exports.PendingEventOrdering = {})); +var RoomVersionStability; +(function (RoomVersionStability) { + RoomVersionStability["Stable"] = "stable"; + RoomVersionStability["Unstable"] = "unstable"; +})(RoomVersionStability = exports.RoomVersionStability || (exports.RoomVersionStability = {})); +var CrossSigningKeyType; +(function (CrossSigningKeyType) { + CrossSigningKeyType["MasterKey"] = "master_key"; + CrossSigningKeyType["SelfSigningKey"] = "self_signing_key"; + CrossSigningKeyType["UserSigningKey"] = "user_signing_key"; +})(CrossSigningKeyType || (CrossSigningKeyType = {})); +exports.M_AUTHENTICATION = new NamespacedValue_1.UnstableValue("m.authentication", "org.matrix.msc2965.authentication"); +/* eslint-enable camelcase */ +// We're using this constant for methods overloading and inspect whether a variable +// contains an eventId or not. This was required to ensure backwards compatibility +// of methods for threads +// Probably not the most graceful solution but does a good enough job for now +const EVENT_ID_PREFIX = "$"; +var ClientEvent; +(function (ClientEvent) { + ClientEvent["Sync"] = "sync"; + ClientEvent["Event"] = "event"; + ClientEvent["ToDeviceEvent"] = "toDeviceEvent"; + ClientEvent["AccountData"] = "accountData"; + ClientEvent["Room"] = "Room"; + ClientEvent["DeleteRoom"] = "deleteRoom"; + ClientEvent["SyncUnexpectedError"] = "sync.unexpectedError"; + ClientEvent["ClientWellKnown"] = "WellKnown.client"; + ClientEvent["ReceivedVoipEvent"] = "received_voip_event"; + ClientEvent["UndecryptableToDeviceEvent"] = "toDeviceEvent.undecryptable"; + ClientEvent["TurnServers"] = "turnServers"; + ClientEvent["TurnServersError"] = "turnServers.error"; +})(ClientEvent = exports.ClientEvent || (exports.ClientEvent = {})); +const SSO_ACTION_PARAM = new NamespacedValue_1.UnstableValue("action", "org.matrix.msc3824.action"); +/** + * Represents a Matrix Client. Only directly construct this if you want to use + * custom modules. Normally, {@link createClient} should be used + * as it specifies 'sensible' defaults for these modules. + */ +class MatrixClient extends typed_event_emitter_1.TypedEventEmitter { + constructor(opts) { + var _a; + super(); + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + this.olmVersion = null; // populated after initCrypto + this.usingExternalCrypto = false; + this.clientRunning = false; + this.timelineSupport = false; + this.urlPreviewCache = {}; + this.supportsCallTransfer = false; // XXX: Intended private, used in code. + this.forceTURN = false; // XXX: Intended private, used in code. + this.iceCandidatePoolSize = 0; // XXX: Intended private, used in code. + // Note: these are all `protected` to let downstream consumers make mistakes if they want to. + // We don't technically support this usage, but have reasons to do this. + this.canSupportVoip = false; + this.peekSync = null; + this.isGuestAccount = false; + this.ongoingScrollbacks = {}; + this.notifTimelineSet = null; + this.fallbackICEServerAllowed = false; + this.syncedLeftRooms = false; + this.canSupport = new Map(); + // The pushprocessor caches useful things, so keep one and re-use it + this.pushProcessor = new pushprocessor_1.PushProcessor(this); + this.turnServers = []; + this.turnServersExpiry = 0; + this.txnCtr = 0; + this.mediaHandler = new mediaHandler_1.MediaHandler(this); + this.pendingEventEncryption = new Map(); + this.useE2eForGroupCall = true; + this.startCallEventHandler = () => { + if (this.isInitialSyncComplete()) { + this.callEventHandler.start(); + this.groupCallEventHandler.start(); + this.off(ClientEvent.Sync, this.startCallEventHandler); + } + }; + /** + * Once the client has been initialised, we want to clear notifications we + * know for a fact should be here. + * This issue should also be addressed on synapse's side and is tracked as part + * of https://github.com/matrix-org/synapse/issues/14837 + * + * We consider a room or a thread as fully read if the current user has sent + * the last event in the live timeline of that context and if the read receipt + * we have on record matches. + */ + this.fixupRoomNotifications = () => { + var _a; + if (this.isInitialSyncComplete()) { + const unreadRooms = ((_a = this.getRooms()) !== null && _a !== void 0 ? _a : []).filter((room) => { + return room.getUnreadNotificationCount(room_1.NotificationCountType.Total) > 0; + }); + for (const room of unreadRooms) { + const currentUserId = this.getSafeUserId(); + room.fixupNotifications(currentUserId); + } + this.off(ClientEvent.Sync, this.fixupRoomNotifications); + } + }; + opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl); + opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl); + this.baseUrl = opts.baseUrl; + this.idBaseUrl = opts.idBaseUrl; + this.identityServer = opts.identityServer; + this.usingExternalCrypto = (_a = opts.usingExternalCrypto) !== null && _a !== void 0 ? _a : false; + this.store = opts.store || new stub_1.StubStore(); + this.deviceId = opts.deviceId || null; + this.sessionId = (0, randomstring_1.randomString)(10); + const userId = opts.userId || null; + this.credentials = { userId }; + this.http = new http_api_1.MatrixHttpApi(this, { + fetchFn: opts.fetchFn, + baseUrl: opts.baseUrl, + idBaseUrl: opts.idBaseUrl, + accessToken: opts.accessToken, + prefix: http_api_1.ClientPrefix.R0, + onlyData: true, + extraParams: opts.queryParams, + localTimeoutMs: opts.localTimeoutMs, + useAuthorizationHeader: opts.useAuthorizationHeader, + }); + if (opts.deviceToImport) { + if (this.deviceId) { + logger_1.logger.warn("not importing device because device ID is provided to " + + "constructor independently of exported data"); + } + else if (this.credentials.userId) { + logger_1.logger.warn("not importing device because user ID is provided to " + + "constructor independently of exported data"); + } + else if (!opts.deviceToImport.deviceId) { + logger_1.logger.warn("not importing device because no device ID in exported data"); + } + else { + this.deviceId = opts.deviceToImport.deviceId; + this.credentials.userId = opts.deviceToImport.userId; + // will be used during async initialization of the crypto + this.exportedOlmDeviceToImport = opts.deviceToImport.olmDevice; + } + } + else if (opts.pickleKey) { + this.pickleKey = opts.pickleKey; + } + this.scheduler = opts.scheduler; + if (this.scheduler) { + this.scheduler.setProcessFunction((eventToSend) => __awaiter(this, void 0, void 0, function* () { + const room = this.getRoom(eventToSend.getRoomId()); + if (eventToSend.status !== event_1.EventStatus.SENDING) { + this.updatePendingEventStatus(room, eventToSend, event_1.EventStatus.SENDING); + } + const res = yield this.sendEventHttpRequest(eventToSend); + if (room) { + // ensure we update pending event before the next scheduler run so that any listeners to event id + // updates on the synchronous event emitter get a chance to run first. + room.updatePendingEvent(eventToSend, event_1.EventStatus.SENT, res.event_id); + } + return res; + })); + } + if ((0, call_1.supportsMatrixCall)()) { + this.callEventHandler = new callEventHandler_1.CallEventHandler(this); + this.groupCallEventHandler = new groupCallEventHandler_1.GroupCallEventHandler(this); + this.canSupportVoip = true; + // Start listening for calls after the initial sync is done + // We do not need to backfill the call event buffer + // with encrypted events that might never get decrypted + this.on(ClientEvent.Sync, this.startCallEventHandler); + } + this.on(ClientEvent.Sync, this.fixupRoomNotifications); + this.timelineSupport = Boolean(opts.timelineSupport); + this.cryptoStore = opts.cryptoStore; + this.verificationMethods = opts.verificationMethods; + this.cryptoCallbacks = opts.cryptoCallbacks || {}; + this.forceTURN = opts.forceTURN || false; + this.iceCandidatePoolSize = opts.iceCandidatePoolSize === undefined ? 0 : opts.iceCandidatePoolSize; + this.supportsCallTransfer = opts.supportsCallTransfer || false; + this.fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false; + this.isVoipWithNoMediaAllowed = opts.isVoipWithNoMediaAllowed || false; + if (opts.useE2eForGroupCall !== undefined) + this.useE2eForGroupCall = opts.useE2eForGroupCall; + // List of which rooms have encryption enabled: separate from crypto because + // we still want to know which rooms are encrypted even if crypto is disabled: + // we don't want to start sending unencrypted events to them. + this.roomList = new RoomList_1.RoomList(this.cryptoStore); + this.roomNameGenerator = opts.roomNameGenerator; + this.toDeviceMessageQueue = new ToDeviceMessageQueue_1.ToDeviceMessageQueue(this); + // The SDK doesn't really provide a clean way for events to recalculate the push + // actions for themselves, so we have to kinda help them out when they are encrypted. + // We do this so that push rules are correctly executed on events in their decrypted + // state, such as highlights when the user's name is mentioned. + this.on(event_1.MatrixEventEvent.Decrypted, (event) => { + fixNotificationCountOnDecryption(this, event); + }); + // Like above, we have to listen for read receipts from ourselves in order to + // correctly handle notification counts on encrypted rooms. + // This fixes https://github.com/vector-im/element-web/issues/9421 + this.on(room_1.RoomEvent.Receipt, (event, room) => { + var _a; + if (room && this.isRoomEncrypted(room.roomId)) { + // Figure out if we've read something or if it's just informational + const content = event.getContent(); + const isSelf = Object.keys(content).filter((eid) => { + for (const [key, value] of Object.entries(content[eid])) { + if (!utils.isSupportedReceiptType(key)) + continue; + if (!value) + continue; + if (Object.keys(value).includes(this.getUserId())) + return true; + } + return false; + }).length > 0; + if (!isSelf) + return; + // Work backwards to determine how many events are unread. We also set + // a limit for how back we'll look to avoid spinning CPU for too long. + // If we hit the limit, we assume the count is unchanged. + const maxHistory = 20; + const events = room.getLiveTimeline().getEvents(); + let highlightCount = 0; + for (let i = events.length - 1; i >= 0; i--) { + if (i === events.length - maxHistory) + return; // limit reached + const event = events[i]; + if (room.hasUserReadEvent(this.getUserId(), event.getId())) { + // If the user has read the event, then the counting is done. + break; + } + const pushActions = this.getPushActionsForEvent(event); + highlightCount += ((_a = pushActions === null || pushActions === void 0 ? void 0 : pushActions.tweaks) === null || _a === void 0 ? void 0 : _a.highlight) ? 1 : 0; + } + // Note: we don't need to handle 'total' notifications because the counts + // will come from the server. + room.setUnreadNotificationCount(room_1.NotificationCountType.Highlight, highlightCount); + } + }); + this.ignoredInvites = new invites_ignorer_1.IgnoredInvites(this); + } + /** + * High level helper method to begin syncing and poll for new events. To listen for these + * events, add a listener for {@link ClientEvent.Event} + * via {@link MatrixClient#on}. Alternatively, listen for specific + * state change events. + * @param opts - Options to apply when syncing. + */ + startClient(opts) { + return __awaiter(this, void 0, void 0, function* () { + if (this.clientRunning) { + // client is already running. + return; + } + this.clientRunning = true; + // backwards compat for when 'opts' was 'historyLen'. + if (typeof opts === "number") { + opts = { + initialSyncLimit: opts, + }; + } + // Create our own user object artificially (instead of waiting for sync) + // so it's always available, even if the user is not in any rooms etc. + const userId = this.getUserId(); + if (userId) { + this.store.storeUser(new user_1.User(userId)); + } + // periodically poll for turn servers if we support voip + if (this.canSupportVoip) { + this.checkTurnServersIntervalID = setInterval(() => { + this.checkTurnServers(); + }, TURN_CHECK_INTERVAL); + // noinspection ES6MissingAwait + this.checkTurnServers(); + } + if (this.syncApi) { + // This shouldn't happen since we thought the client was not running + logger_1.logger.error("Still have sync object whilst not running: stopping old one"); + this.syncApi.stop(); + } + try { + yield this.getVersions(); + // This should be done with `canSupport` + // TODO: https://github.com/vector-im/element-web/issues/23643 + const { threads, list, fwdPagination } = yield this.doesServerSupportThread(); + thread_1.Thread.setServerSideSupport(threads); + thread_1.Thread.setServerSideListSupport(list); + thread_1.Thread.setServerSideFwdPaginationSupport(fwdPagination); + } + catch (e) { + logger_1.logger.error("Can't fetch server versions, continuing to initialise sync, this will be retried later", e); + } + this.clientOpts = opts !== null && opts !== void 0 ? opts : {}; + if (this.clientOpts.slidingSync) { + this.syncApi = new sliding_sync_sdk_1.SlidingSyncSdk(this.clientOpts.slidingSync, this, this.clientOpts, this.buildSyncApiOptions()); + } + else { + this.syncApi = new sync_1.SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); + } + if (this.clientOpts.hasOwnProperty("experimentalThreadSupport")) { + logger_1.logger.warn("`experimentalThreadSupport` has been deprecated, use `threadSupport` instead"); + } + // If `threadSupport` is omitted and the deprecated `experimentalThreadSupport` has been passed + // We should fallback to that value for backwards compatibility purposes + if (!this.clientOpts.hasOwnProperty("threadSupport") && + this.clientOpts.hasOwnProperty("experimentalThreadSupport")) { + this.clientOpts.threadSupport = this.clientOpts.experimentalThreadSupport; + } + this.syncApi.sync(); + if (this.clientOpts.clientWellKnownPollPeriod !== undefined) { + this.clientWellKnownIntervalID = setInterval(() => { + this.fetchClientWellKnown(); + }, 1000 * this.clientOpts.clientWellKnownPollPeriod); + this.fetchClientWellKnown(); + } + this.toDeviceMessageQueue.start(); + }); + } + /** + * Construct a SyncApiOptions for this client, suitable for passing into the SyncApi constructor + */ + buildSyncApiOptions() { + return { + crypto: this.crypto, + cryptoCallbacks: this.cryptoBackend, + canResetEntireTimeline: (roomId) => { + if (!this.canResetTimelineCallback) { + return false; + } + return this.canResetTimelineCallback(roomId); + }, + }; + } + /** + * High level helper method to stop the client from polling and allow a + * clean shutdown. + */ + stopClient() { + var _a, _b, _c, _d, _e; + (_a = this.cryptoBackend) === null || _a === void 0 ? void 0 : _a.stop(); // crypto might have been initialised even if the client wasn't fully started + if (!this.clientRunning) + return; // already stopped + logger_1.logger.log("stopping MatrixClient"); + this.clientRunning = false; + (_b = this.syncApi) === null || _b === void 0 ? void 0 : _b.stop(); + this.syncApi = undefined; + (_c = this.peekSync) === null || _c === void 0 ? void 0 : _c.stopPeeking(); + (_d = this.callEventHandler) === null || _d === void 0 ? void 0 : _d.stop(); + (_e = this.groupCallEventHandler) === null || _e === void 0 ? void 0 : _e.stop(); + this.callEventHandler = undefined; + this.groupCallEventHandler = undefined; + global.clearInterval(this.checkTurnServersIntervalID); + this.checkTurnServersIntervalID = undefined; + if (this.clientWellKnownIntervalID !== undefined) { + global.clearInterval(this.clientWellKnownIntervalID); + } + this.toDeviceMessageQueue.stop(); + } + /** + * Try to rehydrate a device if available. The client must have been + * initialized with a `cryptoCallback.getDehydrationKey` option, and this + * function must be called before initCrypto and startClient are called. + * + * @returns Promise which resolves to undefined if a device could not be dehydrated, or + * to the new device ID if the dehydration was successful. + * @returns Rejects: with an error response. + */ + rehydrateDevice() { + return __awaiter(this, void 0, void 0, function* () { + if (this.crypto) { + throw new Error("Cannot rehydrate device after crypto is initialized"); + } + if (!this.cryptoCallbacks.getDehydrationKey) { + return; + } + const getDeviceResult = yield this.getDehydratedDevice(); + if (!getDeviceResult) { + return; + } + if (!getDeviceResult.device_data || !getDeviceResult.device_id) { + logger_1.logger.info("no dehydrated device found"); + return; + } + const account = new global.Olm.Account(); + try { + const deviceData = getDeviceResult.device_data; + if (deviceData.algorithm !== dehydration_1.DEHYDRATION_ALGORITHM) { + logger_1.logger.warn("Wrong algorithm for dehydrated device"); + return; + } + logger_1.logger.log("unpickling dehydrated device"); + const key = yield this.cryptoCallbacks.getDehydrationKey(deviceData, (k) => { + // copy the key so that it doesn't get clobbered + account.unpickle(new Uint8Array(k), deviceData.account); + }); + account.unpickle(key, deviceData.account); + logger_1.logger.log("unpickled device"); + const rehydrateResult = yield this.http.authedRequest(http_api_1.Method.Post, "/dehydrated_device/claim", undefined, { + device_id: getDeviceResult.device_id, + }, { + prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2", + }); + if (rehydrateResult.success) { + this.deviceId = getDeviceResult.device_id; + logger_1.logger.info("using dehydrated device"); + const pickleKey = this.pickleKey || "DEFAULT_KEY"; + this.exportedOlmDeviceToImport = { + pickledAccount: account.pickle(pickleKey), + sessions: [], + pickleKey: pickleKey, + }; + account.free(); + return this.deviceId; + } + else { + account.free(); + logger_1.logger.info("not using dehydrated device"); + return; + } + } + catch (e) { + account.free(); + logger_1.logger.warn("could not unpickle", e); + } + }); + } + /** + * Get the current dehydrated device, if any + * @returns A promise of an object containing the dehydrated device + */ + getDehydratedDevice() { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield this.http.authedRequest(http_api_1.Method.Get, "/dehydrated_device", undefined, undefined, { + prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2", + }); + } + catch (e) { + logger_1.logger.info("could not get dehydrated device", e); + return; + } + }); + } + /** + * Set the dehydration key. This will also periodically dehydrate devices to + * the server. + * + * @param key - the dehydration key + * @param keyInfo - Information about the key. Primarily for + * information about how to generate the key from a passphrase. + * @param deviceDisplayName - The device display name for the + * dehydrated device. + * @returns A promise that resolves when the dehydrated device is stored. + */ + setDehydrationKey(key, keyInfo, deviceDisplayName) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + logger_1.logger.warn("not dehydrating device if crypto is not enabled"); + return; + } + return this.crypto.dehydrationManager.setKeyAndQueueDehydration(key, keyInfo, deviceDisplayName); + }); + } + /** + * Creates a new dehydrated device (without queuing periodic dehydration) + * @param key - the dehydration key + * @param keyInfo - Information about the key. Primarily for + * information about how to generate the key from a passphrase. + * @param deviceDisplayName - The device display name for the + * dehydrated device. + * @returns the device id of the newly created dehydrated device + */ + createDehydratedDevice(key, keyInfo, deviceDisplayName) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + logger_1.logger.warn("not dehydrating device if crypto is not enabled"); + return; + } + yield this.crypto.dehydrationManager.setKey(key, keyInfo, deviceDisplayName); + return this.crypto.dehydrationManager.dehydrateDevice(); + }); + } + exportDevice() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + logger_1.logger.warn("not exporting device if crypto is not enabled"); + return; + } + return { + userId: this.credentials.userId, + deviceId: this.deviceId, + // XXX: Private member access. + olmDevice: yield this.crypto.olmDevice.export(), + }; + }); + } + /** + * Clear any data out of the persistent stores used by the client. + * + * @returns Promise which resolves when the stores have been cleared. + */ + clearStores() { + if (this.clientRunning) { + throw new Error("Cannot clear stores while client is running"); + } + const promises = []; + promises.push(this.store.deleteAllData()); + if (this.cryptoStore) { + promises.push(this.cryptoStore.deleteAllData()); + } + // delete the stores used by the rust matrix-sdk-crypto, in case they were used + const deleteRustSdkStore = () => __awaiter(this, void 0, void 0, function* () { + let indexedDB; + try { + indexedDB = global.indexedDB; + } + catch (e) { + // No indexeddb support + return; + } + for (const dbname of [ + `${constants_1.RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto`, + `${constants_1.RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto-meta`, + ]) { + const prom = new Promise((resolve, reject) => { + logger_1.logger.info(`Removing IndexedDB instance ${dbname}`); + const req = indexedDB.deleteDatabase(dbname); + req.onsuccess = (_) => { + logger_1.logger.info(`Removed IndexedDB instance ${dbname}`); + resolve(0); + }; + req.onerror = (e) => { + // In private browsing, Firefox has a global.indexedDB, but attempts to delete an indexeddb + // (even a non-existent one) fail with "DOMException: A mutation operation was attempted on a + // database that did not allow mutations." + // + // it seems like the only thing we can really do is ignore the error. + logger_1.logger.warn(`Failed to remove IndexedDB instance ${dbname}:`, e); + resolve(0); + }; + req.onblocked = (e) => { + logger_1.logger.info(`cannot yet remove IndexedDB instance ${dbname}`); + }; + }); + yield prom; + } + }); + promises.push(deleteRustSdkStore()); + return Promise.all(promises).then(); // .then to fix types + } + /** + * Get the user-id of the logged-in user + * + * @returns MXID for the logged-in user, or null if not logged in + */ + getUserId() { + if (this.credentials && this.credentials.userId) { + return this.credentials.userId; + } + return null; + } + /** + * Get the user-id of the logged-in user + * + * @returns MXID for the logged-in user + * @throws Error if not logged in + */ + getSafeUserId() { + const userId = this.getUserId(); + if (!userId) { + throw new Error("Expected logged in user but found none."); + } + return userId; + } + /** + * Get the domain for this client's MXID + * @returns Domain of this MXID + */ + getDomain() { + if (this.credentials && this.credentials.userId) { + return this.credentials.userId.replace(/^.*?:/, ""); + } + return null; + } + /** + * Get the local part of the current user ID e.g. "foo" in "\@foo:bar". + * @returns The user ID localpart or null. + */ + getUserIdLocalpart() { + if (this.credentials && this.credentials.userId) { + return this.credentials.userId.split(":")[0].substring(1); + } + return null; + } + /** + * Get the device ID of this client + * @returns device ID + */ + getDeviceId() { + return this.deviceId; + } + /** + * Get the session ID of this client + * @returns session ID + */ + getSessionId() { + return this.sessionId; + } + /** + * Check if the runtime environment supports VoIP calling. + * @returns True if VoIP is supported. + */ + supportsVoip() { + return this.canSupportVoip; + } + /** + * @returns + */ + getMediaHandler() { + return this.mediaHandler; + } + /** + * Set whether VoIP calls are forced to use only TURN + * candidates. This is the same as the forceTURN option + * when creating the client. + * @param force - True to force use of TURN servers + */ + setForceTURN(force) { + this.forceTURN = force; + } + /** + * Set whether to advertise transfer support to other parties on Matrix calls. + * @param support - True to advertise the 'm.call.transferee' capability + */ + setSupportsCallTransfer(support) { + this.supportsCallTransfer = support; + } + /** + * Returns true if to-device signalling for group calls will be encrypted with Olm. + * If false, it will be sent unencrypted. + * @returns boolean Whether group call signalling will be encrypted + */ + getUseE2eForGroupCall() { + return this.useE2eForGroupCall; + } + /** + * Creates a new call. + * The place*Call methods on the returned call can be used to actually place a call + * + * @param roomId - The room the call is to be placed in. + * @returns the call or null if the browser doesn't support calling. + */ + createCall(roomId) { + return (0, call_1.createNewMatrixCall)(this, roomId); + } + /** + * Creates a new group call and sends the associated state event + * to alert other members that the room now has a group call. + * + * @param roomId - The room the call is to be placed in. + */ + createGroupCall(roomId, type, isPtt, intent, dataChannelsEnabled, dataChannelOptions) { + return __awaiter(this, void 0, void 0, function* () { + if (this.getGroupCallForRoom(roomId)) { + throw new Error(`${roomId} already has an existing group call`); + } + const room = this.getRoom(roomId); + if (!room) { + throw new Error(`Cannot find room ${roomId}`); + } + // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a + // no media WebRTC connection anyway. + return new groupCall_1.GroupCall(this, room, type, isPtt, intent, undefined, dataChannelsEnabled || this.isVoipWithNoMediaAllowed, dataChannelOptions, this.isVoipWithNoMediaAllowed).create(); + }); + } + /** + * Wait until an initial state for the given room has been processed by the + * client and the client is aware of any ongoing group calls. Awaiting on + * the promise returned by this method before calling getGroupCallForRoom() + * avoids races where getGroupCallForRoom is called before the state for that + * room has been processed. It does not, however, fix other races, eg. two + * clients both creating a group call at the same time. + * @param roomId - The room ID to wait for + * @returns A promise that resolves once existing group calls in the room + * have been processed. + */ + waitUntilRoomReadyForGroupCalls(roomId) { + return this.groupCallEventHandler.waitUntilRoomReadyForGroupCalls(roomId); + } + /** + * Get an existing group call for the provided room. + * @returns The group call or null if it doesn't already exist. + */ + getGroupCallForRoom(roomId) { + return this.groupCallEventHandler.groupCalls.get(roomId) || null; + } + /** + * Get the current sync state. + * @returns the sync state, which may be null. + * @see MatrixClient#event:"sync" + */ + getSyncState() { + var _a, _b; + return (_b = (_a = this.syncApi) === null || _a === void 0 ? void 0 : _a.getSyncState()) !== null && _b !== void 0 ? _b : null; + } + /** + * Returns the additional data object associated with + * the current sync state, or null if there is no + * such data. + * Sync errors, if available, are put in the 'error' key of + * this object. + */ + getSyncStateData() { + if (!this.syncApi) { + return null; + } + return this.syncApi.getSyncStateData(); + } + /** + * Whether the initial sync has completed. + * @returns True if at least one sync has happened. + */ + isInitialSyncComplete() { + const state = this.getSyncState(); + if (!state) { + return false; + } + return state === sync_1.SyncState.Prepared || state === sync_1.SyncState.Syncing; + } + /** + * Return whether the client is configured for a guest account. + * @returns True if this is a guest access_token (or no token is supplied). + */ + isGuest() { + return this.isGuestAccount; + } + /** + * Set whether this client is a guest account. This method is experimental + * and may change without warning. + * @param guest - True if this is a guest account. + */ + setGuest(guest) { + // EXPERIMENTAL: + // If the token is a macaroon, it should be encoded in it that it is a 'guest' + // access token, which means that the SDK can determine this entirely without + // the dev manually flipping this flag. + this.isGuestAccount = guest; + } + /** + * Return the provided scheduler, if any. + * @returns The scheduler or undefined + */ + getScheduler() { + return this.scheduler; + } + /** + * Retry a backed off syncing request immediately. This should only be used when + * the user explicitly attempts to retry their lost connection. + * Will also retry any outbound to-device messages currently in the queue to be sent + * (retries of regular outgoing events are handled separately, per-event). + * @returns True if this resulted in a request being retried. + */ + retryImmediately() { + var _a, _b; + // don't await for this promise: we just want to kick it off + this.toDeviceMessageQueue.sendQueue(); + return (_b = (_a = this.syncApi) === null || _a === void 0 ? void 0 : _a.retryImmediately()) !== null && _b !== void 0 ? _b : false; + } + /** + * Return the global notification EventTimelineSet, if any + * + * @returns the globl notification EventTimelineSet + */ + getNotifTimelineSet() { + return this.notifTimelineSet; + } + /** + * Set the global notification EventTimelineSet + * + */ + setNotifTimelineSet(set) { + this.notifTimelineSet = set; + } + /** + * Gets the capabilities of the homeserver. Always returns an object of + * capability keys and their options, which may be empty. + * @param fresh - True to ignore any cached values. + * @returns Promise which resolves to the capabilities of the homeserver + * @returns Rejects: with an error response. + */ + getCapabilities(fresh = false) { + const now = new Date().getTime(); + if (this.cachedCapabilities && !fresh) { + if (now < this.cachedCapabilities.expiration) { + logger_1.logger.log("Returning cached capabilities"); + return Promise.resolve(this.cachedCapabilities.capabilities); + } + } + return this.http + .authedRequest(http_api_1.Method.Get, "/capabilities") + .catch((e) => { + // We swallow errors because we need a default object anyhow + logger_1.logger.error(e); + return {}; + }) + .then((r = {}) => { + const capabilities = r["capabilities"] || {}; + // If the capabilities missed the cache, cache it for a shorter amount + // of time to try and refresh them later. + const cacheMs = Object.keys(capabilities).length ? CAPABILITIES_CACHE_MS : 60000 + Math.random() * 5000; + this.cachedCapabilities = { + capabilities, + expiration: now + cacheMs, + }; + logger_1.logger.log("Caching capabilities: ", capabilities); + return capabilities; + }); + } + /** + * Initialise support for end-to-end encryption in this client, using libolm. + * + * You should call this method after creating the matrixclient, but *before* + * calling `startClient`, if you want to support end-to-end encryption. + * + * It will return a Promise which will resolve when the crypto layer has been + * successfully initialised. + */ + initCrypto() { + return __awaiter(this, void 0, void 0, function* () { + if (!(0, crypto_1.isCryptoAvailable)()) { + throw new Error(`End-to-end encryption not supported in this js-sdk build: did ` + + `you remember to load the olm library?`); + } + if (this.cryptoBackend) { + logger_1.logger.warn("Attempt to re-initialise e2e encryption on MatrixClient"); + return; + } + if (!this.cryptoStore) { + // the cryptostore is provided by sdk.createClient, so this shouldn't happen + throw new Error(`Cannot enable encryption: no cryptoStore provided`); + } + logger_1.logger.log("Crypto: Starting up crypto store..."); + yield this.cryptoStore.startup(); + // initialise the list of encrypted rooms (whether or not crypto is enabled) + logger_1.logger.log("Crypto: initialising roomlist..."); + yield this.roomList.init(); + const userId = this.getUserId(); + if (userId === null) { + throw new Error(`Cannot enable encryption on MatrixClient with unknown userId: ` + + `ensure userId is passed in createClient().`); + } + if (this.deviceId === null) { + throw new Error(`Cannot enable encryption on MatrixClient with unknown deviceId: ` + + `ensure deviceId is passed in createClient().`); + } + const crypto = new crypto_1.Crypto(this, userId, this.deviceId, this.store, this.cryptoStore, this.roomList, this.verificationMethods); + this.reEmitter.reEmit(crypto, [ + crypto_1.CryptoEvent.KeyBackupFailed, + crypto_1.CryptoEvent.KeyBackupSessionsRemaining, + crypto_1.CryptoEvent.RoomKeyRequest, + crypto_1.CryptoEvent.RoomKeyRequestCancellation, + crypto_1.CryptoEvent.Warning, + crypto_1.CryptoEvent.DevicesUpdated, + crypto_1.CryptoEvent.WillUpdateDevices, + crypto_1.CryptoEvent.DeviceVerificationChanged, + crypto_1.CryptoEvent.UserTrustStatusChanged, + crypto_1.CryptoEvent.KeysChanged, + ]); + logger_1.logger.log("Crypto: initialising crypto object..."); + yield crypto.init({ + exportedOlmDevice: this.exportedOlmDeviceToImport, + pickleKey: this.pickleKey, + }); + delete this.exportedOlmDeviceToImport; + this.olmVersion = crypto_1.Crypto.getOlmVersion(); + // if crypto initialisation was successful, tell it to attach its event handlers. + crypto.registerEventHandlers(this); + this.cryptoBackend = this.crypto = crypto; + // upload our keys in the background + this.crypto.uploadDeviceKeys().catch((e) => { + // TODO: throwing away this error is a really bad idea. + logger_1.logger.error("Error uploading device keys", e); + }); + }); + } + /** + * Initialise support for end-to-end encryption in this client, using the rust matrix-sdk-crypto. + * + * An alternative to {@link initCrypto}. + * + * *WARNING*: this API is very experimental, should not be used in production, and may change without notice! + * Eventually it will be deprecated and `initCrypto` will do the same thing. + * + * @experimental + * + * @returns a Promise which will resolve when the crypto layer has been + * successfully initialised. + */ + initRustCrypto() { + return __awaiter(this, void 0, void 0, function* () { + if (this.cryptoBackend) { + logger_1.logger.warn("Attempt to re-initialise e2e encryption on MatrixClient"); + return; + } + const userId = this.getUserId(); + if (userId === null) { + throw new Error(`Cannot enable encryption on MatrixClient with unknown userId: ` + + `ensure userId is passed in createClient().`); + } + const deviceId = this.getDeviceId(); + if (deviceId === null) { + throw new Error(`Cannot enable encryption on MatrixClient with unknown deviceId: ` + + `ensure deviceId is passed in createClient().`); + } + // importing rust-crypto will download the webassembly, so we delay it until we know it will be + // needed. + const RustCrypto = yield Promise.resolve().then(() => __importStar(require("./rust-crypto"))); + const rustCrypto = yield RustCrypto.initRustCrypto(this.http, userId, deviceId); + this.cryptoBackend = rustCrypto; + // attach the event listeners needed by RustCrypto + this.on(room_member_1.RoomMemberEvent.Membership, rustCrypto.onRoomMembership.bind(rustCrypto)); + }); + } + /** + * Access the crypto API for this client. + * + * If end-to-end encryption has been enabled for this client (via {@link initCrypto} or {@link initRustCrypto}), + * returns an object giving access to the crypto API. Otherwise, returns `undefined`. + */ + getCrypto() { + return this.cryptoBackend; + } + /** + * Is end-to-end crypto enabled for this client. + * @returns True if end-to-end is enabled. + * @deprecated prefer {@link getCrypto} + */ + isCryptoEnabled() { + return !!this.cryptoBackend; + } + /** + * Get the Ed25519 key for this device + * + * @returns base64-encoded ed25519 key. Null if crypto is + * disabled. + */ + getDeviceEd25519Key() { + var _a, _b; + return (_b = (_a = this.crypto) === null || _a === void 0 ? void 0 : _a.getDeviceEd25519Key()) !== null && _b !== void 0 ? _b : null; + } + /** + * Get the Curve25519 key for this device + * + * @returns base64-encoded curve25519 key. Null if crypto is + * disabled. + */ + getDeviceCurve25519Key() { + var _a, _b; + return (_b = (_a = this.crypto) === null || _a === void 0 ? void 0 : _a.getDeviceCurve25519Key()) !== null && _b !== void 0 ? _b : null; + } + /** + * @deprecated Does nothing. + */ + uploadKeys() { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.warn("MatrixClient.uploadKeys is deprecated"); + }); + } + /** + * Download the keys for a list of users and stores the keys in the session + * store. + * @param userIds - The users to fetch. + * @param forceDownload - Always download the keys even if cached. + * + * @returns A promise which resolves to a map userId-\>deviceId-\>{@link DeviceInfo} + */ + downloadKeys(userIds, forceDownload) { + if (!this.crypto) { + return Promise.reject(new Error("End-to-end encryption disabled")); + } + return this.crypto.downloadKeys(userIds, forceDownload); + } + /** + * Get the stored device keys for a user id + * + * @param userId - the user to list keys for. + * + * @returns list of devices + */ + getStoredDevicesForUser(userId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getStoredDevicesForUser(userId) || []; + } + /** + * Get the stored device key for a user id and device id + * + * @param userId - the user to list keys for. + * @param deviceId - unique identifier for the device + * + * @returns device or null + */ + getStoredDevice(userId, deviceId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getStoredDevice(userId, deviceId) || null; + } + /** + * Mark the given device as verified + * + * @param userId - owner of the device + * @param deviceId - unique identifier for the device or user's + * cross-signing public key ID. + * + * @param verified - whether to mark the device as verified. defaults + * to 'true'. + * + * @returns + * + * @remarks + * Fires {@link CryptoEvent#DeviceVerificationChanged} + */ + setDeviceVerified(userId, deviceId, verified = true) { + const prom = this.setDeviceVerification(userId, deviceId, verified, null, null); + // if one of the user's own devices is being marked as verified / unverified, + // check the key backup status, since whether or not we use this depends on + // whether it has a signature from a verified device + if (userId == this.credentials.userId) { + this.checkKeyBackup(); + } + return prom; + } + /** + * Mark the given device as blocked/unblocked + * + * @param userId - owner of the device + * @param deviceId - unique identifier for the device or user's + * cross-signing public key ID. + * + * @param blocked - whether to mark the device as blocked. defaults + * to 'true'. + * + * @returns + * + * @remarks + * Fires {@link CryptoEvent.DeviceVerificationChanged} + */ + setDeviceBlocked(userId, deviceId, blocked = true) { + return this.setDeviceVerification(userId, deviceId, null, blocked, null); + } + /** + * Mark the given device as known/unknown + * + * @param userId - owner of the device + * @param deviceId - unique identifier for the device or user's + * cross-signing public key ID. + * + * @param known - whether to mark the device as known. defaults + * to 'true'. + * + * @returns + * + * @remarks + * Fires {@link CryptoEvent#DeviceVerificationChanged} + */ + setDeviceKnown(userId, deviceId, known = true) { + return this.setDeviceVerification(userId, deviceId, null, null, known); + } + setDeviceVerification(userId, deviceId, verified, blocked, known) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + yield this.crypto.setDeviceVerification(userId, deviceId, verified, blocked, known); + }); + } + /** + * Request a key verification from another user, using a DM. + * + * @param userId - the user to request verification with + * @param roomId - the room to use for verification + * + * @returns resolves to a VerificationRequest + * when the request has been sent to the other party. + */ + requestVerificationDM(userId, roomId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.requestVerificationDM(userId, roomId); + } + /** + * Finds a DM verification request that is already in progress for the given room id + * + * @param roomId - the room to use for verification + * + * @returns the VerificationRequest that is in progress, if any + */ + findVerificationRequestDMInProgress(roomId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.findVerificationRequestDMInProgress(roomId); + } + /** + * Returns all to-device verification requests that are already in progress for the given user id + * + * @param userId - the ID of the user to query + * + * @returns the VerificationRequests that are in progress + */ + getVerificationRequestsToDeviceInProgress(userId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getVerificationRequestsToDeviceInProgress(userId); + } + /** + * Request a key verification from another user. + * + * @param userId - the user to request verification with + * @param devices - array of device IDs to send requests to. Defaults to + * all devices owned by the user + * + * @returns resolves to a VerificationRequest + * when the request has been sent to the other party. + */ + requestVerification(userId, devices) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.requestVerification(userId, devices); + } + /** + * Begin a key verification. + * + * @param method - the verification method to use + * @param userId - the user to verify keys with + * @param deviceId - the device to verify + * + * @returns a verification object + * @deprecated Use `requestVerification` instead. + */ + beginKeyVerification(method, userId, deviceId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.beginKeyVerification(method, userId, deviceId); + } + checkSecretStorageKey(key, info) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.checkSecretStorageKey(key, info); + } + /** + * Set the global override for whether the client should ever send encrypted + * messages to unverified devices. This provides the default for rooms which + * do not specify a value. + * + * @param value - whether to blacklist all unverified devices by default + * + * @deprecated Prefer direct access to {@link CryptoApi.globalBlacklistUnverifiedDevices}: + * + * ```javascript + * client.getCrypto().globalBlacklistUnverifiedDevices = value; + * ``` + */ + setGlobalBlacklistUnverifiedDevices(value) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + this.cryptoBackend.globalBlacklistUnverifiedDevices = value; + return value; + } + /** + * @returns whether to blacklist all unverified devices by default + * + * @deprecated Prefer direct access to {@link CryptoApi.globalBlacklistUnverifiedDevices}: + * + * ```javascript + * value = client.getCrypto().globalBlacklistUnverifiedDevices; + * ``` + */ + getGlobalBlacklistUnverifiedDevices() { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.globalBlacklistUnverifiedDevices; + } + /** + * Set whether sendMessage in a room with unknown and unverified devices + * should throw an error and not send them message. This has 'Global' for + * symmetry with setGlobalBlacklistUnverifiedDevices but there is currently + * no room-level equivalent for this setting. + * + * This API is currently UNSTABLE and may change or be removed without notice. + * + * @param value - whether error on unknown devices + * + * @deprecated Prefer direct access to {@link CryptoApi.globalBlacklistUnverifiedDevices}: + * + * ```ts + * client.getCrypto().globalBlacklistUnverifiedDevices = value; + * ``` + */ + setGlobalErrorOnUnknownDevices(value) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + this.cryptoBackend.globalErrorOnUnknownDevices = value; + } + /** + * @returns whether to error on unknown devices + * + * This API is currently UNSTABLE and may change or be removed without notice. + */ + getGlobalErrorOnUnknownDevices() { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.globalErrorOnUnknownDevices; + } + /** + * Get the user's cross-signing key ID. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param type - The type of key to get the ID of. One of + * "master", "self_signing", or "user_signing". Defaults to "master". + * + * @returns the key ID + */ + getCrossSigningId(type = api_1.CrossSigningKey.Master) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getCrossSigningId(type); + } + /** + * Get the cross signing information for a given user. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param userId - the user ID to get the cross-signing info for. + * + * @returns the cross signing information for the user. + */ + getStoredCrossSigningForUser(userId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getStoredCrossSigningForUser(userId); + } + /** + * Check whether a given user is trusted. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param userId - The ID of the user to check. + */ + checkUserTrust(userId) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.checkUserTrust(userId); + } + /** + * Check whether a given device is trusted. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param userId - The ID of the user whose devices is to be checked. + * @param deviceId - The ID of the device to check + */ + checkDeviceTrust(userId, deviceId) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.checkDeviceTrust(userId, deviceId); + } + /** + * Check whether one of our own devices is cross-signed by our + * user's stored keys, regardless of whether we trust those keys yet. + * + * @param deviceId - The ID of the device to check + * + * @returns true if the device is cross-signed + */ + checkIfOwnDeviceCrossSigned(deviceId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.checkIfOwnDeviceCrossSigned(deviceId); + } + /** + * Check the copy of our cross-signing key that we have in the device list and + * see if we can get the private key. If so, mark it as trusted. + * @param opts - ICheckOwnCrossSigningTrustOpts object + */ + checkOwnCrossSigningTrust(opts) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.checkOwnCrossSigningTrust(opts); + } + /** + * Checks that a given cross-signing private key matches a given public key. + * This can be used by the getCrossSigningKey callback to verify that the + * private key it is about to supply is the one that was requested. + * @param privateKey - The private key + * @param expectedPublicKey - The public key + * @returns true if the key matches, otherwise false + */ + checkCrossSigningPrivateKey(privateKey, expectedPublicKey) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.checkCrossSigningPrivateKey(privateKey, expectedPublicKey); + } + // deprecated: use requestVerification instead + legacyDeviceVerification(userId, deviceId, method) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.legacyDeviceVerification(userId, deviceId, method); + } + /** + * Perform any background tasks that can be done before a message is ready to + * send, in order to speed up sending of the message. + * @param room - the room the event is in + * + * @deprecated Prefer {@link CryptoApi.prepareToEncrypt | `CryptoApi.prepareToEncrypt`}: + * + * ```javascript + * client.getCrypto().prepareToEncrypt(room); + * ``` + */ + prepareToEncrypt(room) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + this.cryptoBackend.prepareToEncrypt(room); + } + /** + * Checks if the user has previously published cross-signing keys + * + * This means downloading the devicelist for the user and checking if the list includes + * the cross-signing pseudo-device. + * + * @deprecated Prefer {@link CryptoApi.userHasCrossSigningKeys | `CryptoApi.userHasCrossSigningKeys`}: + * + * ```javascript + * result = client.getCrypto().userHasCrossSigningKeys(); + * ``` + */ + userHasCrossSigningKeys() { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.userHasCrossSigningKeys(); + } + /** + * Checks whether cross signing: + * - is enabled on this account and trusted by this device + * - has private keys either cached locally or stored in secret storage + * + * If this function returns false, bootstrapCrossSigning() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapCrossSigning() completes successfully, this function should + * return true. + * @returns True if cross-signing is ready to be used on this device + */ + isCrossSigningReady() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.isCrossSigningReady(); + } + /** + * Bootstrap cross-signing by creating keys if needed. If everything is already + * set up, then no changes are made, so this is safe to run to ensure + * cross-signing is ready for use. + * + * This function: + * - creates new cross-signing keys if they are not found locally cached nor in + * secret storage (if it has been setup) + * + * The cross-signing API is currently UNSTABLE and may change without notice. + */ + bootstrapCrossSigning(opts) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.bootstrapCrossSigning(opts); + } + /** + * Whether to trust a others users signatures of their devices. + * If false, devices will only be considered 'verified' if we have + * verified that device individually (effectively disabling cross-signing). + * + * Default: true + * + * @returns True if trusting cross-signed devices + */ + getCryptoTrustCrossSignedDevices() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getCryptoTrustCrossSignedDevices(); + } + /** + * See getCryptoTrustCrossSignedDevices + * + * @param val - True to trust cross-signed devices + */ + setCryptoTrustCrossSignedDevices(val) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + this.crypto.setCryptoTrustCrossSignedDevices(val); + } + /** + * Counts the number of end to end session keys that are waiting to be backed up + * @returns Promise which resolves to the number of sessions requiring backup + */ + countSessionsNeedingBackup() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.countSessionsNeedingBackup(); + } + /** + * Get information about the encryption of an event + * + * @param event - event to be checked + * @returns The event information. + */ + getEventEncryptionInfo(event) { + if (!this.cryptoBackend) { + throw new Error("End-to-end encryption disabled"); + } + return this.cryptoBackend.getEventEncryptionInfo(event); + } + /** + * Create a recovery key from a user-supplied passphrase. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param password - Passphrase string that can be entered by the user + * when restoring the backup as an alternative to entering the recovery key. + * Optional. + * @returns Object with public key metadata, encoded private + * recovery key which should be disposed of after displaying to the user, + * and raw private key to avoid round tripping if needed. + */ + createRecoveryKeyFromPassphrase(password) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.createRecoveryKeyFromPassphrase(password); + } + /** + * Checks whether secret storage: + * - is enabled on this account + * - is storing cross-signing private keys + * - is storing session backup key (if enabled) + * + * If this function returns false, bootstrapSecretStorage() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapSecretStorage() completes successfully, this function should + * return true. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @returns True if secret storage is ready to be used on this device + */ + isSecretStorageReady() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.isSecretStorageReady(); + } + /** + * Bootstrap Secure Secret Storage if needed by creating a default key. If everything is + * already set up, then no changes are made, so this is safe to run to ensure secret + * storage is ready for use. + * + * This function + * - creates a new Secure Secret Storage key if no default key exists + * - if a key backup exists, it is migrated to store the key in the Secret + * Storage + * - creates a backup if none exists, and one is requested + * - migrates Secure Secret Storage to use the latest algorithm, if an outdated + * algorithm is found + * + */ + bootstrapSecretStorage(opts) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.bootstrapSecretStorage(opts); + } + /** + * Add a key for encrypting secrets. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param algorithm - the algorithm used by the key + * @param opts - the options for the algorithm. The properties used + * depend on the algorithm given. + * @param keyName - the name of the key. If not given, a random name will be generated. + * + * @returns An object with: + * keyId: the ID of the key + * keyInfo: details about the key (iv, mac, passphrase) + */ + addSecretStorageKey(algorithm, opts, keyName) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.addSecretStorageKey(algorithm, opts, keyName); + } + /** + * Check whether we have a key with a given ID. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param keyId - The ID of the key to check + * for. Defaults to the default key ID if not provided. + * @returns Whether we have the key. + */ + hasSecretStorageKey(keyId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.hasSecretStorageKey(keyId); + } + /** + * Store an encrypted secret on the server. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param name - The name of the secret + * @param secret - The secret contents. + * @param keys - The IDs of the keys to use to encrypt the secret or null/undefined + * to use the default (will throw if no default key is set). + */ + storeSecret(name, secret, keys) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.storeSecret(name, secret, keys); + } + /** + * Get a secret from storage. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param name - the name of the secret + * + * @returns the contents of the secret + */ + getSecret(name) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getSecret(name); + } + /** + * Check if a secret is stored on the server. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param name - the name of the secret + * @returns map of key name to key info the secret is encrypted + * with, or null if it is not present or not encrypted with a trusted + * key + */ + isSecretStored(name) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.isSecretStored(name); + } + /** + * Request a secret from another device. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param name - the name of the secret to request + * @param devices - the devices to request the secret from + * + * @returns the secret request object + */ + requestSecret(name, devices) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.requestSecret(name, devices); + } + /** + * Get the current default key ID for encrypting secrets. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @returns The default key ID or null if no default key ID is set + */ + getDefaultSecretStorageKeyId() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.getDefaultSecretStorageKeyId(); + } + /** + * Set the current default key ID for encrypting secrets. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param keyId - The new default key ID + */ + setDefaultSecretStorageKeyId(keyId) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.setDefaultSecretStorageKeyId(keyId); + } + /** + * Checks that a given secret storage private key matches a given public key. + * This can be used by the getSecretStorageKey callback to verify that the + * private key it is about to supply is the one that was requested. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param privateKey - The private key + * @param expectedPublicKey - The public key + * @returns true if the key matches, otherwise false + */ + checkSecretStoragePrivateKey(privateKey, expectedPublicKey) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.checkSecretStoragePrivateKey(privateKey, expectedPublicKey); + } + /** + * Get e2e information on the device that sent an event + * + * @param event - event to be checked + */ + getEventSenderDeviceInfo(event) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + return null; + } + return this.crypto.getEventSenderDeviceInfo(event); + }); + } + /** + * Check if the sender of an event is verified + * + * @param event - event to be checked + * + * @returns true if the sender of this event has been verified using + * {@link MatrixClient#setDeviceVerified}. + */ + isEventSenderVerified(event) { + return __awaiter(this, void 0, void 0, function* () { + const device = yield this.getEventSenderDeviceInfo(event); + if (!device) { + return false; + } + return device.isVerified(); + }); + } + /** + * Get outgoing room key request for this event if there is one. + * @param event - The event to check for + * + * @returns A room key request, or null if there is none + */ + getOutgoingRoomKeyRequest(event) { + if (!this.crypto) { + throw new Error("End-to-End encryption disabled"); + } + const wireContent = event.getWireContent(); + const requestBody = { + session_id: wireContent.session_id, + sender_key: wireContent.sender_key, + algorithm: wireContent.algorithm, + room_id: event.getRoomId(), + }; + if (!requestBody.session_id || !requestBody.sender_key || !requestBody.algorithm || !requestBody.room_id) { + return Promise.resolve(null); + } + return this.crypto.cryptoStore.getOutgoingRoomKeyRequest(requestBody); + } + /** + * Cancel a room key request for this event if one is ongoing and resend the + * request. + * @param event - event of which to cancel and resend the room + * key request. + * @returns A promise that will resolve when the key request is queued + */ + cancelAndResendEventRoomKeyRequest(event) { + if (!this.crypto) { + throw new Error("End-to-End encryption disabled"); + } + return event.cancelAndResendKeyRequest(this.crypto, this.getUserId()); + } + /** + * Enable end-to-end encryption for a room. This does not modify room state. + * Any messages sent before the returned promise resolves will be sent unencrypted. + * @param roomId - The room ID to enable encryption in. + * @param config - The encryption config for the room. + * @returns A promise that will resolve when encryption is set up. + */ + setRoomEncryption(roomId, config) { + if (!this.crypto) { + throw new Error("End-to-End encryption disabled"); + } + return this.crypto.setRoomEncryption(roomId, config); + } + /** + * Whether encryption is enabled for a room. + * @param roomId - the room id to query. + * @returns whether encryption is enabled. + */ + isRoomEncrypted(roomId) { + const room = this.getRoom(roomId); + if (!room) { + // we don't know about this room, so can't determine if it should be + // encrypted. Let's assume not. + return false; + } + // if there is an 'm.room.encryption' event in this room, it should be + // encrypted (independently of whether we actually support encryption) + const ev = room.currentState.getStateEvents(event_2.EventType.RoomEncryption, ""); + if (ev) { + return true; + } + // we don't have an m.room.encrypted event, but that might be because + // the server is hiding it from us. Check the store to see if it was + // previously encrypted. + return this.roomList.isRoomEncrypted(roomId); + } + /** + * Encrypts and sends a given object via Olm to-device messages to a given + * set of devices. + * + * @param userDeviceMap - mapping from userId to deviceInfo + * + * @param payload - fields to include in the encrypted payload + * + * @returns Promise which + * resolves once the message has been encrypted and sent to the given + * userDeviceMap, and returns the `{ contentMap, deviceInfoByDeviceId }` + * of the successfully sent messages. + */ + encryptAndSendToDevices(userDeviceInfoArr, payload) { + if (!this.crypto) { + throw new Error("End-to-End encryption disabled"); + } + return this.crypto.encryptAndSendToDevices(userDeviceInfoArr, payload); + } + /** + * Forces the current outbound group session to be discarded such + * that another one will be created next time an event is sent. + * + * @param roomId - The ID of the room to discard the session for + * + * @deprecated Prefer {@link CryptoApi.forceDiscardSession | `CryptoApi.forceDiscardSession`}: + * + */ + forceDiscardSession(roomId) { + if (!this.cryptoBackend) { + throw new Error("End-to-End encryption disabled"); + } + this.cryptoBackend.forceDiscardSession(roomId); + } + /** + * Get a list containing all of the room keys + * + * This should be encrypted before returning it to the user. + * + * @returns a promise which resolves to a list of session export objects + * + * @deprecated Prefer {@link CryptoApi.exportRoomKeys | `CryptoApi.exportRoomKeys`}: + * + * ```javascript + * sessionData = await client.getCrypto().exportRoomKeys(); + * ``` + */ + exportRoomKeys() { + if (!this.cryptoBackend) { + return Promise.reject(new Error("End-to-end encryption disabled")); + } + return this.cryptoBackend.exportRoomKeys(); + } + /** + * Import a list of room keys previously exported by exportRoomKeys + * + * @param keys - a list of session export objects + * + * @returns a promise which resolves when the keys have been imported + */ + importRoomKeys(keys, opts) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.importRoomKeys(keys, opts); + } + /** + * Force a re-check of the local key backup status against + * what's on the server. + * + * @returns Object with backup info (as returned by + * getKeyBackupVersion) in backupInfo and + * trust information (as returned by isKeyBackupTrusted) + * in trustInfo. + */ + checkKeyBackup() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.backupManager.checkKeyBackup(); + } + /** + * Get information about the current key backup. + * @returns Information object from API or null + */ + getKeyBackupVersion() { + return __awaiter(this, void 0, void 0, function* () { + let res; + try { + res = yield this.http.authedRequest(http_api_1.Method.Get, "/room_keys/version", undefined, undefined, { prefix: http_api_1.ClientPrefix.V3 }); + } + catch (e) { + if (e.errcode === "M_NOT_FOUND") { + return null; + } + else { + throw e; + } + } + backup_1.BackupManager.checkBackupVersion(res); + return res; + }); + } + /** + * @param info - key backup info dict from getKeyBackupVersion() + */ + isKeyBackupTrusted(info) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.backupManager.isKeyBackupTrusted(info); + } + /** + * @returns true if the client is configured to back up keys to + * the server, otherwise false. If we haven't completed a successful check + * of key backup status yet, returns null. + */ + getKeyBackupEnabled() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.backupManager.getKeyBackupEnabled(); + } + /** + * Enable backing up of keys, using data previously returned from + * getKeyBackupVersion. + * + * @param info - Backup information object as returned by getKeyBackupVersion + * @returns Promise which resolves when complete. + */ + enableKeyBackup(info) { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.backupManager.enableKeyBackup(info); + } + /** + * Disable backing up of keys. + */ + disableKeyBackup() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + this.crypto.backupManager.disableKeyBackup(); + } + /** + * Set up the data required to create a new backup version. The backup version + * will not be created and enabled until createKeyBackupVersion is called. + * + * @param password - Passphrase string that can be entered by the user + * when restoring the backup as an alternative to entering the recovery key. + * Optional. + * + * @returns Object that can be passed to createKeyBackupVersion and + * additionally has a 'recovery_key' member with the user-facing recovery key string. + */ + prepareKeyBackupVersion(password, opts = { secureSecretStorage: false }) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + // eslint-disable-next-line camelcase + const { algorithm, auth_data, recovery_key, privateKey } = yield this.crypto.backupManager.prepareKeyBackupVersion(password); + if (opts.secureSecretStorage) { + yield this.storeSecret("m.megolm_backup.v1", (0, olmlib_1.encodeBase64)(privateKey)); + logger_1.logger.info("Key backup private key stored in secret storage"); + } + return { + algorithm, + /* eslint-disable camelcase */ + auth_data, + recovery_key, + /* eslint-enable camelcase */ + }; + }); + } + /** + * Check whether the key backup private key is stored in secret storage. + * @returns map of key name to key info the secret is + * encrypted with, or null if it is not present or not encrypted with a + * trusted key + */ + isKeyBackupKeyStored() { + return Promise.resolve(this.isSecretStored("m.megolm_backup.v1")); + } + /** + * Create a new key backup version and enable it, using the information return + * from prepareKeyBackupVersion. + * + * @param info - Info object from prepareKeyBackupVersion + * @returns Object with 'version' param indicating the version created + */ + createKeyBackupVersion(info) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + yield this.crypto.backupManager.createKeyBackupVersion(info); + const data = { + algorithm: info.algorithm, + auth_data: info.auth_data, + }; + // Sign the backup auth data with the device key for backwards compat with + // older devices with cross-signing. This can probably go away very soon in + // favour of just signing with the cross-singing master key. + // XXX: Private member access + yield this.crypto.signObject(data.auth_data); + if (this.cryptoCallbacks.getCrossSigningKey && + // XXX: Private member access + this.crypto.crossSigningInfo.getId()) { + // now also sign the auth data with the cross-signing master key + // we check for the callback explicitly here because we still want to be able + // to create an un-cross-signed key backup if there is a cross-signing key but + // no callback supplied. + // XXX: Private member access + yield this.crypto.crossSigningInfo.signObject(data.auth_data, "master"); + } + const res = yield this.http.authedRequest(http_api_1.Method.Post, "/room_keys/version", undefined, data, { + prefix: http_api_1.ClientPrefix.V3, + }); + // We could assume everything's okay and enable directly, but this ensures + // we run the same signature verification that will be used for future + // sessions. + yield this.checkKeyBackup(); + if (!this.getKeyBackupEnabled()) { + logger_1.logger.error("Key backup not usable even though we just created it"); + } + return res; + }); + } + deleteKeyBackupVersion(version) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + // If we're currently backing up to this backup... stop. + // (We start using it automatically in createKeyBackupVersion + // so this is symmetrical). + if (this.crypto.backupManager.version) { + this.crypto.backupManager.disableKeyBackup(); + } + const path = utils.encodeUri("/room_keys/version/$version", { + $version: version, + }); + yield this.http.authedRequest(http_api_1.Method.Delete, path, undefined, undefined, { prefix: http_api_1.ClientPrefix.V3 }); + }); + } + makeKeyBackupPath(roomId, sessionId, version) { + let path; + if (sessionId !== undefined) { + path = utils.encodeUri("/room_keys/keys/$roomId/$sessionId", { + $roomId: roomId, + $sessionId: sessionId, + }); + } + else if (roomId !== undefined) { + path = utils.encodeUri("/room_keys/keys/$roomId", { + $roomId: roomId, + }); + } + else { + path = "/room_keys/keys"; + } + const queryData = version === undefined ? undefined : { version }; + return { path, queryData }; + } + sendKeyBackup(roomId, sessionId, version, data) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + const path = this.makeKeyBackupPath(roomId, sessionId, version); + yield this.http.authedRequest(http_api_1.Method.Put, path.path, path.queryData, data, { prefix: http_api_1.ClientPrefix.V3 }); + }); + } + /** + * Marks all group sessions as needing to be backed up and schedules them to + * upload in the background as soon as possible. + */ + scheduleAllGroupSessionsForBackup() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + yield this.crypto.backupManager.scheduleAllGroupSessionsForBackup(); + }); + } + /** + * Marks all group sessions as needing to be backed up without scheduling + * them to upload in the background. + * @returns Promise which resolves to the number of sessions requiring a backup. + */ + flagAllGroupSessionsForBackup() { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + return this.crypto.backupManager.flagAllGroupSessionsForBackup(); + } + isValidRecoveryKey(recoveryKey) { + try { + (0, recoverykey_1.decodeRecoveryKey)(recoveryKey); + return true; + } + catch (e) { + return false; + } + } + /** + * Get the raw key for a key backup from the password + * Used when migrating key backups into SSSS + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param password - Passphrase + * @param backupInfo - Backup metadata from `checkKeyBackup` + * @returns key backup key + */ + keyBackupKeyFromPassword(password, backupInfo) { + return (0, key_passphrase_1.keyFromAuthData)(backupInfo.auth_data, password); + } + /** + * Get the raw key for a key backup from the recovery key + * Used when migrating key backups into SSSS + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param recoveryKey - The recovery key + * @returns key backup key + */ + keyBackupKeyFromRecoveryKey(recoveryKey) { + return (0, recoverykey_1.decodeRecoveryKey)(recoveryKey); + } + restoreKeyBackupWithPassword(password, targetRoomId, targetSessionId, backupInfo, opts) { + return __awaiter(this, void 0, void 0, function* () { + const privKey = yield (0, key_passphrase_1.keyFromAuthData)(backupInfo.auth_data, password); + return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + }); + } + /** + * Restore from an existing key backup via a private key stored in secret + * storage. + * + * @param backupInfo - Backup metadata from `checkKeyBackup` + * @param targetRoomId - Room ID to target a specific room. + * Restores all rooms if omitted. + * @param targetSessionId - Session ID to target a specific session. + * Restores all sessions if omitted. + * @param opts - Optional params such as callbacks + * @returns Status of restoration with `total` and `imported` + * key counts. + */ + restoreKeyBackupWithSecretStorage(backupInfo, targetRoomId, targetSessionId, opts) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + const storedKey = yield this.getSecret("m.megolm_backup.v1"); + // ensure that the key is in the right format. If not, fix the key and + // store the fixed version + const fixedKey = (0, crypto_1.fixBackupKey)(storedKey); + if (fixedKey) { + const keys = yield this.crypto.getSecretStorageKey(); + yield this.storeSecret("m.megolm_backup.v1", fixedKey, [keys[0]]); + } + const privKey = (0, olmlib_1.decodeBase64)(fixedKey || storedKey); + return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + }); + } + restoreKeyBackupWithRecoveryKey(recoveryKey, targetRoomId, targetSessionId, backupInfo, opts) { + const privKey = (0, recoverykey_1.decodeRecoveryKey)(recoveryKey); + return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + } + restoreKeyBackupWithCache(targetRoomId, targetSessionId, backupInfo, opts) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + const privKey = yield this.crypto.getSessionBackupPrivateKey(); + if (!privKey) { + throw new Error("Couldn't get key"); + } + return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + }); + } + restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts) { + return __awaiter(this, void 0, void 0, function* () { + const cacheCompleteCallback = opts === null || opts === void 0 ? void 0 : opts.cacheCompleteCallback; + const progressCallback = opts === null || opts === void 0 ? void 0 : opts.progressCallback; + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + let totalKeyCount = 0; + let keys = []; + const path = this.makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version); + const algorithm = yield backup_1.BackupManager.makeAlgorithm(backupInfo, () => __awaiter(this, void 0, void 0, function* () { + return privKey; + })); + const untrusted = algorithm.untrusted; + try { + // If the pubkey computed from the private data we've been given + // doesn't match the one in the auth_data, the user has entered + // a different recovery key / the wrong passphrase. + if (!(yield algorithm.keyMatches(privKey))) { + return Promise.reject(new http_api_1.MatrixError({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY })); + } + // Cache the key, if possible. + // This is async. + this.crypto + .storeSessionBackupPrivateKey(privKey) + .catch((e) => { + logger_1.logger.warn("Error caching session backup key:", e); + }) + .then(cacheCompleteCallback); + if (progressCallback) { + progressCallback({ + stage: "fetch", + }); + } + const res = yield this.http.authedRequest(http_api_1.Method.Get, path.path, path.queryData, undefined, { prefix: http_api_1.ClientPrefix.V3 }); + if (res.rooms) { + const rooms = res.rooms; + for (const [roomId, roomData] of Object.entries(rooms)) { + if (!roomData.sessions) + continue; + totalKeyCount += Object.keys(roomData.sessions).length; + const roomKeys = yield algorithm.decryptSessions(roomData.sessions); + for (const k of roomKeys) { + k.room_id = roomId; + keys.push(k); + } + } + } + else if (res.sessions) { + const sessions = res.sessions; + totalKeyCount = Object.keys(sessions).length; + keys = yield algorithm.decryptSessions(sessions); + for (const k of keys) { + k.room_id = targetRoomId; + } + } + else { + totalKeyCount = 1; + try { + const [key] = yield algorithm.decryptSessions({ + [targetSessionId]: res, + }); + key.room_id = targetRoomId; + key.session_id = targetSessionId; + keys.push(key); + } + catch (e) { + logger_1.logger.log("Failed to decrypt megolm session from backup", e); + } + } + } + finally { + algorithm.free(); + } + yield this.importRoomKeys(keys, { + progressCallback, + untrusted, + source: "backup", + }); + yield this.checkKeyBackup(); + return { total: totalKeyCount, imported: keys.length }; + }); + } + deleteKeysFromBackup(roomId, sessionId, version) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + const path = this.makeKeyBackupPath(roomId, sessionId, version); + yield this.http.authedRequest(http_api_1.Method.Delete, path.path, path.queryData, undefined, { prefix: http_api_1.ClientPrefix.V3 }); + }); + } + /** + * Share shared-history decryption keys with the given users. + * + * @param roomId - the room for which keys should be shared. + * @param userIds - a list of users to share with. The keys will be sent to + * all of the user's current devices. + */ + sendSharedHistoryKeys(roomId, userIds) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + const roomEncryption = this.roomList.getRoomEncryption(roomId); + if (!roomEncryption) { + // unknown room, or unencrypted room + logger_1.logger.error("Unknown room. Not sharing decryption keys"); + return; + } + const deviceInfos = yield this.crypto.downloadKeys(userIds); + const devicesByUser = new Map(); + for (const [userId, devices] of deviceInfos) { + devicesByUser.set(userId, Array.from(devices.values())); + } + // XXX: Private member access + const alg = this.crypto.getRoomDecryptor(roomId, roomEncryption.algorithm); + if (alg.sendSharedHistoryInboundSessions) { + yield alg.sendSharedHistoryInboundSessions(devicesByUser); + } + else { + logger_1.logger.warn("Algorithm does not support sharing previous keys", roomEncryption.algorithm); + } + }); + } + /** + * Get the config for the media repository. + * @returns Promise which resolves with an object containing the config. + */ + getMediaConfig() { + return this.http.authedRequest(http_api_1.Method.Get, "/config", undefined, undefined, { + prefix: http_api_1.MediaPrefix.R0, + }); + } + /** + * Get the room for the given room ID. + * This function will return a valid room for any room for which a Room event + * has been emitted. Note in particular that other events, eg. RoomState.members + * will be emitted for a room before this function will return the given room. + * @param roomId - The room ID + * @returns The Room or null if it doesn't exist or there is no data store. + */ + getRoom(roomId) { + if (!roomId) { + return null; + } + return this.store.getRoom(roomId); + } + /** + * Retrieve all known rooms. + * @returns A list of rooms, or an empty list if there is no data store. + */ + getRooms() { + return this.store.getRooms(); + } + /** + * Retrieve all rooms that should be displayed to the user + * This is essentially getRooms() with some rooms filtered out, eg. old versions + * of rooms that have been replaced or (in future) other rooms that have been + * marked at the protocol level as not to be displayed to the user. + * + * @param msc3946ProcessDynamicPredecessor - if true, look for an + * m.room.predecessor state event and + * use it if found (MSC3946). + * @returns A list of rooms, or an empty list if there is no data store. + */ + getVisibleRooms(msc3946ProcessDynamicPredecessor = false) { + var _a; + const allRooms = this.store.getRooms(); + const replacedRooms = new Set(); + for (const r of allRooms) { + const predecessor = (_a = r.findPredecessor(msc3946ProcessDynamicPredecessor)) === null || _a === void 0 ? void 0 : _a.roomId; + if (predecessor) { + replacedRooms.add(predecessor); + } + } + return allRooms.filter((r) => { + const tombstone = r.currentState.getStateEvents(event_2.EventType.RoomTombstone, ""); + if (tombstone && replacedRooms.has(r.roomId)) { + return false; + } + return true; + }); + } + /** + * Retrieve a user. + * @param userId - The user ID to retrieve. + * @returns A user or null if there is no data store or the user does + * not exist. + */ + getUser(userId) { + return this.store.getUser(userId); + } + /** + * Retrieve all known users. + * @returns A list of users, or an empty list if there is no data store. + */ + getUsers() { + return this.store.getUsers(); + } + /** + * Set account data event for the current user. + * It will retry the request up to 5 times. + * @param eventType - The event type + * @param content - the contents object for the event + * @returns Promise which resolves: an empty object + * @returns Rejects: with an error response. + */ + setAccountData(eventType, content) { + const path = utils.encodeUri("/user/$userId/account_data/$type", { + $userId: this.credentials.userId, + $type: eventType, + }); + return (0, http_api_1.retryNetworkOperation)(5, () => { + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, content); + }); + } + /** + * Get account data event of given type for the current user. + * @param eventType - The event type + * @returns The contents of the given account data event + */ + getAccountData(eventType) { + return this.store.getAccountData(eventType); + } + /** + * Get account data event of given type for the current user. This variant + * gets account data directly from the homeserver if the local store is not + * ready, which can be useful very early in startup before the initial sync. + * @param eventType - The event type + * @returns Promise which resolves: The contents of the given account data event. + * @returns Rejects: with an error response. + */ + getAccountDataFromServer(eventType) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (this.isInitialSyncComplete()) { + const event = this.store.getAccountData(eventType); + if (!event) { + return null; + } + // The network version below returns just the content, so this branch + // does the same to match. + return event.getContent(); + } + const path = utils.encodeUri("/user/$userId/account_data/$type", { + $userId: this.credentials.userId, + $type: eventType, + }); + try { + return yield this.http.authedRequest(http_api_1.Method.Get, path); + } + catch (e) { + if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.errcode) === "M_NOT_FOUND") { + return null; + } + throw e; + } + }); + } + deleteAccountData(eventType) { + return __awaiter(this, void 0, void 0, function* () { + const msc3391DeleteAccountDataServerSupport = this.canSupport.get(feature_1.Feature.AccountDataDeletion); + // if deletion is not supported overwrite with empty content + if (msc3391DeleteAccountDataServerSupport === feature_1.ServerSupport.Unsupported) { + yield this.setAccountData(eventType, {}); + return; + } + const path = utils.encodeUri("/user/$userId/account_data/$type", { + $userId: this.getSafeUserId(), + $type: eventType, + }); + const options = msc3391DeleteAccountDataServerSupport === feature_1.ServerSupport.Unstable + ? { prefix: "/_matrix/client/unstable/org.matrix.msc3391" } + : undefined; + return yield this.http.authedRequest(http_api_1.Method.Delete, path, undefined, undefined, options); + }); + } + /** + * Gets the users that are ignored by this client + * @returns The array of users that are ignored (empty if none) + */ + getIgnoredUsers() { + const event = this.getAccountData("m.ignored_user_list"); + if (!event || !event.getContent() || !event.getContent()["ignored_users"]) + return []; + return Object.keys(event.getContent()["ignored_users"]); + } + /** + * Sets the users that the current user should ignore. + * @param userIds - the user IDs to ignore + * @returns Promise which resolves: an empty object + * @returns Rejects: with an error response. + */ + setIgnoredUsers(userIds) { + const content = { ignored_users: {} }; + userIds.forEach((u) => { + content.ignored_users[u] = {}; + }); + return this.setAccountData("m.ignored_user_list", content); + } + /** + * Gets whether or not a specific user is being ignored by this client. + * @param userId - the user ID to check + * @returns true if the user is ignored, false otherwise + */ + isUserIgnored(userId) { + return this.getIgnoredUsers().includes(userId); + } + /** + * Join a room. If you have already joined the room, this will no-op. + * @param roomIdOrAlias - The room ID or room alias to join. + * @param opts - Options when joining the room. + * @returns Promise which resolves: Room object. + * @returns Rejects: with an error response. + */ + joinRoom(roomIdOrAlias, opts = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (opts.syncRoom === undefined) { + opts.syncRoom = true; + } + const room = this.getRoom(roomIdOrAlias); + if (room === null || room === void 0 ? void 0 : room.hasMembershipState(this.credentials.userId, "join")) { + return Promise.resolve(room); + } + let signPromise = Promise.resolve(); + if (opts.inviteSignUrl) { + const url = new URL(opts.inviteSignUrl); + url.searchParams.set("mxid", this.credentials.userId); + signPromise = this.http.requestOtherUrl(http_api_1.Method.Post, url); + } + const queryString = {}; + if (opts.viaServers) { + queryString["server_name"] = opts.viaServers; + } + try { + const data = {}; + const signedInviteObj = yield signPromise; + if (signedInviteObj) { + data.third_party_signed = signedInviteObj; + } + const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias }); + const res = yield this.http.authedRequest(http_api_1.Method.Post, path, queryString, data); + const roomId = res.room_id; + const syncApi = new sync_1.SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); + const room = syncApi.createRoom(roomId); + if (opts.syncRoom) { + // v2 will do this for us + // return syncApi.syncRoom(room); + } + return room; + } + catch (e) { + throw e; // rethrow for reject + } + }); + } + /** + * Resend an event. Will also retry any to-device messages waiting to be sent. + * @param event - The event to resend. + * @param room - Optional. The room the event is in. Will update the + * timeline entry if provided. + * @returns Promise which resolves: to an ISendEventResponse object + * @returns Rejects: with an error response. + */ + resendEvent(event, room) { + // also kick the to-device queue to retry + this.toDeviceMessageQueue.sendQueue(); + this.updatePendingEventStatus(room, event, event_1.EventStatus.SENDING); + return this.encryptAndSendEvent(room, event); + } + /** + * Cancel a queued or unsent event. + * + * @param event - Event to cancel + * @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state + */ + cancelPendingEvent(event) { + if (![event_1.EventStatus.QUEUED, event_1.EventStatus.NOT_SENT, event_1.EventStatus.ENCRYPTING].includes(event.status)) { + throw new Error("cannot cancel an event with status " + event.status); + } + // if the event is currently being encrypted then + if (event.status === event_1.EventStatus.ENCRYPTING) { + this.pendingEventEncryption.delete(event.getId()); + } + else if (this.scheduler && event.status === event_1.EventStatus.QUEUED) { + // tell the scheduler to forget about it, if it's queued + this.scheduler.removeEventFromQueue(event); + } + // then tell the room about the change of state, which will remove it + // from the room's list of pending events. + const room = this.getRoom(event.getRoomId()); + this.updatePendingEventStatus(room, event, event_1.EventStatus.CANCELLED); + } + /** + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + setRoomName(roomId, name) { + return this.sendStateEvent(roomId, event_2.EventType.RoomName, { name: name }); + } + /** + * @param htmlTopic - Optional. + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + setRoomTopic(roomId, topic, htmlTopic) { + const content = ContentHelpers.makeTopicContent(topic, htmlTopic); + return this.sendStateEvent(roomId, event_2.EventType.RoomTopic, content); + } + /** + * @returns Promise which resolves: to an object keyed by tagId with objects containing a numeric order field. + * @returns Rejects: with an error response. + */ + getRoomTags(roomId) { + const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags", { + $userId: this.credentials.userId, + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @param tagName - name of room tag to be set + * @param metadata - associated with that tag to be stored + * @returns Promise which resolves: to an empty object + * @returns Rejects: with an error response. + */ + setRoomTag(roomId, tagName, metadata) { + const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", { + $userId: this.credentials.userId, + $roomId: roomId, + $tag: tagName, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, metadata); + } + /** + * @param tagName - name of room tag to be removed + * @returns Promise which resolves: to an empty object + * @returns Rejects: with an error response. + */ + deleteRoomTag(roomId, tagName) { + const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", { + $userId: this.credentials.userId, + $roomId: roomId, + $tag: tagName, + }); + return this.http.authedRequest(http_api_1.Method.Delete, path); + } + /** + * @param eventType - event type to be set + * @param content - event content + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + setRoomAccountData(roomId, eventType, content) { + const path = utils.encodeUri("/user/$userId/rooms/$roomId/account_data/$type", { + $userId: this.credentials.userId, + $roomId: roomId, + $type: eventType, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, content); + } + /** + * Set a power level to one or multiple users. + * @returns Promise which resolves: to an ISendEventResponse object + * @returns Rejects: with an error response. + */ + setPowerLevel(roomId, userId, powerLevel, event) { + let content = { + users: {}, + }; + if ((event === null || event === void 0 ? void 0 : event.getType()) === event_2.EventType.RoomPowerLevels) { + // take a copy of the content to ensure we don't corrupt + // existing client state with a failed power level change + content = utils.deepCopy(event.getContent()); + } + const users = Array.isArray(userId) ? userId : [userId]; + for (const user of users) { + if (powerLevel == null) { + delete content.users[user]; + } + else { + content.users[user] = powerLevel; + } + } + const path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", { + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, content); + } + /** + * Create an m.beacon_info event + * @returns + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + unstable_createLiveBeacon(roomId, beaconInfoContent) { + return __awaiter(this, void 0, void 0, function* () { + return this.unstable_setLiveBeacon(roomId, beaconInfoContent); + }); + } + /** + * Upsert a live beacon event + * using a specific m.beacon_info.* event variable type + * @param roomId - string + * @returns + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + unstable_setLiveBeacon(roomId, beaconInfoContent) { + return __awaiter(this, void 0, void 0, function* () { + return this.sendStateEvent(roomId, beacon_1.M_BEACON_INFO.name, beaconInfoContent, this.getUserId()); + }); + } + sendEvent(roomId, threadIdOrEventType, eventTypeOrContent, contentOrTxnId, txnIdOrVoid) { + var _a, _b, _c, _d, _e; + let threadId; + let eventType; + let content; + let txnId; + if (!(threadIdOrEventType === null || threadIdOrEventType === void 0 ? void 0 : threadIdOrEventType.startsWith(EVENT_ID_PREFIX)) && threadIdOrEventType !== null) { + txnId = contentOrTxnId; + content = eventTypeOrContent; + eventType = threadIdOrEventType; + threadId = null; + } + else { + txnId = txnIdOrVoid; + content = contentOrTxnId; + eventType = eventTypeOrContent; + threadId = threadIdOrEventType; + } + // If we expect that an event is part of a thread but is missing the relation + // we need to add it manually, as well as the reply fallback + if (threadId && !((_a = content["m.relates_to"]) === null || _a === void 0 ? void 0 : _a.rel_type)) { + const isReply = !!((_b = content["m.relates_to"]) === null || _b === void 0 ? void 0 : _b["m.in_reply_to"]); + content["m.relates_to"] = Object.assign(Object.assign({}, content["m.relates_to"]), { rel_type: thread_1.THREAD_RELATION_TYPE.name, event_id: threadId, + // Set is_falling_back to true unless this is actually intended to be a reply + is_falling_back: !isReply }); + const thread = (_c = this.getRoom(roomId)) === null || _c === void 0 ? void 0 : _c.getThread(threadId); + if (thread && !isReply) { + content["m.relates_to"]["m.in_reply_to"] = { + event_id: (_e = (_d = thread + .lastReply((ev) => { + return ev.isRelation(thread_1.THREAD_RELATION_TYPE.name) && !ev.status; + })) === null || _d === void 0 ? void 0 : _d.getId()) !== null && _e !== void 0 ? _e : threadId, + }; + } + } + return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId); + } + /** + * @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added. + * @param txnId - Optional. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + sendCompleteEvent(roomId, threadId, eventObject, txnId) { + if (!txnId) { + txnId = this.makeTxnId(); + } + // We always construct a MatrixEvent when sending because the store and scheduler use them. + // We'll extract the params back out if it turns out the client has no scheduler or store. + const localEvent = new event_1.MatrixEvent(Object.assign(eventObject, { + event_id: "~" + roomId + ":" + txnId, + user_id: this.credentials.userId, + sender: this.credentials.userId, + room_id: roomId, + origin_server_ts: new Date().getTime(), + })); + const room = this.getRoom(roomId); + const thread = threadId ? room === null || room === void 0 ? void 0 : room.getThread(threadId) : undefined; + if (thread) { + localEvent.setThread(thread); + } + // set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here + this.reEmitter.reEmit(localEvent, [event_1.MatrixEventEvent.Replaced, event_1.MatrixEventEvent.VisibilityChange]); + room === null || room === void 0 ? void 0 : room.reEmitter.reEmit(localEvent, [event_1.MatrixEventEvent.BeforeRedaction]); + // if this is a relation or redaction of an event + // that hasn't been sent yet (e.g. with a local id starting with a ~) + // then listen for the remote echo of that event so that by the time + // this event does get sent, we have the correct event_id + const targetId = localEvent.getAssociatedId(); + if (targetId === null || targetId === void 0 ? void 0 : targetId.startsWith("~")) { + const target = room === null || room === void 0 ? void 0 : room.getPendingEvents().find((e) => e.getId() === targetId); + target === null || target === void 0 ? void 0 : target.once(event_1.MatrixEventEvent.LocalEventIdReplaced, () => { + localEvent.updateAssociatedId(target.getId()); + }); + } + const type = localEvent.getType(); + logger_1.logger.log(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`); + localEvent.setTxnId(txnId); + localEvent.setStatus(event_1.EventStatus.SENDING); + // add this event immediately to the local store as 'sending'. + room === null || room === void 0 ? void 0 : room.addPendingEvent(localEvent, txnId); + // addPendingEvent can change the state to NOT_SENT if it believes + // that there's other events that have failed. We won't bother to + // try sending the event if the state has changed as such. + if (localEvent.status === event_1.EventStatus.NOT_SENT) { + return Promise.reject(new Error("Event blocked by other events not yet sent")); + } + return this.encryptAndSendEvent(room, localEvent); + } + /** + * encrypts the event if necessary; adds the event to the queue, or sends it; marks the event as sent/unsent + * @returns returns a promise which resolves with the result of the send request + */ + encryptAndSendEvent(room, event) { + let cancelled = false; + // Add an extra Promise.resolve() to turn synchronous exceptions into promise rejections, + // so that we can handle synchronous and asynchronous exceptions with the + // same code path. + return Promise.resolve() + .then(() => { + const encryptionPromise = this.encryptEventIfNeeded(event, room !== null && room !== void 0 ? room : undefined); + if (!encryptionPromise) + return null; // doesn't need encryption + this.pendingEventEncryption.set(event.getId(), encryptionPromise); + this.updatePendingEventStatus(room, event, event_1.EventStatus.ENCRYPTING); + return encryptionPromise.then(() => { + if (!this.pendingEventEncryption.has(event.getId())) { + // cancelled via MatrixClient::cancelPendingEvent + cancelled = true; + return; + } + this.updatePendingEventStatus(room, event, event_1.EventStatus.SENDING); + }); + }) + .then(() => { + if (cancelled) + return {}; + let promise = null; + if (this.scheduler) { + // if this returns a promise then the scheduler has control now and will + // resolve/reject when it is done. Internally, the scheduler will invoke + // processFn which is set to this._sendEventHttpRequest so the same code + // path is executed regardless. + promise = this.scheduler.queueEvent(event); + if (promise && this.scheduler.getQueueForEvent(event).length > 1) { + // event is processed FIFO so if the length is 2 or more we know + // this event is stuck behind an earlier event. + this.updatePendingEventStatus(room, event, event_1.EventStatus.QUEUED); + } + } + if (!promise) { + promise = this.sendEventHttpRequest(event); + if (room) { + promise = promise.then((res) => { + room.updatePendingEvent(event, event_1.EventStatus.SENT, res["event_id"]); + return res; + }); + } + } + return promise; + }) + .catch((err) => { + logger_1.logger.error("Error sending event", err.stack || err); + try { + // set the error on the event before we update the status: + // updating the status emits the event, so the state should be + // consistent at that point. + event.error = err; + this.updatePendingEventStatus(room, event, event_1.EventStatus.NOT_SENT); + } + catch (e) { + logger_1.logger.error("Exception in error handler!", e.stack || err); + } + if (err instanceof http_api_1.MatrixError) { + err.event = event; + } + throw err; + }); + } + encryptEventIfNeeded(event, room) { + if (event.isEncrypted()) { + // this event has already been encrypted; this happens if the + // encryption step succeeded, but the send step failed on the first + // attempt. + return null; + } + if (event.isRedaction()) { + // Redactions do not support encryption in the spec at this time, + // whilst it mostly worked in some clients, it wasn't compliant. + return null; + } + if (!room || !this.isRoomEncrypted(event.getRoomId())) { + return null; + } + if (!this.cryptoBackend && this.usingExternalCrypto) { + // The client has opted to allow sending messages to encrypted + // rooms even if the room is encrypted, and we haven't setup + // crypto. This is useful for users of matrix-org/pantalaimon + return null; + } + if (event.getType() === event_2.EventType.Reaction) { + // For reactions, there is a very little gained by encrypting the entire + // event, as relation data is already kept in the clear. Event + // encryption for a reaction effectively only obscures the event type, + // but the purpose is still obvious from the relation data, so nothing + // is really gained. It also causes quite a few problems, such as: + // * triggers notifications via default push rules + // * prevents server-side bundling for reactions + // The reaction key / content / emoji value does warrant encrypting, but + // this will be handled separately by encrypting just this value. + // See https://github.com/matrix-org/matrix-doc/pull/1849#pullrequestreview-248763642 + return null; + } + if (!this.cryptoBackend) { + throw new Error("This room is configured to use encryption, but your client does not support encryption."); + } + return this.cryptoBackend.encryptEvent(event, room); + } + /** + * Returns the eventType that should be used taking encryption into account + * for a given eventType. + * @param roomId - the room for the events `eventType` relates to + * @param eventType - the event type + * @returns the event type taking encryption into account + */ + getEncryptedIfNeededEventType(roomId, eventType) { + if (eventType === event_2.EventType.Reaction) + return eventType; + return this.isRoomEncrypted(roomId) ? event_2.EventType.RoomMessageEncrypted : eventType; + } + updatePendingEventStatus(room, event, newStatus) { + if (room) { + room.updatePendingEvent(event, newStatus); + } + else { + event.setStatus(newStatus); + } + } + sendEventHttpRequest(event) { + let txnId = event.getTxnId(); + if (!txnId) { + txnId = this.makeTxnId(); + event.setTxnId(txnId); + } + const pathParams = { + $roomId: event.getRoomId(), + $eventType: event.getWireType(), + $stateKey: event.getStateKey(), + $txnId: txnId, + }; + let path; + if (event.isState()) { + let pathTemplate = "/rooms/$roomId/state/$eventType"; + if (event.getStateKey() && event.getStateKey().length > 0) { + pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey"; + } + path = utils.encodeUri(pathTemplate, pathParams); + } + else if (event.isRedaction()) { + const pathTemplate = `/rooms/$roomId/redact/$redactsEventId/$txnId`; + path = utils.encodeUri(pathTemplate, Object.assign({ $redactsEventId: event.event.redacts }, pathParams)); + } + else { + path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams); + } + return this.http + .authedRequest(http_api_1.Method.Put, path, undefined, event.getWireContent()) + .then((res) => { + logger_1.logger.log(`Event sent to ${event.getRoomId()} with event id ${res.event_id}`); + return res; + }); + } + redactEvent(roomId, threadId, eventId, txnId, opts) { + if (!(eventId === null || eventId === void 0 ? void 0 : eventId.startsWith(EVENT_ID_PREFIX))) { + opts = txnId; + txnId = eventId; + eventId = threadId; + threadId = null; + } + const reason = opts === null || opts === void 0 ? void 0 : opts.reason; + if ((opts === null || opts === void 0 ? void 0 : opts.with_relations) && + this.canSupport.get(feature_1.Feature.RelationBasedRedactions) === feature_1.ServerSupport.Unsupported) { + throw new Error("Server does not support relation based redactions " + + `roomId ${roomId} eventId ${eventId} txnId: ${txnId} threadId ${threadId}`); + } + const withRelations = (opts === null || opts === void 0 ? void 0 : opts.with_relations) + ? { + [this.canSupport.get(feature_1.Feature.RelationBasedRedactions) === feature_1.ServerSupport.Stable + ? event_2.MSC3912_RELATION_BASED_REDACTIONS_PROP.stable + : event_2.MSC3912_RELATION_BASED_REDACTIONS_PROP.unstable]: opts === null || opts === void 0 ? void 0 : opts.with_relations, + } + : {}; + return this.sendCompleteEvent(roomId, threadId, { + type: event_2.EventType.RoomRedaction, + content: Object.assign(Object.assign({}, withRelations), { reason }), + redacts: eventId, + }, txnId); + } + sendMessage(roomId, threadId, content, txnId) { + if (typeof threadId !== "string" && threadId !== null) { + txnId = content; + content = threadId; + threadId = null; + } + const eventType = event_2.EventType.RoomMessage; + const sendContent = content; + return this.sendEvent(roomId, threadId, eventType, sendContent, txnId); + } + sendTextMessage(roomId, threadId, body, txnId) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + txnId = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeTextMessage(body); + return this.sendMessage(roomId, threadId, content, txnId); + } + sendNotice(roomId, threadId, body, txnId) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + txnId = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeNotice(body); + return this.sendMessage(roomId, threadId, content, txnId); + } + sendEmoteMessage(roomId, threadId, body, txnId) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + txnId = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeEmoteMessage(body); + return this.sendMessage(roomId, threadId, content, txnId); + } + sendImageMessage(roomId, threadId, url, info, text = "Image") { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + text = info || "Image"; + info = url; + url = threadId; + threadId = null; + } + const content = { + msgtype: event_2.MsgType.Image, + url: url, + info: info, + body: text, + }; + return this.sendMessage(roomId, threadId, content); + } + sendStickerMessage(roomId, threadId, url, info, text = "Sticker") { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + text = info || "Sticker"; + info = url; + url = threadId; + threadId = null; + } + const content = { + url: url, + info: info, + body: text, + }; + return this.sendEvent(roomId, threadId, event_2.EventType.Sticker, content); + } + sendHtmlMessage(roomId, threadId, body, htmlBody) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + htmlBody = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeHtmlMessage(body, htmlBody); + return this.sendMessage(roomId, threadId, content); + } + sendHtmlNotice(roomId, threadId, body, htmlBody) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + htmlBody = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeHtmlNotice(body, htmlBody); + return this.sendMessage(roomId, threadId, content); + } + sendHtmlEmote(roomId, threadId, body, htmlBody) { + if (!(threadId === null || threadId === void 0 ? void 0 : threadId.startsWith(EVENT_ID_PREFIX)) && threadId !== null) { + htmlBody = body; + body = threadId; + threadId = null; + } + const content = ContentHelpers.makeHtmlEmote(body, htmlBody); + return this.sendMessage(roomId, threadId, content); + } + /** + * Send a receipt. + * @param event - The event being acknowledged + * @param receiptType - The kind of receipt e.g. "m.read". Other than + * ReceiptType.Read are experimental! + * @param body - Additional content to send alongside the receipt. + * @param unthreaded - An unthreaded receipt will clear room+thread notifications + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + sendReceipt(event, receiptType, body, unthreaded = false) { + return __awaiter(this, void 0, void 0, function* () { + if (this.isGuest()) { + return Promise.resolve({}); // guests cannot send receipts so don't bother. + } + const path = utils.encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { + $roomId: event.getRoomId(), + $receiptType: receiptType, + $eventId: event.getId(), + }); + if (!unthreaded) { + const isThread = !!event.threadRootId; + body = Object.assign(Object.assign({}, body), { thread_id: isThread ? event.threadRootId : read_receipts_1.MAIN_ROOM_TIMELINE }); + } + const promise = this.http.authedRequest(http_api_1.Method.Post, path, undefined, body || {}); + const room = this.getRoom(event.getRoomId()); + if (room && this.credentials.userId) { + room.addLocalEchoReceipt(this.credentials.userId, event, receiptType); + } + return promise; + }); + } + /** + * Send a read receipt. + * @param event - The event that has been read. + * @param receiptType - other than ReceiptType.Read are experimental! Optional. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + sendReadReceipt(event, receiptType = read_receipts_1.ReceiptType.Read, unthreaded = false) { + return __awaiter(this, void 0, void 0, function* () { + if (!event) + return; + const eventId = event.getId(); + const room = this.getRoom(event.getRoomId()); + if (room === null || room === void 0 ? void 0 : room.hasPendingEvent(eventId)) { + throw new Error(`Cannot set read receipt to a pending event (${eventId})`); + } + return this.sendReceipt(event, receiptType, {}, unthreaded); + }); + } + /** + * Set a marker to indicate the point in a room before which the user has read every + * event. This can be retrieved from room account data (the event type is `m.fully_read`) + * and displayed as a horizontal line in the timeline that is visually distinct to the + * position of the user's own read receipt. + * @param roomId - ID of the room that has been read + * @param rmEventId - ID of the event that has been read + * @param rrEvent - the event tracked by the read receipt. This is here for + * convenience because the RR and the RM are commonly updated at the same time as each + * other. The local echo of this receipt will be done if set. Optional. + * @param rpEvent - the m.read.private read receipt event for when we don't + * want other users to see the read receipts. This is experimental. Optional. + * @returns Promise which resolves: the empty object, `{}`. + */ + setRoomReadMarkers(roomId, rmEventId, rrEvent, rpEvent) { + return __awaiter(this, void 0, void 0, function* () { + const room = this.getRoom(roomId); + if (room && room.hasPendingEvent(rmEventId)) { + throw new Error(`Cannot set read marker to a pending event (${rmEventId})`); + } + // Add the optional RR update, do local echo like `sendReceipt` + let rrEventId; + if (rrEvent) { + rrEventId = rrEvent.getId(); + if (room === null || room === void 0 ? void 0 : room.hasPendingEvent(rrEventId)) { + throw new Error(`Cannot set read receipt to a pending event (${rrEventId})`); + } + room === null || room === void 0 ? void 0 : room.addLocalEchoReceipt(this.credentials.userId, rrEvent, read_receipts_1.ReceiptType.Read); + } + // Add the optional private RR update, do local echo like `sendReceipt` + let rpEventId; + if (rpEvent) { + rpEventId = rpEvent.getId(); + if (room === null || room === void 0 ? void 0 : room.hasPendingEvent(rpEventId)) { + throw new Error(`Cannot set read receipt to a pending event (${rpEventId})`); + } + room === null || room === void 0 ? void 0 : room.addLocalEchoReceipt(this.credentials.userId, rpEvent, read_receipts_1.ReceiptType.ReadPrivate); + } + return yield this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId); + }); + } + /** + * Get a preview of the given URL as of (roughly) the given point in time, + * described as an object with OpenGraph keys and associated values. + * Attributes may be synthesized where actual OG metadata is lacking. + * Caches results to prevent hammering the server. + * @param url - The URL to get preview data for + * @param ts - The preferred point in time that the preview should + * describe (ms since epoch). The preview returned will either be the most + * recent one preceding this timestamp if available, or failing that the next + * most recent available preview. + * @returns Promise which resolves: Object of OG metadata. + * @returns Rejects: with an error response. + * May return synthesized attributes if the URL lacked OG meta. + */ + getUrlPreview(url, ts) { + // bucket the timestamp to the nearest minute to prevent excessive spam to the server + // Surely 60-second accuracy is enough for anyone. + ts = Math.floor(ts / 60000) * 60000; + const parsed = new URL(url); + parsed.hash = ""; // strip the hash as it won't affect the preview + url = parsed.toString(); + const key = ts + "_" + url; + // If there's already a request in flight (or we've handled it), return that instead. + const cachedPreview = this.urlPreviewCache[key]; + if (cachedPreview) { + return cachedPreview; + } + const resp = this.http.authedRequest(http_api_1.Method.Get, "/preview_url", { + url, + ts: ts.toString(), + }, undefined, { + prefix: http_api_1.MediaPrefix.R0, + }); + // TODO: Expire the URL preview cache sometimes + this.urlPreviewCache[key] = resp; + return resp; + } + /** + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + sendTyping(roomId, isTyping, timeoutMs) { + if (this.isGuest()) { + return Promise.resolve({}); // guests cannot send typing notifications so don't bother. + } + const path = utils.encodeUri("/rooms/$roomId/typing/$userId", { + $roomId: roomId, + $userId: this.getUserId(), + }); + const data = { + typing: isTyping, + }; + if (isTyping) { + data.timeout = timeoutMs ? timeoutMs : 20000; + } + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, data); + } + /** + * Determines the history of room upgrades for a given room, as far as the + * client can see. Returns an array of Rooms where the first entry is the + * oldest and the last entry is the newest (likely current) room. If the + * provided room is not found, this returns an empty list. This works in + * both directions, looking for older and newer rooms of the given room. + * @param roomId - The room ID to search from + * @param verifyLinks - If true, the function will only return rooms + * which can be proven to be linked. For example, rooms which have a create + * event pointing to an old room which the client is not aware of or doesn't + * have a matching tombstone would not be returned. + * @param msc3946ProcessDynamicPredecessor - if true, look for + * m.room.predecessor state events as well as create events, and prefer + * predecessor events where they exist (MSC3946). + * @returns An array of rooms representing the upgrade + * history. + */ + getRoomUpgradeHistory(roomId, verifyLinks = false, msc3946ProcessDynamicPredecessor = false) { + const currentRoom = this.getRoom(roomId); + if (!currentRoom) + return []; + const before = this.findPredecessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor); + const after = this.findSuccessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor); + return [...before, currentRoom, ...after]; + } + findPredecessorRooms(room, verifyLinks, msc3946ProcessDynamicPredecessor) { + var _a, _b; + const ret = []; + // Work backwards from newer to older rooms + let predecessorRoomId = (_a = room.findPredecessor(msc3946ProcessDynamicPredecessor)) === null || _a === void 0 ? void 0 : _a.roomId; + while (predecessorRoomId !== null) { + const predecessorRoom = this.getRoom(predecessorRoomId); + if (predecessorRoom === null) { + break; + } + if (verifyLinks) { + const tombstone = predecessorRoom.currentState.getStateEvents(event_2.EventType.RoomTombstone, ""); + if (!tombstone || tombstone.getContent()["replacement_room"] !== room.roomId) { + break; + } + } + // Insert at the front because we're working backwards from the currentRoom + ret.splice(0, 0, predecessorRoom); + room = predecessorRoom; + predecessorRoomId = (_b = room.findPredecessor(msc3946ProcessDynamicPredecessor)) === null || _b === void 0 ? void 0 : _b.roomId; + } + return ret; + } + findSuccessorRooms(room, verifyLinks, msc3946ProcessDynamicPredecessor) { + var _a; + const ret = []; + // Work forwards, looking at tombstone events + let tombstoneEvent = room.currentState.getStateEvents(event_2.EventType.RoomTombstone, ""); + while (tombstoneEvent) { + const successorRoom = this.getRoom(tombstoneEvent.getContent()["replacement_room"]); + if (!successorRoom) + break; // end of the chain + if (successorRoom.roomId === room.roomId) + break; // Tombstone is referencing its own room + if (verifyLinks) { + const predecessorRoomId = (_a = successorRoom.findPredecessor(msc3946ProcessDynamicPredecessor)) === null || _a === void 0 ? void 0 : _a.roomId; + if (!predecessorRoomId || predecessorRoomId !== room.roomId) { + break; + } + } + // Push to the end because we're looking forwards + ret.push(successorRoom); + const roomIds = new Set(ret.map((ref) => ref.roomId)); + if (roomIds.size < ret.length) { + // The last room added to the list introduced a previous roomId + // To avoid recursion, return the last rooms - 1 + return ret.slice(0, ret.length - 1); + } + // Set the current room to the reference room so we know where we're at + room = successorRoom; + tombstoneEvent = room.currentState.getStateEvents(event_2.EventType.RoomTombstone, ""); + } + return ret; + } + /** + * @param reason - Optional. + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + invite(roomId, userId, reason) { + return this.membershipChange(roomId, userId, "invite", reason); + } + /** + * Invite a user to a room based on their email address. + * @param roomId - The room to invite the user to. + * @param email - The email address to invite. + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + inviteByEmail(roomId, email) { + return this.inviteByThreePid(roomId, "email", email); + } + /** + * Invite a user to a room based on a third-party identifier. + * @param roomId - The room to invite the user to. + * @param medium - The medium to invite the user e.g. "email". + * @param address - The address for the specified medium. + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + inviteByThreePid(roomId, medium, address) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const path = utils.encodeUri("/rooms/$roomId/invite", { $roomId: roomId }); + const identityServerUrl = this.getIdentityServerUrl(true); + if (!identityServerUrl) { + return Promise.reject(new http_api_1.MatrixError({ + error: "No supplied identity server URL", + errcode: "ORG.MATRIX.JSSDK_MISSING_PARAM", + })); + } + const params = { + id_server: identityServerUrl, + medium: medium, + address: address, + }; + if (((_a = this.identityServer) === null || _a === void 0 ? void 0 : _a.getAccessToken) && (yield this.doesServerAcceptIdentityAccessToken())) { + const identityAccessToken = yield this.identityServer.getAccessToken(); + if (identityAccessToken) { + params["id_access_token"] = identityAccessToken; + } + } + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, params); + }); + } + /** + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + leave(roomId) { + return this.membershipChange(roomId, undefined, "leave"); + } + /** + * Leaves all rooms in the chain of room upgrades based on the given room. By + * default, this will leave all the previous and upgraded rooms, including the + * given room. To only leave the given room and any previous rooms, keeping the + * upgraded (modern) rooms untouched supply `false` to `includeFuture`. + * @param roomId - The room ID to start leaving at + * @param includeFuture - If true, the whole chain (past and future) of + * upgraded rooms will be left. + * @returns Promise which resolves when completed with an object keyed + * by room ID and value of the error encountered when leaving or null. + */ + leaveRoomChain(roomId, includeFuture = true) { + const upgradeHistory = this.getRoomUpgradeHistory(roomId); + let eligibleToLeave = upgradeHistory; + if (!includeFuture) { + eligibleToLeave = []; + for (const room of upgradeHistory) { + eligibleToLeave.push(room); + if (room.roomId === roomId) { + break; + } + } + } + const populationResults = {}; + const promises = []; + const doLeave = (roomId) => { + return this.leave(roomId) + .then(() => { + delete populationResults[roomId]; + }) + .catch((err) => { + // suppress error + populationResults[roomId] = err; + }); + }; + for (const room of eligibleToLeave) { + promises.push(doLeave(room.roomId)); + } + return Promise.all(promises).then(() => populationResults); + } + /** + * @param reason - Optional. + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + ban(roomId, userId, reason) { + return this.membershipChange(roomId, userId, "ban", reason); + } + /** + * @param deleteRoom - True to delete the room from the store on success. + * Default: true. + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + forget(roomId, deleteRoom = true) { + const promise = this.membershipChange(roomId, undefined, "forget"); + if (!deleteRoom) { + return promise; + } + return promise.then((response) => { + this.store.removeRoom(roomId); + this.emit(ClientEvent.DeleteRoom, roomId); + return response; + }); + } + /** + * @returns Promise which resolves: Object (currently empty) + * @returns Rejects: with an error response. + */ + unban(roomId, userId) { + // unbanning != set their state to leave: this used to be + // the case, but was then changed so that leaving was always + // a revoking of privilege, otherwise two people racing to + // kick / ban someone could end up banning and then un-banning + // them. + const path = utils.encodeUri("/rooms/$roomId/unban", { + $roomId: roomId, + }); + const data = { + user_id: userId, + }; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data); + } + /** + * @param reason - Optional. + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + kick(roomId, userId, reason) { + const path = utils.encodeUri("/rooms/$roomId/kick", { + $roomId: roomId, + }); + const data = { + user_id: userId, + reason: reason, + }; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data); + } + membershipChange(roomId, userId, membership, reason) { + // API returns an empty object + const path = utils.encodeUri("/rooms/$room_id/$membership", { + $room_id: roomId, + $membership: membership, + }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, { + user_id: userId, + reason: reason, + }); + } + /** + * Obtain a dict of actions which should be performed for this event according + * to the push rules for this user. Caches the dict on the event. + * @param event - The event to get push actions for. + * @param forceRecalculate - forces to recalculate actions for an event + * Useful when an event just got decrypted + * @returns A dict of actions to perform. + */ + getPushActionsForEvent(event, forceRecalculate = false) { + if (!event.getPushActions() || forceRecalculate) { + event.setPushActions(this.pushProcessor.actionsForEvent(event)); + } + return event.getPushActions(); + } + setProfileInfo(info, data) { + const path = utils.encodeUri("/profile/$userId/$info", { + $userId: this.credentials.userId, + $info: info, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, data); + } + /** + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + setDisplayName(name) { + return __awaiter(this, void 0, void 0, function* () { + const prom = yield this.setProfileInfo("displayname", { displayname: name }); + // XXX: synthesise a profile update for ourselves because Synapse is broken and won't + const user = this.getUser(this.getUserId()); + if (user) { + user.displayName = name; + user.emit(user_1.UserEvent.DisplayName, user.events.presence, user); + } + return prom; + }); + } + /** + * @returns Promise which resolves: `{}` an empty object. + * @returns Rejects: with an error response. + */ + setAvatarUrl(url) { + return __awaiter(this, void 0, void 0, function* () { + const prom = yield this.setProfileInfo("avatar_url", { avatar_url: url }); + // XXX: synthesise a profile update for ourselves because Synapse is broken and won't + const user = this.getUser(this.getUserId()); + if (user) { + user.avatarUrl = url; + user.emit(user_1.UserEvent.AvatarUrl, user.events.presence, user); + } + return prom; + }); + } + /** + * Turn an MXC URL into an HTTP one. This method is experimental and + * may change. + * @param mxcUrl - The MXC URL + * @param width - The desired width of the thumbnail. + * @param height - The desired height of the thumbnail. + * @param resizeMethod - The thumbnail resize method to use, either + * "crop" or "scale". + * @param allowDirectLinks - If true, return any non-mxc URLs + * directly. Fetching such URLs will leak information about the user to + * anyone they share a room with. If false, will return null for such URLs. + * @returns the avatar URL or null. + */ + mxcUrlToHttp(mxcUrl, width, height, resizeMethod, allowDirectLinks) { + return (0, content_repo_1.getHttpUriForMxc)(this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks); + } + /** + * @param opts - Options to apply + * @returns Promise which resolves + * @returns Rejects: with an error response. + * @throws If 'presence' isn't a valid presence enum value. + */ + setPresence(opts) { + return __awaiter(this, void 0, void 0, function* () { + const path = utils.encodeUri("/presence/$userId/status", { + $userId: this.credentials.userId, + }); + const validStates = ["offline", "online", "unavailable"]; + if (validStates.indexOf(opts.presence) === -1) { + throw new Error("Bad presence value: " + opts.presence); + } + yield this.http.authedRequest(http_api_1.Method.Put, path, undefined, opts); + }); + } + /** + * @param userId - The user to get presence for + * @returns Promise which resolves: The presence state for this user. + * @returns Rejects: with an error response. + */ + getPresence(userId) { + const path = utils.encodeUri("/presence/$userId/status", { + $userId: userId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Retrieve older messages from the given room and put them in the timeline. + * + * If this is called multiple times whilst a request is ongoing, the same + * Promise will be returned. If there was a problem requesting scrollback, there + * will be a small delay before another request can be made (to prevent tight-looping + * when there is no connection). + * + * @param room - The room to get older messages in. + * @param limit - Optional. The maximum number of previous events to + * pull in. Default: 30. + * @returns Promise which resolves: Room. If you are at the beginning + * of the timeline, `Room.oldState.paginationToken` will be + * `null`. + * @returns Rejects: with an error response. + */ + scrollback(room, limit = 30) { + let timeToWaitMs = 0; + let info = this.ongoingScrollbacks[room.roomId] || {}; + if (info.promise) { + return info.promise; + } + else if (info.errorTs) { + const timeWaitedMs = Date.now() - info.errorTs; + timeToWaitMs = Math.max(SCROLLBACK_DELAY_MS - timeWaitedMs, 0); + } + if (room.oldState.paginationToken === null) { + return Promise.resolve(room); // already at the start. + } + // attempt to grab more events from the store first + const numAdded = this.store.scrollback(room, limit).length; + if (numAdded === limit) { + // store contained everything we needed. + return Promise.resolve(room); + } + // reduce the required number of events appropriately + limit = limit - numAdded; + const promise = new Promise((resolve, reject) => { + // wait for a time before doing this request + // (which may be 0 in order not to special case the code paths) + (0, utils_1.sleep)(timeToWaitMs) + .then(() => { + return this.createMessagesRequest(room.roomId, room.oldState.paginationToken, limit, event_timeline_1.Direction.Backward); + }) + .then((res) => { + var _a, _b; + const matrixEvents = res.chunk.map(this.getEventMapper()); + if (res.state) { + const stateEvents = res.state.map(this.getEventMapper()); + room.currentState.setUnknownStateEvents(stateEvents); + } + const [timelineEvents, threadedEvents] = room.partitionThreadedEvents(matrixEvents); + this.processAggregatedTimelineEvents(room, timelineEvents); + room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline()); + this.processThreadEvents(room, threadedEvents, true); + room.oldState.paginationToken = (_a = res.end) !== null && _a !== void 0 ? _a : null; + if (res.chunk.length === 0) { + room.oldState.paginationToken = null; + } + this.store.storeEvents(room, matrixEvents, (_b = res.end) !== null && _b !== void 0 ? _b : null, true); + delete this.ongoingScrollbacks[room.roomId]; + resolve(room); + }) + .catch((err) => { + this.ongoingScrollbacks[room.roomId] = { + errorTs: Date.now(), + }; + reject(err); + }); + }); + info = { promise }; + this.ongoingScrollbacks[room.roomId] = info; + return promise; + } + getEventMapper(options) { + return (0, event_mapper_1.eventMapperFor)(this, options || {}); + } + /** + * Get an EventTimeline for the given event + * + *

If the EventTimelineSet object already has the given event in its store, the + * corresponding timeline will be returned. Otherwise, a /context request is + * made, and used to construct an EventTimeline. + * If the event does not belong to this EventTimelineSet then undefined will be returned. + * + * @param timelineSet - The timelineSet to look for the event in, must be bound to a room + * @param eventId - The ID of the event to look for + * + * @returns Promise which resolves: + * {@link EventTimeline} including the given event + */ + getEventTimeline(timelineSet, eventId) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + // don't allow any timeline support unless it's been enabled. + if (!this.timelineSupport) { + throw new Error("timeline support is disabled. Set the 'timelineSupport'" + + " parameter to true when creating MatrixClient to enable it."); + } + if (!(timelineSet === null || timelineSet === void 0 ? void 0 : timelineSet.room)) { + throw new Error("getEventTimeline only supports room timelines"); + } + if (timelineSet.getTimelineForEvent(eventId)) { + return timelineSet.getTimelineForEvent(eventId); + } + if (timelineSet.thread && this.supportsThreads()) { + return this.getThreadTimeline(timelineSet, eventId); + } + const path = utils.encodeUri("/rooms/$roomId/context/$eventId", { + $roomId: timelineSet.room.roomId, + $eventId: eventId, + }); + let params = undefined; + if ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.lazyLoadMembers) { + params = { filter: JSON.stringify(filter_1.Filter.LAZY_LOADING_MESSAGES_FILTER) }; + } + // TODO: we should implement a backoff (as per scrollback()) to deal more nicely with HTTP errors. + const res = yield this.http.authedRequest(http_api_1.Method.Get, path, params); + if (!res.event) { + throw new Error("'event' not in '/context' result - homeserver too old?"); + } + // by the time the request completes, the event might have ended up in the timeline. + if (timelineSet.getTimelineForEvent(eventId)) { + return timelineSet.getTimelineForEvent(eventId); + } + const mapper = this.getEventMapper(); + const event = mapper(res.event); + if (event.isRelation(thread_1.THREAD_RELATION_TYPE.name)) { + logger_1.logger.warn("Tried loading a regular timeline at the position of a thread event"); + return undefined; + } + const events = [ + // Order events from most recent to oldest (reverse-chronological). + // We start with the last event, since that's the point at which we have known state. + // events_after is already backwards; events_before is forwards. + ...res.events_after.reverse().map(mapper), + event, + ...res.events_before.map(mapper), + ]; + // Here we handle non-thread timelines only, but still process any thread events to populate thread summaries. + let timeline = timelineSet.getTimelineForEvent(events[0].getId()); + if (timeline) { + timeline.getState(event_timeline_1.EventTimeline.BACKWARDS).setUnknownStateEvents(res.state.map(mapper)); + } + else { + timeline = timelineSet.addTimeline(); + timeline.initialiseState(res.state.map(mapper)); + timeline.getState(event_timeline_1.EventTimeline.FORWARDS).paginationToken = res.end; + } + const [timelineEvents, threadedEvents] = timelineSet.room.partitionThreadedEvents(events); + timelineSet.addEventsToTimeline(timelineEvents, true, timeline, res.start); + // The target event is not in a thread but process the contextual events, so we can show any threads around it. + this.processThreadEvents(timelineSet.room, threadedEvents, true); + this.processAggregatedTimelineEvents(timelineSet.room, timelineEvents); + // There is no guarantee that the event ended up in "timeline" (we might have switched to a neighbouring + // timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up + // anywhere, if it was later redacted, so we just return the timeline we first thought of. + return ((_d = (_b = timelineSet.getTimelineForEvent(eventId)) !== null && _b !== void 0 ? _b : (_c = timelineSet.room.findThreadForEvent(event)) === null || _c === void 0 ? void 0 : _c.liveTimeline) !== null && _d !== void 0 ? _d : timeline); + }); + } + getThreadTimeline(timelineSet, eventId) { + var _a, _b, _c, _d, _e, _f, _g, _h; + return __awaiter(this, void 0, void 0, function* () { + if (!this.supportsThreads()) { + throw new Error("could not get thread timeline: no client support"); + } + if (!timelineSet.room) { + throw new Error("could not get thread timeline: not a room timeline"); + } + if (!timelineSet.thread) { + throw new Error("could not get thread timeline: not a thread timeline"); + } + const path = utils.encodeUri("/rooms/$roomId/context/$eventId", { + $roomId: timelineSet.room.roomId, + $eventId: eventId, + }); + const params = { + limit: "0", + }; + if ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.lazyLoadMembers) { + params.filter = JSON.stringify(filter_1.Filter.LAZY_LOADING_MESSAGES_FILTER); + } + // TODO: we should implement a backoff (as per scrollback()) to deal more nicely with HTTP errors. + const res = yield this.http.authedRequest(http_api_1.Method.Get, path, params); + const mapper = this.getEventMapper(); + const event = mapper(res.event); + if (!timelineSet.canContain(event)) { + return undefined; + } + if (thread_1.Thread.hasServerSideSupport) { + if (thread_1.Thread.hasServerSideFwdPaginationSupport) { + if (!timelineSet.thread) { + throw new Error("could not get thread timeline: not a thread timeline"); + } + const thread = timelineSet.thread; + const resOlder = yield this.fetchRelations(timelineSet.room.roomId, thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { dir: event_timeline_1.Direction.Backward, from: res.start }); + const resNewer = yield this.fetchRelations(timelineSet.room.roomId, thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { dir: event_timeline_1.Direction.Forward, from: res.end }); + const events = [ + // Order events from most recent to oldest (reverse-chronological). + // We start with the last event, since that's the point at which we have known state. + // events_after is already backwards; events_before is forwards. + ...resNewer.chunk.reverse().map(mapper), + event, + ...resOlder.chunk.map(mapper), + ]; + for (const event of events) { + yield ((_b = timelineSet.thread) === null || _b === void 0 ? void 0 : _b.processEvent(event)); + } + // Here we handle non-thread timelines only, but still process any thread events to populate thread summaries. + let timeline = timelineSet.getTimelineForEvent(event.getId()); + if (timeline) { + timeline.getState(event_timeline_1.EventTimeline.BACKWARDS).setUnknownStateEvents(res.state.map(mapper)); + } + else { + timeline = timelineSet.addTimeline(); + timeline.initialiseState(res.state.map(mapper)); + } + timelineSet.addEventsToTimeline(events, true, timeline, resNewer.next_batch); + if (!resOlder.next_batch) { + const originalEvent = yield this.fetchRoomEvent(timelineSet.room.roomId, thread.id); + timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null); + } + timeline.setPaginationToken((_c = resOlder.next_batch) !== null && _c !== void 0 ? _c : null, event_timeline_1.Direction.Backward); + timeline.setPaginationToken((_d = resNewer.next_batch) !== null && _d !== void 0 ? _d : null, event_timeline_1.Direction.Forward); + this.processAggregatedTimelineEvents(timelineSet.room, events); + // There is no guarantee that the event ended up in "timeline" (we might have switched to a neighbouring + // timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up + // anywhere, if it was later redacted, so we just return the timeline we first thought of. + return (_e = timelineSet.getTimelineForEvent(eventId)) !== null && _e !== void 0 ? _e : timeline; + } + else { + // Where the event is a thread reply (not a root) and running in MSC-enabled mode the Thread timeline only + // functions contiguously, so we have to jump through some hoops to get our target event in it. + // XXX: workaround for https://github.com/vector-im/element-meta/issues/150 + const thread = timelineSet.thread; + const resOlder = yield this.fetchRelations(timelineSet.room.roomId, thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { dir: event_timeline_1.Direction.Backward, from: res.start }); + const eventsNewer = []; + let nextBatch = res.end; + while (nextBatch) { + const resNewer = yield this.fetchRelations(timelineSet.room.roomId, thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { dir: event_timeline_1.Direction.Forward, from: nextBatch }); + nextBatch = (_f = resNewer.next_batch) !== null && _f !== void 0 ? _f : null; + eventsNewer.push(...resNewer.chunk); + } + const events = [ + // Order events from most recent to oldest (reverse-chronological). + // We start with the last event, since that's the point at which we have known state. + // events_after is already backwards; events_before is forwards. + ...eventsNewer.reverse().map(mapper), + event, + ...resOlder.chunk.map(mapper), + ]; + for (const event of events) { + yield ((_g = timelineSet.thread) === null || _g === void 0 ? void 0 : _g.processEvent(event)); + } + // Here we handle non-thread timelines only, but still process any thread events to populate thread + // summaries. + const timeline = timelineSet.getLiveTimeline(); + timeline.getState(event_timeline_1.EventTimeline.BACKWARDS).setUnknownStateEvents(res.state.map(mapper)); + timelineSet.addEventsToTimeline(events, true, timeline, null); + if (!resOlder.next_batch) { + const originalEvent = yield this.fetchRoomEvent(timelineSet.room.roomId, thread.id); + timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null); + } + timeline.setPaginationToken((_h = resOlder.next_batch) !== null && _h !== void 0 ? _h : null, event_timeline_1.Direction.Backward); + timeline.setPaginationToken(null, event_timeline_1.Direction.Forward); + this.processAggregatedTimelineEvents(timelineSet.room, events); + return timeline; + } + } + }); + } + /** + * Get an EventTimeline for the latest events in the room. This will just + * call `/messages` to get the latest message in the room, then use + * `client.getEventTimeline(...)` to construct a new timeline from it. + * + * @param timelineSet - The timelineSet to find or add the timeline to + * + * @returns Promise which resolves: + * {@link EventTimeline} timeline with the latest events in the room + */ + getLatestTimeline(timelineSet) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + // don't allow any timeline support unless it's been enabled. + if (!this.timelineSupport) { + throw new Error("timeline support is disabled. Set the 'timelineSupport'" + + " parameter to true when creating MatrixClient to enable it."); + } + if (!timelineSet.room) { + throw new Error("getLatestTimeline only supports room timelines"); + } + let event; + if (timelineSet.threadListType !== null) { + const res = yield this.createThreadListMessagesRequest(timelineSet.room.roomId, null, 1, event_timeline_1.Direction.Backward, timelineSet.threadListType, timelineSet.getFilter()); + event = (_a = res.chunk) === null || _a === void 0 ? void 0 : _a[0]; + } + else if (timelineSet.thread && thread_1.Thread.hasServerSideSupport) { + const res = yield this.fetchRelations(timelineSet.room.roomId, timelineSet.thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { dir: event_timeline_1.Direction.Backward, limit: 1 }); + event = (_b = res.chunk) === null || _b === void 0 ? void 0 : _b[0]; + } + else { + const messagesPath = utils.encodeUri("/rooms/$roomId/messages", { + $roomId: timelineSet.room.roomId, + }); + const params = { + dir: "b", + }; + if ((_c = this.clientOpts) === null || _c === void 0 ? void 0 : _c.lazyLoadMembers) { + params.filter = JSON.stringify(filter_1.Filter.LAZY_LOADING_MESSAGES_FILTER); + } + const res = yield this.http.authedRequest(http_api_1.Method.Get, messagesPath, params); + event = (_d = res.chunk) === null || _d === void 0 ? void 0 : _d[0]; + } + if (!event) { + throw new Error("No message returned when trying to construct getLatestTimeline"); + } + return this.getEventTimeline(timelineSet, event.event_id); + }); + } + /** + * Makes a request to /messages with the appropriate lazy loading filter set. + * XXX: if we do get rid of scrollback (as it's not used at the moment), + * we could inline this method again in paginateEventTimeline as that would + * then be the only call-site + * @param limit - the maximum amount of events the retrieve + * @param dir - 'f' or 'b' + * @param timelineFilter - the timeline filter to pass + */ + // XXX: Intended private, used in code. + createMessagesRequest(roomId, fromToken, limit = 30, dir, timelineFilter) { + var _a, _b; + const path = utils.encodeUri("/rooms/$roomId/messages", { $roomId: roomId }); + const params = { + limit: limit.toString(), + dir: dir, + }; + if (fromToken) { + params.from = fromToken; + } + let filter = null; + if ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.lazyLoadMembers) { + // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER, + // so the timelineFilter doesn't get written into it below + filter = Object.assign({}, filter_1.Filter.LAZY_LOADING_MESSAGES_FILTER); + } + if (timelineFilter) { + // XXX: it's horrific that /messages' filter parameter doesn't match + // /sync's one - see https://matrix.org/jira/browse/SPEC-451 + filter = filter || {}; + Object.assign(filter, (_b = timelineFilter.getRoomTimelineFilterComponent()) === null || _b === void 0 ? void 0 : _b.toJSON()); + } + if (filter) { + params.filter = JSON.stringify(filter); + } + return this.http.authedRequest(http_api_1.Method.Get, path, params); + } + /** + * Makes a request to /messages with the appropriate lazy loading filter set. + * XXX: if we do get rid of scrollback (as it's not used at the moment), + * we could inline this method again in paginateEventTimeline as that would + * then be the only call-site + * @param limit - the maximum amount of events the retrieve + * @param dir - 'f' or 'b' + * @param timelineFilter - the timeline filter to pass + */ + // XXX: Intended private, used by room.fetchRoomThreads + createThreadListMessagesRequest(roomId, fromToken, limit = 30, dir = event_timeline_1.Direction.Backward, threadListType = thread_1.ThreadFilterType.All, timelineFilter) { + var _a, _b; + const path = utils.encodeUri("/rooms/$roomId/threads", { $roomId: roomId }); + const params = { + limit: limit.toString(), + dir: dir, + include: (0, thread_1.threadFilterTypeToFilter)(threadListType), + }; + if (fromToken) { + params.from = fromToken; + } + let filter = {}; + if ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.lazyLoadMembers) { + // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER, + // so the timelineFilter doesn't get written into it below + filter = Object.assign({}, filter_1.Filter.LAZY_LOADING_MESSAGES_FILTER); + } + if (timelineFilter) { + // XXX: it's horrific that /messages' filter parameter doesn't match + // /sync's one - see https://matrix.org/jira/browse/SPEC-451 + filter = Object.assign(Object.assign({}, filter), (_b = timelineFilter.getRoomTimelineFilterComponent()) === null || _b === void 0 ? void 0 : _b.toJSON()); + } + if (Object.keys(filter).length) { + params.filter = JSON.stringify(filter); + } + const opts = { + prefix: thread_1.Thread.hasServerSideListSupport === thread_1.FeatureSupport.Stable + ? "/_matrix/client/v1" + : "/_matrix/client/unstable/org.matrix.msc3856", + }; + return this.http + .authedRequest(http_api_1.Method.Get, path, params, undefined, opts) + .then((res) => { + var _a; + return (Object.assign(Object.assign({}, res), { chunk: (_a = res.chunk) === null || _a === void 0 ? void 0 : _a.reverse(), start: res.prev_batch, end: res.next_batch })); + }); + } + /** + * Take an EventTimeline, and back/forward-fill results. + * + * @param eventTimeline - timeline object to be updated + * + * @returns Promise which resolves to a boolean: false if there are no + * events and we reached either end of the timeline; else true. + */ + paginateEventTimeline(eventTimeline, opts) { + var _a, _b, _c; + const isNotifTimeline = eventTimeline.getTimelineSet() === this.notifTimelineSet; + const room = this.getRoom(eventTimeline.getRoomId()); + const threadListType = eventTimeline.getTimelineSet().threadListType; + const thread = eventTimeline.getTimelineSet().thread; + // TODO: we should implement a backoff (as per scrollback()) to deal more + // nicely with HTTP errors. + opts = opts || {}; + const backwards = opts.backwards || false; + if (isNotifTimeline) { + if (!backwards) { + throw new Error("paginateNotifTimeline can only paginate backwards"); + } + } + const dir = backwards ? event_timeline_1.EventTimeline.BACKWARDS : event_timeline_1.EventTimeline.FORWARDS; + const token = eventTimeline.getPaginationToken(dir); + const pendingRequest = eventTimeline.paginationRequests[dir]; + if (pendingRequest) { + // already a request in progress - return the existing promise + return pendingRequest; + } + let path; + let params; + let promise; + if (isNotifTimeline) { + path = "/notifications"; + params = { + limit: ((_a = opts.limit) !== null && _a !== void 0 ? _a : 30).toString(), + only: "highlight", + }; + if (token && token !== "end") { + params.from = token; + } + promise = this.http + .authedRequest(http_api_1.Method.Get, path, params) + .then((res) => __awaiter(this, void 0, void 0, function* () { + const token = res.next_token; + const matrixEvents = []; + res.notifications = res.notifications.filter(utils_1.noUnsafeEventProps); + for (let i = 0; i < res.notifications.length; i++) { + const notification = res.notifications[i]; + const event = this.getEventMapper()(notification.event); + event.setPushActions(pushprocessor_1.PushProcessor.actionListToActionsObject(notification.actions)); + event.event.room_id = notification.room_id; // XXX: gutwrenching + matrixEvents[i] = event; + } + // No need to partition events for threads here, everything lives + // in the notification timeline set + const timelineSet = eventTimeline.getTimelineSet(); + timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token); + this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents); + // if we've hit the end of the timeline, we need to stop trying to + // paginate. We need to keep the 'forwards' token though, to make sure + // we can recover from gappy syncs. + if (backwards && !res.next_token) { + eventTimeline.setPaginationToken(null, dir); + } + return Boolean(res.next_token); + })) + .finally(() => { + eventTimeline.paginationRequests[dir] = null; + }); + eventTimeline.paginationRequests[dir] = promise; + } + else if (threadListType !== null) { + if (!room) { + throw new Error("Unknown room " + eventTimeline.getRoomId()); + } + if (!thread_1.Thread.hasServerSideFwdPaginationSupport && dir === event_timeline_1.Direction.Forward) { + throw new Error("Cannot paginate threads forwards without server-side support for MSC 3715"); + } + promise = this.createThreadListMessagesRequest(eventTimeline.getRoomId(), token, opts.limit, dir, threadListType, eventTimeline.getFilter()) + .then((res) => { + if (res.state) { + const roomState = eventTimeline.getState(dir); + const stateEvents = res.state.filter(utils_1.noUnsafeEventProps).map(this.getEventMapper()); + roomState.setUnknownStateEvents(stateEvents); + } + const token = res.end; + const matrixEvents = res.chunk.filter(utils_1.noUnsafeEventProps).map(this.getEventMapper()); + const timelineSet = eventTimeline.getTimelineSet(); + timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token); + this.processAggregatedTimelineEvents(room, matrixEvents); + this.processThreadRoots(room, matrixEvents, backwards); + // if we've hit the end of the timeline, we need to stop trying to + // paginate. We need to keep the 'forwards' token though, to make sure + // we can recover from gappy syncs. + if (backwards && res.end == res.start) { + eventTimeline.setPaginationToken(null, dir); + } + return res.end !== res.start; + }) + .finally(() => { + eventTimeline.paginationRequests[dir] = null; + }); + eventTimeline.paginationRequests[dir] = promise; + } + else if (thread) { + const room = this.getRoom((_b = eventTimeline.getRoomId()) !== null && _b !== void 0 ? _b : undefined); + if (!room) { + throw new Error("Unknown room " + eventTimeline.getRoomId()); + } + promise = this.fetchRelations((_c = eventTimeline.getRoomId()) !== null && _c !== void 0 ? _c : "", thread.id, thread_1.THREAD_RELATION_TYPE.name, null, { + dir, + limit: opts.limit, + from: token !== null && token !== void 0 ? token : undefined, + }) + .then((res) => __awaiter(this, void 0, void 0, function* () { + var _d; + const mapper = this.getEventMapper(); + const matrixEvents = res.chunk.filter(utils_1.noUnsafeEventProps).map(mapper); + // Process latest events first + for (const event of matrixEvents.slice().reverse()) { + yield (thread === null || thread === void 0 ? void 0 : thread.processEvent(event)); + const sender = event.getSender(); + if (!backwards || (thread === null || thread === void 0 ? void 0 : thread.getEventReadUpTo(sender)) === null) { + room.addLocalEchoReceipt(sender, event, read_receipts_1.ReceiptType.Read); + } + } + const newToken = res.next_batch; + const timelineSet = eventTimeline.getTimelineSet(); + timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, newToken !== null && newToken !== void 0 ? newToken : null); + if (!newToken && backwards) { + const originalEvent = yield this.fetchRoomEvent((_d = eventTimeline.getRoomId()) !== null && _d !== void 0 ? _d : "", thread.id); + timelineSet.addEventsToTimeline([mapper(originalEvent)], true, eventTimeline, null); + } + this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents); + // if we've hit the end of the timeline, we need to stop trying to + // paginate. We need to keep the 'forwards' token though, to make sure + // we can recover from gappy syncs. + if (backwards && !newToken) { + eventTimeline.setPaginationToken(null, dir); + } + return Boolean(newToken); + })) + .finally(() => { + eventTimeline.paginationRequests[dir] = null; + }); + eventTimeline.paginationRequests[dir] = promise; + } + else { + if (!room) { + throw new Error("Unknown room " + eventTimeline.getRoomId()); + } + promise = this.createMessagesRequest(eventTimeline.getRoomId(), token, opts.limit, dir, eventTimeline.getFilter()) + .then((res) => { + if (res.state) { + const roomState = eventTimeline.getState(dir); + const stateEvents = res.state.filter(utils_1.noUnsafeEventProps).map(this.getEventMapper()); + roomState.setUnknownStateEvents(stateEvents); + } + const token = res.end; + const matrixEvents = res.chunk.filter(utils_1.noUnsafeEventProps).map(this.getEventMapper()); + const timelineSet = eventTimeline.getTimelineSet(); + const [timelineEvents] = room.partitionThreadedEvents(matrixEvents); + timelineSet.addEventsToTimeline(timelineEvents, backwards, eventTimeline, token); + this.processAggregatedTimelineEvents(room, timelineEvents); + this.processThreadRoots(room, timelineEvents.filter((it) => it.getServerAggregatedRelation(thread_1.THREAD_RELATION_TYPE.name)), false); + const atEnd = res.end === undefined || res.end === res.start; + // if we've hit the end of the timeline, we need to stop trying to + // paginate. We need to keep the 'forwards' token though, to make sure + // we can recover from gappy syncs. + if (backwards && atEnd) { + eventTimeline.setPaginationToken(null, dir); + } + return !atEnd; + }) + .finally(() => { + eventTimeline.paginationRequests[dir] = null; + }); + eventTimeline.paginationRequests[dir] = promise; + } + return promise; + } + /** + * Reset the notifTimelineSet entirely, paginating in some historical notifs as + * a starting point for subsequent pagination. + */ + resetNotifTimelineSet() { + if (!this.notifTimelineSet) { + return; + } + // FIXME: This thing is a total hack, and results in duplicate events being + // added to the timeline both from /sync and /notifications, and lots of + // slow and wasteful processing and pagination. The correct solution is to + // extend /messages or /search or something to filter on notifications. + // use the fictitious token 'end'. in practice we would ideally give it + // the oldest backwards pagination token from /sync, but /sync doesn't + // know about /notifications, so we have no choice but to start paginating + // from the current point in time. This may well overlap with historical + // notifs which are then inserted into the timeline by /sync responses. + this.notifTimelineSet.resetLiveTimeline("end"); + // we could try to paginate a single event at this point in order to get + // a more valid pagination token, but it just ends up with an out of order + // timeline. given what a mess this is and given we're going to have duplicate + // events anyway, just leave it with the dummy token for now. + /* + this.paginateNotifTimeline(this._notifTimelineSet.getLiveTimeline(), { + backwards: true, + limit: 1 + }); + */ + } + /** + * Peek into a room and receive updates about the room. This only works if the + * history visibility for the room is world_readable. + * @param roomId - The room to attempt to peek into. + * @returns Promise which resolves: Room object + * @returns Rejects: with an error response. + */ + peekInRoom(roomId) { + var _a; + (_a = this.peekSync) === null || _a === void 0 ? void 0 : _a.stopPeeking(); + this.peekSync = new sync_1.SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); + return this.peekSync.peek(roomId); + } + /** + * Stop any ongoing room peeking. + */ + stopPeeking() { + if (this.peekSync) { + this.peekSync.stopPeeking(); + this.peekSync = null; + } + } + /** + * Set r/w flags for guest access in a room. + * @param roomId - The room to configure guest access in. + * @param opts - Options + * @returns Promise which resolves + * @returns Rejects: with an error response. + */ + setGuestAccess(roomId, opts) { + const writePromise = this.sendStateEvent(roomId, event_2.EventType.RoomGuestAccess, { + guest_access: opts.allowJoin ? "can_join" : "forbidden", + }, ""); + let readPromise = Promise.resolve(undefined); + if (opts.allowRead) { + readPromise = this.sendStateEvent(roomId, event_2.EventType.RoomHistoryVisibility, { + history_visibility: "world_readable", + }, ""); + } + return Promise.all([readPromise, writePromise]).then(); // .then() to hide results for contract + } + /** + * Requests an email verification token for the purposes of registration. + * This API requests a token from the homeserver. + * The doesServerRequireIdServerParam() method can be used to determine if + * the server requires the id_server parameter to be provided. + * + * Parameters and return value are as for requestEmailToken + + * @param email - As requestEmailToken + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestRegisterEmailToken(email, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/register/email/requestToken", { + email: email, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Requests a text message verification token for the purposes of registration. + * This API requests a token from the homeserver. + * The doesServerRequireIdServerParam() method can be used to determine if + * the server requires the id_server parameter to be provided. + * + * @param phoneCountry - The ISO 3166-1 alpha-2 code for the country in which + * phoneNumber should be parsed relative to. + * @param phoneNumber - The phone number, in national or international format + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestRegisterMsisdnToken(phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/register/msisdn/requestToken", { + country: phoneCountry, + phone_number: phoneNumber, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Requests an email verification token for the purposes of adding a + * third party identifier to an account. + * This API requests a token from the homeserver. + * The doesServerRequireIdServerParam() method can be used to determine if + * the server requires the id_server parameter to be provided. + * If an account with the given email address already exists and is + * associated with an account other than the one the user is authed as, + * it will either send an email to the address informing them of this + * or return M_THREEPID_IN_USE (which one is up to the homeserver). + * + * @param email - As requestEmailToken + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestAdd3pidEmailToken(email, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/account/3pid/email/requestToken", { + email: email, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Requests a text message verification token for the purposes of adding a + * third party identifier to an account. + * This API proxies the identity server /validate/email/requestToken API, + * adding specific behaviour for the addition of phone numbers to an + * account, as requestAdd3pidEmailToken. + * + * @param phoneCountry - As requestRegisterMsisdnToken + * @param phoneNumber - As requestRegisterMsisdnToken + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestAdd3pidMsisdnToken(phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/account/3pid/msisdn/requestToken", { + country: phoneCountry, + phone_number: phoneNumber, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Requests an email verification token for the purposes of resetting + * the password on an account. + * This API proxies the identity server /validate/email/requestToken API, + * adding specific behaviour for the password resetting. Specifically, + * if no account with the given email address exists, it may either + * return M_THREEPID_NOT_FOUND or send an email + * to the address informing them of this (which one is up to the homeserver). + * + * requestEmailToken calls the equivalent API directly on the identity server, + * therefore bypassing the password reset specific logic. + * + * @param email - As requestEmailToken + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestPasswordEmailToken(email, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/account/password/email/requestToken", { + email: email, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Requests a text message verification token for the purposes of resetting + * the password on an account. + * This API proxies the identity server /validate/email/requestToken API, + * adding specific behaviour for the password resetting, as requestPasswordEmailToken. + * + * @param phoneCountry - As requestRegisterMsisdnToken + * @param phoneNumber - As requestRegisterMsisdnToken + * @param clientSecret - As requestEmailToken + * @param sendAttempt - As requestEmailToken + * @param nextLink - As requestEmailToken + * @returns Promise which resolves: As requestEmailToken + */ + requestPasswordMsisdnToken(phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) { + return this.requestTokenFromEndpoint("/account/password/msisdn/requestToken", { + country: phoneCountry, + phone_number: phoneNumber, + client_secret: clientSecret, + send_attempt: sendAttempt, + next_link: nextLink, + }); + } + /** + * Internal utility function for requesting validation tokens from usage-specific + * requestToken endpoints. + * + * @param endpoint - The endpoint to send the request to + * @param params - Parameters for the POST request + * @returns Promise which resolves: As requestEmailToken + */ + requestTokenFromEndpoint(endpoint, params) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const postParams = Object.assign({}, params); + // If the HS supports separate add and bind, then requestToken endpoints + // don't need an IS as they are all validated by the HS directly. + if (!(yield this.doesServerSupportSeparateAddAndBind()) && this.idBaseUrl) { + const idServerUrl = new URL(this.idBaseUrl); + postParams.id_server = idServerUrl.host; + if (((_a = this.identityServer) === null || _a === void 0 ? void 0 : _a.getAccessToken) && (yield this.doesServerAcceptIdentityAccessToken())) { + const identityAccessToken = yield this.identityServer.getAccessToken(); + if (identityAccessToken) { + postParams.id_access_token = identityAccessToken; + } + } + } + return this.http.request(http_api_1.Method.Post, endpoint, undefined, postParams); + }); + } + /** + * Get the room-kind push rule associated with a room. + * @param scope - "global" or device-specific. + * @param roomId - the id of the room. + * @returns the rule or undefined. + */ + getRoomPushRule(scope, roomId) { + var _a, _b; + // There can be only room-kind push rule per room + // and its id is the room id. + if (this.pushRules) { + return (_b = (_a = this.pushRules[scope]) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.find((rule) => rule.rule_id === roomId); + } + else { + throw new Error("SyncApi.sync() must be done before accessing to push rules."); + } + } + /** + * Set a room-kind muting push rule in a room. + * The operation also updates MatrixClient.pushRules at the end. + * @param scope - "global" or device-specific. + * @param roomId - the id of the room. + * @param mute - the mute state. + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + setRoomMutePushRule(scope, roomId, mute) { + let promise; + let hasDontNotifyRule = false; + // Get the existing room-kind push rule if any + const roomPushRule = this.getRoomPushRule(scope, roomId); + if (roomPushRule === null || roomPushRule === void 0 ? void 0 : roomPushRule.actions.includes(PushRules_1.PushRuleActionName.DontNotify)) { + hasDontNotifyRule = true; + } + if (!mute) { + // Remove the rule only if it is a muting rule + if (hasDontNotifyRule) { + promise = this.deletePushRule(scope, PushRules_1.PushRuleKind.RoomSpecific, roomPushRule.rule_id); + } + } + else { + if (!roomPushRule) { + promise = this.addPushRule(scope, PushRules_1.PushRuleKind.RoomSpecific, roomId, { + actions: [PushRules_1.PushRuleActionName.DontNotify], + }); + } + else if (!hasDontNotifyRule) { + // Remove the existing one before setting the mute push rule + // This is a workaround to SYN-590 (Push rule update fails) + const deferred = utils.defer(); + this.deletePushRule(scope, PushRules_1.PushRuleKind.RoomSpecific, roomPushRule.rule_id) + .then(() => { + this.addPushRule(scope, PushRules_1.PushRuleKind.RoomSpecific, roomId, { + actions: [PushRules_1.PushRuleActionName.DontNotify], + }) + .then(() => { + deferred.resolve(); + }) + .catch((err) => { + deferred.reject(err); + }); + }) + .catch((err) => { + deferred.reject(err); + }); + promise = deferred.promise; + } + } + if (promise) { + return new Promise((resolve, reject) => { + // Update this.pushRules when the operation completes + promise + .then(() => { + this.getPushRules() + .then((result) => { + this.pushRules = result; + resolve(); + }) + .catch((err) => { + reject(err); + }); + }) + .catch((err) => { + // Update it even if the previous operation fails. This can help the + // app to recover when push settings has been modified from another client + this.getPushRules() + .then((result) => { + this.pushRules = result; + reject(err); + }) + .catch((err2) => { + reject(err); + }); + }); + }); + } + } + searchMessageText(opts) { + const roomEvents = { + search_term: opts.query, + }; + if ("keys" in opts) { + roomEvents.keys = opts.keys; + } + return this.search({ + body: { + search_categories: { + room_events: roomEvents, + }, + }, + }); + } + /** + * Perform a server-side search for room events. + * + * The returned promise resolves to an object containing the fields: + * + * * count: estimate of the number of results + * * next_batch: token for back-pagination; if undefined, there are no more results + * * highlights: a list of words to highlight from the stemming algorithm + * * results: a list of results + * + * Each entry in the results list is a SearchResult. + * + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + searchRoomEvents(opts) { + // TODO: support search groups + const body = { + search_categories: { + room_events: { + search_term: opts.term, + filter: opts.filter, + order_by: search_1.SearchOrderBy.Recent, + event_context: { + before_limit: 1, + after_limit: 1, + include_profile: true, + }, + }, + }, + }; + const searchResults = { + _query: body, + results: [], + highlights: [], + }; + return this.search({ body: body }).then((res) => this.processRoomEventsSearch(searchResults, res)); + } + /** + * Take a result from an earlier searchRoomEvents call, and backfill results. + * + * @param searchResults - the results object to be updated + * @returns Promise which resolves: updated result object + * @returns Rejects: with an error response. + */ + backPaginateRoomEventsSearch(searchResults) { + // TODO: we should implement a backoff (as per scrollback()) to deal more + // nicely with HTTP errors. + if (!searchResults.next_batch) { + return Promise.reject(new Error("Cannot backpaginate event search any further")); + } + if (searchResults.pendingRequest) { + // already a request in progress - return the existing promise + return searchResults.pendingRequest; + } + const searchOpts = { + body: searchResults._query, + next_batch: searchResults.next_batch, + }; + const promise = this.search(searchOpts, searchResults.abortSignal) + .then((res) => this.processRoomEventsSearch(searchResults, res)) + .finally(() => { + searchResults.pendingRequest = undefined; + }); + searchResults.pendingRequest = promise; + return promise; + } + /** + * helper for searchRoomEvents and backPaginateRoomEventsSearch. Processes the + * response from the API call and updates the searchResults + * + * @returns searchResults + * @internal + */ + // XXX: Intended private, used in code + processRoomEventsSearch(searchResults, response) { + var _a, _b; + const roomEvents = response.search_categories.room_events; + searchResults.count = roomEvents.count; + searchResults.next_batch = roomEvents.next_batch; + // combine the highlight list with our existing list; + const highlights = new Set(roomEvents.highlights); + searchResults.highlights.forEach((hl) => { + highlights.add(hl); + }); + // turn it back into a list. + searchResults.highlights = Array.from(highlights); + const mapper = this.getEventMapper(); + // append the new results to our existing results + const resultsLength = (_b = (_a = roomEvents.results) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0; + for (let i = 0; i < resultsLength; i++) { + const sr = search_result_1.SearchResult.fromJson(roomEvents.results[i], mapper); + const room = this.getRoom(sr.context.getEvent().getRoomId()); + if (room) { + // Copy over a known event sender if we can + for (const ev of sr.context.getTimeline()) { + const sender = room.getMember(ev.getSender()); + if (!ev.sender && sender) + ev.sender = sender; + } + } + searchResults.results.push(sr); + } + return searchResults; + } + /** + * Populate the store with rooms the user has left. + * @returns Promise which resolves: TODO - Resolved when the rooms have + * been added to the data store. + * @returns Rejects: with an error response. + */ + syncLeftRooms() { + // Guard against multiple calls whilst ongoing and multiple calls post success + if (this.syncedLeftRooms) { + return Promise.resolve([]); // don't call syncRooms again if it succeeded. + } + if (this.syncLeftRoomsPromise) { + return this.syncLeftRoomsPromise; // return the ongoing request + } + const syncApi = new sync_1.SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); + this.syncLeftRoomsPromise = syncApi.syncLeftRooms(); + // cleanup locks + this.syncLeftRoomsPromise + .then(() => { + logger_1.logger.log("Marking success of sync left room request"); + this.syncedLeftRooms = true; // flip the bit on success + }) + .finally(() => { + this.syncLeftRoomsPromise = undefined; // cleanup ongoing request state + }); + return this.syncLeftRoomsPromise; + } + /** + * Create a new filter. + * @param content - The HTTP body for the request + * @returns Promise which resolves to a Filter object. + * @returns Rejects: with an error response. + */ + createFilter(content) { + const path = utils.encodeUri("/user/$userId/filter", { + $userId: this.credentials.userId, + }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, content).then((response) => { + // persist the filter + const filter = filter_1.Filter.fromJson(this.credentials.userId, response.filter_id, content); + this.store.storeFilter(filter); + return filter; + }); + } + /** + * Retrieve a filter. + * @param userId - The user ID of the filter owner + * @param filterId - The filter ID to retrieve + * @param allowCached - True to allow cached filters to be returned. + * Default: True. + * @returns Promise which resolves: a Filter object + * @returns Rejects: with an error response. + */ + getFilter(userId, filterId, allowCached) { + if (allowCached) { + const filter = this.store.getFilter(userId, filterId); + if (filter) { + return Promise.resolve(filter); + } + } + const path = utils.encodeUri("/user/$userId/filter/$filterId", { + $userId: userId, + $filterId: filterId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path).then((response) => { + // persist the filter + const filter = filter_1.Filter.fromJson(userId, filterId, response); + this.store.storeFilter(filter); + return filter; + }); + } + /** + * @returns Filter ID + */ + getOrCreateFilter(filterName, filter) { + return __awaiter(this, void 0, void 0, function* () { + const filterId = this.store.getFilterIdByName(filterName); + let existingId; + if (filterId) { + // check that the existing filter matches our expectations + try { + const existingFilter = yield this.getFilter(this.credentials.userId, filterId, true); + if (existingFilter) { + const oldDef = existingFilter.getDefinition(); + const newDef = filter.getDefinition(); + if (utils.deepCompare(oldDef, newDef)) { + // super, just use that. + // debuglog("Using existing filter ID %s: %s", filterId, + // JSON.stringify(oldDef)); + existingId = filterId; + } + } + } + catch (error) { + // Synapse currently returns the following when the filter cannot be found: + // { + // errcode: "M_UNKNOWN", + // name: "M_UNKNOWN", + // message: "No row found", + // } + if (error.errcode !== "M_UNKNOWN" && error.errcode !== "M_NOT_FOUND") { + throw error; + } + } + // if the filter doesn't exist anymore on the server, remove from store + if (!existingId) { + this.store.setFilterIdByName(filterName, undefined); + } + } + if (existingId) { + return existingId; + } + // create a new filter + const createdFilter = yield this.createFilter(filter.getDefinition()); + this.store.setFilterIdByName(filterName, createdFilter.filterId); + return createdFilter.filterId; + }); + } + /** + * Gets a bearer token from the homeserver that the user can + * present to a third party in order to prove their ownership + * of the Matrix account they are logged into. + * @returns Promise which resolves: Token object + * @returns Rejects: with an error response. + */ + getOpenIdToken() { + const path = utils.encodeUri("/user/$userId/openid/request_token", { + $userId: this.credentials.userId, + }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, {}); + } + /** + * @returns Promise which resolves: ITurnServerResponse object + * @returns Rejects: with an error response. + */ + turnServer() { + return this.http.authedRequest(http_api_1.Method.Get, "/voip/turnServer"); + } + /** + * Get the TURN servers for this homeserver. + * @returns The servers or an empty list. + */ + getTurnServers() { + return this.turnServers || []; + } + /** + * Get the unix timestamp (in milliseconds) at which the current + * TURN credentials (from getTurnServers) expire + * @returns The expiry timestamp in milliseconds + */ + getTurnServersExpiry() { + return this.turnServersExpiry; + } + get pollingTurnServers() { + return this.checkTurnServersIntervalID !== undefined; + } + // XXX: Intended private, used in code. + checkTurnServers() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.canSupportVoip) { + return; + } + let credentialsGood = false; + const remainingTime = this.turnServersExpiry - Date.now(); + if (remainingTime > TURN_CHECK_INTERVAL) { + logger_1.logger.debug("TURN creds are valid for another " + remainingTime + " ms: not fetching new ones."); + credentialsGood = true; + } + else { + logger_1.logger.debug("Fetching new TURN credentials"); + try { + const res = yield this.turnServer(); + if (res.uris) { + logger_1.logger.log("Got TURN URIs: " + res.uris + " refresh in " + res.ttl + " secs"); + // map the response to a format that can be fed to RTCPeerConnection + const servers = { + urls: res.uris, + username: res.username, + credential: res.password, + }; + this.turnServers = [servers]; + // The TTL is in seconds but we work in ms + this.turnServersExpiry = Date.now() + res.ttl * 1000; + credentialsGood = true; + this.emit(ClientEvent.TurnServers, this.turnServers); + } + } + catch (err) { + logger_1.logger.error("Failed to get TURN URIs", err); + if (err.httpStatus === 403) { + // We got a 403, so there's no point in looping forever. + logger_1.logger.info("TURN access unavailable for this account: stopping credentials checks"); + if (this.checkTurnServersIntervalID !== null) + global.clearInterval(this.checkTurnServersIntervalID); + this.checkTurnServersIntervalID = undefined; + this.emit(ClientEvent.TurnServersError, err, true); // fatal + } + else { + // otherwise, if we failed for whatever reason, try again the next time we're called. + this.emit(ClientEvent.TurnServersError, err, false); // non-fatal + } + } + } + return credentialsGood; + }); + } + /** + * Set whether to allow a fallback ICE server should be used for negotiating a + * WebRTC connection if the homeserver doesn't provide any servers. Defaults to + * false. + * + */ + setFallbackICEServerAllowed(allow) { + this.fallbackICEServerAllowed = allow; + } + /** + * Get whether to allow a fallback ICE server should be used for negotiating a + * WebRTC connection if the homeserver doesn't provide any servers. Defaults to + * false. + * + * @returns + */ + isFallbackICEServerAllowed() { + return this.fallbackICEServerAllowed; + } + /** + * Determines if the current user is an administrator of the Synapse homeserver. + * Returns false if untrue or the homeserver does not appear to be a Synapse + * homeserver. This function is implementation specific and may change + * as a result. + * @returns true if the user appears to be a Synapse administrator. + */ + isSynapseAdministrator() { + const path = utils.encodeUri("/_synapse/admin/v1/users/$userId/admin", { $userId: this.getUserId() }); + return this.http + .authedRequest(http_api_1.Method.Get, path, undefined, undefined, { prefix: "" }) + .then((r) => r.admin); // pull out the specific boolean we want + } + /** + * Performs a whois lookup on a user using Synapse's administrator API. + * This function is implementation specific and may change as a + * result. + * @param userId - the User ID to look up. + * @returns the whois response - see Synapse docs for information. + */ + whoisSynapseUser(userId) { + const path = utils.encodeUri("/_synapse/admin/v1/whois/$userId", { $userId: userId }); + return this.http.authedRequest(http_api_1.Method.Get, path, undefined, undefined, { prefix: "" }); + } + /** + * Deactivates a user using Synapse's administrator API. This + * function is implementation specific and may change as a result. + * @param userId - the User ID to deactivate. + * @returns the deactivate response - see Synapse docs for information. + */ + deactivateSynapseUser(userId) { + const path = utils.encodeUri("/_synapse/admin/v1/deactivate/$userId", { $userId: userId }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, undefined, { prefix: "" }); + } + fetchClientWellKnown() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // `getRawClientConfig` does not throw or reject on network errors, instead + // it absorbs errors and returns `{}`. + this.clientWellKnownPromise = autodiscovery_1.AutoDiscovery.getRawClientConfig((_a = this.getDomain()) !== null && _a !== void 0 ? _a : undefined); + this.clientWellKnown = yield this.clientWellKnownPromise; + this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown); + }); + } + getClientWellKnown() { + return this.clientWellKnown; + } + waitForClientWellKnown() { + if (!this.clientRunning) { + throw new Error("Client is not running"); + } + return this.clientWellKnownPromise; + } + /** + * store client options with boolean/string/numeric values + * to know in the next session what flags the sync data was + * created with (e.g. lazy loading) + * @param opts - the complete set of client options + * @returns for store operation + */ + storeClientOptions() { + // XXX: Intended private, used in code + const primTypes = ["boolean", "string", "number"]; + const serializableOpts = Object.entries(this.clientOpts) + .filter(([key, value]) => { + return primTypes.includes(typeof value); + }) + .reduce((obj, [key, value]) => { + obj[key] = value; + return obj; + }, {}); + return this.store.storeClientOptions(serializableOpts); + } + /** + * Gets a set of room IDs in common with another user + * @param userId - The userId to check. + * @returns Promise which resolves to a set of rooms + * @returns Rejects: with an error response. + */ + // eslint-disable-next-line + _unstable_getSharedRooms(userId) { + return __awaiter(this, void 0, void 0, function* () { + const sharedRoomsSupport = yield this.doesServerSupportUnstableFeature("uk.half-shot.msc2666"); + const mutualRoomsSupport = yield this.doesServerSupportUnstableFeature("uk.half-shot.msc2666.mutual_rooms"); + if (!sharedRoomsSupport && !mutualRoomsSupport) { + throw Error("Server does not support mutual_rooms API"); + } + const path = utils.encodeUri(`/uk.half-shot.msc2666/user/${mutualRoomsSupport ? "mutual_rooms" : "shared_rooms"}/$userId`, { $userId: userId }); + const res = yield this.http.authedRequest(http_api_1.Method.Get, path, undefined, undefined, { + prefix: http_api_1.ClientPrefix.Unstable, + }); + return res.joined; + }); + } + /** + * Get the API versions supported by the server, along with any + * unstable APIs it supports + * @returns The server /versions response + */ + getVersions() { + return __awaiter(this, void 0, void 0, function* () { + if (this.serverVersionsPromise) { + return this.serverVersionsPromise; + } + this.serverVersionsPromise = this.http + .request(http_api_1.Method.Get, "/_matrix/client/versions", undefined, // queryParams + undefined, // data + { + prefix: "", + }) + .catch((e) => { + // Need to unset this if it fails, otherwise we'll never retry + this.serverVersionsPromise = undefined; + // but rethrow the exception to anything that was waiting + throw e; + }); + const serverVersions = yield this.serverVersionsPromise; + this.canSupport = yield (0, feature_1.buildFeatureSupportMap)(serverVersions); + return this.serverVersionsPromise; + }); + } + /** + * Check if a particular spec version is supported by the server. + * @param version - The spec version (such as "r0.5.0") to check for. + * @returns Whether it is supported + */ + isVersionSupported(version) { + return __awaiter(this, void 0, void 0, function* () { + const { versions } = yield this.getVersions(); + return versions && versions.includes(version); + }); + } + /** + * Query the server to see if it supports members lazy loading + * @returns true if server supports lazy loading + */ + doesServerSupportLazyLoading() { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return false; + const versions = response["versions"]; + const unstableFeatures = response["unstable_features"]; + return ((versions && versions.includes("r0.5.0")) || (unstableFeatures && unstableFeatures["m.lazy_load_members"])); + }); + } + /** + * Query the server to see if the `id_server` parameter is required + * when registering with an 3pid, adding a 3pid or resetting password. + * @returns true if id_server parameter is required + */ + doesServerRequireIdServerParam() { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return true; + const versions = response["versions"]; + // Supporting r0.6.0 is the same as having the flag set to false + if (versions && versions.includes("r0.6.0")) { + return false; + } + const unstableFeatures = response["unstable_features"]; + if (!unstableFeatures) + return true; + if (unstableFeatures["m.require_identity_server"] === undefined) { + return true; + } + else { + return unstableFeatures["m.require_identity_server"]; + } + }); + } + /** + * Query the server to see if the `id_access_token` parameter can be safely + * passed to the homeserver. Some homeservers may trigger errors if they are not + * prepared for the new parameter. + * @returns true if id_access_token can be sent + */ + doesServerAcceptIdentityAccessToken() { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return false; + const versions = response["versions"]; + const unstableFeatures = response["unstable_features"]; + return (versions && versions.includes("r0.6.0")) || (unstableFeatures && unstableFeatures["m.id_access_token"]); + }); + } + /** + * Query the server to see if it supports separate 3PID add and bind functions. + * This affects the sequence of API calls clients should use for these operations, + * so it's helpful to be able to check for support. + * @returns true if separate functions are supported + */ + doesServerSupportSeparateAddAndBind() { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return false; + const versions = response["versions"]; + const unstableFeatures = response["unstable_features"]; + return (versions === null || versions === void 0 ? void 0 : versions.includes("r0.6.0")) || (unstableFeatures === null || unstableFeatures === void 0 ? void 0 : unstableFeatures["m.separate_add_and_bind"]); + }); + } + /** + * Query the server to see if it lists support for an unstable feature + * in the /versions response + * @param feature - the feature name + * @returns true if the feature is supported + */ + doesServerSupportUnstableFeature(feature) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return false; + const unstableFeatures = response["unstable_features"]; + return unstableFeatures && !!unstableFeatures[feature]; + }); + } + /** + * Query the server to see if it is forcing encryption to be enabled for + * a given room preset, based on the /versions response. + * @param presetName - The name of the preset to check. + * @returns true if the server is forcing encryption + * for the preset. + */ + doesServerForceEncryptionForPreset(presetName) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.getVersions(); + if (!response) + return false; + const unstableFeatures = response["unstable_features"]; + // The preset name in the versions response will be without the _chat suffix. + const versionsPresetName = presetName.includes("_chat") + ? presetName.substring(0, presetName.indexOf("_chat")) + : presetName; + return unstableFeatures && !!unstableFeatures[`io.element.e2ee_forced.${versionsPresetName}`]; + }); + } + doesServerSupportThread() { + return __awaiter(this, void 0, void 0, function* () { + if (yield this.isVersionSupported("v1.4")) { + return { + threads: thread_1.FeatureSupport.Stable, + list: thread_1.FeatureSupport.Stable, + fwdPagination: thread_1.FeatureSupport.Stable, + }; + } + try { + const [threadUnstable, threadStable, listUnstable, listStable, fwdPaginationUnstable, fwdPaginationStable] = yield Promise.all([ + this.doesServerSupportUnstableFeature("org.matrix.msc3440"), + this.doesServerSupportUnstableFeature("org.matrix.msc3440.stable"), + this.doesServerSupportUnstableFeature("org.matrix.msc3856"), + this.doesServerSupportUnstableFeature("org.matrix.msc3856.stable"), + this.doesServerSupportUnstableFeature("org.matrix.msc3715"), + this.doesServerSupportUnstableFeature("org.matrix.msc3715.stable"), + ]); + return { + threads: (0, thread_1.determineFeatureSupport)(threadStable, threadUnstable), + list: (0, thread_1.determineFeatureSupport)(listStable, listUnstable), + fwdPagination: (0, thread_1.determineFeatureSupport)(fwdPaginationStable, fwdPaginationUnstable), + }; + } + catch (e) { + return { + threads: thread_1.FeatureSupport.None, + list: thread_1.FeatureSupport.None, + fwdPagination: thread_1.FeatureSupport.None, + }; + } + }); + } + /** + * Query the server to see if it supports the MSC2457 `logout_devices` parameter when setting password + * @returns true if server supports the `logout_devices` parameter + */ + doesServerSupportLogoutDevices() { + return this.isVersionSupported("r0.6.1"); + } + /** + * Get if lazy loading members is being used. + * @returns Whether or not members are lazy loaded by this client + */ + hasLazyLoadMembersEnabled() { + var _a; + return !!((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.lazyLoadMembers); + } + /** + * Set a function which is called when /sync returns a 'limited' response. + * It is called with a room ID and returns a boolean. It should return 'true' if the SDK + * can SAFELY remove events from this room. It may not be safe to remove events if there + * are other references to the timelines for this room, e.g because the client is + * actively viewing events in this room. + * Default: returns false. + * @param cb - The callback which will be invoked. + */ + setCanResetTimelineCallback(cb) { + this.canResetTimelineCallback = cb; + } + /** + * Get the callback set via `setCanResetTimelineCallback`. + * @returns The callback or null + */ + getCanResetTimelineCallback() { + return this.canResetTimelineCallback; + } + /** + * Returns relations for a given event. Handles encryption transparently, + * with the caveat that the amount of events returned might be 0, even though you get a nextBatch. + * When the returned promise resolves, all messages should have finished trying to decrypt. + * @param roomId - the room of the event + * @param eventId - the id of the event + * @param relationType - the rel_type of the relations requested + * @param eventType - the event type of the relations requested + * @param opts - options with optional values for the request. + * @returns an object with `events` as `MatrixEvent[]` and optionally `nextBatch` if more relations are available. + */ + relations(roomId, eventId, relationType, eventType, opts = { dir: event_timeline_1.Direction.Backward }) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const fetchedEventType = eventType ? this.getEncryptedIfNeededEventType(roomId, eventType) : null; + const [eventResult, result] = yield Promise.all([ + this.fetchRoomEvent(roomId, eventId), + this.fetchRelations(roomId, eventId, relationType, fetchedEventType, opts), + ]); + const mapper = this.getEventMapper(); + const originalEvent = eventResult ? mapper(eventResult) : undefined; + let events = result.chunk.map(mapper); + if (fetchedEventType === event_2.EventType.RoomMessageEncrypted) { + const allEvents = originalEvent ? events.concat(originalEvent) : events; + yield Promise.all(allEvents.map((e) => this.decryptEventIfNeeded(e))); + if (eventType !== null) { + events = events.filter((e) => e.getType() === eventType); + } + } + if (originalEvent && relationType === event_2.RelationType.Replace) { + events = events.filter((e) => e.getSender() === originalEvent.getSender()); + } + return { + originalEvent: originalEvent !== null && originalEvent !== void 0 ? originalEvent : null, + events, + nextBatch: (_a = result.next_batch) !== null && _a !== void 0 ? _a : null, + prevBatch: (_b = result.prev_batch) !== null && _b !== void 0 ? _b : null, + }; + }); + } + /** + * The app may wish to see if we have a key cached without + * triggering a user interaction. + */ + getCrossSigningCacheCallbacks() { + var _a; + // XXX: Private member access + return (_a = this.crypto) === null || _a === void 0 ? void 0 : _a.crossSigningInfo.getCacheCallbacks(); + } + /** + * Generates a random string suitable for use as a client secret. This + * method is experimental and may change. + * @returns A new client secret + */ + generateClientSecret() { + return (0, randomstring_1.randomString)(32); + } + /** + * Attempts to decrypt an event + * @param event - The event to decrypt + * @returns A decryption promise + */ + decryptEventIfNeeded(event, options) { + if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) { + event.attemptDecryption(this.cryptoBackend, options); + } + if (event.isBeingDecrypted()) { + return event.getDecryptionPromise(); + } + else { + return Promise.resolve(); + } + } + termsUrlForService(serviceType, baseUrl) { + switch (serviceType) { + case service_types_1.SERVICE_TYPES.IS: + return this.http.getUrl("/terms", undefined, http_api_1.IdentityPrefix.V2, baseUrl); + case service_types_1.SERVICE_TYPES.IM: + return this.http.getUrl("/terms", undefined, "/_matrix/integrations/v1", baseUrl); + default: + throw new Error("Unsupported service type"); + } + } + /** + * Get the Homeserver URL of this client + * @returns Homeserver URL of this client + */ + getHomeserverUrl() { + return this.baseUrl; + } + /** + * Get the identity server URL of this client + * @param stripProto - whether or not to strip the protocol from the URL + * @returns Identity server URL of this client + */ + getIdentityServerUrl(stripProto = false) { + var _a, _b; + if (stripProto && (((_a = this.idBaseUrl) === null || _a === void 0 ? void 0 : _a.startsWith("http://")) || ((_b = this.idBaseUrl) === null || _b === void 0 ? void 0 : _b.startsWith("https://")))) { + return this.idBaseUrl.split("://")[1]; + } + return this.idBaseUrl; + } + /** + * Set the identity server URL of this client + * @param url - New identity server URL + */ + setIdentityServerUrl(url) { + this.idBaseUrl = utils.ensureNoTrailingSlash(url); + this.http.setIdBaseUrl(this.idBaseUrl); + } + /** + * Get the access token associated with this account. + * @returns The access_token or null + */ + getAccessToken() { + return this.http.opts.accessToken || null; + } + /** + * Set the access token associated with this account. + * @param token - The new access token. + */ + setAccessToken(token) { + this.http.opts.accessToken = token; + } + /** + * @returns true if there is a valid access_token for this client. + */ + isLoggedIn() { + return this.http.opts.accessToken !== undefined; + } + /** + * Make up a new transaction id + * + * @returns a new, unique, transaction id + */ + makeTxnId() { + return "m" + new Date().getTime() + "." + this.txnCtr++; + } + /** + * Check whether a username is available prior to registration. An error response + * indicates an invalid/unavailable username. + * @param username - The username to check the availability of. + * @returns Promise which resolves: to boolean of whether the username is available. + */ + isUsernameAvailable(username) { + return this.http + .authedRequest(http_api_1.Method.Get, "/register/available", { username }) + .then((response) => { + return response.available; + }) + .catch((response) => { + if (response.errcode === "M_USER_IN_USE") { + return false; + } + return Promise.reject(response); + }); + } + /** + * @param bindThreepids - Set key 'email' to true to bind any email + * threepid uses during registration in the identity server. Set 'msisdn' to + * true to bind msisdn. + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + register(username, password, sessionId, auth, bindThreepids, guestAccessToken, inhibitLogin) { + // backwards compat + if (bindThreepids === true) { + bindThreepids = { email: true }; + } + else if (bindThreepids === null || bindThreepids === undefined || bindThreepids === false) { + bindThreepids = {}; + } + if (sessionId) { + auth.session = sessionId; + } + const params = { + auth: auth, + refresh_token: true, // always ask for a refresh token - does nothing if unsupported + }; + if (username !== undefined && username !== null) { + params.username = username; + } + if (password !== undefined && password !== null) { + params.password = password; + } + if (bindThreepids.email) { + params.bind_email = true; + } + if (bindThreepids.msisdn) { + params.bind_msisdn = true; + } + if (guestAccessToken !== undefined && guestAccessToken !== null) { + params.guest_access_token = guestAccessToken; + } + if (inhibitLogin !== undefined && inhibitLogin !== null) { + params.inhibit_login = inhibitLogin; + } + // Temporary parameter added to make the register endpoint advertise + // msisdn flows. This exists because there are clients that break + // when given stages they don't recognise. This parameter will cease + // to be necessary once these old clients are gone. + // Only send it if we send any params at all (the password param is + // mandatory, so if we send any params, we'll send the password param) + if (password !== undefined && password !== null) { + params.x_show_msisdn = true; + } + return this.registerRequest(params); + } + /** + * Register a guest account. + * This method returns the auth info needed to create a new authenticated client, + * Remember to call `setGuest(true)` on the (guest-)authenticated client, e.g: + * ```javascript + * const tmpClient = await sdk.createClient(MATRIX_INSTANCE); + * const { user_id, device_id, access_token } = tmpClient.registerGuest(); + * const client = createClient({ + * baseUrl: MATRIX_INSTANCE, + * accessToken: access_token, + * userId: user_id, + * deviceId: device_id, + * }) + * client.setGuest(true); + * ``` + * + * @param body - JSON HTTP body to provide. + * @returns Promise which resolves: JSON object that contains: + * `{ user_id, device_id, access_token, home_server }` + * @returns Rejects: with an error response. + */ + registerGuest({ body } = {}) { + // TODO: Types + return this.registerRequest(body || {}, "guest"); + } + /** + * @param data - parameters for registration request + * @param kind - type of user to register. may be "guest" + * @returns Promise which resolves: to the /register response + * @returns Rejects: with an error response. + */ + registerRequest(data, kind) { + const params = {}; + if (kind) { + params.kind = kind; + } + return this.http.request(http_api_1.Method.Post, "/register", params, data); + } + /** + * Refreshes an access token using a provided refresh token. The refresh token + * must be valid for the current access token known to the client instance. + * + * Note that this function will not cause a logout if the token is deemed + * unknown by the server - the caller is responsible for managing logout + * actions on error. + * @param refreshToken - The refresh token. + * @returns Promise which resolves to the new token. + * @returns Rejects with an error response. + */ + refreshToken(refreshToken) { + return this.http.authedRequest(http_api_1.Method.Post, "/refresh", undefined, { refresh_token: refreshToken }, { + prefix: http_api_1.ClientPrefix.V1, + inhibitLogoutEmit: true, // we don't want to cause logout loops + }); + } + /** + * @returns Promise which resolves to the available login flows + * @returns Rejects: with an error response. + */ + loginFlows() { + return this.http.request(http_api_1.Method.Get, "/login"); + } + /** + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + login(loginType, data) { + // TODO: Types + const loginData = { + type: loginType, + }; + // merge data into loginData + Object.assign(loginData, data); + return this.http + .authedRequest(http_api_1.Method.Post, "/login", undefined, loginData) + .then((response) => { + if (response.access_token && response.user_id) { + this.http.opts.accessToken = response.access_token; + this.credentials = { + userId: response.user_id, + }; + } + return response; + }); + } + /** + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + loginWithPassword(user, password) { + // TODO: Types + return this.login("m.login.password", { + user: user, + password: password, + }); + } + /** + * @param relayState - URL Callback after SAML2 Authentication + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + loginWithSAML2(relayState) { + // TODO: Types + return this.login("m.login.saml2", { + relay_state: relayState, + }); + } + /** + * @param redirectUrl - The URL to redirect to after the HS + * authenticates with CAS. + * @returns The HS URL to hit to begin the CAS login process. + */ + getCasLoginUrl(redirectUrl) { + return this.getSsoLoginUrl(redirectUrl, "cas"); + } + /** + * @param redirectUrl - The URL to redirect to after the HS + * authenticates with the SSO. + * @param loginType - The type of SSO login we are doing (sso or cas). + * Defaults to 'sso'. + * @param idpId - The ID of the Identity Provider being targeted, optional. + * @param action - the SSO flow to indicate to the IdP, optional. + * @returns The HS URL to hit to begin the SSO login process. + */ + getSsoLoginUrl(redirectUrl, loginType = "sso", idpId, action) { + let url = "/login/" + loginType + "/redirect"; + if (idpId) { + url += "/" + idpId; + } + const params = { + redirectUrl, + [SSO_ACTION_PARAM.unstable]: action, + }; + return this.http.getUrl(url, params, http_api_1.ClientPrefix.R0).href; + } + /** + * @param token - Login token previously received from homeserver + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + loginWithToken(token) { + // TODO: Types + return this.login("m.login.token", { + token: token, + }); + } + /** + * Logs out the current session. + * Obviously, further calls that require authorisation should fail after this + * method is called. The state of the MatrixClient object is not affected: + * it is up to the caller to either reset or destroy the MatrixClient after + * this method succeeds. + * @param stopClient - whether to stop the client before calling /logout to prevent invalid token errors. + * @returns Promise which resolves: On success, the empty object `{}` + */ + logout(stopClient = false) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if ((_b = (_a = this.crypto) === null || _a === void 0 ? void 0 : _a.backupManager) === null || _b === void 0 ? void 0 : _b.getKeyBackupEnabled()) { + try { + while ((yield this.crypto.backupManager.backupPendingKeys(200)) > 0) + ; + } + catch (err) { + logger_1.logger.error("Key backup request failed when logging out. Some keys may be missing from backup", err); + } + } + if (stopClient) { + this.stopClient(); + this.http.abort(); + } + return this.http.authedRequest(http_api_1.Method.Post, "/logout"); + }); + } + /** + * Deactivates the logged-in account. + * Obviously, further calls that require authorisation should fail after this + * method is called. The state of the MatrixClient object is not affected: + * it is up to the caller to either reset or destroy the MatrixClient after + * this method succeeds. + * @param auth - Optional. Auth data to supply for User-Interactive auth. + * @param erase - Optional. If set, send as `erase` attribute in the + * JSON request body, indicating whether the account should be erased. Defaults + * to false. + * @returns Promise which resolves: On success, the empty object + */ + deactivateAccount(auth, erase) { + const body = {}; + if (auth) { + body.auth = auth; + } + if (erase !== undefined) { + body.erase = erase; + } + return this.http.authedRequest(http_api_1.Method.Post, "/account/deactivate", undefined, body); + } + /** + * Make a request for an `m.login.token` to be issued as per + * [MSC3882](https://github.com/matrix-org/matrix-spec-proposals/pull/3882). + * The server may require User-Interactive auth. + * Note that this is UNSTABLE and subject to breaking changes without notice. + * @param auth - Optional. Auth data to supply for User-Interactive auth. + * @returns Promise which resolves: On success, the token response + * or UIA auth data. + */ + requestLoginToken(auth) { + const body = { auth }; + return this.http.authedRequest(http_api_1.Method.Post, "/org.matrix.msc3882/login/token", undefined, // no query params + body, { prefix: http_api_1.ClientPrefix.Unstable }); + } + /** + * Get the fallback URL to use for unknown interactive-auth stages. + * + * @param loginType - the type of stage being attempted + * @param authSessionId - the auth session ID provided by the homeserver + * + * @returns HS URL to hit to for the fallback interface + */ + getFallbackAuthUrl(loginType, authSessionId) { + const path = utils.encodeUri("/auth/$loginType/fallback/web", { + $loginType: loginType, + }); + return this.http.getUrl(path, { + session: authSessionId, + }, http_api_1.ClientPrefix.R0).href; + } + /** + * Create a new room. + * @param options - a list of options to pass to the /createRoom API. + * @returns Promise which resolves: `{room_id: {string}}` + * @returns Rejects: with an error response. + */ + createRoom(options) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // eslint-disable-line camelcase + // some valid options include: room_alias_name, visibility, invite + // inject the id_access_token if inviting 3rd party addresses + const invitesNeedingToken = (options.invite_3pid || []).filter((i) => !i.id_access_token); + if (invitesNeedingToken.length > 0 && + ((_a = this.identityServer) === null || _a === void 0 ? void 0 : _a.getAccessToken) && + (yield this.doesServerAcceptIdentityAccessToken())) { + const identityAccessToken = yield this.identityServer.getAccessToken(); + if (identityAccessToken) { + for (const invite of invitesNeedingToken) { + invite.id_access_token = identityAccessToken; + } + } + } + return this.http.authedRequest(http_api_1.Method.Post, "/createRoom", undefined, options); + }); + } + /** + * Fetches relations for a given event + * @param roomId - the room of the event + * @param eventId - the id of the event + * @param relationType - the rel_type of the relations requested + * @param eventType - the event type of the relations requested + * @param opts - options with optional values for the request. + * @returns the response, with chunk, prev_batch and, next_batch. + */ + fetchRelations(roomId, eventId, relationType, eventType, opts = { dir: event_timeline_1.Direction.Backward }) { + let params = opts; + if (thread_1.Thread.hasServerSideFwdPaginationSupport === thread_1.FeatureSupport.Experimental) { + params = (0, utils_1.replaceParam)("dir", "org.matrix.msc3715.dir", params); + } + const queryString = utils.encodeParams(params); + let templatedUrl = "/rooms/$roomId/relations/$eventId"; + if (relationType !== null) { + templatedUrl += "/$relationType"; + if (eventType !== null) { + templatedUrl += "/$eventType"; + } + } + else if (eventType !== null) { + logger_1.logger.warn(`eventType: ${eventType} ignored when fetching + relations as relationType is null`); + eventType = null; + } + const path = utils.encodeUri(templatedUrl + "?" + queryString, { + $roomId: roomId, + $eventId: eventId, + $relationType: relationType, + $eventType: eventType, + }); + return this.http.authedRequest(http_api_1.Method.Get, path, undefined, undefined, { + prefix: http_api_1.ClientPrefix.V1, + }); + } + /** + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + roomState(roomId) { + const path = utils.encodeUri("/rooms/$roomId/state", { $roomId: roomId }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Get an event in a room by its event id. + * + * @returns Promise which resolves to an object containing the event. + * @returns Rejects: with an error response. + */ + fetchRoomEvent(roomId, eventId) { + const path = utils.encodeUri("/rooms/$roomId/event/$eventId", { + $roomId: roomId, + $eventId: eventId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @param includeMembership - the membership type to include in the response + * @param excludeMembership - the membership type to exclude from the response + * @param atEventId - the id of the event for which moment in the timeline the members should be returned for + * @returns Promise which resolves: dictionary of userid to profile information + * @returns Rejects: with an error response. + */ + members(roomId, includeMembership, excludeMembership, atEventId) { + const queryParams = {}; + if (includeMembership) { + queryParams.membership = includeMembership; + } + if (excludeMembership) { + queryParams.not_membership = excludeMembership; + } + if (atEventId) { + queryParams.at = atEventId; + } + const queryString = utils.encodeParams(queryParams); + const path = utils.encodeUri("/rooms/$roomId/members?" + queryString, { $roomId: roomId }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Upgrades a room to a new protocol version + * @param newVersion - The target version to upgrade to + * @returns Promise which resolves: Object with key 'replacement_room' + * @returns Rejects: with an error response. + */ + upgradeRoom(roomId, newVersion) { + // eslint-disable-line camelcase + const path = utils.encodeUri("/rooms/$roomId/upgrade", { $roomId: roomId }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, { new_version: newVersion }); + } + /** + * Retrieve a state event. + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + getStateEvent(roomId, eventType, stateKey) { + const pathParams = { + $roomId: roomId, + $eventType: eventType, + $stateKey: stateKey, + }; + let path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams); + if (stateKey !== undefined) { + path = utils.encodeUri(path + "/$stateKey", pathParams); + } + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @param opts - Options for the request function. + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + sendStateEvent(roomId, eventType, content, stateKey = "", opts = {}) { + const pathParams = { + $roomId: roomId, + $eventType: eventType, + $stateKey: stateKey, + }; + let path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams); + if (stateKey !== undefined) { + path = utils.encodeUri(path + "/$stateKey", pathParams); + } + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, content, opts); + } + /** + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + roomInitialSync(roomId, limit) { + var _a; + const path = utils.encodeUri("/rooms/$roomId/initialSync", { $roomId: roomId }); + return this.http.authedRequest(http_api_1.Method.Get, path, { limit: (_a = limit === null || limit === void 0 ? void 0 : limit.toString()) !== null && _a !== void 0 ? _a : "30" }); + } + /** + * Set a marker to indicate the point in a room before which the user has read every + * event. This can be retrieved from room account data (the event type is `m.fully_read`) + * and displayed as a horizontal line in the timeline that is visually distinct to the + * position of the user's own read receipt. + * @param roomId - ID of the room that has been read + * @param rmEventId - ID of the event that has been read + * @param rrEventId - ID of the event tracked by the read receipt. This is here + * for convenience because the RR and the RM are commonly updated at the same time as + * each other. Optional. + * @param rpEventId - rpEvent the m.read.private read receipt event for when we + * don't want other users to see the read receipts. This is experimental. Optional. + * @returns Promise which resolves: the empty object, `{}`. + */ + setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId) { + return __awaiter(this, void 0, void 0, function* () { + const path = utils.encodeUri("/rooms/$roomId/read_markers", { + $roomId: roomId, + }); + const content = { + [read_receipts_1.ReceiptType.FullyRead]: rmEventId, + [read_receipts_1.ReceiptType.Read]: rrEventId, + }; + if ((yield this.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) || + (yield this.isVersionSupported("v1.4"))) { + content[read_receipts_1.ReceiptType.ReadPrivate] = rpEventId; + } + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, content); + }); + } + /** + * @returns Promise which resolves: A list of the user's current rooms + * @returns Rejects: with an error response. + */ + getJoinedRooms() { + const path = utils.encodeUri("/joined_rooms", {}); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Retrieve membership info. for a room. + * @param roomId - ID of the room to get membership for + * @returns Promise which resolves: A list of currently joined users + * and their profile data. + * @returns Rejects: with an error response. + */ + getJoinedRoomMembers(roomId) { + const path = utils.encodeUri("/rooms/$roomId/joined_members", { + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @param options - Options for this request + * @param server - The remote server to query for the room list. + * Optional. If unspecified, get the local home + * server's public room list. + * @param limit - Maximum number of entries to return + * @param since - Token to paginate from + * @returns Promise which resolves: IPublicRoomsResponse + * @returns Rejects: with an error response. + */ + publicRooms(_a = {}) { + var { server, limit, since } = _a, options = __rest(_a, ["server", "limit", "since"]); + const queryParams = { server, limit, since }; + if (Object.keys(options).length === 0) { + return this.http.authedRequest(http_api_1.Method.Get, "/publicRooms", queryParams); + } + else { + return this.http.authedRequest(http_api_1.Method.Post, "/publicRooms", queryParams, options); + } + } + /** + * Create an alias to room ID mapping. + * @param alias - The room alias to create. + * @param roomId - The room ID to link the alias to. + * @returns Promise which resolves: an empty object `{}` + * @returns Rejects: with an error response. + */ + createAlias(alias, roomId) { + const path = utils.encodeUri("/directory/room/$alias", { + $alias: alias, + }); + const data = { + room_id: roomId, + }; + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, data); + } + /** + * Delete an alias to room ID mapping. This alias must be on your local server, + * and you must have sufficient access to do this operation. + * @param alias - The room alias to delete. + * @returns Promise which resolves: an empty object `{}`. + * @returns Rejects: with an error response. + */ + deleteAlias(alias) { + const path = utils.encodeUri("/directory/room/$alias", { + $alias: alias, + }); + return this.http.authedRequest(http_api_1.Method.Delete, path); + } + /** + * Gets the local aliases for the room. Note: this includes all local aliases, unlike the + * curated list from the m.room.canonical_alias state event. + * @param roomId - The room ID to get local aliases for. + * @returns Promise which resolves: an object with an `aliases` property, containing an array of local aliases + * @returns Rejects: with an error response. + */ + getLocalAliases(roomId) { + const path = utils.encodeUri("/rooms/$roomId/aliases", { $roomId: roomId }); + const prefix = http_api_1.ClientPrefix.V3; + return this.http.authedRequest(http_api_1.Method.Get, path, undefined, undefined, { prefix }); + } + /** + * Get room info for the given alias. + * @param alias - The room alias to resolve. + * @returns Promise which resolves: Object with room_id and servers. + * @returns Rejects: with an error response. + */ + getRoomIdForAlias(alias) { + // eslint-disable-line camelcase + // TODO: deprecate this or resolveRoomAlias + const path = utils.encodeUri("/directory/room/$alias", { + $alias: alias, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @returns Promise which resolves: Object with room_id and servers. + * @returns Rejects: with an error response. + */ + // eslint-disable-next-line camelcase + resolveRoomAlias(roomAlias) { + // TODO: deprecate this or getRoomIdForAlias + const path = utils.encodeUri("/directory/room/$alias", { $alias: roomAlias }); + return this.http.request(http_api_1.Method.Get, path); + } + /** + * Get the visibility of a room in the current HS's room directory + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + getRoomDirectoryVisibility(roomId) { + const path = utils.encodeUri("/directory/list/room/$roomId", { + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Set the visbility of a room in the current HS's room directory + * @param visibility - "public" to make the room visible + * in the public directory, or "private" to make + * it invisible. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + setRoomDirectoryVisibility(roomId, visibility) { + const path = utils.encodeUri("/directory/list/room/$roomId", { + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, { visibility }); + } + /** + * Set the visbility of a room bridged to a 3rd party network in + * the current HS's room directory. + * @param networkId - the network ID of the 3rd party + * instance under which this room is published under. + * @param visibility - "public" to make the room visible + * in the public directory, or "private" to make + * it invisible. + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + setRoomDirectoryVisibilityAppService(networkId, roomId, visibility) { + // TODO: Types + const path = utils.encodeUri("/directory/list/appservice/$networkId/$roomId", { + $networkId: networkId, + $roomId: roomId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, { visibility: visibility }); + } + /** + * Query the user directory with a term matching user IDs, display names and domains. + * @param term - the term with which to search. + * @param limit - the maximum number of results to return. The server will + * apply a limit if unspecified. + * @returns Promise which resolves: an array of results. + */ + searchUserDirectory({ term, limit }) { + const body = { + search_term: term, + }; + if (limit !== undefined) { + body.limit = limit; + } + return this.http.authedRequest(http_api_1.Method.Post, "/user_directory/search", undefined, body); + } + /** + * Upload a file to the media repository on the homeserver. + * + * @param file - The object to upload. On a browser, something that + * can be sent to XMLHttpRequest.send (typically a File). Under node.js, + * a a Buffer, String or ReadStream. + * + * @param opts - options object + * + * @returns Promise which resolves to response object, as + * determined by this.opts.onlyData, opts.rawResponse, and + * opts.onlyContentUri. Rejects with an error (usually a MatrixError). + */ + uploadContent(file, opts) { + return this.http.uploadContent(file, opts); + } + /** + * Cancel a file upload in progress + * @param upload - The object returned from uploadContent + * @returns true if canceled, otherwise false + */ + cancelUpload(upload) { + return this.http.cancelUpload(upload); + } + /** + * Get a list of all file uploads in progress + * @returns Array of objects representing current uploads. + * Currently in progress is element 0. Keys: + * - promise: The promise associated with the upload + * - loaded: Number of bytes uploaded + * - total: Total number of bytes to upload + */ + getCurrentUploads() { + return this.http.getCurrentUploads(); + } + /** + * @param info - The kind of info to retrieve (e.g. 'displayname', + * 'avatar_url'). + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + */ + getProfileInfo(userId, info) { + const path = info + ? utils.encodeUri("/profile/$userId/$info", { $userId: userId, $info: info }) + : utils.encodeUri("/profile/$userId", { $userId: userId }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * @returns Promise which resolves to a list of the user's threepids. + * @returns Rejects: with an error response. + */ + getThreePids() { + return this.http.authedRequest(http_api_1.Method.Get, "/account/3pid"); + } + /** + * Add a 3PID to your homeserver account and optionally bind it to an identity + * server as well. An identity server is required as part of the `creds` object. + * + * This API is deprecated, and you should instead use `addThreePidOnly` + * for homeservers that support it. + * + * @returns Promise which resolves: on success + * @returns Rejects: with an error response. + */ + addThreePid(creds, bind) { + // TODO: Types + const path = "/account/3pid"; + const data = { + threePidCreds: creds, + bind: bind, + }; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data); + } + /** + * Add a 3PID to your homeserver account. This API does not use an identity + * server, as the homeserver is expected to handle 3PID ownership validation. + * + * You can check whether a homeserver supports this API via + * `doesServerSupportSeparateAddAndBind`. + * + * @param data - A object with 3PID validation data from having called + * `account/3pid//requestToken` on the homeserver. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + addThreePidOnly(data) { + return __awaiter(this, void 0, void 0, function* () { + const path = "/account/3pid/add"; + const prefix = (yield this.isVersionSupported("r0.6.0")) ? http_api_1.ClientPrefix.R0 : http_api_1.ClientPrefix.Unstable; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data, { prefix }); + }); + } + /** + * Bind a 3PID for discovery onto an identity server via the homeserver. The + * identity server handles 3PID ownership validation and the homeserver records + * the new binding to track where all 3PIDs for the account are bound. + * + * You can check whether a homeserver supports this API via + * `doesServerSupportSeparateAddAndBind`. + * + * @param data - A object with 3PID validation data from having called + * `validate//requestToken` on the identity server. It should also + * contain `id_server` and `id_access_token` fields as well. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + bindThreePid(data) { + return __awaiter(this, void 0, void 0, function* () { + const path = "/account/3pid/bind"; + const prefix = (yield this.isVersionSupported("r0.6.0")) ? http_api_1.ClientPrefix.R0 : http_api_1.ClientPrefix.Unstable; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data, { prefix }); + }); + } + /** + * Unbind a 3PID for discovery on an identity server via the homeserver. The + * homeserver removes its record of the binding to keep an updated record of + * where all 3PIDs for the account are bound. + * + * @param medium - The threepid medium (eg. 'email') + * @param address - The threepid address (eg. 'bob\@example.com') + * this must be as returned by getThreePids. + * @returns Promise which resolves: on success + * @returns Rejects: with an error response. + */ + unbindThreePid(medium, address) { + return __awaiter(this, void 0, void 0, function* () { + const path = "/account/3pid/unbind"; + const data = { + medium, + address, + id_server: this.getIdentityServerUrl(true), + }; + const prefix = (yield this.isVersionSupported("r0.6.0")) ? http_api_1.ClientPrefix.R0 : http_api_1.ClientPrefix.Unstable; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data, { prefix }); + }); + } + /** + * @param medium - The threepid medium (eg. 'email') + * @param address - The threepid address (eg. 'bob\@example.com') + * this must be as returned by getThreePids. + * @returns Promise which resolves: The server response on success + * (generally the empty JSON object) + * @returns Rejects: with an error response. + */ + deleteThreePid(medium, address) { + const path = "/account/3pid/delete"; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, { medium, address }); + } + /** + * Make a request to change your password. + * @param newPassword - The new desired password. + * @param logoutDevices - Should all sessions be logged out after the password change. Defaults to true. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + setPassword(authDict, newPassword, logoutDevices) { + const path = "/account/password"; + const data = { + auth: authDict, + new_password: newPassword, + logout_devices: logoutDevices, + }; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, data); + } + /** + * Gets all devices recorded for the logged-in user + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + getDevices() { + return this.http.authedRequest(http_api_1.Method.Get, "/devices"); + } + /** + * Gets specific device details for the logged-in user + * @param deviceId - device to query + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + getDevice(deviceId) { + const path = utils.encodeUri("/devices/$device_id", { + $device_id: deviceId, + }); + return this.http.authedRequest(http_api_1.Method.Get, path); + } + /** + * Update the given device + * + * @param deviceId - device to update + * @param body - body of request + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + // eslint-disable-next-line camelcase + setDeviceDetails(deviceId, body) { + const path = utils.encodeUri("/devices/$device_id", { + $device_id: deviceId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, body); + } + /** + * Delete the given device + * + * @param deviceId - device to delete + * @param auth - Optional. Auth data to supply for User-Interactive auth. + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + deleteDevice(deviceId, auth) { + const path = utils.encodeUri("/devices/$device_id", { + $device_id: deviceId, + }); + const body = {}; + if (auth) { + body.auth = auth; + } + return this.http.authedRequest(http_api_1.Method.Delete, path, undefined, body); + } + /** + * Delete multiple device + * + * @param devices - IDs of the devices to delete + * @param auth - Optional. Auth data to supply for User-Interactive auth. + * @returns Promise which resolves: result object + * @returns Rejects: with an error response. + */ + deleteMultipleDevices(devices, auth) { + const body = { devices }; + if (auth) { + body.auth = auth; + } + const path = "/delete_devices"; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, body); + } + /** + * Gets all pushers registered for the logged-in user + * + * @returns Promise which resolves: Array of objects representing pushers + * @returns Rejects: with an error response. + */ + getPushers() { + return __awaiter(this, void 0, void 0, function* () { + const response = yield this.http.authedRequest(http_api_1.Method.Get, "/pushers"); + // Migration path for clients that connect to a homeserver that does not support + // MSC3881 yet, see https://github.com/matrix-org/matrix-spec-proposals/blob/kerry/remote-push-toggle/proposals/3881-remote-push-notification-toggling.md#migration + if (!(yield this.doesServerSupportUnstableFeature("org.matrix.msc3881"))) { + response.pushers = response.pushers.map((pusher) => { + if (!pusher.hasOwnProperty(event_2.PUSHER_ENABLED.name)) { + pusher[event_2.PUSHER_ENABLED.name] = true; + } + return pusher; + }); + } + return response; + }); + } + /** + * Adds a new pusher or updates an existing pusher + * + * @param pusher - Object representing a pusher + * @returns Promise which resolves: Empty json object on success + * @returns Rejects: with an error response. + */ + setPusher(pusher) { + const path = "/pushers/set"; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, pusher); + } + /** + * Persists local notification settings + * @returns Promise which resolves: an empty object + * @returns Rejects: with an error response. + */ + setLocalNotificationSettings(deviceId, notificationSettings) { + const key = `${event_2.LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`; + return this.setAccountData(key, notificationSettings); + } + /** + * Get the push rules for the account from the server. + * @returns Promise which resolves to the push rules. + * @returns Rejects: with an error response. + */ + getPushRules() { + return this.http.authedRequest(http_api_1.Method.Get, "/pushrules/").then((rules) => { + this.setPushRules(rules); + return this.pushRules; + }); + } + /** + * Update the push rules for the account. This should be called whenever + * updated push rules are available. + */ + setPushRules(rules) { + // Fix-up defaults, if applicable. + this.pushRules = pushprocessor_1.PushProcessor.rewriteDefaultRules(rules, this.getUserId()); + // Pre-calculate any necessary caches. + this.pushProcessor.updateCachedPushRuleKeys(this.pushRules); + } + /** + * @returns Promise which resolves: an empty object `{}` + * @returns Rejects: with an error response. + */ + addPushRule(scope, kind, ruleId, body) { + // NB. Scope not uri encoded because devices need the '/' + const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", { + $kind: kind, + $ruleId: ruleId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, body); + } + /** + * @returns Promise which resolves: an empty object `{}` + * @returns Rejects: with an error response. + */ + deletePushRule(scope, kind, ruleId) { + // NB. Scope not uri encoded because devices need the '/' + const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", { + $kind: kind, + $ruleId: ruleId, + }); + return this.http.authedRequest(http_api_1.Method.Delete, path); + } + /** + * Enable or disable a push notification rule. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + setPushRuleEnabled(scope, kind, ruleId, enabled) { + const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/enabled", { + $kind: kind, + $ruleId: ruleId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, { enabled: enabled }); + } + /** + * Set the actions for a push notification rule. + * @returns Promise which resolves: to an empty object `{}` + * @returns Rejects: with an error response. + */ + setPushRuleActions(scope, kind, ruleId, actions) { + const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/actions", { + $kind: kind, + $ruleId: ruleId, + }); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, { actions: actions }); + } + /** + * Perform a server-side search. + * @param next_batch - the batch token to pass in the query string + * @param body - the JSON object to pass to the request body. + * @param abortSignal - optional signal used to cancel the http request. + * @returns Promise which resolves to the search response object. + * @returns Rejects: with an error response. + */ + search({ body, next_batch: nextBatch }, abortSignal) { + const queryParams = {}; + if (nextBatch) { + queryParams.next_batch = nextBatch; + } + return this.http.authedRequest(http_api_1.Method.Post, "/search", queryParams, body, { abortSignal }); + } + /** + * Upload keys + * + * @param content - body of upload request + * + * @param opts - this method no longer takes any opts, + * used to take opts.device_id but this was not removed from the spec as a redundant parameter + * + * @returns Promise which resolves: result object. Rejects: with + * an error response ({@link MatrixError}). + */ + uploadKeysRequest(content, opts) { + return this.http.authedRequest(http_api_1.Method.Post, "/keys/upload", undefined, content); + } + uploadKeySignatures(content) { + return this.http.authedRequest(http_api_1.Method.Post, "/keys/signatures/upload", undefined, content, { + prefix: http_api_1.ClientPrefix.V3, + }); + } + /** + * Download device keys + * + * @param userIds - list of users to get keys for + * + * @param token - sync token to pass in the query request, to help + * the HS give the most recent results + * + * @returns Promise which resolves: result object. Rejects: with + * an error response ({@link MatrixError}). + */ + downloadKeysForUsers(userIds, { token } = {}) { + const content = { + device_keys: {}, + }; + if (token !== undefined) { + content.token = token; + } + userIds.forEach((u) => { + content.device_keys[u] = []; + }); + return this.http.authedRequest(http_api_1.Method.Post, "/keys/query", undefined, content); + } + /** + * Claim one-time keys + * + * @param devices - a list of [userId, deviceId] pairs + * + * @param keyAlgorithm - desired key type + * + * @param timeout - the time (in milliseconds) to wait for keys from remote + * servers + * + * @returns Promise which resolves: result object. Rejects: with + * an error response ({@link MatrixError}). + */ + claimOneTimeKeys(devices, keyAlgorithm = "signed_curve25519", timeout) { + const queries = {}; + if (keyAlgorithm === undefined) { + keyAlgorithm = "signed_curve25519"; + } + for (const [userId, deviceId] of devices) { + const query = queries[userId] || {}; + queries[userId] = query; + query[deviceId] = keyAlgorithm; + } + const content = { one_time_keys: queries }; + if (timeout) { + content.timeout = timeout; + } + const path = "/keys/claim"; + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, content); + } + /** + * Ask the server for a list of users who have changed their device lists + * between a pair of sync tokens + * + * + * @returns Promise which resolves: result object. Rejects: with + * an error response ({@link MatrixError}). + */ + getKeyChanges(oldToken, newToken) { + const qps = { + from: oldToken, + to: newToken, + }; + return this.http.authedRequest(http_api_1.Method.Get, "/keys/changes", qps); + } + uploadDeviceSigningKeys(auth, keys) { + // API returns empty object + const data = Object.assign({}, keys); + if (auth) + Object.assign(data, { auth }); + return this.http.authedRequest(http_api_1.Method.Post, "/keys/device_signing/upload", undefined, data, { + prefix: http_api_1.ClientPrefix.Unstable, + }); + } + /** + * Register with an identity server using the OpenID token from the user's + * Homeserver, which can be retrieved via + * {@link MatrixClient#getOpenIdToken}. + * + * Note that the `/account/register` endpoint (as well as IS authentication in + * general) was added as part of the v2 API version. + * + * @returns Promise which resolves: with object containing an Identity + * Server access token. + * @returns Rejects: with an error response. + */ + registerWithIdentityServer(hsOpenIdToken) { + if (!this.idBaseUrl) { + throw new Error("No identity server base URL set"); + } + const uri = this.http.getUrl("/account/register", undefined, http_api_1.IdentityPrefix.V2, this.idBaseUrl); + return this.http.requestOtherUrl(http_api_1.Method.Post, uri, hsOpenIdToken); + } + /** + * Requests an email verification token directly from an identity server. + * + * This API is used as part of binding an email for discovery on an identity + * server. The validation data that results should be passed to the + * `bindThreePid` method to complete the binding process. + * + * @param email - The email address to request a token for + * @param clientSecret - A secret binary string generated by the client. + * It is recommended this be around 16 ASCII characters. + * @param sendAttempt - If an identity server sees a duplicate request + * with the same sendAttempt, it will not send another email. + * To request another email to be sent, use a larger value for + * the sendAttempt param as was used in the previous request. + * @param nextLink - Optional If specified, the client will be redirected + * to this link after validation. + * @param identityAccessToken - The `access_token` field of the identity + * server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves: TODO + * @returns Rejects: with an error response. + * @throws Error if no identity server is set + */ + requestEmailToken(email, clientSecret, sendAttempt, nextLink, identityAccessToken) { + const params = { + client_secret: clientSecret, + email: email, + send_attempt: sendAttempt === null || sendAttempt === void 0 ? void 0 : sendAttempt.toString(), + }; + if (nextLink) { + params.next_link = nextLink; + } + return this.http.idServerRequest(http_api_1.Method.Post, "/validate/email/requestToken", params, http_api_1.IdentityPrefix.V2, identityAccessToken); + } + /** + * Requests a MSISDN verification token directly from an identity server. + * + * This API is used as part of binding a MSISDN for discovery on an identity + * server. The validation data that results should be passed to the + * `bindThreePid` method to complete the binding process. + * + * @param phoneCountry - The ISO 3166-1 alpha-2 code for the country in + * which phoneNumber should be parsed relative to. + * @param phoneNumber - The phone number, in national or international + * format + * @param clientSecret - A secret binary string generated by the client. + * It is recommended this be around 16 ASCII characters. + * @param sendAttempt - If an identity server sees a duplicate request + * with the same sendAttempt, it will not send another SMS. + * To request another SMS to be sent, use a larger value for + * the sendAttempt param as was used in the previous request. + * @param nextLink - Optional If specified, the client will be redirected + * to this link after validation. + * @param identityAccessToken - The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves to an object with a sid string + * @returns Rejects: with an error response. + * @throws Error if no identity server is set + */ + requestMsisdnToken(phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink, identityAccessToken) { + const params = { + client_secret: clientSecret, + country: phoneCountry, + phone_number: phoneNumber, + send_attempt: sendAttempt === null || sendAttempt === void 0 ? void 0 : sendAttempt.toString(), + }; + if (nextLink) { + params.next_link = nextLink; + } + return this.http.idServerRequest(http_api_1.Method.Post, "/validate/msisdn/requestToken", params, http_api_1.IdentityPrefix.V2, identityAccessToken); + } + /** + * Submits a MSISDN token to the identity server + * + * This is used when submitting the code sent by SMS to a phone number. + * The identity server has an equivalent API for email but the js-sdk does + * not expose this, since email is normally validated by the user clicking + * a link rather than entering a code. + * + * @param sid - The sid given in the response to requestToken + * @param clientSecret - A secret binary string generated by the client. + * This must be the same value submitted in the requestToken call. + * @param msisdnToken - The MSISDN token, as enetered by the user. + * @param identityAccessToken - The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves: Object, currently with no parameters. + * @returns Rejects: with an error response. + * @throws Error if No identity server is set + */ + submitMsisdnToken(sid, clientSecret, msisdnToken, identityAccessToken) { + // TODO: Types + const params = { + sid: sid, + client_secret: clientSecret, + token: msisdnToken, + }; + return this.http.idServerRequest(http_api_1.Method.Post, "/validate/msisdn/submitToken", params, http_api_1.IdentityPrefix.V2, identityAccessToken); + } + /** + * Submits a MSISDN token to an arbitrary URL. + * + * This is used when submitting the code sent by SMS to a phone number in the + * newer 3PID flow where the homeserver validates 3PID ownership (as part of + * `requestAdd3pidMsisdnToken`). The homeserver response may include a + * `submit_url` to specify where the token should be sent, and this helper can + * be used to pass the token to this URL. + * + * @param url - The URL to submit the token to + * @param sid - The sid given in the response to requestToken + * @param clientSecret - A secret binary string generated by the client. + * This must be the same value submitted in the requestToken call. + * @param msisdnToken - The MSISDN token, as enetered by the user. + * + * @returns Promise which resolves: Object, currently with no parameters. + * @returns Rejects: with an error response. + */ + submitMsisdnTokenOtherUrl(url, sid, clientSecret, msisdnToken) { + // TODO: Types + const params = { + sid: sid, + client_secret: clientSecret, + token: msisdnToken, + }; + return this.http.requestOtherUrl(http_api_1.Method.Post, url, params); + } + /** + * Gets the V2 hashing information from the identity server. Primarily useful for + * lookups. + * @param identityAccessToken - The access token for the identity server. + * @returns The hashing information for the identity server. + */ + getIdentityHashDetails(identityAccessToken) { + // TODO: Types + return this.http.idServerRequest(http_api_1.Method.Get, "/hash_details", undefined, http_api_1.IdentityPrefix.V2, identityAccessToken); + } + /** + * Performs a hashed lookup of addresses against the identity server. This is + * only supported on identity servers which have at least the version 2 API. + * @param addressPairs - An array of 2 element arrays. + * The first element of each pair is the address, the second is the 3PID medium. + * Eg: `["email@example.org", "email"]` + * @param identityAccessToken - The access token for the identity server. + * @returns A collection of address mappings to + * found MXIDs. Results where no user could be found will not be listed. + */ + identityHashedLookup(addressPairs, identityAccessToken) { + return __awaiter(this, void 0, void 0, function* () { + const params = { + // addresses: ["email@example.org", "10005550000"], + // algorithm: "sha256", + // pepper: "abc123" + }; + // Get hash information first before trying to do a lookup + const hashes = yield this.getIdentityHashDetails(identityAccessToken); + if (!hashes || !hashes["lookup_pepper"] || !hashes["algorithms"]) { + throw new Error("Unsupported identity server: bad response"); + } + params["pepper"] = hashes["lookup_pepper"]; + const localMapping = { + // hashed identifier => plain text address + // For use in this function's return format + }; + // When picking an algorithm, we pick the hashed over no hashes + if (hashes["algorithms"].includes("sha256")) { + // Abuse the olm hashing + const olmutil = new global.Olm.Utility(); + params["addresses"] = addressPairs.map((p) => { + const addr = p[0].toLowerCase(); // lowercase to get consistent hashes + const med = p[1].toLowerCase(); + const hashed = olmutil + .sha256(`${addr} ${med} ${params["pepper"]}`) + .replace(/\+/g, "-") + .replace(/\//g, "_"); // URL-safe base64 + // Map the hash to a known (case-sensitive) address. We use the case + // sensitive version because the caller might be expecting that. + localMapping[hashed] = p[0]; + return hashed; + }); + params["algorithm"] = "sha256"; + } + else if (hashes["algorithms"].includes("none")) { + params["addresses"] = addressPairs.map((p) => { + const addr = p[0].toLowerCase(); // lowercase to get consistent hashes + const med = p[1].toLowerCase(); + const unhashed = `${addr} ${med}`; + // Map the unhashed values to a known (case-sensitive) address. We use + // the case-sensitive version because the caller might be expecting that. + localMapping[unhashed] = p[0]; + return unhashed; + }); + params["algorithm"] = "none"; + } + else { + throw new Error("Unsupported identity server: unknown hash algorithm"); + } + const response = yield this.http.idServerRequest(http_api_1.Method.Post, "/lookup", params, http_api_1.IdentityPrefix.V2, identityAccessToken); + if (!(response === null || response === void 0 ? void 0 : response["mappings"])) + return []; // no results + const foundAddresses = []; + for (const hashed of Object.keys(response["mappings"])) { + const mxid = response["mappings"][hashed]; + const plainAddress = localMapping[hashed]; + if (!plainAddress) { + throw new Error("Identity server returned more results than expected"); + } + foundAddresses.push({ address: plainAddress, mxid }); + } + return foundAddresses; + }); + } + /** + * Looks up the public Matrix ID mapping for a given 3rd party + * identifier from the identity server + * + * @param medium - The medium of the threepid, eg. 'email' + * @param address - The textual address of the threepid + * @param identityAccessToken - The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves: A threepid mapping + * object or the empty object if no mapping + * exists + * @returns Rejects: with an error response. + */ + lookupThreePid(medium, address, identityAccessToken) { + return __awaiter(this, void 0, void 0, function* () { + // TODO: Types + // Note: we're using the V2 API by calling this function, but our + // function contract requires a V1 response. We therefore have to + // convert it manually. + const response = yield this.identityHashedLookup([[address, medium]], identityAccessToken); + const result = response.find((p) => p.address === address); + if (!result) { + return {}; + } + const mapping = { + address, + medium, + mxid: result.mxid, + // We can't reasonably fill these parameters: + // not_before + // not_after + // ts + // signatures + }; + return mapping; + }); + } + /** + * Looks up the public Matrix ID mappings for multiple 3PIDs. + * + * @param query - Array of arrays containing + * [medium, address] + * @param identityAccessToken - The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves: Lookup results from IS. + * @returns Rejects: with an error response. + */ + bulkLookupThreePids(query, identityAccessToken) { + return __awaiter(this, void 0, void 0, function* () { + // TODO: Types + // Note: we're using the V2 API by calling this function, but our + // function contract requires a V1 response. We therefore have to + // convert it manually. + const response = yield this.identityHashedLookup( + // We have to reverse the query order to get [address, medium] pairs + query.map((p) => [p[1], p[0]]), identityAccessToken); + const v1results = []; + for (const mapping of response) { + const originalQuery = query.find((p) => p[1] === mapping.address); + if (!originalQuery) { + throw new Error("Identity sever returned unexpected results"); + } + v1results.push([ + originalQuery[0], + mapping.address, + mapping.mxid, + ]); + } + return { threepids: v1results }; + }); + } + /** + * Get account info from the identity server. This is useful as a neutral check + * to verify that other APIs are likely to approve access by testing that the + * token is valid, terms have been agreed, etc. + * + * @param identityAccessToken - The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @returns Promise which resolves: an object with account info. + * @returns Rejects: with an error response. + */ + getIdentityAccount(identityAccessToken) { + // TODO: Types + return this.http.idServerRequest(http_api_1.Method.Get, "/account", undefined, http_api_1.IdentityPrefix.V2, identityAccessToken); + } + /** + * Send an event to a specific list of devices. + * This is a low-level API that simply wraps the HTTP API + * call to send to-device messages. We recommend using + * queueToDevice() which is a higher level API. + * + * @param eventType - type of event to send + * content to send. Map from user_id to device_id to content object. + * @param txnId - transaction id. One will be made up if not + * supplied. + * @returns Promise which resolves: to an empty object `{}` + */ + sendToDevice(eventType, contentMap, txnId) { + const path = utils.encodeUri("/sendToDevice/$eventType/$txnId", { + $eventType: eventType, + $txnId: txnId ? txnId : this.makeTxnId(), + }); + const body = { + messages: utils.recursiveMapToObject(contentMap), + }; + const targets = new Map(); + for (const [userId, deviceMessages] of contentMap) { + targets.set(userId, Array.from(deviceMessages.keys())); + } + logger_1.logger.log(`PUT ${path}`, targets); + return this.http.authedRequest(http_api_1.Method.Put, path, undefined, body); + } + /** + * Sends events directly to specific devices using Matrix's to-device + * messaging system. The batch will be split up into appropriately sized + * batches for sending and stored in the store so they can be retried + * later if they fail to send. Retries will happen automatically. + * @param batch - The to-device messages to send + */ + queueToDevice(batch) { + return this.toDeviceMessageQueue.queueBatch(batch); + } + /** + * Get the third party protocols that can be reached using + * this HS + * @returns Promise which resolves to the result object + */ + getThirdpartyProtocols() { + return this.http + .authedRequest(http_api_1.Method.Get, "/thirdparty/protocols") + .then((response) => { + // sanity check + if (!response || typeof response !== "object") { + throw new Error(`/thirdparty/protocols did not return an object: ${response}`); + } + return response; + }); + } + /** + * Get information on how a specific place on a third party protocol + * may be reached. + * @param protocol - The protocol given in getThirdpartyProtocols() + * @param params - Protocol-specific parameters, as given in the + * response to getThirdpartyProtocols() + * @returns Promise which resolves to the result object + */ + getThirdpartyLocation(protocol, params) { + const path = utils.encodeUri("/thirdparty/location/$protocol", { + $protocol: protocol, + }); + return this.http.authedRequest(http_api_1.Method.Get, path, params); + } + /** + * Get information on how a specific user on a third party protocol + * may be reached. + * @param protocol - The protocol given in getThirdpartyProtocols() + * @param params - Protocol-specific parameters, as given in the + * response to getThirdpartyProtocols() + * @returns Promise which resolves to the result object + */ + getThirdpartyUser(protocol, params) { + // TODO: Types + const path = utils.encodeUri("/thirdparty/user/$protocol", { + $protocol: protocol, + }); + return this.http.authedRequest(http_api_1.Method.Get, path, params); + } + getTerms(serviceType, baseUrl) { + // TODO: Types + const url = this.termsUrlForService(serviceType, baseUrl); + return this.http.requestOtherUrl(http_api_1.Method.Get, url); + } + agreeToTerms(serviceType, baseUrl, accessToken, termsUrls) { + const url = this.termsUrlForService(serviceType, baseUrl); + const headers = { + Authorization: "Bearer " + accessToken, + }; + return this.http.requestOtherUrl(http_api_1.Method.Post, url, { + user_accepts: termsUrls, + }, { headers }); + } + /** + * Reports an event as inappropriate to the server, which may then notify the appropriate people. + * @param roomId - The room in which the event being reported is located. + * @param eventId - The event to report. + * @param score - The score to rate this content as where -100 is most offensive and 0 is inoffensive. + * @param reason - The reason the content is being reported. May be blank. + * @returns Promise which resolves to an empty object if successful + */ + reportEvent(roomId, eventId, score, reason) { + const path = utils.encodeUri("/rooms/$roomId/report/$eventId", { + $roomId: roomId, + $eventId: eventId, + }); + return this.http.authedRequest(http_api_1.Method.Post, path, undefined, { score, reason }); + } + /** + * Fetches or paginates a room hierarchy as defined by MSC2946. + * Falls back gracefully to sourcing its data from `getSpaceSummary` if this API is not yet supported by the server. + * @param roomId - The ID of the space-room to use as the root of the summary. + * @param limit - The maximum number of rooms to return per page. + * @param maxDepth - The maximum depth in the tree from the root room to return. + * @param suggestedOnly - Whether to only return rooms with suggested=true. + * @param fromToken - The opaque token to paginate a previous request. + * @returns the response, with next_batch & rooms fields. + */ + getRoomHierarchy(roomId, limit, maxDepth, suggestedOnly = false, fromToken) { + const path = utils.encodeUri("/rooms/$roomId/hierarchy", { + $roomId: roomId, + }); + const queryParams = { + suggested_only: String(suggestedOnly), + max_depth: maxDepth === null || maxDepth === void 0 ? void 0 : maxDepth.toString(), + from: fromToken, + limit: limit === null || limit === void 0 ? void 0 : limit.toString(), + }; + return this.http + .authedRequest(http_api_1.Method.Get, path, queryParams, undefined, { + prefix: http_api_1.ClientPrefix.V1, + }) + .catch((e) => { + if (e.errcode === "M_UNRECOGNIZED") { + // fall back to the prefixed hierarchy API. + return this.http.authedRequest(http_api_1.Method.Get, path, queryParams, undefined, { + prefix: "/_matrix/client/unstable/org.matrix.msc2946", + }); + } + throw e; + }); + } + /** + * Creates a new file tree space with the given name. The client will pick + * defaults for how it expects to be able to support the remaining API offered + * by the returned class. + * + * Note that this is UNSTABLE and may have breaking changes without notice. + * @param name - The name of the tree space. + * @returns Promise which resolves to the created space. + */ + unstableCreateFileTree(name) { + return __awaiter(this, void 0, void 0, function* () { + const { room_id: roomId } = yield this.createRoom({ + name: name, + preset: partials_1.Preset.PrivateChat, + power_level_content_override: Object.assign(Object.assign({}, MSC3089TreeSpace_1.DEFAULT_TREE_POWER_LEVELS_TEMPLATE), { users: { + [this.getUserId()]: 100, + } }), + creation_content: { + [event_2.RoomCreateTypeField]: event_2.RoomType.Space, + }, + initial_state: [ + { + type: event_2.UNSTABLE_MSC3088_PURPOSE.name, + state_key: event_2.UNSTABLE_MSC3089_TREE_SUBTYPE.name, + content: { + [event_2.UNSTABLE_MSC3088_ENABLED.name]: true, + }, + }, + { + type: event_2.EventType.RoomEncryption, + state_key: "", + content: { + algorithm: olmlib.MEGOLM_ALGORITHM, + }, + }, + ], + }); + return new MSC3089TreeSpace_1.MSC3089TreeSpace(this, roomId); + }); + } + /** + * Gets a reference to a tree space, if the room ID given is a tree space. If the room + * does not appear to be a tree space then null is returned. + * + * Note that this is UNSTABLE and may have breaking changes without notice. + * @param roomId - The room ID to get a tree space reference for. + * @returns The tree space, or null if not a tree space. + */ + unstableGetFileTreeSpace(roomId) { + var _a, _b; + const room = this.getRoom(roomId); + if ((room === null || room === void 0 ? void 0 : room.getMyMembership()) !== "join") + return null; + const createEvent = room.currentState.getStateEvents(event_2.EventType.RoomCreate, ""); + const purposeEvent = room.currentState.getStateEvents(event_2.UNSTABLE_MSC3088_PURPOSE.name, event_2.UNSTABLE_MSC3089_TREE_SUBTYPE.name); + if (!createEvent) + throw new Error("Expected single room create event"); + if (!((_a = purposeEvent === null || purposeEvent === void 0 ? void 0 : purposeEvent.getContent()) === null || _a === void 0 ? void 0 : _a[event_2.UNSTABLE_MSC3088_ENABLED.name])) + return null; + if (((_b = createEvent.getContent()) === null || _b === void 0 ? void 0 : _b[event_2.RoomCreateTypeField]) !== event_2.RoomType.Space) + return null; + return new MSC3089TreeSpace_1.MSC3089TreeSpace(this, roomId); + } + /** + * Perform a single MSC3575 sliding sync request. + * @param req - The request to make. + * @param proxyBaseUrl - The base URL for the sliding sync proxy. + * @param abortSignal - Optional signal to abort request mid-flight. + * @returns The sliding sync response, or a standard error. + * @throws on non 2xx status codes with an object with a field "httpStatus":number. + */ + slidingSync(req, proxyBaseUrl, abortSignal) { + const qps = {}; + if (req.pos) { + qps.pos = req.pos; + delete req.pos; + } + if (req.timeout) { + qps.timeout = req.timeout; + delete req.timeout; + } + const clientTimeout = req.clientTimeout; + delete req.clientTimeout; + return this.http.authedRequest(http_api_1.Method.Post, "/sync", qps, req, { + prefix: "/_matrix/client/unstable/org.matrix.msc3575", + baseUrl: proxyBaseUrl, + localTimeoutMs: clientTimeout, + abortSignal, + }); + } + /** + * @deprecated use supportsThreads() instead + */ + supportsExperimentalThreads() { + var _a; + logger_1.logger.warn(`supportsExperimentalThreads() is deprecated, use supportThreads() instead`); + return ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.experimentalThreadSupport) || false; + } + /** + * A helper to determine thread support + * @returns a boolean to determine if threads are enabled + */ + supportsThreads() { + var _a; + return ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.threadSupport) || false; + } + /** + * A helper to determine intentional mentions support + * @returns a boolean to determine if intentional mentions are enabled + * @experimental + */ + supportsIntentionalMentions() { + var _a; + return ((_a = this.clientOpts) === null || _a === void 0 ? void 0 : _a.intentionalMentions) || false; + } + /** + * Fetches the summary of a room as defined by an initial version of MSC3266 and implemented in Synapse + * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266 + * @param roomIdOrAlias - The ID or alias of the room to get the summary of. + * @param via - The list of servers which know about the room if only an ID was provided. + */ + getRoomSummary(roomIdOrAlias, via) { + return __awaiter(this, void 0, void 0, function* () { + const path = utils.encodeUri("/rooms/$roomid/summary", { $roomid: roomIdOrAlias }); + return this.http.authedRequest(http_api_1.Method.Get, path, { via }, undefined, { + prefix: "/_matrix/client/unstable/im.nheko.summary", + }); + }); + } + /** + * Processes a list of threaded events and adds them to their respective timelines + * @param room - the room the adds the threaded events + * @param threadedEvents - an array of the threaded events + * @param toStartOfTimeline - the direction in which we want to add the events + */ + processThreadEvents(room, threadedEvents, toStartOfTimeline) { + room.processThreadedEvents(threadedEvents, toStartOfTimeline); + } + /** + * Processes a list of thread roots and creates a thread model + * @param room - the room to create the threads in + * @param threadedEvents - an array of thread roots + * @param toStartOfTimeline - the direction + */ + processThreadRoots(room, threadedEvents, toStartOfTimeline) { + room.processThreadRoots(threadedEvents, toStartOfTimeline); + } + processBeaconEvents(room, events) { + this.processAggregatedTimelineEvents(room, events); + } + /** + * Calls aggregation functions for event types that are aggregated + * Polls and location beacons + * @param room - room the events belong to + * @param events - timeline events to be processed + * @returns + */ + processAggregatedTimelineEvents(room, events) { + if (!(events === null || events === void 0 ? void 0 : events.length)) + return; + if (!room) + return; + room.currentState.processBeaconEvents(events, this); + room.processPollEvents(events); + } + /** + * Fetches information about the user for the configured access token. + */ + whoami() { + return __awaiter(this, void 0, void 0, function* () { + return this.http.authedRequest(http_api_1.Method.Get, "/account/whoami"); + }); + } + /** + * Find the event_id closest to the given timestamp in the given direction. + * @returns Resolves: A promise of an object containing the event_id and + * origin_server_ts of the closest event to the timestamp in the given direction + * @returns Rejects: when the request fails (module:http-api.MatrixError) + */ + timestampToEvent(roomId, timestamp, dir) { + return __awaiter(this, void 0, void 0, function* () { + const path = utils.encodeUri("/rooms/$roomId/timestamp_to_event", { + $roomId: roomId, + }); + const queryParams = { + ts: timestamp.toString(), + dir: dir, + }; + try { + return yield this.http.authedRequest(http_api_1.Method.Get, path, queryParams, undefined, { + prefix: http_api_1.ClientPrefix.V1, + }); + } + catch (err) { + // Fallback to the prefixed unstable endpoint. Since the stable endpoint is + // new, we should also try the unstable endpoint before giving up. We can + // remove this fallback request in a year (remove after 2023-11-28). + if (err.errcode === "M_UNRECOGNIZED" && + // XXX: The 400 status code check should be removed in the future + // when Synapse is compliant with MSC3743. + (err.httpStatus === 400 || + // This the correct standard status code for an unsupported + // endpoint according to MSC3743. Not Found and Method Not Allowed + // both indicate that this endpoint+verb combination is + // not supported. + err.httpStatus === 404 || + err.httpStatus === 405)) { + return yield this.http.authedRequest(http_api_1.Method.Get, path, queryParams, undefined, { + prefix: "/_matrix/client/unstable/org.matrix.msc3030", + }); + } + throw err; + } + }); + } +} +exports.MatrixClient = MatrixClient; +MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY = "RESTORE_BACKUP_ERROR_BAD_KEY"; +/** + * recalculates an accurate notifications count on event decryption. + * Servers do not have enough knowledge about encrypted events to calculate an + * accurate notification_count + */ +function fixNotificationCountOnDecryption(cli, event) { + var _a, _b; + const ourUserId = cli.getUserId(); + const eventId = event.getId(); + const room = cli.getRoom(event.getRoomId()); + if (!room || !ourUserId || !eventId) + return; + const oldActions = event.getPushActions(); + const actions = cli.getPushActionsForEvent(event, true); + const isThreadEvent = !!event.threadRootId && !event.isThreadRoot; + const currentHighlightCount = room.getUnreadCountForEventContext(room_1.NotificationCountType.Highlight, event); + // Ensure the unread counts are kept up to date if the event is encrypted + // We also want to make sure that the notification count goes up if we already + // have encrypted events to avoid other code from resetting 'highlight' to zero. + const oldHighlight = !!((_a = oldActions === null || oldActions === void 0 ? void 0 : oldActions.tweaks) === null || _a === void 0 ? void 0 : _a.highlight); + const newHighlight = !!((_b = actions === null || actions === void 0 ? void 0 : actions.tweaks) === null || _b === void 0 ? void 0 : _b.highlight); + let hasReadEvent; + if (isThreadEvent) { + const thread = room.getThread(event.threadRootId); + hasReadEvent = thread + ? thread.hasUserReadEvent(ourUserId, eventId) + : // If the thread object does not exist in the room yet, we don't + // want to calculate notification for this event yet. We have not + // restored the read receipts yet and can't accurately calculate + // notifications at this stage. + // + // This issue can likely go away when MSC3874 is implemented + true; + } + else { + hasReadEvent = room.hasUserReadEvent(ourUserId, eventId); + } + if (hasReadEvent) { + // If the event has been read, ignore it. + return; + } + if (oldHighlight !== newHighlight || currentHighlightCount > 0) { + // TODO: Handle mentions received while the client is offline + // See also https://github.com/vector-im/element-web/issues/9069 + let newCount = currentHighlightCount; + if (newHighlight && !oldHighlight) + newCount++; + if (!newHighlight && oldHighlight) + newCount--; + if (isThreadEvent) { + room.setThreadUnreadNotificationCount(event.threadRootId, room_1.NotificationCountType.Highlight, newCount); + } + else { + room.setUnreadNotificationCount(room_1.NotificationCountType.Highlight, newCount); + } + } + // Total count is used to typically increment a room notification counter, but not loudly highlight it. + const currentTotalCount = room.getUnreadCountForEventContext(room_1.NotificationCountType.Total, event); + // `notify` is used in practice for incrementing the total count + const newNotify = !!(actions === null || actions === void 0 ? void 0 : actions.notify); + // The room total count is NEVER incremented by the server for encrypted rooms. We basically ignore + // the server here as it's always going to tell us to increment for encrypted events. + if (newNotify) { + if (isThreadEvent) { + room.setThreadUnreadNotificationCount(event.threadRootId, room_1.NotificationCountType.Total, currentTotalCount + 1); + } + else { + room.setUnreadNotificationCount(room_1.NotificationCountType.Total, currentTotalCount + 1); + } + } +} +exports.fixNotificationCountOnDecryption = fixNotificationCountOnDecryption; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./@types/PushRules":304,"./@types/beacon":305,"./@types/event":306,"./@types/partials":309,"./@types/read_receipts":311,"./@types/search":313,"./NamespacedValue":316,"./ReEmitter":317,"./ToDeviceMessageQueue":318,"./autodiscovery":319,"./content-helpers":322,"./content-repo":323,"./crypto":341,"./crypto/RoomList":329,"./crypto/api":336,"./crypto/backup":337,"./crypto/dehydration":339,"./crypto/key_passphrase":342,"./crypto/olmlib":343,"./crypto/recoverykey":344,"./event-mapper":360,"./feature":362,"./filter":364,"./http-api":367,"./logger":374,"./models/MSC3089TreeSpace":377,"./models/event":383,"./models/event-timeline":382,"./models/invites-ignorer":384,"./models/room":392,"./models/room-member":389,"./models/search-result":393,"./models/thread":394,"./models/typed-event-emitter":395,"./models/user":396,"./pushprocessor":397,"./randomstring":398,"./rust-crypto":402,"./rust-crypto/constants":401,"./service-types":405,"./sliding-sync-sdk":406,"./store/stub":412,"./sync":414,"./utils":416,"./webrtc/call":418,"./webrtc/callEventHandler":419,"./webrtc/groupCall":422,"./webrtc/groupCallEventHandler":423,"./webrtc/mediaHandler":424}],322:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseBeaconContent = exports.makeBeaconContent = exports.parseBeaconInfoContent = exports.makeBeaconInfoContent = exports.parseTopicContent = exports.makeTopicContent = exports.parseLocationEvent = exports.makeLocationContent = exports.getTextForLocationEvent = exports.makeEmoteMessage = exports.makeNotice = exports.makeTextMessage = exports.makeHtmlEmote = exports.makeHtmlNotice = exports.makeHtmlMessage = void 0; +const event_1 = require("./@types/event"); +const extensible_events_1 = require("./@types/extensible_events"); +const utilities_1 = require("./extensible_events_v1/utilities"); +const location_1 = require("./@types/location"); +const topic_1 = require("./@types/topic"); +/** + * Generates the content for a HTML Message event + * @param body - the plaintext body of the message + * @param htmlBody - the HTML representation of the message + * @returns + */ +function makeHtmlMessage(body, htmlBody) { + return { + msgtype: event_1.MsgType.Text, + format: "org.matrix.custom.html", + body: body, + formatted_body: htmlBody, + }; +} +exports.makeHtmlMessage = makeHtmlMessage; +/** + * Generates the content for a HTML Notice event + * @param body - the plaintext body of the notice + * @param htmlBody - the HTML representation of the notice + * @returns + */ +function makeHtmlNotice(body, htmlBody) { + return { + msgtype: event_1.MsgType.Notice, + format: "org.matrix.custom.html", + body: body, + formatted_body: htmlBody, + }; +} +exports.makeHtmlNotice = makeHtmlNotice; +/** + * Generates the content for a HTML Emote event + * @param body - the plaintext body of the emote + * @param htmlBody - the HTML representation of the emote + * @returns + */ +function makeHtmlEmote(body, htmlBody) { + return { + msgtype: event_1.MsgType.Emote, + format: "org.matrix.custom.html", + body: body, + formatted_body: htmlBody, + }; +} +exports.makeHtmlEmote = makeHtmlEmote; +/** + * Generates the content for a Plaintext Message event + * @param body - the plaintext body of the emote + * @returns + */ +function makeTextMessage(body) { + return { + msgtype: event_1.MsgType.Text, + body: body, + }; +} +exports.makeTextMessage = makeTextMessage; +/** + * Generates the content for a Plaintext Notice event + * @param body - the plaintext body of the notice + * @returns + */ +function makeNotice(body) { + return { + msgtype: event_1.MsgType.Notice, + body: body, + }; +} +exports.makeNotice = makeNotice; +/** + * Generates the content for a Plaintext Emote event + * @param body - the plaintext body of the emote + * @returns + */ +function makeEmoteMessage(body) { + return { + msgtype: event_1.MsgType.Emote, + body: body, + }; +} +exports.makeEmoteMessage = makeEmoteMessage; +/** Location content helpers */ +const getTextForLocationEvent = (uri, assetType, timestamp, description) => { + const date = `at ${new Date(timestamp).toISOString()}`; + const assetName = assetType === location_1.LocationAssetType.Self ? "User" : undefined; + const quotedDescription = description ? `"${description}"` : undefined; + return [assetName, "Location", quotedDescription, uri, date].filter(Boolean).join(" "); +}; +exports.getTextForLocationEvent = getTextForLocationEvent; +/** + * Generates the content for a Location event + * @param uri - a geo:// uri for the location + * @param timestamp - the timestamp when the location was correct (milliseconds since the UNIX epoch) + * @param description - the (optional) label for this location on the map + * @param assetType - the (optional) asset type of this location e.g. "m.self" + * @param text - optional. A text for the location + */ +const makeLocationContent = ( +// this is first but optional +// to avoid a breaking change +text, uri, timestamp, description, assetType) => { + const defaultedText = text !== null && text !== void 0 ? text : (0, exports.getTextForLocationEvent)(uri, assetType || location_1.LocationAssetType.Self, timestamp, description); + const timestampEvent = timestamp ? { [location_1.M_TIMESTAMP.name]: timestamp } : {}; + return Object.assign({ msgtype: event_1.MsgType.Location, body: defaultedText, geo_uri: uri, [location_1.M_LOCATION.name]: { + description, + uri, + }, [location_1.M_ASSET.name]: { + type: assetType || location_1.LocationAssetType.Self, + }, [extensible_events_1.M_TEXT.name]: defaultedText }, timestampEvent); +}; +exports.makeLocationContent = makeLocationContent; +/** + * Parse location event content and transform to + * a backwards compatible modern m.location event format + */ +const parseLocationEvent = (wireEventContent) => { + var _a, _b; + const location = location_1.M_LOCATION.findIn(wireEventContent); + const asset = location_1.M_ASSET.findIn(wireEventContent); + const timestamp = location_1.M_TIMESTAMP.findIn(wireEventContent); + const text = extensible_events_1.M_TEXT.findIn(wireEventContent); + const geoUri = (_a = location === null || location === void 0 ? void 0 : location.uri) !== null && _a !== void 0 ? _a : wireEventContent === null || wireEventContent === void 0 ? void 0 : wireEventContent.geo_uri; + const description = location === null || location === void 0 ? void 0 : location.description; + const assetType = (_b = asset === null || asset === void 0 ? void 0 : asset.type) !== null && _b !== void 0 ? _b : location_1.LocationAssetType.Self; + const fallbackText = text !== null && text !== void 0 ? text : wireEventContent.body; + return (0, exports.makeLocationContent)(fallbackText, geoUri, timestamp !== null && timestamp !== void 0 ? timestamp : undefined, description, assetType); +}; +exports.parseLocationEvent = parseLocationEvent; +const makeTopicContent = (topic, htmlTopic) => { + const renderings = [{ body: topic, mimetype: "text/plain" }]; + if ((0, utilities_1.isProvided)(htmlTopic)) { + renderings.push({ body: htmlTopic, mimetype: "text/html" }); + } + return { topic, [topic_1.M_TOPIC.name]: renderings }; +}; +exports.makeTopicContent = makeTopicContent; +const parseTopicContent = (content) => { + var _a, _b, _c; + const mtopic = topic_1.M_TOPIC.findIn(content); + if (!Array.isArray(mtopic)) { + return { text: content.topic }; + } + const text = (_b = (_a = mtopic === null || mtopic === void 0 ? void 0 : mtopic.find((r) => !(0, utilities_1.isProvided)(r.mimetype) || r.mimetype === "text/plain")) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : content.topic; + const html = (_c = mtopic === null || mtopic === void 0 ? void 0 : mtopic.find((r) => r.mimetype === "text/html")) === null || _c === void 0 ? void 0 : _c.body; + return { text, html }; +}; +exports.parseTopicContent = parseTopicContent; +const makeBeaconInfoContent = (timeout, isLive, description, assetType, timestamp) => ({ + description, + timeout, + live: isLive, + [location_1.M_TIMESTAMP.name]: timestamp || Date.now(), + [location_1.M_ASSET.name]: { + type: assetType !== null && assetType !== void 0 ? assetType : location_1.LocationAssetType.Self, + }, +}); +exports.makeBeaconInfoContent = makeBeaconInfoContent; +/** + * Flatten beacon info event content + */ +const parseBeaconInfoContent = (content) => { + var _a; + const { description, timeout, live } = content; + const timestamp = (_a = location_1.M_TIMESTAMP.findIn(content)) !== null && _a !== void 0 ? _a : undefined; + const asset = location_1.M_ASSET.findIn(content); + return { + description, + timeout, + live, + assetType: asset === null || asset === void 0 ? void 0 : asset.type, + timestamp, + }; +}; +exports.parseBeaconInfoContent = parseBeaconInfoContent; +const makeBeaconContent = (uri, timestamp, beaconInfoEventId, description) => ({ + [location_1.M_LOCATION.name]: { + description, + uri, + }, + [location_1.M_TIMESTAMP.name]: timestamp, + "m.relates_to": { + rel_type: extensible_events_1.REFERENCE_RELATION.name, + event_id: beaconInfoEventId, + }, +}); +exports.makeBeaconContent = makeBeaconContent; +const parseBeaconContent = (content) => { + var _a; + const location = location_1.M_LOCATION.findIn(content); + const timestamp = (_a = location_1.M_TIMESTAMP.findIn(content)) !== null && _a !== void 0 ? _a : undefined; + return { + description: location === null || location === void 0 ? void 0 : location.description, + uri: location === null || location === void 0 ? void 0 : location.uri, + timestamp, + }; +}; +exports.parseBeaconContent = parseBeaconContent; + +},{"./@types/event":306,"./@types/extensible_events":307,"./@types/location":308,"./@types/topic":315,"./extensible_events_v1/utilities":361}],323:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getHttpUriForMxc = void 0; +const utils = __importStar(require("./utils")); +/** + * Get the HTTP URL for an MXC URI. + * @param baseUrl - The base homeserver url which has a content repo. + * @param mxc - The mxc:// URI. + * @param width - The desired width of the thumbnail. + * @param height - The desired height of the thumbnail. + * @param resizeMethod - The thumbnail resize method to use, either + * "crop" or "scale". + * @param allowDirectLinks - If true, return any non-mxc URLs + * directly. Fetching such URLs will leak information about the user to + * anyone they share a room with. If false, will return the emptry string + * for such URLs. + * @returns The complete URL to the content. + */ +function getHttpUriForMxc(baseUrl, mxc, width, height, resizeMethod, allowDirectLinks = false) { + if (typeof mxc !== "string" || !mxc) { + return ""; + } + if (mxc.indexOf("mxc://") !== 0) { + if (allowDirectLinks) { + return mxc; + } + else { + return ""; + } + } + let serverAndMediaId = mxc.slice(6); // strips mxc:// + let prefix = "/_matrix/media/r0/download/"; + const params = {}; + if (width) { + params["width"] = Math.round(width).toString(); + } + if (height) { + params["height"] = Math.round(height).toString(); + } + if (resizeMethod) { + params["method"] = resizeMethod; + } + if (Object.keys(params).length > 0) { + // these are thumbnailing params so they probably want the + // thumbnailing API... + prefix = "/_matrix/media/r0/thumbnail/"; + } + const fragmentOffset = serverAndMediaId.indexOf("#"); + let fragment = ""; + if (fragmentOffset >= 0) { + fragment = serverAndMediaId.slice(fragmentOffset); + serverAndMediaId = serverAndMediaId.slice(0, fragmentOffset); + } + const urlParams = Object.keys(params).length === 0 ? "" : "?" + utils.encodeParams(params); + return baseUrl + prefix + serverAndMediaId + urlParams + fragment; +} +exports.getHttpUriForMxc = getHttpUriForMxc; + +},{"./utils":416}],324:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.requestKeysDuringVerification = exports.createCryptoStoreCacheCallbacks = exports.DeviceTrustLevel = exports.UserTrustLevel = exports.CrossSigningLevel = exports.CrossSigningInfo = void 0; +const olmlib_1 = require("./olmlib"); +const logger_1 = require("../logger"); +const indexeddb_crypto_store_1 = require("../crypto/store/indexeddb-crypto-store"); +const aes_1 = require("./aes"); +const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; +function publicKeyFromKeyInfo(keyInfo) { + // `keys` is an object with { [`ed25519:${pubKey}`]: pubKey } + // We assume only a single key, and we want the bare form without type + // prefix, so we select the values. + return Object.values(keyInfo.keys)[0]; +} +class CrossSigningInfo { + /** + * Information about a user's cross-signing keys + * + * @param userId - the user that the information is about + * @param callbacks - Callbacks used to interact with the app + * Requires getCrossSigningKey and saveCrossSigningKeys + * @param cacheCallbacks - Callbacks used to interact with the cache + */ + constructor(userId, callbacks = {}, cacheCallbacks = {}) { + this.userId = userId; + this.callbacks = callbacks; + this.cacheCallbacks = cacheCallbacks; + this.keys = {}; + this.firstUse = true; + // This tracks whether we've ever verified this user with any identity. + // When you verify a user, any devices online at the time that receive + // the verifying signature via the homeserver will latch this to true + // and can use it in the future to detect cases where the user has + // become unverified later for any reason. + this.crossSigningVerifiedBefore = false; + } + static fromStorage(obj, userId) { + const res = new CrossSigningInfo(userId); + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) { + // @ts-ignore - ts doesn't like this and nor should we + res[prop] = obj[prop]; + } + } + return res; + } + toStorage() { + return { + keys: this.keys, + firstUse: this.firstUse, + crossSigningVerifiedBefore: this.crossSigningVerifiedBefore, + }; + } + /** + * Calls the app callback to ask for a private key + * + * @param type - The key type ("master", "self_signing", or "user_signing") + * @param expectedPubkey - The matching public key or undefined to use + * the stored public key for the given key type. + * @returns An array with [ public key, Olm.PkSigning ] + */ + getCrossSigningKey(type, expectedPubkey) { + return __awaiter(this, void 0, void 0, function* () { + const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0; + if (!this.callbacks.getCrossSigningKey) { + throw new Error("No getCrossSigningKey callback supplied"); + } + if (expectedPubkey === undefined) { + expectedPubkey = this.getId(type); + } + function validateKey(key) { + if (!key) + return; + const signing = new global.Olm.PkSigning(); + const gotPubkey = signing.init_with_seed(key); + if (gotPubkey === expectedPubkey) { + return [gotPubkey, signing]; + } + signing.free(); + } + let privkey = null; + if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) { + privkey = yield this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey); + } + const cacheresult = validateKey(privkey); + if (cacheresult) { + return cacheresult; + } + privkey = yield this.callbacks.getCrossSigningKey(type, expectedPubkey); + const result = validateKey(privkey); + if (result) { + if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) { + yield this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey); + } + return result; + } + /* No keysource even returned a key */ + if (!privkey) { + throw new Error("getCrossSigningKey callback for " + type + " returned falsey"); + } + /* We got some keys from the keysource, but none of them were valid */ + throw new Error("Key type " + type + " from getCrossSigningKey callback did not match"); + }); + } + /** + * Check whether the private keys exist in secret storage. + * XXX: This could be static, be we often seem to have an instance when we + * want to know this anyway... + * + * @param secretStorage - The secret store using account data + * @returns map of key name to key info the secret is encrypted + * with, or null if it is not present or not encrypted with a trusted + * key + */ + isStoredInSecretStorage(secretStorage) { + return __awaiter(this, void 0, void 0, function* () { + // check what SSSS keys have encrypted the master key (if any) + const stored = (yield secretStorage.isStored("m.cross_signing.master")) || {}; + // then check which of those SSSS keys have also encrypted the SSK and USK + function intersect(s) { + for (const k of Object.keys(stored)) { + if (!s[k]) { + delete stored[k]; + } + } + } + for (const type of ["self_signing", "user_signing"]) { + intersect((yield secretStorage.isStored(`m.cross_signing.${type}`)) || {}); + } + return Object.keys(stored).length ? stored : null; + }); + } + /** + * Store private keys in secret storage for use by other devices. This is + * typically called in conjunction with the creation of new cross-signing + * keys. + * + * @param keys - The keys to store + * @param secretStorage - The secret store using account data + */ + static storeInSecretStorage(keys, secretStorage) { + return __awaiter(this, void 0, void 0, function* () { + for (const [type, privateKey] of keys) { + const encodedKey = (0, olmlib_1.encodeBase64)(privateKey); + yield secretStorage.store(`m.cross_signing.${type}`, encodedKey); + } + }); + } + /** + * Get private keys from secret storage created by some other device. This + * also passes the private keys to the app-specific callback. + * + * @param type - The type of key to get. One of "master", + * "self_signing", or "user_signing". + * @param secretStorage - The secret store using account data + * @returns The private key + */ + static getFromSecretStorage(type, secretStorage) { + return __awaiter(this, void 0, void 0, function* () { + const encodedKey = yield secretStorage.get(`m.cross_signing.${type}`); + if (!encodedKey) { + return null; + } + return (0, olmlib_1.decodeBase64)(encodedKey); + }); + } + /** + * Check whether the private keys exist in the local key cache. + * + * @param type - The type of key to get. One of "master", + * "self_signing", or "user_signing". Optional, will check all by default. + * @returns True if all keys are stored in the local cache. + */ + isStoredInKeyCache(type) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const cacheCallbacks = this.cacheCallbacks; + if (!cacheCallbacks) + return false; + const types = type ? [type] : ["master", "self_signing", "user_signing"]; + for (const t of types) { + if (!(yield ((_a = cacheCallbacks.getCrossSigningKeyCache) === null || _a === void 0 ? void 0 : _a.call(cacheCallbacks, t)))) { + return false; + } + } + return true; + }); + } + /** + * Get cross-signing private keys from the local cache. + * + * @returns A map from key type (string) to private key (Uint8Array) + */ + getCrossSigningKeysFromCache() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const keys = new Map(); + const cacheCallbacks = this.cacheCallbacks; + if (!cacheCallbacks) + return keys; + for (const type of ["master", "self_signing", "user_signing"]) { + const privKey = yield ((_a = cacheCallbacks.getCrossSigningKeyCache) === null || _a === void 0 ? void 0 : _a.call(cacheCallbacks, type)); + if (!privKey) { + continue; + } + keys.set(type, privKey); + } + return keys; + }); + } + /** + * Get the ID used to identify the user. This can also be used to test for + * the existence of a given key type. + * + * @param type - The type of key to get the ID of. One of "master", + * "self_signing", or "user_signing". Defaults to "master". + * + * @returns the ID + */ + getId(type = "master") { + if (!this.keys[type]) + return null; + const keyInfo = this.keys[type]; + return publicKeyFromKeyInfo(keyInfo); + } + /** + * Create new cross-signing keys for the given key types. The public keys + * will be held in this class, while the private keys are passed off to the + * `saveCrossSigningKeys` application callback. + * + * @param level - The key types to reset + */ + resetKeys(level) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.callbacks.saveCrossSigningKeys) { + throw new Error("No saveCrossSigningKeys callback supplied"); + } + // If we're resetting the master key, we reset all keys + if (level === undefined || level & CrossSigningLevel.MASTER || !this.keys.master) { + level = CrossSigningLevel.MASTER | CrossSigningLevel.USER_SIGNING | CrossSigningLevel.SELF_SIGNING; + } + else if (level === 0) { + return; + } + const privateKeys = {}; + const keys = {}; + let masterSigning; + let masterPub; + try { + if (level & CrossSigningLevel.MASTER) { + masterSigning = new global.Olm.PkSigning(); + privateKeys.master = masterSigning.generate_seed(); + masterPub = masterSigning.init_with_seed(privateKeys.master); + keys.master = { + user_id: this.userId, + usage: ["master"], + keys: { + ["ed25519:" + masterPub]: masterPub, + }, + }; + } + else { + [masterPub, masterSigning] = yield this.getCrossSigningKey("master"); + } + if (level & CrossSigningLevel.SELF_SIGNING) { + const sskSigning = new global.Olm.PkSigning(); + try { + privateKeys.self_signing = sskSigning.generate_seed(); + const sskPub = sskSigning.init_with_seed(privateKeys.self_signing); + keys.self_signing = { + user_id: this.userId, + usage: ["self_signing"], + keys: { + ["ed25519:" + sskPub]: sskPub, + }, + }; + (0, olmlib_1.pkSign)(keys.self_signing, masterSigning, this.userId, masterPub); + } + finally { + sskSigning.free(); + } + } + if (level & CrossSigningLevel.USER_SIGNING) { + const uskSigning = new global.Olm.PkSigning(); + try { + privateKeys.user_signing = uskSigning.generate_seed(); + const uskPub = uskSigning.init_with_seed(privateKeys.user_signing); + keys.user_signing = { + user_id: this.userId, + usage: ["user_signing"], + keys: { + ["ed25519:" + uskPub]: uskPub, + }, + }; + (0, olmlib_1.pkSign)(keys.user_signing, masterSigning, this.userId, masterPub); + } + finally { + uskSigning.free(); + } + } + Object.assign(this.keys, keys); + this.callbacks.saveCrossSigningKeys(privateKeys); + } + finally { + if (masterSigning) { + masterSigning.free(); + } + } + }); + } + /** + * unsets the keys, used when another session has reset the keys, to disable cross-signing + */ + clearKeys() { + this.keys = {}; + } + setKeys(keys) { + const signingKeys = {}; + if (keys.master) { + if (keys.master.user_id !== this.userId) { + const error = "Mismatched user ID " + keys.master.user_id + " in master key from " + this.userId; + logger_1.logger.error(error); + throw new Error(error); + } + if (!this.keys.master) { + // this is the first key we've seen, so first-use is true + this.firstUse = true; + } + else if (publicKeyFromKeyInfo(keys.master) !== this.getId()) { + // this is a different key, so first-use is false + this.firstUse = false; + } // otherwise, same key, so no change + signingKeys.master = keys.master; + } + else if (this.keys.master) { + signingKeys.master = this.keys.master; + } + else { + throw new Error("Tried to set cross-signing keys without a master key"); + } + const masterKey = publicKeyFromKeyInfo(signingKeys.master); + // verify signatures + if (keys.user_signing) { + if (keys.user_signing.user_id !== this.userId) { + const error = "Mismatched user ID " + keys.master.user_id + " in user_signing key from " + this.userId; + logger_1.logger.error(error); + throw new Error(error); + } + try { + (0, olmlib_1.pkVerify)(keys.user_signing, masterKey, this.userId); + } + catch (e) { + logger_1.logger.error("invalid signature on user-signing key"); + // FIXME: what do we want to do here? + throw e; + } + } + if (keys.self_signing) { + if (keys.self_signing.user_id !== this.userId) { + const error = "Mismatched user ID " + keys.master.user_id + " in self_signing key from " + this.userId; + logger_1.logger.error(error); + throw new Error(error); + } + try { + (0, olmlib_1.pkVerify)(keys.self_signing, masterKey, this.userId); + } + catch (e) { + logger_1.logger.error("invalid signature on self-signing key"); + // FIXME: what do we want to do here? + throw e; + } + } + // if everything checks out, then save the keys + if (keys.master) { + this.keys.master = keys.master; + // if the master key is set, then the old self-signing and user-signing keys are obsolete + delete this.keys["self_signing"]; + delete this.keys["user_signing"]; + } + if (keys.self_signing) { + this.keys.self_signing = keys.self_signing; + } + if (keys.user_signing) { + this.keys.user_signing = keys.user_signing; + } + } + updateCrossSigningVerifiedBefore(isCrossSigningVerified) { + // It is critical that this value latches forward from false to true but + // never back to false to avoid a downgrade attack. + if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) { + this.crossSigningVerifiedBefore = true; + } + } + signObject(data, type) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.keys[type]) { + throw new Error("Attempted to sign with " + type + " key but no such key present"); + } + const [pubkey, signing] = yield this.getCrossSigningKey(type); + try { + (0, olmlib_1.pkSign)(data, signing, this.userId, pubkey); + return data; + } + finally { + signing.free(); + } + }); + } + signUser(key) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.keys.user_signing) { + logger_1.logger.info("No user signing key: not signing user"); + return; + } + return this.signObject(key.keys.master, "user_signing"); + }); + } + signDevice(userId, device) { + return __awaiter(this, void 0, void 0, function* () { + if (userId !== this.userId) { + throw new Error(`Trying to sign ${userId}'s device; can only sign our own device`); + } + if (!this.keys.self_signing) { + logger_1.logger.info("No self signing key: not signing device"); + return; + } + return this.signObject({ + algorithms: device.algorithms, + keys: device.keys, + device_id: device.deviceId, + user_id: userId, + }, "self_signing"); + }); + } + /** + * Check whether a given user is trusted. + * + * @param userCrossSigning - Cross signing info for user + * + * @returns + */ + checkUserTrust(userCrossSigning) { + // if we're checking our own key, then it's trusted if the master key + // and self-signing key match + if (this.userId === userCrossSigning.userId && + this.getId() && + this.getId() === userCrossSigning.getId() && + this.getId("self_signing") && + this.getId("self_signing") === userCrossSigning.getId("self_signing")) { + return new UserTrustLevel(true, true, this.firstUse); + } + if (!this.keys.user_signing) { + // If there's no user signing key, they can't possibly be verified. + // They may be TOFU trusted though. + return new UserTrustLevel(false, false, userCrossSigning.firstUse); + } + let userTrusted; + const userMaster = userCrossSigning.keys.master; + const uskId = this.getId("user_signing"); + try { + (0, olmlib_1.pkVerify)(userMaster, uskId, this.userId); + userTrusted = true; + } + catch (e) { + userTrusted = false; + } + return new UserTrustLevel(userTrusted, userCrossSigning.crossSigningVerifiedBefore, userCrossSigning.firstUse); + } + /** + * Check whether a given device is trusted. + * + * @param userCrossSigning - Cross signing info for user + * @param device - The device to check + * @param localTrust - Whether the device is trusted locally + * @param trustCrossSignedDevices - Whether we trust cross signed devices + * + * @returns + */ + checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) { + const userTrust = this.checkUserTrust(userCrossSigning); + const userSSK = userCrossSigning.keys.self_signing; + if (!userSSK) { + // if the user has no self-signing key then we cannot make any + // trust assertions about this device from cross-signing + return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices); + } + const deviceObj = deviceToObject(device, userCrossSigning.userId); + try { + // if we can verify the user's SSK from their master key... + (0, olmlib_1.pkVerify)(userSSK, userCrossSigning.getId(), userCrossSigning.userId); + // ...and this device's key from their SSK... + (0, olmlib_1.pkVerify)(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId); + // ...then we trust this device as much as far as we trust the user + return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust, trustCrossSignedDevices); + } + catch (e) { + return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices); + } + } + /** + * @returns Cache callbacks + */ + getCacheCallbacks() { + return this.cacheCallbacks; + } +} +exports.CrossSigningInfo = CrossSigningInfo; +function deviceToObject(device, userId) { + return { + algorithms: device.algorithms, + keys: device.keys, + device_id: device.deviceId, + user_id: userId, + signatures: device.signatures, + }; +} +var CrossSigningLevel; +(function (CrossSigningLevel) { + CrossSigningLevel[CrossSigningLevel["MASTER"] = 4] = "MASTER"; + CrossSigningLevel[CrossSigningLevel["USER_SIGNING"] = 2] = "USER_SIGNING"; + CrossSigningLevel[CrossSigningLevel["SELF_SIGNING"] = 1] = "SELF_SIGNING"; +})(CrossSigningLevel = exports.CrossSigningLevel || (exports.CrossSigningLevel = {})); +/** + * Represents the ways in which we trust a user + */ +class UserTrustLevel { + constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) { + this.crossSigningVerified = crossSigningVerified; + this.crossSigningVerifiedBefore = crossSigningVerifiedBefore; + this.tofu = tofu; + } + /** + * @returns true if this user is verified via any means + */ + isVerified() { + return this.isCrossSigningVerified(); + } + /** + * @returns true if this user is verified via cross signing + */ + isCrossSigningVerified() { + return this.crossSigningVerified; + } + /** + * @returns true if we ever verified this user before (at least for + * the history of verifications observed by this device). + */ + wasCrossSigningVerified() { + return this.crossSigningVerifiedBefore; + } + /** + * @returns true if this user's key is trusted on first use + */ + isTofu() { + return this.tofu; + } +} +exports.UserTrustLevel = UserTrustLevel; +/** + * Represents the ways in which we trust a device + */ +class DeviceTrustLevel { + constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices) { + this.crossSigningVerified = crossSigningVerified; + this.tofu = tofu; + this.localVerified = localVerified; + this.trustCrossSignedDevices = trustCrossSignedDevices; + } + static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) { + return new DeviceTrustLevel(userTrustLevel.isCrossSigningVerified(), userTrustLevel.isTofu(), localVerified, trustCrossSignedDevices); + } + /** + * @returns true if this device is verified via any means + */ + isVerified() { + return Boolean(this.isLocallyVerified() || (this.trustCrossSignedDevices && this.isCrossSigningVerified())); + } + /** + * @returns true if this device is verified via cross signing + */ + isCrossSigningVerified() { + return this.crossSigningVerified; + } + /** + * @returns true if this device is verified locally + */ + isLocallyVerified() { + return this.localVerified; + } + /** + * @returns true if this device is trusted from a user's key + * that is trusted on first use + */ + isTofu() { + return this.tofu; + } +} +exports.DeviceTrustLevel = DeviceTrustLevel; +function createCryptoStoreCacheCallbacks(store, olmDevice) { + return { + getCrossSigningKeyCache: function (type, _expectedPublicKey) { + return __awaiter(this, void 0, void 0, function* () { + const key = yield new Promise((resolve) => { + return store.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + store.getSecretStorePrivateKey(txn, resolve, type); + }); + }); + if (key && key.ciphertext) { + const pickleKey = Buffer.from(olmDevice.pickleKey); + const decrypted = yield (0, aes_1.decryptAES)(key, pickleKey, type); + return (0, olmlib_1.decodeBase64)(decrypted); + } + else { + return key; + } + }); + }, + storeCrossSigningKeyCache: function (type, key) { + return __awaiter(this, void 0, void 0, function* () { + if (!(key instanceof Uint8Array)) { + throw new Error(`storeCrossSigningKeyCache expects Uint8Array, got ${key}`); + } + const pickleKey = Buffer.from(olmDevice.pickleKey); + const encryptedKey = yield (0, aes_1.encryptAES)((0, olmlib_1.encodeBase64)(key), pickleKey, type); + return store.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + store.storeSecretStorePrivateKey(txn, type, encryptedKey); + }); + }); + }, + }; +} +exports.createCryptoStoreCacheCallbacks = createCryptoStoreCacheCallbacks; +/** + * Request cross-signing keys from another device during verification. + * + * @param baseApis - base Matrix API interface + * @param userId - The user ID being verified + * @param deviceId - The device ID being verified + */ +function requestKeysDuringVerification(baseApis, userId, deviceId) { + return __awaiter(this, void 0, void 0, function* () { + // If this is a self-verification, ask the other party for keys + if (baseApis.getUserId() !== userId) { + return; + } + logger_1.logger.log("Cross-signing: Self-verification done; requesting keys"); + // This happens asynchronously, and we're not concerned about waiting for + // it. We return here in order to test. + return new Promise((resolve, reject) => { + const client = baseApis; + const original = client.crypto.crossSigningInfo; + // We already have all of the infrastructure we need to validate and + // cache cross-signing keys, so instead of replicating that, here we set + // up callbacks that request them from the other device and call + // CrossSigningInfo.getCrossSigningKey() to validate/cache + const crossSigning = new CrossSigningInfo(original.userId, { + getCrossSigningKey: (type) => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.debug("Cross-signing: requesting secret", type, deviceId); + const { promise } = client.requestSecret(`m.cross_signing.${type}`, [deviceId]); + const result = yield promise; + const decoded = (0, olmlib_1.decodeBase64)(result); + return Uint8Array.from(decoded); + }), + }, original.getCacheCallbacks()); + crossSigning.keys = original.keys; + // XXX: get all keys out if we get one key out + // https://github.com/vector-im/element-web/issues/12604 + // then change here to reject on the timeout + // Requests can be ignored, so don't wait around forever + const timeout = new Promise((resolve) => { + setTimeout(resolve, KEY_REQUEST_TIMEOUT_MS, new Error("Timeout")); + }); + // also request and cache the key backup key + const backupKeyPromise = (() => __awaiter(this, void 0, void 0, function* () { + const cachedKey = yield client.crypto.getSessionBackupPrivateKey(); + if (!cachedKey) { + logger_1.logger.info("No cached backup key found. Requesting..."); + const secretReq = client.requestSecret("m.megolm_backup.v1", [deviceId]); + const base64Key = yield secretReq.promise; + logger_1.logger.info("Got key backup key, decoding..."); + const decodedKey = (0, olmlib_1.decodeBase64)(base64Key); + logger_1.logger.info("Decoded backup key, storing..."); + yield client.crypto.storeSessionBackupPrivateKey(Uint8Array.from(decodedKey)); + logger_1.logger.info("Backup key stored. Starting backup restore..."); + const backupInfo = yield client.getKeyBackupVersion(); + // no need to await for this - just let it go in the bg + client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => { + logger_1.logger.info("Backup restored."); + }); + } + }))(); + // We call getCrossSigningKey() for its side-effects + return Promise.race([ + Promise.all([ + crossSigning.getCrossSigningKey("master"), + crossSigning.getCrossSigningKey("self_signing"), + crossSigning.getCrossSigningKey("user_signing"), + backupKeyPromise, + ]), + timeout, + ]).then(resolve, reject); + }).catch((e) => { + logger_1.logger.warn("Cross-signing: failure while requesting keys:", e); + }); + }); +} +exports.requestKeysDuringVerification = requestKeysDuringVerification; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"../crypto/store/indexeddb-crypto-store":346,"../logger":374,"./aes":331,"./olmlib":343,"buffer":68}],325:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DeviceList = exports.TrackingStatus = void 0; +/** + * Manages the list of other users' devices + */ +const logger_1 = require("../logger"); +const deviceinfo_1 = require("./deviceinfo"); +const CrossSigning_1 = require("./CrossSigning"); +const olmlib = __importStar(require("./olmlib")); +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +const utils_1 = require("../utils"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const index_1 = require("./index"); +/* State transition diagram for DeviceList.deviceTrackingStatus + * + * | + * stopTrackingDeviceList V + * +---------------------> NOT_TRACKED + * | | + * +<--------------------+ | startTrackingDeviceList + * | | V + * | +-------------> PENDING_DOWNLOAD <--------------------+-+ + * | | ^ | | | + * | | restart download | | start download | | invalidateUserDeviceList + * | | client failed | | | | + * | | | V | | + * | +------------ DOWNLOAD_IN_PROGRESS -------------------+ | + * | | | | + * +<-------------------+ | download successful | + * ^ V | + * +----------------------- UP_TO_DATE ------------------------+ + */ +// constants for DeviceList.deviceTrackingStatus +var TrackingStatus; +(function (TrackingStatus) { + TrackingStatus[TrackingStatus["NotTracked"] = 0] = "NotTracked"; + TrackingStatus[TrackingStatus["PendingDownload"] = 1] = "PendingDownload"; + TrackingStatus[TrackingStatus["DownloadInProgress"] = 2] = "DownloadInProgress"; + TrackingStatus[TrackingStatus["UpToDate"] = 3] = "UpToDate"; +})(TrackingStatus = exports.TrackingStatus || (exports.TrackingStatus = {})); +class DeviceList extends typed_event_emitter_1.TypedEventEmitter { + constructor(baseApis, cryptoStore, olmDevice, + // Maximum number of user IDs per request to prevent server overload (#1619) + keyDownloadChunkSize = 250) { + super(); + this.cryptoStore = cryptoStore; + this.keyDownloadChunkSize = keyDownloadChunkSize; + this.devices = {}; + this.crossSigningInfo = {}; + // map of identity keys to the user who owns it + this.userByIdentityKey = {}; + // which users we are tracking device status for. + this.deviceTrackingStatus = {}; // loaded from storage in load() + // The 'next_batch' sync token at the point the data was written, + // ie. a token representing the point immediately after the + // moment represented by the snapshot in the db. + this.syncToken = null; + this.keyDownloadsInProgressByUser = new Map(); + // Set whenever changes are made other than setting the sync token + this.dirty = false; + // Promise resolved when device data is saved + this.savePromise = null; + // Function that resolves the save promise + this.resolveSavePromise = null; + // The time the save is scheduled for + this.savePromiseTime = null; + // The timer used to delay the save + this.saveTimer = null; + // True if we have fetched data from the server or loaded a non-empty + // set of device data from the store + this.hasFetched = null; + this.serialiser = new DeviceListUpdateSerialiser(baseApis, olmDevice, this); + } + /** + * Load the device tracking state from storage + */ + load() { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => { + this.cryptoStore.getEndToEndDeviceData(txn, (deviceData) => { + var _a; + this.hasFetched = Boolean(deviceData && deviceData.devices); + this.devices = deviceData ? deviceData.devices : {}; + this.crossSigningInfo = deviceData ? deviceData.crossSigningInfo || {} : {}; + this.deviceTrackingStatus = deviceData ? deviceData.trackingStatus : {}; + this.syncToken = (_a = deviceData === null || deviceData === void 0 ? void 0 : deviceData.syncToken) !== null && _a !== void 0 ? _a : null; + this.userByIdentityKey = {}; + for (const user of Object.keys(this.devices)) { + const userDevices = this.devices[user]; + for (const device of Object.keys(userDevices)) { + const idKey = userDevices[device].keys["curve25519:" + device]; + if (idKey !== undefined) { + this.userByIdentityKey[idKey] = user; + } + } + } + }); + }); + for (const u of Object.keys(this.deviceTrackingStatus)) { + // if a download was in progress when we got shut down, it isn't any more. + if (this.deviceTrackingStatus[u] == TrackingStatus.DownloadInProgress) { + this.deviceTrackingStatus[u] = TrackingStatus.PendingDownload; + } + } + }); + } + stop() { + if (this.saveTimer !== null) { + clearTimeout(this.saveTimer); + } + } + /** + * Save the device tracking state to storage, if any changes are + * pending other than updating the sync token + * + * The actual save will be delayed by a short amount of time to + * aggregate multiple writes to the database. + * + * @param delay - Time in ms before which the save actually happens. + * By default, the save is delayed for a short period in order to batch + * multiple writes, but this behaviour can be disabled by passing 0. + * + * @returns true if the data was saved, false if + * it was not (eg. because no changes were pending). The promise + * will only resolve once the data is saved, so may take some time + * to resolve. + */ + saveIfDirty(delay = 500) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.dirty) + return Promise.resolve(false); + // Delay saves for a bit so we can aggregate multiple saves that happen + // in quick succession (eg. when a whole room's devices are marked as known) + const targetTime = Date.now() + delay; + if (this.savePromiseTime && targetTime < this.savePromiseTime) { + // There's a save scheduled but for after we would like: cancel + // it & schedule one for the time we want + clearTimeout(this.saveTimer); + this.saveTimer = null; + this.savePromiseTime = null; + // (but keep the save promise since whatever called save before + // will still want to know when the save is done) + } + let savePromise = this.savePromise; + if (savePromise === null) { + savePromise = new Promise((resolve) => { + this.resolveSavePromise = resolve; + }); + this.savePromise = savePromise; + } + if (this.saveTimer === null) { + const resolveSavePromise = this.resolveSavePromise; + this.savePromiseTime = targetTime; + this.saveTimer = setTimeout(() => { + logger_1.logger.log("Saving device tracking data", this.syncToken); + // null out savePromise now (after the delay but before the write), + // otherwise we could return the existing promise when the save has + // actually already happened. + this.savePromiseTime = null; + this.saveTimer = null; + this.savePromise = null; + this.resolveSavePromise = null; + this.cryptoStore + .doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => { + var _a; + this.cryptoStore.storeEndToEndDeviceData({ + devices: this.devices, + crossSigningInfo: this.crossSigningInfo, + trackingStatus: this.deviceTrackingStatus, + syncToken: (_a = this.syncToken) !== null && _a !== void 0 ? _a : undefined, + }, txn); + }) + .then(() => { + // The device list is considered dirty until the write completes. + this.dirty = false; + resolveSavePromise === null || resolveSavePromise === void 0 ? void 0 : resolveSavePromise(true); + }, (err) => { + logger_1.logger.error("Failed to save device tracking data", this.syncToken); + logger_1.logger.error(err); + }); + }, delay); + } + return savePromise; + }); + } + /** + * Gets the sync token last set with setSyncToken + * + * @returns The sync token + */ + getSyncToken() { + return this.syncToken; + } + /** + * Sets the sync token that the app will pass as the 'since' to the /sync + * endpoint next time it syncs. + * The sync token must always be set after any changes made as a result of + * data in that sync since setting the sync token to a newer one will mean + * those changed will not be synced from the server if a new client starts + * up with that data. + * + * @param st - The sync token + */ + setSyncToken(st) { + this.syncToken = st; + } + /** + * Ensures up to date keys for a list of users are stored in the session store, + * downloading and storing them if they're not (or if forceDownload is + * true). + * @param userIds - The users to fetch. + * @param forceDownload - Always download the keys even if cached. + * + * @returns A promise which resolves to a map userId-\>deviceId-\>{@link DeviceInfo}. + */ + downloadKeys(userIds, forceDownload) { + const usersToDownload = []; + const promises = []; + userIds.forEach((u) => { + const trackingStatus = this.deviceTrackingStatus[u]; + if (this.keyDownloadsInProgressByUser.has(u)) { + // already a key download in progress/queued for this user; its results + // will be good enough for us. + logger_1.logger.log(`downloadKeys: already have a download in progress for ` + `${u}: awaiting its result`); + promises.push(this.keyDownloadsInProgressByUser.get(u)); + } + else if (forceDownload || trackingStatus != TrackingStatus.UpToDate) { + usersToDownload.push(u); + } + }); + if (usersToDownload.length != 0) { + logger_1.logger.log("downloadKeys: downloading for", usersToDownload); + const downloadPromise = this.doKeyDownload(usersToDownload); + promises.push(downloadPromise); + } + if (promises.length === 0) { + logger_1.logger.log("downloadKeys: already have all necessary keys"); + } + return Promise.all(promises).then(() => { + return this.getDevicesFromStore(userIds); + }); + } + /** + * Get the stored device keys for a list of user ids + * + * @param userIds - the list of users to list keys for. + * + * @returns userId-\>deviceId-\>{@link DeviceInfo}. + */ + getDevicesFromStore(userIds) { + const stored = new Map(); + userIds.forEach((userId) => { + var _a; + const deviceMap = new Map(); + (_a = this.getStoredDevicesForUser(userId)) === null || _a === void 0 ? void 0 : _a.forEach(function (device) { + deviceMap.set(device.deviceId, device); + }); + stored.set(userId, deviceMap); + }); + return stored; + } + /** + * Returns a list of all user IDs the DeviceList knows about + * + * @returns All known user IDs + */ + getKnownUserIds() { + return Object.keys(this.devices); + } + /** + * Get the stored device keys for a user id + * + * @param userId - the user to list keys for. + * + * @returns list of devices, or null if we haven't + * managed to get a list of devices for this user yet. + */ + getStoredDevicesForUser(userId) { + const devs = this.devices[userId]; + if (!devs) { + return null; + } + const res = []; + for (const deviceId in devs) { + if (devs.hasOwnProperty(deviceId)) { + res.push(deviceinfo_1.DeviceInfo.fromStorage(devs[deviceId], deviceId)); + } + } + return res; + } + /** + * Get the stored device data for a user, in raw object form + * + * @param userId - the user to get data for + * + * @returns `deviceId->{object}` devices, or undefined if + * there is no data for this user. + */ + getRawStoredDevicesForUser(userId) { + return this.devices[userId]; + } + getStoredCrossSigningForUser(userId) { + if (!this.crossSigningInfo[userId]) + return null; + return CrossSigning_1.CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId); + } + storeCrossSigningForUser(userId, info) { + this.crossSigningInfo[userId] = info; + this.dirty = true; + } + /** + * Get the stored keys for a single device + * + * + * @returns device, or undefined + * if we don't know about this device + */ + getStoredDevice(userId, deviceId) { + const devs = this.devices[userId]; + if (!(devs === null || devs === void 0 ? void 0 : devs[deviceId])) { + return undefined; + } + return deviceinfo_1.DeviceInfo.fromStorage(devs[deviceId], deviceId); + } + /** + * Get a user ID by one of their device's curve25519 identity key + * + * @param algorithm - encryption algorithm + * @param senderKey - curve25519 key to match + * + * @returns user ID + */ + getUserByIdentityKey(algorithm, senderKey) { + if (algorithm !== olmlib.OLM_ALGORITHM && algorithm !== olmlib.MEGOLM_ALGORITHM) { + // we only deal in olm keys + return null; + } + return this.userByIdentityKey[senderKey]; + } + /** + * Find a device by curve25519 identity key + * + * @param algorithm - encryption algorithm + * @param senderKey - curve25519 key to match + */ + getDeviceByIdentityKey(algorithm, senderKey) { + const userId = this.getUserByIdentityKey(algorithm, senderKey); + if (!userId) { + return null; + } + const devices = this.devices[userId]; + if (!devices) { + return null; + } + for (const deviceId in devices) { + if (!devices.hasOwnProperty(deviceId)) { + continue; + } + const device = devices[deviceId]; + for (const keyId in device.keys) { + if (!device.keys.hasOwnProperty(keyId)) { + continue; + } + if (keyId.indexOf("curve25519:") !== 0) { + continue; + } + const deviceKey = device.keys[keyId]; + if (deviceKey == senderKey) { + return deviceinfo_1.DeviceInfo.fromStorage(device, deviceId); + } + } + } + // doesn't match a known device + return null; + } + /** + * Replaces the list of devices for a user with the given device list + * + * @param userId - The user ID + * @param devices - New device info for user + */ + storeDevicesForUser(userId, devices) { + this.setRawStoredDevicesForUser(userId, devices); + this.dirty = true; + } + /** + * flag the given user for device-list tracking, if they are not already. + * + * This will mean that a subsequent call to refreshOutdatedDeviceLists() + * will download the device list for the user, and that subsequent calls to + * invalidateUserDeviceList will trigger more updates. + * + */ + startTrackingDeviceList(userId) { + // sanity-check the userId. This is mostly paranoia, but if synapse + // can't parse the userId we give it as an mxid, it 500s the whole + // request and we can never update the device lists again (because + // the broken userId is always 'invalid' and always included in any + // refresh request). + // By checking it is at least a string, we can eliminate a class of + // silly errors. + if (typeof userId !== "string") { + throw new Error("userId must be a string; was " + userId); + } + if (!this.deviceTrackingStatus[userId]) { + logger_1.logger.log("Now tracking device list for " + userId); + this.deviceTrackingStatus[userId] = TrackingStatus.PendingDownload; + // we don't yet persist the tracking status, since there may be a lot + // of calls; we save all data together once the sync is done + this.dirty = true; + } + } + /** + * Mark the given user as no longer being tracked for device-list updates. + * + * This won't affect any in-progress downloads, which will still go on to + * complete; it will just mean that we don't think that we have an up-to-date + * list for future calls to downloadKeys. + * + */ + stopTrackingDeviceList(userId) { + if (this.deviceTrackingStatus[userId]) { + logger_1.logger.log("No longer tracking device list for " + userId); + this.deviceTrackingStatus[userId] = TrackingStatus.NotTracked; + // we don't yet persist the tracking status, since there may be a lot + // of calls; we save all data together once the sync is done + this.dirty = true; + } + } + /** + * Set all users we're currently tracking to untracked + * + * This will flag each user whose devices we are tracking as in need of an + * update. + */ + stopTrackingAllDeviceLists() { + for (const userId of Object.keys(this.deviceTrackingStatus)) { + this.deviceTrackingStatus[userId] = TrackingStatus.NotTracked; + } + this.dirty = true; + } + /** + * Mark the cached device list for the given user outdated. + * + * If we are not tracking this user's devices, we'll do nothing. Otherwise + * we flag the user as needing an update. + * + * This doesn't actually set off an update, so that several users can be + * batched together. Call refreshOutdatedDeviceLists() for that. + * + */ + invalidateUserDeviceList(userId) { + if (this.deviceTrackingStatus[userId]) { + logger_1.logger.log("Marking device list outdated for", userId); + this.deviceTrackingStatus[userId] = TrackingStatus.PendingDownload; + // we don't yet persist the tracking status, since there may be a lot + // of calls; we save all data together once the sync is done + this.dirty = true; + } + } + /** + * If we have users who have outdated device lists, start key downloads for them + * + * @returns which completes when the download completes; normally there + * is no need to wait for this (it's mostly for the unit tests). + */ + refreshOutdatedDeviceLists() { + this.saveIfDirty(); + const usersToDownload = []; + for (const userId of Object.keys(this.deviceTrackingStatus)) { + const stat = this.deviceTrackingStatus[userId]; + if (stat == TrackingStatus.PendingDownload) { + usersToDownload.push(userId); + } + } + return this.doKeyDownload(usersToDownload); + } + /** + * Set the stored device data for a user, in raw object form + * Used only by internal class DeviceListUpdateSerialiser + * + * @param userId - the user to get data for + * + * @param devices - `deviceId->{object}` the new devices + */ + setRawStoredDevicesForUser(userId, devices) { + // remove old devices from userByIdentityKey + if (this.devices[userId] !== undefined) { + for (const [deviceId, dev] of Object.entries(this.devices[userId])) { + const identityKey = dev.keys["curve25519:" + deviceId]; + delete this.userByIdentityKey[identityKey]; + } + } + this.devices[userId] = devices; + // add new devices into userByIdentityKey + for (const [deviceId, dev] of Object.entries(devices)) { + const identityKey = dev.keys["curve25519:" + deviceId]; + this.userByIdentityKey[identityKey] = userId; + } + } + setRawStoredCrossSigningForUser(userId, info) { + this.crossSigningInfo[userId] = info; + } + /** + * Fire off download update requests for the given users, and update the + * device list tracking status for them, and the + * keyDownloadsInProgressByUser map for them. + * + * @param users - list of userIds + * + * @returns resolves when all the users listed have + * been updated. rejects if there was a problem updating any of the + * users. + */ + doKeyDownload(users) { + if (users.length === 0) { + // nothing to do + return Promise.resolve(); + } + const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken).then(() => { + finished(true); + }, (e) => { + logger_1.logger.error("Error downloading keys for " + users + ":", e); + finished(false); + throw e; + }); + users.forEach((u) => { + this.keyDownloadsInProgressByUser.set(u, prom); + const stat = this.deviceTrackingStatus[u]; + if (stat == TrackingStatus.PendingDownload) { + this.deviceTrackingStatus[u] = TrackingStatus.DownloadInProgress; + } + }); + const finished = (success) => { + this.emit(index_1.CryptoEvent.WillUpdateDevices, users, !this.hasFetched); + users.forEach((u) => { + this.dirty = true; + // we may have queued up another download request for this user + // since we started this request. If that happens, we should + // ignore the completion of the first one. + if (this.keyDownloadsInProgressByUser.get(u) !== prom) { + logger_1.logger.log("Another update in the queue for", u, "- not marking up-to-date"); + return; + } + this.keyDownloadsInProgressByUser.delete(u); + const stat = this.deviceTrackingStatus[u]; + if (stat == TrackingStatus.DownloadInProgress) { + if (success) { + // we didn't get any new invalidations since this download started: + // this user's device list is now up to date. + this.deviceTrackingStatus[u] = TrackingStatus.UpToDate; + logger_1.logger.log("Device list for", u, "now up to date"); + } + else { + this.deviceTrackingStatus[u] = TrackingStatus.PendingDownload; + } + } + }); + this.saveIfDirty(); + this.emit(index_1.CryptoEvent.DevicesUpdated, users, !this.hasFetched); + this.hasFetched = true; + }; + return prom; + } +} +exports.DeviceList = DeviceList; +/** + * Serialises updates to device lists + * + * Ensures that results from /keys/query are not overwritten if a second call + * completes *before* an earlier one. + * + * It currently does this by ensuring only one call to /keys/query happens at a + * time (and queuing other requests up). + */ +class DeviceListUpdateSerialiser { + /* + * @param baseApis - Base API object + * @param olmDevice - The Olm Device + * @param deviceList - The device list object, the device list to be updated + */ + constructor(baseApis, olmDevice, deviceList) { + this.baseApis = baseApis; + this.olmDevice = olmDevice; + this.deviceList = deviceList; + this.downloadInProgress = false; + // users which are queued for download + // userId -> true + this.keyDownloadsQueuedByUser = {}; + } + /** + * Make a key query request for the given users + * + * @param users - list of user ids + * + * @param syncToken - sync token to pass in the query request, to + * help the HS give the most recent results + * + * @returns resolves when all the users listed have + * been updated. rejects if there was a problem updating any of the + * users. + */ + updateDevicesForUsers(users, syncToken) { + users.forEach((u) => { + this.keyDownloadsQueuedByUser[u] = true; + }); + if (!this.queuedQueryDeferred) { + this.queuedQueryDeferred = (0, utils_1.defer)(); + } + // We always take the new sync token and just use the latest one we've + // been given, since it just needs to be at least as recent as the + // sync response the device invalidation message arrived in + this.syncToken = syncToken; + if (this.downloadInProgress) { + // just queue up these users + logger_1.logger.log("Queued key download for", users); + return this.queuedQueryDeferred.promise; + } + // start a new download. + return this.doQueuedQueries(); + } + doQueuedQueries() { + if (this.downloadInProgress) { + throw new Error("DeviceListUpdateSerialiser.doQueuedQueries called with request active"); + } + const downloadUsers = Object.keys(this.keyDownloadsQueuedByUser); + this.keyDownloadsQueuedByUser = {}; + const deferred = this.queuedQueryDeferred; + this.queuedQueryDeferred = undefined; + logger_1.logger.log("Starting key download for", downloadUsers); + this.downloadInProgress = true; + const opts = {}; + if (this.syncToken) { + opts.token = this.syncToken; + } + const factories = []; + for (let i = 0; i < downloadUsers.length; i += this.deviceList.keyDownloadChunkSize) { + const userSlice = downloadUsers.slice(i, i + this.deviceList.keyDownloadChunkSize); + factories.push(() => this.baseApis.downloadKeysForUsers(userSlice, opts)); + } + (0, utils_1.chunkPromises)(factories, 3) + .then((responses) => __awaiter(this, void 0, void 0, function* () { + const dk = Object.assign({}, ...responses.map((res) => res.device_keys || {})); + const masterKeys = Object.assign({}, ...responses.map((res) => res.master_keys || {})); + const ssks = Object.assign({}, ...responses.map((res) => res.self_signing_keys || {})); + const usks = Object.assign({}, ...responses.map((res) => res.user_signing_keys || {})); + // yield to other things that want to execute in between users, to + // avoid wedging the CPU + // (https://github.com/vector-im/element-web/issues/3158) + // + // of course we ought to do this in a web worker or similar, but + // this serves as an easy solution for now. + for (const userId of downloadUsers) { + yield (0, utils_1.sleep)(5); + try { + yield this.processQueryResponseForUser(userId, dk[userId], { + master: masterKeys === null || masterKeys === void 0 ? void 0 : masterKeys[userId], + self_signing: ssks === null || ssks === void 0 ? void 0 : ssks[userId], + user_signing: usks === null || usks === void 0 ? void 0 : usks[userId], + }); + } + catch (e) { + // log the error but continue, so that one bad key + // doesn't kill the whole process + logger_1.logger.error(`Error processing keys for ${userId}:`, e); + } + } + })) + .then(() => { + logger_1.logger.log("Completed key download for " + downloadUsers); + this.downloadInProgress = false; + deferred === null || deferred === void 0 ? void 0 : deferred.resolve(); + // if we have queued users, fire off another request. + if (this.queuedQueryDeferred) { + this.doQueuedQueries(); + } + }, (e) => { + logger_1.logger.warn("Error downloading keys for " + downloadUsers + ":", e); + this.downloadInProgress = false; + deferred === null || deferred === void 0 ? void 0 : deferred.reject(e); + }); + return deferred.promise; + } + processQueryResponseForUser(userId, dkResponse, crossSigningResponse) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log("got device keys for " + userId + ":", dkResponse); + logger_1.logger.log("got cross-signing keys for " + userId + ":", crossSigningResponse); + { + // map from deviceid -> deviceinfo for this user + const userStore = {}; + const devs = this.deviceList.getRawStoredDevicesForUser(userId); + if (devs) { + Object.keys(devs).forEach((deviceId) => { + const d = deviceinfo_1.DeviceInfo.fromStorage(devs[deviceId], deviceId); + userStore[deviceId] = d; + }); + } + yield updateStoredDeviceKeysForUser(this.olmDevice, userId, userStore, dkResponse || {}, this.baseApis.getUserId(), this.baseApis.deviceId); + // put the updates into the object that will be returned as our results + const storage = {}; + Object.keys(userStore).forEach((deviceId) => { + storage[deviceId] = userStore[deviceId].toStorage(); + }); + this.deviceList.setRawStoredDevicesForUser(userId, storage); + } + // now do the same for the cross-signing keys + { + // FIXME: should we be ignoring empty cross-signing responses, or + // should we be dropping the keys? + if (crossSigningResponse && + (crossSigningResponse.master || crossSigningResponse.self_signing || crossSigningResponse.user_signing)) { + const crossSigning = this.deviceList.getStoredCrossSigningForUser(userId) || new CrossSigning_1.CrossSigningInfo(userId); + crossSigning.setKeys(crossSigningResponse); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); + // NB. Unlike most events in the js-sdk, this one is internal to the + // js-sdk and is not re-emitted + this.deviceList.emit(index_1.CryptoEvent.UserCrossSigningUpdated, userId); + } + } + }); + } +} +function updateStoredDeviceKeysForUser(olmDevice, userId, userStore, userResult, localUserId, localDeviceId) { + return __awaiter(this, void 0, void 0, function* () { + let updated = false; + // remove any devices in the store which aren't in the response + for (const deviceId in userStore) { + if (!userStore.hasOwnProperty(deviceId)) { + continue; + } + if (!(deviceId in userResult)) { + if (userId === localUserId && deviceId === localDeviceId) { + logger_1.logger.warn(`Local device ${deviceId} missing from sync, skipping removal`); + continue; + } + logger_1.logger.log("Device " + userId + ":" + deviceId + " has been removed"); + delete userStore[deviceId]; + updated = true; + } + } + for (const deviceId in userResult) { + if (!userResult.hasOwnProperty(deviceId)) { + continue; + } + const deviceResult = userResult[deviceId]; + // check that the user_id and device_id in the response object are + // correct + if (deviceResult.user_id !== userId) { + logger_1.logger.warn("Mismatched user_id " + deviceResult.user_id + " in keys from " + userId + ":" + deviceId); + continue; + } + if (deviceResult.device_id !== deviceId) { + logger_1.logger.warn("Mismatched device_id " + deviceResult.device_id + " in keys from " + userId + ":" + deviceId); + continue; + } + if (yield storeDeviceKeys(olmDevice, userStore, deviceResult)) { + updated = true; + } + } + return updated; + }); +} +/* + * Process a device in a /query response, and add it to the userStore + * + * returns (a promise for) true if a change was made, else false + */ +function storeDeviceKeys(olmDevice, userStore, deviceResult) { + return __awaiter(this, void 0, void 0, function* () { + if (!deviceResult.keys) { + // no keys? + return false; + } + const deviceId = deviceResult.device_id; + const userId = deviceResult.user_id; + const signKeyId = "ed25519:" + deviceId; + const signKey = deviceResult.keys[signKeyId]; + if (!signKey) { + logger_1.logger.warn("Device " + userId + ":" + deviceId + " has no ed25519 key"); + return false; + } + const unsigned = deviceResult.unsigned || {}; + const signatures = deviceResult.signatures || {}; + try { + yield olmlib.verifySignature(olmDevice, deviceResult, userId, deviceId, signKey); + } + catch (e) { + logger_1.logger.warn("Unable to verify signature on device " + userId + ":" + deviceId + ":" + e); + return false; + } + // DeviceInfo + let deviceStore; + if (deviceId in userStore) { + // already have this device. + deviceStore = userStore[deviceId]; + if (deviceStore.getFingerprint() != signKey) { + // this should only happen if the list has been MITMed; we are + // best off sticking with the original keys. + // + // Should we warn the user about it somehow? + logger_1.logger.warn("Ed25519 key for device " + userId + ":" + deviceId + " has changed"); + return false; + } + } + else { + userStore[deviceId] = deviceStore = new deviceinfo_1.DeviceInfo(deviceId); + } + deviceStore.keys = deviceResult.keys || {}; + deviceStore.algorithms = deviceResult.algorithms || []; + deviceStore.unsigned = unsigned; + deviceStore.signatures = signatures; + return true; + }); +} + +},{"../logger":374,"../models/typed-event-emitter":395,"../utils":416,"./CrossSigning":324,"./deviceinfo":340,"./index":341,"./olmlib":343,"./store/indexeddb-crypto-store":346}],326:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EncryptionSetupOperation = exports.EncryptionSetupBuilder = void 0; +const logger_1 = require("../logger"); +const event_1 = require("../models/event"); +const CrossSigning_1 = require("./CrossSigning"); +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +const http_api_1 = require("../http-api"); +const client_1 = require("../client"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +/** + * Builds an EncryptionSetupOperation by calling any of the add.. methods. + * Once done, `buildOperation()` can be called which allows to apply to operation. + * + * This is used as a helper by Crypto to keep track of all the network requests + * and other side-effects of bootstrapping, so it can be applied in one go (and retried in the future) + * Also keeps track of all the private keys created during bootstrapping, so we don't need to prompt for them + * more than once. + */ +class EncryptionSetupBuilder { + /** + * @param accountData - pre-existing account data, will only be read, not written. + * @param delegateCryptoCallbacks - crypto callbacks to delegate to if the key isn't in cache yet + */ + constructor(accountData, delegateCryptoCallbacks) { + this.accountDataClientAdapter = new AccountDataClientAdapter(accountData); + this.crossSigningCallbacks = new CrossSigningCallbacks(); + this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks); + } + /** + * Adds new cross-signing public keys + * + * @param authUpload - Function called to await an interactive auth + * flow when uploading device signing keys. + * Args: + * A function that makes the request requiring auth. Receives + * the auth data as an object. Can be called multiple times, first with + * an empty authDict, to obtain the flows. + * @param keys - the new keys + */ + addCrossSigningKeys(authUpload, keys) { + this.crossSigningKeys = { authUpload, keys }; + } + /** + * Adds the key backup info to be updated on the server + * + * Used either to create a new key backup, or add signatures + * from the new MSK. + * + * @param keyBackupInfo - as received from/sent to the server + */ + addSessionBackup(keyBackupInfo) { + this.keyBackupInfo = keyBackupInfo; + } + /** + * Adds the session backup private key to be updated in the local cache + * + * Used after fixing the format of the key + * + */ + addSessionBackupPrivateKeyToCache(privateKey) { + this.sessionBackupPrivateKey = privateKey; + } + /** + * Add signatures from a given user and device/x-sign key + * Used to sign the new cross-signing key with the device key + * + */ + addKeySignature(userId, deviceId, signature) { + if (!this.keySignatures) { + this.keySignatures = {}; + } + const userSignatures = this.keySignatures[userId] || {}; + this.keySignatures[userId] = userSignatures; + userSignatures[deviceId] = signature; + } + setAccountData(type, content) { + return __awaiter(this, void 0, void 0, function* () { + yield this.accountDataClientAdapter.setAccountData(type, content); + }); + } + /** + * builds the operation containing all the parts that have been added to the builder + */ + buildOperation() { + const accountData = this.accountDataClientAdapter.values; + return new EncryptionSetupOperation(accountData, this.crossSigningKeys, this.keyBackupInfo, this.keySignatures); + } + /** + * Stores the created keys locally. + * + * This does not yet store the operation in a way that it can be restored, + * but that is the idea in the future. + */ + persist(crypto) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // store private keys in cache + if (this.crossSigningKeys) { + const cacheCallbacks = (0, CrossSigning_1.createCryptoStoreCacheCallbacks)(crypto.cryptoStore, crypto.olmDevice); + for (const type of ["master", "self_signing", "user_signing"]) { + logger_1.logger.log(`Cache ${type} cross-signing private key locally`); + const privateKey = this.crossSigningCallbacks.privateKeys.get(type); + yield ((_a = cacheCallbacks.storeCrossSigningKeyCache) === null || _a === void 0 ? void 0 : _a.call(cacheCallbacks, type, privateKey)); + } + // store own cross-sign pubkeys as trusted + yield crypto.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + crypto.cryptoStore.storeCrossSigningKeys(txn, this.crossSigningKeys.keys); + }); + } + // store session backup key in cache + if (this.sessionBackupPrivateKey) { + yield crypto.storeSessionBackupPrivateKey(this.sessionBackupPrivateKey); + } + }); + } +} +exports.EncryptionSetupBuilder = EncryptionSetupBuilder; +/** + * Can be created from EncryptionSetupBuilder, or + * (in a follow-up PR, not implemented yet) restored from storage, to retry. + * + * It does not have knowledge of any private keys, unlike the builder. + */ +class EncryptionSetupOperation { + /** + */ + constructor(accountData, crossSigningKeys, keyBackupInfo, keySignatures) { + this.accountData = accountData; + this.crossSigningKeys = crossSigningKeys; + this.keyBackupInfo = keyBackupInfo; + this.keySignatures = keySignatures; + } + /** + * Runs the (remaining part of, in the future) operation by sending requests to the server. + */ + apply(crypto) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const baseApis = crypto.baseApis; + // upload cross-signing keys + if (this.crossSigningKeys) { + const keys = {}; + for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) { + keys[(name + "_key")] = key; + } + // We must only call `uploadDeviceSigningKeys` from inside this auth + // helper to ensure we properly handle auth errors. + yield ((_b = (_a = this.crossSigningKeys).authUpload) === null || _b === void 0 ? void 0 : _b.call(_a, (authDict) => { + return baseApis.uploadDeviceSigningKeys(authDict, keys); + })); + // pass the new keys to the main instance of our own CrossSigningInfo. + crypto.crossSigningInfo.setKeys(this.crossSigningKeys.keys); + } + // set account data + if (this.accountData) { + for (const [type, content] of this.accountData) { + yield baseApis.setAccountData(type, content); + } + } + // upload first cross-signing signatures with the new key + // (e.g. signing our own device) + if (this.keySignatures) { + yield baseApis.uploadKeySignatures(this.keySignatures); + } + // need to create/update key backup info + if (this.keyBackupInfo) { + if (this.keyBackupInfo.version) { + // session backup signature + // The backup is trusted because the user provided the private key. + // Sign the backup with the cross signing key so the key backup can + // be trusted via cross-signing. + yield baseApis.http.authedRequest(http_api_1.Method.Put, "/room_keys/version/" + this.keyBackupInfo.version, undefined, { + algorithm: this.keyBackupInfo.algorithm, + auth_data: this.keyBackupInfo.auth_data, + }, { prefix: http_api_1.ClientPrefix.V3 }); + } + else { + // add new key backup + yield baseApis.http.authedRequest(http_api_1.Method.Post, "/room_keys/version", undefined, this.keyBackupInfo, { + prefix: http_api_1.ClientPrefix.V3, + }); + } + } + }); + } +} +exports.EncryptionSetupOperation = EncryptionSetupOperation; +/** + * Catches account data set by SecretStorage during bootstrapping by + * implementing the methods related to account data in MatrixClient + */ +class AccountDataClientAdapter extends typed_event_emitter_1.TypedEventEmitter { + /** + * @param existingValues - existing account data + */ + constructor(existingValues) { + super(); + this.existingValues = existingValues; + // + this.values = new Map(); + } + /** + * @returns the content of the account data + */ + getAccountDataFromServer(type) { + return Promise.resolve(this.getAccountData(type)); + } + /** + * @returns the content of the account data + */ + getAccountData(type) { + const modifiedValue = this.values.get(type); + if (modifiedValue) { + return modifiedValue; + } + const existingValue = this.existingValues.get(type); + if (existingValue) { + return existingValue.getContent(); + } + return null; + } + setAccountData(type, content) { + const lastEvent = this.values.get(type); + this.values.set(type, content); + // ensure accountData is emitted on the next tick, + // as SecretStorage listens for it while calling this method + // and it seems to rely on this. + return Promise.resolve().then(() => { + const event = new event_1.MatrixEvent({ type, content }); + this.emit(client_1.ClientEvent.AccountData, event, lastEvent); + return {}; + }); + } +} +/** + * Catches the private cross-signing keys set during bootstrapping + * by both cache callbacks (see createCryptoStoreCacheCallbacks) as non-cache callbacks. + * See CrossSigningInfo constructor + */ +class CrossSigningCallbacks { + constructor() { + this.privateKeys = new Map(); + } + // cache callbacks + getCrossSigningKeyCache(type, expectedPublicKey) { + return this.getCrossSigningKey(type, expectedPublicKey); + } + storeCrossSigningKeyCache(type, key) { + this.privateKeys.set(type, key); + return Promise.resolve(); + } + // non-cache callbacks + getCrossSigningKey(type, expectedPubkey) { + var _a; + return Promise.resolve((_a = this.privateKeys.get(type)) !== null && _a !== void 0 ? _a : null); + } + saveCrossSigningKeys(privateKeys) { + for (const [type, privateKey] of Object.entries(privateKeys)) { + this.privateKeys.set(type, privateKey); + } + } +} +/** + * Catches the 4S private key set during bootstrapping by implementing + * the SecretStorage crypto callbacks + */ +class SSSSCryptoCallbacks { + constructor(delegateCryptoCallbacks) { + this.delegateCryptoCallbacks = delegateCryptoCallbacks; + this.privateKeys = new Map(); + } + getSecretStorageKey({ keys }, name) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + for (const keyId of Object.keys(keys)) { + const privateKey = this.privateKeys.get(keyId); + if (privateKey) { + return [keyId, privateKey]; + } + } + // if we don't have the key cached yet, ask + // for it to the general crypto callbacks and cache it + if ((_a = this === null || this === void 0 ? void 0 : this.delegateCryptoCallbacks) === null || _a === void 0 ? void 0 : _a.getSecretStorageKey) { + const result = yield this.delegateCryptoCallbacks.getSecretStorageKey({ keys }, name); + if (result) { + const [keyId, privateKey] = result; + this.privateKeys.set(keyId, privateKey); + } + return result; + } + return null; + }); + } + addPrivateKey(keyId, keyInfo, privKey) { + var _a, _b; + this.privateKeys.set(keyId, privKey); + // Also pass along to application to cache if it wishes + (_b = (_a = this.delegateCryptoCallbacks) === null || _a === void 0 ? void 0 : _a.cacheSecretStorageKey) === null || _b === void 0 ? void 0 : _b.call(_a, keyId, keyInfo, privKey); + } +} + +},{"../client":321,"../http-api":367,"../logger":374,"../models/event":383,"../models/typed-event-emitter":395,"./CrossSigning":324,"./store/indexeddb-crypto-store":346}],327:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WITHHELD_MESSAGES = exports.OlmDevice = exports.PayloadTooLargeError = void 0; +const logger_1 = require("../logger"); +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +const algorithms = __importStar(require("./algorithms")); +// The maximum size of an event is 65K, and we base64 the content, so this is a +// reasonable approximation to the biggest plaintext we can encrypt. +const MAX_PLAINTEXT_LENGTH = (65536 * 3) / 4; +class PayloadTooLargeError extends Error { + constructor() { + super(...arguments); + this.data = { + errcode: "M_TOO_LARGE", + error: "Payload too large for encrypted message", + }; + } +} +exports.PayloadTooLargeError = PayloadTooLargeError; +function checkPayloadLength(payloadString) { + if (payloadString === undefined) { + throw new Error("payloadString undefined"); + } + if (payloadString.length > MAX_PLAINTEXT_LENGTH) { + // might as well fail early here rather than letting the olm library throw + // a cryptic memory allocation error. + // + // Note that even if we manage to do the encryption, the message send may fail, + // because by the time we've wrapped the ciphertext in the event object, it may + // exceed 65K. But at least we won't just fail with "abort()" in that case. + throw new PayloadTooLargeError(`Message too long (${payloadString.length} bytes). ` + + `The maximum for an encrypted message is ${MAX_PLAINTEXT_LENGTH} bytes.`); + } +} +/** + * Manages the olm cryptography functions. Each OlmDevice has a single + * OlmAccount and a number of OlmSessions. + * + * Accounts and sessions are kept pickled in the cryptoStore. + */ +class OlmDevice { + constructor(cryptoStore) { + this.cryptoStore = cryptoStore; + this.pickleKey = "DEFAULT_KEY"; // set by consumers + /** Curve25519 key for the account, unknown until we load the account from storage in init() */ + this.deviceCurve25519Key = null; + /** Ed25519 key for the account, unknown until we load the account from storage in init() */ + this.deviceEd25519Key = null; + this.maxOneTimeKeys = null; + // we don't bother stashing outboundgroupsessions in the cryptoStore - + // instead we keep them here. + this.outboundGroupSessionStore = {}; + // Store a set of decrypted message indexes for each group session. + // This partially mitigates a replay attack where a MITM resends a group + // message into the room. + // + // When we decrypt a message and the message index matches a previously + // decrypted message, one possible cause of that is that we are decrypting + // the same event, and may not indicate an actual replay attack. For + // example, this could happen if we receive events, forget about them, and + // then re-fetch them when we backfill. So we store the event ID and + // timestamp corresponding to each message index when we first decrypt it, + // and compare these against the event ID and timestamp every time we use + // that same index. If they match, then we're probably decrypting the same + // event and we don't consider it a replay attack. + // + // Keys are strings of form "||" + // Values are objects of the form "{id: , timestamp: }" + this.inboundGroupSessionMessageIndexes = {}; + // Keep track of sessions that we're starting, so that we don't start + // multiple sessions for the same device at the same time. + this.sessionsInProgress = {}; // set by consumers + // Used by olm to serialise prekey message decryptions + this.olmPrekeyPromise = Promise.resolve(); // set by consumers + } + /** + * @returns The version of Olm. + */ + static getOlmVersion() { + return global.Olm.get_library_version(); + } + /** + * Initialise the OlmAccount. This must be called before any other operations + * on the OlmDevice. + * + * Data from an exported Olm device can be provided + * in order to re-create this device. + * + * Attempts to load the OlmAccount from the crypto store, or creates one if none is + * found. + * + * Reads the device keys from the OlmAccount object. + * + * @param fromExportedDevice - (Optional) data from exported device + * that must be re-created. + * If present, opts.pickleKey is ignored + * (exported data already provides a pickle key) + * @param pickleKey - (Optional) pickle key to set instead of default one + */ + init({ pickleKey, fromExportedDevice } = {}) { + return __awaiter(this, void 0, void 0, function* () { + let e2eKeys; + const account = new global.Olm.Account(); + try { + if (fromExportedDevice) { + if (pickleKey) { + logger_1.logger.warn("ignoring opts.pickleKey" + " because opts.fromExportedDevice is present."); + } + this.pickleKey = fromExportedDevice.pickleKey; + yield this.initialiseFromExportedDevice(fromExportedDevice, account); + } + else { + if (pickleKey) { + this.pickleKey = pickleKey; + } + yield this.initialiseAccount(account); + } + e2eKeys = JSON.parse(account.identity_keys()); + this.maxOneTimeKeys = account.max_number_of_one_time_keys(); + } + finally { + account.free(); + } + this.deviceCurve25519Key = e2eKeys.curve25519; + this.deviceEd25519Key = e2eKeys.ed25519; + }); + } + /** + * Populates the crypto store using data that was exported from an existing device. + * Note that for now only the “account” and “sessions” stores are populated; + * Other stores will be as with a new device. + * + * @param exportedData - Data exported from another device + * through the “export” method. + * @param account - an olm account to initialize + */ + initialiseFromExportedDevice(exportedData, account) { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT, indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.cryptoStore.storeAccount(txn, exportedData.pickledAccount); + exportedData.sessions.forEach((session) => { + const { deviceKey, sessionId } = session; + const sessionInfo = { + session: session.session, + lastReceivedMessageTs: session.lastReceivedMessageTs, + }; + this.cryptoStore.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn); + }); + }); + account.unpickle(this.pickleKey, exportedData.pickledAccount); + }); + } + initialiseAccount(account) { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.cryptoStore.getAccount(txn, (pickledAccount) => { + if (pickledAccount !== null) { + account.unpickle(this.pickleKey, pickledAccount); + } + else { + account.create(); + pickledAccount = account.pickle(this.pickleKey); + this.cryptoStore.storeAccount(txn, pickledAccount); + } + }); + }); + }); + } + /** + * extract our OlmAccount from the crypto store and call the given function + * with the account object + * The `account` object is usable only within the callback passed to this + * function and will be freed as soon the callback returns. It is *not* + * usable for the rest of the lifetime of the transaction. + * This function requires a live transaction object from cryptoStore.doTxn() + * and therefore may only be called in a doTxn() callback. + * + * @param txn - Opaque transaction object from cryptoStore.doTxn() + * @internal + */ + getAccount(txn, func) { + this.cryptoStore.getAccount(txn, (pickledAccount) => { + const account = new global.Olm.Account(); + try { + account.unpickle(this.pickleKey, pickledAccount); + func(account); + } + finally { + account.free(); + } + }); + } + /* + * Saves an account to the crypto store. + * This function requires a live transaction object from cryptoStore.doTxn() + * and therefore may only be called in a doTxn() callback. + * + * @param txn - Opaque transaction object from cryptoStore.doTxn() + * @param Olm.Account object + * @internal + */ + storeAccount(txn, account) { + this.cryptoStore.storeAccount(txn, account.pickle(this.pickleKey)); + } + /** + * Export data for re-creating the Olm device later. + * TODO export data other than just account and (P2P) sessions. + * + * @returns The exported data + */ + export() { + return __awaiter(this, void 0, void 0, function* () { + const result = { + pickleKey: this.pickleKey, + }; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT, indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.cryptoStore.getAccount(txn, (pickledAccount) => { + result.pickledAccount = pickledAccount; + }); + result.sessions = []; + // Note that the pickledSession object we get in the callback + // is not exactly the same thing you get in method _getSession + // see documentation of IndexedDBCryptoStore.getAllEndToEndSessions + this.cryptoStore.getAllEndToEndSessions(txn, (pickledSession) => { + result.sessions.push(pickledSession); + }); + }); + return result; + }); + } + /** + * extract an OlmSession from the session store and call the given function + * The session is usable only within the callback passed to this + * function and will be freed as soon the callback returns. It is *not* + * usable for the rest of the lifetime of the transaction. + * + * @param txn - Opaque transaction object from cryptoStore.doTxn() + * @internal + */ + getSession(deviceKey, sessionId, txn, func) { + this.cryptoStore.getEndToEndSession(deviceKey, sessionId, txn, (sessionInfo) => { + this.unpickleSession(sessionInfo, func); + }); + } + /** + * Creates a session object from a session pickle and executes the given + * function with it. The session object is destroyed once the function + * returns. + * + * @internal + */ + unpickleSession(sessionInfo, func) { + const session = new global.Olm.Session(); + try { + session.unpickle(this.pickleKey, sessionInfo.session); + const unpickledSessInfo = Object.assign({}, sessionInfo, { session }); + func(unpickledSessInfo); + } + finally { + session.free(); + } + } + /** + * store our OlmSession in the session store + * + * @param sessionInfo - `{session: OlmSession, lastReceivedMessageTs: int}` + * @param txn - Opaque transaction object from cryptoStore.doTxn() + * @internal + */ + saveSession(deviceKey, sessionInfo, txn) { + const sessionId = sessionInfo.session.session_id(); + logger_1.logger.debug(`Saving Olm session ${sessionId} with device ${deviceKey}: ${sessionInfo.session.describe()}`); + // Why do we re-use the input object for this, overwriting the same key with a different + // type? Is it because we want to erase the unpickled session to enforce that it's no longer + // used? A comment would be great. + const pickledSessionInfo = Object.assign(sessionInfo, { + session: sessionInfo.session.pickle(this.pickleKey), + }); + this.cryptoStore.storeEndToEndSession(deviceKey, sessionId, pickledSessionInfo, txn); + } + /** + * get an OlmUtility and call the given function + * + * @returns result of func + * @internal + */ + getUtility(func) { + const utility = new global.Olm.Utility(); + try { + return func(utility); + } + finally { + utility.free(); + } + } + /** + * Signs a message with the ed25519 key for this account. + * + * @param message - message to be signed + * @returns base64-encoded signature + */ + sign(message) { + return __awaiter(this, void 0, void 0, function* () { + let result; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + result = account.sign(message); + }); + }); + return result; + }); + } + /** + * Get the current (unused, unpublished) one-time keys for this account. + * + * @returns one time keys; an object with the single property + * curve25519, which is itself an object mapping key id to Curve25519 + * key. + */ + getOneTimeKeys() { + return __awaiter(this, void 0, void 0, function* () { + let result; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + result = JSON.parse(account.one_time_keys()); + }); + }); + return result; + }); + } + /** + * Get the maximum number of one-time keys we can store. + * + * @returns number of keys + */ + maxNumberOfOneTimeKeys() { + var _a; + return (_a = this.maxOneTimeKeys) !== null && _a !== void 0 ? _a : -1; + } + /** + * Marks all of the one-time keys as published. + */ + markKeysAsPublished() { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + account.mark_keys_as_published(); + this.storeAccount(txn, account); + }); + }); + }); + } + /** + * Generate some new one-time keys + * + * @param numKeys - number of keys to generate + * @returns Resolved once the account is saved back having generated the keys + */ + generateOneTimeKeys(numKeys) { + return this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + account.generate_one_time_keys(numKeys); + this.storeAccount(txn, account); + }); + }); + } + /** + * Generate a new fallback keys + * + * @returns Resolved once the account is saved back having generated the key + */ + generateFallbackKey() { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + account.generate_fallback_key(); + this.storeAccount(txn, account); + }); + }); + }); + } + getFallbackKey() { + return __awaiter(this, void 0, void 0, function* () { + let result; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + result = JSON.parse(account.unpublished_fallback_key()); + }); + }); + return result; + }); + } + forgetOldFallbackKey() { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.getAccount(txn, (account) => { + account.forget_old_fallback_key(); + this.storeAccount(txn, account); + }); + }); + }); + } + /** + * Generate a new outbound session + * + * The new session will be stored in the cryptoStore. + * + * @param theirIdentityKey - remote user's Curve25519 identity key + * @param theirOneTimeKey - remote user's one-time Curve25519 key + * @returns sessionId for the outbound session. + */ + createOutboundSession(theirIdentityKey, theirOneTimeKey) { + return __awaiter(this, void 0, void 0, function* () { + let newSessionId; + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT, indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.getAccount(txn, (account) => { + const session = new global.Olm.Session(); + try { + session.create_outbound(account, theirIdentityKey, theirOneTimeKey); + newSessionId = session.session_id(); + this.storeAccount(txn, account); + const sessionInfo = { + session, + // Pretend we've received a message at this point, otherwise + // if we try to send a message to the device, it won't use + // this session + lastReceivedMessageTs: Date.now(), + }; + this.saveSession(theirIdentityKey, sessionInfo, txn); + } + finally { + session.free(); + } + }); + }, logger_1.logger.withPrefix("[createOutboundSession]")); + return newSessionId; + }); + } + /** + * Generate a new inbound session, given an incoming message + * + * @param theirDeviceIdentityKey - remote user's Curve25519 identity key + * @param messageType - messageType field from the received message (must be 0) + * @param ciphertext - base64-encoded body from the received message + * + * @returns decrypted payload, and + * session id of new session + * + * @throws Error if the received message was not valid (for instance, it didn't use a valid one-time key). + */ + createInboundSession(theirDeviceIdentityKey, messageType, ciphertext) { + return __awaiter(this, void 0, void 0, function* () { + if (messageType !== 0) { + throw new Error("Need messageType == 0 to create inbound session"); + } + let result; // eslint-disable-line camelcase + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT, indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.getAccount(txn, (account) => { + const session = new global.Olm.Session(); + try { + session.create_inbound_from(account, theirDeviceIdentityKey, ciphertext); + account.remove_one_time_keys(session); + this.storeAccount(txn, account); + const payloadString = session.decrypt(messageType, ciphertext); + const sessionInfo = { + session, + // this counts as a received message: set last received message time + // to now + lastReceivedMessageTs: Date.now(), + }; + this.saveSession(theirDeviceIdentityKey, sessionInfo, txn); + result = { + payload: payloadString, + session_id: session.session_id(), + }; + } + finally { + session.free(); + } + }); + }, logger_1.logger.withPrefix("[createInboundSession]")); + return result; + }); + } + /** + * Get a list of known session IDs for the given device + * + * @param theirDeviceIdentityKey - Curve25519 identity key for the + * remote device + * @returns a list of known session ids for the device + */ + getSessionIdsForDevice(theirDeviceIdentityKey) { + return __awaiter(this, void 0, void 0, function* () { + const log = logger_1.logger.withPrefix("[getSessionIdsForDevice]"); + if (theirDeviceIdentityKey in this.sessionsInProgress) { + log.debug(`Waiting for Olm session for ${theirDeviceIdentityKey} to be created`); + try { + yield this.sessionsInProgress[theirDeviceIdentityKey]; + } + catch (e) { + // if the session failed to be created, just fall through and + // return an empty result + } + } + let sessionIds; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.cryptoStore.getEndToEndSessions(theirDeviceIdentityKey, txn, (sessions) => { + sessionIds = Object.keys(sessions); + }); + }, log); + return sessionIds; + }); + } + /** + * Get the right olm session id for encrypting messages to the given identity key + * + * @param theirDeviceIdentityKey - Curve25519 identity key for the + * remote device + * @param nowait - Don't wait for an in-progress session to complete. + * This should only be set to true of the calling function is the function + * that marked the session as being in-progress. + * @param log - A possibly customised log + * @returns session id, or null if no established session + */ + getSessionIdForDevice(theirDeviceIdentityKey, nowait = false, log) { + return __awaiter(this, void 0, void 0, function* () { + const sessionInfos = yield this.getSessionInfoForDevice(theirDeviceIdentityKey, nowait, log); + if (sessionInfos.length === 0) { + return null; + } + // Use the session that has most recently received a message + let idxOfBest = 0; + for (let i = 1; i < sessionInfos.length; i++) { + const thisSessInfo = sessionInfos[i]; + const thisLastReceived = thisSessInfo.lastReceivedMessageTs === undefined ? 0 : thisSessInfo.lastReceivedMessageTs; + const bestSessInfo = sessionInfos[idxOfBest]; + const bestLastReceived = bestSessInfo.lastReceivedMessageTs === undefined ? 0 : bestSessInfo.lastReceivedMessageTs; + if (thisLastReceived > bestLastReceived || + (thisLastReceived === bestLastReceived && thisSessInfo.sessionId < bestSessInfo.sessionId)) { + idxOfBest = i; + } + } + return sessionInfos[idxOfBest].sessionId; + }); + } + /** + * Get information on the active Olm sessions for a device. + *

+ * Returns an array, with an entry for each active session. The first entry in + * the result will be the one used for outgoing messages. Each entry contains + * the keys 'hasReceivedMessage' (true if the session has received an incoming + * message and is therefore past the pre-key stage), and 'sessionId'. + * + * @param deviceIdentityKey - Curve25519 identity key for the device + * @param nowait - Don't wait for an in-progress session to complete. + * This should only be set to true of the calling function is the function + * that marked the session as being in-progress. + * @param log - A possibly customised log + */ + getSessionInfoForDevice(deviceIdentityKey, nowait = false, log = logger_1.logger) { + return __awaiter(this, void 0, void 0, function* () { + log = log.withPrefix("[getSessionInfoForDevice]"); + if (deviceIdentityKey in this.sessionsInProgress && !nowait) { + log.debug(`Waiting for Olm session for ${deviceIdentityKey} to be created`); + try { + yield this.sessionsInProgress[deviceIdentityKey]; + } + catch (e) { + // if the session failed to be created, then just fall through and + // return an empty result + } + } + const info = []; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.cryptoStore.getEndToEndSessions(deviceIdentityKey, txn, (sessions) => { + const sessionIds = Object.keys(sessions).sort(); + for (const sessionId of sessionIds) { + this.unpickleSession(sessions[sessionId], (sessInfo) => { + info.push({ + lastReceivedMessageTs: sessInfo.lastReceivedMessageTs, + hasReceivedMessage: sessInfo.session.has_received_message(), + sessionId, + }); + }); + } + }); + }, log); + return info; + }); + } + /** + * Encrypt an outgoing message using an existing session + * + * @param theirDeviceIdentityKey - Curve25519 identity key for the + * remote device + * @param sessionId - the id of the active session + * @param payloadString - payload to be encrypted and sent + * + * @returns ciphertext + */ + encryptMessage(theirDeviceIdentityKey, sessionId, payloadString) { + return __awaiter(this, void 0, void 0, function* () { + checkPayloadLength(payloadString); + let res; + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => { + const sessionDesc = sessionInfo.session.describe(); + logger_1.logger.log("encryptMessage: Olm Session ID " + + sessionId + + " to " + + theirDeviceIdentityKey + + ": " + + sessionDesc); + res = sessionInfo.session.encrypt(payloadString); + this.saveSession(theirDeviceIdentityKey, sessionInfo, txn); + }); + }, logger_1.logger.withPrefix("[encryptMessage]")); + return res; + }); + } + /** + * Decrypt an incoming message using an existing session + * + * @param theirDeviceIdentityKey - Curve25519 identity key for the + * remote device + * @param sessionId - the id of the active session + * @param messageType - messageType field from the received message + * @param ciphertext - base64-encoded body from the received message + * + * @returns decrypted payload. + */ + decryptMessage(theirDeviceIdentityKey, sessionId, messageType, ciphertext) { + return __awaiter(this, void 0, void 0, function* () { + let payloadString; + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => { + const sessionDesc = sessionInfo.session.describe(); + logger_1.logger.log("decryptMessage: Olm Session ID " + + sessionId + + " from " + + theirDeviceIdentityKey + + ": " + + sessionDesc); + payloadString = sessionInfo.session.decrypt(messageType, ciphertext); + sessionInfo.lastReceivedMessageTs = Date.now(); + this.saveSession(theirDeviceIdentityKey, sessionInfo, txn); + }); + }, logger_1.logger.withPrefix("[decryptMessage]")); + return payloadString; + }); + } + /** + * Determine if an incoming messages is a prekey message matching an existing session + * + * @param theirDeviceIdentityKey - Curve25519 identity key for the + * remote device + * @param sessionId - the id of the active session + * @param messageType - messageType field from the received message + * @param ciphertext - base64-encoded body from the received message + * + * @returns true if the received message is a prekey message which matches + * the given session. + */ + matchesSession(theirDeviceIdentityKey, sessionId, messageType, ciphertext) { + return __awaiter(this, void 0, void 0, function* () { + if (messageType !== 0) { + return false; + } + let matches; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SESSIONS], (txn) => { + this.getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => { + matches = sessionInfo.session.matches_inbound(ciphertext); + }); + }, logger_1.logger.withPrefix("[matchesSession]")); + return matches; + }); + } + recordSessionProblem(deviceKey, type, fixed) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info(`Recording problem on olm session with ${deviceKey} of type ${type}. Recreating: ${fixed}`); + yield this.cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed); + }); + } + sessionMayHaveProblems(deviceKey, timestamp) { + return this.cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp); + } + filterOutNotifiedErrorDevices(devices) { + return this.cryptoStore.filterOutNotifiedErrorDevices(devices); + } + // Outbound group session + // ====================== + /** + * store an OutboundGroupSession in outboundGroupSessionStore + * + * @internal + */ + saveOutboundGroupSession(session) { + this.outboundGroupSessionStore[session.session_id()] = session.pickle(this.pickleKey); + } + /** + * extract an OutboundGroupSession from outboundGroupSessionStore and call the + * given function + * + * @returns result of func + * @internal + */ + getOutboundGroupSession(sessionId, func) { + const pickled = this.outboundGroupSessionStore[sessionId]; + if (pickled === undefined) { + throw new Error("Unknown outbound group session " + sessionId); + } + const session = new global.Olm.OutboundGroupSession(); + try { + session.unpickle(this.pickleKey, pickled); + return func(session); + } + finally { + session.free(); + } + } + /** + * Generate a new outbound group session + * + * @returns sessionId for the outbound session. + */ + createOutboundGroupSession() { + const session = new global.Olm.OutboundGroupSession(); + try { + session.create(); + this.saveOutboundGroupSession(session); + return session.session_id(); + } + finally { + session.free(); + } + } + /** + * Encrypt an outgoing message with an outbound group session + * + * @param sessionId - the id of the outboundgroupsession + * @param payloadString - payload to be encrypted and sent + * + * @returns ciphertext + */ + encryptGroupMessage(sessionId, payloadString) { + logger_1.logger.log(`encrypting msg with megolm session ${sessionId}`); + checkPayloadLength(payloadString); + return this.getOutboundGroupSession(sessionId, (session) => { + const res = session.encrypt(payloadString); + this.saveOutboundGroupSession(session); + return res; + }); + } + /** + * Get the session keys for an outbound group session + * + * @param sessionId - the id of the outbound group session + * + * @returns current chain index, and + * base64-encoded secret key. + */ + getOutboundGroupSessionKey(sessionId) { + return this.getOutboundGroupSession(sessionId, function (session) { + return { + chain_index: session.message_index(), + key: session.session_key(), + }; + }); + } + // Inbound group session + // ===================== + /** + * Unpickle a session from a sessionData object and invoke the given function. + * The session is valid only until func returns. + * + * @param sessionData - Object describing the session. + * @param func - Invoked with the unpickled session + * @returns result of func + */ + unpickleInboundGroupSession(sessionData, func) { + const session = new global.Olm.InboundGroupSession(); + try { + session.unpickle(this.pickleKey, sessionData.session); + return func(session); + } + finally { + session.free(); + } + } + /** + * extract an InboundGroupSession from the crypto store and call the given function + * + * @param roomId - The room ID to extract the session for, or null to fetch + * sessions for any room. + * @param txn - Opaque transaction object from cryptoStore.doTxn() + * @param func - function to call. + * + * @internal + */ + getInboundGroupSession(roomId, senderKey, sessionId, txn, func) { + this.cryptoStore.getEndToEndInboundGroupSession(senderKey, sessionId, txn, (sessionData, withheld) => { + if (sessionData === null) { + func(null, null, withheld); + return; + } + // if we were given a room ID, check that the it matches the original one for the session. This stops + // the HS pretending a message was targeting a different room. + if (roomId !== null && roomId !== sessionData.room_id) { + throw new Error("Mismatched room_id for inbound group session (expected " + + sessionData.room_id + + ", was " + + roomId + + ")"); + } + this.unpickleInboundGroupSession(sessionData, (session) => { + func(session, sessionData, withheld); + }); + }); + } + /** + * Add an inbound group session to the session store + * + * @param roomId - room in which this session will be used + * @param senderKey - base64-encoded curve25519 key of the sender + * @param forwardingCurve25519KeyChain - Devices involved in forwarding + * this session to us. + * @param sessionId - session identifier + * @param sessionKey - base64-encoded secret key + * @param keysClaimed - Other keys the sender claims. + * @param exportFormat - true if the megolm keys are in export format + * (ie, they lack an ed25519 signature) + * @param extraSessionData - any other data to be include with the session + */ + addInboundGroupSession(roomId, senderKey, forwardingCurve25519KeyChain, sessionId, sessionKey, keysClaimed, exportFormat, extraSessionData = {}) { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [ + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD, + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS, + ], (txn) => { + /* if we already have this session, consider updating it */ + this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (existingSession, existingSessionData) => { + // new session. + const session = new global.Olm.InboundGroupSession(); + try { + if (exportFormat) { + session.import_session(sessionKey); + } + else { + session.create(sessionKey); + } + if (sessionId != session.session_id()) { + throw new Error("Mismatched group session ID from senderKey: " + senderKey); + } + if (existingSession) { + logger_1.logger.log(`Update for megolm session ${senderKey}|${sessionId}`); + if (existingSession.first_known_index() <= session.first_known_index()) { + if (!existingSessionData.untrusted || extraSessionData.untrusted) { + // existing session has less-than-or-equal index + // (i.e. can decrypt at least as much), and the + // new session's trust does not win over the old + // session's trust, so keep it + logger_1.logger.log(`Keeping existing megolm session ${senderKey}|${sessionId}`); + return; + } + if (existingSession.first_known_index() < session.first_known_index()) { + // We want to upgrade the existing session's trust, + // but we can't just use the new session because we'll + // lose the lower index. Check that the sessions connect + // properly, and then manually set the existing session + // as trusted. + if (existingSession.export_session(session.first_known_index()) === + session.export_session(session.first_known_index())) { + logger_1.logger.info("Upgrading trust of existing megolm session " + + `${senderKey}|${sessionId} based on newly-received trusted session`); + existingSessionData.untrusted = false; + this.cryptoStore.storeEndToEndInboundGroupSession(senderKey, sessionId, existingSessionData, txn); + } + else { + logger_1.logger.warn(`Newly-received megolm session ${senderKey}|$sessionId}` + + " does not match existing session! Keeping existing session"); + } + return; + } + // If the sessions have the same index, go ahead and store the new trusted one. + } + } + logger_1.logger.info(`Storing megolm session ${senderKey}|${sessionId} with first index ` + + session.first_known_index()); + const sessionData = Object.assign({}, extraSessionData, { + room_id: roomId, + session: session.pickle(this.pickleKey), + keysClaimed: keysClaimed, + forwardingCurve25519KeyChain: forwardingCurve25519KeyChain, + }); + this.cryptoStore.storeEndToEndInboundGroupSession(senderKey, sessionId, sessionData, txn); + if (!existingSession && extraSessionData.sharedHistory) { + this.cryptoStore.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn); + } + } + finally { + session.free(); + } + }); + }, logger_1.logger.withPrefix("[addInboundGroupSession]")); + }); + } + /** + * Record in the data store why an inbound group session was withheld. + * + * @param roomId - room that the session belongs to + * @param senderKey - base64-encoded curve25519 key of the sender + * @param sessionId - session identifier + * @param code - reason code + * @param reason - human-readable version of `code` + */ + addInboundGroupSessionWithheld(roomId, senderKey, sessionId, code, reason) { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD], (txn) => { + this.cryptoStore.storeEndToEndInboundGroupSessionWithheld(senderKey, sessionId, { + room_id: roomId, + code: code, + reason: reason, + }, txn); + }); + }); + } + /** + * Decrypt a received message with an inbound group session + * + * @param roomId - room in which the message was received + * @param senderKey - base64-encoded curve25519 key of the sender + * @param sessionId - session identifier + * @param body - base64-encoded body of the encrypted message + * @param eventId - ID of the event being decrypted + * @param timestamp - timestamp of the event being decrypted + * + * @returns null if the sessionId is unknown + */ + decryptGroupMessage(roomId, senderKey, sessionId, body, eventId, timestamp) { + return __awaiter(this, void 0, void 0, function* () { + let result = null; + // when the localstorage crypto store is used as an indexeddb backend, + // exceptions thrown from within the inner function are not passed through + // to the top level, so we store exceptions in a variable and raise them at + // the end + let error; + yield this.cryptoStore.doTxn("readwrite", [ + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD, + ], (txn) => { + this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => { + if (session === null || sessionData === null) { + if (withheld) { + error = new algorithms.DecryptionError("MEGOLM_UNKNOWN_INBOUND_SESSION_ID", calculateWithheldMessage(withheld), { + session: senderKey + "|" + sessionId, + }); + } + result = null; + return; + } + let res; + try { + res = session.decrypt(body); + } + catch (e) { + if ((e === null || e === void 0 ? void 0 : e.message) === "OLM.UNKNOWN_MESSAGE_INDEX" && withheld) { + error = new algorithms.DecryptionError("MEGOLM_UNKNOWN_INBOUND_SESSION_ID", calculateWithheldMessage(withheld), { + session: senderKey + "|" + sessionId, + }); + } + else { + error = e; + } + return; + } + let plaintext = res.plaintext; + if (plaintext === undefined) { + // @ts-ignore - Compatibility for older olm versions. + plaintext = res; + } + else { + // Check if we have seen this message index before to detect replay attacks. + // If the event ID and timestamp are specified, and the match the event ID + // and timestamp from the last time we used this message index, then we + // don't consider it a replay attack. + const messageIndexKey = senderKey + "|" + sessionId + "|" + res.message_index; + if (messageIndexKey in this.inboundGroupSessionMessageIndexes) { + const msgInfo = this.inboundGroupSessionMessageIndexes[messageIndexKey]; + if (msgInfo.id !== eventId || msgInfo.timestamp !== timestamp) { + error = new Error("Duplicate message index, possible replay attack: " + messageIndexKey); + return; + } + } + this.inboundGroupSessionMessageIndexes[messageIndexKey] = { + id: eventId, + timestamp: timestamp, + }; + } + sessionData.session = session.pickle(this.pickleKey); + this.cryptoStore.storeEndToEndInboundGroupSession(senderKey, sessionId, sessionData, txn); + result = { + result: plaintext, + keysClaimed: sessionData.keysClaimed || {}, + senderKey: senderKey, + forwardingCurve25519KeyChain: sessionData.forwardingCurve25519KeyChain || [], + untrusted: !!sessionData.untrusted, + }; + }); + }, logger_1.logger.withPrefix("[decryptGroupMessage]")); + if (error) { + throw error; + } + return result; + }); + } + /** + * Determine if we have the keys for a given megolm session + * + * @param roomId - room in which the message was received + * @param senderKey - base64-encoded curve25519 key of the sender + * @param sessionId - session identifier + * + * @returns true if we have the keys to this session + */ + hasInboundSessionKeys(roomId, senderKey, sessionId) { + return __awaiter(this, void 0, void 0, function* () { + let result; + yield this.cryptoStore.doTxn("readonly", [ + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD, + ], (txn) => { + this.cryptoStore.getEndToEndInboundGroupSession(senderKey, sessionId, txn, (sessionData) => { + if (sessionData === null) { + result = false; + return; + } + if (roomId !== sessionData.room_id) { + logger_1.logger.warn(`requested keys for inbound group session ${senderKey}|` + + `${sessionId}, with incorrect room_id ` + + `(expected ${sessionData.room_id}, ` + + `was ${roomId})`); + result = false; + } + else { + result = true; + } + }); + }, logger_1.logger.withPrefix("[hasInboundSessionKeys]")); + return result; + }); + } + /** + * Extract the keys to a given megolm session, for sharing + * + * @param roomId - room in which the message was received + * @param senderKey - base64-encoded curve25519 key of the sender + * @param sessionId - session identifier + * @param chainIndex - The chain index at which to export the session. + * If omitted, export at the first index we know about. + * + * @returns + * details of the session key. The key is a base64-encoded megolm key in + * export format. + * + * @throws Error If the given chain index could not be obtained from the known + * index (ie. the given chain index is before the first we have). + */ + getInboundGroupSessionKey(roomId, senderKey, sessionId, chainIndex) { + return __awaiter(this, void 0, void 0, function* () { + let result = null; + yield this.cryptoStore.doTxn("readonly", [ + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD, + ], (txn) => { + this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (session, sessionData) => { + if (session === null || sessionData === null) { + result = null; + return; + } + if (chainIndex === undefined) { + chainIndex = session.first_known_index(); + } + const exportedSession = session.export_session(chainIndex); + const claimedKeys = sessionData.keysClaimed || {}; + const senderEd25519Key = claimedKeys.ed25519 || null; + const forwardingKeyChain = sessionData.forwardingCurve25519KeyChain || []; + // older forwarded keys didn't set the "untrusted" + // property, but can be identified by having a + // non-empty forwarding key chain. These keys should + // be marked as untrusted since we don't know that they + // can be trusted + const untrusted = "untrusted" in sessionData ? sessionData.untrusted : forwardingKeyChain.length > 0; + result = { + chain_index: chainIndex, + key: exportedSession, + forwarding_curve25519_key_chain: forwardingKeyChain, + sender_claimed_ed25519_key: senderEd25519Key, + shared_history: sessionData.sharedHistory || false, + untrusted: untrusted, + }; + }); + }, logger_1.logger.withPrefix("[getInboundGroupSessionKey]")); + return result; + }); + } + /** + * Export an inbound group session + * + * @param senderKey - base64-encoded curve25519 key of the sender + * @param sessionId - session identifier + * @param sessionData - The session object from the store + * @returns exported session data + */ + exportInboundGroupSession(senderKey, sessionId, sessionData) { + return this.unpickleInboundGroupSession(sessionData, (session) => { + const messageIndex = session.first_known_index(); + return { + "sender_key": senderKey, + "sender_claimed_keys": sessionData.keysClaimed, + "room_id": sessionData.room_id, + "session_id": sessionId, + "session_key": session.export_session(messageIndex), + "forwarding_curve25519_key_chain": sessionData.forwardingCurve25519KeyChain || [], + "first_known_index": session.first_known_index(), + "org.matrix.msc3061.shared_history": sessionData.sharedHistory || false, + }; + }); + } + getSharedHistoryInboundGroupSessions(roomId) { + return __awaiter(this, void 0, void 0, function* () { + let result; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS], (txn) => { + result = this.cryptoStore.getSharedHistoryInboundGroupSessions(roomId, txn); + }, logger_1.logger.withPrefix("[getSharedHistoryInboundGroupSessionsForRoom]")); + return result; + }); + } + // Utilities + // ========= + /** + * Verify an ed25519 signature. + * + * @param key - ed25519 key + * @param message - message which was signed + * @param signature - base64-encoded signature to be checked + * + * @throws Error if there is a problem with the verification. If the key was + * too small then the message will be "OLM.INVALID_BASE64". If the signature + * was invalid then the message will be "OLM.BAD_MESSAGE_MAC". + */ + verifySignature(key, message, signature) { + this.getUtility(function (util) { + util.ed25519_verify(key, message, signature); + }); + } +} +exports.OlmDevice = OlmDevice; +exports.WITHHELD_MESSAGES = { + "m.unverified": "The sender has disabled encrypting to unverified devices.", + "m.blacklisted": "The sender has blocked you.", + "m.unauthorised": "You are not authorised to read the message.", + "m.no_olm": "Unable to establish a secure channel.", +}; +/** + * Calculate the message to use for the exception when a session key is withheld. + * + * @param withheld - An object that describes why the key was withheld. + * + * @returns the message + * + * @internal + */ +function calculateWithheldMessage(withheld) { + if (withheld.code && withheld.code in exports.WITHHELD_MESSAGES) { + return exports.WITHHELD_MESSAGES[withheld.code]; + } + else if (withheld.reason) { + return withheld.reason; + } + else { + return "decryption key withheld"; + } +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../logger":374,"./algorithms":333,"./store/indexeddb-crypto-store":346}],328:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OutgoingRoomKeyRequestManager = exports.RoomKeyRequestState = void 0; +const uuid_1 = require("uuid"); +const logger_1 = require("../logger"); +const event_1 = require("../@types/event"); +const utils_1 = require("../utils"); +/** + * Internal module. Management of outgoing room key requests. + * + * See https://docs.google.com/document/d/1m4gQkcnJkxNuBmb5NoFCIadIY-DyqqNAS3lloE73BlQ + * for draft documentation on what we're supposed to be implementing here. + */ +// delay between deciding we want some keys, and sending out the request, to +// allow for (a) it turning up anyway, (b) grouping requests together +const SEND_KEY_REQUESTS_DELAY_MS = 500; +/** + * possible states for a room key request + * + * The state machine looks like: + * ``` + * + * | (cancellation sent) + * | .-------------------------------------------------. + * | | | + * V V (cancellation requested) | + * UNSENT -----------------------------+ | + * | | | + * | | | + * | (send successful) | CANCELLATION_PENDING_AND_WILL_RESEND + * V | Λ + * SENT | | + * |-------------------------------- | --------------' + * | | (cancellation requested with intent + * | | to resend the original request) + * | | + * | (cancellation requested) | + * V | + * CANCELLATION_PENDING | + * | | + * | (cancellation sent) | + * V | + * (deleted) <---------------------------+ + * ``` + */ +var RoomKeyRequestState; +(function (RoomKeyRequestState) { + /** request not yet sent */ + RoomKeyRequestState[RoomKeyRequestState["Unsent"] = 0] = "Unsent"; + /** request sent, awaiting reply */ + RoomKeyRequestState[RoomKeyRequestState["Sent"] = 1] = "Sent"; + /** reply received, cancellation not yet sent */ + RoomKeyRequestState[RoomKeyRequestState["CancellationPending"] = 2] = "CancellationPending"; + /** + * Cancellation not yet sent and will transition to UNSENT instead of + * being deleted once the cancellation has been sent. + */ + RoomKeyRequestState[RoomKeyRequestState["CancellationPendingAndWillResend"] = 3] = "CancellationPendingAndWillResend"; +})(RoomKeyRequestState = exports.RoomKeyRequestState || (exports.RoomKeyRequestState = {})); +class OutgoingRoomKeyRequestManager { + constructor(baseApis, deviceId, cryptoStore) { + this.baseApis = baseApis; + this.deviceId = deviceId; + this.cryptoStore = cryptoStore; + // sanity check to ensure that we don't end up with two concurrent runs + // of sendOutgoingRoomKeyRequests + this.sendOutgoingRoomKeyRequestsRunning = false; + this.clientRunning = true; + } + /** + * Called when the client is stopped. Stops any running background processes. + */ + stop() { + logger_1.logger.log("stopping OutgoingRoomKeyRequestManager"); + // stop the timer on the next run + this.clientRunning = false; + } + /** + * Send any requests that have been queued + */ + sendQueuedRequests() { + this.startTimer(); + } + /** + * Queue up a room key request, if we haven't already queued or sent one. + * + * The `requestBody` is compared (with a deep-equality check) against + * previous queued or sent requests and if it matches, no change is made. + * Otherwise, a request is added to the pending list, and a job is started + * in the background to send it. + * + * @param resend - whether to resend the key request if there is + * already one + * + * @returns resolves when the request has been added to the + * pending list (or we have established that a similar request already + * exists) + */ + queueRoomKeyRequest(requestBody, recipients, resend = false) { + return __awaiter(this, void 0, void 0, function* () { + const req = yield this.cryptoStore.getOutgoingRoomKeyRequest(requestBody); + if (!req) { + yield this.cryptoStore.getOrAddOutgoingRoomKeyRequest({ + requestBody: requestBody, + recipients: recipients, + requestId: this.baseApis.makeTxnId(), + state: RoomKeyRequestState.Unsent, + }); + } + else { + switch (req.state) { + case RoomKeyRequestState.CancellationPendingAndWillResend: + case RoomKeyRequestState.Unsent: + // nothing to do here, since we're going to send a request anyways + return; + case RoomKeyRequestState.CancellationPending: { + // existing request is about to be cancelled. If we want to + // resend, then change the state so that it resends after + // cancelling. Otherwise, just cancel the cancellation. + const state = resend + ? RoomKeyRequestState.CancellationPendingAndWillResend + : RoomKeyRequestState.Sent; + yield this.cryptoStore.updateOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.CancellationPending, { + state, + cancellationTxnId: this.baseApis.makeTxnId(), + }); + break; + } + case RoomKeyRequestState.Sent: { + // a request has already been sent. If we don't want to + // resend, then do nothing. If we do want to, then cancel the + // existing request and send a new one. + if (resend) { + const state = RoomKeyRequestState.CancellationPendingAndWillResend; + const updatedReq = yield this.cryptoStore.updateOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Sent, { + state, + cancellationTxnId: this.baseApis.makeTxnId(), + // need to use a new transaction ID so that + // the request gets sent + requestTxnId: this.baseApis.makeTxnId(), + }); + if (!updatedReq) { + // updateOutgoingRoomKeyRequest couldn't find the request + // in state ROOM_KEY_REQUEST_STATES.SENT, so we must have + // raced with another tab to mark the request cancelled. + // Try again, to make sure the request is resent. + return this.queueRoomKeyRequest(requestBody, recipients, resend); + } + // We don't want to wait for the timer, so we send it + // immediately. (We might actually end up racing with the timer, + // but that's ok: even if we make the request twice, we'll do it + // with the same transaction_id, so only one message will get + // sent). + // + // (We also don't want to wait for the response from the server + // here, as it will slow down processing of received keys if we + // do.) + try { + yield this.sendOutgoingRoomKeyRequestCancellation(updatedReq, true); + } + catch (e) { + logger_1.logger.error("Error sending room key request cancellation;" + " will retry later.", e); + } + // The request has transitioned from + // CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We + // still need to resend the request which is now UNSENT, so + // start the timer if it isn't already started. + } + break; + } + default: + throw new Error("unhandled state: " + req.state); + } + } + }); + } + /** + * Cancel room key requests, if any match the given requestBody + * + * + * @returns resolves when the request has been updated in our + * pending list. + */ + cancelRoomKeyRequest(requestBody) { + return this.cryptoStore.getOutgoingRoomKeyRequest(requestBody).then((req) => { + if (!req) { + // no request was made for this key + return; + } + switch (req.state) { + case RoomKeyRequestState.CancellationPending: + case RoomKeyRequestState.CancellationPendingAndWillResend: + // nothing to do here + return; + case RoomKeyRequestState.Unsent: + // just delete it + // FIXME: ghahah we may have attempted to send it, and + // not yet got a successful response. So the server + // may have seen it, so we still need to send a cancellation + // in that case :/ + logger_1.logger.log("deleting unnecessary room key request for " + stringifyRequestBody(requestBody)); + return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent); + case RoomKeyRequestState.Sent: { + // send a cancellation. + return this.cryptoStore + .updateOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Sent, { + state: RoomKeyRequestState.CancellationPending, + cancellationTxnId: this.baseApis.makeTxnId(), + }) + .then((updatedReq) => { + if (!updatedReq) { + // updateOutgoingRoomKeyRequest couldn't find the + // request in state ROOM_KEY_REQUEST_STATES.SENT, + // so we must have raced with another tab to mark + // the request cancelled. There is no point in + // sending another cancellation since the other tab + // will do it. + logger_1.logger.log("Tried to cancel room key request for " + + stringifyRequestBody(requestBody) + + " but it was already cancelled in another tab"); + return; + } + // We don't want to wait for the timer, so we send it + // immediately. (We might actually end up racing with the timer, + // but that's ok: even if we make the request twice, we'll do it + // with the same transaction_id, so only one message will get + // sent). + // + // (We also don't want to wait for the response from the server + // here, as it will slow down processing of received keys if we + // do.) + this.sendOutgoingRoomKeyRequestCancellation(updatedReq).catch((e) => { + logger_1.logger.error("Error sending room key request cancellation;" + " will retry later.", e); + this.startTimer(); + }); + }); + } + default: + throw new Error("unhandled state: " + req.state); + } + }); + } + /** + * Look for room key requests by target device and state + * + * @param userId - Target user ID + * @param deviceId - Target device ID + * + * @returns resolves to a list of all the {@link OutgoingRoomKeyRequest} + */ + getOutgoingSentRoomKeyRequest(userId, deviceId) { + return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]); + } + /** + * Find anything in `sent` state, and kick it around the loop again. + * This is intended for situations where something substantial has changed, and we + * don't really expect the other end to even care about the cancellation. + * For example, after initialization or self-verification. + * @returns An array of `queueRoomKeyRequest` outputs. + */ + cancelAndResendAllOutgoingRequests() { + return __awaiter(this, void 0, void 0, function* () { + const outgoings = yield this.cryptoStore.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent); + return Promise.all(outgoings.map(({ requestBody, recipients }) => this.queueRoomKeyRequest(requestBody, recipients, true))); + }); + } + // start the background timer to send queued requests, if the timer isn't + // already running + startTimer() { + if (this.sendOutgoingRoomKeyRequestsTimer) { + return; + } + const startSendingOutgoingRoomKeyRequests = () => { + if (this.sendOutgoingRoomKeyRequestsRunning) { + throw new Error("RoomKeyRequestSend already in progress!"); + } + this.sendOutgoingRoomKeyRequestsRunning = true; + this.sendOutgoingRoomKeyRequests() + .finally(() => { + this.sendOutgoingRoomKeyRequestsRunning = false; + }) + .catch((e) => { + // this should only happen if there is an indexeddb error, + // in which case we're a bit stuffed anyway. + logger_1.logger.warn(`error in OutgoingRoomKeyRequestManager: ${e}`); + }); + }; + this.sendOutgoingRoomKeyRequestsTimer = setTimeout(startSendingOutgoingRoomKeyRequests, SEND_KEY_REQUESTS_DELAY_MS); + } + // look for and send any queued requests. Runs itself recursively until + // there are no more requests, or there is an error (in which case, the + // timer will be restarted before the promise resolves). + sendOutgoingRoomKeyRequests() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.clientRunning) { + this.sendOutgoingRoomKeyRequestsTimer = undefined; + return; + } + const req = yield this.cryptoStore.getOutgoingRoomKeyRequestByState([ + RoomKeyRequestState.CancellationPending, + RoomKeyRequestState.CancellationPendingAndWillResend, + RoomKeyRequestState.Unsent, + ]); + if (!req) { + this.sendOutgoingRoomKeyRequestsTimer = undefined; + return; + } + try { + switch (req.state) { + case RoomKeyRequestState.Unsent: + yield this.sendOutgoingRoomKeyRequest(req); + break; + case RoomKeyRequestState.CancellationPending: + yield this.sendOutgoingRoomKeyRequestCancellation(req); + break; + case RoomKeyRequestState.CancellationPendingAndWillResend: + yield this.sendOutgoingRoomKeyRequestCancellation(req, true); + break; + } + // go around the loop again + return this.sendOutgoingRoomKeyRequests(); + } + catch (e) { + logger_1.logger.error("Error sending room key request; will retry later.", e); + this.sendOutgoingRoomKeyRequestsTimer = undefined; + } + }); + } + // given a RoomKeyRequest, send it and update the request record + sendOutgoingRoomKeyRequest(req) { + logger_1.logger.log(`Requesting keys for ${stringifyRequestBody(req.requestBody)}` + + ` from ${stringifyRecipientList(req.recipients)}` + + `(id ${req.requestId})`); + const requestMessage = { + action: "request", + requesting_device_id: this.deviceId, + request_id: req.requestId, + body: req.requestBody, + }; + return this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId).then(() => { + return this.cryptoStore.updateOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent, { + state: RoomKeyRequestState.Sent, + }); + }); + } + // Given a RoomKeyRequest, cancel it and delete the request record unless + // andResend is set, in which case transition to UNSENT. + sendOutgoingRoomKeyRequestCancellation(req, andResend = false) { + logger_1.logger.log(`Sending cancellation for key request for ` + + `${stringifyRequestBody(req.requestBody)} to ` + + `${stringifyRecipientList(req.recipients)} ` + + `(cancellation id ${req.cancellationTxnId})`); + const requestMessage = { + action: "request_cancellation", + requesting_device_id: this.deviceId, + request_id: req.requestId, + }; + return this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId).then(() => { + if (andResend) { + // We want to resend, so transition to UNSENT + return this.cryptoStore.updateOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.CancellationPendingAndWillResend, { state: RoomKeyRequestState.Unsent }); + } + return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.CancellationPending); + }); + } + // send a RoomKeyRequest to a list of recipients + sendMessageToDevices(message, recipients, txnId) { + const contentMap = new utils_1.MapWithDefault(() => new Map()); + for (const recip of recipients) { + const userDeviceMap = contentMap.getOrCreate(recip.userId); + userDeviceMap.set(recip.deviceId, Object.assign(Object.assign({}, message), { [event_1.ToDeviceMessageId]: (0, uuid_1.v4)() })); + } + return this.baseApis.sendToDevice(event_1.EventType.RoomKeyRequest, contentMap, txnId); + } +} +exports.OutgoingRoomKeyRequestManager = OutgoingRoomKeyRequestManager; +function stringifyRequestBody(requestBody) { + // we assume that the request is for megolm keys, which are identified by + // room id and session id + return requestBody.room_id + " / " + requestBody.session_id; +} +function stringifyRecipientList(recipients) { + return `[${recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")}]`; +} + +},{"../@types/event":306,"../logger":374,"../utils":416,"uuid":287}],329:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomList = void 0; +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +/* eslint-enable camelcase */ +class RoomList { + constructor(cryptoStore) { + this.cryptoStore = cryptoStore; + // Object of roomId -> room e2e info object (body of the m.room.encryption event) + this.roomEncryption = {}; + } + init() { + return __awaiter(this, void 0, void 0, function* () { + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ROOMS], (txn) => { + this.cryptoStore.getEndToEndRooms(txn, (result) => { + this.roomEncryption = result; + }); + }); + }); + } + getRoomEncryption(roomId) { + return this.roomEncryption[roomId] || null; + } + isRoomEncrypted(roomId) { + return Boolean(this.getRoomEncryption(roomId)); + } + setRoomEncryption(roomId, roomInfo) { + return __awaiter(this, void 0, void 0, function* () { + // important that this happens before calling into the store + // as it prevents the Crypto::setRoomEncryption from calling + // this twice for consecutive m.room.encryption events + this.roomEncryption[roomId] = roomInfo; + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ROOMS], (txn) => { + this.cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn); + }); + }); + } +} +exports.RoomList = RoomList; + +},{"./store/indexeddb-crypto-store":346}],330:[function(require,module,exports){ +"use strict"; +/* +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SecretStorage = exports.SECRET_STORAGE_ALGORITHM_V1_AES = void 0; +const uuid_1 = require("uuid"); +const logger_1 = require("../logger"); +const olmlib = __importStar(require("./olmlib")); +const randomstring_1 = require("../randomstring"); +const aes_1 = require("./aes"); +const client_1 = require("../client"); +const utils_1 = require("../utils"); +const event_1 = require("../@types/event"); +exports.SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2"; +/** + * Implements Secure Secret Storage and Sharing (MSC1946) + */ +class SecretStorage { + // In it's pure javascript days, this was relying on some proper Javascript-style + // type-abuse where sometimes we'd pass in a fake client object with just the account + // data methods implemented, which is all this class needs unless you use the secret + // sharing code, so it was fine. As a low-touch TypeScript migration, this now has + // an extra, optional param for a real matrix client, so you can not pass it as long + // as you don't request any secrets. + // A better solution would probably be to split this class up into secret storage and + // secret sharing which are really two separate things, even though they share an MSC. + constructor(accountDataAdapter, cryptoCallbacks, baseApis) { + this.accountDataAdapter = accountDataAdapter; + this.cryptoCallbacks = cryptoCallbacks; + this.baseApis = baseApis; + this.requests = new Map(); + } + getDefaultKeyId() { + return __awaiter(this, void 0, void 0, function* () { + const defaultKey = yield this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.default_key"); + if (!defaultKey) + return null; + return defaultKey.key; + }); + } + setDefaultKeyId(keyId) { + return new Promise((resolve, reject) => { + const listener = (ev) => { + if (ev.getType() === "m.secret_storage.default_key" && ev.getContent().key === keyId) { + this.accountDataAdapter.removeListener(client_1.ClientEvent.AccountData, listener); + resolve(); + } + }; + this.accountDataAdapter.on(client_1.ClientEvent.AccountData, listener); + this.accountDataAdapter.setAccountData("m.secret_storage.default_key", { key: keyId }).catch((e) => { + this.accountDataAdapter.removeListener(client_1.ClientEvent.AccountData, listener); + reject(e); + }); + }); + } + /** + * Add a key for encrypting secrets. + * + * @param algorithm - the algorithm used by the key. + * @param opts - the options for the algorithm. The properties used + * depend on the algorithm given. + * @param keyId - the ID of the key. If not given, a random + * ID will be generated. + * + * @returns An object with: + * keyId: the ID of the key + * keyInfo: details about the key (iv, mac, passphrase) + */ + addKey(algorithm, opts = {}, keyId) { + return __awaiter(this, void 0, void 0, function* () { + if (algorithm !== exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + throw new Error(`Unknown key algorithm ${algorithm}`); + } + const keyInfo = { algorithm }; + if (opts.name) { + keyInfo.name = opts.name; + } + if (opts.passphrase) { + keyInfo.passphrase = opts.passphrase; + } + if (opts.key) { + const { iv, mac } = yield (0, aes_1.calculateKeyCheck)(opts.key); + keyInfo.iv = iv; + keyInfo.mac = mac; + } + if (!keyId) { + do { + keyId = (0, randomstring_1.randomString)(32); + } while (yield this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`)); + } + yield this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo); + return { + keyId, + keyInfo, + }; + }); + } + /** + * Get the key information for a given ID. + * + * @param keyId - The ID of the key to check + * for. Defaults to the default key ID if not provided. + * @returns If the key was found, the return value is an array of + * the form [keyId, keyInfo]. Otherwise, null is returned. + * XXX: why is this an array when addKey returns an object? + */ + getKey(keyId) { + return __awaiter(this, void 0, void 0, function* () { + if (!keyId) { + keyId = yield this.getDefaultKeyId(); + } + if (!keyId) { + return null; + } + const keyInfo = yield this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId); + return keyInfo ? [keyId, keyInfo] : null; + }); + } + /** + * Check whether we have a key with a given ID. + * + * @param keyId - The ID of the key to check + * for. Defaults to the default key ID if not provided. + * @returns Whether we have the key. + */ + hasKey(keyId) { + return __awaiter(this, void 0, void 0, function* () { + return Boolean(yield this.getKey(keyId)); + }); + } + /** + * Check whether a key matches what we expect based on the key info + * + * @param key - the key to check + * @param info - the key info + * + * @returns whether or not the key matches + */ + checkKey(key, info) { + return __awaiter(this, void 0, void 0, function* () { + if (info.algorithm === exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + if (info.mac) { + const { mac } = yield (0, aes_1.calculateKeyCheck)(key, info.iv); + return info.mac.replace(/=+$/g, "") === mac.replace(/=+$/g, ""); + } + else { + // if we have no information, we have to assume the key is right + return true; + } + } + else { + throw new Error("Unknown algorithm"); + } + }); + } + /** + * Store an encrypted secret on the server + * + * @param name - The name of the secret + * @param secret - The secret contents. + * @param keys - The IDs of the keys to use to encrypt the secret + * or null/undefined to use the default key. + */ + store(name, secret, keys) { + return __awaiter(this, void 0, void 0, function* () { + const encrypted = {}; + if (!keys) { + const defaultKeyId = yield this.getDefaultKeyId(); + if (!defaultKeyId) { + throw new Error("No keys specified and no default key present"); + } + keys = [defaultKeyId]; + } + if (keys.length === 0) { + throw new Error("Zero keys given to encrypt with!"); + } + for (const keyId of keys) { + // get key information from key storage + const keyInfo = yield this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId); + if (!keyInfo) { + throw new Error("Unknown key: " + keyId); + } + // encrypt secret, based on the algorithm + if (keyInfo.algorithm === exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + const keys = { [keyId]: keyInfo }; + const [, encryption] = yield this.getSecretStorageKey(keys, name); + encrypted[keyId] = yield encryption.encrypt(secret); + } + else { + logger_1.logger.warn("unknown algorithm for secret storage key " + keyId + ": " + keyInfo.algorithm); + // do nothing if we don't understand the encryption algorithm + } + } + // save encrypted secret + yield this.accountDataAdapter.setAccountData(name, { encrypted }); + }); + } + /** + * Get a secret from storage. + * + * @param name - the name of the secret + * + * @returns the contents of the secret + */ + get(name) { + return __awaiter(this, void 0, void 0, function* () { + const secretInfo = yield this.accountDataAdapter.getAccountDataFromServer(name); + if (!secretInfo) { + return; + } + if (!secretInfo.encrypted) { + throw new Error("Content is not encrypted!"); + } + // get possible keys to decrypt + const keys = {}; + for (const keyId of Object.keys(secretInfo.encrypted)) { + // get key information from key storage + const keyInfo = yield this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId); + const encInfo = secretInfo.encrypted[keyId]; + // only use keys we understand the encryption algorithm of + if (keyInfo.algorithm === exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + if (encInfo.iv && encInfo.ciphertext && encInfo.mac) { + keys[keyId] = keyInfo; + } + } + } + if (Object.keys(keys).length === 0) { + throw new Error(`Could not decrypt ${name} because none of ` + + `the keys it is encrypted with are for a supported algorithm`); + } + // fetch private key from app + const [keyId, decryption] = yield this.getSecretStorageKey(keys, name); + const encInfo = secretInfo.encrypted[keyId]; + return decryption.decrypt(encInfo); + }); + } + /** + * Check if a secret is stored on the server. + * + * @param name - the name of the secret + * + * @returns map of key name to key info the secret is encrypted + * with, or null if it is not present or not encrypted with a trusted + * key + */ + isStored(name) { + return __awaiter(this, void 0, void 0, function* () { + // check if secret exists + const secretInfo = yield this.accountDataAdapter.getAccountDataFromServer(name); + if (!(secretInfo === null || secretInfo === void 0 ? void 0 : secretInfo.encrypted)) + return null; + const ret = {}; + // filter secret encryption keys with supported algorithm + for (const keyId of Object.keys(secretInfo.encrypted)) { + // get key information from key storage + const keyInfo = yield this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId); + if (!keyInfo) + continue; + const encInfo = secretInfo.encrypted[keyId]; + // only use keys we understand the encryption algorithm of + if (keyInfo.algorithm === exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + if (encInfo.iv && encInfo.ciphertext && encInfo.mac) { + ret[keyId] = keyInfo; + } + } + } + return Object.keys(ret).length ? ret : null; + }); + } + /** + * Request a secret from another device + * + * @param name - the name of the secret to request + * @param devices - the devices to request the secret from + */ + request(name, devices) { + const requestId = this.baseApis.makeTxnId(); + const deferred = (0, utils_1.defer)(); + this.requests.set(requestId, { name, devices, deferred }); + const cancel = (reason) => { + // send cancellation event + const cancelData = { + action: "request_cancellation", + requesting_device_id: this.baseApis.deviceId, + request_id: requestId, + }; + const toDevice = new Map(); + for (const device of devices) { + toDevice.set(device, cancelData); + } + this.baseApis.sendToDevice("m.secret.request", new Map([[this.baseApis.getUserId(), toDevice]])); + // and reject the promise so that anyone waiting on it will be + // notified + deferred.reject(new Error(reason || "Cancelled")); + }; + // send request to devices + const requestData = { + name, + action: "request", + requesting_device_id: this.baseApis.deviceId, + request_id: requestId, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + const toDevice = new Map(); + for (const device of devices) { + toDevice.set(device, requestData); + } + logger_1.logger.info(`Request secret ${name} from ${devices}, id ${requestId}`); + this.baseApis.sendToDevice("m.secret.request", new Map([[this.baseApis.getUserId(), toDevice]])); + return { + requestId, + promise: deferred.promise, + cancel, + }; + } + onRequestReceived(event) { + return __awaiter(this, void 0, void 0, function* () { + const sender = event.getSender(); + const content = event.getContent(); + if (sender !== this.baseApis.getUserId() || + !(content.name && content.action && content.requesting_device_id && content.request_id)) { + // ignore requests from anyone else, for now + return; + } + const deviceId = content.requesting_device_id; + // check if it's a cancel + if (content.action === "request_cancellation") { + /* + Looks like we intended to emit events when we got cancelations, but + we never put anything in the _incomingRequests object, and the request + itself doesn't use events anyway so if we were to wire up cancellations, + they probably ought to use the same callback interface. I'm leaving them + disabled for now while converting this file to typescript. + if (this._incomingRequests[deviceId] + && this._incomingRequests[deviceId][content.request_id]) { + logger.info( + "received request cancellation for secret (" + sender + + ", " + deviceId + ", " + content.request_id + ")", + ); + this.baseApis.emit("crypto.secrets.requestCancelled", { + user_id: sender, + device_id: deviceId, + request_id: content.request_id, + }); + } + */ + } + else if (content.action === "request") { + if (deviceId === this.baseApis.deviceId) { + // no point in trying to send ourself the secret + return; + } + // check if we have the secret + logger_1.logger.info("received request for secret (" + sender + ", " + deviceId + ", " + content.request_id + ")"); + if (!this.cryptoCallbacks.onSecretRequested) { + return; + } + const secret = yield this.cryptoCallbacks.onSecretRequested(sender, deviceId, content.request_id, content.name, this.baseApis.checkDeviceTrust(sender, deviceId)); + if (secret) { + logger_1.logger.info(`Preparing ${content.name} secret for ${deviceId}`); + const payload = { + type: "m.secret.send", + content: { + request_id: content.request_id, + secret: secret, + }, + }; + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + yield olmlib.ensureOlmSessionsForDevices(this.baseApis.crypto.olmDevice, this.baseApis, new Map([[sender, [this.baseApis.getStoredDevice(sender, deviceId)]]])); + yield olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.baseApis.getUserId(), this.baseApis.deviceId, this.baseApis.crypto.olmDevice, sender, this.baseApis.getStoredDevice(sender, deviceId), payload); + const contentMap = new Map([[sender, new Map([[deviceId, encryptedContent]])]]); + logger_1.logger.info(`Sending ${content.name} secret for ${deviceId}`); + this.baseApis.sendToDevice("m.room.encrypted", contentMap); + } + else { + logger_1.logger.info(`Request denied for ${content.name} secret for ${deviceId}`); + } + } + }); + } + onSecretReceived(event) { + if (event.getSender() !== this.baseApis.getUserId()) { + // we shouldn't be receiving secrets from anyone else, so ignore + // because someone could be trying to send us bogus data + return; + } + if (!olmlib.isOlmEncrypted(event)) { + logger_1.logger.error("secret event not properly encrypted"); + return; + } + const content = event.getContent(); + const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(olmlib.OLM_ALGORITHM, event.getSenderKey() || ""); + if (senderKeyUser !== event.getSender()) { + logger_1.logger.error("sending device does not belong to the user it claims to be from"); + return; + } + logger_1.logger.log("got secret share for request", content.request_id); + const requestControl = this.requests.get(content.request_id); + if (requestControl) { + // make sure that the device that sent it is one of the devices that + // we requested from + const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, event.getSenderKey()); + if (!deviceInfo) { + logger_1.logger.log("secret share from unknown device with key", event.getSenderKey()); + return; + } + if (!requestControl.devices.includes(deviceInfo.deviceId)) { + logger_1.logger.log("unsolicited secret share from device", deviceInfo.deviceId); + return; + } + // unsure that the sender is trusted. In theory, this check is + // unnecessary since we only accept secret shares from devices that + // we requested from, but it doesn't hurt. + const deviceTrust = this.baseApis.crypto.checkDeviceInfoTrust(event.getSender(), deviceInfo); + if (!deviceTrust.isVerified()) { + logger_1.logger.log("secret share from unverified device"); + return; + } + logger_1.logger.log(`Successfully received secret ${requestControl.name} ` + `from ${deviceInfo.deviceId}`); + requestControl.deferred.resolve(content.secret); + } + } + getSecretStorageKey(keys, name) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.cryptoCallbacks.getSecretStorageKey) { + throw new Error("No getSecretStorageKey callback supplied"); + } + const returned = yield this.cryptoCallbacks.getSecretStorageKey({ keys }, name); + if (!returned) { + throw new Error("getSecretStorageKey callback returned falsey"); + } + if (returned.length < 2) { + throw new Error("getSecretStorageKey callback returned invalid data"); + } + const [keyId, privateKey] = returned; + if (!keys[keyId]) { + throw new Error("App returned unknown key from getSecretStorageKey!"); + } + if (keys[keyId].algorithm === exports.SECRET_STORAGE_ALGORITHM_V1_AES) { + const decryption = { + encrypt: function (secret) { + return (0, aes_1.encryptAES)(secret, privateKey, name); + }, + decrypt: function (encInfo) { + return (0, aes_1.decryptAES)(encInfo, privateKey, name); + }, + }; + return [keyId, decryption]; + } + else { + throw new Error("Unknown key type: " + keys[keyId].algorithm); + } + }); + } +} +exports.SecretStorage = SecretStorage; + +},{"../@types/event":306,"../client":321,"../logger":374,"../randomstring":398,"../utils":416,"./aes":331,"./olmlib":343,"uuid":287}],331:[function(require,module,exports){ +"use strict"; +/* +Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.calculateKeyCheck = exports.decryptAES = exports.encryptAES = void 0; +const olmlib_1 = require("./olmlib"); +const crypto_1 = require("./crypto"); +// salt for HKDF, with 8 bytes of zeros +const zeroSalt = new Uint8Array(8); +/** + * encrypt a string + * + * @param data - the plaintext to encrypt + * @param key - the encryption key to use + * @param name - the name of the secret + * @param ivStr - the initialization vector to use + */ +function encryptAES(data, key, name, ivStr) { + return __awaiter(this, void 0, void 0, function* () { + let iv; + if (ivStr) { + iv = (0, olmlib_1.decodeBase64)(ivStr); + } + else { + iv = new Uint8Array(16); + crypto_1.crypto.getRandomValues(iv); + // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary + // (which would mean we wouldn't be able to decrypt on Android). The loss + // of a single bit of iv is a price we have to pay. + iv[8] &= 0x7f; + } + const [aesKey, hmacKey] = yield deriveKeys(key, name); + const encodedData = new crypto_1.TextEncoder().encode(data); + const ciphertext = yield crypto_1.subtleCrypto.encrypt({ + name: "AES-CTR", + counter: iv, + length: 64, + }, aesKey, encodedData); + const hmac = yield crypto_1.subtleCrypto.sign({ name: "HMAC" }, hmacKey, ciphertext); + return { + iv: (0, olmlib_1.encodeBase64)(iv), + ciphertext: (0, olmlib_1.encodeBase64)(ciphertext), + mac: (0, olmlib_1.encodeBase64)(hmac), + }; + }); +} +exports.encryptAES = encryptAES; +/** + * decrypt a string + * + * @param data - the encrypted data + * @param key - the encryption key to use + * @param name - the name of the secret + */ +function decryptAES(data, key, name) { + return __awaiter(this, void 0, void 0, function* () { + const [aesKey, hmacKey] = yield deriveKeys(key, name); + const ciphertext = (0, olmlib_1.decodeBase64)(data.ciphertext); + if (!(yield crypto_1.subtleCrypto.verify({ name: "HMAC" }, hmacKey, (0, olmlib_1.decodeBase64)(data.mac), ciphertext))) { + throw new Error(`Error decrypting secret ${name}: bad MAC`); + } + const plaintext = yield crypto_1.subtleCrypto.decrypt({ + name: "AES-CTR", + counter: (0, olmlib_1.decodeBase64)(data.iv), + length: 64, + }, aesKey, ciphertext); + return new TextDecoder().decode(new Uint8Array(plaintext)); + }); +} +exports.decryptAES = decryptAES; +function deriveKeys(key, name) { + return __awaiter(this, void 0, void 0, function* () { + const hkdfkey = yield crypto_1.subtleCrypto.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]); + const keybits = yield crypto_1.subtleCrypto.deriveBits({ + name: "HKDF", + salt: zeroSalt, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879 + info: new crypto_1.TextEncoder().encode(name), + hash: "SHA-256", + }, hkdfkey, 512); + const aesKey = keybits.slice(0, 32); + const hmacKey = keybits.slice(32); + const aesProm = crypto_1.subtleCrypto.importKey("raw", aesKey, { name: "AES-CTR" }, false, ["encrypt", "decrypt"]); + const hmacProm = crypto_1.subtleCrypto.importKey("raw", hmacKey, { + name: "HMAC", + hash: { name: "SHA-256" }, + }, false, ["sign", "verify"]); + return Promise.all([aesProm, hmacProm]); + }); +} +// string of zeroes, for calculating the key check +const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/** Calculate the MAC for checking the key. + * + * @param key - the key to use + * @param iv - The initialization vector as a base64-encoded string. + * If omitted, a random initialization vector will be created. + * @returns An object that contains, `mac` and `iv` properties. + */ +function calculateKeyCheck(key, iv) { + return encryptAES(ZERO_STR, key, "", iv); +} +exports.calculateKeyCheck = calculateKeyCheck; + +},{"./crypto":338,"./olmlib":343}],332:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.registerAlgorithm = exports.UnknownDeviceError = exports.DecryptionError = exports.DecryptionAlgorithm = exports.EncryptionAlgorithm = exports.DECRYPTION_CLASSES = exports.ENCRYPTION_CLASSES = void 0; +/** + * Map of registered encryption algorithm classes. A map from string to {@link EncryptionAlgorithm} class + */ +exports.ENCRYPTION_CLASSES = new Map(); +/** + * map of registered encryption algorithm classes. Map from string to {@link DecryptionAlgorithm} class + */ +exports.DECRYPTION_CLASSES = new Map(); +/** + * base type for encryption implementations + */ +class EncryptionAlgorithm { + /** + * @param params - parameters + */ + constructor(params) { + this.userId = params.userId; + this.deviceId = params.deviceId; + this.crypto = params.crypto; + this.olmDevice = params.olmDevice; + this.baseApis = params.baseApis; + this.roomId = params.roomId; + } + /** + * Perform any background tasks that can be done before a message is ready to + * send, in order to speed up sending of the message. + * + * @param room - the room the event is in + */ + prepareToEncrypt(room) { } + /** + * Called when the membership of a member of the room changes. + * + * @param event - event causing the change + * @param member - user whose membership changed + * @param oldMembership - previous membership + * @public + */ + onRoomMembership(event, member, oldMembership) { } +} +exports.EncryptionAlgorithm = EncryptionAlgorithm; +/** + * base type for decryption implementations + */ +class DecryptionAlgorithm { + constructor(params) { + this.userId = params.userId; + this.crypto = params.crypto; + this.olmDevice = params.olmDevice; + this.baseApis = params.baseApis; + this.roomId = params.roomId; + } + /** + * Handle a key event + * + * @param params - event key event + */ + onRoomKeyEvent(params) { + return __awaiter(this, void 0, void 0, function* () { + // ignore by default + }); + } + /** + * Import a room key + * + * @param opts - object + */ + importRoomKey(session, opts) { + return __awaiter(this, void 0, void 0, function* () { + // ignore by default + }); + } + /** + * Determine if we have the keys necessary to respond to a room key request + * + * @returns true if we have the keys and could (theoretically) share + * them; else false. + */ + hasKeysForKeyRequest(keyRequest) { + return Promise.resolve(false); + } + /** + * Send the response to a room key request + * + */ + shareKeysWithDevice(keyRequest) { + throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm"); + } + /** + * Retry decrypting all the events from a sender that haven't been + * decrypted yet. + * + * @param senderKey - the sender's key + */ + retryDecryptionFromSender(senderKey) { + return __awaiter(this, void 0, void 0, function* () { + // ignore by default + return false; + }); + } +} +exports.DecryptionAlgorithm = DecryptionAlgorithm; +/** + * Exception thrown when decryption fails + * + * @param msg - user-visible message describing the problem + * + * @param details - key/value pairs reported in the logs but not shown + * to the user. + */ +class DecryptionError extends Error { + constructor(code, msg, details) { + super(msg); + this.code = code; + this.code = code; + this.name = "DecryptionError"; + this.detailedString = detailedStringForDecryptionError(this, details); + } +} +exports.DecryptionError = DecryptionError; +function detailedStringForDecryptionError(err, details) { + let result = err.name + "[msg: " + err.message; + if (details) { + result += + ", " + + Object.keys(details) + .map((k) => k + ": " + details[k]) + .join(", "); + } + result += "]"; + return result; +} +class UnknownDeviceError extends Error { + /** + * Exception thrown specifically when we want to warn the user to consider + * the security of their conversation before continuing + * + * @param msg - message describing the problem + * @param devices - set of unknown devices per user we're warning about + */ + constructor(msg, devices, event) { + super(msg); + this.devices = devices; + this.event = event; + this.name = "UnknownDeviceError"; + this.devices = devices; + } +} +exports.UnknownDeviceError = UnknownDeviceError; +/** + * Registers an encryption/decryption class for a particular algorithm + * + * @param algorithm - algorithm tag to register for + * + * @param encryptor - {@link EncryptionAlgorithm} implementation + * + * @param decryptor - {@link DecryptionAlgorithm} implementation + */ +function registerAlgorithm(algorithm, encryptor, decryptor) { + exports.ENCRYPTION_CLASSES.set(algorithm, encryptor); + exports.DECRYPTION_CLASSES.set(algorithm, decryptor); +} +exports.registerAlgorithm = registerAlgorithm; + +},{}],333:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +require("./olm"); +require("./megolm"); +__exportStar(require("./base"), exports); + +},{"./base":332,"./megolm":334,"./olm":335}],334:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021, 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MegolmDecryption = exports.MegolmEncryption = exports.isRoomSharedHistory = void 0; +/** + * Defines m.olm encryption/decryption + */ +const uuid_1 = require("uuid"); +const logger_1 = require("../../logger"); +const olmlib = __importStar(require("../olmlib")); +const base_1 = require("./base"); +const OlmDevice_1 = require("../OlmDevice"); +const event_1 = require("../../@types/event"); +const OutgoingRoomKeyRequestManager_1 = require("../OutgoingRoomKeyRequestManager"); +const utils_1 = require("../../utils"); +// determine whether the key can be shared with invitees +function isRoomSharedHistory(room) { + var _a, _b; + const visibilityEvent = (_a = room === null || room === void 0 ? void 0 : room.currentState) === null || _a === void 0 ? void 0 : _a.getStateEvents("m.room.history_visibility", ""); + // NOTE: if the room visibility is unset, it would normally default to + // "world_readable". + // (https://spec.matrix.org/unstable/client-server-api/#server-behaviour-5) + // But we will be paranoid here, and treat it as a situation where the room + // is not shared-history + const visibility = (_b = visibilityEvent === null || visibilityEvent === void 0 ? void 0 : visibilityEvent.getContent()) === null || _b === void 0 ? void 0 : _b.history_visibility; + return ["world_readable", "shared"].includes(visibility); +} +exports.isRoomSharedHistory = isRoomSharedHistory; +/** + * Tests whether an encrypted content has a ciphertext. + * Ciphertext can be a string or object depending on the content type {@link IEncryptedContent}. + * + * @param content - Encrypted content + * @returns true: has ciphertext, else false + */ +const hasCiphertext = (content) => { + return typeof content.ciphertext === "string" + ? !!content.ciphertext.length + : !!Object.keys(content.ciphertext).length; +}; +/** + * @internal + */ +class OutboundSessionInfo { + /** + * @param sharedHistory - whether the session can be freely shared with + * other group members, according to the room history visibility settings + */ + constructor(sessionId, sharedHistory = false) { + this.sessionId = sessionId; + this.sharedHistory = sharedHistory; + /** number of times this session has been used */ + this.useCount = 0; + /** devices with which we have shared the session key `userId -> {deviceId -> SharedWithData}` */ + this.sharedWithDevices = new utils_1.MapWithDefault(() => new Map()); + this.blockedDevicesNotified = new utils_1.MapWithDefault(() => new Map()); + this.creationTime = new Date().getTime(); + } + /** + * Check if it's time to rotate the session + */ + needsRotation(rotationPeriodMsgs, rotationPeriodMs) { + const sessionLifetime = new Date().getTime() - this.creationTime; + if (this.useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) { + logger_1.logger.log("Rotating megolm session after " + this.useCount + " messages, " + sessionLifetime + "ms"); + return true; + } + return false; + } + markSharedWithDevice(userId, deviceId, deviceKey, chainIndex) { + this.sharedWithDevices.getOrCreate(userId).set(deviceId, { deviceKey, messageIndex: chainIndex }); + } + markNotifiedBlockedDevice(userId, deviceId) { + this.blockedDevicesNotified.getOrCreate(userId).set(deviceId, true); + } + /** + * Determine if this session has been shared with devices which it shouldn't + * have been. + * + * @param devicesInRoom - `userId -> {deviceId -> object}` + * devices we should shared the session with. + * + * @returns true if we have shared the session with devices which aren't + * in devicesInRoom. + */ + sharedWithTooManyDevices(devicesInRoom) { + var _a; + for (const [userId, devices] of this.sharedWithDevices) { + if (!devicesInRoom.has(userId)) { + logger_1.logger.log("Starting new megolm session because we shared with " + userId); + return true; + } + for (const [deviceId] of devices) { + if (!((_a = devicesInRoom.get(userId)) === null || _a === void 0 ? void 0 : _a.get(deviceId))) { + logger_1.logger.log("Starting new megolm session because we shared with " + userId + ":" + deviceId); + return true; + } + } + } + return false; + } +} +/** + * Megolm encryption implementation + * + * @param params - parameters, as per {@link EncryptionAlgorithm} + */ +class MegolmEncryption extends base_1.EncryptionAlgorithm { + constructor(params) { + var _a, _b, _c, _d; + super(params); + // the most recent attempt to set up a session. This is used to serialise + // the session setups, so that we have a race-free view of which session we + // are using, and which devices we have shared the keys with. It resolves + // with an OutboundSessionInfo (or undefined, for the first message in the + // room). + this.setupPromise = Promise.resolve(null); + // Map of outbound sessions by sessions ID. Used if we need a particular + // session (the session we're currently using to send is always obtained + // using setupPromise). + this.outboundSessions = {}; + this.roomId = params.roomId; + this.prefixedLogger = logger_1.logger.withPrefix(`[${this.roomId} encryption]`); + this.sessionRotationPeriodMsgs = (_b = (_a = params.config) === null || _a === void 0 ? void 0 : _a.rotation_period_msgs) !== null && _b !== void 0 ? _b : 100; + this.sessionRotationPeriodMs = (_d = (_c = params.config) === null || _c === void 0 ? void 0 : _c.rotation_period_ms) !== null && _d !== void 0 ? _d : 7 * 24 * 3600 * 1000; + } + /** + * @internal + * + * @param devicesInRoom - The devices in this room, indexed by user ID + * @param blocked - The devices that are blocked, indexed by user ID + * @param singleOlmCreationPhase - Only perform one round of olm + * session creation + * + * This method updates the setupPromise field of the class by chaining a new + * call on top of the existing promise, and then catching and discarding any + * errors that might happen while setting up the outbound group session. This + * is done to ensure that `setupPromise` always resolves to `null` or the + * `OutboundSessionInfo`. + * + * Using `>>=` to represent the promise chaining operation, it does the + * following: + * + * ``` + * setupPromise = previousSetupPromise >>= setup >>= discardErrors + * ``` + * + * The initial value for the `setupPromise` is a promise that resolves to + * `null`. The forceDiscardSession() resets setupPromise to this initial + * promise. + * + * @returns Promise which resolves to the + * OutboundSessionInfo when setup is complete. + */ + ensureOutboundSession(room, devicesInRoom, blocked, singleOlmCreationPhase = false) { + return __awaiter(this, void 0, void 0, function* () { + // takes the previous OutboundSessionInfo, and considers whether to create + // a new one. Also shares the key with any (new) devices in the room. + // + // returns a promise which resolves once the keyshare is successful. + const setup = (oldSession) => __awaiter(this, void 0, void 0, function* () { + const sharedHistory = isRoomSharedHistory(room); + const session = yield this.prepareSession(devicesInRoom, sharedHistory, oldSession); + yield this.shareSession(devicesInRoom, sharedHistory, singleOlmCreationPhase, blocked, session); + return session; + }); + // first wait for the previous share to complete + const fallible = this.setupPromise.then(setup); + // Ensure any failures are logged for debugging and make sure that the + // promise chain remains unbroken + // + // setupPromise resolves to `null` or the `OutboundSessionInfo` whether + // or not the share succeeds + this.setupPromise = fallible.catch((e) => { + this.prefixedLogger.error(`Failed to setup outbound session`, e); + return null; + }); + // but we return a promise which only resolves if the share was successful. + return fallible; + }); + } + prepareSession(devicesInRoom, sharedHistory, session) { + return __awaiter(this, void 0, void 0, function* () { + // history visibility changed + if (session && sharedHistory !== session.sharedHistory) { + session = null; + } + // need to make a brand new session? + if (session === null || session === void 0 ? void 0 : session.needsRotation(this.sessionRotationPeriodMsgs, this.sessionRotationPeriodMs)) { + this.prefixedLogger.log("Starting new megolm session because we need to rotate."); + session = null; + } + // determine if we have shared with anyone we shouldn't have + if (session === null || session === void 0 ? void 0 : session.sharedWithTooManyDevices(devicesInRoom)) { + session = null; + } + if (!session) { + this.prefixedLogger.log("Starting new megolm session"); + session = yield this.prepareNewSession(sharedHistory); + this.prefixedLogger.log(`Started new megolm session ${session.sessionId}`); + this.outboundSessions[session.sessionId] = session; + } + return session; + }); + } + shareSession(devicesInRoom, sharedHistory, singleOlmCreationPhase, blocked, session) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // now check if we need to share with any devices + const shareMap = {}; + for (const [userId, userDevices] of devicesInRoom) { + for (const [deviceId, deviceInfo] of userDevices) { + const key = deviceInfo.getIdentityKey(); + if (key == this.olmDevice.deviceCurve25519Key) { + // don't bother sending to ourself + continue; + } + if (!((_a = session.sharedWithDevices.get(userId)) === null || _a === void 0 ? void 0 : _a.get(deviceId))) { + shareMap[userId] = shareMap[userId] || []; + shareMap[userId].push(deviceInfo); + } + } + } + const key = this.olmDevice.getOutboundGroupSessionKey(session.sessionId); + const payload = { + type: "m.room_key", + content: { + "algorithm": olmlib.MEGOLM_ALGORITHM, + "room_id": this.roomId, + "session_id": session.sessionId, + "session_key": key.key, + "chain_index": key.chain_index, + "org.matrix.msc3061.shared_history": sharedHistory, + }, + }; + const [devicesWithoutSession, olmSessions] = yield olmlib.getExistingOlmSessions(this.olmDevice, this.baseApis, shareMap); + yield Promise.all([ + (() => __awaiter(this, void 0, void 0, function* () { + // share keys with devices that we already have a session for + const olmSessionList = Array.from(olmSessions.entries()) + .map(([userId, sessionsByUser]) => Array.from(sessionsByUser.entries()).map(([deviceId, session]) => `${userId}/${deviceId}: ${session.sessionId}`)) + .flat(1); + this.prefixedLogger.debug("Sharing keys with devices with existing Olm sessions:", olmSessionList); + yield this.shareKeyWithOlmSessions(session, key, payload, olmSessions); + this.prefixedLogger.debug("Shared keys with existing Olm sessions"); + }))(), + (() => __awaiter(this, void 0, void 0, function* () { + const deviceList = Array.from(devicesWithoutSession.entries()) + .map(([userId, devicesByUser]) => devicesByUser.map((device) => `${userId}/${device.deviceId}`)) + .flat(1); + this.prefixedLogger.debug("Sharing keys (start phase 1) with devices without existing Olm sessions:", deviceList); + const errorDevices = []; + // meanwhile, establish olm sessions for devices that we don't + // already have a session for, and share keys with them. If + // we're doing two phases of olm session creation, use a + // shorter timeout when fetching one-time keys for the first + // phase. + const start = Date.now(); + const failedServers = []; + yield this.shareKeyWithDevices(session, key, payload, devicesWithoutSession, errorDevices, singleOlmCreationPhase ? 10000 : 2000, failedServers); + this.prefixedLogger.debug("Shared keys (end phase 1) with devices without existing Olm sessions"); + if (!singleOlmCreationPhase && Date.now() - start < 10000) { + // perform the second phase of olm session creation if requested, + // and if the first phase didn't take too long + (() => __awaiter(this, void 0, void 0, function* () { + // Retry sending keys to devices that we were unable to establish + // an olm session for. This time, we use a longer timeout, but we + // do this in the background and don't block anything else while we + // do this. We only need to retry users from servers that didn't + // respond the first time. + const retryDevices = new utils_1.MapWithDefault(() => []); + const failedServerMap = new Set(); + for (const server of failedServers) { + failedServerMap.add(server); + } + const failedDevices = []; + for (const { userId, deviceInfo } of errorDevices) { + const userHS = userId.slice(userId.indexOf(":") + 1); + if (failedServerMap.has(userHS)) { + retryDevices.getOrCreate(userId).push(deviceInfo); + } + else { + // if we aren't going to retry, then handle it + // as a failed device + failedDevices.push({ userId, deviceInfo }); + } + } + const retryDeviceList = Array.from(retryDevices.entries()) + .map(([userId, devicesByUser]) => devicesByUser.map((device) => `${userId}/${device.deviceId}`)) + .flat(1); + if (retryDeviceList.length > 0) { + this.prefixedLogger.debug("Sharing keys (start phase 2) with devices without existing Olm sessions:", retryDeviceList); + yield this.shareKeyWithDevices(session, key, payload, retryDevices, failedDevices, 30000); + this.prefixedLogger.debug("Shared keys (end phase 2) with devices without existing Olm sessions"); + } + yield this.notifyFailedOlmDevices(session, key, failedDevices); + }))(); + } + else { + yield this.notifyFailedOlmDevices(session, key, errorDevices); + } + }))(), + (() => __awaiter(this, void 0, void 0, function* () { + var _b; + this.prefixedLogger.debug(`There are ${blocked.size} blocked devices:`, Array.from(blocked.entries()) + .map(([userId, blockedByUser]) => Array.from(blockedByUser.entries()).map(([deviceId, _deviceInfo]) => `${userId}/${deviceId}`)) + .flat(1)); + // also, notify newly blocked devices that they're blocked + const blockedMap = new utils_1.MapWithDefault(() => new Map()); + let blockedCount = 0; + for (const [userId, userBlockedDevices] of blocked) { + for (const [deviceId, device] of userBlockedDevices) { + if (((_b = session.blockedDevicesNotified.get(userId)) === null || _b === void 0 ? void 0 : _b.get(deviceId)) === undefined) { + blockedMap.getOrCreate(userId).set(deviceId, { device }); + blockedCount++; + } + } + } + if (blockedCount) { + this.prefixedLogger.debug(`Notifying ${blockedCount} newly blocked devices:`, Array.from(blockedMap.entries()) + .map(([userId, blockedByUser]) => Object.entries(blockedByUser).map(([deviceId, _deviceInfo]) => `${userId}/${deviceId}`)) + .flat(1)); + yield this.notifyBlockedDevices(session, blockedMap); + this.prefixedLogger.debug(`Notified ${blockedCount} newly blocked devices`); + } + }))(), + ]); + }); + } + /** + * @internal + * + * + * @returns session + */ + prepareNewSession(sharedHistory) { + return __awaiter(this, void 0, void 0, function* () { + const sessionId = this.olmDevice.createOutboundGroupSession(); + const key = this.olmDevice.getOutboundGroupSessionKey(sessionId); + yield this.olmDevice.addInboundGroupSession(this.roomId, this.olmDevice.deviceCurve25519Key, [], sessionId, key.key, { ed25519: this.olmDevice.deviceEd25519Key }, false, { sharedHistory }); + // don't wait for it to complete + this.crypto.backupManager.backupGroupSession(this.olmDevice.deviceCurve25519Key, sessionId); + return new OutboundSessionInfo(sessionId, sharedHistory); + }); + } + /** + * Determines what devices in devicesByUser don't have an olm session as given + * in devicemap. + * + * @internal + * + * @param deviceMap - the devices that have olm sessions, as returned by + * olmlib.ensureOlmSessionsForDevices. + * @param devicesByUser - a map of user IDs to array of deviceInfo + * @param noOlmDevices - an array to fill with devices that don't have + * olm sessions + * + * @returns an array of devices that don't have olm sessions. If + * noOlmDevices is specified, then noOlmDevices will be returned. + */ + getDevicesWithoutSessions(deviceMap, devicesByUser, noOlmDevices = []) { + for (const [userId, devicesToShareWith] of devicesByUser) { + const sessionResults = deviceMap.get(userId); + for (const deviceInfo of devicesToShareWith) { + const deviceId = deviceInfo.deviceId; + const sessionResult = sessionResults === null || sessionResults === void 0 ? void 0 : sessionResults.get(deviceId); + if (!(sessionResult === null || sessionResult === void 0 ? void 0 : sessionResult.sessionId)) { + // no session with this device, probably because there + // were no one-time keys. + noOlmDevices.push({ userId, deviceInfo }); + sessionResults === null || sessionResults === void 0 ? void 0 : sessionResults.delete(deviceId); + // ensureOlmSessionsForUsers has already done the logging, + // so just skip it. + continue; + } + } + } + return noOlmDevices; + } + /** + * Splits the user device map into multiple chunks to reduce the number of + * devices we encrypt to per API call. + * + * @internal + * + * @param devicesByUser - map from userid to list of devices + * + * @returns the blocked devices, split into chunks + */ + splitDevices(devicesByUser) { + const maxDevicesPerRequest = 20; + // use an array where the slices of a content map gets stored + let currentSlice = []; + const mapSlices = [currentSlice]; + for (const [userId, userDevices] of devicesByUser) { + for (const deviceInfo of userDevices.values()) { + currentSlice.push({ + userId: userId, + deviceInfo: deviceInfo.device, + }); + } + // We do this in the per-user loop as we prefer that all messages to the + // same user end up in the same API call to make it easier for the + // server (e.g. only have to send one EDU if a remote user, etc). This + // does mean that if a user has many devices we may go over the desired + // limit, but its not a hard limit so that is fine. + if (currentSlice.length > maxDevicesPerRequest) { + // the current slice is filled up. Start inserting into the next slice + currentSlice = []; + mapSlices.push(currentSlice); + } + } + if (currentSlice.length === 0) { + mapSlices.pop(); + } + return mapSlices; + } + /** + * @internal + * + * + * @param chainIndex - current chain index + * + * @param userDeviceMap - mapping from userId to deviceInfo + * + * @param payload - fields to include in the encrypted payload + * + * @returns Promise which resolves once the key sharing + * for the given userDeviceMap is generated and has been sent. + */ + encryptAndSendKeysToDevices(session, chainIndex, devices, payload) { + return this.crypto + .encryptAndSendToDevices(devices, payload) + .then(() => { + // store that we successfully uploaded the keys of the current slice + for (const device of devices) { + session.markSharedWithDevice(device.userId, device.deviceInfo.deviceId, device.deviceInfo.getIdentityKey(), chainIndex); + } + }) + .catch((error) => { + this.prefixedLogger.error("failed to encryptAndSendToDevices", error); + throw error; + }); + } + /** + * @internal + * + * + * @param userDeviceMap - list of blocked devices to notify + * + * @param payload - fields to include in the notification payload + * + * @returns Promise which resolves once the notifications + * for the given userDeviceMap is generated and has been sent. + */ + sendBlockedNotificationsToDevices(session, userDeviceMap, payload) { + return __awaiter(this, void 0, void 0, function* () { + const contentMap = new utils_1.MapWithDefault(() => new Map()); + for (const val of userDeviceMap) { + const userId = val.userId; + const blockedInfo = val.deviceInfo; + const deviceInfo = blockedInfo.deviceInfo; + const deviceId = deviceInfo.deviceId; + const message = Object.assign(Object.assign({}, payload), { code: blockedInfo.code, reason: blockedInfo.reason, [event_1.ToDeviceMessageId]: (0, uuid_1.v4)() }); + if (message.code === "m.no_olm") { + delete message.room_id; + delete message.session_id; + } + contentMap.getOrCreate(userId).set(deviceId, message); + } + yield this.baseApis.sendToDevice("m.room_key.withheld", contentMap); + // record the fact that we notified these blocked devices + for (const [userId, userDeviceMap] of contentMap) { + for (const deviceId of userDeviceMap.keys()) { + session.markNotifiedBlockedDevice(userId, deviceId); + } + } + }); + } + /** + * Re-shares a megolm session key with devices if the key has already been + * sent to them. + * + * @param senderKey - The key of the originating device for the session + * @param sessionId - ID of the outbound session to share + * @param userId - ID of the user who owns the target device + * @param device - The target device + */ + reshareKeyWithDevice(senderKey, sessionId, userId, device) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const obSessionInfo = this.outboundSessions[sessionId]; + if (!obSessionInfo) { + this.prefixedLogger.debug(`megolm session ${senderKey}|${sessionId} not found: not re-sharing keys`); + return; + } + // The chain index of the key we previously sent this device + if (!obSessionInfo.sharedWithDevices.has(userId)) { + this.prefixedLogger.debug(`megolm session ${senderKey}|${sessionId} never shared with user ${userId}`); + return; + } + const sessionSharedData = (_a = obSessionInfo.sharedWithDevices.get(userId)) === null || _a === void 0 ? void 0 : _a.get(device.deviceId); + if (sessionSharedData === undefined) { + this.prefixedLogger.debug(`megolm session ${senderKey}|${sessionId} never shared with device ${userId}:${device.deviceId}`); + return; + } + if (sessionSharedData.deviceKey !== device.getIdentityKey()) { + this.prefixedLogger.warn(`Megolm session ${senderKey}|${sessionId} has been shared with device ${device.deviceId} but ` + + `with identity key ${sessionSharedData.deviceKey}. Key is now ${device.getIdentityKey()}!`); + return; + } + // get the key from the inbound session: the outbound one will already + // have been ratcheted to the next chain index. + const key = yield this.olmDevice.getInboundGroupSessionKey(this.roomId, senderKey, sessionId, sessionSharedData.messageIndex); + if (!key) { + this.prefixedLogger.warn(`No inbound session key found for megolm session ${senderKey}|${sessionId}: not re-sharing keys`); + return; + } + yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, new Map([[userId, [device]]])); + const payload = { + type: "m.forwarded_room_key", + content: { + "algorithm": olmlib.MEGOLM_ALGORITHM, + "room_id": this.roomId, + "session_id": sessionId, + "session_key": key.key, + "chain_index": key.chain_index, + "sender_key": senderKey, + "sender_claimed_ed25519_key": key.sender_claimed_ed25519_key, + "forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain, + "org.matrix.msc3061.shared_history": key.shared_history || false, + }, + }; + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + yield olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, userId, device, payload); + yield this.baseApis.sendToDevice("m.room.encrypted", new Map([[userId, new Map([[device.deviceId, encryptedContent]])]])); + this.prefixedLogger.debug(`Re-shared key for megolm session ${senderKey}|${sessionId} with ${userId}:${device.deviceId}`); + }); + } + /** + * @internal + * + * + * @param key - the session key as returned by + * OlmDevice.getOutboundGroupSessionKey + * + * @param payload - the base to-device message payload for sharing keys + * + * @param devicesByUser - map from userid to list of devices + * + * @param errorDevices - array that will be populated with the devices that we can't get an + * olm session for + * + * @param otkTimeout - The timeout in milliseconds when requesting + * one-time keys for establishing new olm sessions. + * + * @param failedServers - An array to fill with remote servers that + * failed to respond to one-time-key requests. + */ + shareKeyWithDevices(session, key, payload, devicesByUser, errorDevices, otkTimeout, failedServers) { + return __awaiter(this, void 0, void 0, function* () { + const devicemap = yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, false, otkTimeout, failedServers, this.prefixedLogger); + this.getDevicesWithoutSessions(devicemap, devicesByUser, errorDevices); + yield this.shareKeyWithOlmSessions(session, key, payload, devicemap); + }); + } + shareKeyWithOlmSessions(session, key, payload, deviceMap) { + return __awaiter(this, void 0, void 0, function* () { + const userDeviceMaps = this.splitDevices(deviceMap); + for (let i = 0; i < userDeviceMaps.length; i++) { + const taskDetail = `megolm keys for ${session.sessionId} (slice ${i + 1}/${userDeviceMaps.length})`; + try { + this.prefixedLogger.debug(`Sharing ${taskDetail}`, userDeviceMaps[i].map((d) => `${d.userId}/${d.deviceInfo.deviceId}`)); + yield this.encryptAndSendKeysToDevices(session, key.chain_index, userDeviceMaps[i], payload); + this.prefixedLogger.debug(`Shared ${taskDetail}`); + } + catch (e) { + this.prefixedLogger.error(`Failed to share ${taskDetail}`); + throw e; + } + } + }); + } + /** + * Notify devices that we weren't able to create olm sessions. + * + * + * + * @param failedDevices - the devices that we were unable to + * create olm sessions for, as returned by shareKeyWithDevices + */ + notifyFailedOlmDevices(session, key, failedDevices) { + return __awaiter(this, void 0, void 0, function* () { + this.prefixedLogger.debug(`Notifying ${failedDevices.length} devices we failed to create Olm sessions`); + // mark the devices that failed as "handled" because we don't want to try + // to claim a one-time-key for dead devices on every message. + for (const { userId, deviceInfo } of failedDevices) { + const deviceId = deviceInfo.deviceId; + session.markSharedWithDevice(userId, deviceId, deviceInfo.getIdentityKey(), key.chain_index); + } + const unnotifiedFailedDevices = yield this.olmDevice.filterOutNotifiedErrorDevices(failedDevices); + this.prefixedLogger.debug(`Need to notify ${unnotifiedFailedDevices.length} failed devices which haven't been notified before`); + const blockedMap = new utils_1.MapWithDefault(() => new Map()); + for (const { userId, deviceInfo } of unnotifiedFailedDevices) { + // we use a similar format to what + // olmlib.ensureOlmSessionsForDevices returns, so that + // we can use the same function to split + blockedMap.getOrCreate(userId).set(deviceInfo.deviceId, { + device: { + code: "m.no_olm", + reason: OlmDevice_1.WITHHELD_MESSAGES["m.no_olm"], + deviceInfo, + }, + }); + } + // send the notifications + yield this.notifyBlockedDevices(session, blockedMap); + this.prefixedLogger.debug(`Notified ${unnotifiedFailedDevices.length} devices we failed to create Olm sessions`); + }); + } + /** + * Notify blocked devices that they have been blocked. + * + * + * @param devicesByUser - map from userid to device ID to blocked data + */ + notifyBlockedDevices(session, devicesByUser) { + return __awaiter(this, void 0, void 0, function* () { + const payload = { + room_id: this.roomId, + session_id: session.sessionId, + algorithm: olmlib.MEGOLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + }; + const userDeviceMaps = this.splitDevices(devicesByUser); + for (let i = 0; i < userDeviceMaps.length; i++) { + try { + yield this.sendBlockedNotificationsToDevices(session, userDeviceMaps[i], payload); + this.prefixedLogger.log(`Completed blacklist notification for ${session.sessionId} ` + + `(slice ${i + 1}/${userDeviceMaps.length})`); + } + catch (e) { + this.prefixedLogger.log(`blacklist notification for ${session.sessionId} ` + + `(slice ${i + 1}/${userDeviceMaps.length}) failed`); + throw e; + } + } + }); + } + /** + * Perform any background tasks that can be done before a message is ready to + * send, in order to speed up sending of the message. + * + * @param room - the room the event is in + * @returns A function that, when called, will stop the preparation + */ + prepareToEncrypt(room) { + if (room.roomId !== this.roomId) { + throw new Error("MegolmEncryption.prepareToEncrypt called on unexpected room"); + } + if (this.encryptionPreparation != null) { + // We're already preparing something, so don't do anything else. + const elapsedTime = Date.now() - this.encryptionPreparation.startTime; + this.prefixedLogger.debug(`Already started preparing to encrypt for this room ${elapsedTime}ms ago, skipping`); + return this.encryptionPreparation.cancel; + } + this.prefixedLogger.debug("Preparing to encrypt events"); + let cancelled = false; + const isCancelled = () => cancelled; + this.encryptionPreparation = { + startTime: Date.now(), + promise: (() => __awaiter(this, void 0, void 0, function* () { + try { + // Attempt to enumerate the devices in room, and gracefully + // handle cancellation if it occurs. + const getDevicesResult = yield this.getDevicesInRoom(room, false, isCancelled); + if (getDevicesResult === null) + return; + const [devicesInRoom, blocked] = getDevicesResult; + if (this.crypto.globalErrorOnUnknownDevices) { + // Drop unknown devices for now. When the message gets sent, we'll + // throw an error, but we'll still be prepared to send to the known + // devices. + this.removeUnknownDevices(devicesInRoom); + } + this.prefixedLogger.debug("Ensuring outbound megolm session"); + yield this.ensureOutboundSession(room, devicesInRoom, blocked, true); + this.prefixedLogger.debug("Ready to encrypt events"); + } + catch (e) { + this.prefixedLogger.error("Failed to prepare to encrypt events", e); + } + finally { + delete this.encryptionPreparation; + } + }))(), + cancel: () => { + // The caller has indicated that the process should be cancelled, + // so tell the promise that we'd like to halt, and reset the preparation state. + cancelled = true; + delete this.encryptionPreparation; + }, + }; + return this.encryptionPreparation.cancel; + } + /** + * @param content - plaintext event content + * + * @returns Promise which resolves to the new event body + */ + encryptMessage(room, eventType, content) { + return __awaiter(this, void 0, void 0, function* () { + this.prefixedLogger.log("Starting to encrypt event"); + if (this.encryptionPreparation != null) { + // If we started sending keys, wait for it to be done. + // FIXME: check if we need to cancel + // (https://github.com/matrix-org/matrix-js-sdk/issues/1255) + try { + yield this.encryptionPreparation.promise; + } + catch (e) { + // ignore any errors -- if the preparation failed, we'll just + // restart everything here + } + } + /** + * When using in-room messages and the room has encryption enabled, + * clients should ensure that encryption does not hinder the verification. + */ + const forceDistributeToUnverified = this.isVerificationEvent(eventType, content); + const [devicesInRoom, blocked] = yield this.getDevicesInRoom(room, forceDistributeToUnverified); + // check if any of these devices are not yet known to the user. + // if so, warn the user so they can verify or ignore. + if (this.crypto.globalErrorOnUnknownDevices) { + this.checkForUnknownDevices(devicesInRoom); + } + const session = yield this.ensureOutboundSession(room, devicesInRoom, blocked); + const payloadJson = { + room_id: this.roomId, + type: eventType, + content: content, + }; + const ciphertext = this.olmDevice.encryptGroupMessage(session.sessionId, JSON.stringify(payloadJson)); + const encryptedContent = { + algorithm: olmlib.MEGOLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: ciphertext, + session_id: session.sessionId, + // Include our device ID so that recipients can send us a + // m.new_device message if they don't have our session key. + // XXX: Do we still need this now that m.new_device messages + // no longer exist since #483? + device_id: this.deviceId, + }; + session.useCount++; + return encryptedContent; + }); + } + isVerificationEvent(eventType, content) { + switch (eventType) { + case event_1.EventType.KeyVerificationCancel: + case event_1.EventType.KeyVerificationDone: + case event_1.EventType.KeyVerificationMac: + case event_1.EventType.KeyVerificationStart: + case event_1.EventType.KeyVerificationKey: + case event_1.EventType.KeyVerificationReady: + case event_1.EventType.KeyVerificationAccept: { + return true; + } + case event_1.EventType.RoomMessage: { + return content["msgtype"] === event_1.MsgType.KeyVerificationRequest; + } + default: { + return false; + } + } + } + /** + * Forces the current outbound group session to be discarded such + * that another one will be created next time an event is sent. + * + * This should not normally be necessary. + */ + forceDiscardSession() { + this.setupPromise = this.setupPromise.then(() => null); + } + /** + * Checks the devices we're about to send to and see if any are entirely + * unknown to the user. If so, warn the user, and mark them as known to + * give the user a chance to go verify them before re-sending this message. + * + * @param devicesInRoom - `userId -> {deviceId -> object}` + * devices we should shared the session with. + */ + checkForUnknownDevices(devicesInRoom) { + const unknownDevices = new utils_1.MapWithDefault(() => new Map()); + for (const [userId, userDevices] of devicesInRoom) { + for (const [deviceId, device] of userDevices) { + if (device.isUnverified() && !device.isKnown()) { + unknownDevices.getOrCreate(userId).set(deviceId, device); + } + } + } + if (unknownDevices.size) { + // it'd be kind to pass unknownDevices up to the user in this error + throw new base_1.UnknownDeviceError("This room contains unknown devices which have not been verified. " + + "We strongly recommend you verify them before continuing.", unknownDevices); + } + } + /** + * Remove unknown devices from a set of devices. The devicesInRoom parameter + * will be modified. + * + * @param devicesInRoom - `userId -> {deviceId -> object}` + * devices we should shared the session with. + */ + removeUnknownDevices(devicesInRoom) { + for (const [userId, userDevices] of devicesInRoom) { + for (const [deviceId, device] of userDevices) { + if (device.isUnverified() && !device.isKnown()) { + userDevices.delete(deviceId); + } + } + if (userDevices.size === 0) { + devicesInRoom.delete(userId); + } + } + } + getDevicesInRoom(room, forceDistributeToUnverified = false, isCancelled) { + return __awaiter(this, void 0, void 0, function* () { + const members = yield room.getEncryptionTargetMembers(); + this.prefixedLogger.debug(`Encrypting for users (shouldEncryptForInvitedMembers: ${room.shouldEncryptForInvitedMembers()}):`, members.map((u) => `${u.userId} (${u.membership})`)); + const roomMembers = members.map(function (u) { + return u.userId; + }); + // The global value is treated as a default for when rooms don't specify a value. + let isBlacklisting = this.crypto.globalBlacklistUnverifiedDevices; + const isRoomBlacklisting = room.getBlacklistUnverifiedDevices(); + if (typeof isRoomBlacklisting === "boolean") { + isBlacklisting = isRoomBlacklisting; + } + // We are happy to use a cached version here: we assume that if we already + // have a list of the user's devices, then we already share an e2e room + // with them, which means that they will have announced any new devices via + // device_lists in their /sync response. This cache should then be maintained + // using all the device_lists changes and left fields. + // See https://github.com/vector-im/element-web/issues/2305 for details. + const devices = yield this.crypto.downloadKeys(roomMembers, false); + if ((isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()) === true) { + return null; + } + const blocked = new utils_1.MapWithDefault(() => new Map()); + // remove any blocked devices + for (const [userId, userDevices] of devices) { + for (const [deviceId, userDevice] of userDevices) { + // Yield prior to checking each device so that we don't block + // updating/rendering for too long. + // See https://github.com/vector-im/element-web/issues/21612 + if (isCancelled !== undefined) + yield (0, utils_1.immediate)(); + if ((isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()) === true) + return null; + const deviceTrust = this.crypto.checkDeviceTrust(userId, deviceId); + if (userDevice.isBlocked() || + (!deviceTrust.isVerified() && isBlacklisting && !forceDistributeToUnverified)) { + const blockedDevices = blocked.getOrCreate(userId); + const isBlocked = userDevice.isBlocked(); + blockedDevices.set(deviceId, { + code: isBlocked ? "m.blacklisted" : "m.unverified", + reason: OlmDevice_1.WITHHELD_MESSAGES[isBlocked ? "m.blacklisted" : "m.unverified"], + deviceInfo: userDevice, + }); + userDevices.delete(deviceId); + } + } + } + return [devices, blocked]; + }); + } +} +exports.MegolmEncryption = MegolmEncryption; +/** + * Megolm decryption implementation + * + * @param params - parameters, as per {@link DecryptionAlgorithm} + */ +class MegolmDecryption extends base_1.DecryptionAlgorithm { + constructor(params) { + super(params); + // events which we couldn't decrypt due to unknown sessions / + // indexes, or which we could only decrypt with untrusted keys: + // map from senderKey|sessionId to Set of MatrixEvents + this.pendingEvents = new Map(); + // this gets stubbed out by the unit tests. + this.olmlib = olmlib; + this.roomId = params.roomId; + this.prefixedLogger = logger_1.logger.withPrefix(`[${this.roomId} decryption]`); + } + /** + * returns a promise which resolves to a + * {@link EventDecryptionResult} once we have finished + * decrypting, or rejects with an `algorithms.DecryptionError` if there is a + * problem decrypting the event. + */ + decryptEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getWireContent(); + if (!content.sender_key || !content.session_id || !content.ciphertext) { + throw new base_1.DecryptionError("MEGOLM_MISSING_FIELDS", "Missing fields in input"); + } + // we add the event to the pending list *before* we start decryption. + // + // then, if the key turns up while decryption is in progress (and + // decryption fails), we will schedule a retry. + // (fixes https://github.com/vector-im/element-web/issues/5001) + this.addEventToPendingList(event); + let res; + try { + res = yield this.olmDevice.decryptGroupMessage(event.getRoomId(), content.sender_key, content.session_id, content.ciphertext, event.getId(), event.getTs()); + } + catch (e) { + if (e.name === "DecryptionError") { + // re-throw decryption errors as-is + throw e; + } + let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR"; + if ((e === null || e === void 0 ? void 0 : e.message) === "OLM.UNKNOWN_MESSAGE_INDEX") { + this.requestKeysForEvent(event); + errorCode = "OLM_UNKNOWN_MESSAGE_INDEX"; + } + throw new base_1.DecryptionError(errorCode, e instanceof Error ? e.message : "Unknown Error: Error is undefined", { + session: content.sender_key + "|" + content.session_id, + }); + } + if (res === null) { + // We've got a message for a session we don't have. + // try and get the missing key from the backup first + this.crypto.backupManager.queryKeyBackupRateLimited(event.getRoomId(), content.session_id).catch(() => { }); + // (XXX: We might actually have received this key since we started + // decrypting, in which case we'll have scheduled a retry, and this + // request will be redundant. We could probably check to see if the + // event is still in the pending list; if not, a retry will have been + // scheduled, so we needn't send out the request here.) + this.requestKeysForEvent(event); + // See if there was a problem with the olm session at the time the + // event was sent. Use a fuzz factor of 2 minutes. + const problem = yield this.olmDevice.sessionMayHaveProblems(content.sender_key, event.getTs() - 120000); + if (problem) { + this.prefixedLogger.info(`When handling UISI from ${event.getSender()} (sender key ${content.sender_key}): ` + + `recent session problem with that sender:`, problem); + let problemDescription = PROBLEM_DESCRIPTIONS[problem.type] || PROBLEM_DESCRIPTIONS.unknown; + if (problem.fixed) { + problemDescription += " Trying to create a new secure channel and re-requesting the keys."; + } + throw new base_1.DecryptionError("MEGOLM_UNKNOWN_INBOUND_SESSION_ID", problemDescription, { + session: content.sender_key + "|" + content.session_id, + }); + } + throw new base_1.DecryptionError("MEGOLM_UNKNOWN_INBOUND_SESSION_ID", "The sender's device has not sent us the keys for this message.", { + session: content.sender_key + "|" + content.session_id, + }); + } + // Success. We can remove the event from the pending list, if + // that hasn't already happened. However, if the event was + // decrypted with an untrusted key, leave it on the pending + // list so it will be retried if we find a trusted key later. + if (!res.untrusted) { + this.removeEventFromPendingList(event); + } + const payload = JSON.parse(res.result); + // belt-and-braces check that the room id matches that indicated by the HS + // (this is somewhat redundant, since the megolm session is scoped to the + // room, so neither the sender nor a MITM can lie about the room_id). + if (payload.room_id !== event.getRoomId()) { + throw new base_1.DecryptionError("MEGOLM_BAD_ROOM", "Message intended for room " + payload.room_id); + } + return { + clearEvent: payload, + senderCurve25519Key: res.senderKey, + claimedEd25519Key: res.keysClaimed.ed25519, + forwardingCurve25519KeyChain: res.forwardingCurve25519KeyChain, + untrusted: res.untrusted, + }; + }); + } + requestKeysForEvent(event) { + const wireContent = event.getWireContent(); + const recipients = event.getKeyRequestRecipients(this.userId); + this.crypto.requestRoomKey({ + room_id: event.getRoomId(), + algorithm: wireContent.algorithm, + sender_key: wireContent.sender_key, + session_id: wireContent.session_id, + }, recipients); + } + /** + * Add an event to the list of those awaiting their session keys. + * + * @internal + * + */ + addEventToPendingList(event) { + var _a; + const content = event.getWireContent(); + const senderKey = content.sender_key; + const sessionId = content.session_id; + if (!this.pendingEvents.has(senderKey)) { + this.pendingEvents.set(senderKey, new Map()); + } + const senderPendingEvents = this.pendingEvents.get(senderKey); + if (!senderPendingEvents.has(sessionId)) { + senderPendingEvents.set(sessionId, new Set()); + } + (_a = senderPendingEvents.get(sessionId)) === null || _a === void 0 ? void 0 : _a.add(event); + } + /** + * Remove an event from the list of those awaiting their session keys. + * + * @internal + * + */ + removeEventFromPendingList(event) { + const content = event.getWireContent(); + const senderKey = content.sender_key; + const sessionId = content.session_id; + const senderPendingEvents = this.pendingEvents.get(senderKey); + const pendingEvents = senderPendingEvents === null || senderPendingEvents === void 0 ? void 0 : senderPendingEvents.get(sessionId); + if (!pendingEvents) { + return; + } + pendingEvents.delete(event); + if (pendingEvents.size === 0) { + senderPendingEvents.delete(sessionId); + } + if (senderPendingEvents.size === 0) { + this.pendingEvents.delete(senderKey); + } + } + /** + * Parse a RoomKey out of an `m.room_key` event. + * + * @param event - the event containing the room key. + * + * @returns The `RoomKey` if it could be successfully parsed out of the + * event. + * + * @internal + * + */ + roomKeyFromEvent(event) { + const senderKey = event.getSenderKey(); + const content = event.getContent(); + const extraSessionData = {}; + if (!content.room_id || !content.session_key || !content.session_id || !content.algorithm) { + this.prefixedLogger.error("key event is missing fields"); + return; + } + if (!olmlib.isOlmEncrypted(event)) { + this.prefixedLogger.error("key event not properly encrypted"); + return; + } + if (content["org.matrix.msc3061.shared_history"]) { + extraSessionData.sharedHistory = true; + } + const roomKey = { + senderKey: senderKey, + sessionId: content.session_id, + sessionKey: content.session_key, + extraSessionData, + exportFormat: false, + roomId: content.room_id, + algorithm: content.algorithm, + forwardingKeyChain: [], + keysClaimed: event.getKeysClaimed(), + }; + return roomKey; + } + /** + * Parse a RoomKey out of an `m.forwarded_room_key` event. + * + * @param event - the event containing the forwarded room key. + * + * @returns The `RoomKey` if it could be successfully parsed out of the + * event. + * + * @internal + * + */ + forwardedRoomKeyFromEvent(event) { + // the properties in m.forwarded_room_key are a superset of those in m.room_key, so + // start by parsing the m.room_key fields. + const roomKey = this.roomKeyFromEvent(event); + if (!roomKey) { + return; + } + const senderKey = event.getSenderKey(); + const content = event.getContent(); + const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(olmlib.OLM_ALGORITHM, senderKey); + // We received this to-device event from event.getSenderKey(), but the original + // creator of the room key is claimed in the content. + const claimedCurve25519Key = content.sender_key; + const claimedEd25519Key = content.sender_claimed_ed25519_key; + let forwardingKeyChain = Array.isArray(content.forwarding_curve25519_key_chain) + ? content.forwarding_curve25519_key_chain + : []; + // copy content before we modify it + forwardingKeyChain = forwardingKeyChain.slice(); + forwardingKeyChain.push(senderKey); + // Check if we have all the fields we need. + if (senderKeyUser !== event.getSender()) { + this.prefixedLogger.error("sending device does not belong to the user it claims to be from"); + return; + } + if (!claimedCurve25519Key) { + this.prefixedLogger.error("forwarded_room_key event is missing sender_key field"); + return; + } + if (!claimedEd25519Key) { + this.prefixedLogger.error(`forwarded_room_key_event is missing sender_claimed_ed25519_key field`); + return; + } + const keysClaimed = { + ed25519: claimedEd25519Key, + }; + // FIXME: We're reusing the same field to track both: + // + // 1. The Olm identity we've received this room key from. + // 2. The Olm identity deduced (in the trusted case) or claiming (in the + // untrusted case) to be the original creator of this room key. + // + // We now overwrite the value tracking usage 1 with the value tracking usage 2. + roomKey.senderKey = claimedCurve25519Key; + // Replace our keysClaimed as well. + roomKey.keysClaimed = keysClaimed; + roomKey.exportFormat = true; + roomKey.forwardingKeyChain = forwardingKeyChain; + // forwarded keys are always untrusted + roomKey.extraSessionData.untrusted = true; + return roomKey; + } + /** + * Determine if we should accept the forwarded room key that was found in the given + * event. + * + * @param event - An `m.forwarded_room_key` event. + * @param roomKey - The room key that was found in the event. + * + * @returns promise that will resolve to a boolean telling us if it's ok to + * accept the given forwarded room key. + * + * @internal + * + */ + shouldAcceptForwardedKey(event, roomKey) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const senderKey = event.getSenderKey(); + const sendingDevice = (_a = this.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, senderKey)) !== null && _a !== void 0 ? _a : undefined; + const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice); + // Using the plaintext sender here is fine since we checked that the + // sender matches to the user id in the device keys when this event was + // originally decrypted. This can obviously only happen if the device + // keys have been downloaded, but if they haven't the + // `deviceTrust.isVerified()` flag would be false as well. + // + // It would still be far nicer if the `sendingDevice` had a user ID + // attached to it that went through signature checks. + const fromUs = event.getSender() === this.baseApis.getUserId(); + const keyFromOurVerifiedDevice = deviceTrust.isVerified() && fromUs; + const weRequested = yield this.wasRoomKeyRequested(event, roomKey); + const fromInviter = this.wasRoomKeyForwardedByInviter(event, roomKey); + const sharedAsHistory = this.wasRoomKeyForwardedAsHistory(roomKey); + return (weRequested && keyFromOurVerifiedDevice) || (fromInviter && sharedAsHistory); + }); + } + /** + * Did we ever request the given room key from the event sender and its + * accompanying device. + * + * @param event - An `m.forwarded_room_key` event. + * @param roomKey - The room key that was found in the event. + * + * @internal + * + */ + wasRoomKeyRequested(event, roomKey) { + return __awaiter(this, void 0, void 0, function* () { + // We send the `m.room_key_request` out as a wildcard to-device request, + // otherwise we would have to duplicate the same content for each + // device. This is why we need to pass in "*" as the device id here. + const outgoingRequests = yield this.crypto.cryptoStore.getOutgoingRoomKeyRequestsByTarget(event.getSender(), "*", [OutgoingRoomKeyRequestManager_1.RoomKeyRequestState.Sent]); + return outgoingRequests.some((req) => req.requestBody.room_id === roomKey.roomId && req.requestBody.session_id === roomKey.sessionId); + }); + } + wasRoomKeyForwardedByInviter(event, roomKey) { + var _a, _b, _c; + // TODO: This is supposed to have a time limit. We should only accept + // such keys if we happen to receive them for a recently joined room. + const room = this.baseApis.getRoom(roomKey.roomId); + const senderKey = event.getSenderKey(); + if (!senderKey) { + return false; + } + const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey(olmlib.OLM_ALGORITHM, senderKey); + if (!senderKeyUser) { + return false; + } + const memberEvent = (_a = room === null || room === void 0 ? void 0 : room.getMember(this.userId)) === null || _a === void 0 ? void 0 : _a.events.member; + const fromInviter = (memberEvent === null || memberEvent === void 0 ? void 0 : memberEvent.getSender()) === senderKeyUser || + (((_b = memberEvent === null || memberEvent === void 0 ? void 0 : memberEvent.getUnsigned()) === null || _b === void 0 ? void 0 : _b.prev_sender) === senderKeyUser && + ((_c = memberEvent === null || memberEvent === void 0 ? void 0 : memberEvent.getPrevContent()) === null || _c === void 0 ? void 0 : _c.membership) === "invite"); + if (room && fromInviter) { + return true; + } + else { + return false; + } + } + wasRoomKeyForwardedAsHistory(roomKey) { + const room = this.baseApis.getRoom(roomKey.roomId); + // If the key is not for a known room, then something fishy is going on, + // so we reject the key out of caution. In practice, this is a bit moot + // because we'll only accept shared_history forwarded by the inviter, and + // we won't know who was the inviter for an unknown room, so we'll reject + // it anyway. + if (room && roomKey.extraSessionData.sharedHistory) { + return true; + } + else { + return false; + } + } + /** + * Check if a forwarded room key should be parked. + * + * A forwarded room key should be parked if it's a key for a room we're not + * in. We park the forwarded room key in case *this sender* invites us to + * that room later. + */ + shouldParkForwardedKey(roomKey) { + const room = this.baseApis.getRoom(roomKey.roomId); + if (!room && roomKey.extraSessionData.sharedHistory) { + return true; + } + else { + return false; + } + } + /** + * Park the given room key to our store. + * + * @param event - An `m.forwarded_room_key` event. + * @param roomKey - The room key that was found in the event. + * + * @internal + * + */ + parkForwardedKey(event, roomKey) { + return __awaiter(this, void 0, void 0, function* () { + const parkedData = { + senderId: event.getSender(), + senderKey: roomKey.senderKey, + sessionId: roomKey.sessionId, + sessionKey: roomKey.sessionKey, + keysClaimed: roomKey.keysClaimed, + forwardingCurve25519KeyChain: roomKey.forwardingKeyChain, + }; + yield this.crypto.cryptoStore.doTxn("readwrite", ["parked_shared_history"], (txn) => this.crypto.cryptoStore.addParkedSharedHistory(roomKey.roomId, parkedData, txn), logger_1.logger.withPrefix("[addParkedSharedHistory]")); + }); + } + /** + * Add the given room key to our store. + * + * @param roomKey - The room key that should be added to the store. + * + * @internal + * + */ + addRoomKey(roomKey) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield this.olmDevice.addInboundGroupSession(roomKey.roomId, roomKey.senderKey, roomKey.forwardingKeyChain, roomKey.sessionId, roomKey.sessionKey, roomKey.keysClaimed, roomKey.exportFormat, roomKey.extraSessionData); + // have another go at decrypting events sent with this session. + if (yield this.retryDecryption(roomKey.senderKey, roomKey.sessionId, !roomKey.extraSessionData.untrusted)) { + // cancel any outstanding room key requests for this session. + // Only do this if we managed to decrypt every message in the + // session, because if we didn't, we leave the other key + // requests in the hopes that someone sends us a key that + // includes an earlier index. + this.crypto.cancelRoomKeyRequest({ + algorithm: roomKey.algorithm, + room_id: roomKey.roomId, + session_id: roomKey.sessionId, + sender_key: roomKey.senderKey, + }); + } + // don't wait for the keys to be backed up for the server + yield this.crypto.backupManager.backupGroupSession(roomKey.senderKey, roomKey.sessionId); + } + catch (e) { + this.prefixedLogger.error(`Error handling m.room_key_event: ${e}`); + } + }); + } + /** + * Handle room keys that have been forwarded to us as an + * `m.forwarded_room_key` event. + * + * Forwarded room keys need special handling since we have no way of knowing + * who the original creator of the room key was. This naturally means that + * forwarded room keys are always untrusted and should only be accepted in + * some cases. + * + * @param event - An `m.forwarded_room_key` event. + * + * @internal + * + */ + onForwardedRoomKey(event) { + return __awaiter(this, void 0, void 0, function* () { + const roomKey = this.forwardedRoomKeyFromEvent(event); + if (!roomKey) { + return; + } + if (yield this.shouldAcceptForwardedKey(event, roomKey)) { + yield this.addRoomKey(roomKey); + } + else if (this.shouldParkForwardedKey(roomKey)) { + yield this.parkForwardedKey(event, roomKey); + } + }); + } + onRoomKeyEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (event.getType() == "m.forwarded_room_key") { + yield this.onForwardedRoomKey(event); + } + else { + const roomKey = this.roomKeyFromEvent(event); + if (!roomKey) { + return; + } + yield this.addRoomKey(roomKey); + } + }); + } + /** + * @param event - key event + */ + onRoomKeyWithheldEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + const senderKey = content.sender_key; + if (content.code === "m.no_olm") { + yield this.onNoOlmWithheldEvent(event); + } + else if (content.code === "m.unavailable") { + // this simply means that the other device didn't have the key, which isn't very useful information. Don't + // record it in the storage + } + else { + yield this.olmDevice.addInboundGroupSessionWithheld(content.room_id, senderKey, content.session_id, content.code, content.reason); + } + // Having recorded the problem, retry decryption on any affected messages. + // It's unlikely we'll be able to decrypt sucessfully now, but this will + // update the error message. + // + if (content.session_id) { + yield this.retryDecryption(senderKey, content.session_id); + } + else { + // no_olm messages aren't specific to a given megolm session, so + // we trigger retrying decryption for all the messages from the sender's + // key, so that we can update the error message to indicate the olm + // session problem. + yield this.retryDecryptionFromSender(senderKey); + } + }); + } + onNoOlmWithheldEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + const senderKey = content.sender_key; + const sender = event.getSender(); + this.prefixedLogger.warn(`${sender}:${senderKey} was unable to establish an olm session with us`); + // if the sender says that they haven't been able to establish an olm + // session, let's proactively establish one + if (yield this.olmDevice.getSessionIdForDevice(senderKey)) { + // a session has already been established, so we don't need to + // create a new one. + this.prefixedLogger.debug("New session already created. Not creating a new one."); + yield this.olmDevice.recordSessionProblem(senderKey, "no_olm", true); + return; + } + let device = this.crypto.deviceList.getDeviceByIdentityKey(content.algorithm, senderKey); + if (!device) { + // if we don't know about the device, fetch the user's devices again + // and retry before giving up + yield this.crypto.downloadKeys([sender], false); + device = this.crypto.deviceList.getDeviceByIdentityKey(content.algorithm, senderKey); + if (!device) { + this.prefixedLogger.info("Couldn't find device for identity key " + senderKey + ": not establishing session"); + yield this.olmDevice.recordSessionProblem(senderKey, "no_olm", false); + return; + } + } + // XXX: switch this to use encryptAndSendToDevices() rather than duplicating it? + yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, new Map([[sender, [device]]]), false); + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + yield olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, undefined, this.olmDevice, sender, device, { type: "m.dummy" }); + yield this.olmDevice.recordSessionProblem(senderKey, "no_olm", true); + yield this.baseApis.sendToDevice("m.room.encrypted", new Map([[sender, new Map([[device.deviceId, encryptedContent]])]])); + }); + } + hasKeysForKeyRequest(keyRequest) { + const body = keyRequest.requestBody; + return this.olmDevice.hasInboundSessionKeys(body.room_id, body.sender_key, body.session_id); + } + shareKeysWithDevice(keyRequest) { + const userId = keyRequest.userId; + const deviceId = keyRequest.deviceId; + const deviceInfo = this.crypto.getStoredDevice(userId, deviceId); + const body = keyRequest.requestBody; + // XXX: switch this to use encryptAndSendToDevices()? + this.olmlib + .ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, new Map([[userId, [deviceInfo]]])) + .then((devicemap) => { + var _a; + const olmSessionResult = (_a = devicemap.get(userId)) === null || _a === void 0 ? void 0 : _a.get(deviceId); + if (!(olmSessionResult === null || olmSessionResult === void 0 ? void 0 : olmSessionResult.sessionId)) { + // no session with this device, probably because there + // were no one-time keys. + // + // ensureOlmSessionsForUsers has already done the logging, + // so just skip it. + return null; + } + this.prefixedLogger.log("sharing keys for session " + + body.sender_key + + "|" + + body.session_id + + " with device " + + userId + + ":" + + deviceId); + return this.buildKeyForwardingMessage(body.room_id, body.sender_key, body.session_id); + }) + .then((payload) => { + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + return this.olmlib + .encryptMessageForDevice(encryptedContent.ciphertext, this.userId, undefined, this.olmDevice, userId, deviceInfo, payload) + .then(() => { + // TODO: retries + return this.baseApis.sendToDevice("m.room.encrypted", new Map([[userId, new Map([[deviceId, encryptedContent]])]])); + }); + }); + } + buildKeyForwardingMessage(roomId, senderKey, sessionId) { + return __awaiter(this, void 0, void 0, function* () { + const key = yield this.olmDevice.getInboundGroupSessionKey(roomId, senderKey, sessionId); + return { + type: "m.forwarded_room_key", + content: { + "algorithm": olmlib.MEGOLM_ALGORITHM, + "room_id": roomId, + "sender_key": senderKey, + "sender_claimed_ed25519_key": key.sender_claimed_ed25519_key, + "session_id": sessionId, + "session_key": key.key, + "chain_index": key.chain_index, + "forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain, + "org.matrix.msc3061.shared_history": key.shared_history || false, + }, + }; + }); + } + /** + * @param untrusted - whether the key should be considered as untrusted + * @param source - where the key came from + */ + importRoomKey(session, { untrusted, source } = {}) { + const extraSessionData = {}; + if (untrusted || session.untrusted) { + extraSessionData.untrusted = true; + } + if (session["org.matrix.msc3061.shared_history"]) { + extraSessionData.sharedHistory = true; + } + return this.olmDevice + .addInboundGroupSession(session.room_id, session.sender_key, session.forwarding_curve25519_key_chain, session.session_id, session.session_key, session.sender_claimed_keys, true, extraSessionData) + .then(() => { + if (source !== "backup") { + // don't wait for it to complete + this.crypto.backupManager.backupGroupSession(session.sender_key, session.session_id).catch((e) => { + // This throws if the upload failed, but this is fine + // since it will have written it to the db and will retry. + this.prefixedLogger.log("Failed to back up megolm session", e); + }); + } + // have another go at decrypting events sent with this session. + this.retryDecryption(session.sender_key, session.session_id, !extraSessionData.untrusted); + }); + } + /** + * Have another go at decrypting events after we receive a key. Resolves once + * decryption has been re-attempted on all events. + * + * @internal + * @param forceRedecryptIfUntrusted - whether messages that were already + * successfully decrypted using untrusted keys should be re-decrypted + * + * @returns whether all messages were successfully + * decrypted with trusted keys + */ + retryDecryption(senderKey, sessionId, forceRedecryptIfUntrusted) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const senderPendingEvents = this.pendingEvents.get(senderKey); + if (!senderPendingEvents) { + return true; + } + const pending = senderPendingEvents.get(sessionId); + if (!pending) { + return true; + } + const pendingList = [...pending]; + this.prefixedLogger.debug("Retrying decryption on events:", pendingList.map((e) => `${e.getId()}`)); + yield Promise.all(pendingList.map((ev) => __awaiter(this, void 0, void 0, function* () { + try { + yield ev.attemptDecryption(this.crypto, { isRetry: true, forceRedecryptIfUntrusted }); + } + catch (e) { + // don't die if something goes wrong + } + }))); + // If decrypted successfully with trusted keys, they'll have + // been removed from pendingEvents + return !((_a = this.pendingEvents.get(senderKey)) === null || _a === void 0 ? void 0 : _a.has(sessionId)); + }); + } + retryDecryptionFromSender(senderKey) { + return __awaiter(this, void 0, void 0, function* () { + const senderPendingEvents = this.pendingEvents.get(senderKey); + if (!senderPendingEvents) { + return true; + } + this.pendingEvents.delete(senderKey); + yield Promise.all([...senderPendingEvents].map(([_sessionId, pending]) => __awaiter(this, void 0, void 0, function* () { + yield Promise.all([...pending].map((ev) => __awaiter(this, void 0, void 0, function* () { + try { + yield ev.attemptDecryption(this.crypto); + } + catch (e) { + // don't die if something goes wrong + } + }))); + }))); + return !this.pendingEvents.has(senderKey); + }); + } + sendSharedHistoryInboundSessions(devicesByUser) { + return __awaiter(this, void 0, void 0, function* () { + yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser); + const sharedHistorySessions = yield this.olmDevice.getSharedHistoryInboundGroupSessions(this.roomId); + this.prefixedLogger.log(`Sharing history in with users ${Array.from(devicesByUser.keys())}`, sharedHistorySessions.map(([senderKey, sessionId]) => `${senderKey}|${sessionId}`)); + for (const [senderKey, sessionId] of sharedHistorySessions) { + const payload = yield this.buildKeyForwardingMessage(this.roomId, senderKey, sessionId); + // FIXME: use encryptAndSendToDevices() rather than duplicating it here. + const promises = []; + const contentMap = new Map(); + for (const [userId, devices] of devicesByUser) { + const deviceMessages = new Map(); + contentMap.set(userId, deviceMessages); + for (const deviceInfo of devices) { + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + deviceMessages.set(deviceInfo.deviceId, encryptedContent); + promises.push(olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, undefined, this.olmDevice, userId, deviceInfo, payload)); + } + } + yield Promise.all(promises); + // prune out any devices that encryptMessageForDevice could not encrypt for, + // in which case it will have just not added anything to the ciphertext object. + // There's no point sending messages to devices if we couldn't encrypt to them, + // since that's effectively a blank message. + for (const [userId, deviceMessages] of contentMap) { + for (const [deviceId, content] of deviceMessages) { + if (!hasCiphertext(content)) { + this.prefixedLogger.log("No ciphertext for device " + userId + ":" + deviceId + ": pruning"); + deviceMessages.delete(deviceId); + } + } + // No devices left for that user? Strip that too. + if (deviceMessages.size === 0) { + this.prefixedLogger.log("Pruned all devices for user " + userId); + contentMap.delete(userId); + } + } + // Is there anything left? + if (contentMap.size === 0) { + this.prefixedLogger.log("No users left to send to: aborting"); + return; + } + yield this.baseApis.sendToDevice("m.room.encrypted", contentMap); + } + }); + } +} +exports.MegolmDecryption = MegolmDecryption; +const PROBLEM_DESCRIPTIONS = { + no_olm: "The sender was unable to establish a secure channel.", + unknown: "The secure channel with the sender was corrupted.", +}; +(0, base_1.registerAlgorithm)(olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption); + +},{"../../@types/event":306,"../../logger":374,"../../utils":416,"../OlmDevice":327,"../OutgoingRoomKeyRequestManager":328,"../olmlib":343,"./base":332,"uuid":287}],335:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const logger_1 = require("../../logger"); +const olmlib = __importStar(require("../olmlib")); +const deviceinfo_1 = require("../deviceinfo"); +const base_1 = require("./base"); +const DeviceVerification = deviceinfo_1.DeviceInfo.DeviceVerification; +/** + * Olm encryption implementation + * + * @param params - parameters, as per {@link EncryptionAlgorithm} + */ +class OlmEncryption extends base_1.EncryptionAlgorithm { + constructor() { + super(...arguments); + this.sessionPrepared = false; + this.prepPromise = null; + } + /** + * @internal + + * @param roomMembers - list of currently-joined users in the room + * @returns Promise which resolves when setup is complete + */ + ensureSession(roomMembers) { + if (this.prepPromise) { + // prep already in progress + return this.prepPromise; + } + if (this.sessionPrepared) { + // prep already done + return Promise.resolve(); + } + this.prepPromise = this.crypto + .downloadKeys(roomMembers) + .then(() => { + return this.crypto.ensureOlmSessionsForUsers(roomMembers); + }) + .then(() => { + this.sessionPrepared = true; + }) + .finally(() => { + this.prepPromise = null; + }); + return this.prepPromise; + } + /** + * @param content - plaintext event content + * + * @returns Promise which resolves to the new event body + */ + encryptMessage(room, eventType, content) { + return __awaiter(this, void 0, void 0, function* () { + // pick the list of recipients based on the membership list. + // + // TODO: there is a race condition here! What if a new user turns up + // just as you are sending a secret message? + const members = yield room.getEncryptionTargetMembers(); + const users = members.map(function (u) { + return u.userId; + }); + yield this.ensureSession(users); + const payloadFields = { + room_id: room.roomId, + type: eventType, + content: content, + }; + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + }; + const promises = []; + for (const userId of users) { + const devices = this.crypto.getStoredDevicesForUser(userId) || []; + for (const deviceInfo of devices) { + const key = deviceInfo.getIdentityKey(); + if (key == this.olmDevice.deviceCurve25519Key) { + // don't bother sending to ourself + continue; + } + if (deviceInfo.verified == DeviceVerification.BLOCKED) { + // don't bother setting up sessions with blocked users + continue; + } + promises.push(olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, userId, deviceInfo, payloadFields)); + } + } + return Promise.all(promises).then(() => encryptedContent); + }); + } +} +/** + * Olm decryption implementation + * + * @param params - parameters, as per {@link DecryptionAlgorithm} + */ +class OlmDecryption extends base_1.DecryptionAlgorithm { + /** + * returns a promise which resolves to a + * {@link EventDecryptionResult} once we have finished + * decrypting. Rejects with an `algorithms.DecryptionError` if there is a + * problem decrypting the event. + */ + decryptEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getWireContent(); + const deviceKey = content.sender_key; + const ciphertext = content.ciphertext; + if (!ciphertext) { + throw new base_1.DecryptionError("OLM_MISSING_CIPHERTEXT", "Missing ciphertext"); + } + if (!(this.olmDevice.deviceCurve25519Key in ciphertext)) { + throw new base_1.DecryptionError("OLM_NOT_INCLUDED_IN_RECIPIENTS", "Not included in recipients"); + } + const message = ciphertext[this.olmDevice.deviceCurve25519Key]; + let payloadString; + try { + payloadString = yield this.decryptMessage(deviceKey, message); + } + catch (e) { + throw new base_1.DecryptionError("OLM_BAD_ENCRYPTED_MESSAGE", "Bad Encrypted Message", { + sender: deviceKey, + err: e, + }); + } + const payload = JSON.parse(payloadString); + // check that we were the intended recipient, to avoid unknown-key attack + // https://github.com/vector-im/vector-web/issues/2483 + if (payload.recipient != this.userId) { + throw new base_1.DecryptionError("OLM_BAD_RECIPIENT", "Message was intented for " + payload.recipient); + } + if (payload.recipient_keys.ed25519 != this.olmDevice.deviceEd25519Key) { + throw new base_1.DecryptionError("OLM_BAD_RECIPIENT_KEY", "Message not intended for this device", { + intended: payload.recipient_keys.ed25519, + our_key: this.olmDevice.deviceEd25519Key, + }); + } + // check that the device that encrypted the event belongs to the user + // that the event claims it's from. We need to make sure that our + // device list is up-to-date. If the device is unknown, we can only + // assume that the device logged out. Some event handlers, such as + // secret sharing, may be more strict and reject events that come from + // unknown devices. + yield this.crypto.deviceList.downloadKeys([event.getSender()], false); + const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey(olmlib.OLM_ALGORITHM, deviceKey); + if (senderKeyUser !== event.getSender() && senderKeyUser != undefined) { + throw new base_1.DecryptionError("OLM_BAD_SENDER", "Message claimed to be from " + event.getSender(), { + real_sender: senderKeyUser, + }); + } + // check that the original sender matches what the homeserver told us, to + // avoid people masquerading as others. + // (this check is also provided via the sender's embedded ed25519 key, + // which is checked elsewhere). + if (payload.sender != event.getSender()) { + throw new base_1.DecryptionError("OLM_FORWARDED_MESSAGE", "Message forwarded from " + payload.sender, { + reported_sender: event.getSender(), + }); + } + // Olm events intended for a room have a room_id. + if (payload.room_id !== event.getRoomId()) { + throw new base_1.DecryptionError("OLM_BAD_ROOM", "Message intended for room " + payload.room_id, { + reported_room: event.getRoomId() || "ROOM_ID_UNDEFINED", + }); + } + const claimedKeys = payload.keys || {}; + return { + clearEvent: payload, + senderCurve25519Key: deviceKey, + claimedEd25519Key: claimedKeys.ed25519 || null, + }; + }); + } + /** + * Attempt to decrypt an Olm message + * + * @param theirDeviceIdentityKey - Curve25519 identity key of the sender + * @param message - message object, with 'type' and 'body' fields + * + * @returns payload, if decrypted successfully. + */ + decryptMessage(theirDeviceIdentityKey, message) { + // This is a wrapper that serialises decryptions of prekey messages, because + // otherwise we race between deciding we have no active sessions for the message + // and creating a new one, which we can only do once because it removes the OTK. + if (message.type !== 0) { + // not a prekey message: we can safely just try & decrypt it + return this.reallyDecryptMessage(theirDeviceIdentityKey, message); + } + else { + const myPromise = this.olmDevice.olmPrekeyPromise.then(() => { + return this.reallyDecryptMessage(theirDeviceIdentityKey, message); + }); + // we want the error, but don't propagate it to the next decryption + this.olmDevice.olmPrekeyPromise = myPromise.catch(() => { }); + return myPromise; + } + } + reallyDecryptMessage(theirDeviceIdentityKey, message) { + return __awaiter(this, void 0, void 0, function* () { + const sessionIds = yield this.olmDevice.getSessionIdsForDevice(theirDeviceIdentityKey); + // try each session in turn. + const decryptionErrors = {}; + for (const sessionId of sessionIds) { + try { + const payload = yield this.olmDevice.decryptMessage(theirDeviceIdentityKey, sessionId, message.type, message.body); + logger_1.logger.log("Decrypted Olm message from " + theirDeviceIdentityKey + " with session " + sessionId); + return payload; + } + catch (e) { + const foundSession = yield this.olmDevice.matchesSession(theirDeviceIdentityKey, sessionId, message.type, message.body); + if (foundSession) { + // decryption failed, but it was a prekey message matching this + // session, so it should have worked. + throw new Error("Error decrypting prekey message with existing session id " + + sessionId + + ": " + + e.message); + } + // otherwise it's probably a message for another session; carry on, but + // keep a record of the error + decryptionErrors[sessionId] = e.message; + } + } + if (message.type !== 0) { + // not a prekey message, so it should have matched an existing session, but it + // didn't work. + if (sessionIds.length === 0) { + throw new Error("No existing sessions"); + } + throw new Error("Error decrypting non-prekey message with existing sessions: " + JSON.stringify(decryptionErrors)); + } + // prekey message which doesn't match any existing sessions: make a new + // session. + let res; + try { + res = yield this.olmDevice.createInboundSession(theirDeviceIdentityKey, message.type, message.body); + } + catch (e) { + decryptionErrors["(new)"] = e.message; + throw new Error("Error decrypting prekey message: " + JSON.stringify(decryptionErrors)); + } + logger_1.logger.log("created new inbound Olm session ID " + res.session_id + " with " + theirDeviceIdentityKey); + return res.payload; + }); + } +} +(0, base_1.registerAlgorithm)(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption); + +},{"../../logger":374,"../deviceinfo":340,"../olmlib":343,"./base":332}],336:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CrossSigningKey = void 0; +// TODO: Merge this with crypto.js once converted +var CrossSigningKey; +(function (CrossSigningKey) { + CrossSigningKey["Master"] = "master"; + CrossSigningKey["SelfSigning"] = "self_signing"; + CrossSigningKey["UserSigning"] = "user_signing"; +})(CrossSigningKey = exports.CrossSigningKey || (exports.CrossSigningKey = {})); + +},{}],337:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DefaultAlgorithm = exports.algorithmsByName = exports.Aes256 = exports.Curve25519 = exports.BackupManager = void 0; +const client_1 = require("../client"); +const logger_1 = require("../logger"); +const olmlib_1 = require("./olmlib"); +const key_passphrase_1 = require("./key_passphrase"); +const utils_1 = require("../utils"); +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +const recoverykey_1 = require("./recoverykey"); +const aes_1 = require("./aes"); +const NamespacedValue_1 = require("../NamespacedValue"); +const index_1 = require("./index"); +const crypto_1 = require("./crypto"); +const http_api_1 = require("../http-api"); +const KEY_BACKUP_KEYS_PER_REQUEST = 200; +const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms +/** + * Manages the key backup. + */ +class BackupManager { + constructor(baseApis, getKey) { + this.baseApis = baseApis; + this.getKey = getKey; + this.sessionLastCheckAttemptedTime = {}; // When did we last try to check the server for a given session id? + this.checkedForBackup = false; + this.sendingBackups = false; + } + get version() { + return this.backupInfo && this.backupInfo.version; + } + /** + * Performs a quick check to ensure that the backup info looks sane. + * + * Throws an error if a problem is detected. + * + * @param info - the key backup info + */ + static checkBackupVersion(info) { + const Algorithm = exports.algorithmsByName[info.algorithm]; + if (!Algorithm) { + throw new Error("Unknown backup algorithm: " + info.algorithm); + } + if (typeof info.auth_data !== "object") { + throw new Error("Invalid backup data returned"); + } + return Algorithm.checkBackupVersion(info); + } + static makeAlgorithm(info, getKey) { + const Algorithm = exports.algorithmsByName[info.algorithm]; + if (!Algorithm) { + throw new Error("Unknown backup algorithm"); + } + return Algorithm.init(info.auth_data, getKey); + } + enableKeyBackup(info) { + return __awaiter(this, void 0, void 0, function* () { + this.backupInfo = info; + if (this.algorithm) { + this.algorithm.free(); + } + this.algorithm = yield BackupManager.makeAlgorithm(info, this.getKey); + this.baseApis.emit(index_1.CryptoEvent.KeyBackupStatus, true); + // There may be keys left over from a partially completed backup, so + // schedule a send to check. + this.scheduleKeyBackupSend(); + }); + } + /** + * Disable backing up of keys. + */ + disableKeyBackup() { + if (this.algorithm) { + this.algorithm.free(); + } + this.algorithm = undefined; + this.backupInfo = undefined; + this.baseApis.emit(index_1.CryptoEvent.KeyBackupStatus, false); + } + getKeyBackupEnabled() { + if (!this.checkedForBackup) { + return null; + } + return Boolean(this.algorithm); + } + prepareKeyBackupVersion(key, algorithm) { + return __awaiter(this, void 0, void 0, function* () { + const Algorithm = algorithm ? exports.algorithmsByName[algorithm] : exports.DefaultAlgorithm; + if (!Algorithm) { + throw new Error("Unknown backup algorithm"); + } + const [privateKey, authData] = yield Algorithm.prepare(key); + const recoveryKey = (0, recoverykey_1.encodeRecoveryKey)(privateKey); + return { + algorithm: Algorithm.algorithmName, + auth_data: authData, + recovery_key: recoveryKey, + privateKey, + }; + }); + } + createKeyBackupVersion(info) { + return __awaiter(this, void 0, void 0, function* () { + this.algorithm = yield BackupManager.makeAlgorithm(info, this.getKey); + }); + } + /** + * Check the server for an active key backup and + * if one is present and has a valid signature from + * one of the user's verified devices, start backing up + * to it. + */ + checkAndStart() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log("Checking key backup status..."); + if (this.baseApis.isGuest()) { + logger_1.logger.log("Skipping key backup check since user is guest"); + this.checkedForBackup = true; + return null; + } + let backupInfo; + try { + backupInfo = (_a = (yield this.baseApis.getKeyBackupVersion())) !== null && _a !== void 0 ? _a : undefined; + } + catch (e) { + logger_1.logger.log("Error checking for active key backup", e); + if (e.httpStatus === 404) { + // 404 is returned when the key backup does not exist, so that + // counts as successfully checking. + this.checkedForBackup = true; + } + return null; + } + this.checkedForBackup = true; + const trustInfo = yield this.isKeyBackupTrusted(backupInfo); + if (trustInfo.usable && !this.backupInfo) { + logger_1.logger.log(`Found usable key backup v${backupInfo.version}: enabling key backups`); + yield this.enableKeyBackup(backupInfo); + } + else if (!trustInfo.usable && this.backupInfo) { + logger_1.logger.log("No usable key backup: disabling key backup"); + this.disableKeyBackup(); + } + else if (!trustInfo.usable && !this.backupInfo) { + logger_1.logger.log("No usable key backup: not enabling key backup"); + } + else if (trustInfo.usable && this.backupInfo) { + // may not be the same version: if not, we should switch + if (backupInfo.version !== this.backupInfo.version) { + logger_1.logger.log(`On backup version ${this.backupInfo.version} but ` + + `found version ${backupInfo.version}: switching.`); + this.disableKeyBackup(); + yield this.enableKeyBackup(backupInfo); + // We're now using a new backup, so schedule all the keys we have to be + // uploaded to the new backup. This is a bit of a workaround to upload + // keys to a new backup in *most* cases, but it won't cover all cases + // because we don't remember what backup version we uploaded keys to: + // see https://github.com/vector-im/element-web/issues/14833 + yield this.scheduleAllGroupSessionsForBackup(); + } + else { + logger_1.logger.log(`Backup version ${backupInfo.version} still current`); + } + } + return { backupInfo, trustInfo }; + }); + } + /** + * Forces a re-check of the key backup and enables/disables it + * as appropriate. + * + * @returns Object with backup info (as returned by + * getKeyBackupVersion) in backupInfo and + * trust information (as returned by isKeyBackupTrusted) + * in trustInfo. + */ + checkKeyBackup() { + return __awaiter(this, void 0, void 0, function* () { + this.checkedForBackup = false; + return this.checkAndStart(); + }); + } + /** + * Attempts to retrieve a session from a key backup, if enough time + * has elapsed since the last check for this session id. + */ + queryKeyBackupRateLimited(targetRoomId, targetSessionId) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.backupInfo) { + return; + } + const now = new Date().getTime(); + if (!this.sessionLastCheckAttemptedTime[targetSessionId] || + now - this.sessionLastCheckAttemptedTime[targetSessionId] > KEY_BACKUP_CHECK_RATE_LIMIT) { + this.sessionLastCheckAttemptedTime[targetSessionId] = now; + yield this.baseApis.restoreKeyBackupWithCache(targetRoomId, targetSessionId, this.backupInfo, {}); + } + }); + } + /** + * Check if the given backup info is trusted. + * + * @param backupInfo - key backup info dict from /room_keys/version + */ + isKeyBackupTrusted(backupInfo) { + return __awaiter(this, void 0, void 0, function* () { + const ret = { + usable: false, + trusted_locally: false, + sigs: [], + }; + if (!backupInfo || !backupInfo.algorithm || !backupInfo.auth_data || !backupInfo.auth_data.signatures) { + logger_1.logger.info("Key backup is absent or missing required data"); + return ret; + } + const userId = this.baseApis.getUserId(); + const privKey = yield this.baseApis.crypto.getSessionBackupPrivateKey(); + if (privKey) { + let algorithm = null; + try { + algorithm = yield BackupManager.makeAlgorithm(backupInfo, () => __awaiter(this, void 0, void 0, function* () { return privKey; })); + if (yield algorithm.keyMatches(privKey)) { + logger_1.logger.info("Backup is trusted locally"); + ret.trusted_locally = true; + } + } + catch (_a) { + // do nothing -- if we have an error, then we don't mark it as + // locally trusted + } + finally { + algorithm === null || algorithm === void 0 ? void 0 : algorithm.free(); + } + } + const mySigs = backupInfo.auth_data.signatures[userId] || {}; + for (const keyId of Object.keys(mySigs)) { + const keyIdParts = keyId.split(":"); + if (keyIdParts[0] !== "ed25519") { + logger_1.logger.log("Ignoring unknown signature type: " + keyIdParts[0]); + continue; + } + // Could be a cross-signing master key, but just say this is the device + // ID for backwards compat + const sigInfo = { deviceId: keyIdParts[1] }; + // first check to see if it's from our cross-signing key + const crossSigningId = this.baseApis.crypto.crossSigningInfo.getId(); + if (crossSigningId === sigInfo.deviceId) { + sigInfo.crossSigningId = true; + try { + yield (0, olmlib_1.verifySignature)(this.baseApis.crypto.olmDevice, backupInfo.auth_data, userId, sigInfo.deviceId, crossSigningId); + sigInfo.valid = true; + } + catch (e) { + logger_1.logger.warn("Bad signature from cross signing key " + crossSigningId, e); + sigInfo.valid = false; + } + ret.sigs.push(sigInfo); + continue; + } + // Now look for a sig from a device + // At some point this can probably go away and we'll just support + // it being signed by the cross-signing master key + const device = this.baseApis.crypto.deviceList.getStoredDevice(userId, sigInfo.deviceId); + if (device) { + sigInfo.device = device; + sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(userId, sigInfo.deviceId); + try { + yield (0, olmlib_1.verifySignature)(this.baseApis.crypto.olmDevice, backupInfo.auth_data, userId, device.deviceId, device.getFingerprint()); + sigInfo.valid = true; + } + catch (e) { + logger_1.logger.info("Bad signature from key ID " + + keyId + + " userID " + + this.baseApis.getUserId() + + " device ID " + + device.deviceId + + " fingerprint: " + + device.getFingerprint(), backupInfo.auth_data, e); + sigInfo.valid = false; + } + } + else { + sigInfo.valid = null; // Can't determine validity because we don't have the signing device + logger_1.logger.info("Ignoring signature from unknown key " + keyId); + } + ret.sigs.push(sigInfo); + } + ret.usable = ret.sigs.some((s) => { + var _a; + return s.valid && ((s.device && ((_a = s.deviceTrust) === null || _a === void 0 ? void 0 : _a.isVerified())) || s.crossSigningId); + }); + return ret; + }); + } + /** + * Schedules sending all keys waiting to be sent to the backup, if not already + * scheduled. Retries if necessary. + * + * @param maxDelay - Maximum delay to wait in ms. 0 means no delay. + */ + scheduleKeyBackupSend(maxDelay = 10000) { + return __awaiter(this, void 0, void 0, function* () { + if (this.sendingBackups) + return; + this.sendingBackups = true; + try { + // wait between 0 and `maxDelay` seconds, to avoid backup + // requests from different clients hitting the server all at + // the same time when a new key is sent + const delay = Math.random() * maxDelay; + yield (0, utils_1.sleep)(delay); + let numFailures = 0; // number of consecutive failures + for (;;) { + if (!this.algorithm) { + return; + } + try { + const numBackedUp = yield this.backupPendingKeys(KEY_BACKUP_KEYS_PER_REQUEST); + if (numBackedUp === 0) { + // no sessions left needing backup: we're done + return; + } + numFailures = 0; + } + catch (err) { + numFailures++; + logger_1.logger.log("Key backup request failed", err); + if (err.data) { + if (err.data.errcode == "M_NOT_FOUND" || + err.data.errcode == "M_WRONG_ROOM_KEYS_VERSION") { + // Re-check key backup status on error, so we can be + // sure to present the current situation when asked. + yield this.checkKeyBackup(); + // Backup version has changed or this backup version + // has been deleted + this.baseApis.crypto.emit(index_1.CryptoEvent.KeyBackupFailed, err.data.errcode); + throw err; + } + } + } + if (numFailures) { + // exponential backoff if we have failures + yield (0, utils_1.sleep)(1000 * Math.pow(2, Math.min(numFailures - 1, 4))); + } + } + } + finally { + this.sendingBackups = false; + } + }); + } + /** + * Take some e2e keys waiting to be backed up and send them + * to the backup. + * + * @param limit - Maximum number of keys to back up + * @returns Number of sessions backed up + */ + backupPendingKeys(limit) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const sessions = yield this.baseApis.crypto.cryptoStore.getSessionsNeedingBackup(limit); + if (!sessions.length) { + return 0; + } + let remaining = yield this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); + this.baseApis.crypto.emit(index_1.CryptoEvent.KeyBackupSessionsRemaining, remaining); + const rooms = {}; + for (const session of sessions) { + const roomId = session.sessionData.room_id; + (0, utils_1.safeSet)(rooms, roomId, rooms[roomId] || { sessions: {} }); + const sessionData = this.baseApis.crypto.olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); + sessionData.algorithm = olmlib_1.MEGOLM_ALGORITHM; + const forwardedCount = (sessionData.forwarding_curve25519_key_chain || []).length; + const userId = this.baseApis.crypto.deviceList.getUserByIdentityKey(olmlib_1.MEGOLM_ALGORITHM, session.senderKey); + const device = (_a = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(olmlib_1.MEGOLM_ALGORITHM, session.senderKey)) !== null && _a !== void 0 ? _a : undefined; + const verified = this.baseApis.crypto.checkDeviceInfoTrust(userId, device).isVerified(); + (0, utils_1.safeSet)(rooms[roomId]["sessions"], session.sessionId, { + first_message_index: sessionData.first_known_index, + forwarded_count: forwardedCount, + is_verified: verified, + session_data: yield this.algorithm.encryptSession(sessionData), + }); + } + yield this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, { rooms }); + yield this.baseApis.crypto.cryptoStore.unmarkSessionsNeedingBackup(sessions); + remaining = yield this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); + this.baseApis.crypto.emit(index_1.CryptoEvent.KeyBackupSessionsRemaining, remaining); + return sessions.length; + }); + } + backupGroupSession(senderKey, sessionId) { + return __awaiter(this, void 0, void 0, function* () { + yield this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([ + { + senderKey: senderKey, + sessionId: sessionId, + }, + ]); + if (this.backupInfo) { + // don't wait for this to complete: it will delay so + // happens in the background + this.scheduleKeyBackupSend(); + } + // if this.backupInfo is not set, then the keys will be backed up when + // this.enableKeyBackup is called + }); + } + /** + * Marks all group sessions as needing to be backed up and schedules them to + * upload in the background as soon as possible. + */ + scheduleAllGroupSessionsForBackup() { + return __awaiter(this, void 0, void 0, function* () { + yield this.flagAllGroupSessionsForBackup(); + // Schedule keys to upload in the background as soon as possible. + this.scheduleKeyBackupSend(0 /* maxDelay */); + }); + } + /** + * Marks all group sessions as needing to be backed up without scheduling + * them to upload in the background. + * @returns Promise which resolves to the number of sessions now requiring a backup + * (which will be equal to the number of sessions in the store). + */ + flagAllGroupSessionsForBackup() { + return __awaiter(this, void 0, void 0, function* () { + yield this.baseApis.crypto.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_BACKUP], (txn) => { + this.baseApis.crypto.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { + if (session !== null) { + this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([session], txn); + } + }); + }); + const remaining = yield this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); + this.baseApis.emit(index_1.CryptoEvent.KeyBackupSessionsRemaining, remaining); + return remaining; + }); + } + /** + * Counts the number of end to end session keys that are waiting to be backed up + * @returns Promise which resolves to the number of sessions requiring backup + */ + countSessionsNeedingBackup() { + return this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); + } +} +exports.BackupManager = BackupManager; +class Curve25519 { + constructor(authData, publicKey, // FIXME: PkEncryption + getKey) { + this.authData = authData; + this.publicKey = publicKey; + this.getKey = getKey; + } + static init(authData, getKey) { + return __awaiter(this, void 0, void 0, function* () { + if (!authData || !("public_key" in authData)) { + throw new Error("auth_data missing required information"); + } + const publicKey = new global.Olm.PkEncryption(); + publicKey.set_recipient_key(authData.public_key); + return new Curve25519(authData, publicKey, getKey); + }); + } + static prepare(key) { + return __awaiter(this, void 0, void 0, function* () { + const decryption = new global.Olm.PkDecryption(); + try { + const authData = {}; + if (!key) { + authData.public_key = decryption.generate_key(); + } + else if (key instanceof Uint8Array) { + authData.public_key = decryption.init_with_private_key(key); + } + else { + const derivation = yield (0, key_passphrase_1.keyFromPassphrase)(key); + authData.private_key_salt = derivation.salt; + authData.private_key_iterations = derivation.iterations; + authData.public_key = decryption.init_with_private_key(derivation.key); + } + const publicKey = new global.Olm.PkEncryption(); + publicKey.set_recipient_key(authData.public_key); + return [decryption.get_private_key(), authData]; + } + finally { + decryption.free(); + } + }); + } + static checkBackupVersion(info) { + if (!("public_key" in info.auth_data)) { + throw new Error("Invalid backup data returned"); + } + } + get untrusted() { + return true; + } + encryptSession(data) { + return __awaiter(this, void 0, void 0, function* () { + const plainText = Object.assign({}, data); + delete plainText.session_id; + delete plainText.room_id; + delete plainText.first_known_index; + return this.publicKey.encrypt(JSON.stringify(plainText)); + }); + } + decryptSessions(sessions) { + return __awaiter(this, void 0, void 0, function* () { + const privKey = yield this.getKey(); + const decryption = new global.Olm.PkDecryption(); + try { + const backupPubKey = decryption.init_with_private_key(privKey); + if (backupPubKey !== this.authData.public_key) { + throw new http_api_1.MatrixError({ errcode: client_1.MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY }); + } + const keys = []; + for (const [sessionId, sessionData] of Object.entries(sessions)) { + try { + const decrypted = JSON.parse(decryption.decrypt(sessionData.session_data.ephemeral, sessionData.session_data.mac, sessionData.session_data.ciphertext)); + decrypted.session_id = sessionId; + keys.push(decrypted); + } + catch (e) { + logger_1.logger.log("Failed to decrypt megolm session from backup", e, sessionData); + } + } + return keys; + } + finally { + decryption.free(); + } + }); + } + keyMatches(key) { + return __awaiter(this, void 0, void 0, function* () { + const decryption = new global.Olm.PkDecryption(); + let pubKey; + try { + pubKey = decryption.init_with_private_key(key); + } + finally { + decryption.free(); + } + return pubKey === this.authData.public_key; + }); + } + free() { + this.publicKey.free(); + } +} +exports.Curve25519 = Curve25519; +Curve25519.algorithmName = "m.megolm_backup.v1.curve25519-aes-sha2"; +function randomBytes(size) { + const buf = new Uint8Array(size); + crypto_1.crypto.getRandomValues(buf); + return buf; +} +const UNSTABLE_MSC3270_NAME = new NamespacedValue_1.UnstableValue("m.megolm_backup.v1.aes-hmac-sha2", "org.matrix.msc3270.v1.aes-hmac-sha2"); +class Aes256 { + constructor(authData, key) { + this.authData = authData; + this.key = key; + } + static init(authData, getKey) { + return __awaiter(this, void 0, void 0, function* () { + if (!authData) { + throw new Error("auth_data missing"); + } + const key = yield getKey(); + if (authData.mac) { + const { mac } = yield (0, aes_1.calculateKeyCheck)(key, authData.iv); + if (authData.mac.replace(/=+$/g, "") !== mac.replace(/=+/g, "")) { + throw new Error("Key does not match"); + } + } + return new Aes256(authData, key); + }); + } + static prepare(key) { + return __awaiter(this, void 0, void 0, function* () { + let outKey; + const authData = {}; + if (!key) { + outKey = randomBytes(32); + } + else if (key instanceof Uint8Array) { + outKey = new Uint8Array(key); + } + else { + const derivation = yield (0, key_passphrase_1.keyFromPassphrase)(key); + authData.private_key_salt = derivation.salt; + authData.private_key_iterations = derivation.iterations; + outKey = derivation.key; + } + const { iv, mac } = yield (0, aes_1.calculateKeyCheck)(outKey); + authData.iv = iv; + authData.mac = mac; + return [outKey, authData]; + }); + } + static checkBackupVersion(info) { + if (!("iv" in info.auth_data && "mac" in info.auth_data)) { + throw new Error("Invalid backup data returned"); + } + } + get untrusted() { + return false; + } + encryptSession(data) { + const plainText = Object.assign({}, data); + delete plainText.session_id; + delete plainText.room_id; + delete plainText.first_known_index; + return (0, aes_1.encryptAES)(JSON.stringify(plainText), this.key, data.session_id); + } + decryptSessions(sessions) { + return __awaiter(this, void 0, void 0, function* () { + const keys = []; + for (const [sessionId, sessionData] of Object.entries(sessions)) { + try { + const decrypted = JSON.parse(yield (0, aes_1.decryptAES)(sessionData.session_data, this.key, sessionId)); + decrypted.session_id = sessionId; + keys.push(decrypted); + } + catch (e) { + logger_1.logger.log("Failed to decrypt megolm session from backup", e, sessionData); + } + } + return keys; + }); + } + keyMatches(key) { + return __awaiter(this, void 0, void 0, function* () { + if (this.authData.mac) { + const { mac } = yield (0, aes_1.calculateKeyCheck)(key, this.authData.iv); + return this.authData.mac.replace(/=+$/g, "") === mac.replace(/=+/g, ""); + } + else { + // if we have no information, we have to assume the key is right + return true; + } + }); + } + free() { + this.key.fill(0); + } +} +exports.Aes256 = Aes256; +Aes256.algorithmName = UNSTABLE_MSC3270_NAME.name; +exports.algorithmsByName = { + [Curve25519.algorithmName]: Curve25519, + [Aes256.algorithmName]: Aes256, +}; +exports.DefaultAlgorithm = Curve25519; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../NamespacedValue":316,"../client":321,"../http-api":367,"../logger":374,"../utils":416,"./aes":331,"./crypto":338,"./index":341,"./key_passphrase":342,"./olmlib":343,"./recoverykey":344,"./store/indexeddb-crypto-store":346}],338:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var _a, _b, _c, _d, _e, _f, _g; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.setTextEncoder = exports.setCrypto = exports.TextEncoder = exports.subtleCrypto = exports.crypto = void 0; +const logger_1 = require("../logger"); +exports.crypto = (_a = global.window) === null || _a === void 0 ? void 0 : _a.crypto; +exports.subtleCrypto = (_d = (_c = (_b = global.window) === null || _b === void 0 ? void 0 : _b.crypto) === null || _c === void 0 ? void 0 : _c.subtle) !== null && _d !== void 0 ? _d : (_f = (_e = global.window) === null || _e === void 0 ? void 0 : _e.crypto) === null || _f === void 0 ? void 0 : _f.webkitSubtle; +exports.TextEncoder = (_g = global.window) === null || _g === void 0 ? void 0 : _g.TextEncoder; +/* eslint-disable @typescript-eslint/no-var-requires */ +if (!exports.crypto) { + try { + exports.crypto = require("crypto").webcrypto; + } + catch (e) { + logger_1.logger.error("Failed to load webcrypto", e); + } +} +if (!exports.subtleCrypto) { + exports.subtleCrypto = exports.crypto === null || exports.crypto === void 0 ? void 0 : exports.crypto.subtle; +} +if (!exports.TextEncoder) { + try { + exports.TextEncoder = require("util").TextEncoder; + } + catch (e) { + logger_1.logger.error("Failed to load TextEncoder util", e); + } +} +/* eslint-enable @typescript-eslint/no-var-requires */ +function setCrypto(_crypto) { + var _a; + exports.crypto = _crypto; + exports.subtleCrypto = (_a = _crypto.subtle) !== null && _a !== void 0 ? _a : _crypto.webkitSubtle; +} +exports.setCrypto = setCrypto; +function setTextEncoder(_TextEncoder) { + exports.TextEncoder = _TextEncoder; +} +exports.setTextEncoder = setTextEncoder; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../logger":374,"crypto":78,"util":286}],339:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +Copyright 2020-2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DehydrationManager = exports.DEHYDRATION_ALGORITHM = void 0; +const another_json_1 = __importDefault(require("another-json")); +const olmlib_1 = require("./olmlib"); +const indexeddb_crypto_store_1 = require("../crypto/store/indexeddb-crypto-store"); +const aes_1 = require("./aes"); +const logger_1 = require("../logger"); +const http_api_1 = require("../http-api"); +exports.DEHYDRATION_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle"; +const oneweek = 7 * 24 * 60 * 60 * 1000; +class DehydrationManager { + constructor(crypto) { + this.crypto = crypto; + this.inProgress = false; + this.getDehydrationKeyFromCache(); + } + getDehydrationKeyFromCache() { + return this.crypto.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.crypto.cryptoStore.getSecretStorePrivateKey(txn, (result) => __awaiter(this, void 0, void 0, function* () { + if (result) { + const { key, keyInfo, deviceDisplayName, time } = result; + const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey); + const decrypted = yield (0, aes_1.decryptAES)(key, pickleKey, exports.DEHYDRATION_ALGORITHM); + this.key = (0, olmlib_1.decodeBase64)(decrypted); + this.keyInfo = keyInfo; + this.deviceDisplayName = deviceDisplayName; + const now = Date.now(); + const delay = Math.max(1, time + oneweek - now); + this.timeoutId = global.setTimeout(this.dehydrateDevice.bind(this), delay); + } + }), "dehydration"); + }); + } + /** set the key, and queue periodic dehydration to the server in the background */ + setKeyAndQueueDehydration(key, keyInfo = {}, deviceDisplayName) { + return __awaiter(this, void 0, void 0, function* () { + const matches = yield this.setKey(key, keyInfo, deviceDisplayName); + if (!matches) { + // start dehydration in the background + this.dehydrateDevice(); + } + }); + } + setKey(key, keyInfo = {}, deviceDisplayName) { + return __awaiter(this, void 0, void 0, function* () { + if (!key) { + // unsetting the key -- cancel any pending dehydration task + if (this.timeoutId) { + global.clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } + // clear storage + yield this.crypto.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.crypto.cryptoStore.storeSecretStorePrivateKey(txn, "dehydration", null); + }); + this.key = undefined; + this.keyInfo = undefined; + return; + } + // Check to see if it's the same key as before. If it's different, + // dehydrate a new device. If it's the same, we can keep the same + // device. (Assume that keyInfo and deviceDisplayName will be the + // same if the key is the same.) + let matches = !!this.key && key.length == this.key.length; + for (let i = 0; matches && i < key.length; i++) { + if (key[i] != this.key[i]) { + matches = false; + } + } + if (!matches) { + this.key = key; + this.keyInfo = keyInfo; + this.deviceDisplayName = deviceDisplayName; + } + return matches; + }); + } + /** returns the device id of the newly created dehydrated device */ + dehydrateDevice() { + return __awaiter(this, void 0, void 0, function* () { + if (this.inProgress) { + logger_1.logger.log("Dehydration already in progress -- not starting new dehydration"); + return; + } + this.inProgress = true; + if (this.timeoutId) { + global.clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } + try { + const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey); + // update the crypto store with the timestamp + const key = yield (0, aes_1.encryptAES)((0, olmlib_1.encodeBase64)(this.key), pickleKey, exports.DEHYDRATION_ALGORITHM); + yield this.crypto.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.crypto.cryptoStore.storeSecretStorePrivateKey(txn, "dehydration", { + keyInfo: this.keyInfo, + key, + deviceDisplayName: this.deviceDisplayName, + time: Date.now(), + }); + }); + logger_1.logger.log("Attempting to dehydrate device"); + logger_1.logger.log("Creating account"); + // create the account and all the necessary keys + const account = new global.Olm.Account(); + account.create(); + const e2eKeys = JSON.parse(account.identity_keys()); + const maxKeys = account.max_number_of_one_time_keys(); + // FIXME: generate in small batches? + account.generate_one_time_keys(maxKeys / 2); + account.generate_fallback_key(); + const otks = JSON.parse(account.one_time_keys()); + const fallbacks = JSON.parse(account.fallback_key()); + account.mark_keys_as_published(); + // dehydrate the account and store it on the server + const pickledAccount = account.pickle(new Uint8Array(this.key)); + const deviceData = { + algorithm: exports.DEHYDRATION_ALGORITHM, + account: pickledAccount, + }; + if (this.keyInfo.passphrase) { + deviceData.passphrase = this.keyInfo.passphrase; + } + logger_1.logger.log("Uploading account to server"); + // eslint-disable-next-line camelcase + const dehydrateResult = yield this.crypto.baseApis.http.authedRequest(http_api_1.Method.Put, "/dehydrated_device", undefined, { + device_data: deviceData, + initial_device_display_name: this.deviceDisplayName, + }, { + prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2", + }); + // send the keys to the server + const deviceId = dehydrateResult.device_id; + logger_1.logger.log("Preparing device keys", deviceId); + const deviceKeys = { + algorithms: this.crypto.supportedAlgorithms, + device_id: deviceId, + user_id: this.crypto.userId, + keys: { + [`ed25519:${deviceId}`]: e2eKeys.ed25519, + [`curve25519:${deviceId}`]: e2eKeys.curve25519, + }, + }; + const deviceSignature = account.sign(another_json_1.default.stringify(deviceKeys)); + deviceKeys.signatures = { + [this.crypto.userId]: { + [`ed25519:${deviceId}`]: deviceSignature, + }, + }; + if (this.crypto.crossSigningInfo.getId("self_signing")) { + yield this.crypto.crossSigningInfo.signObject(deviceKeys, "self_signing"); + } + logger_1.logger.log("Preparing one-time keys"); + const oneTimeKeys = {}; + for (const [keyId, key] of Object.entries(otks.curve25519)) { + const k = { key }; + const signature = account.sign(another_json_1.default.stringify(k)); + k.signatures = { + [this.crypto.userId]: { + [`ed25519:${deviceId}`]: signature, + }, + }; + oneTimeKeys[`signed_curve25519:${keyId}`] = k; + } + logger_1.logger.log("Preparing fallback keys"); + const fallbackKeys = {}; + for (const [keyId, key] of Object.entries(fallbacks.curve25519)) { + const k = { key, fallback: true }; + const signature = account.sign(another_json_1.default.stringify(k)); + k.signatures = { + [this.crypto.userId]: { + [`ed25519:${deviceId}`]: signature, + }, + }; + fallbackKeys[`signed_curve25519:${keyId}`] = k; + } + logger_1.logger.log("Uploading keys to server"); + yield this.crypto.baseApis.http.authedRequest(http_api_1.Method.Post, "/keys/upload/" + encodeURI(deviceId), undefined, { + "device_keys": deviceKeys, + "one_time_keys": oneTimeKeys, + "org.matrix.msc2732.fallback_keys": fallbackKeys, + }); + logger_1.logger.log("Done dehydrating"); + // dehydrate again in a week + this.timeoutId = global.setTimeout(this.dehydrateDevice.bind(this), oneweek); + return deviceId; + } + finally { + this.inProgress = false; + } + }); + } + stop() { + if (this.timeoutId) { + global.clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } + } +} +exports.DehydrationManager = DehydrationManager; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"../crypto/store/indexeddb-crypto-store":346,"../http-api":367,"../logger":374,"./aes":331,"./olmlib":343,"another-json":1,"buffer":68}],340:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DeviceInfo = void 0; +var DeviceVerification; +(function (DeviceVerification) { + DeviceVerification[DeviceVerification["Blocked"] = -1] = "Blocked"; + DeviceVerification[DeviceVerification["Unverified"] = 0] = "Unverified"; + DeviceVerification[DeviceVerification["Verified"] = 1] = "Verified"; +})(DeviceVerification || (DeviceVerification = {})); +/** + * Information about a user's device + */ +class DeviceInfo { + /** + * rehydrate a DeviceInfo from the session store + * + * @param obj - raw object from session store + * @param deviceId - id of the device + * + * @returns new DeviceInfo + */ + static fromStorage(obj, deviceId) { + const res = new DeviceInfo(deviceId); + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) { + // @ts-ignore - this is messy and typescript doesn't like it + res[prop] = obj[prop]; + } + } + return res; + } + /** + * @param deviceId - id of the device + */ + constructor(deviceId) { + this.deviceId = deviceId; + /** list of algorithms supported by this device */ + this.algorithms = []; + /** a map from `: -> ` */ + this.keys = {}; + /** whether the device has been verified/blocked by the user */ + this.verified = DeviceVerification.Unverified; + /** + * whether the user knows of this device's existence + * (useful when warning the user that a user has added new devices) + */ + this.known = false; + /** additional data from the homeserver */ + this.unsigned = {}; + this.signatures = {}; + } + /** + * Prepare a DeviceInfo for JSON serialisation in the session store + * + * @returns deviceinfo with non-serialised members removed + */ + toStorage() { + return { + algorithms: this.algorithms, + keys: this.keys, + verified: this.verified, + known: this.known, + unsigned: this.unsigned, + signatures: this.signatures, + }; + } + /** + * Get the fingerprint for this device (ie, the Ed25519 key) + * + * @returns base64-encoded fingerprint of this device + */ + getFingerprint() { + return this.keys["ed25519:" + this.deviceId]; + } + /** + * Get the identity key for this device (ie, the Curve25519 key) + * + * @returns base64-encoded identity key of this device + */ + getIdentityKey() { + return this.keys["curve25519:" + this.deviceId]; + } + /** + * Get the configured display name for this device, if any + * + * @returns displayname + */ + getDisplayName() { + return this.unsigned.device_display_name || null; + } + /** + * Returns true if this device is blocked + * + * @returns true if blocked + */ + isBlocked() { + return this.verified == DeviceVerification.Blocked; + } + /** + * Returns true if this device is verified + * + * @returns true if verified + */ + isVerified() { + return this.verified == DeviceVerification.Verified; + } + /** + * Returns true if this device is unverified + * + * @returns true if unverified + */ + isUnverified() { + return this.verified == DeviceVerification.Unverified; + } + /** + * Returns true if the user knows about this device's existence + * + * @returns true if known + */ + isKnown() { + return this.known === true; + } +} +exports.DeviceInfo = DeviceInfo; +DeviceInfo.DeviceVerification = { + VERIFIED: DeviceVerification.Verified, + UNVERIFIED: DeviceVerification.Unverified, + BLOCKED: DeviceVerification.Blocked, +}; + +},{}],341:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +Copyright 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2018-2019 New Vector Ltd +Copyright 2019-2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IncomingRoomKeyRequest = exports.fixBackupKey = exports.Crypto = exports.CryptoEvent = exports.isCryptoAvailable = exports.verificationMethods = void 0; +const another_json_1 = __importDefault(require("another-json")); +const uuid_1 = require("uuid"); +const event_1 = require("../@types/event"); +const ReEmitter_1 = require("../ReEmitter"); +const logger_1 = require("../logger"); +const OlmDevice_1 = require("./OlmDevice"); +const olmlib = __importStar(require("./olmlib")); +const DeviceList_1 = require("./DeviceList"); +const deviceinfo_1 = require("./deviceinfo"); +const algorithms = __importStar(require("./algorithms")); +const CrossSigning_1 = require("./CrossSigning"); +const EncryptionSetup_1 = require("./EncryptionSetup"); +const SecretStorage_1 = require("./SecretStorage"); +const OutgoingRoomKeyRequestManager_1 = require("./OutgoingRoomKeyRequestManager"); +const indexeddb_crypto_store_1 = require("./store/indexeddb-crypto-store"); +const QRCode_1 = require("./verification/QRCode"); +const SAS_1 = require("./verification/SAS"); +const key_passphrase_1 = require("./key_passphrase"); +const recoverykey_1 = require("./recoverykey"); +const VerificationRequest_1 = require("./verification/request/VerificationRequest"); +const InRoomChannel_1 = require("./verification/request/InRoomChannel"); +const ToDeviceChannel_1 = require("./verification/request/ToDeviceChannel"); +const IllegalMethod_1 = require("./verification/IllegalMethod"); +const errors_1 = require("../errors"); +const aes_1 = require("./aes"); +const dehydration_1 = require("./dehydration"); +const backup_1 = require("./backup"); +const room_1 = require("../models/room"); +const room_member_1 = require("../models/room-member"); +const event_2 = require("../models/event"); +const client_1 = require("../client"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const room_state_1 = require("../models/room-state"); +const utils_1 = require("../utils"); +const DeviceVerification = deviceinfo_1.DeviceInfo.DeviceVerification; +const defaultVerificationMethods = { + [QRCode_1.ReciprocateQRCode.NAME]: QRCode_1.ReciprocateQRCode, + [SAS_1.SAS.NAME]: SAS_1.SAS, + // These two can't be used for actual verification, but we do + // need to be able to define them here for the verification flows + // to start. + [QRCode_1.SHOW_QR_CODE_METHOD]: IllegalMethod_1.IllegalMethod, + [QRCode_1.SCAN_QR_CODE_METHOD]: IllegalMethod_1.IllegalMethod, +}; +/** + * verification method names + */ +// legacy export identifier +exports.verificationMethods = { + RECIPROCATE_QR_CODE: QRCode_1.ReciprocateQRCode.NAME, + SAS: SAS_1.SAS.NAME, +}; +function isCryptoAvailable() { + return Boolean(global.Olm); +} +exports.isCryptoAvailable = isCryptoAvailable; +const MIN_FORCE_SESSION_INTERVAL_MS = 60 * 60 * 1000; +var CryptoEvent; +(function (CryptoEvent) { + CryptoEvent["DeviceVerificationChanged"] = "deviceVerificationChanged"; + CryptoEvent["UserTrustStatusChanged"] = "userTrustStatusChanged"; + CryptoEvent["UserCrossSigningUpdated"] = "userCrossSigningUpdated"; + CryptoEvent["RoomKeyRequest"] = "crypto.roomKeyRequest"; + CryptoEvent["RoomKeyRequestCancellation"] = "crypto.roomKeyRequestCancellation"; + CryptoEvent["KeyBackupStatus"] = "crypto.keyBackupStatus"; + CryptoEvent["KeyBackupFailed"] = "crypto.keyBackupFailed"; + CryptoEvent["KeyBackupSessionsRemaining"] = "crypto.keyBackupSessionsRemaining"; + CryptoEvent["KeySignatureUploadFailure"] = "crypto.keySignatureUploadFailure"; + CryptoEvent["VerificationRequest"] = "crypto.verification.request"; + CryptoEvent["Warning"] = "crypto.warning"; + CryptoEvent["WillUpdateDevices"] = "crypto.willUpdateDevices"; + CryptoEvent["DevicesUpdated"] = "crypto.devicesUpdated"; + CryptoEvent["KeysChanged"] = "crossSigning.keysChanged"; +})(CryptoEvent = exports.CryptoEvent || (exports.CryptoEvent = {})); +class Crypto extends typed_event_emitter_1.TypedEventEmitter { + /** + * @returns The version of Olm. + */ + static getOlmVersion() { + return OlmDevice_1.OlmDevice.getOlmVersion(); + } + /** + * Cryptography bits + * + * This module is internal to the js-sdk; the public API is via MatrixClient. + * + * @internal + * + * @param baseApis - base matrix api interface + * + * @param userId - The user ID for the local user + * + * @param deviceId - The identifier for this device. + * + * @param clientStore - the MatrixClient data store. + * + * @param cryptoStore - storage for the crypto layer. + * + * @param roomList - An initialised RoomList object + * + * @param verificationMethods - Array of verification methods to use. + * Each element can either be a string from MatrixClient.verificationMethods + * or a class that implements a verification method. + */ + constructor(baseApis, userId, deviceId, clientStore, cryptoStore, roomList, verificationMethods) { + super(); + this.baseApis = baseApis; + this.userId = userId; + this.deviceId = deviceId; + this.clientStore = clientStore; + this.cryptoStore = cryptoStore; + this.roomList = roomList; + this.trustCrossSignedDevices = true; + // the last time we did a check for the number of one-time-keys on the server. + this.lastOneTimeKeyCheck = null; + this.oneTimeKeyCheckInProgress = false; + // EncryptionAlgorithm instance for each room + this.roomEncryptors = new Map(); + // map from algorithm to DecryptionAlgorithm instance, for each room + this.roomDecryptors = new Map(); + this.deviceKeys = {}; // type: key + this.globalBlacklistUnverifiedDevices = false; + this.globalErrorOnUnknownDevices = true; + // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations + // we received in the current sync. + this.receivedRoomKeyRequests = []; + this.receivedRoomKeyRequestCancellations = []; + // true if we are currently processing received room key requests + this.processingRoomKeyRequests = false; + // controls whether device tracking is delayed + // until calling encryptEvent or trackRoomDevices, + // or done immediately upon enabling room encryption. + this.lazyLoadMembers = false; + // in case lazyLoadMembers is true, + // track if an initial tracking of all the room members + // has happened for a given room. This is delayed + // to avoid loading room members as long as possible. + this.roomDeviceTrackingState = {}; + // The timestamp of the last time we forced establishment + // of a new session for each device, in milliseconds. + // { + // userId: { + // deviceId: 1234567890000, + // }, + // } + // Map: user Id → device Id → timestamp + this.lastNewSessionForced = new utils_1.MapWithDefault(() => new utils_1.MapWithDefault(() => 0)); + // This flag will be unset whilst the client processes a sync response + // so that we don't start requesting keys until we've actually finished + // processing the response. + this.sendKeyRequestsImmediately = false; + /* + * Event handler for DeviceList's userNewDevices event + */ + this.onDeviceListUserCrossSigningUpdated = (userId) => __awaiter(this, void 0, void 0, function* () { + if (userId === this.userId) { + // An update to our own cross-signing key. + // Get the new key first: + const newCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); + const seenPubkey = newCrossSigning ? newCrossSigning.getId() : null; + const currentPubkey = this.crossSigningInfo.getId(); + const changed = currentPubkey !== seenPubkey; + if (currentPubkey && seenPubkey && !changed) { + // If it's not changed, just make sure everything is up to date + yield this.checkOwnCrossSigningTrust(); + } + else { + // We'll now be in a state where cross-signing on the account is not trusted + // because our locally stored cross-signing keys will not match the ones + // on the server for our account. So we clear our own stored cross-signing keys, + // effectively disabling cross-signing until the user gets verified by the device + // that reset the keys + this.storeTrustedSelfKeys(null); + // emit cross-signing has been disabled + this.emit(CryptoEvent.KeysChanged, {}); + // as the trust for our own user has changed, + // also emit an event for this + this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); + } + } + else { + yield this.checkDeviceVerifications(userId); + // Update verified before latch using the current state and save the new + // latch value in the device list store. + const crossSigning = this.deviceList.getStoredCrossSigningForUser(userId); + if (crossSigning) { + crossSigning.updateCrossSigningVerifiedBefore(this.checkUserTrust(userId).isCrossSigningVerified()); + this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); + } + this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); + } + }); + this.onMembership = (event, member, oldMembership) => { + try { + this.onRoomMembership(event, member, oldMembership); + } + catch (e) { + logger_1.logger.error("Error handling membership change:", e); + } + }; + this.onToDeviceEvent = (event) => { + try { + logger_1.logger.log(`received to-device ${event.getType()} from: ` + + `${event.getSender()} id: ${event.getContent()[event_1.ToDeviceMessageId]}`); + if (event.getType() == "m.room_key" || event.getType() == "m.forwarded_room_key") { + this.onRoomKeyEvent(event); + } + else if (event.getType() == "m.room_key_request") { + this.onRoomKeyRequestEvent(event); + } + else if (event.getType() === "m.secret.request") { + this.secretStorage.onRequestReceived(event); + } + else if (event.getType() === "m.secret.send") { + this.secretStorage.onSecretReceived(event); + } + else if (event.getType() === "m.room_key.withheld") { + this.onRoomKeyWithheldEvent(event); + } + else if (event.getContent().transaction_id) { + this.onKeyVerificationMessage(event); + } + else if (event.getContent().msgtype === "m.bad.encrypted") { + this.onToDeviceBadEncrypted(event); + } + else if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { + if (!event.isBeingDecrypted()) { + event.attemptDecryption(this); + } + // once the event has been decrypted, try again + event.once(event_2.MatrixEventEvent.Decrypted, (ev) => { + this.onToDeviceEvent(ev); + }); + } + } + catch (e) { + logger_1.logger.error("Error handling toDeviceEvent:", e); + } + }; + /** + * Handle key verification requests sent as timeline events + * + * @internal + * @param event - the timeline event + * @param room - not used + * @param atStart - not used + * @param removed - not used + * @param whether - this is a live event + */ + this.onTimelineEvent = (event, room, atStart, removed, { liveEvent = true } = {}) => { + if (!InRoomChannel_1.InRoomChannel.validateEvent(event, this.baseApis)) { + return; + } + const createRequest = (event) => { + const channel = new InRoomChannel_1.InRoomChannel(this.baseApis, event.getRoomId()); + return new VerificationRequest_1.VerificationRequest(channel, this.verificationMethods, this.baseApis); + }; + this.handleVerificationEvent(event, this.inRoomVerificationRequests, createRequest, liveEvent); + }; + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + if (verificationMethods) { + this.verificationMethods = new Map(); + for (const method of verificationMethods) { + if (typeof method === "string") { + if (defaultVerificationMethods[method]) { + this.verificationMethods.set(method, defaultVerificationMethods[method]); + } + } + else if (method["NAME"]) { + this.verificationMethods.set(method["NAME"], method); + } + else { + logger_1.logger.warn(`Excluding unknown verification method ${method}`); + } + } + } + else { + this.verificationMethods = new Map(Object.entries(defaultVerificationMethods)); + } + this.backupManager = new backup_1.BackupManager(baseApis, () => __awaiter(this, void 0, void 0, function* () { + // try to get key from cache + const cachedKey = yield this.getSessionBackupPrivateKey(); + if (cachedKey) { + return cachedKey; + } + // try to get key from secret storage + const storedKey = yield this.getSecret("m.megolm_backup.v1"); + if (storedKey) { + // ensure that the key is in the right format. If not, fix the key and + // store the fixed version + const fixedKey = fixBackupKey(storedKey); + if (fixedKey) { + const keys = yield this.getSecretStorageKey(); + yield this.storeSecret("m.megolm_backup.v1", fixedKey, [keys[0]]); + } + return olmlib.decodeBase64(fixedKey || storedKey); + } + // try to get key from app + if (this.baseApis.cryptoCallbacks && this.baseApis.cryptoCallbacks.getBackupKey) { + return this.baseApis.cryptoCallbacks.getBackupKey(); + } + throw new Error("Unable to get private key"); + })); + this.olmDevice = new OlmDevice_1.OlmDevice(cryptoStore); + this.deviceList = new DeviceList_1.DeviceList(baseApis, cryptoStore, this.olmDevice); + // XXX: This isn't removed at any point, but then none of the event listeners + // this class sets seem to be removed at any point... :/ + this.deviceList.on(CryptoEvent.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); + this.reEmitter.reEmit(this.deviceList, [CryptoEvent.DevicesUpdated, CryptoEvent.WillUpdateDevices]); + this.supportedAlgorithms = Array.from(algorithms.DECRYPTION_CLASSES.keys()); + this.outgoingRoomKeyRequestManager = new OutgoingRoomKeyRequestManager_1.OutgoingRoomKeyRequestManager(baseApis, this.deviceId, this.cryptoStore); + this.toDeviceVerificationRequests = new ToDeviceChannel_1.ToDeviceRequests(); + this.inRoomVerificationRequests = new InRoomChannel_1.InRoomRequests(); + const cryptoCallbacks = this.baseApis.cryptoCallbacks || {}; + const cacheCallbacks = (0, CrossSigning_1.createCryptoStoreCacheCallbacks)(cryptoStore, this.olmDevice); + this.crossSigningInfo = new CrossSigning_1.CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks); + // Yes, we pass the client twice here: see SecretStorage + this.secretStorage = new SecretStorage_1.SecretStorage(baseApis, cryptoCallbacks, baseApis); + this.dehydrationManager = new dehydration_1.DehydrationManager(this); + // Assuming no app-supplied callback, default to getting from SSSS. + if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) { + cryptoCallbacks.getCrossSigningKey = (type) => __awaiter(this, void 0, void 0, function* () { + return CrossSigning_1.CrossSigningInfo.getFromSecretStorage(type, this.secretStorage); + }); + } + } + /** + * Initialise the crypto module so that it is ready for use + * + * Returns a promise which resolves once the crypto module is ready for use. + * + * @param exportedOlmDevice - (Optional) data from exported device + * that must be re-created. + */ + init({ exportedOlmDevice, pickleKey } = {}) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log("Crypto: initialising Olm..."); + yield global.Olm.init(); + logger_1.logger.log(exportedOlmDevice + ? "Crypto: initialising Olm device from exported device..." + : "Crypto: initialising Olm device..."); + yield this.olmDevice.init({ fromExportedDevice: exportedOlmDevice, pickleKey }); + logger_1.logger.log("Crypto: loading device list..."); + yield this.deviceList.load(); + // build our device keys: these will later be uploaded + this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key; + this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key; + logger_1.logger.log("Crypto: fetching own devices..."); + let myDevices = this.deviceList.getRawStoredDevicesForUser(this.userId); + if (!myDevices) { + myDevices = {}; + } + if (!myDevices[this.deviceId]) { + // add our own deviceinfo to the cryptoStore + logger_1.logger.log("Crypto: adding this device to the store..."); + const deviceInfo = { + keys: this.deviceKeys, + algorithms: this.supportedAlgorithms, + verified: DeviceVerification.VERIFIED, + known: true, + }; + myDevices[this.deviceId] = deviceInfo; + this.deviceList.storeDevicesForUser(this.userId, myDevices); + this.deviceList.saveIfDirty(); + } + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.cryptoStore.getCrossSigningKeys(txn, (keys) => { + // can be an empty object after resetting cross-signing keys, see storeTrustedSelfKeys + if (keys && Object.keys(keys).length !== 0) { + logger_1.logger.log("Loaded cross-signing public keys from crypto store"); + this.crossSigningInfo.setKeys(keys); + } + }); + }); + // make sure we are keeping track of our own devices + // (this is important for key backups & things) + this.deviceList.startTrackingDeviceList(this.userId); + logger_1.logger.log("Crypto: checking for key backup..."); + this.backupManager.checkAndStart(); + }); + } + /** + * Whether to trust a others users signatures of their devices. + * If false, devices will only be considered 'verified' if we have + * verified that device individually (effectively disabling cross-signing). + * + * Default: true + * + * @returns True if trusting cross-signed devices + */ + getCryptoTrustCrossSignedDevices() { + return this.trustCrossSignedDevices; + } + /** + * See getCryptoTrustCrossSignedDevices + + * This may be set before initCrypto() is called to ensure no races occur. + * + * @param val - True to trust cross-signed devices + */ + setCryptoTrustCrossSignedDevices(val) { + this.trustCrossSignedDevices = val; + for (const userId of this.deviceList.getKnownUserIds()) { + const devices = this.deviceList.getRawStoredDevicesForUser(userId); + for (const deviceId of Object.keys(devices)) { + const deviceTrust = this.checkDeviceTrust(userId, deviceId); + // If the device is locally verified then isVerified() is always true, + // so this will only have caused the value to change if the device is + // cross-signing verified but not locally verified + if (!deviceTrust.isLocallyVerified() && deviceTrust.isCrossSigningVerified()) { + const deviceObj = this.deviceList.getStoredDevice(userId, deviceId); + this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); + } + } + } + } + /** + * Create a recovery key from a user-supplied passphrase. + * + * @param password - Passphrase string that can be entered by the user + * when restoring the backup as an alternative to entering the recovery key. + * Optional. + * @returns Object with public key metadata, encoded private + * recovery key which should be disposed of after displaying to the user, + * and raw private key to avoid round tripping if needed. + */ + createRecoveryKeyFromPassphrase(password) { + return __awaiter(this, void 0, void 0, function* () { + const decryption = new global.Olm.PkDecryption(); + try { + const keyInfo = {}; + if (password) { + const derivation = yield (0, key_passphrase_1.keyFromPassphrase)(password); + keyInfo.passphrase = { + algorithm: "m.pbkdf2", + iterations: derivation.iterations, + salt: derivation.salt, + }; + keyInfo.pubkey = decryption.init_with_private_key(derivation.key); + } + else { + keyInfo.pubkey = decryption.generate_key(); + } + const privateKey = decryption.get_private_key(); + const encodedPrivateKey = (0, recoverykey_1.encodeRecoveryKey)(privateKey); + return { + keyInfo: keyInfo, + encodedPrivateKey, + privateKey, + }; + } + finally { + decryption === null || decryption === void 0 ? void 0 : decryption.free(); + } + }); + } + /** + * Checks if the user has previously published cross-signing keys + * + * This means downloading the devicelist for the user and checking if the list includes + * the cross-signing pseudo-device. + * + * @internal + */ + userHasCrossSigningKeys() { + return __awaiter(this, void 0, void 0, function* () { + yield this.downloadKeys([this.userId]); + return this.deviceList.getStoredCrossSigningForUser(this.userId) !== null; + }); + } + /** + * Checks whether cross signing: + * - is enabled on this account and trusted by this device + * - has private keys either cached locally or stored in secret storage + * + * If this function returns false, bootstrapCrossSigning() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapCrossSigning() completes successfully, this function should + * return true. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @returns True if cross-signing is ready to be used on this device + */ + isCrossSigningReady() { + return __awaiter(this, void 0, void 0, function* () { + const publicKeysOnDevice = this.crossSigningInfo.getId(); + const privateKeysExistSomewhere = (yield this.crossSigningInfo.isStoredInKeyCache()) || + (yield this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage)); + return !!(publicKeysOnDevice && privateKeysExistSomewhere); + }); + } + /** + * Checks whether secret storage: + * - is enabled on this account + * - is storing cross-signing private keys + * - is storing session backup key (if enabled) + * + * If this function returns false, bootstrapSecretStorage() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapSecretStorage() completes successfully, this function should + * return true. + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @returns True if secret storage is ready to be used on this device + */ + isSecretStorageReady() { + return __awaiter(this, void 0, void 0, function* () { + const secretStorageKeyInAccount = yield this.secretStorage.hasKey(); + const privateKeysInStorage = yield this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage); + const sessionBackupInStorage = !this.backupManager.getKeyBackupEnabled() || (yield this.baseApis.isKeyBackupKeyStored()); + return !!(secretStorageKeyInAccount && privateKeysInStorage && sessionBackupInStorage); + }); + } + /** + * Bootstrap cross-signing by creating keys if needed. If everything is already + * set up, then no changes are made, so this is safe to run to ensure + * cross-signing is ready for use. + * + * This function: + * - creates new cross-signing keys if they are not found locally cached nor in + * secret storage (if it has been setup) + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * + * @param authUploadDeviceSigningKeys - Function + * called to await an interactive auth flow when uploading device signing keys. + * @param setupNewCrossSigning - Optional. Reset even if keys + * already exist. + * Args: + * A function that makes the request requiring auth. Receives the + * auth data as an object. Can be called multiple times, first with an empty + * authDict, to obtain the flows. + */ + bootstrapCrossSigning({ authUploadDeviceSigningKeys, setupNewCrossSigning, } = {}) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log("Bootstrapping cross-signing"); + const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks; + const builder = new EncryptionSetup_1.EncryptionSetupBuilder(this.baseApis.store.accountData, delegateCryptoCallbacks); + const crossSigningInfo = new CrossSigning_1.CrossSigningInfo(this.userId, builder.crossSigningCallbacks, builder.crossSigningCallbacks); + // Reset the cross-signing keys + const resetCrossSigning = () => __awaiter(this, void 0, void 0, function* () { + crossSigningInfo.resetKeys(); + // Sign master key with device key + yield this.signObject(crossSigningInfo.keys.master); + // Store auth flow helper function, as we need to call it when uploading + // to ensure we handle auth errors properly. + builder.addCrossSigningKeys(authUploadDeviceSigningKeys, crossSigningInfo.keys); + // Cross-sign own device + const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); + const deviceSignature = yield crossSigningInfo.signDevice(this.userId, device); + builder.addKeySignature(this.userId, this.deviceId, deviceSignature); + // Sign message key backup with cross-signing master key + if (this.backupManager.backupInfo) { + yield crossSigningInfo.signObject(this.backupManager.backupInfo.auth_data, "master"); + builder.addSessionBackup(this.backupManager.backupInfo); + } + }); + const publicKeysOnDevice = this.crossSigningInfo.getId(); + const privateKeysInCache = yield this.crossSigningInfo.isStoredInKeyCache(); + const privateKeysInStorage = yield this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage); + const privateKeysExistSomewhere = privateKeysInCache || privateKeysInStorage; + // Log all relevant state for easier parsing of debug logs. + logger_1.logger.log({ + setupNewCrossSigning, + publicKeysOnDevice, + privateKeysInCache, + privateKeysInStorage, + privateKeysExistSomewhere, + }); + if (!privateKeysExistSomewhere || setupNewCrossSigning) { + logger_1.logger.log("Cross-signing private keys not found locally or in secret storage, " + "creating new keys"); + // If a user has multiple devices, it important to only call bootstrap + // as part of some UI flow (and not silently during startup), as they + // may have setup cross-signing on a platform which has not saved keys + // to secret storage, and this would reset them. In such a case, you + // should prompt the user to verify any existing devices first (and + // request private keys from those devices) before calling bootstrap. + yield resetCrossSigning(); + } + else if (publicKeysOnDevice && privateKeysInCache) { + logger_1.logger.log("Cross-signing public keys trusted and private keys found locally"); + } + else if (privateKeysInStorage) { + logger_1.logger.log("Cross-signing private keys not found locally, but they are available " + + "in secret storage, reading storage and caching locally"); + yield this.checkOwnCrossSigningTrust({ + allowPrivateKeyRequests: true, + }); + } + // Assuming no app-supplied callback, default to storing new private keys in + // secret storage if it exists. If it does not, it is assumed this will be + // done as part of setting up secret storage later. + const crossSigningPrivateKeys = builder.crossSigningCallbacks.privateKeys; + if (crossSigningPrivateKeys.size && !this.baseApis.cryptoCallbacks.saveCrossSigningKeys) { + const secretStorage = new SecretStorage_1.SecretStorage(builder.accountDataClientAdapter, builder.ssssCryptoCallbacks, undefined); + if (yield secretStorage.hasKey()) { + logger_1.logger.log("Storing new cross-signing private keys in secret storage"); + // This is writing to in-memory account data in + // builder.accountDataClientAdapter so won't fail + yield CrossSigning_1.CrossSigningInfo.storeInSecretStorage(crossSigningPrivateKeys, secretStorage); + } + } + const operation = builder.buildOperation(); + yield operation.apply(this); + // This persists private keys and public keys as trusted, + // only do this if apply succeeded for now as retry isn't in place yet + yield builder.persist(this); + logger_1.logger.log("Cross-signing ready"); + }); + } + /** + * Bootstrap Secure Secret Storage if needed by creating a default key. If everything is + * already set up, then no changes are made, so this is safe to run to ensure secret + * storage is ready for use. + * + * This function + * - creates a new Secure Secret Storage key if no default key exists + * - if a key backup exists, it is migrated to store the key in the Secret + * Storage + * - creates a backup if none exists, and one is requested + * - migrates Secure Secret Storage to use the latest algorithm, if an outdated + * algorithm is found + * + * The Secure Secret Storage API is currently UNSTABLE and may change without notice. + * + * @param createSecretStorageKey - Optional. Function + * called to await a secret storage key creation flow. + * Returns a Promise which resolves to an object with public key metadata, encoded private + * recovery key which should be disposed of after displaying to the user, + * and raw private key to avoid round tripping if needed. + * @param keyBackupInfo - The current key backup object. If passed, + * the passphrase and recovery key from this backup will be used. + * @param setupNewKeyBackup - If true, a new key backup version will be + * created and the private key stored in the new SSSS store. Ignored if keyBackupInfo + * is supplied. + * @param setupNewSecretStorage - Optional. Reset even if keys already exist. + * @param getKeyBackupPassphrase - Optional. Function called to get the user's + * current key backup passphrase. Should return a promise that resolves with a Buffer + * containing the key, or rejects if the key cannot be obtained. + * Returns: + * A promise which resolves to key creation data for + * SecretStorage#addKey: an object with `passphrase` etc fields. + */ + // TODO this does not resolve with what it says it does + bootstrapSecretStorage({ createSecretStorageKey = () => __awaiter(this, void 0, void 0, function* () { return ({}); }), keyBackupInfo, setupNewKeyBackup, setupNewSecretStorage, getKeyBackupPassphrase, } = {}) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log("Bootstrapping Secure Secret Storage"); + const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks; + const builder = new EncryptionSetup_1.EncryptionSetupBuilder(this.baseApis.store.accountData, delegateCryptoCallbacks); + const secretStorage = new SecretStorage_1.SecretStorage(builder.accountDataClientAdapter, builder.ssssCryptoCallbacks, undefined); + // the ID of the new SSSS key, if we create one + let newKeyId = null; + // create a new SSSS key and set it as default + const createSSSS = (opts, privateKey) => __awaiter(this, void 0, void 0, function* () { + if (privateKey) { + opts.key = privateKey; + } + const { keyId, keyInfo } = yield secretStorage.addKey(SecretStorage_1.SECRET_STORAGE_ALGORITHM_V1_AES, opts); + if (privateKey) { + // make the private key available to encrypt 4S secrets + builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey); + } + yield secretStorage.setDefaultKeyId(keyId); + return keyId; + }); + const ensureCanCheckPassphrase = (keyId, keyInfo) => __awaiter(this, void 0, void 0, function* () { + var _a, _b; + if (!keyInfo.mac) { + const key = yield ((_b = (_a = this.baseApis.cryptoCallbacks).getSecretStorageKey) === null || _b === void 0 ? void 0 : _b.call(_a, { keys: { [keyId]: keyInfo } }, "")); + if (key) { + const privateKey = key[1]; + builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey); + const { iv, mac } = yield (0, aes_1.calculateKeyCheck)(privateKey); + keyInfo.iv = iv; + keyInfo.mac = mac; + yield builder.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo); + } + } + }); + const signKeyBackupWithCrossSigning = (keyBackupAuthData) => __awaiter(this, void 0, void 0, function* () { + if (this.crossSigningInfo.getId() && (yield this.crossSigningInfo.isStoredInKeyCache("master"))) { + try { + logger_1.logger.log("Adding cross-signing signature to key backup"); + yield this.crossSigningInfo.signObject(keyBackupAuthData, "master"); + } + catch (e) { + // This step is not critical (just helpful), so we catch here + // and continue if it fails. + logger_1.logger.error("Signing key backup with cross-signing keys failed", e); + } + } + else { + logger_1.logger.warn("Cross-signing keys not available, skipping signature on key backup"); + } + }); + const oldSSSSKey = yield this.getSecretStorageKey(); + const [oldKeyId, oldKeyInfo] = oldSSSSKey || [null, null]; + const storageExists = !setupNewSecretStorage && oldKeyInfo && oldKeyInfo.algorithm === SecretStorage_1.SECRET_STORAGE_ALGORITHM_V1_AES; + // Log all relevant state for easier parsing of debug logs. + logger_1.logger.log({ + keyBackupInfo, + setupNewKeyBackup, + setupNewSecretStorage, + storageExists, + oldKeyInfo, + }); + if (!storageExists && !keyBackupInfo) { + // either we don't have anything, or we've been asked to restart + // from scratch + logger_1.logger.log("Secret storage does not exist, creating new storage key"); + // if we already have a usable default SSSS key and aren't resetting + // SSSS just use it. otherwise, create a new one + // Note: we leave the old SSSS key in place: there could be other + // secrets using it, in theory. We could move them to the new key but a) + // that would mean we'd need to prompt for the old passphrase, and b) + // it's not clear that would be the right thing to do anyway. + const { keyInfo = {}, privateKey } = yield createSecretStorageKey(); + newKeyId = yield createSSSS(keyInfo, privateKey); + } + else if (!storageExists && keyBackupInfo) { + // we have an existing backup, but no SSSS + logger_1.logger.log("Secret storage does not exist, using key backup key"); + // if we have the backup key already cached, use it; otherwise use the + // callback to prompt for the key + const backupKey = (yield this.getSessionBackupPrivateKey()) || (yield (getKeyBackupPassphrase === null || getKeyBackupPassphrase === void 0 ? void 0 : getKeyBackupPassphrase())); + // create a new SSSS key and use the backup key as the new SSSS key + const opts = {}; + if (keyBackupInfo.auth_data.private_key_salt && keyBackupInfo.auth_data.private_key_iterations) { + // FIXME: ??? + opts.passphrase = { + algorithm: "m.pbkdf2", + iterations: keyBackupInfo.auth_data.private_key_iterations, + salt: keyBackupInfo.auth_data.private_key_salt, + bits: 256, + }; + } + newKeyId = yield createSSSS(opts, backupKey); + // store the backup key in secret storage + yield secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId]); + // The backup is trusted because the user provided the private key. + // Sign the backup with the cross-signing key so the key backup can + // be trusted via cross-signing. + yield signKeyBackupWithCrossSigning(keyBackupInfo.auth_data); + builder.addSessionBackup(keyBackupInfo); + } + else { + // 4S is already set up + logger_1.logger.log("Secret storage exists"); + if (oldKeyInfo && oldKeyInfo.algorithm === SecretStorage_1.SECRET_STORAGE_ALGORITHM_V1_AES) { + // make sure that the default key has the information needed to + // check the passphrase + yield ensureCanCheckPassphrase(oldKeyId, oldKeyInfo); + } + } + // If we have cross-signing private keys cached, store them in secret + // storage if they are not there already. + if (!this.baseApis.cryptoCallbacks.saveCrossSigningKeys && + (yield this.isCrossSigningReady()) && + (newKeyId || !(yield this.crossSigningInfo.isStoredInSecretStorage(secretStorage)))) { + logger_1.logger.log("Copying cross-signing private keys from cache to secret storage"); + const crossSigningPrivateKeys = yield this.crossSigningInfo.getCrossSigningKeysFromCache(); + // This is writing to in-memory account data in + // builder.accountDataClientAdapter so won't fail + yield CrossSigning_1.CrossSigningInfo.storeInSecretStorage(crossSigningPrivateKeys, secretStorage); + } + if (setupNewKeyBackup && !keyBackupInfo) { + logger_1.logger.log("Creating new message key backup version"); + const info = yield this.baseApis.prepareKeyBackupVersion(null /* random key */, + // don't write to secret storage, as it will write to this.secretStorage. + // Here, we want to capture all the side-effects of bootstrapping, + // and want to write to the local secretStorage object + { secureSecretStorage: false }); + // write the key ourselves to 4S + const privateKey = (0, recoverykey_1.decodeRecoveryKey)(info.recovery_key); + yield secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(privateKey)); + // create keyBackupInfo object to add to builder + const data = { + algorithm: info.algorithm, + auth_data: info.auth_data, + }; + // Sign with cross-signing master key + yield signKeyBackupWithCrossSigning(data.auth_data); + // sign with the device fingerprint + yield this.signObject(data.auth_data); + builder.addSessionBackup(data); + } + // Cache the session backup key + const sessionBackupKey = yield secretStorage.get("m.megolm_backup.v1"); + if (sessionBackupKey) { + logger_1.logger.info("Got session backup key from secret storage: caching"); + // fix up the backup key if it's in the wrong format, and replace + // in secret storage + const fixedBackupKey = fixBackupKey(sessionBackupKey); + if (fixedBackupKey) { + const keyId = newKeyId || oldKeyId; + yield secretStorage.store("m.megolm_backup.v1", fixedBackupKey, keyId ? [keyId] : null); + } + const decodedBackupKey = new Uint8Array(olmlib.decodeBase64(fixedBackupKey || sessionBackupKey)); + builder.addSessionBackupPrivateKeyToCache(decodedBackupKey); + } + else if (this.backupManager.getKeyBackupEnabled()) { + // key backup is enabled but we don't have a session backup key in SSSS: see if we have one in + // the cache or the user can provide one, and if so, write it to SSSS + const backupKey = (yield this.getSessionBackupPrivateKey()) || (yield (getKeyBackupPassphrase === null || getKeyBackupPassphrase === void 0 ? void 0 : getKeyBackupPassphrase())); + if (!backupKey) { + // This will require user intervention to recover from since we don't have the key + // backup key anywhere. The user should probably just set up a new key backup and + // the key for the new backup will be stored. If we hit this scenario in the wild + // with any frequency, we should do more than just log an error. + logger_1.logger.error("Key backup is enabled but couldn't get key backup key!"); + return; + } + logger_1.logger.info("Got session backup key from cache/user that wasn't in SSSS: saving to SSSS"); + yield secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey)); + } + const operation = builder.buildOperation(); + yield operation.apply(this); + // this persists private keys and public keys as trusted, + // only do this if apply succeeded for now as retry isn't in place yet + yield builder.persist(this); + logger_1.logger.log("Secure Secret Storage ready"); + }); + } + addSecretStorageKey(algorithm, opts, keyID) { + return this.secretStorage.addKey(algorithm, opts, keyID); + } + hasSecretStorageKey(keyID) { + return this.secretStorage.hasKey(keyID); + } + getSecretStorageKey(keyID) { + return this.secretStorage.getKey(keyID); + } + storeSecret(name, secret, keys) { + return this.secretStorage.store(name, secret, keys); + } + getSecret(name) { + return this.secretStorage.get(name); + } + isSecretStored(name) { + return this.secretStorage.isStored(name); + } + requestSecret(name, devices) { + if (!devices) { + devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(this.userId)); + } + return this.secretStorage.request(name, devices); + } + getDefaultSecretStorageKeyId() { + return this.secretStorage.getDefaultKeyId(); + } + setDefaultSecretStorageKeyId(k) { + return this.secretStorage.setDefaultKeyId(k); + } + checkSecretStorageKey(key, info) { + return this.secretStorage.checkKey(key, info); + } + /** + * Checks that a given secret storage private key matches a given public key. + * This can be used by the getSecretStorageKey callback to verify that the + * private key it is about to supply is the one that was requested. + * + * @param privateKey - The private key + * @param expectedPublicKey - The public key + * @returns true if the key matches, otherwise false + */ + checkSecretStoragePrivateKey(privateKey, expectedPublicKey) { + let decryption = null; + try { + decryption = new global.Olm.PkDecryption(); + const gotPubkey = decryption.init_with_private_key(privateKey); + // make sure it agrees with the given pubkey + return gotPubkey === expectedPublicKey; + } + finally { + decryption === null || decryption === void 0 ? void 0 : decryption.free(); + } + } + /** + * Fetches the backup private key, if cached + * @returns the key, if any, or null + */ + getSessionBackupPrivateKey() { + return __awaiter(this, void 0, void 0, function* () { + let key = yield new Promise((resolve) => { + // TODO types + this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.cryptoStore.getSecretStorePrivateKey(txn, resolve, "m.megolm_backup.v1"); + }); + }); + // make sure we have a Uint8Array, rather than a string + if (key && typeof key === "string") { + key = new Uint8Array(olmlib.decodeBase64(fixBackupKey(key) || key)); + yield this.storeSessionBackupPrivateKey(key); + } + if (key && key.ciphertext) { + const pickleKey = Buffer.from(this.olmDevice.pickleKey); + const decrypted = yield (0, aes_1.decryptAES)(key, pickleKey, "m.megolm_backup.v1"); + key = olmlib.decodeBase64(decrypted); + } + return key; + }); + } + /** + * Stores the session backup key to the cache + * @param key - the private key + * @returns a promise so you can catch failures + */ + storeSessionBackupPrivateKey(key) { + return __awaiter(this, void 0, void 0, function* () { + if (!(key instanceof Uint8Array)) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`); + } + const pickleKey = Buffer.from(this.olmDevice.pickleKey); + const encryptedKey = yield (0, aes_1.encryptAES)(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1"); + return this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", encryptedKey); + }); + }); + } + /** + * Checks that a given cross-signing private key matches a given public key. + * This can be used by the getCrossSigningKey callback to verify that the + * private key it is about to supply is the one that was requested. + * + * @param privateKey - The private key + * @param expectedPublicKey - The public key + * @returns true if the key matches, otherwise false + */ + checkCrossSigningPrivateKey(privateKey, expectedPublicKey) { + let signing = null; + try { + signing = new global.Olm.PkSigning(); + const gotPubkey = signing.init_with_seed(privateKey); + // make sure it agrees with the given pubkey + return gotPubkey === expectedPublicKey; + } + finally { + signing === null || signing === void 0 ? void 0 : signing.free(); + } + } + /** + * Run various follow-up actions after cross-signing keys have changed locally + * (either by resetting the keys for the account or by getting them from secret + * storage), such as signing the current device, upgrading device + * verifications, etc. + */ + afterCrossSigningLocalKeyChange() { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info("Starting cross-signing key change post-processing"); + // sign the current device with the new key, and upload to the server + const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); + const signedDevice = yield this.crossSigningInfo.signDevice(this.userId, device); + logger_1.logger.info(`Starting background key sig upload for ${this.deviceId}`); + const upload = ({ shouldEmit = false }) => { + return this.baseApis + .uploadKeySignatures({ + [this.userId]: { + [this.deviceId]: signedDevice, + }, + }) + .then((response) => { + const { failures } = response || {}; + if (Object.keys(failures || []).length > 0) { + if (shouldEmit) { + this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "afterCrossSigningLocalKeyChange", upload); + } + throw new errors_1.KeySignatureUploadError("Key upload failed", { failures }); + } + logger_1.logger.info(`Finished background key sig upload for ${this.deviceId}`); + }) + .catch((e) => { + logger_1.logger.error(`Error during background key sig upload for ${this.deviceId}`, e); + }); + }; + upload({ shouldEmit: true }); + const shouldUpgradeCb = this.baseApis.cryptoCallbacks.shouldUpgradeDeviceVerifications; + if (shouldUpgradeCb) { + logger_1.logger.info("Starting device verification upgrade"); + // Check all users for signatures if upgrade callback present + // FIXME: do this in batches + const users = {}; + for (const [userId, crossSigningInfo] of Object.entries(this.deviceList.crossSigningInfo)) { + const upgradeInfo = yield this.checkForDeviceVerificationUpgrade(userId, CrossSigning_1.CrossSigningInfo.fromStorage(crossSigningInfo, userId)); + if (upgradeInfo) { + users[userId] = upgradeInfo; + } + } + if (Object.keys(users).length > 0) { + logger_1.logger.info(`Found ${Object.keys(users).length} verif users to upgrade`); + try { + const usersToUpgrade = yield shouldUpgradeCb({ users: users }); + if (usersToUpgrade) { + for (const userId of usersToUpgrade) { + if (userId in users) { + yield this.baseApis.setDeviceVerified(userId, users[userId].crossSigningInfo.getId()); + } + } + } + } + catch (e) { + logger_1.logger.log("shouldUpgradeDeviceVerifications threw an error: not upgrading", e); + } + } + logger_1.logger.info("Finished device verification upgrade"); + } + logger_1.logger.info("Finished cross-signing key change post-processing"); + }); + } + /** + * Check if a user's cross-signing key is a candidate for upgrading from device + * verification. + * + * @param userId - the user whose cross-signing information is to be checked + * @param crossSigningInfo - the cross-signing information to check + */ + checkForDeviceVerificationUpgrade(userId, crossSigningInfo) { + return __awaiter(this, void 0, void 0, function* () { + // only upgrade if this is the first cross-signing key that we've seen for + // them, and if their cross-signing key isn't already verified + const trustLevel = this.crossSigningInfo.checkUserTrust(crossSigningInfo); + if (crossSigningInfo.firstUse && !trustLevel.isVerified()) { + const devices = this.deviceList.getRawStoredDevicesForUser(userId); + const deviceIds = yield this.checkForValidDeviceSignature(userId, crossSigningInfo.keys.master, devices); + if (deviceIds.length) { + return { + devices: deviceIds.map((deviceId) => deviceinfo_1.DeviceInfo.fromStorage(devices[deviceId], deviceId)), + crossSigningInfo, + }; + } + } + }); + } + /** + * Check if the cross-signing key is signed by a verified device. + * + * @param userId - the user ID whose key is being checked + * @param key - the key that is being checked + * @param devices - the user's devices. Should be a map from device ID + * to device info + */ + checkForValidDeviceSignature(userId, key, devices) { + return __awaiter(this, void 0, void 0, function* () { + const deviceIds = []; + if (devices && key.signatures && key.signatures[userId]) { + for (const signame of Object.keys(key.signatures[userId])) { + const [, deviceId] = signame.split(":", 2); + if (deviceId in devices && devices[deviceId].verified === DeviceVerification.VERIFIED) { + try { + yield olmlib.verifySignature(this.olmDevice, key, userId, deviceId, devices[deviceId].keys[signame]); + deviceIds.push(deviceId); + } + catch (e) { } + } + } + } + return deviceIds; + }); + } + /** + * Get the user's cross-signing key ID. + * + * @param type - The type of key to get the ID of. One of + * "master", "self_signing", or "user_signing". Defaults to "master". + * + * @returns the key ID + */ + getCrossSigningId(type) { + return this.crossSigningInfo.getId(type); + } + /** + * Get the cross signing information for a given user. + * + * @param userId - the user ID to get the cross-signing info for. + * + * @returns the cross signing information for the user. + */ + getStoredCrossSigningForUser(userId) { + return this.deviceList.getStoredCrossSigningForUser(userId); + } + /** + * Check whether a given user is trusted. + * + * @param userId - The ID of the user to check. + * + * @returns + */ + checkUserTrust(userId) { + const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); + if (!userCrossSigning) { + return new CrossSigning_1.UserTrustLevel(false, false, false); + } + return this.crossSigningInfo.checkUserTrust(userCrossSigning); + } + /** + * Check whether a given device is trusted. + * + * @param userId - The ID of the user whose devices is to be checked. + * @param deviceId - The ID of the device to check + * + * @returns + */ + checkDeviceTrust(userId, deviceId) { + const device = this.deviceList.getStoredDevice(userId, deviceId); + return this.checkDeviceInfoTrust(userId, device); + } + /** + * Check whether a given deviceinfo is trusted. + * + * @param userId - The ID of the user whose devices is to be checked. + * @param device - The device info object to check + * + * @returns + */ + checkDeviceInfoTrust(userId, device) { + const trustedLocally = !!(device === null || device === void 0 ? void 0 : device.isVerified()); + const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); + if (device && userCrossSigning) { + // The trustCrossSignedDevices only affects trust of other people's cross-signing + // signatures + const trustCrossSig = this.trustCrossSignedDevices || userId === this.userId; + return this.crossSigningInfo.checkDeviceTrust(userCrossSigning, device, trustedLocally, trustCrossSig); + } + else { + return new CrossSigning_1.DeviceTrustLevel(false, false, trustedLocally, false); + } + } + /** + * Check whether one of our own devices is cross-signed by our + * user's stored keys, regardless of whether we trust those keys yet. + * + * @param deviceId - The ID of the device to check + * + * @returns true if the device is cross-signed + */ + checkIfOwnDeviceCrossSigned(deviceId) { + var _a; + const device = this.deviceList.getStoredDevice(this.userId, deviceId); + if (!device) + return false; + const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(this.userId); + return ((_a = userCrossSigning === null || userCrossSigning === void 0 ? void 0 : userCrossSigning.checkDeviceTrust(userCrossSigning, device, false, true).isCrossSigningVerified()) !== null && _a !== void 0 ? _a : false); + } + /** + * Check the copy of our cross-signing key that we have in the device list and + * see if we can get the private key. If so, mark it as trusted. + */ + checkOwnCrossSigningTrust({ allowPrivateKeyRequests = false, } = {}) { + return __awaiter(this, void 0, void 0, function* () { + const userId = this.userId; + // Before proceeding, ensure our cross-signing public keys have been + // downloaded via the device list. + yield this.downloadKeys([this.userId]); + // Also check which private keys are locally cached. + const crossSigningPrivateKeys = yield this.crossSigningInfo.getCrossSigningKeysFromCache(); + // If we see an update to our own master key, check it against the master + // key we have and, if it matches, mark it as verified + // First, get the new cross-signing info + const newCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); + if (!newCrossSigning) { + logger_1.logger.error("Got cross-signing update event for user " + userId + " but no new cross-signing information found!"); + return; + } + const seenPubkey = newCrossSigning.getId(); + const masterChanged = this.crossSigningInfo.getId() !== seenPubkey; + const masterExistsNotLocallyCached = newCrossSigning.getId() && !crossSigningPrivateKeys.has("master"); + if (masterChanged) { + logger_1.logger.info("Got new master public key", seenPubkey); + } + if (allowPrivateKeyRequests && (masterChanged || masterExistsNotLocallyCached)) { + logger_1.logger.info("Attempting to retrieve cross-signing master private key"); + let signing = null; + // It's important for control flow that we leave any errors alone for + // higher levels to handle so that e.g. cancelling access properly + // aborts any larger operation as well. + try { + const ret = yield this.crossSigningInfo.getCrossSigningKey("master", seenPubkey); + signing = ret[1]; + logger_1.logger.info("Got cross-signing master private key"); + } + finally { + signing === null || signing === void 0 ? void 0 : signing.free(); + } + } + const oldSelfSigningId = this.crossSigningInfo.getId("self_signing"); + const oldUserSigningId = this.crossSigningInfo.getId("user_signing"); + // Update the version of our keys in our cross-signing object and the local store + this.storeTrustedSelfKeys(newCrossSigning.keys); + const selfSigningChanged = oldSelfSigningId !== newCrossSigning.getId("self_signing"); + const userSigningChanged = oldUserSigningId !== newCrossSigning.getId("user_signing"); + const selfSigningExistsNotLocallyCached = newCrossSigning.getId("self_signing") && !crossSigningPrivateKeys.has("self_signing"); + const userSigningExistsNotLocallyCached = newCrossSigning.getId("user_signing") && !crossSigningPrivateKeys.has("user_signing"); + const keySignatures = {}; + if (selfSigningChanged) { + logger_1.logger.info("Got new self-signing key", newCrossSigning.getId("self_signing")); + } + if (allowPrivateKeyRequests && (selfSigningChanged || selfSigningExistsNotLocallyCached)) { + logger_1.logger.info("Attempting to retrieve cross-signing self-signing private key"); + let signing = null; + try { + const ret = yield this.crossSigningInfo.getCrossSigningKey("self_signing", newCrossSigning.getId("self_signing")); + signing = ret[1]; + logger_1.logger.info("Got cross-signing self-signing private key"); + } + finally { + signing === null || signing === void 0 ? void 0 : signing.free(); + } + const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); + const signedDevice = yield this.crossSigningInfo.signDevice(this.userId, device); + keySignatures[this.deviceId] = signedDevice; + } + if (userSigningChanged) { + logger_1.logger.info("Got new user-signing key", newCrossSigning.getId("user_signing")); + } + if (allowPrivateKeyRequests && (userSigningChanged || userSigningExistsNotLocallyCached)) { + logger_1.logger.info("Attempting to retrieve cross-signing user-signing private key"); + let signing = null; + try { + const ret = yield this.crossSigningInfo.getCrossSigningKey("user_signing", newCrossSigning.getId("user_signing")); + signing = ret[1]; + logger_1.logger.info("Got cross-signing user-signing private key"); + } + finally { + signing === null || signing === void 0 ? void 0 : signing.free(); + } + } + if (masterChanged) { + const masterKey = this.crossSigningInfo.keys.master; + yield this.signObject(masterKey); + const deviceSig = masterKey.signatures[this.userId]["ed25519:" + this.deviceId]; + // Include only the _new_ device signature in the upload. + // We may have existing signatures from deleted devices, which will cause + // the entire upload to fail. + keySignatures[this.crossSigningInfo.getId()] = Object.assign({}, masterKey, { + signatures: { + [this.userId]: { + ["ed25519:" + this.deviceId]: deviceSig, + }, + }, + }); + } + const keysToUpload = Object.keys(keySignatures); + if (keysToUpload.length) { + const upload = ({ shouldEmit = false }) => { + logger_1.logger.info(`Starting background key sig upload for ${keysToUpload}`); + return this.baseApis + .uploadKeySignatures({ [this.userId]: keySignatures }) + .then((response) => { + const { failures } = response || {}; + logger_1.logger.info(`Finished background key sig upload for ${keysToUpload}`); + if (Object.keys(failures || []).length > 0) { + if (shouldEmit) { + this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "checkOwnCrossSigningTrust", upload); + } + throw new errors_1.KeySignatureUploadError("Key upload failed", { failures }); + } + }) + .catch((e) => { + logger_1.logger.error(`Error during background key sig upload for ${keysToUpload}`, e); + }); + }; + upload({ shouldEmit: true }); + } + this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); + if (masterChanged) { + this.emit(CryptoEvent.KeysChanged, {}); + yield this.afterCrossSigningLocalKeyChange(); + } + // Now we may be able to trust our key backup + yield this.backupManager.checkKeyBackup(); + // FIXME: if we previously trusted the backup, should we automatically sign + // the backup with the new key (if not already signed)? + }); + } + /** + * Store a set of keys as our own, trusted, cross-signing keys. + * + * @param keys - The new trusted set of keys + */ + storeTrustedSelfKeys(keys) { + return __awaiter(this, void 0, void 0, function* () { + if (keys) { + this.crossSigningInfo.setKeys(keys); + } + else { + this.crossSigningInfo.clearKeys(); + } + yield this.cryptoStore.doTxn("readwrite", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + this.cryptoStore.storeCrossSigningKeys(txn, this.crossSigningInfo.keys); + }); + }); + } + /** + * Check if the master key is signed by a verified device, and if so, prompt + * the application to mark it as verified. + * + * @param userId - the user ID whose key should be checked + */ + checkDeviceVerifications(userId) { + return __awaiter(this, void 0, void 0, function* () { + const shouldUpgradeCb = this.baseApis.cryptoCallbacks.shouldUpgradeDeviceVerifications; + if (!shouldUpgradeCb) { + // Upgrading skipped when callback is not present. + return; + } + logger_1.logger.info(`Starting device verification upgrade for ${userId}`); + if (this.crossSigningInfo.keys.user_signing) { + const crossSigningInfo = this.deviceList.getStoredCrossSigningForUser(userId); + if (crossSigningInfo) { + const upgradeInfo = yield this.checkForDeviceVerificationUpgrade(userId, crossSigningInfo); + if (upgradeInfo) { + const usersToUpgrade = yield shouldUpgradeCb({ + users: { + [userId]: upgradeInfo, + }, + }); + if (usersToUpgrade.includes(userId)) { + yield this.baseApis.setDeviceVerified(userId, crossSigningInfo.getId()); + } + } + } + } + logger_1.logger.info(`Finished device verification upgrade for ${userId}`); + }); + } + /** + */ + enableLazyLoading() { + this.lazyLoadMembers = true; + } + /** + * Tell the crypto module to register for MatrixClient events which it needs to + * listen for + * + * @param eventEmitter - event source where we can register + * for event notifications + */ + registerEventHandlers(eventEmitter) { + eventEmitter.on(room_member_1.RoomMemberEvent.Membership, this.onMembership); + eventEmitter.on(client_1.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + eventEmitter.on(room_1.RoomEvent.Timeline, this.onTimelineEvent); + eventEmitter.on(event_2.MatrixEventEvent.Decrypted, this.onTimelineEvent); + } + /** + * @deprecated this does nothing and will be removed in a future version + */ + start() { + logger_1.logger.warn("MatrixClient.crypto.start() is deprecated"); + } + /** Stop background processes related to crypto */ + stop() { + this.outgoingRoomKeyRequestManager.stop(); + this.deviceList.stop(); + this.dehydrationManager.stop(); + } + /** + * Get the Ed25519 key for this device + * + * @returns base64-encoded ed25519 key. + */ + getDeviceEd25519Key() { + return this.olmDevice.deviceEd25519Key; + } + /** + * Get the Curve25519 key for this device + * + * @returns base64-encoded curve25519 key. + */ + getDeviceCurve25519Key() { + return this.olmDevice.deviceCurve25519Key; + } + /** + * Set the global override for whether the client should ever send encrypted + * messages to unverified devices. This provides the default for rooms which + * do not specify a value. + * + * @param value - whether to blacklist all unverified devices by default + * + * @deprecated For external code, use {@link MatrixClient#setGlobalBlacklistUnverifiedDevices}. For + * internal code, set {@link MatrixClient#globalBlacklistUnverifiedDevices} directly. + */ + setGlobalBlacklistUnverifiedDevices(value) { + this.globalBlacklistUnverifiedDevices = value; + } + /** + * @returns whether to blacklist all unverified devices by default + * + * @deprecated For external code, use {@link MatrixClient#getGlobalBlacklistUnverifiedDevices}. For + * internal code, reference {@link MatrixClient#globalBlacklistUnverifiedDevices} directly. + */ + getGlobalBlacklistUnverifiedDevices() { + return this.globalBlacklistUnverifiedDevices; + } + /** + * Upload the device keys to the homeserver. + * @returns A promise that will resolve when the keys are uploaded. + */ + uploadDeviceKeys() { + const deviceKeys = { + algorithms: this.supportedAlgorithms, + device_id: this.deviceId, + keys: this.deviceKeys, + user_id: this.userId, + }; + return this.signObject(deviceKeys).then(() => { + return this.baseApis.uploadKeysRequest({ + device_keys: deviceKeys, + }); + }); + } + /** + * Stores the current one_time_key count which will be handled later (in a call of + * onSyncCompleted). The count is e.g. coming from a /sync response. + * + * @param currentCount - The current count of one_time_keys to be stored + */ + updateOneTimeKeyCount(currentCount) { + if (isFinite(currentCount)) { + this.oneTimeKeyCount = currentCount; + } + else { + throw new TypeError("Parameter for updateOneTimeKeyCount has to be a number"); + } + } + setNeedsNewFallback(needsNewFallback) { + this.needsNewFallback = needsNewFallback; + } + getNeedsNewFallback() { + return !!this.needsNewFallback; + } + // check if it's time to upload one-time keys, and do so if so. + maybeUploadOneTimeKeys() { + // frequency with which to check & upload one-time keys + const uploadPeriod = 1000 * 60; // one minute + // max number of keys to upload at once + // Creating keys can be an expensive operation so we limit the + // number we generate in one go to avoid blocking the application + // for too long. + const maxKeysPerCycle = 5; + if (this.oneTimeKeyCheckInProgress) { + return; + } + const now = Date.now(); + if (this.lastOneTimeKeyCheck !== null && now - this.lastOneTimeKeyCheck < uploadPeriod) { + // we've done a key upload recently. + return; + } + this.lastOneTimeKeyCheck = now; + // We need to keep a pool of one time public keys on the server so that + // other devices can start conversations with us. But we can only store + // a finite number of private keys in the olm Account object. + // To complicate things further then can be a delay between a device + // claiming a public one time key from the server and it sending us a + // message. We need to keep the corresponding private key locally until + // we receive the message. + // But that message might never arrive leaving us stuck with duff + // private keys clogging up our local storage. + // So we need some kind of engineering compromise to balance all of + // these factors. + // Check how many keys we can store in the Account object. + const maxOneTimeKeys = this.olmDevice.maxNumberOfOneTimeKeys(); + // Try to keep at most half that number on the server. This leaves the + // rest of the slots free to hold keys that have been claimed from the + // server but we haven't received a message for. + // If we run out of slots when generating new keys then olm will + // discard the oldest private keys first. This will eventually clean + // out stale private keys that won't receive a message. + const keyLimit = Math.floor(maxOneTimeKeys / 2); + const uploadLoop = (keyCount) => __awaiter(this, void 0, void 0, function* () { + while (keyLimit > keyCount || this.getNeedsNewFallback()) { + // Ask olm to generate new one time keys, then upload them to synapse. + if (keyLimit > keyCount) { + logger_1.logger.info("generating oneTimeKeys"); + const keysThisLoop = Math.min(keyLimit - keyCount, maxKeysPerCycle); + yield this.olmDevice.generateOneTimeKeys(keysThisLoop); + } + if (this.getNeedsNewFallback()) { + const fallbackKeys = yield this.olmDevice.getFallbackKey(); + // if fallbackKeys is non-empty, we've already generated a + // fallback key, but it hasn't been published yet, so we + // can use that instead of generating a new one + if (!fallbackKeys.curve25519 || Object.keys(fallbackKeys.curve25519).length == 0) { + logger_1.logger.info("generating fallback key"); + if (this.fallbackCleanup) { + // cancel any pending fallback cleanup because generating + // a new fallback key will already drop the old fallback + // that would have been dropped, and we don't want to kill + // the current key + clearTimeout(this.fallbackCleanup); + delete this.fallbackCleanup; + } + yield this.olmDevice.generateFallbackKey(); + } + } + logger_1.logger.info("calling uploadOneTimeKeys"); + const res = yield this.uploadOneTimeKeys(); + if (res.one_time_key_counts && res.one_time_key_counts.signed_curve25519) { + // if the response contains a more up to date value use this + // for the next loop + keyCount = res.one_time_key_counts.signed_curve25519; + } + else { + throw new Error("response for uploading keys does not contain " + "one_time_key_counts.signed_curve25519"); + } + } + }); + this.oneTimeKeyCheckInProgress = true; + Promise.resolve() + .then(() => { + if (this.oneTimeKeyCount !== undefined) { + // We already have the current one_time_key count from a /sync response. + // Use this value instead of asking the server for the current key count. + return Promise.resolve(this.oneTimeKeyCount); + } + // ask the server how many keys we have + return this.baseApis.uploadKeysRequest({}).then((res) => { + return res.one_time_key_counts.signed_curve25519 || 0; + }); + }) + .then((keyCount) => { + // Start the uploadLoop with the current keyCount. The function checks if + // we need to upload new keys or not. + // If there are too many keys on the server then we don't need to + // create any more keys. + return uploadLoop(keyCount); + }) + .catch((e) => { + logger_1.logger.error("Error uploading one-time keys", e.stack || e); + }) + .finally(() => { + // reset oneTimeKeyCount to prevent start uploading based on old data. + // it will be set again on the next /sync-response + this.oneTimeKeyCount = undefined; + this.oneTimeKeyCheckInProgress = false; + }); + } + // returns a promise which resolves to the response + uploadOneTimeKeys() { + return __awaiter(this, void 0, void 0, function* () { + const promises = []; + let fallbackJson; + if (this.getNeedsNewFallback()) { + fallbackJson = {}; + const fallbackKeys = yield this.olmDevice.getFallbackKey(); + for (const [keyId, key] of Object.entries(fallbackKeys.curve25519)) { + const k = { key, fallback: true }; + fallbackJson["signed_curve25519:" + keyId] = k; + promises.push(this.signObject(k)); + } + this.setNeedsNewFallback(false); + } + const oneTimeKeys = yield this.olmDevice.getOneTimeKeys(); + const oneTimeJson = {}; + for (const keyId in oneTimeKeys.curve25519) { + if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) { + const k = { + key: oneTimeKeys.curve25519[keyId], + }; + oneTimeJson["signed_curve25519:" + keyId] = k; + promises.push(this.signObject(k)); + } + } + yield Promise.all(promises); + const requestBody = { + one_time_keys: oneTimeJson, + }; + if (fallbackJson) { + requestBody["org.matrix.msc2732.fallback_keys"] = fallbackJson; + requestBody["fallback_keys"] = fallbackJson; + } + const res = yield this.baseApis.uploadKeysRequest(requestBody); + if (fallbackJson) { + this.fallbackCleanup = setTimeout(() => { + delete this.fallbackCleanup; + this.olmDevice.forgetOldFallbackKey(); + }, 60 * 60 * 1000); + } + yield this.olmDevice.markKeysAsPublished(); + return res; + }); + } + /** + * Download the keys for a list of users and stores the keys in the session + * store. + * @param userIds - The users to fetch. + * @param forceDownload - Always download the keys even if cached. + * + * @returns A promise which resolves to a map `userId->deviceId->{@link DeviceInfo}`. + */ + downloadKeys(userIds, forceDownload) { + return this.deviceList.downloadKeys(userIds, !!forceDownload); + } + /** + * Get the stored device keys for a user id + * + * @param userId - the user to list keys for. + * + * @returns list of devices, or null if we haven't + * managed to get a list of devices for this user yet. + */ + getStoredDevicesForUser(userId) { + return this.deviceList.getStoredDevicesForUser(userId); + } + /** + * Get the stored keys for a single device + * + * + * @returns device, or undefined + * if we don't know about this device + */ + getStoredDevice(userId, deviceId) { + return this.deviceList.getStoredDevice(userId, deviceId); + } + /** + * Save the device list, if necessary + * + * @param delay - Time in ms before which the save actually happens. + * By default, the save is delayed for a short period in order to batch + * multiple writes, but this behaviour can be disabled by passing 0. + * + * @returns true if the data was saved, false if + * it was not (eg. because no changes were pending). The promise + * will only resolve once the data is saved, so may take some time + * to resolve. + */ + saveDeviceList(delay) { + return this.deviceList.saveIfDirty(delay); + } + /** + * Update the blocked/verified state of the given device + * + * @param userId - owner of the device + * @param deviceId - unique identifier for the device or user's + * cross-signing public key ID. + * + * @param verified - whether to mark the device as verified. Null to + * leave unchanged. + * + * @param blocked - whether to mark the device as blocked. Null to + * leave unchanged. + * + * @param known - whether to mark that the user has been made aware of + * the existence of this device. Null to leave unchanged + * + * @param keys - The list of keys that was present + * during the device verification. This will be double checked with the list + * of keys the given device has currently. + * + * @returns updated DeviceInfo + */ + setDeviceVerification(userId, deviceId, verified = null, blocked = null, known = null, keys) { + return __awaiter(this, void 0, void 0, function* () { + // Check if the 'device' is actually a cross signing key + // The js-sdk's verification treats cross-signing keys as devices + // and so uses this method to mark them verified. + const xsk = this.deviceList.getStoredCrossSigningForUser(userId); + if (xsk && xsk.getId() === deviceId) { + if (blocked !== null || known !== null) { + throw new Error("Cannot set blocked or known for a cross-signing key"); + } + if (!verified) { + throw new Error("Cannot set a cross-signing key as unverified"); + } + const gotKeyId = keys ? Object.values(keys)[0] : null; + if (keys && (Object.values(keys).length !== 1 || gotKeyId !== xsk.getId())) { + throw new Error(`Key did not match expected value: expected ${xsk.getId()}, got ${gotKeyId}`); + } + if (!this.crossSigningInfo.getId() && userId === this.crossSigningInfo.userId) { + this.storeTrustedSelfKeys(xsk.keys); + // This will cause our own user trust to change, so emit the event + this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); + } + // Now sign the master key with our user signing key (unless it's ourself) + if (userId !== this.userId) { + logger_1.logger.info("Master key " + xsk.getId() + " for " + userId + " marked verified. Signing..."); + const device = yield this.crossSigningInfo.signUser(xsk); + if (device) { + const upload = ({ shouldEmit = false }) => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info("Uploading signature for " + userId + "..."); + const response = yield this.baseApis.uploadKeySignatures({ + [userId]: { + [deviceId]: device, + }, + }); + const { failures } = response || {}; + if (Object.keys(failures || []).length > 0) { + if (shouldEmit) { + this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload); + } + /* Throwing here causes the process to be cancelled and the other + * user to be notified */ + throw new errors_1.KeySignatureUploadError("Key upload failed", { failures }); + } + }); + yield upload({ shouldEmit: true }); + // This will emit events when it comes back down the sync + // (we could do local echo to speed things up) + } + return device; // TODO types + } + else { + return xsk; + } + } + const devices = this.deviceList.getRawStoredDevicesForUser(userId); + if (!devices || !devices[deviceId]) { + throw new Error("Unknown device " + userId + ":" + deviceId); + } + const dev = devices[deviceId]; + let verificationStatus = dev.verified; + if (verified) { + if (keys) { + for (const [keyId, key] of Object.entries(keys)) { + if (dev.keys[keyId] !== key) { + throw new Error(`Key did not match expected value: expected ${key}, got ${dev.keys[keyId]}`); + } + } + } + verificationStatus = DeviceVerification.VERIFIED; + } + else if (verified !== null && verificationStatus == DeviceVerification.VERIFIED) { + verificationStatus = DeviceVerification.UNVERIFIED; + } + if (blocked) { + verificationStatus = DeviceVerification.BLOCKED; + } + else if (blocked !== null && verificationStatus == DeviceVerification.BLOCKED) { + verificationStatus = DeviceVerification.UNVERIFIED; + } + let knownStatus = dev.known; + if (known !== null) { + knownStatus = known; + } + if (dev.verified !== verificationStatus || dev.known !== knownStatus) { + dev.verified = verificationStatus; + dev.known = knownStatus; + this.deviceList.storeDevicesForUser(userId, devices); + this.deviceList.saveIfDirty(); + } + // do cross-signing + if (verified && userId === this.userId) { + logger_1.logger.info("Own device " + deviceId + " marked verified: signing"); + // Signing only needed if other device not already signed + let device; + const deviceTrust = this.checkDeviceTrust(userId, deviceId); + if (deviceTrust.isCrossSigningVerified()) { + logger_1.logger.log(`Own device ${deviceId} already cross-signing verified`); + } + else { + device = (yield this.crossSigningInfo.signDevice(userId, deviceinfo_1.DeviceInfo.fromStorage(dev, deviceId))); + } + if (device) { + const upload = ({ shouldEmit = false }) => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info("Uploading signature for " + deviceId); + const response = yield this.baseApis.uploadKeySignatures({ + [userId]: { + [deviceId]: device, + }, + }); + const { failures } = response || {}; + if (Object.keys(failures || []).length > 0) { + if (shouldEmit) { + this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload); + } + throw new errors_1.KeySignatureUploadError("Key upload failed", { failures }); + } + }); + yield upload({ shouldEmit: true }); + // XXX: we'll need to wait for the device list to be updated + } + } + const deviceObj = deviceinfo_1.DeviceInfo.fromStorage(dev, deviceId); + this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); + return deviceObj; + }); + } + findVerificationRequestDMInProgress(roomId) { + return this.inRoomVerificationRequests.findRequestInProgress(roomId); + } + getVerificationRequestsToDeviceInProgress(userId) { + return this.toDeviceVerificationRequests.getRequestsInProgress(userId); + } + requestVerificationDM(userId, roomId) { + const existingRequest = this.inRoomVerificationRequests.findRequestInProgress(roomId); + if (existingRequest) { + return Promise.resolve(existingRequest); + } + const channel = new InRoomChannel_1.InRoomChannel(this.baseApis, roomId, userId); + return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests); + } + requestVerification(userId, devices) { + if (!devices) { + devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(userId)); + } + const existingRequest = this.toDeviceVerificationRequests.findRequestInProgress(userId, devices); + if (existingRequest) { + return Promise.resolve(existingRequest); + } + const channel = new ToDeviceChannel_1.ToDeviceChannel(this.baseApis, userId, devices, ToDeviceChannel_1.ToDeviceChannel.makeTransactionId()); + return this.requestVerificationWithChannel(userId, channel, this.toDeviceVerificationRequests); + } + requestVerificationWithChannel(userId, channel, requestsMap) { + return __awaiter(this, void 0, void 0, function* () { + let request = new VerificationRequest_1.VerificationRequest(channel, this.verificationMethods, this.baseApis); + // if transaction id is already known, add request + if (channel.transactionId) { + requestsMap.setRequestByChannel(channel, request); + } + yield request.sendRequest(); + // don't replace the request created by a racing remote echo + const racingRequest = requestsMap.getRequestByChannel(channel); + if (racingRequest) { + request = racingRequest; + } + else { + logger_1.logger.log(`Crypto: adding new request to ` + `requestsByTxnId with id ${channel.transactionId} ${channel.roomId}`); + requestsMap.setRequestByChannel(channel, request); + } + return request; + }); + } + beginKeyVerification(method, userId, deviceId, transactionId = null) { + let request; + if (transactionId) { + request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId); + if (!request) { + throw new Error(`No request found for user ${userId} with ` + `transactionId ${transactionId}`); + } + } + else { + transactionId = ToDeviceChannel_1.ToDeviceChannel.makeTransactionId(); + const channel = new ToDeviceChannel_1.ToDeviceChannel(this.baseApis, userId, [deviceId], transactionId, deviceId); + request = new VerificationRequest_1.VerificationRequest(channel, this.verificationMethods, this.baseApis); + this.toDeviceVerificationRequests.setRequestBySenderAndTxnId(userId, transactionId, request); + } + return request.beginKeyVerification(method, { userId, deviceId }); + } + legacyDeviceVerification(userId, deviceId, method) { + return __awaiter(this, void 0, void 0, function* () { + const transactionId = ToDeviceChannel_1.ToDeviceChannel.makeTransactionId(); + const channel = new ToDeviceChannel_1.ToDeviceChannel(this.baseApis, userId, [deviceId], transactionId, deviceId); + const request = new VerificationRequest_1.VerificationRequest(channel, this.verificationMethods, this.baseApis); + this.toDeviceVerificationRequests.setRequestBySenderAndTxnId(userId, transactionId, request); + const verifier = request.beginKeyVerification(method, { userId, deviceId }); + // either reject by an error from verify() while sending .start + // or resolve when the request receives the + // local (fake remote) echo for sending the .start event + yield Promise.race([verifier.verify(), request.waitFor((r) => r.started)]); + return request; + }); + } + /** + * Get information on the active olm sessions with a user + *

+ * Returns a map from device id to an object with keys 'deviceIdKey' (the + * device's curve25519 identity key) and 'sessions' (an array of objects in the + * same format as that returned by + * {@link OlmDevice#getSessionInfoForDevice}). + *

+ * This method is provided for debugging purposes. + * + * @param userId - id of user to inspect + */ + getOlmSessionsForUser(userId) { + return __awaiter(this, void 0, void 0, function* () { + const devices = this.getStoredDevicesForUser(userId) || []; + const result = {}; + for (const device of devices) { + const deviceKey = device.getIdentityKey(); + const sessions = yield this.olmDevice.getSessionInfoForDevice(deviceKey); + result[device.deviceId] = { + deviceIdKey: deviceKey, + sessions: sessions, + }; + } + return result; + }); + } + /** + * Get the device which sent an event + * + * @param event - event to be checked + */ + getEventSenderDeviceInfo(event) { + const senderKey = event.getSenderKey(); + const algorithm = event.getWireContent().algorithm; + if (!senderKey || !algorithm) { + return null; + } + if (event.isKeySourceUntrusted()) { + // we got the key for this event from a source that we consider untrusted + return null; + } + // senderKey is the Curve25519 identity key of the device which the event + // was sent from. In the case of Megolm, it's actually the Curve25519 + // identity key of the device which set up the Megolm session. + const device = this.deviceList.getDeviceByIdentityKey(algorithm, senderKey); + if (device === null) { + // we haven't downloaded the details of this device yet. + return null; + } + // so far so good, but now we need to check that the sender of this event + // hadn't advertised someone else's Curve25519 key as their own. We do that + // by checking the Ed25519 claimed by the event (or, in the case of megolm, + // the event which set up the megolm session), to check that it matches the + // fingerprint of the purported sending device. + // + // (see https://github.com/vector-im/vector-web/issues/2215) + const claimedKey = event.getClaimedEd25519Key(); + if (!claimedKey) { + logger_1.logger.warn("Event " + event.getId() + " claims no ed25519 key: " + "cannot verify sending device"); + return null; + } + if (claimedKey !== device.getFingerprint()) { + logger_1.logger.warn("Event " + + event.getId() + + " claims ed25519 key " + + claimedKey + + " but sender device has key " + + device.getFingerprint()); + return null; + } + return device; + } + /** + * Get information about the encryption of an event + * + * @param event - event to be checked + * + * @returns An object with the fields: + * - encrypted: whether the event is encrypted (if not encrypted, some of the + * other properties may not be set) + * - senderKey: the sender's key + * - algorithm: the algorithm used to encrypt the event + * - authenticated: whether we can be sure that the owner of the senderKey + * sent the event + * - sender: the sender's device information, if available + * - mismatchedSender: if the event's ed25519 and curve25519 keys don't match + * (only meaningful if `sender` is set) + */ + getEventEncryptionInfo(event) { + var _a, _b; + const ret = {}; + ret.senderKey = (_a = event.getSenderKey()) !== null && _a !== void 0 ? _a : undefined; + ret.algorithm = event.getWireContent().algorithm; + if (!ret.senderKey || !ret.algorithm) { + ret.encrypted = false; + return ret; + } + ret.encrypted = true; + if (event.isKeySourceUntrusted()) { + // we got the key this event from somewhere else + // TODO: check if we can trust the forwarders. + ret.authenticated = false; + } + else { + ret.authenticated = true; + } + // senderKey is the Curve25519 identity key of the device which the event + // was sent from. In the case of Megolm, it's actually the Curve25519 + // identity key of the device which set up the Megolm session. + ret.sender = (_b = this.deviceList.getDeviceByIdentityKey(ret.algorithm, ret.senderKey)) !== null && _b !== void 0 ? _b : undefined; + // so far so good, but now we need to check that the sender of this event + // hadn't advertised someone else's Curve25519 key as their own. We do that + // by checking the Ed25519 claimed by the event (or, in the case of megolm, + // the event which set up the megolm session), to check that it matches the + // fingerprint of the purported sending device. + // + // (see https://github.com/vector-im/vector-web/issues/2215) + const claimedKey = event.getClaimedEd25519Key(); + if (!claimedKey) { + logger_1.logger.warn("Event " + event.getId() + " claims no ed25519 key: " + "cannot verify sending device"); + ret.mismatchedSender = true; + } + if (ret.sender && claimedKey !== ret.sender.getFingerprint()) { + logger_1.logger.warn("Event " + + event.getId() + + " claims ed25519 key " + + claimedKey + + "but sender device has key " + + ret.sender.getFingerprint()); + ret.mismatchedSender = true; + } + return ret; + } + /** + * Forces the current outbound group session to be discarded such + * that another one will be created next time an event is sent. + * + * @param roomId - The ID of the room to discard the session for + * + * This should not normally be necessary. + */ + forceDiscardSession(roomId) { + const alg = this.roomEncryptors.get(roomId); + if (alg === undefined) + throw new Error("Room not encrypted"); + if (alg.forceDiscardSession === undefined) { + throw new Error("Room encryption algorithm doesn't support session discarding"); + } + alg.forceDiscardSession(); + return Promise.resolve(); + } + /** + * Configure a room to use encryption (ie, save a flag in the cryptoStore). + * + * @param roomId - The room ID to enable encryption in. + * + * @param config - The encryption config for the room. + * + * @param inhibitDeviceQuery - true to suppress device list query for + * users in the room (for now). In case lazy loading is enabled, + * the device query is always inhibited as the members are not tracked. + * + * @deprecated It is normally incorrect to call this method directly. Encryption + * is enabled by receiving an `m.room.encryption` event (which we may have sent + * previously). + */ + setRoomEncryption(roomId, config, inhibitDeviceQuery) { + return __awaiter(this, void 0, void 0, function* () { + const room = this.clientStore.getRoom(roomId); + if (!room) { + throw new Error(`Unable to enable encryption tracking devices in unknown room ${roomId}`); + } + yield this.setRoomEncryptionImpl(room, config); + if (!this.lazyLoadMembers && !inhibitDeviceQuery) { + this.deviceList.refreshOutdatedDeviceLists(); + } + }); + } + /** + * Set up encryption for a room. + * + * This is called when an m.room.encryption event is received. It saves a flag + * for the room in the cryptoStore (if it wasn't already set), sets up an "encryptor" for + * the room, and enables device-list tracking for the room. + * + * It does not initiate a device list query for the room. That is normally + * done once we finish processing the sync, in onSyncCompleted. + * + * @param room - The room to enable encryption in. + * @param config - The encryption config for the room. + */ + setRoomEncryptionImpl(room, config) { + return __awaiter(this, void 0, void 0, function* () { + const roomId = room.roomId; + // ignore crypto events with no algorithm defined + // This will happen if a crypto event is redacted before we fetch the room state + // It would otherwise just throw later as an unknown algorithm would, but we may + // as well catch this here + if (!config.algorithm) { + logger_1.logger.log("Ignoring setRoomEncryption with no algorithm"); + return; + } + // if state is being replayed from storage, we might already have a configuration + // for this room as they are persisted as well. + // We just need to make sure the algorithm is initialized in this case. + // However, if the new config is different, + // we should bail out as room encryption can't be changed once set. + const existingConfig = this.roomList.getRoomEncryption(roomId); + if (existingConfig) { + if (JSON.stringify(existingConfig) != JSON.stringify(config)) { + logger_1.logger.error("Ignoring m.room.encryption event which requests " + "a change of config in " + roomId); + return; + } + } + // if we already have encryption in this room, we should ignore this event, + // as it would reset the encryption algorithm. + // This is at least expected to be called twice, as sync calls onCryptoEvent + // for both the timeline and state sections in the /sync response, + // the encryption event would appear in both. + // If it's called more than twice though, + // it signals a bug on client or server. + const existingAlg = this.roomEncryptors.get(roomId); + if (existingAlg) { + return; + } + // _roomList.getRoomEncryption will not race with _roomList.setRoomEncryption + // because it first stores in memory. We should await the promise only + // after all the in-memory state (roomEncryptors and _roomList) has been updated + // to avoid races when calling this method multiple times. Hence keep a hold of the promise. + let storeConfigPromise = null; + if (!existingConfig) { + storeConfigPromise = this.roomList.setRoomEncryption(roomId, config); + } + const AlgClass = algorithms.ENCRYPTION_CLASSES.get(config.algorithm); + if (!AlgClass) { + throw new Error("Unable to encrypt with " + config.algorithm); + } + const alg = new AlgClass({ + userId: this.userId, + deviceId: this.deviceId, + crypto: this, + olmDevice: this.olmDevice, + baseApis: this.baseApis, + roomId, + config, + }); + this.roomEncryptors.set(roomId, alg); + if (storeConfigPromise) { + yield storeConfigPromise; + } + logger_1.logger.log(`Enabling encryption in ${roomId}`); + // we don't want to force a download of the full membership list of this room, but as soon as we have that + // list we can start tracking the device list. + if (room.membersLoaded()) { + yield this.trackRoomDevicesImpl(room); + } + else { + // wait for the membership list to be loaded + const onState = (_state) => { + room.off(room_state_1.RoomStateEvent.Update, onState); + if (room.membersLoaded()) { + this.trackRoomDevicesImpl(room).catch((e) => { + logger_1.logger.error(`Error enabling device tracking in ${roomId}`, e); + }); + } + }; + room.on(room_state_1.RoomStateEvent.Update, onState); + } + }); + } + /** + * Make sure we are tracking the device lists for all users in this room. + * + * @param roomId - The room ID to start tracking devices in. + * @returns when all devices for the room have been fetched and marked to track + * @deprecated there's normally no need to call this function: device list tracking + * will be enabled as soon as we have the full membership list. + */ + trackRoomDevices(roomId) { + const room = this.clientStore.getRoom(roomId); + if (!room) { + throw new Error(`Unable to start tracking devices in unknown room ${roomId}`); + } + return this.trackRoomDevicesImpl(room); + } + /** + * Make sure we are tracking the device lists for all users in this room. + * + * This is normally called when we are about to send an encrypted event, to make sure + * we have all the devices in the room; but it is also called when processing an + * m.room.encryption state event (if lazy-loading is disabled), or when members are + * loaded (if lazy-loading is enabled), to prepare the device list. + * + * @param room - Room to enable device-list tracking in + */ + trackRoomDevicesImpl(room) { + const roomId = room.roomId; + const trackMembers = () => __awaiter(this, void 0, void 0, function* () { + // not an encrypted room + if (!this.roomEncryptors.has(roomId)) { + return; + } + logger_1.logger.log(`Starting to track devices for room ${roomId} ...`); + const members = yield room.getEncryptionTargetMembers(); + members.forEach((m) => { + this.deviceList.startTrackingDeviceList(m.userId); + }); + }); + let promise = this.roomDeviceTrackingState[roomId]; + if (!promise) { + promise = trackMembers(); + this.roomDeviceTrackingState[roomId] = promise.catch((err) => { + delete this.roomDeviceTrackingState[roomId]; + throw err; + }); + } + return promise; + } + /** + * Try to make sure we have established olm sessions for all known devices for + * the given users. + * + * @param users - list of user ids + * @param force - If true, force a new Olm session to be created. Default false. + * + * @returns resolves once the sessions are complete, to + * an Object mapping from userId to deviceId to + * {@link OlmSessionResult} + */ + ensureOlmSessionsForUsers(users, force) { + // map user Id → DeviceInfo[] + const devicesByUser = new Map(); + for (const userId of users) { + const userDevices = []; + devicesByUser.set(userId, userDevices); + const devices = this.getStoredDevicesForUser(userId) || []; + for (const deviceInfo of devices) { + const key = deviceInfo.getIdentityKey(); + if (key == this.olmDevice.deviceCurve25519Key) { + // don't bother setting up session to ourself + continue; + } + if (deviceInfo.verified == DeviceVerification.BLOCKED) { + // don't bother setting up sessions with blocked users + continue; + } + userDevices.push(deviceInfo); + } + } + return olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, force); + } + /** + * Get a list containing all of the room keys + * + * @returns a list of session export objects + */ + exportRoomKeys() { + return __awaiter(this, void 0, void 0, function* () { + const exportedSessions = []; + yield this.cryptoStore.doTxn("readonly", [indexeddb_crypto_store_1.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { + this.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (s) => { + if (s === null) + return; + const sess = this.olmDevice.exportInboundGroupSession(s.senderKey, s.sessionId, s.sessionData); + delete sess.first_known_index; + sess.algorithm = olmlib.MEGOLM_ALGORITHM; + exportedSessions.push(sess); + }); + }); + return exportedSessions; + }); + } + /** + * Import a list of room keys previously exported by exportRoomKeys + * + * @param keys - a list of session export objects + * @returns a promise which resolves once the keys have been imported + */ + importRoomKeys(keys, opts = {}) { + let successes = 0; + let failures = 0; + const total = keys.length; + function updateProgress() { + var _a; + (_a = opts.progressCallback) === null || _a === void 0 ? void 0 : _a.call(opts, { + stage: "load_keys", + successes, + failures, + total, + }); + } + return Promise.all(keys.map((key) => { + if (!key.room_id || !key.algorithm) { + logger_1.logger.warn("ignoring room key entry with missing fields", key); + failures++; + if (opts.progressCallback) { + updateProgress(); + } + return null; + } + const alg = this.getRoomDecryptor(key.room_id, key.algorithm); + return alg.importRoomKey(key, opts).finally(() => { + successes++; + if (opts.progressCallback) { + updateProgress(); + } + }); + })).then(); + } + /** + * Counts the number of end to end session keys that are waiting to be backed up + * @returns Promise which resolves to the number of sessions requiring backup + */ + countSessionsNeedingBackup() { + return this.backupManager.countSessionsNeedingBackup(); + } + /** + * Perform any background tasks that can be done before a message is ready to + * send, in order to speed up sending of the message. + * + * @param room - the room the event is in + */ + prepareToEncrypt(room) { + const alg = this.roomEncryptors.get(room.roomId); + if (alg) { + alg.prepareToEncrypt(room); + } + } + /** + * Encrypt an event according to the configuration of the room. + * + * @param event - event to be sent + * + * @param room - destination room. + * + * @returns Promise which resolves when the event has been + * encrypted, or null if nothing was needed + */ + encryptEvent(event, room) { + return __awaiter(this, void 0, void 0, function* () { + const roomId = event.getRoomId(); + const alg = this.roomEncryptors.get(roomId); + if (!alg) { + // MatrixClient has already checked that this room should be encrypted, + // so this is an unexpected situation. + throw new Error("Room " + + roomId + + " was previously configured to use encryption, but is " + + "no longer. Perhaps the homeserver is hiding the " + + "configuration event."); + } + // wait for all the room devices to be loaded + yield this.trackRoomDevicesImpl(room); + let content = event.getContent(); + // If event has an m.relates_to then we need + // to put this on the wrapping event instead + const mRelatesTo = content["m.relates_to"]; + if (mRelatesTo) { + // Clone content here so we don't remove `m.relates_to` from the local-echo + content = Object.assign({}, content); + delete content["m.relates_to"]; + } + // Treat element's performance metrics the same as `m.relates_to` (when present) + const elementPerfMetrics = content["io.element.performance_metrics"]; + if (elementPerfMetrics) { + content = Object.assign({}, content); + delete content["io.element.performance_metrics"]; + } + const encryptedContent = (yield alg.encryptMessage(room, event.getType(), content)); + if (mRelatesTo) { + encryptedContent["m.relates_to"] = mRelatesTo; + } + if (elementPerfMetrics) { + encryptedContent["io.element.performance_metrics"] = elementPerfMetrics; + } + event.makeEncrypted("m.room.encrypted", encryptedContent, this.olmDevice.deviceCurve25519Key, this.olmDevice.deviceEd25519Key); + }); + } + /** + * Decrypt a received event + * + * + * @returns resolves once we have + * finished decrypting. Rejects with an `algorithms.DecryptionError` if there + * is a problem decrypting the event. + */ + decryptEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (event.isRedacted()) { + // Try to decrypt the redaction event, to support encrypted + // redaction reasons. If we can't decrypt, just fall back to using + // the original redacted_because. + const redactionEvent = new event_2.MatrixEvent(Object.assign({ room_id: event.getRoomId() }, event.getUnsigned().redacted_because)); + let redactedBecause = event.getUnsigned().redacted_because; + if (redactionEvent.isEncrypted()) { + try { + const decryptedEvent = yield this.decryptEvent(redactionEvent); + redactedBecause = decryptedEvent.clearEvent; + } + catch (e) { + logger_1.logger.warn("Decryption of redaction failed. Falling back to unencrypted event.", e); + } + } + return { + clearEvent: { + room_id: event.getRoomId(), + type: "m.room.message", + content: {}, + unsigned: { + redacted_because: redactedBecause, + }, + }, + }; + } + else { + const content = event.getWireContent(); + const alg = this.getRoomDecryptor(event.getRoomId(), content.algorithm); + return alg.decryptEvent(event); + } + }); + } + /** + * Handle the notification from /sync or /keys/changes that device lists have + * been changed. + * + * @param syncData - Object containing sync tokens associated with this sync + * @param syncDeviceLists - device_lists field from /sync, or response from + * /keys/changes + */ + handleDeviceListChanges(syncData, syncDeviceLists) { + return __awaiter(this, void 0, void 0, function* () { + // Initial syncs don't have device change lists. We'll either get the complete list + // of changes for the interval or will have invalidated everything in willProcessSync + if (!syncData.oldSyncToken) + return; + // Here, we're relying on the fact that we only ever save the sync data after + // sucessfully saving the device list data, so we're guaranteed that the device + // list store is at least as fresh as the sync token from the sync store, ie. + // any device changes received in sync tokens prior to the 'next' token here + // have been processed and are reflected in the current device list. + // If we didn't make this assumption, we'd have to use the /keys/changes API + // to get key changes between the sync token in the device list and the 'old' + // sync token used here to make sure we didn't miss any. + yield this.evalDeviceListChanges(syncDeviceLists); + }); + } + /** + * Send a request for some room keys, if we have not already done so + * + * @param resend - whether to resend the key request if there is + * already one + * + * @returns a promise that resolves when the key request is queued + */ + requestRoomKey(requestBody, recipients, resend = false) { + return this.outgoingRoomKeyRequestManager + .queueRoomKeyRequest(requestBody, recipients, resend) + .then(() => { + if (this.sendKeyRequestsImmediately) { + this.outgoingRoomKeyRequestManager.sendQueuedRequests(); + } + }) + .catch((e) => { + // this normally means we couldn't talk to the store + logger_1.logger.error("Error requesting key for event", e); + }); + } + /** + * Cancel any earlier room key request + * + * @param requestBody - parameters to match for cancellation + */ + cancelRoomKeyRequest(requestBody) { + this.outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody).catch((e) => { + logger_1.logger.warn("Error clearing pending room key requests", e); + }); + } + /** + * Re-send any outgoing key requests, eg after verification + * @returns + */ + cancelAndResendAllOutgoingKeyRequests() { + return __awaiter(this, void 0, void 0, function* () { + yield this.outgoingRoomKeyRequestManager.cancelAndResendAllOutgoingRequests(); + }); + } + /** + * handle an m.room.encryption event + * + * @param room - in which the event was received + * @param event - encryption event to be processed + */ + onCryptoEvent(room, event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + yield this.setRoomEncryptionImpl(room, content); + }); + } + /** + * Called before the result of a sync is processed + * + * @param syncData - the data from the 'MatrixClient.sync' event + */ + onSyncWillProcess(syncData) { + return __awaiter(this, void 0, void 0, function* () { + if (!syncData.oldSyncToken) { + // If there is no old sync token, we start all our tracking from + // scratch, so mark everything as untracked. onCryptoEvent will + // be called for all e2e rooms during the processing of the sync, + // at which point we'll start tracking all the users of that room. + logger_1.logger.log("Initial sync performed - resetting device tracking state"); + this.deviceList.stopTrackingAllDeviceLists(); + // we always track our own device list (for key backups etc) + this.deviceList.startTrackingDeviceList(this.userId); + this.roomDeviceTrackingState = {}; + } + this.sendKeyRequestsImmediately = false; + }); + } + /** + * handle the completion of a /sync + * + * This is called after the processing of each successful /sync response. + * It is an opportunity to do a batch process on the information received. + * + * @param syncData - the data from the 'MatrixClient.sync' event + */ + onSyncCompleted(syncData) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + this.deviceList.setSyncToken((_a = syncData.nextSyncToken) !== null && _a !== void 0 ? _a : null); + this.deviceList.saveIfDirty(); + // we always track our own device list (for key backups etc) + this.deviceList.startTrackingDeviceList(this.userId); + this.deviceList.refreshOutdatedDeviceLists(); + // we don't start uploading one-time keys until we've caught up with + // to-device messages, to help us avoid throwing away one-time-keys that we + // are about to receive messages for + // (https://github.com/vector-im/element-web/issues/2782). + if (!syncData.catchingUp) { + this.maybeUploadOneTimeKeys(); + this.processReceivedRoomKeyRequests(); + // likewise don't start requesting keys until we've caught up + // on to_device messages, otherwise we'll request keys that we're + // just about to get. + this.outgoingRoomKeyRequestManager.sendQueuedRequests(); + // Sync has finished so send key requests straight away. + this.sendKeyRequestsImmediately = true; + } + }); + } + /** + * Trigger the appropriate invalidations and removes for a given + * device list + * + * @param deviceLists - device_lists field from /sync, or response from + * /keys/changes + */ + evalDeviceListChanges(deviceLists) { + return __awaiter(this, void 0, void 0, function* () { + if (Array.isArray(deviceLists === null || deviceLists === void 0 ? void 0 : deviceLists.changed)) { + deviceLists.changed.forEach((u) => { + this.deviceList.invalidateUserDeviceList(u); + }); + } + if (Array.isArray(deviceLists === null || deviceLists === void 0 ? void 0 : deviceLists.left) && deviceLists.left.length) { + // Check we really don't share any rooms with these users + // any more: the server isn't required to give us the + // exact correct set. + const e2eUserIds = new Set(yield this.getTrackedE2eUsers()); + deviceLists.left.forEach((u) => { + if (!e2eUserIds.has(u)) { + this.deviceList.stopTrackingDeviceList(u); + } + }); + } + }); + } + /** + * Get a list of all the IDs of users we share an e2e room with + * for which we are tracking devices already + * + * @returns List of user IDs + */ + getTrackedE2eUsers() { + return __awaiter(this, void 0, void 0, function* () { + const e2eUserIds = []; + for (const room of this.getTrackedE2eRooms()) { + const members = yield room.getEncryptionTargetMembers(); + for (const member of members) { + e2eUserIds.push(member.userId); + } + } + return e2eUserIds; + }); + } + /** + * Get a list of the e2e-enabled rooms we are members of, + * and for which we are already tracking the devices + * + * @returns + */ + getTrackedE2eRooms() { + return this.clientStore.getRooms().filter((room) => { + // check for rooms with encryption enabled + const alg = this.roomEncryptors.get(room.roomId); + if (!alg) { + return false; + } + if (!this.roomDeviceTrackingState[room.roomId]) { + return false; + } + // ignore any rooms which we have left + const myMembership = room.getMyMembership(); + return myMembership === "join" || myMembership === "invite"; + }); + } + /** + * Encrypts and sends a given object via Olm to-device messages to a given + * set of devices. + * @param userDeviceInfoArr - the devices to send to + * @param payload - fields to include in the encrypted payload + * @returns Promise which + * resolves once the message has been encrypted and sent to the given + * userDeviceMap, and returns the `{ contentMap, deviceInfoByDeviceId }` + * of the successfully sent messages. + */ + encryptAndSendToDevices(userDeviceInfoArr, payload) { + return __awaiter(this, void 0, void 0, function* () { + const toDeviceBatch = { + eventType: event_1.EventType.RoomMessageEncrypted, + batch: [], + }; + try { + yield Promise.all(userDeviceInfoArr.map(({ userId, deviceInfo }) => __awaiter(this, void 0, void 0, function* () { + const deviceId = deviceInfo.deviceId; + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + toDeviceBatch.batch.push({ + userId, + deviceId, + payload: encryptedContent, + }); + yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, new Map([[userId, [deviceInfo]]])); + yield olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, userId, deviceInfo, payload); + }))); + // prune out any devices that encryptMessageForDevice could not encrypt for, + // in which case it will have just not added anything to the ciphertext object. + // There's no point sending messages to devices if we couldn't encrypt to them, + // since that's effectively a blank message. + toDeviceBatch.batch = toDeviceBatch.batch.filter((msg) => { + if (Object.keys(msg.payload.ciphertext).length > 0) { + return true; + } + else { + logger_1.logger.log(`No ciphertext for device ${msg.userId}:${msg.deviceId}: pruning`); + return false; + } + }); + try { + yield this.baseApis.queueToDevice(toDeviceBatch); + } + catch (e) { + logger_1.logger.error("sendToDevice failed", e); + throw e; + } + } + catch (e) { + logger_1.logger.error("encryptAndSendToDevices promises failed", e); + throw e; + } + }); + } + preprocessToDeviceMessages(events) { + return __awaiter(this, void 0, void 0, function* () { + // all we do here is filter out encrypted to-device messages with the wrong algorithm. Decryption + // happens later in decryptEvent, via the EventMapper + return events.filter((toDevice) => { + var _a; + if (toDevice.type === event_1.EventType.RoomMessageEncrypted && + !["m.olm.v1.curve25519-aes-sha2"].includes((_a = toDevice.content) === null || _a === void 0 ? void 0 : _a.algorithm)) { + logger_1.logger.log("Ignoring invalid encrypted to-device event from " + toDevice.sender); + return false; + } + return true; + }); + }); + } + preprocessOneTimeKeyCounts(oneTimeKeysCounts) { + const currentCount = oneTimeKeysCounts.get("signed_curve25519") || 0; + this.updateOneTimeKeyCount(currentCount); + return Promise.resolve(); + } + preprocessUnusedFallbackKeys(unusedFallbackKeys) { + this.setNeedsNewFallback(!unusedFallbackKeys.has("signed_curve25519")); + return Promise.resolve(); + } + /** + * Handle a key event + * + * @internal + * @param event - key event + */ + onRoomKeyEvent(event) { + const content = event.getContent(); + if (!content.room_id || !content.algorithm) { + logger_1.logger.error("key event is missing fields"); + return; + } + if (!this.backupManager.checkedForBackup) { + // don't bother awaiting on this - the important thing is that we retry if we + // haven't managed to check before + this.backupManager.checkAndStart(); + } + const alg = this.getRoomDecryptor(content.room_id, content.algorithm); + alg.onRoomKeyEvent(event); + } + /** + * Handle a key withheld event + * + * @internal + * @param event - key withheld event + */ + onRoomKeyWithheldEvent(event) { + const content = event.getContent(); + if ((content.code !== "m.no_olm" && (!content.room_id || !content.session_id)) || + !content.algorithm || + !content.sender_key) { + logger_1.logger.error("key withheld event is missing fields"); + return; + } + logger_1.logger.info(`Got room key withheld event from ${event.getSender()} ` + + `for ${content.algorithm} session ${content.sender_key}|${content.session_id} ` + + `in room ${content.room_id} with code ${content.code} (${content.reason})`); + const alg = this.getRoomDecryptor(content.room_id, content.algorithm); + if (alg.onRoomKeyWithheldEvent) { + alg.onRoomKeyWithheldEvent(event); + } + if (!content.room_id) { + // retry decryption for all events sent by the sender_key. This will + // update the events to show a message indicating that the olm session was + // wedged. + const roomDecryptors = this.getRoomDecryptors(content.algorithm); + for (const decryptor of roomDecryptors) { + decryptor.retryDecryptionFromSender(content.sender_key); + } + } + } + /** + * Handle a general key verification event. + * + * @internal + * @param event - verification start event + */ + onKeyVerificationMessage(event) { + if (!ToDeviceChannel_1.ToDeviceChannel.validateEvent(event, this.baseApis)) { + return; + } + const createRequest = (event) => { + if (!ToDeviceChannel_1.ToDeviceChannel.canCreateRequest(ToDeviceChannel_1.ToDeviceChannel.getEventType(event))) { + return; + } + const content = event.getContent(); + const deviceId = content && content.from_device; + if (!deviceId) { + return; + } + const userId = event.getSender(); + const channel = new ToDeviceChannel_1.ToDeviceChannel(this.baseApis, userId, [deviceId]); + return new VerificationRequest_1.VerificationRequest(channel, this.verificationMethods, this.baseApis); + }; + this.handleVerificationEvent(event, this.toDeviceVerificationRequests, createRequest); + } + handleVerificationEvent(event, requestsMap, createRequest, isLiveEvent = true) { + return __awaiter(this, void 0, void 0, function* () { + // Wait for event to get its final ID with pendingEventOrdering: "chronological", since DM channels depend on it. + if (event.isSending() && event.status != event_2.EventStatus.SENT) { + let eventIdListener; + let statusListener; + try { + yield new Promise((resolve, reject) => { + eventIdListener = resolve; + statusListener = () => { + if (event.status == event_2.EventStatus.CANCELLED) { + reject(new Error("Event status set to CANCELLED.")); + } + }; + event.once(event_2.MatrixEventEvent.LocalEventIdReplaced, eventIdListener); + event.on(event_2.MatrixEventEvent.Status, statusListener); + }); + } + catch (err) { + logger_1.logger.error("error while waiting for the verification event to be sent: ", err); + return; + } + finally { + event.removeListener(event_2.MatrixEventEvent.LocalEventIdReplaced, eventIdListener); + event.removeListener(event_2.MatrixEventEvent.Status, statusListener); + } + } + let request = requestsMap.getRequest(event); + let isNewRequest = false; + if (!request) { + request = createRequest(event); + // a request could not be made from this event, so ignore event + if (!request) { + logger_1.logger.log(`Crypto: could not find VerificationRequest for ` + + `${event.getType()}, and could not create one, so ignoring.`); + return; + } + isNewRequest = true; + requestsMap.setRequest(event, request); + } + event.setVerificationRequest(request); + try { + yield request.channel.handleEvent(event, request, isLiveEvent); + } + catch (err) { + logger_1.logger.error("error while handling verification event", err); + } + const shouldEmit = isNewRequest && + !request.initiatedByMe && + !request.invalid && // check it has enough events to pass the UNSENT stage + !request.observeOnly; + if (shouldEmit) { + this.baseApis.emit(CryptoEvent.VerificationRequest, request); + } + }); + } + /** + * Handle a toDevice event that couldn't be decrypted + * + * @internal + * @param event - undecryptable event + */ + onToDeviceBadEncrypted(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getWireContent(); + const sender = event.getSender(); + const algorithm = content.algorithm; + const deviceKey = content.sender_key; + this.baseApis.emit(client_1.ClientEvent.UndecryptableToDeviceEvent, event); + // retry decryption for all events sent by the sender_key. This will + // update the events to show a message indicating that the olm session was + // wedged. + const retryDecryption = () => { + const roomDecryptors = this.getRoomDecryptors(olmlib.MEGOLM_ALGORITHM); + for (const decryptor of roomDecryptors) { + decryptor.retryDecryptionFromSender(deviceKey); + } + }; + if (sender === undefined || deviceKey === undefined || deviceKey === undefined) { + return; + } + // check when we last forced a new session with this device: if we've already done so + // recently, don't do it again. + const lastNewSessionDevices = this.lastNewSessionForced.getOrCreate(sender); + const lastNewSessionForced = lastNewSessionDevices.getOrCreate(deviceKey); + if (lastNewSessionForced + MIN_FORCE_SESSION_INTERVAL_MS > Date.now()) { + logger_1.logger.debug("New session already forced with device " + + sender + + ":" + + deviceKey + + " at " + + lastNewSessionForced + + ": not forcing another"); + yield this.olmDevice.recordSessionProblem(deviceKey, "wedged", true); + retryDecryption(); + return; + } + // establish a new olm session with this device since we're failing to decrypt messages + // on a current session. + // Note that an undecryptable message from another device could easily be spoofed - + // is there anything we can do to mitigate this? + let device = this.deviceList.getDeviceByIdentityKey(algorithm, deviceKey); + if (!device) { + // if we don't know about the device, fetch the user's devices again + // and retry before giving up + yield this.downloadKeys([sender], false); + device = this.deviceList.getDeviceByIdentityKey(algorithm, deviceKey); + if (!device) { + logger_1.logger.info("Couldn't find device for identity key " + deviceKey + ": not re-establishing session"); + yield this.olmDevice.recordSessionProblem(deviceKey, "wedged", false); + retryDecryption(); + return; + } + } + const devicesByUser = new Map([[sender, [device]]]); + yield olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, true); + lastNewSessionDevices.set(deviceKey, Date.now()); + // Now send a blank message on that session so the other side knows about it. + // (The keyshare request is sent in the clear so that won't do) + // We send this first such that, as long as the toDevice messages arrive in the + // same order we sent them, the other end will get this first, set up the new session, + // then get the keyshare request and send the key over this new session (because it + // is the session it has most recently received a message on). + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [event_1.ToDeviceMessageId]: (0, uuid_1.v4)(), + }; + yield olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, sender, device, { type: "m.dummy" }); + yield this.olmDevice.recordSessionProblem(deviceKey, "wedged", true); + retryDecryption(); + yield this.baseApis.sendToDevice("m.room.encrypted", new Map([[sender, new Map([[device.deviceId, encryptedContent]])]])); + // Most of the time this probably won't be necessary since we'll have queued up a key request when + // we failed to decrypt the message and will be waiting a bit for the key to arrive before sending + // it. This won't always be the case though so we need to re-send any that have already been sent + // to avoid races. + const requestsToResend = yield this.outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest(sender, device.deviceId); + for (const keyReq of requestsToResend) { + this.requestRoomKey(keyReq.requestBody, keyReq.recipients, true); + } + }); + } + /** + * Handle a change in the membership state of a member of a room + * + * @internal + * @param event - event causing the change + * @param member - user whose membership changed + * @param oldMembership - previous membership + */ + onRoomMembership(event, member, oldMembership) { + // this event handler is registered on the *client* (as opposed to the room + // member itself), which means it is only called on changes to the *live* + // membership state (ie, it is not called when we back-paginate, nor when + // we load the state in the initialsync). + // + // Further, it is automatically registered and called when new members + // arrive in the room. + var _a; + const roomId = member.roomId; + const alg = this.roomEncryptors.get(roomId); + if (!alg) { + // not encrypting in this room + return; + } + // only mark users in this room as tracked if we already started tracking in this room + // this way we don't start device queries after sync on behalf of this room which we won't use + // the result of anyway, as we'll need to do a query again once all the members are fetched + // by calling _trackRoomDevices + if (roomId in this.roomDeviceTrackingState) { + if (member.membership == "join") { + logger_1.logger.log("Join event for " + member.userId + " in " + roomId); + // make sure we are tracking the deviceList for this user + this.deviceList.startTrackingDeviceList(member.userId); + } + else if (member.membership == "invite" && + ((_a = this.clientStore.getRoom(roomId)) === null || _a === void 0 ? void 0 : _a.shouldEncryptForInvitedMembers())) { + logger_1.logger.log("Invite event for " + member.userId + " in " + roomId); + this.deviceList.startTrackingDeviceList(member.userId); + } + } + alg.onRoomMembership(event, member, oldMembership); + } + /** + * Called when we get an m.room_key_request event. + * + * @internal + * @param event - key request event + */ + onRoomKeyRequestEvent(event) { + const content = event.getContent(); + if (content.action === "request") { + // Queue it up for now, because they tend to arrive before the room state + // events at initial sync, and we want to see if we know anything about the + // room before passing them on to the app. + const req = new IncomingRoomKeyRequest(event); + this.receivedRoomKeyRequests.push(req); + } + else if (content.action === "request_cancellation") { + const req = new IncomingRoomKeyRequestCancellation(event); + this.receivedRoomKeyRequestCancellations.push(req); + } + } + /** + * Process any m.room_key_request events which were queued up during the + * current sync. + * + * @internal + */ + processReceivedRoomKeyRequests() { + return __awaiter(this, void 0, void 0, function* () { + if (this.processingRoomKeyRequests) { + // we're still processing last time's requests; keep queuing new ones + // up for now. + return; + } + this.processingRoomKeyRequests = true; + try { + // we need to grab and clear the queues in the synchronous bit of this method, + // so that we don't end up racing with the next /sync. + const requests = this.receivedRoomKeyRequests; + this.receivedRoomKeyRequests = []; + const cancellations = this.receivedRoomKeyRequestCancellations; + this.receivedRoomKeyRequestCancellations = []; + // Process all of the requests, *then* all of the cancellations. + // + // This makes sure that if we get a request and its cancellation in the + // same /sync result, then we process the request before the + // cancellation (and end up with a cancelled request), rather than the + // cancellation before the request (and end up with an outstanding + // request which should have been cancelled.) + yield Promise.all(requests.map((req) => this.processReceivedRoomKeyRequest(req))); + yield Promise.all(cancellations.map((cancellation) => this.processReceivedRoomKeyRequestCancellation(cancellation))); + } + catch (e) { + logger_1.logger.error(`Error processing room key requsts: ${e}`); + } + finally { + this.processingRoomKeyRequests = false; + } + }); + } + /** + * Helper for processReceivedRoomKeyRequests + * + */ + processReceivedRoomKeyRequest(req) { + return __awaiter(this, void 0, void 0, function* () { + const userId = req.userId; + const deviceId = req.deviceId; + const body = req.requestBody; + const roomId = body.room_id; + const alg = body.algorithm; + logger_1.logger.log(`m.room_key_request from ${userId}:${deviceId}` + + ` for ${roomId} / ${body.session_id} (id ${req.requestId})`); + if (userId !== this.userId) { + if (!this.roomEncryptors.get(roomId)) { + logger_1.logger.debug(`room key request for unencrypted room ${roomId}`); + return; + } + const encryptor = this.roomEncryptors.get(roomId); + const device = this.deviceList.getStoredDevice(userId, deviceId); + if (!device) { + logger_1.logger.debug(`Ignoring keyshare for unknown device ${userId}:${deviceId}`); + return; + } + try { + yield encryptor.reshareKeyWithDevice(body.sender_key, body.session_id, userId, device); + } + catch (e) { + logger_1.logger.warn("Failed to re-share keys for session " + + body.session_id + + " with device " + + userId + + ":" + + device.deviceId, e); + } + return; + } + if (deviceId === this.deviceId) { + // We'll always get these because we send room key requests to + // '*' (ie. 'all devices') which includes the sending device, + // so ignore requests from ourself because apart from it being + // very silly, it won't work because an Olm session cannot send + // messages to itself. + // The log here is probably superfluous since we know this will + // always happen, but let's log anyway for now just in case it + // causes issues. + logger_1.logger.log("Ignoring room key request from ourselves"); + return; + } + // todo: should we queue up requests we don't yet have keys for, + // in case they turn up later? + // if we don't have a decryptor for this room/alg, we don't have + // the keys for the requested events, and can drop the requests. + if (!this.roomDecryptors.has(roomId)) { + logger_1.logger.log(`room key request for unencrypted room ${roomId}`); + return; + } + const decryptor = this.roomDecryptors.get(roomId).get(alg); + if (!decryptor) { + logger_1.logger.log(`room key request for unknown alg ${alg} in room ${roomId}`); + return; + } + if (!(yield decryptor.hasKeysForKeyRequest(req))) { + logger_1.logger.log(`room key request for unknown session ${roomId} / ` + body.session_id); + return; + } + req.share = () => { + decryptor.shareKeysWithDevice(req); + }; + // if the device is verified already, share the keys + if (this.checkDeviceTrust(userId, deviceId).isVerified()) { + logger_1.logger.log("device is already verified: sharing keys"); + req.share(); + return; + } + this.emit(CryptoEvent.RoomKeyRequest, req); + }); + } + /** + * Helper for processReceivedRoomKeyRequests + * + */ + processReceivedRoomKeyRequestCancellation(cancellation) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`m.room_key_request cancellation for ${cancellation.userId}:` + + `${cancellation.deviceId} (id ${cancellation.requestId})`); + // we should probably only notify the app of cancellations we told it + // about, but we don't currently have a record of that, so we just pass + // everything through. + this.emit(CryptoEvent.RoomKeyRequestCancellation, cancellation); + }); + } + /** + * Get a decryptor for a given room and algorithm. + * + * If we already have a decryptor for the given room and algorithm, return + * it. Otherwise try to instantiate it. + * + * @internal + * + * @param roomId - room id for decryptor. If undefined, a temporary + * decryptor is instantiated. + * + * @param algorithm - crypto algorithm + * + * @throws {@link DecryptionError} if the algorithm is unknown + */ + getRoomDecryptor(roomId, algorithm) { + let decryptors; + let alg; + if (roomId) { + decryptors = this.roomDecryptors.get(roomId); + if (!decryptors) { + decryptors = new Map(); + this.roomDecryptors.set(roomId, decryptors); + } + alg = decryptors.get(algorithm); + if (alg) { + return alg; + } + } + const AlgClass = algorithms.DECRYPTION_CLASSES.get(algorithm); + if (!AlgClass) { + throw new algorithms.DecryptionError("UNKNOWN_ENCRYPTION_ALGORITHM", 'Unknown encryption algorithm "' + algorithm + '".'); + } + alg = new AlgClass({ + userId: this.userId, + crypto: this, + olmDevice: this.olmDevice, + baseApis: this.baseApis, + roomId: roomId !== null && roomId !== void 0 ? roomId : undefined, + }); + if (decryptors) { + decryptors.set(algorithm, alg); + } + return alg; + } + /** + * Get all the room decryptors for a given encryption algorithm. + * + * @param algorithm - The encryption algorithm + * + * @returns An array of room decryptors + */ + getRoomDecryptors(algorithm) { + const decryptors = []; + for (const d of this.roomDecryptors.values()) { + if (d.has(algorithm)) { + decryptors.push(d.get(algorithm)); + } + } + return decryptors; + } + /** + * sign the given object with our ed25519 key + * + * @param obj - Object to which we will add a 'signatures' property + */ + signObject(obj) { + return __awaiter(this, void 0, void 0, function* () { + const sigs = new Map(Object.entries(obj.signatures || {})); + const unsigned = obj.unsigned; + delete obj.signatures; + delete obj.unsigned; + const userSignatures = sigs.get(this.userId) || {}; + sigs.set(this.userId, userSignatures); + userSignatures["ed25519:" + this.deviceId] = yield this.olmDevice.sign(another_json_1.default.stringify(obj)); + obj.signatures = (0, utils_1.recursiveMapToObject)(sigs); + if (unsigned !== undefined) + obj.unsigned = unsigned; + }); + } +} +exports.Crypto = Crypto; +/** + * Fix up the backup key, that may be in the wrong format due to a bug in a + * migration step. Some backup keys were stored as a comma-separated list of + * integers, rather than a base64-encoded byte array. If this function is + * passed a string that looks like a list of integers rather than a base64 + * string, it will attempt to convert it to the right format. + * + * @param key - the key to check + * @returns If the key is in the wrong format, then the fixed + * key will be returned. Otherwise null will be returned. + * + */ +function fixBackupKey(key) { + if (typeof key !== "string" || key.indexOf(",") < 0) { + return null; + } + const fixedKey = Uint8Array.from(key.split(","), (x) => parseInt(x)); + return olmlib.encodeBase64(fixedKey); +} +exports.fixBackupKey = fixBackupKey; +/** + * Represents a received m.room_key_request event + */ +class IncomingRoomKeyRequest { + constructor(event) { + const content = event.getContent(); + this.userId = event.getSender(); + this.deviceId = content.requesting_device_id; + this.requestId = content.request_id; + this.requestBody = content.body || {}; + this.share = () => { + throw new Error("don't know how to share keys for this request yet"); + }; + } +} +exports.IncomingRoomKeyRequest = IncomingRoomKeyRequest; +/** + * Represents a received m.room_key_request cancellation + */ +class IncomingRoomKeyRequestCancellation { + constructor(event) { + const content = event.getContent(); + this.userId = event.getSender(); + this.deviceId = content.requesting_device_id; + this.requestId = content.request_id; + } +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"../@types/event":306,"../ReEmitter":317,"../client":321,"../errors":359,"../logger":374,"../models/event":383,"../models/room":392,"../models/room-member":389,"../models/room-state":390,"../models/typed-event-emitter":395,"../utils":416,"./CrossSigning":324,"./DeviceList":325,"./EncryptionSetup":326,"./OlmDevice":327,"./OutgoingRoomKeyRequestManager":328,"./SecretStorage":330,"./aes":331,"./algorithms":333,"./backup":337,"./dehydration":339,"./deviceinfo":340,"./key_passphrase":342,"./olmlib":343,"./recoverykey":344,"./store/indexeddb-crypto-store":346,"./verification/IllegalMethod":351,"./verification/QRCode":352,"./verification/SAS":353,"./verification/request/InRoomChannel":355,"./verification/request/ToDeviceChannel":356,"./verification/request/VerificationRequest":357,"another-json":1,"buffer":68,"uuid":287}],342:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.deriveKey = exports.keyFromPassphrase = exports.keyFromAuthData = void 0; +const randomstring_1 = require("../randomstring"); +const crypto_1 = require("./crypto"); +const DEFAULT_ITERATIONS = 500000; +const DEFAULT_BITSIZE = 256; +function keyFromAuthData(authData, password) { + if (!global.Olm) { + throw new Error("Olm is not available"); + } + if (!authData.private_key_salt || !authData.private_key_iterations) { + throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase"); + } + return deriveKey(password, authData.private_key_salt, authData.private_key_iterations, authData.private_key_bits || DEFAULT_BITSIZE); +} +exports.keyFromAuthData = keyFromAuthData; +function keyFromPassphrase(password) { + return __awaiter(this, void 0, void 0, function* () { + if (!global.Olm) { + throw new Error("Olm is not available"); + } + const salt = (0, randomstring_1.randomString)(32); + const key = yield deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE); + return { key, salt, iterations: DEFAULT_ITERATIONS }; + }); +} +exports.keyFromPassphrase = keyFromPassphrase; +function deriveKey(password, salt, iterations, numBits = DEFAULT_BITSIZE) { + return __awaiter(this, void 0, void 0, function* () { + if (!crypto_1.subtleCrypto || !crypto_1.TextEncoder) { + throw new Error("Password-based backup is not available on this platform"); + } + const key = yield crypto_1.subtleCrypto.importKey("raw", new crypto_1.TextEncoder().encode(password), { name: "PBKDF2" }, false, [ + "deriveBits", + ]); + const keybits = yield crypto_1.subtleCrypto.deriveBits({ + name: "PBKDF2", + salt: new crypto_1.TextEncoder().encode(salt), + iterations: iterations, + hash: "SHA-512", + }, key, numBits); + return new Uint8Array(keybits); + }); +} +exports.deriveKey = deriveKey; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../randomstring":398,"./crypto":338}],343:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.decodeBase64 = exports.encodeUnpaddedBase64 = exports.encodeBase64 = exports.isOlmEncrypted = exports.pkVerify = exports.pkSign = exports.verifySignature = exports.ensureOlmSessionsForDevices = exports.getExistingOlmSessions = exports.encryptMessageForDevice = exports.MEGOLM_BACKUP_ALGORITHM = exports.MEGOLM_ALGORITHM = exports.OLM_ALGORITHM = void 0; +/** + * Utilities common to olm encryption algorithms + */ +const another_json_1 = __importDefault(require("another-json")); +const logger_1 = require("../logger"); +const event_1 = require("../@types/event"); +const utils_1 = require("../utils"); +var Algorithm; +(function (Algorithm) { + Algorithm["Olm"] = "m.olm.v1.curve25519-aes-sha2"; + Algorithm["Megolm"] = "m.megolm.v1.aes-sha2"; + Algorithm["MegolmBackup"] = "m.megolm_backup.v1.curve25519-aes-sha2"; +})(Algorithm || (Algorithm = {})); +/** + * matrix algorithm tag for olm + */ +exports.OLM_ALGORITHM = Algorithm.Olm; +/** + * matrix algorithm tag for megolm + */ +exports.MEGOLM_ALGORITHM = Algorithm.Megolm; +/** + * matrix algorithm tag for megolm backups + */ +exports.MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup; +/** + * Encrypt an event payload for an Olm device + * + * @param resultsObject - The `ciphertext` property + * of the m.room.encrypted event to which to add our result + * + * @param olmDevice - olm.js wrapper + * @param payloadFields - fields to include in the encrypted payload + * + * Returns a promise which resolves (to undefined) when the payload + * has been encrypted into `resultsObject` + */ +function encryptMessageForDevice(resultsObject, ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice, payloadFields) { + return __awaiter(this, void 0, void 0, function* () { + const deviceKey = recipientDevice.getIdentityKey(); + const sessionId = yield olmDevice.getSessionIdForDevice(deviceKey); + if (sessionId === null) { + // If we don't have a session for a device then + // we can't encrypt a message for it. + logger_1.logger.log(`[olmlib.encryptMessageForDevice] Unable to find Olm session for device ` + + `${recipientUserId}:${recipientDevice.deviceId}`); + return; + } + logger_1.logger.log(`[olmlib.encryptMessageForDevice] Using Olm session ${sessionId} for device ` + + `${recipientUserId}:${recipientDevice.deviceId}`); + const payload = Object.assign({ sender: ourUserId, + // TODO this appears to no longer be used whatsoever + sender_device: ourDeviceId, + // Include the Ed25519 key so that the recipient knows what + // device this message came from. + // We don't need to include the curve25519 key since the + // recipient will already know this from the olm headers. + // When combined with the device keys retrieved from the + // homeserver signed by the ed25519 key this proves that + // the curve25519 key and the ed25519 key are owned by + // the same device. + keys: { + ed25519: olmDevice.deviceEd25519Key, + }, + // include the recipient device details in the payload, + // to avoid unknown key attacks, per + // https://github.com/vector-im/vector-web/issues/2483 + recipient: recipientUserId, recipient_keys: { + ed25519: recipientDevice.getFingerprint(), + } }, payloadFields); + // TODO: technically, a bunch of that stuff only needs to be included for + // pre-key messages: after that, both sides know exactly which devices are + // involved in the session. If we're looking to reduce data transfer in the + // future, we could elide them for subsequent messages. + resultsObject[deviceKey] = yield olmDevice.encryptMessage(deviceKey, sessionId, JSON.stringify(payload)); + }); +} +exports.encryptMessageForDevice = encryptMessageForDevice; +/** + * Get the existing olm sessions for the given devices, and the devices that + * don't have olm sessions. + * + * + * + * @param devicesByUser - map from userid to list of devices to ensure sessions for + * + * @returns resolves to an array. The first element of the array is a + * a map of user IDs to arrays of deviceInfo, representing the devices that + * don't have established olm sessions. The second element of the array is + * a map from userId to deviceId to {@link OlmSessionResult} + */ +function getExistingOlmSessions(olmDevice, baseApis, devicesByUser) { + return __awaiter(this, void 0, void 0, function* () { + // map user Id → DeviceInfo[] + const devicesWithoutSession = new utils_1.MapWithDefault(() => []); + // map user Id → device Id → IExistingOlmSession + const sessions = new utils_1.MapWithDefault(() => new Map()); + const promises = []; + for (const [userId, devices] of Object.entries(devicesByUser)) { + for (const deviceInfo of devices) { + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + promises.push((() => __awaiter(this, void 0, void 0, function* () { + const sessionId = yield olmDevice.getSessionIdForDevice(key, true); + if (sessionId === null) { + devicesWithoutSession.getOrCreate(userId).push(deviceInfo); + } + else { + sessions.getOrCreate(userId).set(deviceId, { + device: deviceInfo, + sessionId: sessionId, + }); + } + }))()); + } + } + yield Promise.all(promises); + return [devicesWithoutSession, sessions]; + }); +} +exports.getExistingOlmSessions = getExistingOlmSessions; +/** + * Try to make sure we have established olm sessions for the given devices. + * + * @param devicesByUser - map from userid to list of devices to ensure sessions for + * + * @param force - If true, establish a new session even if one + * already exists. + * + * @param otkTimeout - The timeout in milliseconds when requesting + * one-time keys for establishing new olm sessions. + * + * @param failedServers - An array to fill with remote servers that + * failed to respond to one-time-key requests. + * + * @param log - A possibly customised log + * + * @returns resolves once the sessions are complete, to + * an Object mapping from userId to deviceId to + * {@link OlmSessionResult} + */ +function ensureOlmSessionsForDevices(olmDevice, baseApis, devicesByUser, force = false, otkTimeout, failedServers, log = logger_1.logger) { + var _a, _b, _c; + return __awaiter(this, void 0, void 0, function* () { + const devicesWithoutSession = [ + // [userId, deviceId], ... + ]; + // map user Id → device Id → IExistingOlmSession + const result = new Map(); + // map device key → resolve session fn + const resolveSession = new Map(); + // Mark all sessions this task intends to update as in progress. It is + // important to do this for all devices this task cares about in a single + // synchronous operation, as otherwise it is possible to have deadlocks + // where multiple tasks wait indefinitely on another task to update some set + // of common devices. + for (const devices of devicesByUser.values()) { + for (const deviceInfo of devices) { + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We don't start sessions with ourself, so there's no need to + // mark it in progress. + continue; + } + if (!olmDevice.sessionsInProgress[key]) { + // pre-emptively mark the session as in-progress to avoid race + // conditions. If we find that we already have a session, then + // we'll resolve + olmDevice.sessionsInProgress[key] = new Promise((resolve) => { + resolveSession.set(key, (v) => { + delete olmDevice.sessionsInProgress[key]; + resolve(v); + }); + }); + } + } + } + for (const [userId, devices] of devicesByUser) { + const resultDevices = new Map(); + result.set(userId, resultDevices); + for (const deviceInfo of devices) { + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We should never be trying to start a session with ourself. + // Apart from talking to yourself being the first sign of madness, + // olm sessions can't do this because they get confused when + // they get a message and see that the 'other side' has started a + // new chain when this side has an active sender chain. + // If you see this message being logged in the wild, we should find + // the thing that is trying to send Olm messages to itself and fix it. + log.info("Attempted to start session with ourself! Ignoring"); + // We must fill in the section in the return value though, as callers + // expect it to be there. + resultDevices.set(deviceId, { + device: deviceInfo, + sessionId: null, + }); + continue; + } + const forWhom = `for ${key} (${userId}:${deviceId})`; + const sessionId = yield olmDevice.getSessionIdForDevice(key, !!resolveSession.get(key), log); + const resolveSessionFn = resolveSession.get(key); + if (sessionId !== null && resolveSessionFn) { + // we found a session, but we had marked the session as + // in-progress, so resolve it now, which will unmark it and + // unblock anything that was waiting + resolveSessionFn(); + } + if (sessionId === null || force) { + if (force) { + log.info(`Forcing new Olm session ${forWhom}`); + } + else { + log.info(`Making new Olm session ${forWhom}`); + } + devicesWithoutSession.push([userId, deviceId]); + } + resultDevices.set(deviceId, { + device: deviceInfo, + sessionId: sessionId, + }); + } + } + if (devicesWithoutSession.length === 0) { + return result; + } + const oneTimeKeyAlgorithm = "signed_curve25519"; + let res; + let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`; + try { + log.debug(`Claiming ${taskDetail}`); + res = yield baseApis.claimOneTimeKeys(devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout); + log.debug(`Claimed ${taskDetail}`); + } + catch (e) { + for (const resolver of resolveSession.values()) { + resolver(); + } + log.log(`Failed to claim ${taskDetail}`, e, devicesWithoutSession); + throw e; + } + if (failedServers && "failures" in res) { + failedServers.push(...Object.keys(res.failures)); + } + const otkResult = res.one_time_keys || {}; + const promises = []; + for (const [userId, devices] of devicesByUser) { + const userRes = otkResult[userId] || {}; + for (const deviceInfo of devices) { + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We've already logged about this above. Skip here too + // otherwise we'll log saying there are no one-time keys + // which will be confusing. + continue; + } + if (((_b = (_a = result.get(userId)) === null || _a === void 0 ? void 0 : _a.get(deviceId)) === null || _b === void 0 ? void 0 : _b.sessionId) && !force) { + // we already have a result for this device + continue; + } + const deviceRes = userRes[deviceId] || {}; + let oneTimeKey = null; + for (const keyId in deviceRes) { + if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) { + oneTimeKey = deviceRes[keyId]; + } + } + if (!oneTimeKey) { + log.warn(`No one-time keys (alg=${oneTimeKeyAlgorithm}) ` + `for device ${userId}:${deviceId}`); + (_c = resolveSession.get(key)) === null || _c === void 0 ? void 0 : _c(); + continue; + } + promises.push(_verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo).then((sid) => { + var _a, _b; + (_a = resolveSession.get(key)) === null || _a === void 0 ? void 0 : _a(sid !== null && sid !== void 0 ? sid : undefined); + const deviceInfo = (_b = result.get(userId)) === null || _b === void 0 ? void 0 : _b.get(deviceId); + if (deviceInfo) + deviceInfo.sessionId = sid; + }, (e) => { + var _a; + (_a = resolveSession.get(key)) === null || _a === void 0 ? void 0 : _a(); + throw e; + })); + } + } + taskDetail = `Olm sessions for ${promises.length} devices`; + log.debug(`Starting ${taskDetail}`); + yield Promise.all(promises); + log.debug(`Started ${taskDetail}`); + return result; + }); +} +exports.ensureOlmSessionsForDevices = ensureOlmSessionsForDevices; +function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) { + return __awaiter(this, void 0, void 0, function* () { + const deviceId = deviceInfo.deviceId; + try { + yield verifySignature(olmDevice, oneTimeKey, userId, deviceId, deviceInfo.getFingerprint()); + } + catch (e) { + logger_1.logger.error("Unable to verify signature on one-time key for device " + userId + ":" + deviceId + ":", e); + return null; + } + let sid; + try { + sid = yield olmDevice.createOutboundSession(deviceInfo.getIdentityKey(), oneTimeKey.key); + } + catch (e) { + // possibly a bad key + logger_1.logger.error("Error starting olm session with device " + userId + ":" + deviceId + ": " + e); + return null; + } + logger_1.logger.log("Started new olm sessionid " + sid + " for device " + userId + ":" + deviceId); + return sid; + }); +} +/** + * Verify the signature on an object + * + * @param olmDevice - olm wrapper to use for verify op + * + * @param obj - object to check signature on. + * + * @param signingUserId - ID of the user whose signature should be checked + * + * @param signingDeviceId - ID of the device whose signature should be checked + * + * @param signingKey - base64-ed ed25519 public key + * + * Returns a promise which resolves (to undefined) if the the signature is good, + * or rejects with an Error if it is bad. + */ +function verifySignature(olmDevice, obj, signingUserId, signingDeviceId, signingKey) { + return __awaiter(this, void 0, void 0, function* () { + const signKeyId = "ed25519:" + signingDeviceId; + const signatures = obj.signatures || {}; + const userSigs = signatures[signingUserId] || {}; + const signature = userSigs[signKeyId]; + if (!signature) { + throw Error("No signature"); + } + // prepare the canonical json: remove unsigned and signatures, and stringify with anotherjson + const mangledObj = Object.assign({}, obj); + if ("unsigned" in mangledObj) { + delete mangledObj.unsigned; + } + delete mangledObj.signatures; + const json = another_json_1.default.stringify(mangledObj); + olmDevice.verifySignature(signingKey, json, signature); + }); +} +exports.verifySignature = verifySignature; +/** + * Sign a JSON object using public key cryptography + * @param obj - Object to sign. The object will be modified to include + * the new signature + * @param key - the signing object or the private key + * seed + * @param userId - The user ID who owns the signing key + * @param pubKey - The public key (ignored if key is a seed) + * @returns the signature for the object + */ +function pkSign(obj, key, userId, pubKey) { + let createdKey = false; + if (key instanceof Uint8Array) { + const keyObj = new global.Olm.PkSigning(); + pubKey = keyObj.init_with_seed(key); + key = keyObj; + createdKey = true; + } + const sigs = obj.signatures || {}; + delete obj.signatures; + const unsigned = obj.unsigned; + if (obj.unsigned) + delete obj.unsigned; + try { + const mysigs = sigs[userId] || {}; + sigs[userId] = mysigs; + return (mysigs["ed25519:" + pubKey] = key.sign(another_json_1.default.stringify(obj))); + } + finally { + obj.signatures = sigs; + if (unsigned) + obj.unsigned = unsigned; + if (createdKey) { + key.free(); + } + } +} +exports.pkSign = pkSign; +/** + * Verify a signed JSON object + * @param obj - Object to verify + * @param pubKey - The public key to use to verify + * @param userId - The user ID who signed the object + */ +function pkVerify(obj, pubKey, userId) { + const keyId = "ed25519:" + pubKey; + if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) { + throw new Error("No signature"); + } + const signature = obj.signatures[userId][keyId]; + const util = new global.Olm.Utility(); + const sigs = obj.signatures; + delete obj.signatures; + const unsigned = obj.unsigned; + if (obj.unsigned) + delete obj.unsigned; + try { + util.ed25519_verify(pubKey, another_json_1.default.stringify(obj), signature); + } + finally { + obj.signatures = sigs; + if (unsigned) + obj.unsigned = unsigned; + util.free(); + } +} +exports.pkVerify = pkVerify; +/** + * Check that an event was encrypted using olm. + */ +function isOlmEncrypted(event) { + if (!event.getSenderKey()) { + logger_1.logger.error("Event has no sender key (not encrypted?)"); + return false; + } + if (event.getWireType() !== event_1.EventType.RoomMessageEncrypted || + !["m.olm.v1.curve25519-aes-sha2"].includes(event.getWireContent().algorithm)) { + logger_1.logger.error("Event was not encrypted using an appropriate algorithm"); + return false; + } + return true; +} +exports.isOlmEncrypted = isOlmEncrypted; +/** + * Encode a typed array of uint8 as base64. + * @param uint8Array - The data to encode. + * @returns The base64. + */ +function encodeBase64(uint8Array) { + return Buffer.from(uint8Array).toString("base64"); +} +exports.encodeBase64 = encodeBase64; +/** + * Encode a typed array of uint8 as unpadded base64. + * @param uint8Array - The data to encode. + * @returns The unpadded base64. + */ +function encodeUnpaddedBase64(uint8Array) { + return encodeBase64(uint8Array).replace(/=+$/g, ""); +} +exports.encodeUnpaddedBase64 = encodeUnpaddedBase64; +/** + * Decode a base64 string to a typed array of uint8. + * @param base64 - The base64 to decode. + * @returns The decoded data. + */ +function decodeBase64(base64) { + return Buffer.from(base64, "base64"); +} +exports.decodeBase64 = decodeBase64; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"../@types/event":306,"../logger":374,"../utils":416,"another-json":1,"buffer":68}],344:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.decodeRecoveryKey = exports.encodeRecoveryKey = void 0; +const bs58 = __importStar(require("bs58")); +// picked arbitrarily but to try & avoid clashing with any bitcoin ones +// (which are also base58 encoded, but bitcoin's involve a lot more hashing) +const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01]; +function encodeRecoveryKey(key) { + var _a; + const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1); + buf.set(OLM_RECOVERY_KEY_PREFIX, 0); + buf.set(key, OLM_RECOVERY_KEY_PREFIX.length); + let parity = 0; + for (let i = 0; i < buf.length - 1; ++i) { + parity ^= buf[i]; + } + buf[buf.length - 1] = parity; + const base58key = bs58.encode(buf); + return (_a = base58key.match(/.{1,4}/g)) === null || _a === void 0 ? void 0 : _a.join(" "); +} +exports.encodeRecoveryKey = encodeRecoveryKey; +function decodeRecoveryKey(recoveryKey) { + const result = bs58.decode(recoveryKey.replace(/ /g, "")); + let parity = 0; + for (const b of result) { + parity ^= b; + } + if (parity !== 0) { + throw new Error("Incorrect parity"); + } + for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) { + if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) { + throw new Error("Incorrect prefix"); + } + } + if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1) { + throw new Error("Incorrect length"); + } + return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH)); +} +exports.decodeRecoveryKey = decodeRecoveryKey; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"bs58":65,"buffer":68}],345:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.upgradeDatabase = exports.VERSION = exports.Backend = void 0; +const logger_1 = require("../../logger"); +const utils = __importStar(require("../../utils")); +const PROFILE_TRANSACTIONS = false; +/** + * Implementation of a CryptoStore which is backed by an existing + * IndexedDB connection. Generally you want IndexedDBCryptoStore + * which connects to the database and defers to one of these. + */ +class Backend { + /** + */ + constructor(db) { + this.db = db; + this.nextTxnId = 0; + // make sure we close the db on `onversionchange` - otherwise + // attempts to delete the database will block (and subsequent + // attempts to re-create it will also block). + db.onversionchange = () => { + logger_1.logger.log(`versionchange for indexeddb ${this.db.name}: closing`); + db.close(); + }; + } + startup() { + return __awaiter(this, void 0, void 0, function* () { + // No work to do, as the startup is done by the caller (e.g IndexedDBCryptoStore) + // by passing us a ready IDBDatabase instance + return this; + }); + } + deleteAllData() { + return __awaiter(this, void 0, void 0, function* () { + throw Error("This is not implemented, call IDBFactory::deleteDatabase(dbName) instead."); + }); + } + /** + * Look for an existing outgoing room key request, and if none is found, + * add a new one + * + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest}: either the + * same instance as passed in, or the existing one. + */ + getOrAddOutgoingRoomKeyRequest(request) { + const requestBody = request.requestBody; + return new Promise((resolve, reject) => { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + txn.onerror = reject; + // first see if we already have an entry for this request. + this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => { + if (existing) { + // this entry matches the request - return it. + logger_1.logger.log(`already have key request outstanding for ` + + `${requestBody.room_id} / ${requestBody.session_id}: ` + + `not sending another`); + resolve(existing); + return; + } + // we got to the end of the list without finding a match + // - add the new request. + logger_1.logger.log(`enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id); + txn.oncomplete = () => { + resolve(request); + }; + const store = txn.objectStore("outgoingRoomKeyRequests"); + store.add(request); + }); + }); + } + /** + * Look for an existing room key request + * + * @param requestBody - existing request to look for + * + * @returns resolves to the matching + * {@link OutgoingRoomKeyRequest}, or null if + * not found + */ + getOutgoingRoomKeyRequest(requestBody) { + return new Promise((resolve, reject) => { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); + txn.onerror = reject; + this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => { + resolve(existing); + }); + }); + } + /** + * look for an existing room key request in the db + * + * @internal + * @param txn - database transaction + * @param requestBody - existing request to look for + * @param callback - function to call with the results of the + * search. Either passed a matching + * {@link OutgoingRoomKeyRequest}, or null if + * not found. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + _getOutgoingRoomKeyRequest(txn, requestBody, callback) { + const store = txn.objectStore("outgoingRoomKeyRequests"); + const idx = store.index("session"); + const cursorReq = idx.openCursor([requestBody.room_id, requestBody.session_id]); + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; + if (!cursor) { + // no match found + callback(null); + return; + } + const existing = cursor.value; + if (utils.deepCompare(existing.requestBody, requestBody)) { + // got a match + callback(existing); + return; + } + // look at the next entry in the index + cursor.continue(); + }; + } + /** + * Look for room key requests by state + * + * @param wantedStates - list of acceptable states + * + * @returns resolves to the a + * {@link OutgoingRoomKeyRequest}, or null if + * there are no pending requests in those states. If there are multiple + * requests in those states, an arbitrary one is chosen. + */ + getOutgoingRoomKeyRequestByState(wantedStates) { + if (wantedStates.length === 0) { + return Promise.resolve(null); + } + // this is a bit tortuous because we need to make sure we do the lookup + // in a single transaction, to avoid having a race with the insertion + // code. + // index into the wantedStates array + let stateIndex = 0; + let result; + function onsuccess() { + const cursor = this.result; + if (cursor) { + // got a match + result = cursor.value; + return; + } + // try the next state in the list + stateIndex++; + if (stateIndex >= wantedStates.length) { + // no matches + return; + } + const wantedState = wantedStates[stateIndex]; + const cursorReq = this.source.openCursor(wantedState); + cursorReq.onsuccess = onsuccess; + } + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); + const store = txn.objectStore("outgoingRoomKeyRequests"); + const wantedState = wantedStates[stateIndex]; + const cursorReq = store.index("state").openCursor(wantedState); + cursorReq.onsuccess = onsuccess; + return promiseifyTxn(txn).then(() => result); + } + /** + * + * @returns All elements in a given state + */ + getAllOutgoingRoomKeyRequestsByState(wantedState) { + return new Promise((resolve, reject) => { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); + const store = txn.objectStore("outgoingRoomKeyRequests"); + const index = store.index("state"); + const request = index.getAll(wantedState); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + let stateIndex = 0; + const results = []; + function onsuccess() { + const cursor = this.result; + if (cursor) { + const keyReq = cursor.value; + if (keyReq.recipients.some((recipient) => recipient.userId === userId && recipient.deviceId === deviceId)) { + results.push(keyReq); + } + cursor.continue(); + } + else { + // try the next state in the list + stateIndex++; + if (stateIndex >= wantedStates.length) { + // no matches + return; + } + const wantedState = wantedStates[stateIndex]; + const cursorReq = this.source.openCursor(wantedState); + cursorReq.onsuccess = onsuccess; + } + } + const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly"); + const store = txn.objectStore("outgoingRoomKeyRequests"); + const wantedState = wantedStates[stateIndex]; + const cursorReq = store.index("state").openCursor(wantedState); + cursorReq.onsuccess = onsuccess; + return promiseifyTxn(txn).then(() => results); + } + /** + * Look for an existing room key request by id and state, and update it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * @param updates - name/value map of updates to apply + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest} + * updated request, or null if no matching row was found + */ + updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { + let result = null; + function onsuccess() { + const cursor = this.result; + if (!cursor) { + return; + } + const data = cursor.value; + if (data.state != expectedState) { + logger_1.logger.warn(`Cannot update room key request from ${expectedState} ` + + `as it was already updated to ${data.state}`); + return; + } + Object.assign(data, updates); + cursor.update(data); + result = data; + } + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); + cursorReq.onsuccess = onsuccess; + return promiseifyTxn(txn).then(() => result); + } + /** + * Look for an existing room key request by id and state, and delete it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * + * @returns resolves once the operation is completed + */ + deleteOutgoingRoomKeyRequest(requestId, expectedState) { + const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite"); + const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId); + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; + if (!cursor) { + return; + } + const data = cursor.value; + if (data.state != expectedState) { + logger_1.logger.warn(`Cannot delete room key request in state ${data.state} ` + `(expected ${expectedState})`); + return; + } + cursor.delete(); + }; + return promiseifyTxn(txn); + } + // Olm Account + getAccount(txn, func) { + const objectStore = txn.objectStore("account"); + const getReq = objectStore.get("-"); + getReq.onsuccess = function () { + try { + func(getReq.result || null); + } + catch (e) { + abortWithException(txn, e); + } + }; + } + storeAccount(txn, accountPickle) { + const objectStore = txn.objectStore("account"); + objectStore.put(accountPickle, "-"); + } + getCrossSigningKeys(txn, func) { + const objectStore = txn.objectStore("account"); + const getReq = objectStore.get("crossSigningKeys"); + getReq.onsuccess = function () { + try { + func(getReq.result || null); + } + catch (e) { + abortWithException(txn, e); + } + }; + } + getSecretStorePrivateKey(txn, func, type) { + const objectStore = txn.objectStore("account"); + const getReq = objectStore.get(`ssss_cache:${type}`); + getReq.onsuccess = function () { + try { + func(getReq.result || null); + } + catch (e) { + abortWithException(txn, e); + } + }; + } + storeCrossSigningKeys(txn, keys) { + const objectStore = txn.objectStore("account"); + objectStore.put(keys, "crossSigningKeys"); + } + storeSecretStorePrivateKey(txn, type, key) { + const objectStore = txn.objectStore("account"); + objectStore.put(key, `ssss_cache:${type}`); + } + // Olm Sessions + countEndToEndSessions(txn, func) { + const objectStore = txn.objectStore("sessions"); + const countReq = objectStore.count(); + countReq.onsuccess = function () { + try { + func(countReq.result); + } + catch (e) { + abortWithException(txn, e); + } + }; + } + getEndToEndSessions(deviceKey, txn, func) { + const objectStore = txn.objectStore("sessions"); + const idx = objectStore.index("deviceKey"); + const getReq = idx.openCursor(deviceKey); + const results = {}; + getReq.onsuccess = function () { + const cursor = getReq.result; + if (cursor) { + results[cursor.value.sessionId] = { + session: cursor.value.session, + lastReceivedMessageTs: cursor.value.lastReceivedMessageTs, + }; + cursor.continue(); + } + else { + try { + func(results); + } + catch (e) { + abortWithException(txn, e); + } + } + }; + } + getEndToEndSession(deviceKey, sessionId, txn, func) { + const objectStore = txn.objectStore("sessions"); + const getReq = objectStore.get([deviceKey, sessionId]); + getReq.onsuccess = function () { + try { + if (getReq.result) { + func({ + session: getReq.result.session, + lastReceivedMessageTs: getReq.result.lastReceivedMessageTs, + }); + } + else { + func(null); + } + } + catch (e) { + abortWithException(txn, e); + } + }; + } + getAllEndToEndSessions(txn, func) { + const objectStore = txn.objectStore("sessions"); + const getReq = objectStore.openCursor(); + getReq.onsuccess = function () { + try { + const cursor = getReq.result; + if (cursor) { + func(cursor.value); + cursor.continue(); + } + else { + func(null); + } + } + catch (e) { + abortWithException(txn, e); + } + }; + } + storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + const objectStore = txn.objectStore("sessions"); + objectStore.put({ + deviceKey, + sessionId, + session: sessionInfo.session, + lastReceivedMessageTs: sessionInfo.lastReceivedMessageTs, + }); + } + storeEndToEndSessionProblem(deviceKey, type, fixed) { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction("session_problems", "readwrite"); + const objectStore = txn.objectStore("session_problems"); + objectStore.put({ + deviceKey, + type, + fixed, + time: Date.now(), + }); + yield promiseifyTxn(txn); + }); + } + getEndToEndSessionProblem(deviceKey, timestamp) { + return __awaiter(this, void 0, void 0, function* () { + let result = null; + const txn = this.db.transaction("session_problems", "readwrite"); + const objectStore = txn.objectStore("session_problems"); + const index = objectStore.index("deviceKey"); + const req = index.getAll(deviceKey); + req.onsuccess = () => { + const problems = req.result; + if (!problems.length) { + result = null; + return; + } + problems.sort((a, b) => { + return a.time - b.time; + }); + const lastProblem = problems[problems.length - 1]; + for (const problem of problems) { + if (problem.time > timestamp) { + result = Object.assign({}, problem, { fixed: lastProblem.fixed }); + return; + } + } + if (lastProblem.fixed) { + result = null; + } + else { + result = lastProblem; + } + }; + yield promiseifyTxn(txn); + return result; + }); + } + // FIXME: we should probably prune this when devices get deleted + filterOutNotifiedErrorDevices(devices) { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction("notified_error_devices", "readwrite"); + const objectStore = txn.objectStore("notified_error_devices"); + const ret = []; + yield Promise.all(devices.map((device) => { + return new Promise((resolve) => { + const { userId, deviceInfo } = device; + const getReq = objectStore.get([userId, deviceInfo.deviceId]); + getReq.onsuccess = function () { + if (!getReq.result) { + objectStore.put({ userId, deviceId: deviceInfo.deviceId }); + ret.push(device); + } + resolve(); + }; + }); + })); + return ret; + }); + } + // Inbound group sessions + getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + let session = false; + let withheld = false; + const objectStore = txn.objectStore("inbound_group_sessions"); + const getReq = objectStore.get([senderCurve25519Key, sessionId]); + getReq.onsuccess = function () { + try { + if (getReq.result) { + session = getReq.result.session; + } + else { + session = null; + } + if (withheld !== false) { + func(session, withheld); + } + } + catch (e) { + abortWithException(txn, e); + } + }; + const withheldObjectStore = txn.objectStore("inbound_group_sessions_withheld"); + const withheldGetReq = withheldObjectStore.get([senderCurve25519Key, sessionId]); + withheldGetReq.onsuccess = function () { + try { + if (withheldGetReq.result) { + withheld = withheldGetReq.result.session; + } + else { + withheld = null; + } + if (session !== false) { + func(session, withheld); + } + } + catch (e) { + abortWithException(txn, e); + } + }; + } + getAllEndToEndInboundGroupSessions(txn, func) { + const objectStore = txn.objectStore("inbound_group_sessions"); + const getReq = objectStore.openCursor(); + getReq.onsuccess = function () { + const cursor = getReq.result; + if (cursor) { + try { + func({ + senderKey: cursor.value.senderCurve25519Key, + sessionId: cursor.value.sessionId, + sessionData: cursor.value.session, + }); + } + catch (e) { + abortWithException(txn, e); + } + cursor.continue(); + } + else { + try { + func(null); + } + catch (e) { + abortWithException(txn, e); + } + } + }; + } + addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + const objectStore = txn.objectStore("inbound_group_sessions"); + const addReq = objectStore.add({ + senderCurve25519Key, + sessionId, + session: sessionData, + }); + addReq.onerror = (ev) => { + var _a; + if (((_a = addReq.error) === null || _a === void 0 ? void 0 : _a.name) === "ConstraintError") { + // This stops the error from triggering the txn's onerror + ev.stopPropagation(); + // ...and this stops it from aborting the transaction + ev.preventDefault(); + logger_1.logger.log("Ignoring duplicate inbound group session: " + senderCurve25519Key + " / " + sessionId); + } + else { + abortWithException(txn, new Error("Failed to add inbound group session: " + addReq.error)); + } + }; + } + storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + const objectStore = txn.objectStore("inbound_group_sessions"); + objectStore.put({ + senderCurve25519Key, + sessionId, + session: sessionData, + }); + } + storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) { + const objectStore = txn.objectStore("inbound_group_sessions_withheld"); + objectStore.put({ + senderCurve25519Key, + sessionId, + session: sessionData, + }); + } + getEndToEndDeviceData(txn, func) { + const objectStore = txn.objectStore("device_data"); + const getReq = objectStore.get("-"); + getReq.onsuccess = function () { + try { + func(getReq.result || null); + } + catch (e) { + abortWithException(txn, e); + } + }; + } + storeEndToEndDeviceData(deviceData, txn) { + const objectStore = txn.objectStore("device_data"); + objectStore.put(deviceData, "-"); + } + storeEndToEndRoom(roomId, roomInfo, txn) { + const objectStore = txn.objectStore("rooms"); + objectStore.put(roomInfo, roomId); + } + getEndToEndRooms(txn, func) { + const rooms = {}; + const objectStore = txn.objectStore("rooms"); + const getReq = objectStore.openCursor(); + getReq.onsuccess = function () { + const cursor = getReq.result; + if (cursor) { + rooms[cursor.key] = cursor.value; + cursor.continue(); + } + else { + try { + func(rooms); + } + catch (e) { + abortWithException(txn, e); + } + } + }; + } + // session backups + getSessionsNeedingBackup(limit) { + return new Promise((resolve, reject) => { + const sessions = []; + const txn = this.db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly"); + txn.onerror = reject; + txn.oncomplete = function () { + resolve(sessions); + }; + const objectStore = txn.objectStore("sessions_needing_backup"); + const sessionStore = txn.objectStore("inbound_group_sessions"); + const getReq = objectStore.openCursor(); + getReq.onsuccess = function () { + const cursor = getReq.result; + if (cursor) { + const sessionGetReq = sessionStore.get(cursor.key); + sessionGetReq.onsuccess = function () { + sessions.push({ + senderKey: sessionGetReq.result.senderCurve25519Key, + sessionId: sessionGetReq.result.sessionId, + sessionData: sessionGetReq.result.session, + }); + }; + if (!limit || sessions.length < limit) { + cursor.continue(); + } + } + }; + }); + } + countSessionsNeedingBackup(txn) { + if (!txn) { + txn = this.db.transaction("sessions_needing_backup", "readonly"); + } + const objectStore = txn.objectStore("sessions_needing_backup"); + return new Promise((resolve, reject) => { + const req = objectStore.count(); + req.onerror = reject; + req.onsuccess = () => resolve(req.result); + }); + } + unmarkSessionsNeedingBackup(sessions, txn) { + return __awaiter(this, void 0, void 0, function* () { + if (!txn) { + txn = this.db.transaction("sessions_needing_backup", "readwrite"); + } + const objectStore = txn.objectStore("sessions_needing_backup"); + yield Promise.all(sessions.map((session) => { + return new Promise((resolve, reject) => { + const req = objectStore.delete([session.senderKey, session.sessionId]); + req.onsuccess = resolve; + req.onerror = reject; + }); + })); + }); + } + markSessionsNeedingBackup(sessions, txn) { + return __awaiter(this, void 0, void 0, function* () { + if (!txn) { + txn = this.db.transaction("sessions_needing_backup", "readwrite"); + } + const objectStore = txn.objectStore("sessions_needing_backup"); + yield Promise.all(sessions.map((session) => { + return new Promise((resolve, reject) => { + const req = objectStore.put({ + senderCurve25519Key: session.senderKey, + sessionId: session.sessionId, + }); + req.onsuccess = resolve; + req.onerror = reject; + }); + })); + }); + } + addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { + if (!txn) { + txn = this.db.transaction("shared_history_inbound_group_sessions", "readwrite"); + } + const objectStore = txn.objectStore("shared_history_inbound_group_sessions"); + const req = objectStore.get([roomId]); + req.onsuccess = () => { + const { sessions } = req.result || { sessions: [] }; + sessions.push([senderKey, sessionId]); + objectStore.put({ roomId, sessions }); + }; + } + getSharedHistoryInboundGroupSessions(roomId, txn) { + if (!txn) { + txn = this.db.transaction("shared_history_inbound_group_sessions", "readonly"); + } + const objectStore = txn.objectStore("shared_history_inbound_group_sessions"); + const req = objectStore.get([roomId]); + return new Promise((resolve, reject) => { + req.onsuccess = () => { + const { sessions } = req.result || { sessions: [] }; + resolve(sessions); + }; + req.onerror = reject; + }); + } + addParkedSharedHistory(roomId, parkedData, txn) { + if (!txn) { + txn = this.db.transaction("parked_shared_history", "readwrite"); + } + const objectStore = txn.objectStore("parked_shared_history"); + const req = objectStore.get([roomId]); + req.onsuccess = () => { + const { parked } = req.result || { parked: [] }; + parked.push(parkedData); + objectStore.put({ roomId, parked }); + }; + } + takeParkedSharedHistory(roomId, txn) { + if (!txn) { + txn = this.db.transaction("parked_shared_history", "readwrite"); + } + const cursorReq = txn.objectStore("parked_shared_history").openCursor(roomId); + return new Promise((resolve, reject) => { + cursorReq.onsuccess = () => { + const cursor = cursorReq.result; + if (!cursor) { + resolve([]); + return; + } + const data = cursor.value; + cursor.delete(); + resolve(data); + }; + cursorReq.onerror = reject; + }); + } + doTxn(mode, stores, func, log = logger_1.logger) { + let startTime; + let description; + if (PROFILE_TRANSACTIONS) { + const txnId = this.nextTxnId++; + startTime = Date.now(); + description = `${mode} crypto store transaction ${txnId} in ${stores}`; + log.debug(`Starting ${description}`); + } + const txn = this.db.transaction(stores, mode); + const promise = promiseifyTxn(txn); + const result = func(txn); + if (PROFILE_TRANSACTIONS) { + promise.then(() => { + const elapsedTime = Date.now() - startTime; + log.debug(`Finished ${description}, took ${elapsedTime} ms`); + }, () => { + const elapsedTime = Date.now() - startTime; + log.error(`Failed ${description}, took ${elapsedTime} ms`); + }); + } + return promise.then(() => { + return result; + }); + } +} +exports.Backend = Backend; +const DB_MIGRATIONS = [ + (db) => { + createDatabase(db); + }, + (db) => { + db.createObjectStore("account"); + }, + (db) => { + const sessionsStore = db.createObjectStore("sessions", { + keyPath: ["deviceKey", "sessionId"], + }); + sessionsStore.createIndex("deviceKey", "deviceKey"); + }, + (db) => { + db.createObjectStore("inbound_group_sessions", { + keyPath: ["senderCurve25519Key", "sessionId"], + }); + }, + (db) => { + db.createObjectStore("device_data"); + }, + (db) => { + db.createObjectStore("rooms"); + }, + (db) => { + db.createObjectStore("sessions_needing_backup", { + keyPath: ["senderCurve25519Key", "sessionId"], + }); + }, + (db) => { + db.createObjectStore("inbound_group_sessions_withheld", { + keyPath: ["senderCurve25519Key", "sessionId"], + }); + }, + (db) => { + const problemsStore = db.createObjectStore("session_problems", { + keyPath: ["deviceKey", "time"], + }); + problemsStore.createIndex("deviceKey", "deviceKey"); + db.createObjectStore("notified_error_devices", { + keyPath: ["userId", "deviceId"], + }); + }, + (db) => { + db.createObjectStore("shared_history_inbound_group_sessions", { + keyPath: ["roomId"], + }); + }, + (db) => { + db.createObjectStore("parked_shared_history", { + keyPath: ["roomId"], + }); + }, + // Expand as needed. +]; +exports.VERSION = DB_MIGRATIONS.length; +function upgradeDatabase(db, oldVersion) { + logger_1.logger.log(`Upgrading IndexedDBCryptoStore from version ${oldVersion}` + ` to ${exports.VERSION}`); + DB_MIGRATIONS.forEach((migration, index) => { + if (oldVersion <= index) + migration(db); + }); +} +exports.upgradeDatabase = upgradeDatabase; +function createDatabase(db) { + const outgoingRoomKeyRequestsStore = db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" }); + // we assume that the RoomKeyRequestBody will have room_id and session_id + // properties, to make the index efficient. + outgoingRoomKeyRequestsStore.createIndex("session", ["requestBody.room_id", "requestBody.session_id"]); + outgoingRoomKeyRequestsStore.createIndex("state", "state"); +} +/* + * Aborts a transaction with a given exception + * The transaction promise will be rejected with this exception. + */ +function abortWithException(txn, e) { + // We cheekily stick our exception onto the transaction object here + // We could alternatively make the thing we pass back to the app + // an object containing the transaction and exception. + txn._mx_abortexception = e; + try { + txn.abort(); + } + catch (e) { + // sometimes we won't be able to abort the transaction + // (ie. if it's aborted or completed) + } +} +function promiseifyTxn(txn) { + return new Promise((resolve, reject) => { + txn.oncomplete = () => { + if (txn._mx_abortexception !== undefined) { + reject(txn._mx_abortexception); + } + resolve(null); + }; + txn.onerror = (event) => { + if (txn._mx_abortexception !== undefined) { + reject(txn._mx_abortexception); + } + else { + logger_1.logger.log("Error performing indexeddb txn", event); + reject(txn.error); + } + }; + txn.onabort = (event) => { + if (txn._mx_abortexception !== undefined) { + reject(txn._mx_abortexception); + } + else { + logger_1.logger.log("Error performing indexeddb txn", event); + reject(txn.error); + } + }; + }); +} + +},{"../../logger":374,"../../utils":416}],346:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IndexedDBCryptoStore = void 0; +const logger_1 = require("../../logger"); +const localStorage_crypto_store_1 = require("./localStorage-crypto-store"); +const memory_crypto_store_1 = require("./memory-crypto-store"); +const IndexedDBCryptoStoreBackend = __importStar(require("./indexeddb-crypto-store-backend")); +const errors_1 = require("../../errors"); +const IndexedDBHelpers = __importStar(require("../../indexeddb-helpers")); +/** + * Internal module. indexeddb storage for e2e. + */ +/** + * An implementation of CryptoStore, which is normally backed by an indexeddb, + * but with fallback to MemoryCryptoStore. + */ +class IndexedDBCryptoStore { + static exists(indexedDB, dbName) { + return IndexedDBHelpers.exists(indexedDB, dbName); + } + /** + * Create a new IndexedDBCryptoStore + * + * @param indexedDB - global indexedDB instance + * @param dbName - name of db to connect to + */ + constructor(indexedDB, dbName) { + this.indexedDB = indexedDB; + this.dbName = dbName; + } + /** + * Ensure the database exists and is up-to-date, or fall back to + * a local storage or in-memory store. + * + * This must be called before the store can be used. + * + * @returns resolves to either an IndexedDBCryptoStoreBackend.Backend, + * or a MemoryCryptoStore + */ + startup() { + if (this.backendPromise) { + return this.backendPromise; + } + this.backendPromise = new Promise((resolve, reject) => { + if (!this.indexedDB) { + reject(new Error("no indexeddb support available")); + return; + } + logger_1.logger.log(`connecting to indexeddb ${this.dbName}`); + const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION); + req.onupgradeneeded = (ev) => { + const db = req.result; + const oldVersion = ev.oldVersion; + IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion); + }; + req.onblocked = () => { + logger_1.logger.log(`can't yet open IndexedDBCryptoStore because it is open elsewhere`); + }; + req.onerror = (ev) => { + logger_1.logger.log("Error connecting to indexeddb", ev); + reject(req.error); + }; + req.onsuccess = () => { + const db = req.result; + logger_1.logger.log(`connected to indexeddb ${this.dbName}`); + resolve(new IndexedDBCryptoStoreBackend.Backend(db)); + }; + }) + .then((backend) => { + // Edge has IndexedDB but doesn't support compund keys which we use fairly extensively. + // Try a dummy query which will fail if the browser doesn't support compund keys, so + // we can fall back to a different backend. + return backend + .doTxn("readonly", [ + IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, + IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD, + ], (txn) => { + backend.getEndToEndInboundGroupSession("", "", txn, () => { }); + }) + .then(() => backend); + }) + .catch((e) => { + if (e.name === "VersionError") { + logger_1.logger.warn("Crypto DB is too new for us to use!", e); + // don't fall back to a different store: the user has crypto data + // in this db so we should use it or nothing at all. + throw new errors_1.InvalidCryptoStoreError(errors_1.InvalidCryptoStoreState.TooNew); + } + logger_1.logger.warn(`unable to connect to indexeddb ${this.dbName}` + `: falling back to localStorage store: ${e}`); + try { + return new localStorage_crypto_store_1.LocalStorageCryptoStore(global.localStorage); + } + catch (e) { + logger_1.logger.warn(`unable to open localStorage: falling back to in-memory store: ${e}`); + return new memory_crypto_store_1.MemoryCryptoStore(); + } + }) + .then((backend) => { + this.backend = backend; + return backend; + }); + return this.backendPromise; + } + /** + * Delete all data from this store. + * + * @returns resolves when the store has been cleared. + */ + deleteAllData() { + return new Promise((resolve, reject) => { + if (!this.indexedDB) { + reject(new Error("no indexeddb support available")); + return; + } + logger_1.logger.log(`Removing indexeddb instance: ${this.dbName}`); + const req = this.indexedDB.deleteDatabase(this.dbName); + req.onblocked = () => { + logger_1.logger.log(`can't yet delete IndexedDBCryptoStore because it is open elsewhere`); + }; + req.onerror = (ev) => { + logger_1.logger.log("Error deleting data from indexeddb", ev); + reject(req.error); + }; + req.onsuccess = () => { + logger_1.logger.log(`Removed indexeddb instance: ${this.dbName}`); + resolve(); + }; + }).catch((e) => { + // in firefox, with indexedDB disabled, this fails with a + // DOMError. We treat this as non-fatal, so that people can + // still use the app. + logger_1.logger.warn(`unable to delete IndexedDBCryptoStore: ${e}`); + }); + } + /** + * Look for an existing outgoing room key request, and if none is found, + * add a new one + * + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest}: either the + * same instance as passed in, or the existing one. + */ + getOrAddOutgoingRoomKeyRequest(request) { + return this.backend.getOrAddOutgoingRoomKeyRequest(request); + } + /** + * Look for an existing room key request + * + * @param requestBody - existing request to look for + * + * @returns resolves to the matching + * {@link OutgoingRoomKeyRequest}, or null if + * not found + */ + getOutgoingRoomKeyRequest(requestBody) { + return this.backend.getOutgoingRoomKeyRequest(requestBody); + } + /** + * Look for room key requests by state + * + * @param wantedStates - list of acceptable states + * + * @returns resolves to the a + * {@link OutgoingRoomKeyRequest}, or null if + * there are no pending requests in those states. If there are multiple + * requests in those states, an arbitrary one is chosen. + */ + getOutgoingRoomKeyRequestByState(wantedStates) { + return this.backend.getOutgoingRoomKeyRequestByState(wantedStates); + } + /** + * Look for room key requests by state – + * unlike above, return a list of all entries in one state. + * + * @returns Returns an array of requests in the given state + */ + getAllOutgoingRoomKeyRequestsByState(wantedState) { + return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState); + } + /** + * Look for room key requests by target device and state + * + * @param userId - Target user ID + * @param deviceId - Target device ID + * @param wantedStates - list of acceptable states + * + * @returns resolves to a list of all the + * {@link OutgoingRoomKeyRequest} + */ + getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + return this.backend.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates); + } + /** + * Look for an existing room key request by id and state, and update it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * @param updates - name/value map of updates to apply + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest} + * updated request, or null if no matching row was found + */ + updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { + return this.backend.updateOutgoingRoomKeyRequest(requestId, expectedState, updates); + } + /** + * Look for an existing room key request by id and state, and delete it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * + * @returns resolves once the operation is completed + */ + deleteOutgoingRoomKeyRequest(requestId, expectedState) { + return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState); + } + // Olm Account + /* + * Get the account pickle from the store. + * This requires an active transaction. See doTxn(). + * + * @param txn - An active transaction. See doTxn(). + * @param func - Called with the account pickle + */ + getAccount(txn, func) { + this.backend.getAccount(txn, func); + } + /** + * Write the account pickle to the store. + * This requires an active transaction. See doTxn(). + * + * @param txn - An active transaction. See doTxn(). + * @param accountPickle - The new account pickle to store. + */ + storeAccount(txn, accountPickle) { + this.backend.storeAccount(txn, accountPickle); + } + /** + * Get the public part of the cross-signing keys (eg. self-signing key, + * user signing key). + * + * @param txn - An active transaction. See doTxn(). + * @param func - Called with the account keys object: + * `{ key_type: base64 encoded seed }` where key type = user_signing_key_seed or self_signing_key_seed + */ + getCrossSigningKeys(txn, func) { + this.backend.getCrossSigningKeys(txn, func); + } + /** + * @param txn - An active transaction. See doTxn(). + * @param func - Called with the private key + * @param type - A key type + */ + getSecretStorePrivateKey(txn, func, type) { + this.backend.getSecretStorePrivateKey(txn, func, type); + } + /** + * Write the cross-signing keys back to the store + * + * @param txn - An active transaction. See doTxn(). + * @param keys - keys object as getCrossSigningKeys() + */ + storeCrossSigningKeys(txn, keys) { + this.backend.storeCrossSigningKeys(txn, keys); + } + /** + * Write the cross-signing private keys back to the store + * + * @param txn - An active transaction. See doTxn(). + * @param type - The type of cross-signing private key to store + * @param key - keys object as getCrossSigningKeys() + */ + storeSecretStorePrivateKey(txn, type, key) { + this.backend.storeSecretStorePrivateKey(txn, type, key); + } + // Olm sessions + /** + * Returns the number of end-to-end sessions in the store + * @param txn - An active transaction. See doTxn(). + * @param func - Called with the count of sessions + */ + countEndToEndSessions(txn, func) { + this.backend.countEndToEndSessions(txn, func); + } + /** + * Retrieve a specific end-to-end session between the logged-in user + * and another device. + * @param deviceKey - The public key of the other device. + * @param sessionId - The ID of the session to retrieve + * @param txn - An active transaction. See doTxn(). + * @param func - Called with A map from sessionId + * to session information object with 'session' key being the + * Base64 end-to-end session and lastReceivedMessageTs being the + * timestamp in milliseconds at which the session last received + * a message. + */ + getEndToEndSession(deviceKey, sessionId, txn, func) { + this.backend.getEndToEndSession(deviceKey, sessionId, txn, func); + } + /** + * Retrieve the end-to-end sessions between the logged-in user and another + * device. + * @param deviceKey - The public key of the other device. + * @param txn - An active transaction. See doTxn(). + * @param func - Called with A map from sessionId + * to session information object with 'session' key being the + * Base64 end-to-end session and lastReceivedMessageTs being the + * timestamp in milliseconds at which the session last received + * a message. + */ + getEndToEndSessions(deviceKey, txn, func) { + this.backend.getEndToEndSessions(deviceKey, txn, func); + } + /** + * Retrieve all end-to-end sessions + * @param txn - An active transaction. See doTxn(). + * @param func - Called one for each session with + * an object with, deviceKey, lastReceivedMessageTs, sessionId + * and session keys. + */ + getAllEndToEndSessions(txn, func) { + this.backend.getAllEndToEndSessions(txn, func); + } + /** + * Store a session between the logged-in user and another device + * @param deviceKey - The public key of the other device. + * @param sessionId - The ID for this end-to-end session. + * @param sessionInfo - Session information object + * @param txn - An active transaction. See doTxn(). + */ + storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn); + } + storeEndToEndSessionProblem(deviceKey, type, fixed) { + return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed); + } + getEndToEndSessionProblem(deviceKey, timestamp) { + return this.backend.getEndToEndSessionProblem(deviceKey, timestamp); + } + filterOutNotifiedErrorDevices(devices) { + return this.backend.filterOutNotifiedErrorDevices(devices); + } + // Inbound group sessions + /** + * Retrieve the end-to-end inbound group session for a given + * server key and session ID + * @param senderCurve25519Key - The sender's curve 25519 key + * @param sessionId - The ID of the session + * @param txn - An active transaction. See doTxn(). + * @param func - Called with A map from sessionId + * to Base64 end-to-end session. + */ + getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func); + } + /** + * Fetches all inbound group sessions in the store + * @param txn - An active transaction. See doTxn(). + * @param func - Called once for each group session + * in the store with an object having keys `{senderKey, sessionId, sessionData}`, + * then once with null to indicate the end of the list. + */ + getAllEndToEndInboundGroupSessions(txn, func) { + this.backend.getAllEndToEndInboundGroupSessions(txn, func); + } + /** + * Adds an end-to-end inbound group session to the store. + * If there already exists an inbound group session with the same + * senderCurve25519Key and sessionID, the session will not be added. + * @param senderCurve25519Key - The sender's curve 25519 key + * @param sessionId - The ID of the session + * @param sessionData - The session data structure + * @param txn - An active transaction. See doTxn(). + */ + addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); + } + /** + * Writes an end-to-end inbound group session to the store. + * If there already exists an inbound group session with the same + * senderCurve25519Key and sessionID, it will be overwritten. + * @param senderCurve25519Key - The sender's curve 25519 key + * @param sessionId - The ID of the session + * @param sessionData - The session data structure + * @param txn - An active transaction. See doTxn(). + */ + storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); + } + storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) { + this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn); + } + // End-to-end device tracking + /** + * Store the state of all tracked devices + * This contains devices for each user, a tracking state for each user + * and a sync token matching the point in time the snapshot represents. + * These all need to be written out in full each time such that the snapshot + * is always consistent, so they are stored in one object. + * + * @param txn - An active transaction. See doTxn(). + */ + storeEndToEndDeviceData(deviceData, txn) { + this.backend.storeEndToEndDeviceData(deviceData, txn); + } + /** + * Get the state of all tracked devices + * + * @param txn - An active transaction. See doTxn(). + * @param func - Function called with the + * device data + */ + getEndToEndDeviceData(txn, func) { + this.backend.getEndToEndDeviceData(txn, func); + } + // End to End Rooms + /** + * Store the end-to-end state for a room. + * @param roomId - The room's ID. + * @param roomInfo - The end-to-end info for the room. + * @param txn - An active transaction. See doTxn(). + */ + storeEndToEndRoom(roomId, roomInfo, txn) { + this.backend.storeEndToEndRoom(roomId, roomInfo, txn); + } + /** + * Get an object of `roomId->roomInfo` for all e2e rooms in the store + * @param txn - An active transaction. See doTxn(). + * @param func - Function called with the end-to-end encrypted rooms + */ + getEndToEndRooms(txn, func) { + this.backend.getEndToEndRooms(txn, func); + } + // session backups + /** + * Get the inbound group sessions that need to be backed up. + * @param limit - The maximum number of sessions to retrieve. 0 + * for no limit. + * @returns resolves to an array of inbound group sessions + */ + getSessionsNeedingBackup(limit) { + return this.backend.getSessionsNeedingBackup(limit); + } + /** + * Count the inbound group sessions that need to be backed up. + * @param txn - An active transaction. See doTxn(). (optional) + * @returns resolves to the number of sessions + */ + countSessionsNeedingBackup(txn) { + return this.backend.countSessionsNeedingBackup(txn); + } + /** + * Unmark sessions as needing to be backed up. + * @param sessions - The sessions that need to be backed up. + * @param txn - An active transaction. See doTxn(). (optional) + * @returns resolves when the sessions are unmarked + */ + unmarkSessionsNeedingBackup(sessions, txn) { + return this.backend.unmarkSessionsNeedingBackup(sessions, txn); + } + /** + * Mark sessions as needing to be backed up. + * @param sessions - The sessions that need to be backed up. + * @param txn - An active transaction. See doTxn(). (optional) + * @returns resolves when the sessions are marked + */ + markSessionsNeedingBackup(sessions, txn) { + return this.backend.markSessionsNeedingBackup(sessions, txn); + } + /** + * Add a shared-history group session for a room. + * @param roomId - The room that the key belongs to + * @param senderKey - The sender's curve 25519 key + * @param sessionId - The ID of the session + * @param txn - An active transaction. See doTxn(). (optional) + */ + addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) { + this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn); + } + /** + * Get the shared-history group session for a room. + * @param roomId - The room that the key belongs to + * @param txn - An active transaction. See doTxn(). (optional) + * @returns Promise which resolves to an array of [senderKey, sessionId] + */ + getSharedHistoryInboundGroupSessions(roomId, txn) { + return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn); + } + /** + * Park a shared-history group session for a room we may be invited to later. + */ + addParkedSharedHistory(roomId, parkedData, txn) { + this.backend.addParkedSharedHistory(roomId, parkedData, txn); + } + /** + * Pop out all shared-history group sessions for a room. + */ + takeParkedSharedHistory(roomId, txn) { + return this.backend.takeParkedSharedHistory(roomId, txn); + } + /** + * Perform a transaction on the crypto store. Any store methods + * that require a transaction (txn) object to be passed in may + * only be called within a callback of either this function or + * one of the store functions operating on the same transaction. + * + * @param mode - 'readwrite' if you need to call setter + * functions with this transaction. Otherwise, 'readonly'. + * @param stores - List IndexedDBCryptoStore.STORE_* + * options representing all types of object that will be + * accessed or written to with this transaction. + * @param func - Function called with the + * transaction object: an opaque object that should be passed + * to store functions. + * @param log - A possibly customised log + * @returns Promise that resolves with the result of the `func` + * when the transaction is complete. If the backend is + * async (ie. the indexeddb backend) any of the callback + * functions throwing an exception will cause this promise to + * reject with that exception. On synchronous backends, the + * exception will propagate to the caller of the getFoo method. + */ + doTxn(mode, stores, func, log) { + return this.backend.doTxn(mode, stores, func, log); + } +} +exports.IndexedDBCryptoStore = IndexedDBCryptoStore; +IndexedDBCryptoStore.STORE_ACCOUNT = "account"; +IndexedDBCryptoStore.STORE_SESSIONS = "sessions"; +IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = "inbound_group_sessions"; +IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD = "inbound_group_sessions_withheld"; +IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS = "shared_history_inbound_group_sessions"; +IndexedDBCryptoStore.STORE_PARKED_SHARED_HISTORY = "parked_shared_history"; +IndexedDBCryptoStore.STORE_DEVICE_DATA = "device_data"; +IndexedDBCryptoStore.STORE_ROOMS = "rooms"; +IndexedDBCryptoStore.STORE_BACKUP = "sessions_needing_backup"; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../../errors":359,"../../indexeddb-helpers":372,"../../logger":374,"./indexeddb-crypto-store-backend":345,"./localStorage-crypto-store":347,"./memory-crypto-store":348}],347:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LocalStorageCryptoStore = void 0; +const logger_1 = require("../../logger"); +const memory_crypto_store_1 = require("./memory-crypto-store"); +const utils_1 = require("../../utils"); +/** + * Internal module. Partial localStorage backed storage for e2e. + * This is not a full crypto store, just the in-memory store with + * some things backed by localStorage. It exists because indexedDB + * is broken in Firefox private mode or set to, "will not remember + * history". + */ +const E2E_PREFIX = "crypto."; +const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account"; +const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys"; +const KEY_NOTIFIED_ERROR_DEVICES = E2E_PREFIX + "notified_error_devices"; +const KEY_DEVICE_DATA = E2E_PREFIX + "device_data"; +const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/"; +const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/"; +const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/"; +const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup"; +function keyEndToEndSessions(deviceKey) { + return E2E_PREFIX + "sessions/" + deviceKey; +} +function keyEndToEndSessionProblems(deviceKey) { + return E2E_PREFIX + "session.problems/" + deviceKey; +} +function keyEndToEndInboundGroupSession(senderKey, sessionId) { + return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId; +} +function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) { + return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId; +} +function keyEndToEndRoomsPrefix(roomId) { + return KEY_ROOMS_PREFIX + roomId; +} +class LocalStorageCryptoStore extends memory_crypto_store_1.MemoryCryptoStore { + static exists(store) { + var _a; + const length = store.length; + for (let i = 0; i < length; i++) { + if ((_a = store.key(i)) === null || _a === void 0 ? void 0 : _a.startsWith(E2E_PREFIX)) { + return true; + } + } + return false; + } + constructor(store) { + super(); + this.store = store; + } + // Olm Sessions + countEndToEndSessions(txn, func) { + var _a; + let count = 0; + for (let i = 0; i < this.store.length; ++i) { + if ((_a = this.store.key(i)) === null || _a === void 0 ? void 0 : _a.startsWith(keyEndToEndSessions(""))) + ++count; + } + func(count); + } + // eslint-disable-next-line @typescript-eslint/naming-convention + _getEndToEndSessions(deviceKey) { + const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey)); + const fixedSessions = {}; + // fix up any old sessions to be objects rather than just the base64 pickle + for (const [sid, val] of Object.entries(sessions || {})) { + if (typeof val === "string") { + fixedSessions[sid] = { + session: val, + }; + } + else { + fixedSessions[sid] = val; + } + } + return fixedSessions; + } + getEndToEndSession(deviceKey, sessionId, txn, func) { + const sessions = this._getEndToEndSessions(deviceKey); + func(sessions[sessionId] || {}); + } + getEndToEndSessions(deviceKey, txn, func) { + func(this._getEndToEndSessions(deviceKey) || {}); + } + getAllEndToEndSessions(txn, func) { + var _a; + for (let i = 0; i < this.store.length; ++i) { + if ((_a = this.store.key(i)) === null || _a === void 0 ? void 0 : _a.startsWith(keyEndToEndSessions(""))) { + const deviceKey = this.store.key(i).split("/")[1]; + for (const sess of Object.values(this._getEndToEndSessions(deviceKey))) { + func(sess); + } + } + } + } + storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + const sessions = this._getEndToEndSessions(deviceKey) || {}; + sessions[sessionId] = sessionInfo; + setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions); + } + storeEndToEndSessionProblem(deviceKey, type, fixed) { + return __awaiter(this, void 0, void 0, function* () { + const key = keyEndToEndSessionProblems(deviceKey); + const problems = getJsonItem(this.store, key) || []; + problems.push({ type, fixed, time: Date.now() }); + problems.sort((a, b) => { + return a.time - b.time; + }); + setJsonItem(this.store, key, problems); + }); + } + getEndToEndSessionProblem(deviceKey, timestamp) { + return __awaiter(this, void 0, void 0, function* () { + const key = keyEndToEndSessionProblems(deviceKey); + const problems = getJsonItem(this.store, key) || []; + if (!problems.length) { + return null; + } + const lastProblem = problems[problems.length - 1]; + for (const problem of problems) { + if (problem.time > timestamp) { + return Object.assign({}, problem, { fixed: lastProblem.fixed }); + } + } + if (lastProblem.fixed) { + return null; + } + else { + return lastProblem; + } + }); + } + filterOutNotifiedErrorDevices(devices) { + return __awaiter(this, void 0, void 0, function* () { + const notifiedErrorDevices = getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; + const ret = []; + for (const device of devices) { + const { userId, deviceInfo } = device; + if (userId in notifiedErrorDevices) { + if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) { + ret.push(device); + (0, utils_1.safeSet)(notifiedErrorDevices[userId], deviceInfo.deviceId, true); + } + } + else { + ret.push(device); + (0, utils_1.safeSet)(notifiedErrorDevices, userId, { [deviceInfo.deviceId]: true }); + } + } + setJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES, notifiedErrorDevices); + return ret; + }); + } + // Inbound Group Sessions + getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + func(getJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId)), getJsonItem(this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId))); + } + getAllEndToEndInboundGroupSessions(txn, func) { + for (let i = 0; i < this.store.length; ++i) { + const key = this.store.key(i); + if (key === null || key === void 0 ? void 0 : key.startsWith(KEY_INBOUND_SESSION_PREFIX)) { + // we can't use split, as the components we are trying to split out + // might themselves contain '/' characters. We rely on the + // senderKey being a (32-byte) curve25519 key, base64-encoded + // (hence 43 characters long). + func({ + senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43), + sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44), + sessionData: getJsonItem(this.store, key), + }); + } + } + func(null); + } + addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + const existing = getJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId)); + if (!existing) { + this.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn); + } + } + storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + setJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), sessionData); + } + storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) { + setJsonItem(this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId), sessionData); + } + getEndToEndDeviceData(txn, func) { + func(getJsonItem(this.store, KEY_DEVICE_DATA)); + } + storeEndToEndDeviceData(deviceData, txn) { + setJsonItem(this.store, KEY_DEVICE_DATA, deviceData); + } + storeEndToEndRoom(roomId, roomInfo, txn) { + setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo); + } + getEndToEndRooms(txn, func) { + const result = {}; + const prefix = keyEndToEndRoomsPrefix(""); + for (let i = 0; i < this.store.length; ++i) { + const key = this.store.key(i); + if (key === null || key === void 0 ? void 0 : key.startsWith(prefix)) { + const roomId = key.slice(prefix.length); + result[roomId] = getJsonItem(this.store, key); + } + } + func(result); + } + getSessionsNeedingBackup(limit) { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + const sessions = []; + for (const session in sessionsNeedingBackup) { + if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) { + // see getAllEndToEndInboundGroupSessions for the magic number explanations + const senderKey = session.slice(0, 43); + const sessionId = session.slice(44); + this.getEndToEndInboundGroupSession(senderKey, sessionId, null, (sessionData) => { + sessions.push({ + senderKey: senderKey, + sessionId: sessionId, + sessionData: sessionData, + }); + }); + if (limit && sessions.length >= limit) { + break; + } + } + } + return Promise.resolve(sessions); + } + countSessionsNeedingBackup() { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + return Promise.resolve(Object.keys(sessionsNeedingBackup).length); + } + unmarkSessionsNeedingBackup(sessions) { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for (const session of sessions) { + delete sessionsNeedingBackup[session.senderKey + "/" + session.sessionId]; + } + setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup); + return Promise.resolve(); + } + markSessionsNeedingBackup(sessions) { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + for (const session of sessions) { + sessionsNeedingBackup[session.senderKey + "/" + session.sessionId] = true; + } + setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup); + return Promise.resolve(); + } + /** + * Delete all data from this store. + * + * @returns Promise which resolves when the store has been cleared. + */ + deleteAllData() { + this.store.removeItem(KEY_END_TO_END_ACCOUNT); + return Promise.resolve(); + } + // Olm account + getAccount(txn, func) { + const accountPickle = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); + func(accountPickle); + } + storeAccount(txn, accountPickle) { + setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle); + } + getCrossSigningKeys(txn, func) { + const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS); + func(keys); + } + getSecretStorePrivateKey(txn, func, type) { + const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); + func(key); + } + storeCrossSigningKeys(txn, keys) { + setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys); + } + storeSecretStorePrivateKey(txn, type, key) { + setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key); + } + doTxn(mode, stores, func) { + return Promise.resolve(func(null)); + } +} +exports.LocalStorageCryptoStore = LocalStorageCryptoStore; +function getJsonItem(store, key) { + try { + // if the key is absent, store.getItem() returns null, and + // JSON.parse(null) === null, so this returns null. + return JSON.parse(store.getItem(key)); + } + catch (e) { + logger_1.logger.log("Error: Failed to get key %s: %s", key, e.message); + logger_1.logger.log(e.stack); + } + return null; +} +function setJsonItem(store, key, val) { + store.setItem(key, JSON.stringify(val)); +} + +},{"../../logger":374,"../../utils":416,"./memory-crypto-store":348}],348:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MemoryCryptoStore = void 0; +const logger_1 = require("../../logger"); +const utils = __importStar(require("../../utils")); +const utils_1 = require("../../utils"); +/** + * Internal module. in-memory storage for e2e. + */ +class MemoryCryptoStore { + constructor() { + this.outgoingRoomKeyRequests = []; + this.account = null; + this.crossSigningKeys = null; + this.privateKeys = {}; + this.sessions = {}; + this.sessionProblems = {}; + this.notifiedErrorDevices = {}; + this.inboundGroupSessions = {}; + this.inboundGroupSessionsWithheld = {}; + // Opaque device data object + this.deviceData = null; + this.rooms = {}; + this.sessionsNeedingBackup = {}; + this.sharedHistoryInboundGroupSessions = {}; + this.parkedSharedHistory = new Map(); // keyed by room ID + } + /** + * Ensure the database exists and is up-to-date. + * + * This must be called before the store can be used. + * + * @returns resolves to the store. + */ + startup() { + return __awaiter(this, void 0, void 0, function* () { + // No startup work to do for the memory store. + return this; + }); + } + /** + * Delete all data from this store. + * + * @returns Promise which resolves when the store has been cleared. + */ + deleteAllData() { + return Promise.resolve(); + } + /** + * Look for an existing outgoing room key request, and if none is found, + * add a new one + * + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest}: either the + * same instance as passed in, or the existing one. + */ + getOrAddOutgoingRoomKeyRequest(request) { + const requestBody = request.requestBody; + return utils.promiseTry(() => { + // first see if we already have an entry for this request. + const existing = this._getOutgoingRoomKeyRequest(requestBody); + if (existing) { + // this entry matches the request - return it. + logger_1.logger.log(`already have key request outstanding for ` + + `${requestBody.room_id} / ${requestBody.session_id}: ` + + `not sending another`); + return existing; + } + // we got to the end of the list without finding a match + // - add the new request. + logger_1.logger.log(`enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id); + this.outgoingRoomKeyRequests.push(request); + return request; + }); + } + /** + * Look for an existing room key request + * + * @param requestBody - existing request to look for + * + * @returns resolves to the matching + * {@link OutgoingRoomKeyRequest}, or null if + * not found + */ + getOutgoingRoomKeyRequest(requestBody) { + return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody)); + } + /** + * Looks for existing room key request, and returns the result synchronously. + * + * @internal + * + * @param requestBody - existing request to look for + * + * @returns + * the matching request, or null if not found + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + _getOutgoingRoomKeyRequest(requestBody) { + for (const existing of this.outgoingRoomKeyRequests) { + if (utils.deepCompare(existing.requestBody, requestBody)) { + return existing; + } + } + return null; + } + /** + * Look for room key requests by state + * + * @param wantedStates - list of acceptable states + * + * @returns resolves to the a + * {@link OutgoingRoomKeyRequest}, or null if + * there are no pending requests in those states + */ + getOutgoingRoomKeyRequestByState(wantedStates) { + for (const req of this.outgoingRoomKeyRequests) { + for (const state of wantedStates) { + if (req.state === state) { + return Promise.resolve(req); + } + } + } + return Promise.resolve(null); + } + /** + * + * @returns All OutgoingRoomKeyRequests in state + */ + getAllOutgoingRoomKeyRequestsByState(wantedState) { + return Promise.resolve(this.outgoingRoomKeyRequests.filter((r) => r.state == wantedState)); + } + getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) { + const results = []; + for (const req of this.outgoingRoomKeyRequests) { + for (const state of wantedStates) { + if (req.state === state && + req.recipients.some((recipient) => recipient.userId === userId && recipient.deviceId === deviceId)) { + results.push(req); + } + } + } + return Promise.resolve(results); + } + /** + * Look for an existing room key request by id and state, and update it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * @param updates - name/value map of updates to apply + * + * @returns resolves to + * {@link OutgoingRoomKeyRequest} + * updated request, or null if no matching row was found + */ + updateOutgoingRoomKeyRequest(requestId, expectedState, updates) { + for (const req of this.outgoingRoomKeyRequests) { + if (req.requestId !== requestId) { + continue; + } + if (req.state !== expectedState) { + logger_1.logger.warn(`Cannot update room key request from ${expectedState} ` + + `as it was already updated to ${req.state}`); + return Promise.resolve(null); + } + Object.assign(req, updates); + return Promise.resolve(req); + } + return Promise.resolve(null); + } + /** + * Look for an existing room key request by id and state, and delete it if + * found + * + * @param requestId - ID of request to update + * @param expectedState - state we expect to find the request in + * + * @returns resolves once the operation is completed + */ + deleteOutgoingRoomKeyRequest(requestId, expectedState) { + for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) { + const req = this.outgoingRoomKeyRequests[i]; + if (req.requestId !== requestId) { + continue; + } + if (req.state != expectedState) { + logger_1.logger.warn(`Cannot delete room key request in state ${req.state} ` + `(expected ${expectedState})`); + return Promise.resolve(null); + } + this.outgoingRoomKeyRequests.splice(i, 1); + return Promise.resolve(req); + } + return Promise.resolve(null); + } + // Olm Account + getAccount(txn, func) { + func(this.account); + } + storeAccount(txn, accountPickle) { + this.account = accountPickle; + } + getCrossSigningKeys(txn, func) { + func(this.crossSigningKeys); + } + getSecretStorePrivateKey(txn, func, type) { + const result = this.privateKeys[type]; + func(result || null); + } + storeCrossSigningKeys(txn, keys) { + this.crossSigningKeys = keys; + } + storeSecretStorePrivateKey(txn, type, key) { + this.privateKeys[type] = key; + } + // Olm Sessions + countEndToEndSessions(txn, func) { + func(Object.keys(this.sessions).length); + } + getEndToEndSession(deviceKey, sessionId, txn, func) { + const deviceSessions = this.sessions[deviceKey] || {}; + func(deviceSessions[sessionId] || null); + } + getEndToEndSessions(deviceKey, txn, func) { + func(this.sessions[deviceKey] || {}); + } + getAllEndToEndSessions(txn, func) { + Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => { + Object.entries(deviceSessions).forEach(([sessionId, session]) => { + func(Object.assign(Object.assign({}, session), { deviceKey, + sessionId })); + }); + }); + } + storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) { + let deviceSessions = this.sessions[deviceKey]; + if (deviceSessions === undefined) { + deviceSessions = {}; + this.sessions[deviceKey] = deviceSessions; + } + deviceSessions[sessionId] = sessionInfo; + } + storeEndToEndSessionProblem(deviceKey, type, fixed) { + return __awaiter(this, void 0, void 0, function* () { + const problems = (this.sessionProblems[deviceKey] = this.sessionProblems[deviceKey] || []); + problems.push({ type, fixed, time: Date.now() }); + problems.sort((a, b) => { + return a.time - b.time; + }); + }); + } + getEndToEndSessionProblem(deviceKey, timestamp) { + return __awaiter(this, void 0, void 0, function* () { + const problems = this.sessionProblems[deviceKey] || []; + if (!problems.length) { + return null; + } + const lastProblem = problems[problems.length - 1]; + for (const problem of problems) { + if (problem.time > timestamp) { + return Object.assign({}, problem, { fixed: lastProblem.fixed }); + } + } + if (lastProblem.fixed) { + return null; + } + else { + return lastProblem; + } + }); + } + filterOutNotifiedErrorDevices(devices) { + return __awaiter(this, void 0, void 0, function* () { + const notifiedErrorDevices = this.notifiedErrorDevices; + const ret = []; + for (const device of devices) { + const { userId, deviceInfo } = device; + if (userId in notifiedErrorDevices) { + if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) { + ret.push(device); + (0, utils_1.safeSet)(notifiedErrorDevices[userId], deviceInfo.deviceId, true); + } + } + else { + ret.push(device); + (0, utils_1.safeSet)(notifiedErrorDevices, userId, { [deviceInfo.deviceId]: true }); + } + } + return ret; + }); + } + // Inbound Group Sessions + getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { + const k = senderCurve25519Key + "/" + sessionId; + func(this.inboundGroupSessions[k] || null, this.inboundGroupSessionsWithheld[k] || null); + } + getAllEndToEndInboundGroupSessions(txn, func) { + for (const key of Object.keys(this.inboundGroupSessions)) { + // we can't use split, as the components we are trying to split out + // might themselves contain '/' characters. We rely on the + // senderKey being a (32-byte) curve25519 key, base64-encoded + // (hence 43 characters long). + func({ + senderKey: key.slice(0, 43), + sessionId: key.slice(44), + sessionData: this.inboundGroupSessions[key], + }); + } + func(null); + } + addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + const k = senderCurve25519Key + "/" + sessionId; + if (this.inboundGroupSessions[k] === undefined) { + this.inboundGroupSessions[k] = sessionData; + } + } + storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) { + this.inboundGroupSessions[senderCurve25519Key + "/" + sessionId] = sessionData; + } + storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) { + const k = senderCurve25519Key + "/" + sessionId; + this.inboundGroupSessionsWithheld[k] = sessionData; + } + // Device Data + getEndToEndDeviceData(txn, func) { + func(this.deviceData); + } + storeEndToEndDeviceData(deviceData, txn) { + this.deviceData = deviceData; + } + // E2E rooms + storeEndToEndRoom(roomId, roomInfo, txn) { + this.rooms[roomId] = roomInfo; + } + getEndToEndRooms(txn, func) { + func(this.rooms); + } + getSessionsNeedingBackup(limit) { + const sessions = []; + for (const session in this.sessionsNeedingBackup) { + if (this.inboundGroupSessions[session]) { + sessions.push({ + senderKey: session.slice(0, 43), + sessionId: session.slice(44), + sessionData: this.inboundGroupSessions[session], + }); + if (limit && session.length >= limit) { + break; + } + } + } + return Promise.resolve(sessions); + } + countSessionsNeedingBackup() { + return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length); + } + unmarkSessionsNeedingBackup(sessions) { + for (const session of sessions) { + const sessionKey = session.senderKey + "/" + session.sessionId; + delete this.sessionsNeedingBackup[sessionKey]; + } + return Promise.resolve(); + } + markSessionsNeedingBackup(sessions) { + for (const session of sessions) { + const sessionKey = session.senderKey + "/" + session.sessionId; + this.sessionsNeedingBackup[sessionKey] = true; + } + return Promise.resolve(); + } + addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) { + const sessions = this.sharedHistoryInboundGroupSessions[roomId] || []; + sessions.push([senderKey, sessionId]); + this.sharedHistoryInboundGroupSessions[roomId] = sessions; + } + getSharedHistoryInboundGroupSessions(roomId) { + return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []); + } + addParkedSharedHistory(roomId, parkedData) { + var _a; + const parked = (_a = this.parkedSharedHistory.get(roomId)) !== null && _a !== void 0 ? _a : []; + parked.push(parkedData); + this.parkedSharedHistory.set(roomId, parked); + } + takeParkedSharedHistory(roomId) { + var _a; + const parked = (_a = this.parkedSharedHistory.get(roomId)) !== null && _a !== void 0 ? _a : []; + this.parkedSharedHistory.delete(roomId); + return Promise.resolve(parked); + } + // Session key backups + doTxn(mode, stores, func) { + return Promise.resolve(func(null)); + } +} +exports.MemoryCryptoStore = MemoryCryptoStore; + +},{"../../logger":374,"../../utils":416}],349:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VerificationBase = exports.VerificationEvent = exports.SwitchStartEventError = void 0; +/** + * Base class for verification methods. + */ +const event_1 = require("../../models/event"); +const event_2 = require("../../@types/event"); +const logger_1 = require("../../logger"); +const deviceinfo_1 = require("../deviceinfo"); +const Error_1 = require("./Error"); +const CrossSigning_1 = require("../CrossSigning"); +const typed_event_emitter_1 = require("../../models/typed-event-emitter"); +const timeoutException = new Error("Verification timed out"); +class SwitchStartEventError extends Error { + constructor(startEvent) { + super(); + this.startEvent = startEvent; + } +} +exports.SwitchStartEventError = SwitchStartEventError; +var VerificationEvent; +(function (VerificationEvent) { + VerificationEvent["Cancel"] = "cancel"; +})(VerificationEvent = exports.VerificationEvent || (exports.VerificationEvent = {})); +class VerificationBase extends typed_event_emitter_1.TypedEventEmitter { + /** + * Base class for verification methods. + * + *

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.

+ * + *

Subclasses must have a NAME class property.

+ * + * @param channel - the verification channel to send verification messages over. + * TODO: Channel types + * + * @param baseApis - base matrix api interface + * + * @param userId - the user ID that is being verified + * + * @param deviceId - the device ID that is being verified + * + * @param startEvent - the m.key.verification.start event that + * initiated this verification, if any + * + * @param request - the key verification request object related to + * this verification, if any + */ + constructor(channel, baseApis, userId, deviceId, startEvent, request) { + super(); + this.channel = channel; + this.baseApis = baseApis; + this.userId = userId; + this.deviceId = deviceId; + this.startEvent = startEvent; + this.request = request; + this.cancelled = false; + this._done = false; + this.promise = null; + this.transactionTimeoutTimer = null; + } + get initiatedByMe() { + // if there is no start event yet, + // we probably want to send it, + // which happens if we initiate + if (!this.startEvent) { + return true; + } + const sender = this.startEvent.getSender(); + const content = this.startEvent.getContent(); + return sender === this.baseApis.getUserId() && content.from_device === this.baseApis.getDeviceId(); + } + get hasBeenCancelled() { + return this.cancelled; + } + resetTimer() { + logger_1.logger.info("Refreshing/starting the verification transaction timeout timer"); + if (this.transactionTimeoutTimer !== null) { + clearTimeout(this.transactionTimeoutTimer); + } + this.transactionTimeoutTimer = setTimeout(() => { + if (!this._done && !this.cancelled) { + logger_1.logger.info("Triggering verification timeout"); + this.cancel(timeoutException); + } + }, 10 * 60 * 1000); // 10 minutes + } + endTimer() { + if (this.transactionTimeoutTimer !== null) { + clearTimeout(this.transactionTimeoutTimer); + this.transactionTimeoutTimer = null; + } + } + send(type, uncompletedContent) { + return this.channel.send(type, uncompletedContent); + } + waitForEvent(type) { + if (this._done) { + return Promise.reject(new Error("Verification is already done")); + } + const existingEvent = this.request.getEventFromOtherParty(type); + if (existingEvent) { + return Promise.resolve(existingEvent); + } + this.expectedEvent = type; + return new Promise((resolve, reject) => { + this.resolveEvent = resolve; + this.rejectEvent = reject; + }); + } + canSwitchStartEvent(event) { + return false; + } + switchStartEvent(event) { + if (this.canSwitchStartEvent(event)) { + logger_1.logger.log("Verification Base: switching verification start event", { restartingFlow: !!this.rejectEvent }); + if (this.rejectEvent) { + const reject = this.rejectEvent; + this.rejectEvent = undefined; + reject(new SwitchStartEventError(event)); + } + else { + this.startEvent = event; + } + } + } + handleEvent(e) { + var _a; + if (this._done) { + return; + } + else if (e.getType() === this.expectedEvent) { + // if we receive an expected m.key.verification.done, then just + // ignore it, since we don't need to do anything about it + if (this.expectedEvent !== event_2.EventType.KeyVerificationDone) { + this.expectedEvent = undefined; + this.rejectEvent = undefined; + this.resetTimer(); + (_a = this.resolveEvent) === null || _a === void 0 ? void 0 : _a.call(this, e); + } + } + else if (e.getType() === event_2.EventType.KeyVerificationCancel) { + const reject = this.reject; + this.reject = undefined; + // there is only promise to reject if verify has been called + if (reject) { + const content = e.getContent(); + const { reason, code } = content; + reject(new Error(`Other side cancelled verification ` + `because ${reason} (${code})`)); + } + } + else if (this.expectedEvent) { + // only cancel if there is an event expected. + // if there is no event expected, it means verify() wasn't called + // and we're just replaying the timeline events when syncing + // after a refresh when the events haven't been stored in the cache yet. + const exception = new Error("Unexpected message: expecting " + this.expectedEvent + " but got " + e.getType()); + this.expectedEvent = undefined; + if (this.rejectEvent) { + const reject = this.rejectEvent; + this.rejectEvent = undefined; + reject(exception); + } + this.cancel(exception); + } + } + done() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + this.endTimer(); // always kill the activity timer + if (!this._done) { + this.request.onVerifierFinished(); + (_a = this.resolve) === null || _a === void 0 ? void 0 : _a.call(this); + return (0, CrossSigning_1.requestKeysDuringVerification)(this.baseApis, this.userId, this.deviceId); + } + }); + } + cancel(e) { + this.endTimer(); // always kill the activity timer + if (!this._done) { + this.cancelled = true; + this.request.onVerifierCancelled(); + if (this.userId && this.deviceId) { + // send a cancellation to the other user (if it wasn't + // cancelled by the other user) + if (e === timeoutException) { + const timeoutEvent = (0, Error_1.newTimeoutError)(); + this.send(timeoutEvent.getType(), timeoutEvent.getContent()); + } + else if (e instanceof event_1.MatrixEvent) { + const sender = e.getSender(); + if (sender !== this.userId) { + const content = e.getContent(); + if (e.getType() === event_2.EventType.KeyVerificationCancel) { + content.code = content.code || "m.unknown"; + content.reason = content.reason || content.body || "Unknown reason"; + this.send(event_2.EventType.KeyVerificationCancel, content); + } + else { + this.send(event_2.EventType.KeyVerificationCancel, { + code: "m.unknown", + reason: content.body || "Unknown reason", + }); + } + } + } + else { + this.send(event_2.EventType.KeyVerificationCancel, { + code: "m.unknown", + reason: e.toString(), + }); + } + } + 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 { + // FIXME: this causes an "Uncaught promise" console message + // if nothing ends up chaining this promise. + this.promise = Promise.reject(e); + } + // Also emit a 'cancel' event that the app can listen for to detect cancellation + // before calling verify() + this.emit(VerificationEvent.Cancel, e); + } + } + /** + * Begin the key verification + * + * @returns Promise which resolves when the verification has + * completed. + */ + verify() { + if (this.promise) + return this.promise; + this.promise = new Promise((resolve, reject) => { + this.resolve = (...args) => { + this._done = true; + this.endTimer(); + resolve(...args); + }; + this.reject = (e) => { + this._done = true; + this.endTimer(); + reject(e); + }; + }); + if (this.doVerification && !this.started) { + this.started = true; + this.resetTimer(); // restart the timeout + new Promise((resolve, reject) => { + var _a; + const crossSignId = (_a = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(this.userId)) === null || _a === void 0 ? void 0 : _a.getId(); + if (crossSignId === this.deviceId) { + reject(new Error("Device ID is the same as the cross-signing ID")); + } + resolve(); + }) + .then(() => this.doVerification()) + .then(this.done.bind(this), this.cancel.bind(this)); + } + return this.promise; + } + verifyKeys(userId, keys, verifier) { + return __awaiter(this, void 0, void 0, function* () { + // 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 + const verifiedDevices = []; + for (const [keyId, keyInfo] of Object.entries(keys)) { + const deviceId = keyId.split(":", 2)[1]; + const device = this.baseApis.getStoredDevice(userId, deviceId); + if (device) { + verifier(keyId, device, keyInfo); + verifiedDevices.push([deviceId, keyId, device.keys[keyId]]); + } + else { + const crossSigningInfo = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(userId); + if (crossSigningInfo && crossSigningInfo.getId() === deviceId) { + verifier(keyId, deviceinfo_1.DeviceInfo.fromStorage({ + keys: { + [keyId]: deviceId, + }, + }, deviceId), keyInfo); + verifiedDevices.push([deviceId, keyId, deviceId]); + } + else { + logger_1.logger.warn(`verification: Could not find device ${deviceId} to verify`); + } + } + } + // if none of the keys could be verified, then error because the app + // should be informed about that + if (!verifiedDevices.length) { + throw new Error("No devices could be verified"); + } + logger_1.logger.info("Verification completed! Marking devices verified: ", verifiedDevices); + // TODO: There should probably be a batch version of this, otherwise it's going + // to upload each signature in a separate API call which is silly because the + // API supports as many signatures as you like. + for (const [deviceId, keyId, key] of verifiedDevices) { + yield this.baseApis.crypto.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key }); + } + // if one of the user's own devices is being marked as verified / unverified, + // check the key backup status, since whether or not we use this depends on + // whether it has a signature from a verified device + if (userId == this.baseApis.credentials.userId) { + yield this.baseApis.checkKeyBackup(); + } + }); + } + get events() { + return undefined; + } +} +exports.VerificationBase = VerificationBase; + +},{"../../@types/event":306,"../../logger":374,"../../models/event":383,"../../models/typed-event-emitter":395,"../CrossSigning":324,"../deviceinfo":340,"./Error":350}],350:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.errorFromEvent = exports.newInvalidMessageError = exports.newKeyMismatchError = exports.newUnexpectedMessageError = exports.newUnknownMethodError = exports.newTimeoutError = exports.newUserCancelledError = exports.errorFactory = exports.newVerificationError = void 0; +/** + * Error messages. + */ +const event_1 = require("../../models/event"); +const event_2 = require("../../@types/event"); +function newVerificationError(code, reason, extraData) { + const content = Object.assign({}, { code, reason }, extraData); + return new event_1.MatrixEvent({ + type: event_2.EventType.KeyVerificationCancel, + content, + }); +} +exports.newVerificationError = newVerificationError; +function errorFactory(code, reason) { + return function (extraData) { + return newVerificationError(code, reason, extraData); + }; +} +exports.errorFactory = errorFactory; +/** + * The verification was cancelled by the user. + */ +exports.newUserCancelledError = errorFactory("m.user", "Cancelled by user"); +/** + * The verification timed out. + */ +exports.newTimeoutError = errorFactory("m.timeout", "Timed out"); +/** + * An unknown method was selected. + */ +exports.newUnknownMethodError = errorFactory("m.unknown_method", "Unknown method"); +/** + * An unexpected message was sent. + */ +exports.newUnexpectedMessageError = errorFactory("m.unexpected_message", "Unexpected message"); +/** + * The key does not match. + */ +exports.newKeyMismatchError = errorFactory("m.key_mismatch", "Key mismatch"); +/** + * An invalid message was sent. + */ +exports.newInvalidMessageError = errorFactory("m.invalid_message", "Invalid message"); +function errorFromEvent(event) { + const content = event.getContent(); + if (content) { + const { code, reason } = content; + return { code, reason }; + } + else { + return { code: "Unknown error", reason: "m.unknown" }; + } +} +exports.errorFromEvent = errorFromEvent; + +},{"../../@types/event":306,"../../models/event":383}],351:[function(require,module,exports){ +"use strict"; +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IllegalMethod = void 0; +/** + * Verification method that is illegal to have (cannot possibly + * do verification with this method). + */ +const Base_1 = require("./Base"); +class IllegalMethod extends Base_1.VerificationBase { + constructor() { + super(...arguments); + this.doVerification = () => __awaiter(this, void 0, void 0, function* () { + throw new Error("Verification is not possible with this method"); + }); + } + static factory(channel, baseApis, userId, deviceId, startEvent, request) { + return new IllegalMethod(channel, baseApis, userId, deviceId, startEvent, request); + } + // eslint-disable-next-line @typescript-eslint/naming-convention + static get NAME() { + // Typically the name will be something else, but to complete + // the contract we offer a default one here. + return "org.matrix.illegal_method"; + } +} +exports.IllegalMethod = IllegalMethod; + +},{"./Base":349}],352:[function(require,module,exports){ +(function (global,Buffer){(function (){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.QRCodeData = exports.ReciprocateQRCode = exports.QrCodeEvent = exports.SCAN_QR_CODE_METHOD = exports.SHOW_QR_CODE_METHOD = void 0; +/** + * QR code key verification. + */ +const Base_1 = require("./Base"); +const Error_1 = require("./Error"); +const olmlib_1 = require("../olmlib"); +const logger_1 = require("../../logger"); +exports.SHOW_QR_CODE_METHOD = "m.qr_code.show.v1"; +exports.SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1"; +var QrCodeEvent; +(function (QrCodeEvent) { + QrCodeEvent["ShowReciprocateQr"] = "show_reciprocate_qr"; +})(QrCodeEvent = exports.QrCodeEvent || (exports.QrCodeEvent = {})); +class ReciprocateQRCode extends Base_1.VerificationBase { + constructor() { + super(...arguments); + this.doVerification = () => __awaiter(this, void 0, void 0, function* () { + if (!this.startEvent) { + // TODO: Support scanning QR codes + throw new Error("It is not currently possible to start verification" + "with this method yet."); + } + const { qrCodeData } = this.request; + // 1. check the secret + if (this.startEvent.getContent()["secret"] !== (qrCodeData === null || qrCodeData === void 0 ? void 0 : qrCodeData.encodedSharedSecret)) { + throw (0, Error_1.newKeyMismatchError)(); + } + // 2. ask if other user shows shield as well + yield new Promise((resolve, reject) => { + this.reciprocateQREvent = { + confirm: resolve, + cancel: () => reject((0, Error_1.newUserCancelledError)()), + }; + this.emit(QrCodeEvent.ShowReciprocateQr, this.reciprocateQREvent); + }); + // 3. determine key to sign / mark as trusted + const keys = {}; + switch (qrCodeData === null || qrCodeData === void 0 ? void 0 : qrCodeData.mode) { + case Mode.VerifyOtherUser: { + // add master key to keys to be signed, only if we're not doing self-verification + const masterKey = qrCodeData.otherUserMasterKey; + keys[`ed25519:${masterKey}`] = masterKey; + break; + } + case Mode.VerifySelfTrusted: { + const deviceId = this.request.targetDevice.deviceId; + keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey; + break; + } + case Mode.VerifySelfUntrusted: { + const masterKey = qrCodeData.myMasterKey; + keys[`ed25519:${masterKey}`] = masterKey; + break; + } + } + // 4. sign the key (or mark own MSK as verified in case of MODE_VERIFY_SELF_TRUSTED) + yield this.verifyKeys(this.userId, keys, (keyId, device, keyInfo) => { + // make sure the device has the expected keys + const targetKey = keys[keyId]; + if (!targetKey) + throw (0, Error_1.newKeyMismatchError)(); + if (keyInfo !== targetKey) { + logger_1.logger.error("key ID from key info does not match"); + throw (0, Error_1.newKeyMismatchError)(); + } + for (const deviceKeyId in device.keys) { + if (!deviceKeyId.startsWith("ed25519")) + continue; + const deviceTargetKey = keys[deviceKeyId]; + if (!deviceTargetKey) + throw (0, Error_1.newKeyMismatchError)(); + if (device.keys[deviceKeyId] !== deviceTargetKey) { + logger_1.logger.error("master key does not match"); + throw (0, Error_1.newKeyMismatchError)(); + } + } + }); + }); + } + static factory(channel, baseApis, userId, deviceId, startEvent, request) { + return new ReciprocateQRCode(channel, baseApis, userId, deviceId, startEvent, request); + } + // eslint-disable-next-line @typescript-eslint/naming-convention + static get NAME() { + return "m.reciprocate.v1"; + } +} +exports.ReciprocateQRCode = ReciprocateQRCode; +const CODE_VERSION = 0x02; // the version of binary QR codes we support +const BINARY_PREFIX = "MATRIX"; // ASCII, used to prefix the binary format +var Mode; +(function (Mode) { + Mode[Mode["VerifyOtherUser"] = 0] = "VerifyOtherUser"; + Mode[Mode["VerifySelfTrusted"] = 1] = "VerifySelfTrusted"; + Mode[Mode["VerifySelfUntrusted"] = 2] = "VerifySelfUntrusted"; +})(Mode || (Mode = {})); +class QRCodeData { + constructor(mode, sharedSecret, + // only set when mode is MODE_VERIFY_OTHER_USER, master key of other party at time of generating QR code + otherUserMasterKey, + // only set when mode is MODE_VERIFY_SELF_TRUSTED, device key of other party at time of generating QR code + otherDeviceKey, + // only set when mode is MODE_VERIFY_SELF_UNTRUSTED, own master key at time of generating QR code + myMasterKey, buffer) { + this.mode = mode; + this.sharedSecret = sharedSecret; + this.otherUserMasterKey = otherUserMasterKey; + this.otherDeviceKey = otherDeviceKey; + this.myMasterKey = myMasterKey; + this.buffer = buffer; + } + static create(request, client) { + return __awaiter(this, void 0, void 0, function* () { + const sharedSecret = QRCodeData.generateSharedSecret(); + const mode = QRCodeData.determineMode(request, client); + let otherUserMasterKey = null; + let otherDeviceKey = null; + let myMasterKey = null; + if (mode === Mode.VerifyOtherUser) { + const otherUserCrossSigningInfo = client.getStoredCrossSigningForUser(request.otherUserId); + otherUserMasterKey = otherUserCrossSigningInfo.getId("master"); + } + else if (mode === Mode.VerifySelfTrusted) { + otherDeviceKey = yield QRCodeData.getOtherDeviceKey(request, client); + } + else if (mode === Mode.VerifySelfUntrusted) { + const myUserId = client.getUserId(); + const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId); + myMasterKey = myCrossSigningInfo.getId("master"); + } + const qrData = QRCodeData.generateQrData(request, client, mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey); + const buffer = QRCodeData.generateBuffer(qrData); + return new QRCodeData(mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey, buffer); + }); + } + /** + * The unpadded base64 encoded shared secret. + */ + get encodedSharedSecret() { + return this.sharedSecret; + } + getBuffer() { + return this.buffer; + } + static generateSharedSecret() { + const secretBytes = new Uint8Array(11); + global.crypto.getRandomValues(secretBytes); + return (0, olmlib_1.encodeUnpaddedBase64)(secretBytes); + } + static getOtherDeviceKey(request, client) { + return __awaiter(this, void 0, void 0, function* () { + const myUserId = client.getUserId(); + const otherDevice = request.targetDevice; + const device = otherDevice.deviceId ? client.getStoredDevice(myUserId, otherDevice.deviceId) : undefined; + if (!device) { + throw new Error("could not find device " + (otherDevice === null || otherDevice === void 0 ? void 0 : otherDevice.deviceId)); + } + return device.getFingerprint(); + }); + } + static determineMode(request, client) { + const myUserId = client.getUserId(); + const otherUserId = request.otherUserId; + let mode = Mode.VerifyOtherUser; + if (myUserId === otherUserId) { + // Mode changes depending on whether or not we trust the master cross signing key + const myTrust = client.checkUserTrust(myUserId); + if (myTrust.isCrossSigningVerified()) { + mode = Mode.VerifySelfTrusted; + } + else { + mode = Mode.VerifySelfUntrusted; + } + } + return mode; + } + static generateQrData(request, client, mode, encodedSharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey) { + const myUserId = client.getUserId(); + const transactionId = request.channel.transactionId; + const qrData = { + prefix: BINARY_PREFIX, + version: CODE_VERSION, + mode, + transactionId, + firstKeyB64: "", + secondKeyB64: "", + secretB64: encodedSharedSecret, + }; + const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId); + if (mode === Mode.VerifyOtherUser) { + // First key is our master cross signing key + qrData.firstKeyB64 = myCrossSigningInfo.getId("master"); + // Second key is the other user's master cross signing key + qrData.secondKeyB64 = otherUserMasterKey; + } + else if (mode === Mode.VerifySelfTrusted) { + // First key is our master cross signing key + qrData.firstKeyB64 = myCrossSigningInfo.getId("master"); + qrData.secondKeyB64 = otherDeviceKey; + } + else if (mode === Mode.VerifySelfUntrusted) { + // First key is our device's key + qrData.firstKeyB64 = client.getDeviceEd25519Key(); + // Second key is what we think our master cross signing key is + qrData.secondKeyB64 = myMasterKey; + } + return qrData; + } + static generateBuffer(qrData) { + let buf = Buffer.alloc(0); // we'll concat our way through life + const appendByte = (b) => { + const tmpBuf = Buffer.from([b]); + buf = Buffer.concat([buf, tmpBuf]); + }; + const appendInt = (i) => { + const tmpBuf = Buffer.alloc(2); + tmpBuf.writeInt16BE(i, 0); + buf = Buffer.concat([buf, tmpBuf]); + }; + const appendStr = (s, enc, withLengthPrefix = true) => { + const tmpBuf = Buffer.from(s, enc); + if (withLengthPrefix) + appendInt(tmpBuf.byteLength); + buf = Buffer.concat([buf, tmpBuf]); + }; + const appendEncBase64 = (b64) => { + const b = (0, olmlib_1.decodeBase64)(b64); + const tmpBuf = Buffer.from(b); + buf = Buffer.concat([buf, tmpBuf]); + }; + // Actually build the buffer for the QR code + appendStr(qrData.prefix, "ascii", false); + appendByte(qrData.version); + appendByte(qrData.mode); + appendStr(qrData.transactionId, "utf-8"); + appendEncBase64(qrData.firstKeyB64); + appendEncBase64(qrData.secondKeyB64); + appendEncBase64(qrData.secretB64); + return buf; + } +} +exports.QRCodeData = QRCodeData; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) + +},{"../../logger":374,"../olmlib":343,"./Base":349,"./Error":350,"buffer":68}],353:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SAS = exports.SasEvent = void 0; +/** + * Short Authentication String (SAS) verification. + */ +const another_json_1 = __importDefault(require("another-json")); +const Base_1 = require("./Base"); +const Error_1 = require("./Error"); +const logger_1 = require("../../logger"); +const SASDecimal_1 = require("./SASDecimal"); +const event_1 = require("../../@types/event"); +const START_TYPE = event_1.EventType.KeyVerificationStart; +const EVENTS = [event_1.EventType.KeyVerificationAccept, event_1.EventType.KeyVerificationKey, event_1.EventType.KeyVerificationMac]; +let olmutil; +const newMismatchedSASError = (0, Error_1.errorFactory)("m.mismatched_sas", "Mismatched short authentication string"); +const newMismatchedCommitmentError = (0, Error_1.errorFactory)("m.mismatched_commitment", "Mismatched commitment"); +const emojiMapping = [ + ["🐶", "dog"], + ["🐱", "cat"], + ["🦁", "lion"], + ["🐎", "horse"], + ["🦄", "unicorn"], + ["🐷", "pig"], + ["🐘", "elephant"], + ["🐰", "rabbit"], + ["🐼", "panda"], + ["🐓", "rooster"], + ["🐧", "penguin"], + ["🐢", "turtle"], + ["🐟", "fish"], + ["🐙", "octopus"], + ["🦋", "butterfly"], + ["🌷", "flower"], + ["🌳", "tree"], + ["🌵", "cactus"], + ["🍄", "mushroom"], + ["🌏", "globe"], + ["🌙", "moon"], + ["☁️", "cloud"], + ["🔥", "fire"], + ["🍌", "banana"], + ["🍎", "apple"], + ["🍓", "strawberry"], + ["🌽", "corn"], + ["🍕", "pizza"], + ["🎂", "cake"], + ["❤️", "heart"], + ["🙂", "smiley"], + ["🤖", "robot"], + ["🎩", "hat"], + ["👓", "glasses"], + ["🔧", "spanner"], + ["🎅", "santa"], + ["👍", "thumbs up"], + ["☂️", "umbrella"], + ["⌛", "hourglass"], + ["⏰", "clock"], + ["🎁", "gift"], + ["💡", "light bulb"], + ["📕", "book"], + ["✏️", "pencil"], + ["📎", "paperclip"], + ["✂️", "scissors"], + ["🔒", "lock"], + ["🔑", "key"], + ["🔨", "hammer"], + ["☎️", "telephone"], + ["🏁", "flag"], + ["🚂", "train"], + ["🚲", "bicycle"], + ["✈️", "aeroplane"], + ["🚀", "rocket"], + ["🏆", "trophy"], + ["⚽", "ball"], + ["🎸", "guitar"], + ["🎺", "trumpet"], + ["🔔", "bell"], + ["⚓️", "anchor"], + ["🎧", "headphones"], + ["📁", "folder"], + ["📌", "pin"], // 63 +]; +function generateEmojiSas(sasBytes) { + const emojis = [ + // just like base64 encoding + sasBytes[0] >> 2, + ((sasBytes[0] & 0x3) << 4) | (sasBytes[1] >> 4), + ((sasBytes[1] & 0xf) << 2) | (sasBytes[2] >> 6), + sasBytes[2] & 0x3f, + sasBytes[3] >> 2, + ((sasBytes[3] & 0x3) << 4) | (sasBytes[4] >> 4), + ((sasBytes[4] & 0xf) << 2) | (sasBytes[5] >> 6), + ]; + return emojis.map((num) => emojiMapping[num]); +} +const sasGenerators = { + decimal: SASDecimal_1.generateDecimalSas, + emoji: generateEmojiSas, +}; +function generateSas(sasBytes, methods) { + const sas = {}; + for (const method of methods) { + if (method in sasGenerators) { + // @ts-ignore - ts doesn't like us mixing types like this + sas[method] = sasGenerators[method](Array.from(sasBytes)); + } + } + return sas; +} +const macMethods = { + "hkdf-hmac-sha256": "calculate_mac", + "org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64", + "hkdf-hmac-sha256.v2": "calculate_mac_fixed_base64", + "hmac-sha256": "calculate_mac_long_kdf", +}; +function calculateMAC(olmSAS, method) { + return function (input, info) { + const mac = olmSAS[macMethods[method]](input, info); + logger_1.logger.log("SAS calculateMAC:", method, [input, info], mac); + return mac; + }; +} +const calculateKeyAgreement = { + // eslint-disable-next-line @typescript-eslint/naming-convention + "curve25519-hkdf-sha256": function (sas, olmSAS, bytes) { + const ourInfo = `${sas.baseApis.getUserId()}|${sas.baseApis.deviceId}|` + `${sas.ourSASPubKey}|`; + const theirInfo = `${sas.userId}|${sas.deviceId}|${sas.theirSASPubKey}|`; + const sasInfo = "MATRIX_KEY_VERIFICATION_SAS|" + + (sas.initiatedByMe ? ourInfo + theirInfo : theirInfo + ourInfo) + + sas.channel.transactionId; + return olmSAS.generate_bytes(sasInfo, bytes); + }, + "curve25519": function (sas, olmSAS, bytes) { + const ourInfo = `${sas.baseApis.getUserId()}${sas.baseApis.deviceId}`; + const theirInfo = `${sas.userId}${sas.deviceId}`; + const sasInfo = "MATRIX_KEY_VERIFICATION_SAS" + + (sas.initiatedByMe ? ourInfo + theirInfo : theirInfo + ourInfo) + + sas.channel.transactionId; + return olmSAS.generate_bytes(sasInfo, bytes); + }, +}; +/* lists of algorithms/methods that are supported. The key agreement, hashes, + * and MAC lists should be sorted in order of preference (most preferred + * first). + */ +const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"]; +const HASHES_LIST = ["sha256"]; +const MAC_LIST = [ + "hkdf-hmac-sha256.v2", + "org.matrix.msc3783.hkdf-hmac-sha256", + "hkdf-hmac-sha256", + "hmac-sha256", +]; +const SAS_LIST = Object.keys(sasGenerators); +const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST); +const HASHES_SET = new Set(HASHES_LIST); +const MAC_SET = new Set(MAC_LIST); +const SAS_SET = new Set(SAS_LIST); +function intersection(anArray, aSet) { + return Array.isArray(anArray) ? anArray.filter((x) => aSet.has(x)) : []; +} +var SasEvent; +(function (SasEvent) { + SasEvent["ShowSas"] = "show_sas"; +})(SasEvent = exports.SasEvent || (exports.SasEvent = {})); +class SAS extends Base_1.VerificationBase { + constructor() { + super(...arguments); + this.doVerification = () => __awaiter(this, void 0, void 0, function* () { + yield global.Olm.init(); + olmutil = olmutil || new global.Olm.Utility(); + // make sure user's keys are downloaded + yield this.baseApis.downloadKeys([this.userId]); + let retry = false; + do { + try { + if (this.initiatedByMe) { + return yield this.doSendVerification(); + } + else { + return yield this.doRespondVerification(); + } + } + catch (err) { + if (err instanceof Base_1.SwitchStartEventError) { + // this changes what initiatedByMe returns + this.startEvent = err.startEvent; + retry = true; + } + else { + throw err; + } + } + } while (retry); + }); + } + // eslint-disable-next-line @typescript-eslint/naming-convention + static get NAME() { + return "m.sas.v1"; + } + get events() { + return EVENTS; + } + canSwitchStartEvent(event) { + if (event.getType() !== START_TYPE) { + return false; + } + const content = event.getContent(); + return (content === null || content === void 0 ? void 0 : content.method) === SAS.NAME && !!this.waitingForAccept; + } + sendStart() { + return __awaiter(this, void 0, void 0, function* () { + const startContent = this.channel.completeContent(START_TYPE, { + method: SAS.NAME, + from_device: this.baseApis.deviceId, + key_agreement_protocols: KEY_AGREEMENT_LIST, + hashes: HASHES_LIST, + message_authentication_codes: MAC_LIST, + // FIXME: allow app to specify what SAS methods can be used + short_authentication_string: SAS_LIST, + }); + yield this.channel.sendCompleted(START_TYPE, startContent); + return startContent; + }); + } + verifyAndCheckMAC(keyAgreement, sasMethods, olmSAS, macMethod) { + return __awaiter(this, void 0, void 0, function* () { + const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6); + const verifySAS = new Promise((resolve, reject) => { + this.sasEvent = { + sas: generateSas(sasBytes, sasMethods), + confirm: () => __awaiter(this, void 0, void 0, function* () { + try { + yield this.sendMAC(olmSAS, macMethod); + resolve(); + } + catch (err) { + reject(err); + } + }), + cancel: () => reject((0, Error_1.newUserCancelledError)()), + mismatch: () => reject(newMismatchedSASError()), + }; + this.emit(SasEvent.ShowSas, this.sasEvent); + }); + const [e] = yield Promise.all([ + this.waitForEvent(event_1.EventType.KeyVerificationMac).then((e) => { + // we don't expect any more messages from the other + // party, and they may send a m.key.verification.done + // when they're done on their end + this.expectedEvent = event_1.EventType.KeyVerificationDone; + return e; + }), + verifySAS, + ]); + const content = e.getContent(); + yield this.checkMAC(olmSAS, content, macMethod); + }); + } + doSendVerification() { + return __awaiter(this, void 0, void 0, function* () { + this.waitingForAccept = true; + let startContent; + if (this.startEvent) { + startContent = this.channel.completedContentFromEvent(this.startEvent); + } + else { + startContent = yield this.sendStart(); + } + // we might have switched to a different start event, + // but was we didn't call _waitForEvent there was no + // call that could throw yet. So check manually that + // we're still on the initiator side + if (!this.initiatedByMe) { + throw new Base_1.SwitchStartEventError(this.startEvent); + } + let e; + try { + e = yield this.waitForEvent(event_1.EventType.KeyVerificationAccept); + } + finally { + this.waitingForAccept = false; + } + let content = e.getContent(); + const sasMethods = intersection(content.short_authentication_string, SAS_SET); + if (!(KEY_AGREEMENT_SET.has(content.key_agreement_protocol) && + HASHES_SET.has(content.hash) && + MAC_SET.has(content.message_authentication_code) && + sasMethods.length)) { + throw (0, Error_1.newUnknownMethodError)(); + } + if (typeof content.commitment !== "string") { + throw (0, Error_1.newInvalidMessageError)(); + } + const keyAgreement = content.key_agreement_protocol; + const macMethod = content.message_authentication_code; + const hashCommitment = content.commitment; + const olmSAS = new global.Olm.SAS(); + try { + this.ourSASPubKey = olmSAS.get_pubkey(); + yield this.send(event_1.EventType.KeyVerificationKey, { + key: this.ourSASPubKey, + }); + e = yield this.waitForEvent(event_1.EventType.KeyVerificationKey); + // FIXME: make sure event is properly formed + content = e.getContent(); + const commitmentStr = content.key + another_json_1.default.stringify(startContent); + // TODO: use selected hash function (when we support multiple) + if (olmutil.sha256(commitmentStr) !== hashCommitment) { + throw newMismatchedCommitmentError(); + } + this.theirSASPubKey = content.key; + olmSAS.set_their_key(content.key); + yield this.verifyAndCheckMAC(keyAgreement, sasMethods, olmSAS, macMethod); + } + finally { + olmSAS.free(); + } + }); + } + doRespondVerification() { + return __awaiter(this, void 0, void 0, function* () { + // as m.related_to is not included in the encrypted content in e2e rooms, + // we need to make sure it is added + let content = this.channel.completedContentFromEvent(this.startEvent); + // Note: we intersect using our pre-made lists, rather than the sets, + // so that the result will be in our order of preference. Then + // fetching the first element from the array will give our preferred + // method out of the ones offered by the other party. + const keyAgreement = intersection(KEY_AGREEMENT_LIST, new Set(content.key_agreement_protocols))[0]; + const hashMethod = intersection(HASHES_LIST, new Set(content.hashes))[0]; + const macMethod = intersection(MAC_LIST, new Set(content.message_authentication_codes))[0]; + // FIXME: allow app to specify what SAS methods can be used + const sasMethods = intersection(content.short_authentication_string, SAS_SET); + if (!(keyAgreement !== undefined && hashMethod !== undefined && macMethod !== undefined && sasMethods.length)) { + throw (0, Error_1.newUnknownMethodError)(); + } + const olmSAS = new global.Olm.SAS(); + try { + const commitmentStr = olmSAS.get_pubkey() + another_json_1.default.stringify(content); + yield this.send(event_1.EventType.KeyVerificationAccept, { + key_agreement_protocol: keyAgreement, + hash: hashMethod, + message_authentication_code: macMethod, + short_authentication_string: sasMethods, + // TODO: use selected hash function (when we support multiple) + commitment: olmutil.sha256(commitmentStr), + }); + const e = yield this.waitForEvent(event_1.EventType.KeyVerificationKey); + // FIXME: make sure event is properly formed + content = e.getContent(); + this.theirSASPubKey = content.key; + olmSAS.set_their_key(content.key); + this.ourSASPubKey = olmSAS.get_pubkey(); + yield this.send(event_1.EventType.KeyVerificationKey, { + key: this.ourSASPubKey, + }); + yield this.verifyAndCheckMAC(keyAgreement, sasMethods, olmSAS, macMethod); + } + finally { + olmSAS.free(); + } + }); + } + sendMAC(olmSAS, method) { + const mac = {}; + const keyList = []; + const baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + + this.baseApis.getUserId() + + this.baseApis.deviceId + + this.userId + + this.deviceId + + this.channel.transactionId; + const deviceKeyId = `ed25519:${this.baseApis.deviceId}`; + mac[deviceKeyId] = calculateMAC(olmSAS, method)(this.baseApis.getDeviceEd25519Key(), baseInfo + deviceKeyId); + keyList.push(deviceKeyId); + const crossSigningId = this.baseApis.getCrossSigningId(); + if (crossSigningId) { + const crossSigningKeyId = `ed25519:${crossSigningId}`; + mac[crossSigningKeyId] = calculateMAC(olmSAS, method)(crossSigningId, baseInfo + crossSigningKeyId); + keyList.push(crossSigningKeyId); + } + const keys = calculateMAC(olmSAS, method)(keyList.sort().join(","), baseInfo + "KEY_IDS"); + return this.send(event_1.EventType.KeyVerificationMac, { mac, keys }); + } + checkMAC(olmSAS, content, method) { + return __awaiter(this, void 0, void 0, function* () { + const baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + + this.userId + + this.deviceId + + this.baseApis.getUserId() + + this.baseApis.deviceId + + this.channel.transactionId; + if (content.keys !== + calculateMAC(olmSAS, method)(Object.keys(content.mac).sort().join(","), baseInfo + "KEY_IDS")) { + throw (0, Error_1.newKeyMismatchError)(); + } + yield this.verifyKeys(this.userId, content.mac, (keyId, device, keyInfo) => { + if (keyInfo !== calculateMAC(olmSAS, method)(device.keys[keyId], baseInfo + keyId)) { + throw (0, Error_1.newKeyMismatchError)(); + } + }); + }); + } +} +exports.SAS = SAS; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../../@types/event":306,"../../logger":374,"./Base":349,"./Error":350,"./SASDecimal":354,"another-json":1}],354:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateDecimalSas = void 0; +/** + * Implementation of decimal encoding of SAS as per: + * https://spec.matrix.org/v1.4/client-server-api/#sas-method-decimal + * @param sasBytes - the five bytes generated by HKDF + * @returns the derived three numbers between 1000 and 9191 inclusive + */ +function generateDecimalSas(sasBytes) { + /* + * +--------+--------+--------+--------+--------+ + * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | + * +--------+--------+--------+--------+--------+ + * bits: 87654321 87654321 87654321 87654321 87654321 + * \____________/\_____________/\____________/ + * 1st number 2nd number 3rd number + */ + return [ + ((sasBytes[0] << 5) | (sasBytes[1] >> 3)) + 1000, + (((sasBytes[1] & 0x7) << 10) | (sasBytes[2] << 2) | (sasBytes[3] >> 6)) + 1000, + (((sasBytes[3] & 0x3f) << 7) | (sasBytes[4] >> 1)) + 1000, + ]; +} +exports.generateDecimalSas = generateDecimalSas; + +},{}],355:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InRoomRequests = exports.InRoomChannel = void 0; +const VerificationRequest_1 = require("./VerificationRequest"); +const logger_1 = require("../../../logger"); +const event_1 = require("../../../@types/event"); +const MESSAGE_TYPE = event_1.EventType.RoomMessage; +const M_REFERENCE = "m.reference"; +const M_RELATES_TO = "m.relates_to"; +/** + * A key verification channel that sends verification events in the timeline of a room. + * Uses the event id of the initial m.key.verification.request event as a transaction id. + */ +class InRoomChannel { + /** + * @param client - the matrix client, to send messages with and get current user & device from. + * @param roomId - id of the room where verification events should be posted in, should be a DM with the given user. + * @param userId - id of user that the verification request is directed at, should be present in the room. + */ + constructor(client, roomId, userId) { + this.client = client; + this.roomId = roomId; + this.userId = userId; + } + get receiveStartFromOtherDevices() { + return true; + } + /** The transaction id generated/used by this verification channel */ + get transactionId() { + return this.requestEventId; + } + static getOtherPartyUserId(event, client) { + const type = InRoomChannel.getEventType(event); + if (type !== VerificationRequest_1.REQUEST_TYPE) { + return; + } + const ownUserId = client.getUserId(); + const sender = event.getSender(); + const content = event.getContent(); + const receiver = content.to; + if (sender === ownUserId) { + return receiver; + } + else if (receiver === ownUserId) { + return sender; + } + } + /** + * @param event - the event to get the timestamp of + * @returns the timestamp when the event was sent + */ + getTimestamp(event) { + return event.getTs(); + } + /** + * Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel + * @param type - the event type to check + * @returns boolean flag + */ + static canCreateRequest(type) { + return type === VerificationRequest_1.REQUEST_TYPE; + } + canCreateRequest(type) { + return InRoomChannel.canCreateRequest(type); + } + /** + * Extract the transaction id used by a given key verification event, if any + * @param event - the event + * @returns the transaction id + */ + static getTransactionId(event) { + if (InRoomChannel.getEventType(event) === VerificationRequest_1.REQUEST_TYPE) { + return event.getId(); + } + else { + const relation = event.getRelation(); + if ((relation === null || relation === void 0 ? void 0 : relation.rel_type) === M_REFERENCE) { + return relation.event_id; + } + } + } + /** + * Checks whether this event is a well-formed key verification event. + * This only does checks that don't rely on the current state of a potentially already channel + * so we can prevent channels being created by invalid events. + * `handleEvent` can do more checks and choose to ignore invalid events. + * @param event - the event to validate + * @param client - the client to get the current user and device id from + * @returns whether the event is valid and should be passed to handleEvent + */ + static validateEvent(event, client) { + const txnId = InRoomChannel.getTransactionId(event); + if (typeof txnId !== "string" || txnId.length === 0) { + return false; + } + const type = InRoomChannel.getEventType(event); + const content = event.getContent(); + // from here on we're fairly sure that this is supposed to be + // part of a verification request, so be noisy when rejecting something + if (type === VerificationRequest_1.REQUEST_TYPE) { + if (!content || typeof content.to !== "string" || !content.to.length) { + logger_1.logger.log("InRoomChannel: validateEvent: " + "no valid to " + (content && content.to)); + return false; + } + // ignore requests that are not direct to or sent by the syncing user + if (!InRoomChannel.getOtherPartyUserId(event, client)) { + logger_1.logger.log("InRoomChannel: validateEvent: " + + `not directed to or sent by me: ${event.getSender()}` + + `, ${content && content.to}`); + return false; + } + } + return VerificationRequest_1.VerificationRequest.validateEvent(type, event, client); + } + /** + * As m.key.verification.request events are as m.room.message events with the InRoomChannel + * to have a fallback message in non-supporting clients, we map the real event type + * to the symbolic one to keep things in unison with ToDeviceChannel + * @param event - the event to get the type of + * @returns the "symbolic" event type + */ + static getEventType(event) { + const type = event.getType(); + if (type === MESSAGE_TYPE) { + const content = event.getContent(); + if (content) { + const { msgtype } = content; + if (msgtype === VerificationRequest_1.REQUEST_TYPE) { + return VerificationRequest_1.REQUEST_TYPE; + } + } + } + if (type && type !== VerificationRequest_1.REQUEST_TYPE) { + return type; + } + else { + return ""; + } + } + /** + * Changes the state of the channel, request, and verifier in response to a key verification event. + * @param event - to handle + * @param request - the request to forward handling to + * @param isLiveEvent - whether this is an even received through sync or not + * @returns a promise that resolves when any requests as an answer to the passed-in event are sent. + */ + handleEvent(event, request, isLiveEvent = false) { + return __awaiter(this, void 0, void 0, function* () { + // prevent processing the same event multiple times, as under + // some circumstances Room.timeline can get emitted twice for the same event + if (request.hasEventId(event.getId())) { + return; + } + const type = InRoomChannel.getEventType(event); + // do validations that need state (roomId, userId), + // ignore if invalid + if (event.getRoomId() !== this.roomId) { + return; + } + // set userId if not set already + if (!this.userId) { + const userId = InRoomChannel.getOtherPartyUserId(event, this.client); + if (userId) { + this.userId = userId; + } + } + // ignore events not sent by us or the other party + const ownUserId = this.client.getUserId(); + const sender = event.getSender(); + if (this.userId) { + if (sender !== ownUserId && sender !== this.userId) { + logger_1.logger.log(`InRoomChannel: ignoring verification event from non-participating sender ${sender}`); + return; + } + } + if (!this.requestEventId) { + this.requestEventId = InRoomChannel.getTransactionId(event); + } + const isRemoteEcho = !!event.getUnsigned().transaction_id; + const isSentByUs = event.getSender() === this.client.getUserId(); + return request.handleEvent(type, event, isLiveEvent, isRemoteEcho, isSentByUs); + }); + } + /** + * Adds the transaction id (relation) back to a received event + * so it has the same format as returned by `completeContent` before sending. + * The relation can not appear on the event content because of encryption, + * relations are excluded from encryption. + * @param event - the received event + * @returns the content object with the relation added again + */ + completedContentFromEvent(event) { + // ensure m.related_to is included in e2ee rooms + // as the field is excluded from encryption + const content = Object.assign({}, event.getContent()); + content[M_RELATES_TO] = event.getRelation(); + return content; + } + /** + * Add all the fields to content needed for sending it over this channel. + * This is public so verification methods (SAS uses this) can get the exact + * content that will be sent independent of the used channel, + * as they need to calculate the hash of it. + * @param type - the event type + * @param content - the (incomplete) content + * @returns the complete content, as it will be sent. + */ + completeContent(type, content) { + content = Object.assign({}, content); + if (type === VerificationRequest_1.REQUEST_TYPE || type === VerificationRequest_1.READY_TYPE || type === VerificationRequest_1.START_TYPE) { + content.from_device = this.client.getDeviceId(); + } + if (type === VerificationRequest_1.REQUEST_TYPE) { + // type is mapped to m.room.message in the send method + content = { + body: this.client.getUserId() + + " is requesting to verify " + + "your key, but your client does not support in-chat key " + + "verification. You will need to use legacy key " + + "verification to verify keys.", + msgtype: VerificationRequest_1.REQUEST_TYPE, + to: this.userId, + from_device: content.from_device, + methods: content.methods, + }; + } + else { + content[M_RELATES_TO] = { + rel_type: M_REFERENCE, + event_id: this.transactionId, + }; + } + return content; + } + /** + * Send an event over the channel with the content not having gone through `completeContent`. + * @param type - the event type + * @param uncompletedContent - the (incomplete) content + * @returns the promise of the request + */ + send(type, uncompletedContent) { + const content = this.completeContent(type, uncompletedContent); + return this.sendCompleted(type, content); + } + /** + * Send an event over the channel with the content having gone through `completeContent` already. + * @param type - the event type + * @returns the promise of the request + */ + sendCompleted(type, content) { + return __awaiter(this, void 0, void 0, function* () { + let sendType = type; + if (type === VerificationRequest_1.REQUEST_TYPE) { + sendType = MESSAGE_TYPE; + } + const response = yield this.client.sendEvent(this.roomId, sendType, content); + if (type === VerificationRequest_1.REQUEST_TYPE) { + this.requestEventId = response.event_id; + } + }); + } +} +exports.InRoomChannel = InRoomChannel; +class InRoomRequests { + constructor() { + this.requestsByRoomId = new Map(); + } + getRequest(event) { + const roomId = event.getRoomId(); + const txnId = InRoomChannel.getTransactionId(event); + return this.getRequestByTxnId(roomId, txnId); + } + getRequestByChannel(channel) { + return this.getRequestByTxnId(channel.roomId, channel.transactionId); + } + getRequestByTxnId(roomId, txnId) { + const requestsByTxnId = this.requestsByRoomId.get(roomId); + if (requestsByTxnId) { + return requestsByTxnId.get(txnId); + } + } + setRequest(event, request) { + this.doSetRequest(event.getRoomId(), InRoomChannel.getTransactionId(event), request); + } + setRequestByChannel(channel, request) { + this.doSetRequest(channel.roomId, channel.transactionId, request); + } + doSetRequest(roomId, txnId, request) { + let requestsByTxnId = this.requestsByRoomId.get(roomId); + if (!requestsByTxnId) { + requestsByTxnId = new Map(); + this.requestsByRoomId.set(roomId, requestsByTxnId); + } + requestsByTxnId.set(txnId, request); + } + removeRequest(event) { + const roomId = event.getRoomId(); + const requestsByTxnId = this.requestsByRoomId.get(roomId); + if (requestsByTxnId) { + requestsByTxnId.delete(InRoomChannel.getTransactionId(event)); + if (requestsByTxnId.size === 0) { + this.requestsByRoomId.delete(roomId); + } + } + } + findRequestInProgress(roomId) { + const requestsByTxnId = this.requestsByRoomId.get(roomId); + if (requestsByTxnId) { + for (const request of requestsByTxnId.values()) { + if (request.pending) { + return request; + } + } + } + } +} +exports.InRoomRequests = InRoomRequests; + +},{"../../../@types/event":306,"../../../logger":374,"./VerificationRequest":357}],356:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ToDeviceRequests = exports.ToDeviceChannel = void 0; +const randomstring_1 = require("../../../randomstring"); +const logger_1 = require("../../../logger"); +const VerificationRequest_1 = require("./VerificationRequest"); +const Error_1 = require("../Error"); +const event_1 = require("../../../models/event"); +/** + * A key verification channel that sends verification events over to_device messages. + * Generates its own transaction ids. + */ +class ToDeviceChannel { + // userId and devices of user we're about to verify + constructor(client, userId, devices, transactionId, deviceId) { + this.client = client; + this.userId = userId; + this.devices = devices; + this.transactionId = transactionId; + this.deviceId = deviceId; + } + isToDevices(devices) { + if (devices.length === this.devices.length) { + for (const device of devices) { + if (!this.devices.includes(device)) { + return false; + } + } + return true; + } + else { + return false; + } + } + static getEventType(event) { + return event.getType(); + } + /** + * Extract the transaction id used by a given key verification event, if any + * @param event - the event + * @returns the transaction id + */ + static getTransactionId(event) { + const content = event.getContent(); + return content && content.transaction_id; + } + /** + * Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel + * @param type - the event type to check + * @returns boolean flag + */ + static canCreateRequest(type) { + return type === VerificationRequest_1.REQUEST_TYPE || type === VerificationRequest_1.START_TYPE; + } + canCreateRequest(type) { + return ToDeviceChannel.canCreateRequest(type); + } + /** + * Checks whether this event is a well-formed key verification event. + * This only does checks that don't rely on the current state of a potentially already channel + * so we can prevent channels being created by invalid events. + * `handleEvent` can do more checks and choose to ignore invalid events. + * @param event - the event to validate + * @param client - the client to get the current user and device id from + * @returns whether the event is valid and should be passed to handleEvent + */ + static validateEvent(event, client) { + if (event.isCancelled()) { + logger_1.logger.warn("Ignoring flagged verification request from " + event.getSender()); + return false; + } + const content = event.getContent(); + if (!content) { + logger_1.logger.warn("ToDeviceChannel.validateEvent: invalid: no content"); + return false; + } + if (!content.transaction_id) { + logger_1.logger.warn("ToDeviceChannel.validateEvent: invalid: no transaction_id"); + return false; + } + const type = event.getType(); + if (type === VerificationRequest_1.REQUEST_TYPE) { + if (!Number.isFinite(content.timestamp)) { + logger_1.logger.warn("ToDeviceChannel.validateEvent: invalid: no timestamp"); + return false; + } + if (event.getSender() === client.getUserId() && content.from_device == client.getDeviceId()) { + // ignore requests from ourselves, because it doesn't make sense for a + // device to verify itself + logger_1.logger.warn("ToDeviceChannel.validateEvent: invalid: from own device"); + return false; + } + } + return VerificationRequest_1.VerificationRequest.validateEvent(type, event, client); + } + /** + * @param event - the event to get the timestamp of + * @returns the timestamp when the event was sent + */ + getTimestamp(event) { + const content = event.getContent(); + return content && content.timestamp; + } + /** + * Changes the state of the channel, request, and verifier in response to a key verification event. + * @param event - to handle + * @param request - the request to forward handling to + * @param isLiveEvent - whether this is an even received through sync or not + * @returns a promise that resolves when any requests as an answer to the passed-in event are sent. + */ + handleEvent(event, request, isLiveEvent = false) { + return __awaiter(this, void 0, void 0, function* () { + const type = event.getType(); + const content = event.getContent(); + if (type === VerificationRequest_1.REQUEST_TYPE || type === VerificationRequest_1.READY_TYPE || type === VerificationRequest_1.START_TYPE) { + if (!this.transactionId) { + this.transactionId = content.transaction_id; + } + const deviceId = content.from_device; + // adopt deviceId if not set before and valid + if (!this.deviceId && this.devices.includes(deviceId)) { + this.deviceId = deviceId; + } + // if no device id or different from adopted one, cancel with sender + if (!this.deviceId || this.deviceId !== deviceId) { + // also check that message came from the device we sent the request to earlier on + // and do send a cancel message to that device + // (but don't cancel the request for the device we should be talking to) + const cancelContent = this.completeContent(VerificationRequest_1.CANCEL_TYPE, (0, Error_1.errorFromEvent)((0, Error_1.newUnexpectedMessageError)())); + return this.sendToDevices(VerificationRequest_1.CANCEL_TYPE, cancelContent, [deviceId]); + } + } + const wasStarted = request.phase === VerificationRequest_1.PHASE_STARTED || request.phase === VerificationRequest_1.PHASE_READY; + yield request.handleEvent(event.getType(), event, isLiveEvent, false, false); + const isStarted = request.phase === VerificationRequest_1.PHASE_STARTED || request.phase === VerificationRequest_1.PHASE_READY; + const isAcceptingEvent = type === VerificationRequest_1.START_TYPE || type === VerificationRequest_1.READY_TYPE; + // the request has picked a ready or start event, tell the other devices about it + if (isAcceptingEvent && !wasStarted && isStarted && this.deviceId) { + const nonChosenDevices = this.devices.filter((d) => d !== this.deviceId && d !== this.client.getDeviceId()); + if (nonChosenDevices.length) { + const message = this.completeContent(VerificationRequest_1.CANCEL_TYPE, { + code: "m.accepted", + reason: "Verification request accepted by another device", + }); + yield this.sendToDevices(VerificationRequest_1.CANCEL_TYPE, message, nonChosenDevices); + } + } + }); + } + /** + * See {@link InRoomChannel#completedContentFromEvent} for why this is needed. + * @param event - the received event + * @returns the content object + */ + completedContentFromEvent(event) { + return event.getContent(); + } + /** + * Add all the fields to content needed for sending it over this channel. + * This is public so verification methods (SAS uses this) can get the exact + * content that will be sent independent of the used channel, + * as they need to calculate the hash of it. + * @param type - the event type + * @param content - the (incomplete) content + * @returns the complete content, as it will be sent. + */ + completeContent(type, content) { + // make a copy + content = Object.assign({}, content); + if (this.transactionId) { + content.transaction_id = this.transactionId; + } + if (type === VerificationRequest_1.REQUEST_TYPE || type === VerificationRequest_1.READY_TYPE || type === VerificationRequest_1.START_TYPE) { + content.from_device = this.client.getDeviceId(); + } + if (type === VerificationRequest_1.REQUEST_TYPE) { + content.timestamp = Date.now(); + } + return content; + } + /** + * Send an event over the channel with the content not having gone through `completeContent`. + * @param type - the event type + * @param uncompletedContent - the (incomplete) content + * @returns the promise of the request + */ + send(type, uncompletedContent = {}) { + // create transaction id when sending request + if ((type === VerificationRequest_1.REQUEST_TYPE || type === VerificationRequest_1.START_TYPE) && !this.transactionId) { + this.transactionId = ToDeviceChannel.makeTransactionId(); + } + const content = this.completeContent(type, uncompletedContent); + return this.sendCompleted(type, content); + } + /** + * Send an event over the channel with the content having gone through `completeContent` already. + * @param type - the event type + * @returns the promise of the request + */ + sendCompleted(type, content) { + return __awaiter(this, void 0, void 0, function* () { + let result; + if (type === VerificationRequest_1.REQUEST_TYPE || (type === VerificationRequest_1.CANCEL_TYPE && !this.deviceId)) { + result = yield this.sendToDevices(type, content, this.devices); + } + else { + result = yield this.sendToDevices(type, content, [this.deviceId]); + } + // the VerificationRequest state machine requires remote echos of the event + // the client sends itself, so we fake this for to_device messages + const remoteEchoEvent = new event_1.MatrixEvent({ + sender: this.client.getUserId(), + content, + type, + }); + yield this.request.handleEvent(type, remoteEchoEvent, + /*isLiveEvent=*/ true, + /*isRemoteEcho=*/ true, + /*isSentByUs=*/ true); + return result; + }); + } + sendToDevices(type, content, devices) { + return __awaiter(this, void 0, void 0, function* () { + if (devices.length) { + const deviceMessages = new Map(); + for (const deviceId of devices) { + deviceMessages.set(deviceId, content); + } + yield this.client.sendToDevice(type, new Map([[this.userId, deviceMessages]])); + } + }); + } + /** + * Allow Crypto module to create and know the transaction id before the .start event gets sent. + * @returns the transaction id + */ + static makeTransactionId() { + return (0, randomstring_1.randomString)(32); + } +} +exports.ToDeviceChannel = ToDeviceChannel; +class ToDeviceRequests { + constructor() { + this.requestsByUserId = new Map(); + } + getRequest(event) { + return this.getRequestBySenderAndTxnId(event.getSender(), ToDeviceChannel.getTransactionId(event)); + } + getRequestByChannel(channel) { + return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId); + } + getRequestBySenderAndTxnId(sender, txnId) { + const requestsByTxnId = this.requestsByUserId.get(sender); + if (requestsByTxnId) { + return requestsByTxnId.get(txnId); + } + } + setRequest(event, request) { + this.setRequestBySenderAndTxnId(event.getSender(), ToDeviceChannel.getTransactionId(event), request); + } + setRequestByChannel(channel, request) { + this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId, request); + } + setRequestBySenderAndTxnId(sender, txnId, request) { + let requestsByTxnId = this.requestsByUserId.get(sender); + if (!requestsByTxnId) { + requestsByTxnId = new Map(); + this.requestsByUserId.set(sender, requestsByTxnId); + } + requestsByTxnId.set(txnId, request); + } + removeRequest(event) { + const userId = event.getSender(); + const requestsByTxnId = this.requestsByUserId.get(userId); + if (requestsByTxnId) { + requestsByTxnId.delete(ToDeviceChannel.getTransactionId(event)); + if (requestsByTxnId.size === 0) { + this.requestsByUserId.delete(userId); + } + } + } + findRequestInProgress(userId, devices) { + const requestsByTxnId = this.requestsByUserId.get(userId); + if (requestsByTxnId) { + for (const request of requestsByTxnId.values()) { + if (request.pending && request.channel.isToDevices(devices)) { + return request; + } + } + } + } + getRequestsInProgress(userId) { + const requestsByTxnId = this.requestsByUserId.get(userId); + if (requestsByTxnId) { + return Array.from(requestsByTxnId.values()).filter((r) => r.pending); + } + return []; + } +} +exports.ToDeviceRequests = ToDeviceRequests; + +},{"../../../logger":374,"../../../models/event":383,"../../../randomstring":398,"../Error":350,"./VerificationRequest":357}],357:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VerificationRequest = exports.VerificationRequestEvent = exports.PHASE_DONE = exports.PHASE_CANCELLED = exports.PHASE_STARTED = exports.PHASE_READY = exports.PHASE_REQUESTED = exports.PHASE_UNSENT = exports.Phase = exports.READY_TYPE = exports.DONE_TYPE = exports.CANCEL_TYPE = exports.START_TYPE = exports.REQUEST_TYPE = exports.EVENT_PREFIX = void 0; +const logger_1 = require("../../../logger"); +const Error_1 = require("../Error"); +const QRCode_1 = require("../QRCode"); +const event_1 = require("../../../@types/event"); +const typed_event_emitter_1 = require("../../../models/typed-event-emitter"); +// How long after the event's timestamp that the request times out +const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes +// How long after we receive the event that the request times out +const TIMEOUT_FROM_EVENT_RECEIPT = 2 * 60 * 1000; // 2 minutes +// to avoid almost expired verification notifications +// from showing a notification and almost immediately +// disappearing, also ignore verification requests that +// are this amount of time away from expiring. +const VERIFICATION_REQUEST_MARGIN = 3 * 1000; // 3 seconds +exports.EVENT_PREFIX = "m.key.verification."; +exports.REQUEST_TYPE = exports.EVENT_PREFIX + "request"; +exports.START_TYPE = exports.EVENT_PREFIX + "start"; +exports.CANCEL_TYPE = exports.EVENT_PREFIX + "cancel"; +exports.DONE_TYPE = exports.EVENT_PREFIX + "done"; +exports.READY_TYPE = exports.EVENT_PREFIX + "ready"; +var Phase; +(function (Phase) { + Phase[Phase["Unsent"] = 1] = "Unsent"; + Phase[Phase["Requested"] = 2] = "Requested"; + Phase[Phase["Ready"] = 3] = "Ready"; + Phase[Phase["Started"] = 4] = "Started"; + Phase[Phase["Cancelled"] = 5] = "Cancelled"; + Phase[Phase["Done"] = 6] = "Done"; +})(Phase = exports.Phase || (exports.Phase = {})); +// Legacy export fields +exports.PHASE_UNSENT = Phase.Unsent; +exports.PHASE_REQUESTED = Phase.Requested; +exports.PHASE_READY = Phase.Ready; +exports.PHASE_STARTED = Phase.Started; +exports.PHASE_CANCELLED = Phase.Cancelled; +exports.PHASE_DONE = Phase.Done; +var VerificationRequestEvent; +(function (VerificationRequestEvent) { + VerificationRequestEvent["Change"] = "change"; +})(VerificationRequestEvent = exports.VerificationRequestEvent || (exports.VerificationRequestEvent = {})); +/** + * State machine for verification requests. + * Things that differ based on what channel is used to + * send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`. + */ +class VerificationRequest extends typed_event_emitter_1.TypedEventEmitter { + constructor(channel, verificationMethods, client) { + super(); + this.channel = channel; + this.verificationMethods = verificationMethods; + this.client = client; + this.eventsByUs = new Map(); + this.eventsByThem = new Map(); + this._observeOnly = false; + this.timeoutTimer = null; + this._accepting = false; + this._declining = false; + this.verifierHasFinished = false; + this._cancelled = false; + this._chosenMethod = null; + // we keep a copy of the QR Code data (including other user master key) around + // for QR reciprocate verification, to protect against + // cross-signing identity reset between the .ready and .start event + // and signing the wrong key after .start + this._qrCodeData = null; + // The timestamp when we received the request event from the other side + this.requestReceivedAt = null; + this.commonMethods = []; + this.cancelOnTimeout = () => __awaiter(this, void 0, void 0, function* () { + try { + if (this.initiatedByMe) { + yield this.cancel({ + reason: "Other party didn't accept in time", + code: "m.timeout", + }); + } + else { + yield this.cancel({ + reason: "User didn't accept in time", + code: "m.timeout", + }); + } + } + catch (err) { + logger_1.logger.error("Error while cancelling verification request", err); + } + }); + this.channel.request = this; + this.setPhase(exports.PHASE_UNSENT, false); + } + /** + * Stateless validation logic not specific to the channel. + * Invoked by the same static method in either channel. + * @param type - the "symbolic" event type, as returned by the `getEventType` function on the channel. + * @param event - the event to validate. Don't call getType() on it but use the `type` parameter instead. + * @param client - the client to get the current user and device id from + * @returns whether the event is valid and should be passed to handleEvent + */ + static validateEvent(type, event, client) { + const content = event.getContent(); + if (!type || !type.startsWith(exports.EVENT_PREFIX)) { + return false; + } + // from here on we're fairly sure that this is supposed to be + // part of a verification request, so be noisy when rejecting something + if (!content) { + logger_1.logger.log("VerificationRequest: validateEvent: no content"); + return false; + } + if (type === exports.REQUEST_TYPE || type === exports.READY_TYPE) { + if (!Array.isArray(content.methods)) { + logger_1.logger.log("VerificationRequest: validateEvent: " + "fail because methods"); + return false; + } + } + if (type === exports.REQUEST_TYPE || type === exports.READY_TYPE || type === exports.START_TYPE) { + if (typeof content.from_device !== "string" || content.from_device.length === 0) { + logger_1.logger.log("VerificationRequest: validateEvent: " + "fail because from_device"); + return false; + } + } + return true; + } + get invalid() { + return this.phase === exports.PHASE_UNSENT; + } + /** returns whether the phase is PHASE_REQUESTED */ + get requested() { + return this.phase === exports.PHASE_REQUESTED; + } + /** returns whether the phase is PHASE_CANCELLED */ + get cancelled() { + return this.phase === exports.PHASE_CANCELLED; + } + /** returns whether the phase is PHASE_READY */ + get ready() { + return this.phase === exports.PHASE_READY; + } + /** returns whether the phase is PHASE_STARTED */ + get started() { + return this.phase === exports.PHASE_STARTED; + } + /** returns whether the phase is PHASE_DONE */ + get done() { + return this.phase === exports.PHASE_DONE; + } + /** once the phase is PHASE_STARTED (and !initiatedByMe) or PHASE_READY: common methods supported by both sides */ + get methods() { + return this.commonMethods; + } + /** the method picked in the .start event */ + get chosenMethod() { + return this._chosenMethod; + } + calculateEventTimeout(event) { + let effectiveExpiresAt = this.channel.getTimestamp(event) + TIMEOUT_FROM_EVENT_TS; + if (this.requestReceivedAt && !this.initiatedByMe && this.phase <= exports.PHASE_REQUESTED) { + const expiresAtByReceipt = this.requestReceivedAt + TIMEOUT_FROM_EVENT_RECEIPT; + effectiveExpiresAt = Math.min(effectiveExpiresAt, expiresAtByReceipt); + } + return Math.max(0, effectiveExpiresAt - Date.now()); + } + /** The current remaining amount of ms before the request should be automatically cancelled */ + get timeout() { + const requestEvent = this.getEventByEither(exports.REQUEST_TYPE); + if (requestEvent) { + return this.calculateEventTimeout(requestEvent); + } + return 0; + } + /** + * The key verification request event. + * @returns The request event, or falsey if not found. + */ + get requestEvent() { + return this.getEventByEither(exports.REQUEST_TYPE); + } + /** current phase of the request. Some properties might only be defined in a current phase. */ + get phase() { + return this._phase; + } + /** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */ + get verifier() { + return this._verifier; + } + get canAccept() { + return this.phase < exports.PHASE_READY && !this._accepting && !this._declining; + } + get accepting() { + return this._accepting; + } + get declining() { + return this._declining; + } + /** whether this request has sent it's initial event and needs more events to complete */ + get pending() { + return !this.observeOnly && this._phase !== exports.PHASE_DONE && this._phase !== exports.PHASE_CANCELLED; + } + /** Only set after a .ready if the other party can scan a QR code */ + get qrCodeData() { + return this._qrCodeData; + } + /** Checks whether the other party supports a given verification method. + * This is useful when setting up the QR code UI, as it is somewhat asymmetrical: + * if the other party supports SCAN_QR, we should show a QR code in the UI, and vice versa. + * For methods that need to be supported by both ends, use the `methods` property. + * @param method - the method to check + * @param force - to check even if the phase is not ready or started yet, internal usage + * @returns whether or not the other party said the supported the method */ + otherPartySupportsMethod(method, force = false) { + if (!force && !this.ready && !this.started) { + return false; + } + const theirMethodEvent = this.eventsByThem.get(exports.REQUEST_TYPE) || this.eventsByThem.get(exports.READY_TYPE); + if (!theirMethodEvent) { + // if we started straight away with .start event, + // we are assuming that the other side will support the + // chosen method, so return true for that. + if (this.started && this.initiatedByMe) { + const myStartEvent = this.eventsByUs.get(exports.START_TYPE); + const content = myStartEvent && myStartEvent.getContent(); + const myStartMethod = content && content.method; + return method == myStartMethod; + } + return false; + } + const content = theirMethodEvent.getContent(); + if (!content) { + return false; + } + const { methods } = content; + if (!Array.isArray(methods)) { + return false; + } + return methods.includes(method); + } + /** Whether this request was initiated by the syncing user. + * For InRoomChannel, this is who sent the .request event. + * For ToDeviceChannel, this is who sent the .start event + */ + get initiatedByMe() { + // event created by us but no remote echo has been received yet + const noEventsYet = this.eventsByUs.size + this.eventsByThem.size === 0; + if (this._phase === exports.PHASE_UNSENT && noEventsYet) { + return true; + } + const hasMyRequest = this.eventsByUs.has(exports.REQUEST_TYPE); + const hasTheirRequest = this.eventsByThem.has(exports.REQUEST_TYPE); + if (hasMyRequest && !hasTheirRequest) { + return true; + } + if (!hasMyRequest && hasTheirRequest) { + return false; + } + const hasMyStart = this.eventsByUs.has(exports.START_TYPE); + const hasTheirStart = this.eventsByThem.has(exports.START_TYPE); + if (hasMyStart && !hasTheirStart) { + return true; + } + return false; + } + /** The id of the user that initiated the request */ + get requestingUserId() { + if (this.initiatedByMe) { + return this.client.getUserId(); + } + else { + return this.otherUserId; + } + } + /** The id of the user that (will) receive(d) the request */ + get receivingUserId() { + if (this.initiatedByMe) { + return this.otherUserId; + } + else { + return this.client.getUserId(); + } + } + /** The user id of the other party in this request */ + get otherUserId() { + return this.channel.userId; + } + get isSelfVerification() { + return this.client.getUserId() === this.otherUserId; + } + /** + * The id of the user that cancelled the request, + * only defined when phase is PHASE_CANCELLED + */ + get cancellingUserId() { + const myCancel = this.eventsByUs.get(exports.CANCEL_TYPE); + const theirCancel = this.eventsByThem.get(exports.CANCEL_TYPE); + if (myCancel && (!theirCancel || myCancel.getId() < theirCancel.getId())) { + return myCancel.getSender(); + } + if (theirCancel) { + return theirCancel.getSender(); + } + return undefined; + } + /** + * The cancellation code e.g m.user which is responsible for cancelling this verification + */ + get cancellationCode() { + const ev = this.getEventByEither(exports.CANCEL_TYPE); + return ev ? ev.getContent().code : null; + } + get observeOnly() { + return this._observeOnly; + } + /** + * Gets which device the verification should be started with + * given the events sent so far in the verification. This is the + * same algorithm used to determine which device to send the + * verification to when no specific device is specified. + * @returns The device information + */ + get targetDevice() { + const theirFirstEvent = this.eventsByThem.get(exports.REQUEST_TYPE) || + this.eventsByThem.get(exports.READY_TYPE) || + this.eventsByThem.get(exports.START_TYPE); + const theirFirstContent = theirFirstEvent === null || theirFirstEvent === void 0 ? void 0 : theirFirstEvent.getContent(); + const fromDevice = theirFirstContent === null || theirFirstContent === void 0 ? void 0 : theirFirstContent.from_device; + return { + userId: this.otherUserId, + deviceId: fromDevice, + }; + } + /* Start the key verification, creating a verifier and sending a .start event. + * If no previous events have been sent, pass in `targetDevice` to set who to direct this request to. + * @param method - the name of the verification method to use. + * @param targetDevice.userId the id of the user to direct this request to + * @param targetDevice.deviceId the id of the device to direct this request to + * @returns the verifier of the given method + */ + beginKeyVerification(method, targetDevice = null) { + // need to allow also when unsent in case of to_device + if (!this.observeOnly && !this._verifier) { + const validStartPhase = this.phase === exports.PHASE_REQUESTED || + this.phase === exports.PHASE_READY || + (this.phase === exports.PHASE_UNSENT && this.channel.canCreateRequest(exports.START_TYPE)); + if (validStartPhase) { + // when called on a request that was initiated with .request event + // check the method is supported by both sides + if (this.commonMethods.length && !this.commonMethods.includes(method)) { + throw (0, Error_1.newUnknownMethodError)(); + } + this._verifier = this.createVerifier(method, null, targetDevice); + if (!this._verifier) { + throw (0, Error_1.newUnknownMethodError)(); + } + this._chosenMethod = method; + } + } + return this._verifier; + } + /** + * sends the initial .request event. + * @returns resolves when the event has been sent. + */ + sendRequest() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.observeOnly && this._phase === exports.PHASE_UNSENT) { + const methods = [...this.verificationMethods.keys()]; + yield this.channel.send(exports.REQUEST_TYPE, { methods }); + } + }); + } + /** + * Cancels the request, sending a cancellation to the other party + * @param reason - the error reason to send the cancellation with + * @param code - the error code to send the cancellation with + * @returns resolves when the event has been sent. + */ + cancel({ reason = "User declined", code = "m.user" } = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.observeOnly && this._phase !== exports.PHASE_CANCELLED) { + this._declining = true; + this.emit(VerificationRequestEvent.Change); + if (this._verifier) { + return this._verifier.cancel((0, Error_1.errorFactory)(code, reason)()); + } + else { + this._cancellingUserId = this.client.getUserId(); + yield this.channel.send(exports.CANCEL_TYPE, { code, reason }); + } + } + }); + } + /** + * Accepts the request, sending a .ready event to the other party + * @returns resolves when the event has been sent. + */ + accept() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.observeOnly && this.phase === exports.PHASE_REQUESTED && !this.initiatedByMe) { + const methods = [...this.verificationMethods.keys()]; + this._accepting = true; + this.emit(VerificationRequestEvent.Change); + yield this.channel.send(exports.READY_TYPE, { methods }); + } + }); + } + /** + * Can be used to listen for state changes until the callback returns true. + * @param fn - callback to evaluate whether the request is in the desired state. + * Takes the request as an argument. + * @returns that resolves once the callback returns true + * @throws Error when the request is cancelled + */ + waitFor(fn) { + return new Promise((resolve, reject) => { + const check = () => { + let handled = false; + if (fn(this)) { + resolve(this); + handled = true; + } + else if (this.cancelled) { + reject(new Error("cancelled")); + handled = true; + } + if (handled) { + this.off(VerificationRequestEvent.Change, check); + } + return handled; + }; + if (!check()) { + this.on(VerificationRequestEvent.Change, check); + } + }); + } + setPhase(phase, notify = true) { + this._phase = phase; + if (notify) { + this.emit(VerificationRequestEvent.Change); + } + } + getEventByEither(type) { + return this.eventsByThem.get(type) || this.eventsByUs.get(type); + } + getEventBy(type, byThem = false) { + if (byThem) { + return this.eventsByThem.get(type); + } + else { + return this.eventsByUs.get(type); + } + } + calculatePhaseTransitions() { + const transitions = [{ phase: exports.PHASE_UNSENT }]; + const phase = () => transitions[transitions.length - 1].phase; + // always pass by .request first to be sure channel.userId has been set + const hasRequestByThem = this.eventsByThem.has(exports.REQUEST_TYPE); + const requestEvent = this.getEventBy(exports.REQUEST_TYPE, hasRequestByThem); + if (requestEvent) { + transitions.push({ phase: exports.PHASE_REQUESTED, event: requestEvent }); + } + const readyEvent = requestEvent && this.getEventBy(exports.READY_TYPE, !hasRequestByThem); + if (readyEvent && phase() === exports.PHASE_REQUESTED) { + transitions.push({ phase: exports.PHASE_READY, event: readyEvent }); + } + let startEvent; + if (readyEvent || !requestEvent) { + const theirStartEvent = this.eventsByThem.get(exports.START_TYPE); + const ourStartEvent = this.eventsByUs.get(exports.START_TYPE); + // any party can send .start after a .ready or unsent + if (theirStartEvent && ourStartEvent) { + startEvent = + theirStartEvent.getSender() < ourStartEvent.getSender() ? theirStartEvent : ourStartEvent; + } + else { + startEvent = theirStartEvent ? theirStartEvent : ourStartEvent; + } + } + else { + startEvent = this.getEventBy(exports.START_TYPE, !hasRequestByThem); + } + if (startEvent) { + const fromRequestPhase = phase() === exports.PHASE_REQUESTED && (requestEvent === null || requestEvent === void 0 ? void 0 : requestEvent.getSender()) !== startEvent.getSender(); + const fromUnsentPhase = phase() === exports.PHASE_UNSENT && this.channel.canCreateRequest(exports.START_TYPE); + if (fromRequestPhase || phase() === exports.PHASE_READY || fromUnsentPhase) { + transitions.push({ phase: exports.PHASE_STARTED, event: startEvent }); + } + } + const ourDoneEvent = this.eventsByUs.get(exports.DONE_TYPE); + if (this.verifierHasFinished || (ourDoneEvent && phase() === exports.PHASE_STARTED)) { + transitions.push({ phase: exports.PHASE_DONE }); + } + const cancelEvent = this.getEventByEither(exports.CANCEL_TYPE); + if ((this._cancelled || cancelEvent) && phase() !== exports.PHASE_DONE) { + transitions.push({ phase: exports.PHASE_CANCELLED, event: cancelEvent }); + return transitions; + } + return transitions; + } + transitionToPhase(transition) { + const { phase, event } = transition; + // get common methods + if (phase === exports.PHASE_REQUESTED || phase === exports.PHASE_READY) { + if (!this.wasSentByOwnDevice(event)) { + const content = event.getContent(); + this.commonMethods = content.methods.filter((m) => this.verificationMethods.has(m)); + } + } + // detect if we're not a party in the request, and we should just observe + if (!this.observeOnly) { + // if requested or accepted by one of my other devices + if (phase === exports.PHASE_REQUESTED || phase === exports.PHASE_STARTED || phase === exports.PHASE_READY) { + if (this.channel.receiveStartFromOtherDevices && + this.wasSentByOwnUser(event) && + !this.wasSentByOwnDevice(event)) { + this._observeOnly = true; + } + } + } + // create verifier + if (phase === exports.PHASE_STARTED) { + const { method } = event.getContent(); + if (!this._verifier && !this.observeOnly) { + this._verifier = this.createVerifier(method, event); + if (!this._verifier) { + this.cancel({ + code: "m.unknown_method", + reason: `Unknown method: ${method}`, + }); + } + else { + this._chosenMethod = method; + } + } + } + } + applyPhaseTransitions() { + const transitions = this.calculatePhaseTransitions(); + const existingIdx = transitions.findIndex((t) => t.phase === this.phase); + // trim off phases we already went through, if any + const newTransitions = transitions.slice(existingIdx + 1); + // transition to all new phases + for (const transition of newTransitions) { + this.transitionToPhase(transition); + } + return newTransitions; + } + isWinningStartRace(newEvent) { + if (newEvent.getType() !== exports.START_TYPE) { + return false; + } + const oldEvent = this._verifier.startEvent; + let oldRaceIdentifier; + if (this.isSelfVerification) { + // if the verifier does not have a startEvent, + // it is because it's still sending and we are on the initator side + // we know we are sending a .start event because we already + // have a verifier (checked in calling method) + if (oldEvent) { + const oldContent = oldEvent.getContent(); + oldRaceIdentifier = oldContent && oldContent.from_device; + } + else { + oldRaceIdentifier = this.client.getDeviceId(); + } + } + else { + if (oldEvent) { + oldRaceIdentifier = oldEvent.getSender(); + } + else { + oldRaceIdentifier = this.client.getUserId(); + } + } + let newRaceIdentifier; + if (this.isSelfVerification) { + const newContent = newEvent.getContent(); + newRaceIdentifier = newContent && newContent.from_device; + } + else { + newRaceIdentifier = newEvent.getSender(); + } + return newRaceIdentifier < oldRaceIdentifier; + } + hasEventId(eventId) { + for (const event of this.eventsByUs.values()) { + if (event.getId() === eventId) { + return true; + } + } + for (const event of this.eventsByThem.values()) { + if (event.getId() === eventId) { + return true; + } + } + return false; + } + /** + * Changes the state of the request and verifier in response to a key verification event. + * @param type - the "symbolic" event type, as returned by the `getEventType` function on the channel. + * @param event - the event to handle. Don't call getType() on it but use the `type` parameter instead. + * @param isLiveEvent - whether this is an even received through sync or not + * @param isRemoteEcho - whether this is the remote echo of an event sent by the same device + * @param isSentByUs - whether this event is sent by a party that can accept and/or observe the request like one of our peers. + * For InRoomChannel this means any device for the syncing user. For ToDeviceChannel, just the syncing device. + * @returns a promise that resolves when any requests as an answer to the passed-in event are sent. + */ + handleEvent(type, event, isLiveEvent, isRemoteEcho, isSentByUs) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // if reached phase cancelled or done, ignore anything else that comes + if (this.done || this.cancelled) { + return; + } + const wasObserveOnly = this._observeOnly; + this.adjustObserveOnly(event, isLiveEvent); + if (!this.observeOnly && !isRemoteEcho) { + if (yield this.cancelOnError(type, event)) { + return; + } + } + // This assumes verification won't need to send an event with + // the same type for the same party twice. + // This is true for QR and SAS verification, and was + // added here to prevent verification getting cancelled + // when the server duplicates an event (https://github.com/matrix-org/synapse/issues/3365) + const isDuplicateEvent = isSentByUs ? this.eventsByUs.has(type) : this.eventsByThem.has(type); + if (isDuplicateEvent) { + return; + } + const oldPhase = this.phase; + this.addEvent(type, event, isSentByUs); + // this will create if needed the verifier so needs to happen before calling it + const newTransitions = this.applyPhaseTransitions(); + try { + // only pass events from the other side to the verifier, + // no remote echos of our own events + if (this._verifier && !this.observeOnly) { + const newEventWinsRace = this.isWinningStartRace(event); + if (this._verifier.canSwitchStartEvent(event) && newEventWinsRace) { + this._verifier.switchStartEvent(event); + } + else if (!isRemoteEcho) { + if (type === exports.CANCEL_TYPE || ((_a = this._verifier.events) === null || _a === void 0 ? void 0 : _a.includes(type))) { + this._verifier.handleEvent(event); + } + } + } + if (newTransitions.length) { + // create QRCodeData if the other side can scan + // important this happens before emitting a phase change, + // so listeners can rely on it being there already + // We only do this for live events because it is important that + // we sign the keys that were in the QR code, and not the keys + // we happen to have at some later point in time. + if (isLiveEvent && newTransitions.some((t) => t.phase === exports.PHASE_READY)) { + const shouldGenerateQrCode = this.otherPartySupportsMethod(QRCode_1.SCAN_QR_CODE_METHOD, true); + if (shouldGenerateQrCode) { + this._qrCodeData = yield QRCode_1.QRCodeData.create(this, this.client); + } + } + const lastTransition = newTransitions[newTransitions.length - 1]; + const { phase } = lastTransition; + this.setupTimeout(phase); + // set phase as last thing as this emits the "change" event + this.setPhase(phase); + } + else if (this._observeOnly !== wasObserveOnly) { + this.emit(VerificationRequestEvent.Change); + } + } + finally { + // log events we processed so we can see from rageshakes what events were added to a request + logger_1.logger.log(`Verification request ${this.channel.transactionId}: ` + + `${type} event with id:${event.getId()}, ` + + `content:${JSON.stringify(event.getContent())} ` + + `deviceId:${this.channel.deviceId}, ` + + `sender:${event.getSender()}, isSentByUs:${isSentByUs}, ` + + `isLiveEvent:${isLiveEvent}, isRemoteEcho:${isRemoteEcho}, ` + + `phase:${oldPhase}=>${this.phase}, ` + + `observeOnly:${wasObserveOnly}=>${this._observeOnly}`); + } + }); + } + setupTimeout(phase) { + const shouldTimeout = !this.timeoutTimer && !this.observeOnly && phase === exports.PHASE_REQUESTED; + if (shouldTimeout) { + this.timeoutTimer = setTimeout(this.cancelOnTimeout, this.timeout); + } + if (this.timeoutTimer) { + const shouldClear = phase === exports.PHASE_STARTED || phase === exports.PHASE_READY || phase === exports.PHASE_DONE || phase === exports.PHASE_CANCELLED; + if (shouldClear) { + clearTimeout(this.timeoutTimer); + this.timeoutTimer = null; + } + } + } + cancelOnError(type, event) { + return __awaiter(this, void 0, void 0, function* () { + if (type === exports.START_TYPE) { + const method = event.getContent().method; + if (!this.verificationMethods.has(method)) { + yield this.cancel((0, Error_1.errorFromEvent)((0, Error_1.newUnknownMethodError)())); + return true; + } + } + const isUnexpectedRequest = type === exports.REQUEST_TYPE && this.phase !== exports.PHASE_UNSENT; + const isUnexpectedReady = type === exports.READY_TYPE && this.phase !== exports.PHASE_REQUESTED && this.phase !== exports.PHASE_STARTED; + // only if phase has passed from PHASE_UNSENT should we cancel, because events + // are allowed to come in in any order (at least with InRoomChannel). So we only know + // we're dealing with a valid request we should participate in once we've moved to PHASE_REQUESTED. + // Before that, we could be looking at somebody else's verification request and we just + // happen to be in the room + if (this.phase !== exports.PHASE_UNSENT && (isUnexpectedRequest || isUnexpectedReady)) { + logger_1.logger.warn(`Cancelling, unexpected ${type} verification ` + `event from ${event.getSender()}`); + const reason = `Unexpected ${type} event in phase ${this.phase}`; + yield this.cancel((0, Error_1.errorFromEvent)((0, Error_1.newUnexpectedMessageError)({ reason }))); + return true; + } + return false; + }); + } + adjustObserveOnly(event, isLiveEvent = false) { + // don't send out events for historical requests + if (!isLiveEvent) { + this._observeOnly = true; + } + if (this.calculateEventTimeout(event) < VERIFICATION_REQUEST_MARGIN) { + this._observeOnly = true; + } + } + addEvent(type, event, isSentByUs = false) { + if (isSentByUs) { + this.eventsByUs.set(type, event); + } + else { + this.eventsByThem.set(type, event); + } + // once we know the userId of the other party (from the .request event) + // see if any event by anyone else crept into this.eventsByThem + if (type === exports.REQUEST_TYPE) { + for (const [type, event] of this.eventsByThem.entries()) { + if (event.getSender() !== this.otherUserId) { + this.eventsByThem.delete(type); + } + } + // also remember when we received the request event + this.requestReceivedAt = Date.now(); + } + } + createVerifier(method, startEvent = null, targetDevice = null) { + if (!targetDevice) { + targetDevice = this.targetDevice; + } + const { userId, deviceId } = targetDevice; + const VerifierCtor = this.verificationMethods.get(method); + if (!VerifierCtor) { + logger_1.logger.warn("could not find verifier constructor for method", method); + return; + } + return new VerifierCtor(this.channel, this.client, userId, deviceId, startEvent, this); + } + wasSentByOwnUser(event) { + return (event === null || event === void 0 ? void 0 : event.getSender()) === this.client.getUserId(); + } + // only for .request, .ready or .start + wasSentByOwnDevice(event) { + if (!this.wasSentByOwnUser(event)) { + return false; + } + const content = event.getContent(); + if (!content || content.from_device !== this.client.getDeviceId()) { + return false; + } + return true; + } + onVerifierCancelled() { + this._cancelled = true; + // move to cancelled phase + const newTransitions = this.applyPhaseTransitions(); + if (newTransitions.length) { + this.setPhase(newTransitions[newTransitions.length - 1].phase); + } + } + onVerifierFinished() { + this.channel.send(event_1.EventType.KeyVerificationDone, {}); + this.verifierHasFinished = true; + // move to .done phase + const newTransitions = this.applyPhaseTransitions(); + if (newTransitions.length) { + this.setPhase(newTransitions[newTransitions.length - 1].phase); + } + } + getEventFromOtherParty(type) { + return this.eventsByThem.get(type); + } +} +exports.VerificationRequest = VerificationRequest; + +},{"../../../@types/event":306,"../../../logger":374,"../../../models/typed-event-emitter":395,"../Error":350,"../QRCode":352}],358:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomWidgetClient = void 0; +const matrix_widget_api_1 = require("matrix-widget-api"); +const event_1 = require("./models/event"); +const event_2 = require("./@types/event"); +const logger_1 = require("./logger"); +const client_1 = require("./client"); +const sync_1 = require("./sync"); +const sliding_sync_sdk_1 = require("./sliding-sync-sdk"); +const event_3 = require("./models/event"); +const user_1 = require("./models/user"); +const utils_1 = require("./utils"); +/** + * A MatrixClient that routes its requests through the widget API instead of the + * real CS API. + * @experimental This class is considered unstable! + */ +class RoomWidgetClient extends client_1.MatrixClient { + constructor(widgetApi, capabilities, roomId, opts) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; + super(opts); + this.widgetApi = widgetApi; + this.capabilities = capabilities; + this.roomId = roomId; + this.widgetApiReady = new Promise((resolve) => this.widgetApi.once("ready", resolve)); + this.syncState = null; + this.onEvent = (ev) => __awaiter(this, void 0, void 0, function* () { + ev.preventDefault(); + // Verify the room ID matches, since it's possible for the client to + // send us events from other rooms if this widget is always on screen + if (ev.detail.data.room_id === this.roomId) { + const event = new event_3.MatrixEvent(ev.detail.data); + yield this.syncApi.injectRoomEvents(this.room, [], [event]); + this.emit(client_1.ClientEvent.Event, event); + this.setSyncState(sync_1.SyncState.Syncing); + logger_1.logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`); + } + else { + const { event_id: eventId, room_id: roomId } = ev.detail.data; + logger_1.logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`); + } + yield this.ack(ev); + }); + this.onToDevice = (ev) => __awaiter(this, void 0, void 0, function* () { + ev.preventDefault(); + const event = new event_3.MatrixEvent({ + type: ev.detail.data.type, + sender: ev.detail.data.sender, + content: ev.detail.data.content, + }); + // Mark the event as encrypted if it was, using fake contents and keys since those are unknown to us + if (ev.detail.data.encrypted) + event.makeEncrypted(event_2.EventType.RoomMessageEncrypted, {}, "", ""); + this.emit(client_1.ClientEvent.ToDeviceEvent, event); + this.setSyncState(sync_1.SyncState.Syncing); + yield this.ack(ev); + }); + // Request capabilities for the functionality this client needs to support + if (((_a = capabilities.sendEvent) === null || _a === void 0 ? void 0 : _a.length) || + ((_b = capabilities.receiveEvent) === null || _b === void 0 ? void 0 : _b.length) || + capabilities.sendMessage === true || + (Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length) || + capabilities.receiveMessage === true || + (Array.isArray(capabilities.receiveMessage) && capabilities.receiveMessage.length) || + ((_c = capabilities.sendState) === null || _c === void 0 ? void 0 : _c.length) || + ((_d = capabilities.receiveState) === null || _d === void 0 ? void 0 : _d.length)) { + widgetApi.requestCapabilityForRoomTimeline(roomId); + } + (_e = capabilities.sendEvent) === null || _e === void 0 ? void 0 : _e.forEach((eventType) => widgetApi.requestCapabilityToSendEvent(eventType)); + (_f = capabilities.receiveEvent) === null || _f === void 0 ? void 0 : _f.forEach((eventType) => widgetApi.requestCapabilityToReceiveEvent(eventType)); + if (capabilities.sendMessage === true) { + widgetApi.requestCapabilityToSendMessage(); + } + else if (Array.isArray(capabilities.sendMessage)) { + capabilities.sendMessage.forEach((msgType) => widgetApi.requestCapabilityToSendMessage(msgType)); + } + if (capabilities.receiveMessage === true) { + widgetApi.requestCapabilityToReceiveMessage(); + } + else if (Array.isArray(capabilities.receiveMessage)) { + capabilities.receiveMessage.forEach((msgType) => widgetApi.requestCapabilityToReceiveMessage(msgType)); + } + (_g = capabilities.sendState) === null || _g === void 0 ? void 0 : _g.forEach(({ eventType, stateKey }) => widgetApi.requestCapabilityToSendState(eventType, stateKey)); + (_h = capabilities.receiveState) === null || _h === void 0 ? void 0 : _h.forEach(({ eventType, stateKey }) => widgetApi.requestCapabilityToReceiveState(eventType, stateKey)); + (_j = capabilities.sendToDevice) === null || _j === void 0 ? void 0 : _j.forEach((eventType) => widgetApi.requestCapabilityToSendToDevice(eventType)); + (_k = capabilities.receiveToDevice) === null || _k === void 0 ? void 0 : _k.forEach((eventType) => widgetApi.requestCapabilityToReceiveToDevice(eventType)); + if (capabilities.turnServers) { + widgetApi.requestCapability(matrix_widget_api_1.MatrixCapabilities.MSC3846TurnServers); + } + widgetApi.on(`action:${matrix_widget_api_1.WidgetApiToWidgetAction.SendEvent}`, this.onEvent); + widgetApi.on(`action:${matrix_widget_api_1.WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice); + // Open communication with the host + widgetApi.start(); + } + startClient(opts = {}) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + this.lifecycle = new AbortController(); + // Create our own user object artificially (instead of waiting for sync) + // so it's always available, even if the user is not in any rooms etc. + const userId = this.getUserId(); + if (userId) { + this.store.storeUser(new user_1.User(userId)); + } + // Even though we have no access token and cannot sync, the sync class + // still has some valuable helper methods that we make use of, so we + // instantiate it anyways + if (opts.slidingSync) { + this.syncApi = new sliding_sync_sdk_1.SlidingSyncSdk(opts.slidingSync, this, opts, this.buildSyncApiOptions()); + } + else { + this.syncApi = new sync_1.SyncApi(this, opts, this.buildSyncApiOptions()); + } + this.room = this.syncApi.createRoom(this.roomId); + this.store.storeRoom(this.room); + yield this.widgetApiReady; + // Backfill the requested events + // We only get the most recent event for every type + state key combo, + // so it doesn't really matter what order we inject them in + yield Promise.all((_b = (_a = this.capabilities.receiveState) === null || _a === void 0 ? void 0 : _a.map(({ eventType, stateKey }) => __awaiter(this, void 0, void 0, function* () { + const rawEvents = yield this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]); + const events = rawEvents.map((rawEvent) => new event_3.MatrixEvent(rawEvent)); + yield this.syncApi.injectRoomEvents(this.room, [], events); + events.forEach((event) => { + this.emit(client_1.ClientEvent.Event, event); + logger_1.logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`); + }); + }))) !== null && _b !== void 0 ? _b : []); + this.setSyncState(sync_1.SyncState.Syncing); + logger_1.logger.info("Finished backfilling events"); + // Watch for TURN servers, if requested + if (this.capabilities.turnServers) + this.watchTurnServers(); + }); + } + stopClient() { + this.widgetApi.off(`action:${matrix_widget_api_1.WidgetApiToWidgetAction.SendEvent}`, this.onEvent); + this.widgetApi.off(`action:${matrix_widget_api_1.WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice); + super.stopClient(); + this.lifecycle.abort(); // Signal to other async tasks that the client has stopped + } + joinRoom(roomIdOrAlias) { + return __awaiter(this, void 0, void 0, function* () { + if (roomIdOrAlias === this.roomId) + return this.room; + throw new Error(`Unknown room: ${roomIdOrAlias}`); + }); + } + encryptAndSendEvent(room, event) { + return __awaiter(this, void 0, void 0, function* () { + let response; + try { + response = yield this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId); + } + catch (e) { + this.updatePendingEventStatus(room, event, event_1.EventStatus.NOT_SENT); + throw e; + } + room.updatePendingEvent(event, event_1.EventStatus.SENT, response.event_id); + return { event_id: response.event_id }; + }); + } + sendStateEvent(roomId, eventType, content, stateKey = "") { + return __awaiter(this, void 0, void 0, function* () { + return yield this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId); + }); + } + sendToDevice(eventType, contentMap) { + return __awaiter(this, void 0, void 0, function* () { + yield this.widgetApi.sendToDevice(eventType, false, (0, utils_1.recursiveMapToObject)(contentMap)); + return {}; + }); + } + queueToDevice({ eventType, batch }) { + return __awaiter(this, void 0, void 0, function* () { + // map: user Id → device Id → payload + const contentMap = new utils_1.MapWithDefault(() => new Map()); + for (const { userId, deviceId, payload } of batch) { + contentMap.getOrCreate(userId).set(deviceId, payload); + } + yield this.widgetApi.sendToDevice(eventType, false, (0, utils_1.recursiveMapToObject)(contentMap)); + }); + } + encryptAndSendToDevices(userDeviceInfoArr, payload) { + return __awaiter(this, void 0, void 0, function* () { + // map: user Id → device Id → payload + const contentMap = new utils_1.MapWithDefault(() => new Map()); + for (const { userId, deviceInfo: { deviceId }, } of userDeviceInfoArr) { + contentMap.getOrCreate(userId).set(deviceId, payload); + } + yield this.widgetApi.sendToDevice(payload.type, true, (0, utils_1.recursiveMapToObject)(contentMap)); + }); + } + // Overridden since we get TURN servers automatically over the widget API, + // and this method would otherwise complain about missing an access token + checkTurnServers() { + return __awaiter(this, void 0, void 0, function* () { + return this.turnServers.length > 0; + }); + } + // Overridden since we 'sync' manually without the sync API + getSyncState() { + return this.syncState; + } + setSyncState(state) { + const oldState = this.syncState; + this.syncState = state; + this.emit(client_1.ClientEvent.Sync, state, oldState); + } + ack(ev) { + return __awaiter(this, void 0, void 0, function* () { + yield this.widgetApi.transport.reply(ev.detail, {}); + }); + } + watchTurnServers() { + var _a, e_1, _b, _c; + return __awaiter(this, void 0, void 0, function* () { + const servers = this.widgetApi.getTurnServers(); + const onClientStopped = () => { + servers.return(undefined); + }; + this.lifecycle.signal.addEventListener("abort", onClientStopped); + try { + try { + for (var _d = true, servers_1 = __asyncValues(servers), servers_1_1; servers_1_1 = yield servers_1.next(), _a = servers_1_1.done, !_a;) { + _c = servers_1_1.value; + _d = false; + try { + const server = _c; + this.turnServers = [ + { + urls: server.uris, + username: server.username, + credential: server.password, + }, + ]; + this.emit(client_1.ClientEvent.TurnServers, this.turnServers); + logger_1.logger.log(`Received TURN server: ${server.uris}`); + } + finally { + _d = true; + } + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (!_d && !_a && (_b = servers_1.return)) yield _b.call(servers_1); + } + finally { if (e_1) throw e_1.error; } + } + } + catch (e) { + logger_1.logger.warn("Error watching TURN servers", e); + } + finally { + this.lifecycle.signal.removeEventListener("abort", onClientStopped); + } + }); + } +} +exports.RoomWidgetClient = RoomWidgetClient; + +},{"./@types/event":306,"./client":321,"./logger":374,"./models/event":383,"./models/user":396,"./sliding-sync-sdk":406,"./sync":414,"./utils":416,"matrix-widget-api":178}],359:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.KeySignatureUploadError = exports.InvalidCryptoStoreError = exports.InvalidCryptoStoreState = exports.InvalidStoreError = exports.InvalidStoreState = void 0; +var InvalidStoreState; +(function (InvalidStoreState) { + InvalidStoreState[InvalidStoreState["ToggledLazyLoading"] = 0] = "ToggledLazyLoading"; +})(InvalidStoreState = exports.InvalidStoreState || (exports.InvalidStoreState = {})); +class InvalidStoreError extends Error { + constructor(reason, value) { + const message = `Store is invalid because ${reason}, ` + + `please stop the client, delete all data and start the client again`; + super(message); + this.reason = reason; + this.value = value; + this.name = "InvalidStoreError"; + } +} +exports.InvalidStoreError = InvalidStoreError; +InvalidStoreError.TOGGLED_LAZY_LOADING = InvalidStoreState.ToggledLazyLoading; +var InvalidCryptoStoreState; +(function (InvalidCryptoStoreState) { + InvalidCryptoStoreState["TooNew"] = "TOO_NEW"; +})(InvalidCryptoStoreState = exports.InvalidCryptoStoreState || (exports.InvalidCryptoStoreState = {})); +class InvalidCryptoStoreError extends Error { + constructor(reason) { + const message = `Crypto store is invalid because ${reason}, ` + + `please stop the client, delete all data and start the client again`; + super(message); + this.reason = reason; + this.name = "InvalidCryptoStoreError"; + } +} +exports.InvalidCryptoStoreError = InvalidCryptoStoreError; +InvalidCryptoStoreError.TOO_NEW = InvalidCryptoStoreState.TooNew; +class KeySignatureUploadError extends Error { + constructor(message, value) { + super(message); + this.value = value; + } +} +exports.KeySignatureUploadError = KeySignatureUploadError; + +},{}],360:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.eventMapperFor = void 0; +const event_1 = require("./models/event"); +const event_2 = require("./@types/event"); +function eventMapperFor(client, options) { + let preventReEmit = Boolean(options.preventReEmit); + const decrypt = options.decrypt !== false; + function mapper(plainOldJsObject) { + if (options.toDevice) { + delete plainOldJsObject.room_id; + } + const room = client.getRoom(plainOldJsObject.room_id); + let event; + // If the event is already known to the room, let's re-use the model rather than duplicating. + // We avoid doing this to state events as they may be forward or backwards looking which tweaks behaviour. + if (room && plainOldJsObject.state_key === undefined) { + event = room.findEventById(plainOldJsObject.event_id); + } + if (!event || event.status) { + event = new event_1.MatrixEvent(plainOldJsObject); + } + else { + // merge the latest unsigned data from the server + event.setUnsigned(Object.assign(Object.assign({}, event.getUnsigned()), plainOldJsObject.unsigned)); + // prevent doubling up re-emitters + preventReEmit = true; + } + // if there is a complete edit bundled alongside the event, perform the replacement. + // (prior to MSC3925, events were automatically replaced on the server-side. MSC3925 proposes that that doesn't + // happen automatically but the server does provide us with the whole content of the edit event.) + const bundledEdit = event.getServerAggregatedRelation(event_2.RelationType.Replace); + if (bundledEdit === null || bundledEdit === void 0 ? void 0 : bundledEdit.content) { + const replacement = mapper(bundledEdit); + // XXX: it's worth noting that the spec says we should only respect encrypted edits if, once decrypted, the + // replacement has a `m.new_content` property. The problem is that we haven't yet decrypted the replacement + // (it should be happening in the background), so we can't enforce this. Possibly we should for decryption + // to complete, but that sounds a bit racy. For now, we just assume it's ok. + event.makeReplaced(replacement); + } + const thread = room === null || room === void 0 ? void 0 : room.findThreadForEvent(event); + if (thread) { + event.setThread(thread); + } + // TODO: once we get rid of the old libolm-backed crypto, we can restrict this to room events (rather than + // to-device events), because the rust implementation decrypts to-device messages at a higher level. + // Generally we probably want to use a different eventMapper implementation for to-device events because + if (event.isEncrypted()) { + if (!preventReEmit) { + client.reEmitter.reEmit(event, [event_1.MatrixEventEvent.Decrypted]); + } + if (decrypt) { + client.decryptEventIfNeeded(event); + } + } + if (!preventReEmit) { + client.reEmitter.reEmit(event, [event_1.MatrixEventEvent.Replaced, event_1.MatrixEventEvent.VisibilityChange]); + room === null || room === void 0 ? void 0 : room.reEmitter.reEmit(event, [event_1.MatrixEventEvent.BeforeRedaction]); + } + return event; + } + return mapper; +} +exports.eventMapperFor = eventMapperFor; + +},{"./@types/event":306,"./models/event":383}],361:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isOptionalAString = exports.isProvided = void 0; +/** + * Determines if the given optional was provided a value. + * @param s - The optional to test. + * @returns True if the value is defined. + */ +function isProvided(s) { + return s !== null && s !== undefined; +} +exports.isProvided = isProvided; +/** + * Determines if the given optional string is a defined string. + * @param s - The input string. + * @returns True if the input is a defined string. + */ +function isOptionalAString(s) { + return isProvided(s) && typeof s === "string"; +} +exports.isOptionalAString = isOptionalAString; + +},{}],362:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildFeatureSupportMap = exports.Feature = exports.ServerSupport = void 0; +var ServerSupport; +(function (ServerSupport) { + ServerSupport[ServerSupport["Stable"] = 0] = "Stable"; + ServerSupport[ServerSupport["Unstable"] = 1] = "Unstable"; + ServerSupport[ServerSupport["Unsupported"] = 2] = "Unsupported"; +})(ServerSupport = exports.ServerSupport || (exports.ServerSupport = {})); +var Feature; +(function (Feature) { + Feature["Thread"] = "Thread"; + Feature["ThreadUnreadNotifications"] = "ThreadUnreadNotifications"; + Feature["LoginTokenRequest"] = "LoginTokenRequest"; + Feature["RelationBasedRedactions"] = "RelationBasedRedactions"; + Feature["AccountDataDeletion"] = "AccountDataDeletion"; +})(Feature = exports.Feature || (exports.Feature = {})); +const featureSupportResolver = { + [Feature.Thread]: { + unstablePrefixes: ["org.matrix.msc3440"], + matrixVersion: "v1.3", + }, + [Feature.ThreadUnreadNotifications]: { + unstablePrefixes: ["org.matrix.msc3771", "org.matrix.msc3773"], + matrixVersion: "v1.4", + }, + [Feature.LoginTokenRequest]: { + unstablePrefixes: ["org.matrix.msc3882"], + }, + [Feature.RelationBasedRedactions]: { + unstablePrefixes: ["org.matrix.msc3912"], + }, + [Feature.AccountDataDeletion]: { + unstablePrefixes: ["org.matrix.msc3391"], + }, +}; +function buildFeatureSupportMap(versions) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + const supportMap = new Map(); + for (const [feature, supportCondition] of Object.entries(featureSupportResolver)) { + const supportMatrixVersion = (_b = (_a = versions.versions) === null || _a === void 0 ? void 0 : _a.includes(supportCondition.matrixVersion || "")) !== null && _b !== void 0 ? _b : false; + const supportUnstablePrefixes = (_d = (_c = supportCondition.unstablePrefixes) === null || _c === void 0 ? void 0 : _c.every((unstablePrefix) => { + var _a; + return ((_a = versions.unstable_features) === null || _a === void 0 ? void 0 : _a[unstablePrefix]) === true; + })) !== null && _d !== void 0 ? _d : false; + if (supportMatrixVersion) { + supportMap.set(feature, ServerSupport.Stable); + } + else if (supportUnstablePrefixes) { + supportMap.set(feature, ServerSupport.Unstable); + } + else { + supportMap.set(feature, ServerSupport.Unsupported); + } + } + return supportMap; + }); +} +exports.buildFeatureSupportMap = buildFeatureSupportMap; + +},{}],363:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FilterComponent = void 0; +const thread_1 = require("./models/thread"); +/** + * Checks if a value matches a given field value, which may be a * terminated + * wildcard pattern. + * @param actualValue - The value to be compared + * @param filterValue - The filter pattern to be compared + * @returns true if the actualValue matches the filterValue + */ +function matchesWildcard(actualValue, filterValue) { + if (filterValue.endsWith("*")) { + const typePrefix = filterValue.slice(0, -1); + return actualValue.slice(0, typePrefix.length) === typePrefix; + } + else { + return actualValue === filterValue; + } +} +/* eslint-enable camelcase */ +/** + * FilterComponent is a section of a Filter definition which defines the + * types, rooms, senders filters etc to be applied to a particular type of resource. + * This is all ported over from synapse's Filter object. + * + * N.B. that synapse refers to these as 'Filters', and what js-sdk refers to as + * 'Filters' are referred to as 'FilterCollections'. + */ +class FilterComponent { + constructor(filterJson, userId) { + this.filterJson = filterJson; + this.userId = userId; + } + /** + * Checks with the filter component matches the given event + * @param event - event to be checked against the filter + * @returns true if the event matches the filter + */ + check(event) { + var _a, _b; + const bundledRelationships = ((_a = event.getUnsigned()) === null || _a === void 0 ? void 0 : _a["m.relations"]) || {}; + const relations = Object.keys(bundledRelationships); + // Relation senders allows in theory a look-up of any senders + // however clients can only know about the current user participation status + // as sending a whole list of participants could be proven problematic in terms + // of performance + // This should be improved when bundled relationships solve that problem + const relationSenders = []; + if (this.userId && ((_b = bundledRelationships === null || bundledRelationships === void 0 ? void 0 : bundledRelationships[thread_1.THREAD_RELATION_TYPE.name]) === null || _b === void 0 ? void 0 : _b.current_user_participated)) { + relationSenders.push(this.userId); + } + return this.checkFields(event.getRoomId(), event.getSender(), event.getType(), event.getContent() ? event.getContent().url !== undefined : false, relations, relationSenders); + } + /** + * Converts the filter component into the form expected over the wire + */ + toJSON() { + return { + types: this.filterJson.types || null, + not_types: this.filterJson.not_types || [], + rooms: this.filterJson.rooms || null, + not_rooms: this.filterJson.not_rooms || [], + senders: this.filterJson.senders || null, + not_senders: this.filterJson.not_senders || [], + contains_url: this.filterJson.contains_url || null, + [thread_1.FILTER_RELATED_BY_SENDERS.name]: this.filterJson[thread_1.FILTER_RELATED_BY_SENDERS.name] || [], + [thread_1.FILTER_RELATED_BY_REL_TYPES.name]: this.filterJson[thread_1.FILTER_RELATED_BY_REL_TYPES.name] || [], + }; + } + /** + * Checks whether the filter component matches the given event fields. + * @param roomId - the roomId for the event being checked + * @param sender - the sender of the event being checked + * @param eventType - the type of the event being checked + * @param containsUrl - whether the event contains a content.url field + * @param relationTypes - whether has aggregated relation of the given type + * @param relationSenders - whether one of the relation is sent by the user listed + * @returns true if the event fields match the filter + */ + checkFields(roomId, sender, eventType, containsUrl, relationTypes, relationSenders) { + const literalKeys = { + rooms: function (v) { + return roomId === v; + }, + senders: function (v) { + return sender === v; + }, + types: function (v) { + return matchesWildcard(eventType, v); + }, + }; + for (const name in literalKeys) { + const matchFunc = literalKeys[name]; + const notName = "not_" + name; + const disallowedValues = this.filterJson[notName]; + if (disallowedValues === null || disallowedValues === void 0 ? void 0 : disallowedValues.some(matchFunc)) { + return false; + } + const allowedValues = this.filterJson[name]; + if (allowedValues && !allowedValues.some(matchFunc)) { + return false; + } + } + const containsUrlFilter = this.filterJson.contains_url; + if (containsUrlFilter !== undefined && containsUrlFilter !== containsUrl) { + return false; + } + const relationTypesFilter = this.filterJson[thread_1.FILTER_RELATED_BY_REL_TYPES.name]; + if (relationTypesFilter !== undefined) { + if (!this.arrayMatchesFilter(relationTypesFilter, relationTypes)) { + return false; + } + } + const relationSendersFilter = this.filterJson[thread_1.FILTER_RELATED_BY_SENDERS.name]; + if (relationSendersFilter !== undefined) { + if (!this.arrayMatchesFilter(relationSendersFilter, relationSenders)) { + return false; + } + } + return true; + } + arrayMatchesFilter(filter, values) { + return (values.length > 0 && + filter.every((value) => { + return values.includes(value); + })); + } + /** + * Filters a list of events down to those which match this filter component + * @param events - Events to be checked against the filter component + * @returns events which matched the filter component + */ + filter(events) { + return events.filter(this.check, this); + } + /** + * Returns the limit field for a given filter component, providing a default of + * 10 if none is otherwise specified. Cargo-culted from Synapse. + * @returns the limit for this filter component. + */ + limit() { + return this.filterJson.limit !== undefined ? this.filterJson.limit : 10; + } +} +exports.FilterComponent = FilterComponent; + +},{"./models/thread":394}],364:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Filter = void 0; +const sync_1 = require("./@types/sync"); +const filter_component_1 = require("./filter-component"); +/** + */ +function setProp(obj, keyNesting, val) { + const nestedKeys = keyNesting.split("."); + let currentObj = obj; + for (let i = 0; i < nestedKeys.length - 1; i++) { + if (!currentObj[nestedKeys[i]]) { + currentObj[nestedKeys[i]] = {}; + } + currentObj = currentObj[nestedKeys[i]]; + } + currentObj[nestedKeys[nestedKeys.length - 1]] = val; +} +/* eslint-enable camelcase */ +class Filter { + /** + * Create a filter from existing data. + */ + static fromJson(userId, filterId, jsonObj) { + const filter = new Filter(userId, filterId); + filter.setDefinition(jsonObj); + return filter; + } + /** + * Construct a new Filter. + * @param userId - The user ID for this filter. + * @param filterId - The filter ID if known. + */ + constructor(userId, filterId) { + this.userId = userId; + this.filterId = filterId; + this.definition = {}; + } + /** + * Get the ID of this filter on your homeserver (if known) + * @returns The filter ID + */ + getFilterId() { + return this.filterId; + } + /** + * Get the JSON body of the filter. + * @returns The filter definition + */ + getDefinition() { + return this.definition; + } + /** + * Set the JSON body of the filter + * @param definition - The filter definition + */ + setDefinition(definition) { + this.definition = definition; + // This is all ported from synapse's FilterCollection() + // definitions look something like: + // { + // "room": { + // "rooms": ["!abcde:example.com"], + // "not_rooms": ["!123456:example.com"], + // "state": { + // "types": ["m.room.*"], + // "not_rooms": ["!726s6s6q:example.com"], + // "lazy_load_members": true, + // }, + // "timeline": { + // "limit": 10, + // "types": ["m.room.message"], + // "not_rooms": ["!726s6s6q:example.com"], + // "not_senders": ["@spam:example.com"] + // "contains_url": true + // }, + // "ephemeral": { + // "types": ["m.receipt", "m.typing"], + // "not_rooms": ["!726s6s6q:example.com"], + // "not_senders": ["@spam:example.com"] + // } + // }, + // "presence": { + // "types": ["m.presence"], + // "not_senders": ["@alice:example.com"] + // }, + // "event_format": "client", + // "event_fields": ["type", "content", "sender"] + // } + const roomFilterJson = definition.room; + // consider the top level rooms/not_rooms filter + const roomFilterFields = {}; + if (roomFilterJson) { + if (roomFilterJson.rooms) { + roomFilterFields.rooms = roomFilterJson.rooms; + } + if (roomFilterJson.rooms) { + roomFilterFields.not_rooms = roomFilterJson.not_rooms; + } + } + this.roomFilter = new filter_component_1.FilterComponent(roomFilterFields, this.userId); + this.roomTimelineFilter = new filter_component_1.FilterComponent((roomFilterJson === null || roomFilterJson === void 0 ? void 0 : roomFilterJson.timeline) || {}, this.userId); + // don't bother porting this from synapse yet: + // this._room_state_filter = + // new FilterComponent(roomFilterJson.state || {}); + // this._room_ephemeral_filter = + // new FilterComponent(roomFilterJson.ephemeral || {}); + // this._room_account_data_filter = + // new FilterComponent(roomFilterJson.account_data || {}); + // this._presence_filter = + // new FilterComponent(definition.presence || {}); + // this._account_data_filter = + // new FilterComponent(definition.account_data || {}); + } + /** + * Get the room.timeline filter component of the filter + * @returns room timeline filter component + */ + getRoomTimelineFilterComponent() { + return this.roomTimelineFilter; + } + /** + * Filter the list of events based on whether they are allowed in a timeline + * based on this filter + * @param events - the list of events being filtered + * @returns the list of events which match the filter + */ + filterRoomTimeline(events) { + if (this.roomFilter) { + events = this.roomFilter.filter(events); + } + if (this.roomTimelineFilter) { + events = this.roomTimelineFilter.filter(events); + } + return events; + } + /** + * Set the max number of events to return for each room's timeline. + * @param limit - The max number of events to return for each room. + */ + setTimelineLimit(limit) { + setProp(this.definition, "room.timeline.limit", limit); + } + /** + * Enable threads unread notification + */ + setUnreadThreadNotifications(enabled) { + var _a, _b, _c; + this.definition = Object.assign(Object.assign({}, this.definition), { room: Object.assign(Object.assign({}, (_a = this.definition) === null || _a === void 0 ? void 0 : _a.room), { timeline: Object.assign(Object.assign({}, (_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b.room) === null || _c === void 0 ? void 0 : _c.timeline), { [sync_1.UNREAD_THREAD_NOTIFICATIONS.name]: enabled }) }) }); + } + setLazyLoadMembers(enabled) { + setProp(this.definition, "room.state.lazy_load_members", enabled); + } + /** + * Control whether left rooms should be included in responses. + * @param includeLeave - True to make rooms the user has left appear + * in responses. + */ + setIncludeLeaveRooms(includeLeave) { + setProp(this.definition, "room.include_leave", includeLeave); + } +} +exports.Filter = Filter; +Filter.LAZY_LOADING_MESSAGES_FILTER = { + lazy_load_members: true, +}; + +},{"./@types/sync":314,"./filter-component":363}],365:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConnectionError = exports.MatrixError = exports.HTTPError = void 0; +/** + * Construct a generic HTTP error. This is a JavaScript Error with additional information + * specific to HTTP responses. + * @param msg - The error message to include. + * @param httpStatus - The HTTP response status code. + */ +class HTTPError extends Error { + constructor(msg, httpStatus) { + super(msg); + this.httpStatus = httpStatus; + } +} +exports.HTTPError = HTTPError; +class MatrixError extends HTTPError { + /** + * Construct a Matrix error. This is a JavaScript Error with additional + * information specific to the standard Matrix error response. + * @param errorJson - The Matrix error JSON returned from the homeserver. + * @param httpStatus - The numeric HTTP status code given + */ + constructor(errorJson = {}, httpStatus, url, event) { + let message = errorJson.error || "Unknown message"; + if (httpStatus) { + message = `[${httpStatus}] ${message}`; + } + if (url) { + message = `${message} (${url})`; + } + super(`MatrixError: ${message}`, httpStatus); + this.httpStatus = httpStatus; + this.url = url; + this.event = event; + this.errcode = errorJson.errcode; + this.name = errorJson.errcode || "Unknown error code"; + this.data = errorJson; + } +} +exports.MatrixError = MatrixError; +/** + * Construct a ConnectionError. This is a JavaScript Error indicating + * that a request failed because of some error with the connection, either + * CORS was not correctly configured on the server, the server didn't response, + * the request timed out, or the internet connection on the client side went down. + */ +class ConnectionError extends Error { + constructor(message, cause) { + super(message + (cause ? `: ${cause.message}` : "")); + } + get name() { + return "ConnectionError"; + } +} +exports.ConnectionError = ConnectionError; + +},{}],366:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FetchHttpApi = void 0; +/** + * This is an internal module. See {@link MatrixHttpApi} for the public class. + */ +const utils = __importStar(require("../utils")); +const method_1 = require("./method"); +const errors_1 = require("./errors"); +const interface_1 = require("./interface"); +const utils_1 = require("./utils"); +class FetchHttpApi { + constructor(eventEmitter, opts) { + var _a; + this.eventEmitter = eventEmitter; + this.opts = opts; + this.abortController = new AbortController(); + utils.checkObjectHasKeys(opts, ["baseUrl", "prefix"]); + opts.onlyData = !!opts.onlyData; + opts.useAuthorizationHeader = (_a = opts.useAuthorizationHeader) !== null && _a !== void 0 ? _a : true; + } + abort() { + this.abortController.abort(); + this.abortController = new AbortController(); + } + fetch(resource, options) { + if (this.opts.fetchFn) { + return this.opts.fetchFn(resource, options); + } + return global.fetch(resource, options); + } + /** + * Sets the base URL for the identity server + * @param url - The new base url + */ + setIdBaseUrl(url) { + this.opts.idBaseUrl = url; + } + idServerRequest(method, path, params, prefix, accessToken) { + if (!this.opts.idBaseUrl) { + throw new Error("No identity server base URL set"); + } + let queryParams = undefined; + let body = undefined; + if (method === method_1.Method.Get) { + queryParams = params; + } + else { + body = params; + } + const fullUri = this.getUrl(path, queryParams, prefix, this.opts.idBaseUrl); + const opts = { + json: true, + headers: {}, + }; + if (accessToken) { + opts.headers.Authorization = `Bearer ${accessToken}`; + } + return this.requestOtherUrl(method, fullUri, body, opts); + } + /** + * Perform an authorised request to the homeserver. + * @param method - The HTTP method e.g. "GET". + * @param path - The HTTP path after the supplied prefix e.g. + * "/createRoom". + * + * @param queryParams - A dict of query params (these will NOT be + * urlencoded). If unspecified, there will be no query params. + * + * @param body - The HTTP JSON body. + * + * @param opts - additional options. If a number is specified, + * this is treated as `opts.localTimeoutMs`. + * + * @returns Promise which resolves to + * ``` + * { + * data: {Object}, + * headers: {Object}, + * code: {Number}, + * } + * ``` + * If `onlyData` is set, this will resolve to the `data` object only. + * @returns Rejects with an error if a problem occurred. + * This includes network problems and Matrix-specific error JSON. + */ + authedRequest(method, path, queryParams, body, opts = {}) { + if (!queryParams) + queryParams = {}; + if (this.opts.accessToken) { + if (this.opts.useAuthorizationHeader) { + if (!opts.headers) { + opts.headers = {}; + } + if (!opts.headers.Authorization) { + opts.headers.Authorization = "Bearer " + this.opts.accessToken; + } + if (queryParams.access_token) { + delete queryParams.access_token; + } + } + else if (!queryParams.access_token) { + queryParams.access_token = this.opts.accessToken; + } + } + const requestPromise = this.request(method, path, queryParams, body, opts); + requestPromise.catch((err) => { + if (err.errcode == "M_UNKNOWN_TOKEN" && !(opts === null || opts === void 0 ? void 0 : opts.inhibitLogoutEmit)) { + this.eventEmitter.emit(interface_1.HttpApiEvent.SessionLoggedOut, err); + } + else if (err.errcode == "M_CONSENT_NOT_GIVEN") { + this.eventEmitter.emit(interface_1.HttpApiEvent.NoConsent, err.message, err.data.consent_uri); + } + }); + // return the original promise, otherwise tests break due to it having to + // go around the event loop one more time to process the result of the request + return requestPromise; + } + /** + * Perform a request to the homeserver without any credentials. + * @param method - The HTTP method e.g. "GET". + * @param path - The HTTP path after the supplied prefix e.g. + * "/createRoom". + * + * @param queryParams - A dict of query params (these will NOT be + * urlencoded). If unspecified, there will be no query params. + * + * @param body - The HTTP JSON body. + * + * @param opts - additional options + * + * @returns Promise which resolves to + * ``` + * { + * data: {Object}, + * headers: {Object}, + * code: {Number}, + * } + * ``` + * If `onlyData is set, this will resolve to the data` + * object only. + * @returns Rejects with an error if a problem + * occurred. This includes network problems and Matrix-specific error JSON. + */ + request(method, path, queryParams, body, opts) { + const fullUri = this.getUrl(path, queryParams, opts === null || opts === void 0 ? void 0 : opts.prefix, opts === null || opts === void 0 ? void 0 : opts.baseUrl); + return this.requestOtherUrl(method, fullUri, body, opts); + } + /** + * Perform a request to an arbitrary URL. + * @param method - The HTTP method e.g. "GET". + * @param url - The HTTP URL object. + * + * @param body - The HTTP JSON body. + * + * @param opts - additional options + * + * @returns Promise which resolves to data unless `onlyData` is specified as false, + * where the resolved value will be a fetch Response object. + * @returns Rejects with an error if a problem + * occurred. This includes network problems and Matrix-specific error JSON. + */ + requestOtherUrl(method, url, body, opts = {}) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + const headers = Object.assign({}, opts.headers || {}); + const json = (_a = opts.json) !== null && _a !== void 0 ? _a : true; + // We can't use getPrototypeOf here as objects made in other contexts e.g. over postMessage won't have same ref + const jsonBody = json && ((_b = body === null || body === void 0 ? void 0 : body.constructor) === null || _b === void 0 ? void 0 : _b.name) === Object.name; + if (json) { + if (jsonBody && !headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + if (!headers["Accept"]) { + headers["Accept"] = "application/json"; + } + } + const timeout = (_c = opts.localTimeoutMs) !== null && _c !== void 0 ? _c : this.opts.localTimeoutMs; + const keepAlive = (_d = opts.keepAlive) !== null && _d !== void 0 ? _d : false; + const signals = [this.abortController.signal]; + if (timeout !== undefined) { + signals.push((0, utils_1.timeoutSignal)(timeout)); + } + if (opts.abortSignal) { + signals.push(opts.abortSignal); + } + let data; + if (jsonBody) { + data = JSON.stringify(body); + } + else { + data = body; + } + const { signal, cleanup } = (0, utils_1.anySignal)(signals); + let res; + try { + res = yield this.fetch(url, { + signal, + method, + body: data, + headers, + mode: "cors", + redirect: "follow", + referrer: "", + referrerPolicy: "no-referrer", + cache: "no-cache", + credentials: "omit", + keepalive: keepAlive, + }); + } + catch (e) { + if (e.name === "AbortError") { + throw e; + } + throw new errors_1.ConnectionError("fetch failed", e); + } + finally { + cleanup(); + } + if (!res.ok) { + throw (0, utils_1.parseErrorResponse)(res, yield res.text()); + } + if (this.opts.onlyData) { + return json ? res.json() : res.text(); + } + return res; + }); + } + /** + * Form and return a homeserver request URL based on the given path params and prefix. + * @param path - The HTTP path after the supplied prefix e.g. "/createRoom". + * @param queryParams - A dict of query params (these will NOT be urlencoded). + * @param prefix - The full prefix to use e.g. "/_matrix/client/v2_alpha", defaulting to this.opts.prefix. + * @param baseUrl - The baseUrl to use e.g. "https://matrix.org/", defaulting to this.opts.baseUrl. + * @returns URL + */ + getUrl(path, queryParams, prefix, baseUrl) { + const url = new URL((baseUrl !== null && baseUrl !== void 0 ? baseUrl : this.opts.baseUrl) + (prefix !== null && prefix !== void 0 ? prefix : this.opts.prefix) + path); + if (queryParams) { + utils.encodeParams(queryParams, url.searchParams); + } + return url; + } +} +exports.FetchHttpApi = FetchHttpApi; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../utils":416,"./errors":365,"./interface":368,"./method":369,"./utils":371}],367:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MatrixHttpApi = void 0; +const fetch_1 = require("./fetch"); +const prefix_1 = require("./prefix"); +const utils = __importStar(require("../utils")); +const callbacks = __importStar(require("../realtime-callbacks")); +const method_1 = require("./method"); +const errors_1 = require("./errors"); +const utils_1 = require("./utils"); +__exportStar(require("./interface"), exports); +__exportStar(require("./prefix"), exports); +__exportStar(require("./errors"), exports); +__exportStar(require("./method"), exports); +__exportStar(require("./utils"), exports); +class MatrixHttpApi extends fetch_1.FetchHttpApi { + constructor() { + super(...arguments); + this.uploads = []; + } + /** + * Upload content to the homeserver + * + * @param file - The object to upload. On a browser, something that + * can be sent to XMLHttpRequest.send (typically a File). Under node.js, + * a Buffer, String or ReadStream. + * + * @param opts - options object + * + * @returns Promise which resolves to response object, as + * determined by this.opts.onlyData, opts.rawResponse, and + * opts.onlyContentUri. Rejects with an error (usually a MatrixError). + */ + uploadContent(file, opts = {}) { + var _a, _b, _c, _d, _e; + const includeFilename = (_a = opts.includeFilename) !== null && _a !== void 0 ? _a : true; + const abortController = (_b = opts.abortController) !== null && _b !== void 0 ? _b : new AbortController(); + // If the file doesn't have a mime type, use a default since the HS errors if we don't supply one. + const contentType = (_d = (_c = opts.type) !== null && _c !== void 0 ? _c : file.type) !== null && _d !== void 0 ? _d : "application/octet-stream"; + const fileName = (_e = opts.name) !== null && _e !== void 0 ? _e : file.name; + const upload = { + loaded: 0, + total: 0, + abortController, + }; + const defer = utils.defer(); + if (global.XMLHttpRequest) { + const xhr = new global.XMLHttpRequest(); + const timeoutFn = function () { + xhr.abort(); + defer.reject(new Error("Timeout")); + }; + // set an initial timeout of 30s; we'll advance it each time we get a progress notification + let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000); + xhr.onreadystatechange = function () { + switch (xhr.readyState) { + case global.XMLHttpRequest.DONE: + callbacks.clearTimeout(timeoutTimer); + try { + if (xhr.status === 0) { + throw new DOMException(xhr.statusText, "AbortError"); // mimic fetch API + } + if (!xhr.responseText) { + throw new Error("No response body."); + } + if (xhr.status >= 400) { + defer.reject((0, utils_1.parseErrorResponse)(xhr, xhr.responseText)); + } + else { + defer.resolve(JSON.parse(xhr.responseText)); + } + } + catch (err) { + if (err.name === "AbortError") { + defer.reject(err); + return; + } + defer.reject(new errors_1.ConnectionError("request failed", err)); + } + break; + } + }; + xhr.upload.onprogress = (ev) => { + var _a; + callbacks.clearTimeout(timeoutTimer); + upload.loaded = ev.loaded; + upload.total = ev.total; + timeoutTimer = callbacks.setTimeout(timeoutFn, 30000); + (_a = opts.progressHandler) === null || _a === void 0 ? void 0 : _a.call(opts, { + loaded: ev.loaded, + total: ev.total, + }); + }; + const url = this.getUrl("/upload", undefined, prefix_1.MediaPrefix.R0); + if (includeFilename && fileName) { + url.searchParams.set("filename", encodeURIComponent(fileName)); + } + if (!this.opts.useAuthorizationHeader && this.opts.accessToken) { + url.searchParams.set("access_token", encodeURIComponent(this.opts.accessToken)); + } + xhr.open(method_1.Method.Post, url.href); + if (this.opts.useAuthorizationHeader && this.opts.accessToken) { + xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken); + } + xhr.setRequestHeader("Content-Type", contentType); + xhr.send(file); + abortController.signal.addEventListener("abort", () => { + xhr.abort(); + }); + } + else { + const queryParams = {}; + if (includeFilename && fileName) { + queryParams.filename = fileName; + } + const headers = { "Content-Type": contentType }; + this.authedRequest(method_1.Method.Post, "/upload", queryParams, file, { + prefix: prefix_1.MediaPrefix.R0, + headers, + abortSignal: abortController.signal, + }) + .then((response) => { + return this.opts.onlyData ? response : response.json(); + }) + .then(defer.resolve, defer.reject); + } + // remove the upload from the list on completion + upload.promise = defer.promise.finally(() => { + utils.removeElement(this.uploads, (elem) => elem === upload); + }); + abortController.signal.addEventListener("abort", () => { + utils.removeElement(this.uploads, (elem) => elem === upload); + defer.reject(new DOMException("Aborted", "AbortError")); + }); + this.uploads.push(upload); + return upload.promise; + } + cancelUpload(promise) { + const upload = this.uploads.find((u) => u.promise === promise); + if (upload) { + upload.abortController.abort(); + return true; + } + return false; + } + getCurrentUploads() { + return this.uploads; + } + /** + * Get the content repository url with query parameters. + * @returns An object with a 'base', 'path' and 'params' for base URL, + * path and query parameters respectively. + */ + getContentUri() { + return { + base: this.opts.baseUrl, + path: prefix_1.MediaPrefix.R0 + "/upload", + params: { + access_token: this.opts.accessToken, + }, + }; + } +} +exports.MatrixHttpApi = MatrixHttpApi; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"../realtime-callbacks":399,"../utils":416,"./errors":365,"./fetch":366,"./interface":368,"./method":369,"./prefix":370,"./utils":371}],368:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.HttpApiEvent = void 0; +var HttpApiEvent; +(function (HttpApiEvent) { + HttpApiEvent["SessionLoggedOut"] = "Session.logged_out"; + HttpApiEvent["NoConsent"] = "no_consent"; +})(HttpApiEvent = exports.HttpApiEvent || (exports.HttpApiEvent = {})); + +},{}],369:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Method = void 0; +var Method; +(function (Method) { + Method["Get"] = "GET"; + Method["Put"] = "PUT"; + Method["Post"] = "POST"; + Method["Delete"] = "DELETE"; +})(Method = exports.Method || (exports.Method = {})); + +},{}],370:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaPrefix = exports.IdentityPrefix = exports.ClientPrefix = void 0; +var ClientPrefix; +(function (ClientPrefix) { + /** + * A constant representing the URI path for release 0 of the Client-Server HTTP API. + */ + ClientPrefix["R0"] = "/_matrix/client/r0"; + /** + * A constant representing the URI path for the legacy release v1 of the Client-Server HTTP API. + */ + ClientPrefix["V1"] = "/_matrix/client/v1"; + /** + * A constant representing the URI path for Client-Server API endpoints versioned at v3. + */ + ClientPrefix["V3"] = "/_matrix/client/v3"; + /** + * A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs. + */ + ClientPrefix["Unstable"] = "/_matrix/client/unstable"; +})(ClientPrefix = exports.ClientPrefix || (exports.ClientPrefix = {})); +var IdentityPrefix; +(function (IdentityPrefix) { + /** + * URI path for the v2 identity API + */ + IdentityPrefix["V2"] = "/_matrix/identity/v2"; +})(IdentityPrefix = exports.IdentityPrefix || (exports.IdentityPrefix = {})); +var MediaPrefix; +(function (MediaPrefix) { + /** + * URI path for the media repo API + */ + MediaPrefix["R0"] = "/_matrix/media/r0"; +})(MediaPrefix = exports.MediaPrefix || (exports.MediaPrefix = {})); + +},{}],371:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.retryNetworkOperation = exports.parseErrorResponse = exports.anySignal = exports.timeoutSignal = void 0; +const content_type_1 = require("content-type"); +const logger_1 = require("../logger"); +const utils_1 = require("../utils"); +const errors_1 = require("./errors"); +// Ponyfill for https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout +function timeoutSignal(ms) { + const controller = new AbortController(); + setTimeout(() => { + controller.abort(); + }, ms); + return controller.signal; +} +exports.timeoutSignal = timeoutSignal; +function anySignal(signals) { + const controller = new AbortController(); + function cleanup() { + for (const signal of signals) { + signal.removeEventListener("abort", onAbort); + } + } + function onAbort() { + controller.abort(); + cleanup(); + } + for (const signal of signals) { + if (signal.aborted) { + onAbort(); + break; + } + signal.addEventListener("abort", onAbort); + } + return { + signal: controller.signal, + cleanup, + }; +} +exports.anySignal = anySignal; +/** + * Attempt to turn an HTTP error response into a Javascript Error. + * + * If it is a JSON response, we will parse it into a MatrixError. Otherwise + * we return a generic Error. + * + * @param response - response object + * @param body - raw body of the response + * @returns + */ +function parseErrorResponse(response, body) { + let contentType; + try { + contentType = getResponseContentType(response); + } + catch (e) { + return e; + } + if ((contentType === null || contentType === void 0 ? void 0 : contentType.type) === "application/json" && body) { + return new errors_1.MatrixError(JSON.parse(body), response.status, isXhr(response) ? response.responseURL : response.url); + } + if ((contentType === null || contentType === void 0 ? void 0 : contentType.type) === "text/plain") { + return new errors_1.HTTPError(`Server returned ${response.status} error: ${body}`, response.status); + } + return new errors_1.HTTPError(`Server returned ${response.status} error`, response.status); +} +exports.parseErrorResponse = parseErrorResponse; +function isXhr(response) { + return "getResponseHeader" in response; +} +/** + * extract the Content-Type header from the response object, and + * parse it to a `{type, parameters}` object. + * + * returns null if no content-type header could be found. + * + * @param response - response object + * @returns parsed content-type header, or null if not found + */ +function getResponseContentType(response) { + let contentType; + if (isXhr(response)) { + contentType = response.getResponseHeader("Content-Type"); + } + else { + contentType = response.headers.get("Content-Type"); + } + if (!contentType) + return null; + try { + return (0, content_type_1.parse)(contentType); + } + catch (e) { + throw new Error(`Error parsing Content-Type '${contentType}': ${e}`); + } +} +/** + * Retries a network operation run in a callback. + * @param maxAttempts - maximum attempts to try + * @param callback - callback that returns a promise of the network operation. If rejected with ConnectionError, it will be retried by calling the callback again. + * @returns the result of the network operation + * @throws {@link ConnectionError} If after maxAttempts the callback still throws ConnectionError + */ +function retryNetworkOperation(maxAttempts, callback) { + return __awaiter(this, void 0, void 0, function* () { + let attempts = 0; + let lastConnectionError = null; + while (attempts < maxAttempts) { + try { + if (attempts > 0) { + const timeout = 1000 * Math.pow(2, attempts); + logger_1.logger.log(`network operation failed ${attempts} times, retrying in ${timeout}ms...`); + yield (0, utils_1.sleep)(timeout); + } + return yield callback(); + } + catch (err) { + if (err instanceof errors_1.ConnectionError) { + attempts += 1; + lastConnectionError = err; + } + else { + throw err; + } + } + } + throw lastConnectionError; + }); +} +exports.retryNetworkOperation = retryNetworkOperation; + +},{"../logger":374,"../utils":416,"./errors":365,"content-type":72}],372:[function(require,module,exports){ +"use strict"; +/* +Copyright 2019 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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.exists = void 0; +/** + * Check if an IndexedDB database exists. The only way to do so is to try opening it, so + * we do that and then delete it did not exist before. + * + * @param indexedDB - The `indexedDB` interface + * @param dbName - The database name to test for + * @returns Whether the database exists + */ +function exists(indexedDB, dbName) { + return new Promise((resolve, reject) => { + let exists = true; + const req = indexedDB.open(dbName); + req.onupgradeneeded = () => { + // Since we did not provide an explicit version when opening, this event + // should only fire if the DB did not exist before at any version. + exists = false; + }; + req.onblocked = () => reject(req.error); + req.onsuccess = () => { + const db = req.result; + db.close(); + if (!exists) { + // The DB did not exist before, but has been created as part of this + // existence check. Delete it now to restore previous state. Delete can + // actually take a while to complete in some browsers, so don't wait for + // it. This won't block future open calls that a store might issue next to + // properly set up the DB. + indexedDB.deleteDatabase(dbName); + } + resolve(exists); + }; + req.onerror = () => reject(req.error); + }); +} +exports.exists = exists; + +},{}],373:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InteractiveAuth = exports.AuthType = void 0; +const logger_1 = require("./logger"); +const utils_1 = require("./utils"); +const EMAIL_STAGE_TYPE = "m.login.email.identity"; +const MSISDN_STAGE_TYPE = "m.login.msisdn"; +var AuthType; +(function (AuthType) { + AuthType["Password"] = "m.login.password"; + AuthType["Recaptcha"] = "m.login.recaptcha"; + AuthType["Terms"] = "m.login.terms"; + AuthType["Email"] = "m.login.email.identity"; + AuthType["Msisdn"] = "m.login.msisdn"; + AuthType["Sso"] = "m.login.sso"; + AuthType["SsoUnstable"] = "org.matrix.login.sso"; + AuthType["Dummy"] = "m.login.dummy"; + AuthType["RegistrationToken"] = "m.login.registration_token"; + // For backwards compatability with servers that have not yet updated to + // use the stable "m.login.registration_token" type. + // The authentication flow is the same in both cases. + AuthType["UnstableRegistrationToken"] = "org.matrix.msc3231.login.registration_token"; +})(AuthType = exports.AuthType || (exports.AuthType = {})); +class NoAuthFlowFoundError extends Error { + // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + constructor(m, required_stages, flows) { + super(m); + this.required_stages = required_stages; + this.flows = flows; + this.name = "NoAuthFlowFoundError"; + } +} +/** + * Abstracts the logic used to drive the interactive auth process. + * + *

Components implementing an interactive auth flow should instantiate one of + * these, passing in the necessary callbacks to the constructor. They should + * then call attemptAuth, which will return a promise which will resolve or + * reject when the interactive-auth process completes. + * + *

Meanwhile, calls will be made to the startAuthStage and doRequest + * callbacks, and information gathered from the user can be submitted with + * submitAuthDict. + * + * @param opts - options object + */ +class InteractiveAuth { + constructor(opts) { + this.requestingEmailToken = false; + this.attemptAuthDeferred = null; + this.chosenFlow = null; + this.currentStage = null; + this.emailAttempt = 1; + // if we are currently trying to submit an auth dict (which includes polling) + // the promise the will resolve/reject when it completes + this.submitPromise = null; + /** + * Requests a new email token and sets the email sid for the validation session + */ + this.requestEmailToken = () => __awaiter(this, void 0, void 0, function* () { + if (!this.requestingEmailToken) { + logger_1.logger.trace("Requesting email token. Attempt: " + this.emailAttempt); + // If we've picked a flow with email auth, we send the email + // now because we want the request to fail as soon as possible + // if the email address is not valid (ie. already taken or not + // registered, depending on what the operation is). + this.requestingEmailToken = true; + try { + const requestTokenResult = yield this.requestEmailTokenCallback(this.inputs.emailAddress, this.clientSecret, this.emailAttempt++, this.data.session); + this.emailSid = requestTokenResult.sid; + logger_1.logger.trace("Email token request succeeded"); + } + finally { + this.requestingEmailToken = false; + } + } + else { + logger_1.logger.warn("Could not request email token: Already requesting"); + } + }); + this.matrixClient = opts.matrixClient; + this.data = opts.authData || {}; + this.requestCallback = opts.doRequest; + this.busyChangedCallback = opts.busyChanged; + // startAuthStage included for backwards compat + this.stateUpdatedCallback = opts.stateUpdated || opts.startAuthStage; + this.requestEmailTokenCallback = opts.requestEmailToken; + this.inputs = opts.inputs || {}; + if (opts.sessionId) + this.data.session = opts.sessionId; + this.clientSecret = opts.clientSecret || this.matrixClient.generateClientSecret(); + this.emailSid = opts.emailSid; + } + /** + * begin the authentication process. + * + * @returns which resolves to the response on success, + * or rejects with the error on failure. Rejects with NoAuthFlowFoundError if + * no suitable authentication flow can be found + */ + attemptAuth() { + var _a, _b; + // This promise will be quite long-lived and will resolve when the + // request is authenticated and completes successfully. + this.attemptAuthDeferred = (0, utils_1.defer)(); + // pluck the promise out now, as doRequest may clear before we return + const promise = this.attemptAuthDeferred.promise; + // if we have no flows, try a request to acquire the flows + if (!((_a = this.data) === null || _a === void 0 ? void 0 : _a.flows)) { + (_b = this.busyChangedCallback) === null || _b === void 0 ? void 0 : _b.call(this, true); + // use the existing sessionId, if one is present. + const auth = this.data.session ? { session: this.data.session } : null; + this.doRequest(auth).finally(() => { + var _a; + (_a = this.busyChangedCallback) === null || _a === void 0 ? void 0 : _a.call(this, false); + }); + } + else { + this.startNextAuthStage(); + } + return promise; + } + /** + * Poll to check if the auth session or current stage has been + * completed out-of-band. If so, the attemptAuth promise will + * be resolved. + */ + poll() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.data.session) + return; + // likewise don't poll if there is no auth session in progress + if (!this.attemptAuthDeferred) + return; + // if we currently have a request in flight, there's no point making + // another just to check what the status is + if (this.submitPromise) + return; + let authDict = {}; + if (this.currentStage == EMAIL_STAGE_TYPE) { + // The email can be validated out-of-band, but we need to provide the + // creds so the HS can go & check it. + if (this.emailSid) { + const creds = { + sid: this.emailSid, + client_secret: this.clientSecret, + }; + if (yield this.matrixClient.doesServerRequireIdServerParam()) { + const idServerParsedUrl = new URL(this.matrixClient.getIdentityServerUrl()); + creds.id_server = idServerParsedUrl.host; + } + authDict = { + type: EMAIL_STAGE_TYPE, + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + // See https://github.com/matrix-org/matrix-doc/issues/2220 + threepid_creds: creds, + threepidCreds: creds, + }; + } + } + this.submitAuthDict(authDict, true); + }); + } + /** + * get the auth session ID + * + * @returns session id + */ + getSessionId() { + var _a; + return (_a = this.data) === null || _a === void 0 ? void 0 : _a.session; + } + /** + * get the client secret used for validation sessions + * with the identity server. + * + * @returns client secret + */ + getClientSecret() { + return this.clientSecret; + } + /** + * get the server params for a given stage + * + * @param loginType - login type for the stage + * @returns any parameters from the server for this stage + */ + getStageParams(loginType) { + var _a; + return (_a = this.data.params) === null || _a === void 0 ? void 0 : _a[loginType]; + } + getChosenFlow() { + return this.chosenFlow; + } + /** + * submit a new auth dict and fire off the request. This will either + * make attemptAuth resolve/reject, or cause the startAuthStage callback + * to be called for a new stage. + * + * @param authData - new auth dict to send to the server. Should + * include a `type` property denoting the login type, as well as any + * other params for that stage. + * @param background - If true, this request failing will not result + * in the attemptAuth promise being rejected. This can be set to true + * for requests that just poll to see if auth has been completed elsewhere. + */ + submitAuthDict(authData, background = false) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (!this.attemptAuthDeferred) { + throw new Error("submitAuthDict() called before attemptAuth()"); + } + if (!background) { + (_a = this.busyChangedCallback) === null || _a === void 0 ? void 0 : _a.call(this, true); + } + // if we're currently trying a request, wait for it to finish + // as otherwise we can get multiple 200 responses which can mean + // things like multiple logins for register requests. + // (but discard any exceptions as we only care when its done, + // not whether it worked or not) + while (this.submitPromise) { + try { + yield this.submitPromise; + } + catch (e) { } + } + // use the sessionid from the last request, if one is present. + let auth; + if (this.data.session) { + auth = { + session: this.data.session, + }; + Object.assign(auth, authData); + } + else { + auth = authData; + } + try { + // NB. the 'background' flag is deprecated by the busyChanged + // callback and is here for backwards compat + this.submitPromise = this.doRequest(auth, background); + yield this.submitPromise; + } + finally { + this.submitPromise = null; + if (!background) { + (_b = this.busyChangedCallback) === null || _b === void 0 ? void 0 : _b.call(this, false); + } + } + }); + } + /** + * Gets the sid for the email validation session + * Specific to m.login.email.identity + * + * @returns The sid of the email auth session + */ + getEmailSid() { + return this.emailSid; + } + /** + * Sets the sid for the email validation session + * This must be set in order to successfully poll for completion + * of the email validation. + * Specific to m.login.email.identity + * + * @param sid - The sid for the email validation session + */ + setEmailSid(sid) { + this.emailSid = sid; + } + /** + * Fire off a request, and either resolve the promise, or call + * startAuthStage. + * + * @internal + * @param auth - new auth dict, including session id + * @param background - If true, this request is a background poll, so it + * failing will not result in the attemptAuth promise being rejected. + * This can be set to true for requests that just poll to see if auth has + * been completed elsewhere. + */ + doRequest(auth, background = false) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + try { + const result = yield this.requestCallback(auth, background); + this.attemptAuthDeferred.resolve(result); + this.attemptAuthDeferred = null; + } + catch (error) { + // sometimes UI auth errors don't come with flows + const errorFlows = (_b = (_a = error.data) === null || _a === void 0 ? void 0 : _a.flows) !== null && _b !== void 0 ? _b : null; + const haveFlows = this.data.flows || Boolean(errorFlows); + if (error.httpStatus !== 401 || !error.data || !haveFlows) { + // doesn't look like an interactive-auth failure. + if (!background) { + (_c = this.attemptAuthDeferred) === null || _c === void 0 ? void 0 : _c.reject(error); + } + else { + // We ignore all failures here (even non-UI auth related ones) + // since we don't want to suddenly fail if the internet connection + // had a blip whilst we were polling + logger_1.logger.log("Background poll request failed doing UI auth: ignoring", error); + } + } + if (!error.data) { + error.data = {}; + } + // if the error didn't come with flows, completed flows or session ID, + // copy over the ones we have. Synapse sometimes sends responses without + // any UI auth data (eg. when polling for email validation, if the email + // has not yet been validated). This appears to be a Synapse bug, which + // we workaround here. + if (!error.data.flows && + !error.data.completed && + !error.data.session) { + error.data.flows = this.data.flows; + error.data.completed = this.data.completed; + error.data.session = this.data.session; + } + this.data = error.data; + try { + this.startNextAuthStage(); + } + catch (e) { + this.attemptAuthDeferred.reject(e); + this.attemptAuthDeferred = null; + return; + } + if (!this.emailSid && ((_d = this.chosenFlow) === null || _d === void 0 ? void 0 : _d.stages.includes(AuthType.Email))) { + try { + yield this.requestEmailToken(); + // NB. promise is not resolved here - at some point, doRequest + // will be called again and if the user has jumped through all + // the hoops correctly, auth will be complete and the request + // will succeed. + // Also, we should expose the fact that this request has compledted + // so clients can know that the email has actually been sent. + } + catch (e) { + // we failed to request an email token, so fail the request. + // This could be due to the email already beeing registered + // (or not being registered, depending on what we're trying + // to do) or it could be a network failure. Either way, pass + // the failure up as the user can't complete auth if we can't + // send the email, for whatever reason. + this.attemptAuthDeferred.reject(e); + this.attemptAuthDeferred = null; + } + } + } + }); + } + /** + * Pick the next stage and call the callback + * + * @internal + * @throws {@link NoAuthFlowFoundError} If no suitable authentication flow can be found + */ + startNextAuthStage() { + var _a, _b, _c, _d; + const nextStage = this.chooseStage(); + if (!nextStage) { + throw new Error("No incomplete flows from the server"); + } + this.currentStage = nextStage; + if (nextStage === AuthType.Dummy) { + this.submitAuthDict({ + type: "m.login.dummy", + }); + return; + } + if (((_a = this.data) === null || _a === void 0 ? void 0 : _a.errcode) || ((_b = this.data) === null || _b === void 0 ? void 0 : _b.error)) { + this.stateUpdatedCallback(nextStage, { + errcode: ((_c = this.data) === null || _c === void 0 ? void 0 : _c.errcode) || "", + error: ((_d = this.data) === null || _d === void 0 ? void 0 : _d.error) || "", + }); + return; + } + this.stateUpdatedCallback(nextStage, nextStage === EMAIL_STAGE_TYPE ? { emailSid: this.emailSid } : {}); + } + /** + * Pick the next auth stage + * + * @internal + * @returns login type + * @throws {@link NoAuthFlowFoundError} If no suitable authentication flow can be found + */ + chooseStage() { + if (this.chosenFlow === null) { + this.chosenFlow = this.chooseFlow(); + } + logger_1.logger.log("Active flow => %s", JSON.stringify(this.chosenFlow)); + const nextStage = this.firstUncompletedStage(this.chosenFlow); + logger_1.logger.log("Next stage: %s", nextStage); + return nextStage; + } + /** + * Pick one of the flows from the returned list + * If a flow using all of the inputs is found, it will + * be returned, otherwise, null will be returned. + * + * Only flows using all given inputs are chosen because it + * is likely to be surprising if the user provides a + * credential and it is not used. For example, for registration, + * this could result in the email not being used which would leave + * the account with no means to reset a password. + * + * @internal + * @returns flow + * @throws {@link NoAuthFlowFoundError} If no suitable authentication flow can be found + */ + chooseFlow() { + const flows = this.data.flows || []; + // we've been given an email or we've already done an email part + const haveEmail = Boolean(this.inputs.emailAddress) || Boolean(this.emailSid); + const haveMsisdn = Boolean(this.inputs.phoneCountry) && Boolean(this.inputs.phoneNumber); + for (const flow of flows) { + let flowHasEmail = false; + let flowHasMsisdn = false; + for (const stage of flow.stages) { + if (stage === EMAIL_STAGE_TYPE) { + flowHasEmail = true; + } + else if (stage == MSISDN_STAGE_TYPE) { + flowHasMsisdn = true; + } + } + if (flowHasEmail == haveEmail && flowHasMsisdn == haveMsisdn) { + return flow; + } + } + const requiredStages = []; + if (haveEmail) + requiredStages.push(EMAIL_STAGE_TYPE); + if (haveMsisdn) + requiredStages.push(MSISDN_STAGE_TYPE); + // Throw an error with a fairly generic description, but with more + // information such that the app can give a better one if so desired. + throw new NoAuthFlowFoundError("No appropriate authentication flow found", requiredStages, flows); + } + /** + * Get the first uncompleted stage in the given flow + * + * @internal + * @returns login type + */ + firstUncompletedStage(flow) { + const completed = this.data.completed || []; + return flow.stages.find((stageType) => !completed.includes(stageType)); + } +} +exports.InteractiveAuth = InteractiveAuth; + +},{"./logger":374,"./utils":416}],374:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 André Jaenisch +Copyright 2019, 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.logger = void 0; +const loglevel_1 = __importDefault(require("loglevel")); +// This is to demonstrate, that you can use any namespace you want. +// Namespaces allow you to turn on/off the logging for specific parts of the +// application. +// An idea would be to control this via an environment variable (on Node.js). +// See https://www.npmjs.com/package/debug to see how this could be implemented +// Part of #332 is introducing a logging library in the first place. +const DEFAULT_NAMESPACE = "matrix"; +// because rageshakes in react-sdk hijack the console log, also at module load time, +// initializing the logger here races with the initialization of rageshakes. +// to avoid the issue, we override the methodFactory of loglevel that binds to the +// console methods at initialization time by a factory that looks up the console methods +// when logging so we always get the current value of console methods. +loglevel_1.default.methodFactory = function (methodName, logLevel, loggerName) { + return function (...args) { + /* eslint-disable @typescript-eslint/no-invalid-this */ + if (this.prefix) { + args.unshift(this.prefix); + } + /* eslint-enable @typescript-eslint/no-invalid-this */ + const supportedByConsole = methodName === "error" || methodName === "warn" || methodName === "trace" || methodName === "info"; + /* eslint-disable no-console */ + if (supportedByConsole) { + return console[methodName](...args); + } + else { + return console.log(...args); + } + /* eslint-enable no-console */ + }; +}; +/** + * Drop-in replacement for `console` using {@link https://www.npmjs.com/package/loglevel|loglevel}. + * Can be tailored down to specific use cases if needed. + */ +exports.logger = loglevel_1.default.getLogger(DEFAULT_NAMESPACE); +exports.logger.setLevel(loglevel_1.default.levels.DEBUG, false); +function extendLogger(logger) { + logger.withPrefix = function (prefix) { + const existingPrefix = this.prefix || ""; + return getPrefixedLogger(existingPrefix + prefix); + }; +} +extendLogger(exports.logger); +function getPrefixedLogger(prefix) { + const prefixLogger = loglevel_1.default.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`); + if (prefixLogger.prefix !== prefix) { + // Only do this setup work the first time through, as loggers are saved by name. + extendLogger(prefixLogger); + prefixLogger.prefix = prefix; + prefixLogger.setLevel(loglevel_1.default.levels.DEBUG, false); + } + return prefixLogger; +} + +},{"loglevel":151}],375:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2015-2022 The Matrix.org Foundation C.I.C. + +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. +*/ +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 __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createRoomWidgetClient = exports.createClient = exports.setCryptoStoreFactory = exports.GroupCallType = exports.GroupCallState = exports.GroupCallIntent = exports.GroupCallEvent = exports.createNewMatrixCall = exports.SecretStorage = exports.ContentHelpers = void 0; +const memory_crypto_store_1 = require("./crypto/store/memory-crypto-store"); +const memory_1 = require("./store/memory"); +const scheduler_1 = require("./scheduler"); +const client_1 = require("./client"); +const embedded_1 = require("./embedded"); +__exportStar(require("./client"), exports); +__exportStar(require("./embedded"), exports); +__exportStar(require("./http-api"), exports); +__exportStar(require("./autodiscovery"), exports); +__exportStar(require("./sync-accumulator"), exports); +__exportStar(require("./errors"), exports); +__exportStar(require("./models/beacon"), exports); +__exportStar(require("./models/event"), exports); +__exportStar(require("./models/room"), exports); +__exportStar(require("./models/event-timeline"), exports); +__exportStar(require("./models/event-timeline-set"), exports); +__exportStar(require("./models/poll"), exports); +__exportStar(require("./models/room-member"), exports); +__exportStar(require("./models/room-state"), exports); +__exportStar(require("./models/user"), exports); +__exportStar(require("./scheduler"), exports); +__exportStar(require("./filter"), exports); +__exportStar(require("./timeline-window"), exports); +__exportStar(require("./interactive-auth"), exports); +__exportStar(require("./service-types"), exports); +__exportStar(require("./store/memory"), exports); +__exportStar(require("./store/indexeddb"), exports); +__exportStar(require("./crypto/store/memory-crypto-store"), exports); +__exportStar(require("./crypto/store/indexeddb-crypto-store"), exports); +__exportStar(require("./content-repo"), exports); +__exportStar(require("./@types/event"), exports); +__exportStar(require("./@types/PushRules"), exports); +__exportStar(require("./@types/partials"), exports); +__exportStar(require("./@types/requests"), exports); +__exportStar(require("./@types/search"), exports); +__exportStar(require("./models/room-summary"), exports); +exports.ContentHelpers = __importStar(require("./content-helpers")); +exports.SecretStorage = __importStar(require("./secret-storage")); +var call_1 = require("./webrtc/call"); +Object.defineProperty(exports, "createNewMatrixCall", { enumerable: true, get: function () { return call_1.createNewMatrixCall; } }); +var groupCall_1 = require("./webrtc/groupCall"); +Object.defineProperty(exports, "GroupCallEvent", { enumerable: true, get: function () { return groupCall_1.GroupCallEvent; } }); +Object.defineProperty(exports, "GroupCallIntent", { enumerable: true, get: function () { return groupCall_1.GroupCallIntent; } }); +Object.defineProperty(exports, "GroupCallState", { enumerable: true, get: function () { return groupCall_1.GroupCallState; } }); +Object.defineProperty(exports, "GroupCallType", { enumerable: true, get: function () { return groupCall_1.GroupCallType; } }); +let cryptoStoreFactory = () => new memory_crypto_store_1.MemoryCryptoStore(); +/** + * Configure a different factory to be used for creating crypto stores + * + * @param fac - a function which will return a new {@link CryptoStore} + */ +function setCryptoStoreFactory(fac) { + cryptoStoreFactory = fac; +} +exports.setCryptoStoreFactory = setCryptoStoreFactory; +function amendClientOpts(opts) { + var _a, _b, _c; + opts.store = + (_a = opts.store) !== null && _a !== void 0 ? _a : new memory_1.MemoryStore({ + localStorage: global.localStorage, + }); + opts.scheduler = (_b = opts.scheduler) !== null && _b !== void 0 ? _b : new scheduler_1.MatrixScheduler(); + opts.cryptoStore = (_c = opts.cryptoStore) !== null && _c !== void 0 ? _c : cryptoStoreFactory(); + return opts; +} +/** + * Construct a Matrix Client. Similar to {@link MatrixClient} + * except that the 'request', 'store' and 'scheduler' dependencies are satisfied. + * @param opts - The configuration options for this client. These configuration + * options will be passed directly to {@link MatrixClient}. + * + * @returns A new matrix client. + * @see {@link MatrixClient} for the full list of options for + * `opts`. + */ +function createClient(opts) { + return new client_1.MatrixClient(amendClientOpts(opts)); +} +exports.createClient = createClient; +function createRoomWidgetClient(widgetApi, capabilities, roomId, opts) { + return new embedded_1.RoomWidgetClient(widgetApi, capabilities, roomId, amendClientOpts(opts)); +} +exports.createRoomWidgetClient = createRoomWidgetClient; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./@types/PushRules":304,"./@types/event":306,"./@types/partials":309,"./@types/requests":312,"./@types/search":313,"./autodiscovery":319,"./client":321,"./content-helpers":322,"./content-repo":323,"./crypto/store/indexeddb-crypto-store":346,"./crypto/store/memory-crypto-store":348,"./embedded":358,"./errors":359,"./filter":364,"./http-api":367,"./interactive-auth":373,"./models/beacon":378,"./models/event":383,"./models/event-timeline":382,"./models/event-timeline-set":381,"./models/poll":385,"./models/room":392,"./models/room-member":389,"./models/room-state":390,"./models/room-summary":391,"./models/user":396,"./scheduler":403,"./secret-storage":404,"./service-types":405,"./store/indexeddb":410,"./store/memory":411,"./sync-accumulator":413,"./timeline-window":415,"./webrtc/call":418,"./webrtc/groupCall":422}],376:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MSC3089Branch = void 0; +const event_1 = require("../@types/event"); +const event_timeline_1 = require("./event-timeline"); +/** + * Represents a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) branch - a reference + * to a file (leaf) in the tree. Note that this is UNSTABLE and subject to breaking changes + * without notice. + */ +class MSC3089Branch { + constructor(client, indexEvent, directory) { + this.client = client; + this.indexEvent = indexEvent; + this.directory = directory; + // Nothing to do + } + /** + * The file ID. + */ + get id() { + const stateKey = this.indexEvent.getStateKey(); + if (!stateKey) { + throw new Error("State key not found for branch"); + } + return stateKey; + } + /** + * Whether this branch is active/valid. + */ + get isActive() { + return this.indexEvent.getContent()["active"] === true; + } + /** + * Version for the file, one-indexed. + */ + get version() { + var _a; + return (_a = this.indexEvent.getContent()["version"]) !== null && _a !== void 0 ? _a : 1; + } + get roomId() { + return this.indexEvent.getRoomId(); + } + /** + * Deletes the file from the tree, including all prior edits/versions. + * @returns Promise which resolves when complete. + */ + delete() { + return __awaiter(this, void 0, void 0, function* () { + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, {}, this.id); + yield this.client.redactEvent(this.roomId, this.id); + const nextVersion = (yield this.getVersionHistory())[1]; // [0] will be us + if (nextVersion) + yield nextVersion.delete(); // implicit recursion + }); + } + /** + * Gets the name for this file. + * @returns The name, or "Unnamed File" if unknown. + */ + getName() { + return this.indexEvent.getContent()["name"] || "Unnamed File"; + } + /** + * Sets the name for this file. + * @param name - The new name for this file. + * @returns Promise which resolves when complete. + */ + setName(name) { + return __awaiter(this, void 0, void 0, function* () { + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, Object.assign(Object.assign({}, this.indexEvent.getContent()), { name: name }), this.id); + }); + } + /** + * Gets whether or not a file is locked. + * @returns True if locked, false otherwise. + */ + isLocked() { + return this.indexEvent.getContent()["locked"] || false; + } + /** + * Sets a file as locked or unlocked. + * @param locked - True to lock the file, false otherwise. + * @returns Promise which resolves when complete. + */ + setLocked(locked) { + return __awaiter(this, void 0, void 0, function* () { + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, Object.assign(Object.assign({}, this.indexEvent.getContent()), { locked: locked }), this.id); + }); + } + /** + * Gets information about the file needed to download it. + * @returns Information about the file. + */ + getFileInfo() { + return __awaiter(this, void 0, void 0, function* () { + const event = yield this.getFileEvent(); + const file = event.getOriginalContent()["file"]; + const httpUrl = this.client.mxcUrlToHttp(file["url"]); + if (!httpUrl) { + throw new Error(`No HTTP URL available for ${file["url"]}`); + } + return { info: file, httpUrl: httpUrl }; + }); + } + /** + * Gets the event the file points to. + * @returns Promise which resolves to the file's event. + */ + getFileEvent() { + return __awaiter(this, void 0, void 0, function* () { + const room = this.client.getRoom(this.roomId); + if (!room) + throw new Error("Unknown room"); + let event = room.getUnfilteredTimelineSet().findEventById(this.id); + // keep scrolling back if needed until we find the event or reach the start of the room: + while (!event && room.getLiveTimeline().getState(event_timeline_1.EventTimeline.BACKWARDS).paginationToken) { + yield this.client.scrollback(room, 100); + event = room.getUnfilteredTimelineSet().findEventById(this.id); + } + if (!event) + throw new Error("Failed to find event"); + // Sometimes the event isn't decrypted for us, so do that. We specifically set `emit: true` + // to ensure that the relations system in the sdk will function. + yield this.client.decryptEventIfNeeded(event, { emit: true, isRetry: true }); + return event; + }); + } + /** + * Creates a new version of this file with contents in a type that is compatible with MatrixClient.uploadContent(). + * @param name - The name of the file. + * @param encryptedContents - The encrypted contents. + * @param info - The encrypted file information. + * @param additionalContent - Optional event content fields to include in the message. + * @returns Promise which resolves to the file event's sent response. + */ + createNewVersion(name, encryptedContents, info, additionalContent) { + return __awaiter(this, void 0, void 0, function* () { + const fileEventResponse = yield this.directory.createFile(name, encryptedContents, info, Object.assign(Object.assign({}, (additionalContent !== null && additionalContent !== void 0 ? additionalContent : {})), { "m.new_content": true, "m.relates_to": { + rel_type: event_1.RelationType.Replace, + event_id: this.id, + } })); + // Update the version of the new event + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, { + active: true, + name: name, + version: this.version + 1, + }, fileEventResponse["event_id"]); + // Deprecate ourselves + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, Object.assign(Object.assign({}, this.indexEvent.getContent()), { active: false }), this.id); + return fileEventResponse; + }); + } + /** + * Gets the file's version history, starting at this file. + * @returns Promise which resolves to the file's version history, with the + * first element being the current version and the last element being the first version. + */ + getVersionHistory() { + return __awaiter(this, void 0, void 0, function* () { + const fileHistory = []; + fileHistory.push(this); // start with ourselves + const room = this.client.getRoom(this.roomId); + if (!room) + throw new Error("Invalid or unknown room"); + // Clone the timeline to reverse it, getting most-recent-first ordering, hopefully + // shortening the awful loop below. Without the clone, we can unintentionally mutate + // the timeline. + const timelineEvents = [...room.getLiveTimeline().getEvents()].reverse(); + // XXX: This is a very inefficient search, but it's the best we can do with the + // relations structure we have in the SDK. As of writing, it is not worth the + // investment in improving the structure. + let childEvent; + let parentEvent = yield this.getFileEvent(); + do { + childEvent = timelineEvents.find((e) => e.replacingEventId() === parentEvent.getId()); + if (childEvent) { + const branch = this.directory.getFile(childEvent.getId()); + if (branch) { + fileHistory.push(branch); + parentEvent = childEvent; + } + else { + break; // prevent infinite loop + } + } + } while (childEvent); + return fileHistory; + }); + } +} +exports.MSC3089Branch = MSC3089Branch; + +},{"../@types/event":306,"./event-timeline":382}],377:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MSC3089TreeSpace = exports.TreePermissions = exports.DEFAULT_TREE_POWER_LEVELS_TEMPLATE = void 0; +const p_retry_1 = __importDefault(require("p-retry")); +const event_1 = require("../@types/event"); +const logger_1 = require("../logger"); +const utils_1 = require("../utils"); +const MSC3089Branch_1 = require("./MSC3089Branch"); +const megolm_1 = require("../crypto/algorithms/megolm"); +/** + * The recommended defaults for a tree space's power levels. Note that this + * is UNSTABLE and subject to breaking changes without notice. + */ +exports.DEFAULT_TREE_POWER_LEVELS_TEMPLATE = { + // Owner + invite: 100, + kick: 100, + ban: 100, + // Editor + redact: 50, + state_default: 50, + events_default: 50, + // Viewer + users_default: 0, + // Mixed + events: { + [event_1.EventType.RoomPowerLevels]: 100, + [event_1.EventType.RoomHistoryVisibility]: 100, + [event_1.EventType.RoomTombstone]: 100, + [event_1.EventType.RoomEncryption]: 100, + [event_1.EventType.RoomName]: 50, + [event_1.EventType.RoomMessage]: 50, + [event_1.EventType.RoomMessageEncrypted]: 50, + [event_1.EventType.Sticker]: 50, + }, + users: {}, // defined by calling code +}; +/** + * Ease-of-use representation for power levels represented as simple roles. + * Note that this is UNSTABLE and subject to breaking changes without notice. + */ +var TreePermissions; +(function (TreePermissions) { + TreePermissions["Viewer"] = "viewer"; + TreePermissions["Editor"] = "editor"; + TreePermissions["Owner"] = "owner"; +})(TreePermissions = exports.TreePermissions || (exports.TreePermissions = {})); +/** + * Represents a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) + * file tree Space. Note that this is UNSTABLE and subject to breaking changes + * without notice. + */ +class MSC3089TreeSpace { + constructor(client, roomId) { + this.client = client; + this.roomId = roomId; + this.room = this.client.getRoom(this.roomId); + if (!this.room) + throw new Error("Unknown room"); + } + /** + * Syntactic sugar for room ID of the Space. + */ + get id() { + return this.roomId; + } + /** + * Whether or not this is a top level space. + */ + get isTopLevel() { + // XXX: This is absolutely not how you find out if the space is top level + // but is safe for a managed usecase like we offer in the SDK. + const parentEvents = this.room.currentState.getStateEvents(event_1.EventType.SpaceParent); + if (!(parentEvents === null || parentEvents === void 0 ? void 0 : parentEvents.length)) + return true; + return parentEvents.every((e) => { var _a; return !((_a = e.getContent()) === null || _a === void 0 ? void 0 : _a["via"]); }); + } + /** + * Sets the name of the tree space. + * @param name - The new name for the space. + * @returns Promise which resolves when complete. + */ + setName(name) { + return __awaiter(this, void 0, void 0, function* () { + yield this.client.sendStateEvent(this.roomId, event_1.EventType.RoomName, { name }, ""); + }); + } + /** + * Invites a user to the tree space. They will be given the default Viewer + * permission level unless specified elsewhere. + * @param userId - The user ID to invite. + * @param andSubspaces - True (default) to invite the user to all + * directories/subspaces too, recursively. + * @param shareHistoryKeys - True (default) to share encryption keys + * with the invited user. This will allow them to decrypt the events (files) + * in the tree. Keys will not be shared if the room is lacking appropriate + * history visibility (by default, history visibility is "shared" in trees, + * which is an appropriate visibility for these purposes). + * @returns Promise which resolves when complete. + */ + invite(userId, andSubspaces = true, shareHistoryKeys = true) { + return __awaiter(this, void 0, void 0, function* () { + const promises = [this.retryInvite(userId)]; + if (andSubspaces) { + promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys))); + } + return Promise.all(promises).then(() => { + // Note: key sharing is default on because for file trees it is relatively important that the invite + // target can actually decrypt the files. The implied use case is that by inviting a user to the tree + // it means the sender would like the receiver to view/download the files contained within, much like + // sharing a folder in other circles. + if (shareHistoryKeys && (0, megolm_1.isRoomSharedHistory)(this.room)) { + // noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails. + this.client.sendSharedHistoryKeys(this.roomId, [userId]); + } + }); + }); + } + retryInvite(userId) { + return (0, utils_1.simpleRetryOperation)(() => __awaiter(this, void 0, void 0, function* () { + yield this.client.invite(this.roomId, userId).catch((e) => { + // We don't want to retry permission errors forever... + if ((e === null || e === void 0 ? void 0 : e.errcode) === "M_FORBIDDEN") { + throw new p_retry_1.default.AbortError(e); + } + throw e; + }); + })); + } + /** + * Sets the permissions of a user to the given role. Note that if setting a user + * to Owner then they will NOT be able to be demoted. If the user does not have + * permission to change the power level of the target, an error will be thrown. + * @param userId - The user ID to change the role of. + * @param role - The role to assign. + * @returns Promise which resolves when complete. + */ + setPermissions(userId, role) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const currentPls = this.room.currentState.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + if (Array.isArray(currentPls)) + throw new Error("Unexpected return type for power levels"); + const pls = (currentPls === null || currentPls === void 0 ? void 0 : currentPls.getContent()) || {}; + const viewLevel = pls["users_default"] || 0; + const editLevel = pls["events_default"] || 50; + const adminLevel = ((_a = pls["events"]) === null || _a === void 0 ? void 0 : _a[event_1.EventType.RoomPowerLevels]) || 100; + const users = pls["users"] || {}; + switch (role) { + case TreePermissions.Viewer: + users[userId] = viewLevel; + break; + case TreePermissions.Editor: + users[userId] = editLevel; + break; + case TreePermissions.Owner: + users[userId] = adminLevel; + break; + default: + throw new Error("Invalid role: " + role); + } + pls["users"] = users; + yield this.client.sendStateEvent(this.roomId, event_1.EventType.RoomPowerLevels, pls, ""); + }); + } + /** + * Gets the current permissions of a user. Note that any users missing explicit permissions (or not + * in the space) will be considered Viewers. Appropriate membership checks need to be performed + * elsewhere. + * @param userId - The user ID to check permissions of. + * @returns The permissions for the user, defaulting to Viewer. + */ + getPermissions(userId) { + var _a, _b; + const currentPls = this.room.currentState.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + if (Array.isArray(currentPls)) + throw new Error("Unexpected return type for power levels"); + const pls = (currentPls === null || currentPls === void 0 ? void 0 : currentPls.getContent()) || {}; + const viewLevel = pls["users_default"] || 0; + const editLevel = pls["events_default"] || 50; + const adminLevel = ((_a = pls["events"]) === null || _a === void 0 ? void 0 : _a[event_1.EventType.RoomPowerLevels]) || 100; + const userLevel = ((_b = pls["users"]) === null || _b === void 0 ? void 0 : _b[userId]) || viewLevel; + if (userLevel >= adminLevel) + return TreePermissions.Owner; + if (userLevel >= editLevel) + return TreePermissions.Editor; + return TreePermissions.Viewer; + } + /** + * Creates a directory under this tree space, represented as another tree space. + * @param name - The name for the directory. + * @returns Promise which resolves to the created directory. + */ + createDirectory(name) { + return __awaiter(this, void 0, void 0, function* () { + const directory = yield this.client.unstableCreateFileTree(name); + yield this.client.sendStateEvent(this.roomId, event_1.EventType.SpaceChild, { + via: [this.client.getDomain()], + }, directory.roomId); + yield this.client.sendStateEvent(directory.roomId, event_1.EventType.SpaceParent, { + via: [this.client.getDomain()], + }, this.roomId); + return directory; + }); + } + /** + * Gets a list of all known immediate subdirectories to this tree space. + * @returns The tree spaces (directories). May be empty, but not null. + */ + getDirectories() { + const trees = []; + const children = this.room.currentState.getStateEvents(event_1.EventType.SpaceChild); + for (const child of children) { + try { + const stateKey = child.getStateKey(); + if (stateKey) { + const tree = this.client.unstableGetFileTreeSpace(stateKey); + if (tree) + trees.push(tree); + } + } + catch (e) { + logger_1.logger.warn("Unable to create tree space instance for listing. Are we joined?", e); + } + } + return trees; + } + /** + * Gets a subdirectory of a given ID under this tree space. Note that this will not recurse + * into children and instead only look one level deep. + * @param roomId - The room ID (directory ID) to find. + * @returns The directory, or undefined if not found. + */ + getDirectory(roomId) { + return this.getDirectories().find((r) => r.roomId === roomId); + } + /** + * Deletes the tree, kicking all members and deleting **all subdirectories**. + * @returns Promise which resolves when complete. + */ + delete() { + return __awaiter(this, void 0, void 0, function* () { + const subdirectories = this.getDirectories(); + for (const dir of subdirectories) { + yield dir.delete(); + } + const kickMemberships = ["invite", "knock", "join"]; + const members = this.room.currentState.getStateEvents(event_1.EventType.RoomMember); + for (const member of members) { + const isNotUs = member.getStateKey() !== this.client.getUserId(); + if (isNotUs && kickMemberships.includes(member.getContent().membership)) { + const stateKey = member.getStateKey(); + if (!stateKey) { + throw new Error("State key not found for branch"); + } + yield this.client.kick(this.roomId, stateKey, "Room deleted"); + } + } + yield this.client.leave(this.roomId); + }); + } + getOrderedChildren(children) { + const ordered = children + .map((c) => ({ roomId: c.getStateKey(), order: c.getContent()["order"] })) + .filter((c) => c.roomId); + ordered.sort((a, b) => { + var _a, _b, _c, _d; + if (a.order && !b.order) { + return -1; + } + else if (!a.order && b.order) { + return 1; + } + else if (!a.order && !b.order) { + const roomA = this.client.getRoom(a.roomId); + const roomB = this.client.getRoom(b.roomId); + if (!roomA || !roomB) { + // just don't bother trying to do more partial sorting + return (0, utils_1.lexicographicCompare)(a.roomId, b.roomId); + } + const createTsA = (_b = (_a = roomA.currentState.getStateEvents(event_1.EventType.RoomCreate, "")) === null || _a === void 0 ? void 0 : _a.getTs()) !== null && _b !== void 0 ? _b : 0; + const createTsB = (_d = (_c = roomB.currentState.getStateEvents(event_1.EventType.RoomCreate, "")) === null || _c === void 0 ? void 0 : _c.getTs()) !== null && _d !== void 0 ? _d : 0; + if (createTsA === createTsB) { + return (0, utils_1.lexicographicCompare)(a.roomId, b.roomId); + } + return createTsA - createTsB; + } + else { + // both not-null orders + return (0, utils_1.lexicographicCompare)(a.order, b.order); + } + }); + return ordered; + } + getParentRoom() { + const parents = this.room.currentState.getStateEvents(event_1.EventType.SpaceParent); + const parent = parents[0]; // XXX: Wild assumption + if (!parent) + throw new Error("Expected to have a parent in a non-top level space"); + // XXX: We are assuming the parent is a valid tree space. + // We probably don't need to validate the parent room state for this usecase though. + const stateKey = parent.getStateKey(); + if (!stateKey) + throw new Error("No state key found for parent"); + const parentRoom = this.client.getRoom(stateKey); + if (!parentRoom) + throw new Error("Unable to locate room for parent"); + return parentRoom; + } + /** + * Gets the current order index for this directory. Note that if this is the top level space + * then -1 will be returned. + * @returns The order index of this space. + */ + getOrder() { + if (this.isTopLevel) + return -1; + const parentRoom = this.getParentRoom(); + const children = parentRoom.currentState.getStateEvents(event_1.EventType.SpaceChild); + const ordered = this.getOrderedChildren(children); + return ordered.findIndex((c) => c.roomId === this.roomId); + } + /** + * Sets the order index for this directory within its parent. Note that if this is a top level + * space then an error will be thrown. -1 can be used to move the child to the start, and numbers + * larger than the number of children can be used to move the child to the end. + * @param index - The new order index for this space. + * @returns Promise which resolves when complete. + * @throws Throws if this is a top level space. + */ + setOrder(index) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (this.isTopLevel) + throw new Error("Cannot set order of top level spaces currently"); + const parentRoom = this.getParentRoom(); + const children = parentRoom.currentState.getStateEvents(event_1.EventType.SpaceChild); + const ordered = this.getOrderedChildren(children); + index = Math.max(Math.min(index, ordered.length - 1), 0); + const currentIndex = this.getOrder(); + const movingUp = currentIndex < index; + if (movingUp && index === ordered.length - 1) { + index--; + } + else if (!movingUp && index === 0) { + index++; + } + const prev = ordered[movingUp ? index : index - 1]; + const next = ordered[movingUp ? index + 1 : index]; + let newOrder = utils_1.DEFAULT_ALPHABET[0]; + let ensureBeforeIsSane = false; + if (!prev) { + // Move to front + if (next === null || next === void 0 ? void 0 : next.order) { + newOrder = (0, utils_1.prevString)(next.order); + } + } + else if (index === ordered.length - 1) { + // Move to back + if (next === null || next === void 0 ? void 0 : next.order) { + newOrder = (0, utils_1.nextString)(next.order); + } + } + else { + // Move somewhere in the middle + const startOrder = prev === null || prev === void 0 ? void 0 : prev.order; + const endOrder = next === null || next === void 0 ? void 0 : next.order; + if (startOrder && endOrder) { + if (startOrder === endOrder) { + // Error case: just move +1 to break out of awful math + newOrder = (0, utils_1.nextString)(startOrder); + } + else { + newOrder = (0, utils_1.averageBetweenStrings)(startOrder, endOrder); + } + } + else { + if (startOrder) { + // We're at the end (endOrder is null, so no explicit order) + newOrder = (0, utils_1.nextString)(startOrder); + } + else if (endOrder) { + // We're at the start (startOrder is null, so nothing before us) + newOrder = (0, utils_1.prevString)(endOrder); + } + else { + // Both points are unknown. We're likely in a range where all the children + // don't have particular order values, so we may need to update them too. + // The other possibility is there's only us as a child, but we should have + // shown up in the other states. + ensureBeforeIsSane = true; + } + } + } + if (ensureBeforeIsSane) { + // We were asked by the order algorithm to prepare the moving space for a landing + // in the undefined order part of the order array, which means we need to update the + // spaces that come before it with a stable order value. + let lastOrder; + for (let i = 0; i <= index; i++) { + const target = ordered[i]; + if (i === 0) { + lastOrder = target.order; + } + if (!target.order) { + // XXX: We should be creating gaps to avoid conflicts + lastOrder = lastOrder ? (0, utils_1.nextString)(lastOrder) : utils_1.DEFAULT_ALPHABET[0]; + const currentChild = parentRoom.currentState.getStateEvents(event_1.EventType.SpaceChild, target.roomId); + const content = (_a = currentChild === null || currentChild === void 0 ? void 0 : currentChild.getContent()) !== null && _a !== void 0 ? _a : { via: [this.client.getDomain()] }; + yield this.client.sendStateEvent(parentRoom.roomId, event_1.EventType.SpaceChild, Object.assign(Object.assign({}, content), { order: lastOrder }), target.roomId); + } + else { + lastOrder = target.order; + } + } + if (lastOrder) { + newOrder = (0, utils_1.nextString)(lastOrder); + } + } + // TODO: Deal with order conflicts by reordering + // Now we can finally update our own order state + const currentChild = parentRoom.currentState.getStateEvents(event_1.EventType.SpaceChild, this.roomId); + const content = (_b = currentChild === null || currentChild === void 0 ? void 0 : currentChild.getContent()) !== null && _b !== void 0 ? _b : { via: [this.client.getDomain()] }; + yield this.client.sendStateEvent(parentRoom.roomId, event_1.EventType.SpaceChild, Object.assign(Object.assign({}, content), { + // TODO: Safely constrain to 50 character limit required by spaces. + order: newOrder }), this.roomId); + }); + } + /** + * Creates (uploads) a new file to this tree. The file must have already been encrypted for the room. + * The file contents are in a type that is compatible with MatrixClient.uploadContent(). + * @param name - The name of the file. + * @param encryptedContents - The encrypted contents. + * @param info - The encrypted file information. + * @param additionalContent - Optional event content fields to include in the message. + * @returns Promise which resolves to the file event's sent response. + */ + createFile(name, encryptedContents, info, additionalContent) { + return __awaiter(this, void 0, void 0, function* () { + const { content_uri: mxc } = yield this.client.uploadContent(encryptedContents, { + includeFilename: false, + }); + info.url = mxc; + const fileContent = { + msgtype: event_1.MsgType.File, + body: name, + url: mxc, + file: info, + }; + additionalContent = additionalContent !== null && additionalContent !== void 0 ? additionalContent : {}; + if (additionalContent["m.new_content"]) { + // We do the right thing according to the spec, but due to how relations are + // handled we also end up duplicating this information to the regular `content` + // as well. + additionalContent["m.new_content"] = fileContent; + } + const res = yield this.client.sendMessage(this.roomId, Object.assign(Object.assign(Object.assign({}, additionalContent), fileContent), { [event_1.UNSTABLE_MSC3089_LEAF.name]: {} })); + yield this.client.sendStateEvent(this.roomId, event_1.UNSTABLE_MSC3089_BRANCH.name, { + active: true, + name: name, + }, res["event_id"]); + return res; + }); + } + /** + * Retrieves a file from the tree. + * @param fileEventId - The event ID of the file. + * @returns The file, or null if not found. + */ + getFile(fileEventId) { + const branch = this.room.currentState.getStateEvents(event_1.UNSTABLE_MSC3089_BRANCH.name, fileEventId); + return branch ? new MSC3089Branch_1.MSC3089Branch(this.client, branch, this) : null; + } + /** + * Gets an array of all known files for the tree. + * @returns The known files. May be empty, but not null. + */ + listFiles() { + return this.listAllFiles().filter((b) => b.isActive); + } + /** + * Gets an array of all known files for the tree, including inactive/invalid ones. + * @returns The known files. May be empty, but not null. + */ + listAllFiles() { + var _a; + const branches = (_a = this.room.currentState.getStateEvents(event_1.UNSTABLE_MSC3089_BRANCH.name)) !== null && _a !== void 0 ? _a : []; + return branches.map((e) => new MSC3089Branch_1.MSC3089Branch(this.client, e, this)); + } +} +exports.MSC3089TreeSpace = MSC3089TreeSpace; + +},{"../@types/event":306,"../crypto/algorithms/megolm":334,"../logger":374,"../utils":416,"./MSC3089Branch":376,"p-retry":225}],378:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Beacon = exports.getBeaconInfoIdentifier = exports.isTimestampInDuration = exports.BeaconEvent = void 0; +const content_helpers_1 = require("../content-helpers"); +const utils_1 = require("../utils"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +var BeaconEvent; +(function (BeaconEvent) { + BeaconEvent["New"] = "Beacon.new"; + BeaconEvent["Update"] = "Beacon.update"; + BeaconEvent["LivenessChange"] = "Beacon.LivenessChange"; + BeaconEvent["Destroy"] = "Beacon.Destroy"; + BeaconEvent["LocationUpdate"] = "Beacon.LocationUpdate"; +})(BeaconEvent = exports.BeaconEvent || (exports.BeaconEvent = {})); +const isTimestampInDuration = (startTimestamp, durationMs, timestamp) => timestamp >= startTimestamp && startTimestamp + durationMs >= timestamp; +exports.isTimestampInDuration = isTimestampInDuration; +const getBeaconInfoIdentifier = (event) => `${event.getRoomId()}_${event.getStateKey()}`; +exports.getBeaconInfoIdentifier = getBeaconInfoIdentifier; +// https://github.com/matrix-org/matrix-spec-proposals/pull/3672 +class Beacon extends typed_event_emitter_1.TypedEventEmitter { + constructor(rootEvent) { + super(); + this.rootEvent = rootEvent; + this.clearLatestLocation = () => { + this._latestLocationEvent = undefined; + this.emit(BeaconEvent.LocationUpdate, this.latestLocationState); + }; + this.roomId = this.rootEvent.getRoomId(); + this.setBeaconInfo(this.rootEvent); + } + get isLive() { + return !!this._isLive; + } + get identifier() { + return (0, exports.getBeaconInfoIdentifier)(this.rootEvent); + } + get beaconInfoId() { + return this.rootEvent.getId(); + } + get beaconInfoOwner() { + return this.rootEvent.getStateKey(); + } + get beaconInfoEventType() { + return this.rootEvent.getType(); + } + get beaconInfo() { + return this._beaconInfo; + } + get latestLocationState() { + return this._latestLocationEvent && (0, content_helpers_1.parseBeaconContent)(this._latestLocationEvent.getContent()); + } + get latestLocationEvent() { + return this._latestLocationEvent; + } + update(beaconInfoEvent) { + if ((0, exports.getBeaconInfoIdentifier)(beaconInfoEvent) !== this.identifier) { + throw new Error("Invalid updating event"); + } + // don't update beacon with an older event + if (beaconInfoEvent.getTs() < this.rootEvent.getTs()) { + return; + } + this.rootEvent = beaconInfoEvent; + this.setBeaconInfo(this.rootEvent); + this.emit(BeaconEvent.Update, beaconInfoEvent, this); + this.clearLatestLocation(); + } + destroy() { + if (this.livenessWatchTimeout) { + clearTimeout(this.livenessWatchTimeout); + } + this._isLive = false; + this.emit(BeaconEvent.Destroy, this.identifier); + } + /** + * Monitor liveness of a beacon + * Emits BeaconEvent.LivenessChange when beacon expires + */ + monitorLiveness() { + if (this.livenessWatchTimeout) { + clearTimeout(this.livenessWatchTimeout); + } + this.checkLiveness(); + if (!this.beaconInfo) + return; + if (this.isLive) { + const expiryInMs = this.beaconInfo.timestamp + this.beaconInfo.timeout - Date.now(); + if (expiryInMs > 1) { + this.livenessWatchTimeout = setTimeout(() => { + this.monitorLiveness(); + }, expiryInMs); + } + } + else if (this.beaconInfo.timestamp > Date.now()) { + // beacon start timestamp is in the future + // check liveness again then + this.livenessWatchTimeout = setTimeout(() => { + this.monitorLiveness(); + }, this.beaconInfo.timestamp - Date.now()); + } + } + /** + * Process Beacon locations + * Emits BeaconEvent.LocationUpdate + */ + addLocations(beaconLocationEvents) { + var _a; + // discard locations for beacons that are not live + if (!this.isLive) { + return; + } + const validLocationEvents = beaconLocationEvents.filter((event) => { + const content = event.getContent(); + const parsed = (0, content_helpers_1.parseBeaconContent)(content); + if (!parsed.uri || !parsed.timestamp) + return false; // we won't be able to process these + const { timestamp } = parsed; + return (this._beaconInfo.timestamp && + // only include positions that were taken inside the beacon's live period + (0, exports.isTimestampInDuration)(this._beaconInfo.timestamp, this._beaconInfo.timeout, timestamp) && + // ignore positions older than our current latest location + (!this.latestLocationState || timestamp > this.latestLocationState.timestamp)); + }); + const latestLocationEvent = (_a = validLocationEvents.sort(utils_1.sortEventsByLatestContentTimestamp)) === null || _a === void 0 ? void 0 : _a[0]; + if (latestLocationEvent) { + this._latestLocationEvent = latestLocationEvent; + this.emit(BeaconEvent.LocationUpdate, this.latestLocationState); + } + } + setBeaconInfo(event) { + this._beaconInfo = (0, content_helpers_1.parseBeaconInfoContent)(event.getContent()); + this.checkLiveness(); + } + checkLiveness() { + const prevLiveness = this.isLive; + // element web sets a beacon's start timestamp to the senders local current time + // when Alice's system clock deviates slightly from Bob's a beacon Alice intended to be live + // may have a start timestamp in the future from Bob's POV + // handle this by adding 6min of leniency to the start timestamp when it is in the future + if (!this.beaconInfo) + return; + const startTimestamp = this.beaconInfo.timestamp > Date.now() + ? this.beaconInfo.timestamp - 360000 /* 6min */ + : this.beaconInfo.timestamp; + this._isLive = + !!this._beaconInfo.live && + !!startTimestamp && + (0, exports.isTimestampInDuration)(startTimestamp, this._beaconInfo.timeout, Date.now()); + if (prevLiveness !== this.isLive) { + this.emit(BeaconEvent.LivenessChange, this.isLive, this); + } + } +} +exports.Beacon = Beacon; + +},{"../content-helpers":322,"../utils":416,"./typed-event-emitter":395}],379:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EventContext = void 0; +const event_timeline_1 = require("./event-timeline"); +class EventContext { + /** + * Construct a new EventContext + * + * An eventcontext is used for circumstances such as search results, when we + * have a particular event of interest, and a bunch of events before and after + * it. + * + * It also stores pagination tokens for going backwards and forwards in the + * timeline. + * + * @param ourEvent - the event at the centre of this context + */ + constructor(ourEvent) { + this.ourEvent = ourEvent; + this.ourEventIndex = 0; + this.paginateTokens = { + [event_timeline_1.Direction.Backward]: null, + [event_timeline_1.Direction.Forward]: null, + }; + this.timeline = [ourEvent]; + } + /** + * Get the main event of interest + * + * This is a convenience function for getTimeline()[getOurEventIndex()]. + * + * @returns The event at the centre of this context. + */ + getEvent() { + return this.timeline[this.ourEventIndex]; + } + /** + * Get the list of events in this context + * + * @returns An array of MatrixEvents + */ + getTimeline() { + return this.timeline; + } + /** + * Get the index in the timeline of our event + */ + getOurEventIndex() { + return this.ourEventIndex; + } + /** + * Get a pagination token. + * + * @param backwards - true to get the pagination token for going + */ + getPaginateToken(backwards = false) { + return this.paginateTokens[backwards ? event_timeline_1.Direction.Backward : event_timeline_1.Direction.Forward]; + } + /** + * Set a pagination token. + * + * Generally this will be used only by the matrix js sdk. + * + * @param token - pagination token + * @param backwards - true to set the pagination token for going + * backwards in time + */ + setPaginateToken(token, backwards = false) { + this.paginateTokens[backwards ? event_timeline_1.Direction.Backward : event_timeline_1.Direction.Forward] = token !== null && token !== void 0 ? token : null; + } + /** + * Add more events to the timeline + * + * @param events - new events, in timeline order + * @param atStart - true to insert new events at the start + */ + addEvents(events, atStart = false) { + // TODO: should we share logic with Room.addEventsToTimeline? + // Should Room even use EventContext? + if (atStart) { + this.timeline = events.concat(this.timeline); + this.ourEventIndex += events.length; + } + else { + this.timeline = this.timeline.concat(events); + } + } +} +exports.EventContext = EventContext; + +},{"./event-timeline":382}],380:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EventStatus = void 0; +/** + * Enum for event statuses. + * @readonly + */ +var EventStatus; +(function (EventStatus) { + /** The event was not sent and will no longer be retried. */ + EventStatus["NOT_SENT"] = "not_sent"; + /** The message is being encrypted */ + EventStatus["ENCRYPTING"] = "encrypting"; + /** The event is in the process of being sent. */ + EventStatus["SENDING"] = "sending"; + /** The event is in a queue waiting to be sent. */ + EventStatus["QUEUED"] = "queued"; + /** The event has been sent to the server, but we have not yet received the echo. */ + EventStatus["SENT"] = "sent"; + /** The event was cancelled before it was successfully sent. */ + EventStatus["CANCELLED"] = "cancelled"; +})(EventStatus = exports.EventStatus || (exports.EventStatus = {})); + +},{}],381:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EventTimelineSet = exports.DuplicateStrategy = void 0; +const event_timeline_1 = require("./event-timeline"); +const logger_1 = require("../logger"); +const room_1 = require("./room"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const relations_container_1 = require("./relations-container"); +const DEBUG = true; +/* istanbul ignore next */ +let debuglog; +if (DEBUG) { + // using bind means that we get to keep useful line numbers in the console + debuglog = logger_1.logger.log.bind(logger_1.logger); +} +else { + /* istanbul ignore next */ + debuglog = function () { }; +} +var DuplicateStrategy; +(function (DuplicateStrategy) { + DuplicateStrategy["Ignore"] = "ignore"; + DuplicateStrategy["Replace"] = "replace"; +})(DuplicateStrategy = exports.DuplicateStrategy || (exports.DuplicateStrategy = {})); +class EventTimelineSet extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct a set of EventTimeline objects, typically on behalf of a given + * room. A room may have multiple EventTimelineSets for different levels + * of filtering. The global notification list is also an EventTimelineSet, but + * lacks a room. + * + *

This is an ordered sequence of timelines, which may or may not + * be continuous. Each timeline lists a series of events, as well as tracking + * the room state at the start and the end of the timeline (if appropriate). + * It also tracks forward and backward pagination tokens, as well as containing + * links to the next timeline in the sequence. + * + *

There is one special timeline - the 'live' timeline, which represents the + * timeline to which events are being added in real-time as they are received + * from the /sync API. Note that you should not retain references to this + * timeline - even if it is the current timeline right now, it may not remain + * so if the server gives us a timeline gap in /sync. + * + *

In order that we can find events from their ids later, we also maintain a + * map from event_id to timeline and index. + * + * @param room - Room for this timelineSet. May be null for non-room cases, such as the + * notification timeline. + * @param opts - Options inherited from Room. + * @param client - the Matrix client which owns this EventTimelineSet, + * can be omitted if room is specified. + * @param thread - the thread to which this timeline set relates. + * @param isThreadTimeline - Whether this timeline set relates to a thread list timeline + * (e.g., All threads or My threads) + */ + constructor(room, opts = {}, client, thread, threadListType = null) { + var _a, _b, _c; + super(); + this.room = room; + this.thread = thread; + this.threadListType = threadListType; + this._eventIdToTimeline = new Map(); + this.timelineSupport = Boolean(opts.timelineSupport); + this.liveTimeline = new event_timeline_1.EventTimeline(this); + this.displayPendingEvents = opts.pendingEvents !== false; + // just a list - *not* ordered. + this.timelines = [this.liveTimeline]; + this._eventIdToTimeline = new Map(); + this.filter = opts.filter; + this.relations = (_b = (_a = this.room) === null || _a === void 0 ? void 0 : _a.relations) !== null && _b !== void 0 ? _b : new relations_container_1.RelationsContainer((_c = room === null || room === void 0 ? void 0 : room.client) !== null && _c !== void 0 ? _c : client); + } + /** + * Get all the timelines in this set + * @returns the timelines in this set + */ + getTimelines() { + return this.timelines; + } + /** + * Get the filter object this timeline set is filtered on, if any + * @returns the optional filter for this timelineSet + */ + getFilter() { + return this.filter; + } + /** + * Set the filter object this timeline set is filtered on + * (passed to the server when paginating via /messages). + * @param filter - the filter for this timelineSet + */ + setFilter(filter) { + this.filter = filter; + } + /** + * Get the list of pending sent events for this timelineSet's room, filtered + * by the timelineSet's filter if appropriate. + * + * @returns A list of the sent events + * waiting for remote echo. + * + * @throws If `opts.pendingEventOrdering` was not 'detached' + */ + getPendingEvents() { + if (!this.room || !this.displayPendingEvents) { + return []; + } + return this.room.getPendingEvents(); + } + /** + * Get the live timeline for this room. + * + * @returns live timeline + */ + getLiveTimeline() { + return this.liveTimeline; + } + /** + * Set the live timeline for this room. + * + * @returns live timeline + */ + setLiveTimeline(timeline) { + this.liveTimeline = timeline; + } + /** + * Return the timeline (if any) this event is in. + * @param eventId - the eventId being sought + * @returns timeline + */ + eventIdToTimeline(eventId) { + return this._eventIdToTimeline.get(eventId); + } + /** + * Track a new event as if it were in the same timeline as an old event, + * replacing it. + * @param oldEventId - event ID of the original event + * @param newEventId - event ID of the replacement event + */ + replaceEventId(oldEventId, newEventId) { + const existingTimeline = this._eventIdToTimeline.get(oldEventId); + if (existingTimeline) { + this._eventIdToTimeline.delete(oldEventId); + this._eventIdToTimeline.set(newEventId, existingTimeline); + } + } + /** + * Reset the live timeline, and start a new one. + * + *

This is used when /sync returns a 'limited' timeline. + * + * @param backPaginationToken - token for back-paginating the new timeline + * @param forwardPaginationToken - token for forward-paginating the old live timeline, + * if absent or null, all timelines are reset. + * + * @remarks + * Fires {@link RoomEvent.TimelineReset} + */ + resetLiveTimeline(backPaginationToken, forwardPaginationToken) { + // Each EventTimeline has RoomState objects tracking the state at the start + // and end of that timeline. The copies at the end of the live timeline are + // special because they will have listeners attached to monitor changes to + // the current room state, so we move this RoomState from the end of the + // current live timeline to the end of the new one and, if necessary, + // replace it with a newly created one. We also make a copy for the start + // of the new timeline. + // if timeline support is disabled, forget about the old timelines + const resetAllTimelines = !this.timelineSupport || !forwardPaginationToken; + const oldTimeline = this.liveTimeline; + const newTimeline = resetAllTimelines + ? oldTimeline.forkLive(event_timeline_1.EventTimeline.FORWARDS) + : oldTimeline.fork(event_timeline_1.EventTimeline.FORWARDS); + if (resetAllTimelines) { + this.timelines = [newTimeline]; + this._eventIdToTimeline = new Map(); + } + else { + this.timelines.push(newTimeline); + } + if (forwardPaginationToken) { + // Now set the forward pagination token on the old live timeline + // so it can be forward-paginated. + oldTimeline.setPaginationToken(forwardPaginationToken, event_timeline_1.EventTimeline.FORWARDS); + } + // make sure we set the pagination token before firing timelineReset, + // otherwise clients which start back-paginating will fail, and then get + // stuck without realising that they *can* back-paginate. + newTimeline.setPaginationToken(backPaginationToken !== null && backPaginationToken !== void 0 ? backPaginationToken : null, event_timeline_1.EventTimeline.BACKWARDS); + // Now we can swap the live timeline to the new one. + this.liveTimeline = newTimeline; + this.emit(room_1.RoomEvent.TimelineReset, this.room, this, resetAllTimelines); + } + /** + * Get the timeline which contains the given event, if any + * + * @param eventId - event ID to look for + * @returns timeline containing + * the given event, or null if unknown + */ + getTimelineForEvent(eventId) { + if (eventId === null || eventId === undefined) { + return null; + } + const res = this._eventIdToTimeline.get(eventId); + return res === undefined ? null : res; + } + /** + * Get an event which is stored in our timelines + * + * @param eventId - event ID to look for + * @returns the given event, or undefined if unknown + */ + findEventById(eventId) { + const tl = this.getTimelineForEvent(eventId); + if (!tl) { + return undefined; + } + return tl.getEvents().find(function (ev) { + return ev.getId() == eventId; + }); + } + /** + * Add a new timeline to this timeline list + * + * @returns newly-created timeline + */ + addTimeline() { + if (!this.timelineSupport) { + throw new Error("timeline support is disabled. Set the 'timelineSupport'" + + " parameter to true when creating MatrixClient to enable" + + " it."); + } + const timeline = new event_timeline_1.EventTimeline(this); + this.timelines.push(timeline); + return timeline; + } + /** + * Add events to a timeline + * + *

Will fire "Room.timeline" for each event added. + * + * @param events - A list of events to add. + * + * @param toStartOfTimeline - True to add these events to the start + * (oldest) instead of the end (newest) of the timeline. If true, the oldest + * event will be the last element of 'events'. + * + * @param timeline - timeline to + * add events to. + * + * @param paginationToken - token for the next batch of events + * + * @remarks + * Fires {@link RoomEvent.Timeline} + * + */ + addEventsToTimeline(events, toStartOfTimeline, timeline, paginationToken) { + if (!timeline) { + throw new Error("'timeline' not specified for EventTimelineSet.addEventsToTimeline"); + } + if (!toStartOfTimeline && timeline == this.liveTimeline) { + throw new Error("EventTimelineSet.addEventsToTimeline cannot be used for adding events to " + + "the live timeline - use Room.addLiveEvents instead"); + } + if (this.filter) { + events = this.filter.filterRoomTimeline(events); + if (!events.length) { + return; + } + } + const direction = toStartOfTimeline ? event_timeline_1.EventTimeline.BACKWARDS : event_timeline_1.EventTimeline.FORWARDS; + const inverseDirection = toStartOfTimeline ? event_timeline_1.EventTimeline.FORWARDS : event_timeline_1.EventTimeline.BACKWARDS; + // Adding events to timelines can be quite complicated. The following + // illustrates some of the corner-cases. + // + // Let's say we start by knowing about four timelines. timeline3 and + // timeline4 are neighbours: + // + // timeline1 timeline2 timeline3 timeline4 + // [M] [P] [S] <------> [T] + // + // Now we paginate timeline1, and get the following events from the server: + // [M, N, P, R, S, T, U]. + // + // 1. First, we ignore event M, since we already know about it. + // + // 2. Next, we append N to timeline 1. + // + // 3. Next, we don't add event P, since we already know about it, + // but we do link together the timelines. We now have: + // + // timeline1 timeline2 timeline3 timeline4 + // [M, N] <---> [P] [S] <------> [T] + // + // 4. Now we add event R to timeline2: + // + // timeline1 timeline2 timeline3 timeline4 + // [M, N] <---> [P, R] [S] <------> [T] + // + // Note that we have switched the timeline we are working on from + // timeline1 to timeline2. + // + // 5. We ignore event S, but again join the timelines: + // + // timeline1 timeline2 timeline3 timeline4 + // [M, N] <---> [P, R] <---> [S] <------> [T] + // + // 6. We ignore event T, and the timelines are already joined, so there + // is nothing to do. + // + // 7. Finally, we add event U to timeline4: + // + // timeline1 timeline2 timeline3 timeline4 + // [M, N] <---> [P, R] <---> [S] <------> [T, U] + // + // The important thing to note in the above is what happened when we + // already knew about a given event: + // + // - if it was appropriate, we joined up the timelines (steps 3, 5). + // - in any case, we started adding further events to the timeline which + // contained the event we knew about (steps 3, 5, 6). + // + // + // So much for adding events to the timeline. But what do we want to do + // with the pagination token? + // + // In the case above, we will be given a pagination token which tells us how to + // get events beyond 'U' - in this case, it makes sense to store this + // against timeline4. But what if timeline4 already had 'U' and beyond? in + // that case, our best bet is to throw away the pagination token we were + // given and stick with whatever token timeline4 had previously. In short, + // we want to only store the pagination token if the last event we receive + // is one we didn't previously know about. + // + // We make an exception for this if it turns out that we already knew about + // *all* of the events, and we weren't able to join up any timelines. When + // that happens, it means our existing pagination token is faulty, since it + // is only telling us what we already know. Rather than repeatedly + // paginating with the same token, we might as well use the new pagination + // token in the hope that we eventually work our way out of the mess. + let didUpdate = false; + let lastEventWasNew = false; + for (const event of events) { + const eventId = event.getId(); + const existingTimeline = this._eventIdToTimeline.get(eventId); + if (!existingTimeline) { + // we don't know about this event yet. Just add it to the timeline. + this.addEventToTimeline(event, timeline, { + toStartOfTimeline, + }); + lastEventWasNew = true; + didUpdate = true; + continue; + } + lastEventWasNew = false; + if (existingTimeline == timeline) { + debuglog("Event " + eventId + " already in timeline " + timeline); + continue; + } + const neighbour = timeline.getNeighbouringTimeline(direction); + if (neighbour) { + // this timeline already has a neighbour in the relevant direction; + // let's assume the timelines are already correctly linked up, and + // skip over to it. + // + // there's probably some edge-case here where we end up with an + // event which is in a timeline a way down the chain, and there is + // a break in the chain somewhere. But I can't really imagine how + // that would happen, so I'm going to ignore it for now. + // + if (existingTimeline == neighbour) { + debuglog("Event " + eventId + " in neighbouring timeline - " + "switching to " + existingTimeline); + } + else { + debuglog("Event " + eventId + " already in a different " + "timeline " + existingTimeline); + } + timeline = existingTimeline; + continue; + } + // time to join the timelines. + logger_1.logger.info("Already have timeline for " + eventId + " - joining timeline " + timeline + " to " + existingTimeline); + // Variables to keep the line length limited below. + const existingIsLive = existingTimeline === this.liveTimeline; + const timelineIsLive = timeline === this.liveTimeline; + const backwardsIsLive = direction === event_timeline_1.EventTimeline.BACKWARDS && existingIsLive; + const forwardsIsLive = direction === event_timeline_1.EventTimeline.FORWARDS && timelineIsLive; + if (backwardsIsLive || forwardsIsLive) { + // The live timeline should never be spliced into a non-live position. + // We use independent logging to better discover the problem at a glance. + if (backwardsIsLive) { + logger_1.logger.warn("Refusing to set a preceding existingTimeLine on our " + + "timeline as the existingTimeLine is live (" + + existingTimeline + + ")"); + } + if (forwardsIsLive) { + logger_1.logger.warn("Refusing to set our preceding timeline on a existingTimeLine " + + "as our timeline is live (" + + timeline + + ")"); + } + continue; // abort splicing - try next event + } + timeline.setNeighbouringTimeline(existingTimeline, direction); + existingTimeline.setNeighbouringTimeline(timeline, inverseDirection); + timeline = existingTimeline; + didUpdate = true; + } + // see above - if the last event was new to us, or if we didn't find any + // new information, we update the pagination token for whatever + // timeline we ended up on. + if (lastEventWasNew || !didUpdate) { + if (direction === event_timeline_1.EventTimeline.FORWARDS && timeline === this.liveTimeline) { + logger_1.logger.warn({ lastEventWasNew, didUpdate }); // for debugging + logger_1.logger.warn(`Refusing to set forwards pagination token of live timeline ` + `${timeline} to ${paginationToken}`); + return; + } + timeline.setPaginationToken(paginationToken !== null && paginationToken !== void 0 ? paginationToken : null, direction); + } + } + addLiveEvent(event, duplicateStrategyOrOpts, fromCache = false, roomState) { + let duplicateStrategy = duplicateStrategyOrOpts || DuplicateStrategy.Ignore; + let timelineWasEmpty; + if (typeof duplicateStrategyOrOpts === "object") { + ({ + duplicateStrategy = DuplicateStrategy.Ignore, + fromCache = false, + roomState, + timelineWasEmpty, + } = duplicateStrategyOrOpts); + } + else if (duplicateStrategyOrOpts !== undefined) { + // Deprecation warning + // FIXME: Remove after 2023-06-01 (technical debt) + logger_1.logger.warn("Overload deprecated: " + + "`EventTimelineSet.addLiveEvent(event, duplicateStrategy?, fromCache?, roomState?)` " + + "is deprecated in favor of the overload with " + + "`EventTimelineSet.addLiveEvent(event, IAddLiveEventOptions)`"); + } + if (this.filter) { + const events = this.filter.filterRoomTimeline([event]); + if (!events.length) { + return; + } + } + const timeline = this._eventIdToTimeline.get(event.getId()); + if (timeline) { + if (duplicateStrategy === DuplicateStrategy.Replace) { + debuglog("EventTimelineSet.addLiveEvent: replacing duplicate event " + event.getId()); + const tlEvents = timeline.getEvents(); + for (let j = 0; j < tlEvents.length; j++) { + if (tlEvents[j].getId() === event.getId()) { + // still need to set the right metadata on this event + if (!roomState) { + roomState = timeline.getState(event_timeline_1.EventTimeline.FORWARDS); + } + event_timeline_1.EventTimeline.setEventMetadata(event, roomState, false); + tlEvents[j] = event; + // XXX: we need to fire an event when this happens. + break; + } + } + } + else { + debuglog("EventTimelineSet.addLiveEvent: ignoring duplicate event " + event.getId()); + } + return; + } + this.addEventToTimeline(event, this.liveTimeline, { + toStartOfTimeline: false, + fromCache, + roomState, + timelineWasEmpty, + }); + } + addEventToTimeline(event, timeline, toStartOfTimelineOrOpts, fromCache = false, roomState) { + var _a, _b; + let toStartOfTimeline = !!toStartOfTimelineOrOpts; + let timelineWasEmpty; + if (typeof toStartOfTimelineOrOpts === "object") { + ({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts); + } + else if (toStartOfTimelineOrOpts !== undefined) { + // Deprecation warning + // FIXME: Remove after 2023-06-01 (technical debt) + logger_1.logger.warn("Overload deprecated: " + + "`EventTimelineSet.addEventToTimeline(event, timeline, toStartOfTimeline, fromCache?, roomState?)` " + + "is deprecated in favor of the overload with " + + "`EventTimelineSet.addEventToTimeline(event, timeline, IAddEventToTimelineOptions)`"); + } + if (timeline.getTimelineSet() !== this) { + throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " + + "in timelineSet(threadId=${(_a = this.thread) === null || _a === void 0 ? void 0 : _a.id})`); + } + // Make sure events don't get mixed in timelines they shouldn't be in (e.g. a + // threaded message should not be in the main timeline). + // + // We can only run this check for timelines with a `room` because `canContain` + // requires it + if (this.room && !this.canContain(event)) { + let eventDebugString = `event=${event.getId()}`; + if (event.threadRootId) { + eventDebugString += `(belongs to thread=${event.threadRootId})`; + } + logger_1.logger.warn(`EventTimelineSet.addEventToTimeline: Ignoring ${eventDebugString} that does not belong ` + + `in timeline=${timeline.toString()} timelineSet(threadId=${(_b = this.thread) === null || _b === void 0 ? void 0 : _b.id})`); + return; + } + const eventId = event.getId(); + timeline.addEvent(event, { + toStartOfTimeline, + roomState, + timelineWasEmpty, + }); + this._eventIdToTimeline.set(eventId, timeline); + this.relations.aggregateParentEvent(event); + this.relations.aggregateChildEvent(event, this); + const data = { + timeline: timeline, + liveEvent: !toStartOfTimeline && timeline == this.liveTimeline && !fromCache, + }; + this.emit(room_1.RoomEvent.Timeline, event, this.room, Boolean(toStartOfTimeline), false, data); + } + /** + * Replaces event with ID oldEventId with one with newEventId, if oldEventId is + * recognised. Otherwise, add to the live timeline. Used to handle remote echos. + * + * @param localEvent - the new event to be added to the timeline + * @param oldEventId - the ID of the original event + * @param newEventId - the ID of the replacement event + * + * @remarks + * Fires {@link RoomEvent.Timeline} + */ + handleRemoteEcho(localEvent, oldEventId, newEventId) { + // XXX: why don't we infer newEventId from localEvent? + const existingTimeline = this._eventIdToTimeline.get(oldEventId); + if (existingTimeline) { + this._eventIdToTimeline.delete(oldEventId); + this._eventIdToTimeline.set(newEventId, existingTimeline); + } + else if (!this.filter || this.filter.filterRoomTimeline([localEvent]).length) { + this.addEventToTimeline(localEvent, this.liveTimeline, { + toStartOfTimeline: false, + }); + } + } + /** + * Removes a single event from this room. + * + * @param eventId - The id of the event to remove + * + * @returns the removed event, or null if the event was not found + * in this room. + */ + removeEvent(eventId) { + const timeline = this._eventIdToTimeline.get(eventId); + if (!timeline) { + return null; + } + const removed = timeline.removeEvent(eventId); + if (removed) { + this._eventIdToTimeline.delete(eventId); + const data = { + timeline: timeline, + }; + this.emit(room_1.RoomEvent.Timeline, removed, this.room, undefined, true, data); + } + return removed; + } + /** + * Determine where two events appear in the timeline relative to one another + * + * @param eventId1 - The id of the first event + * @param eventId2 - The id of the second event + + * @returns a number less than zero if eventId1 precedes eventId2, and + * greater than zero if eventId1 succeeds eventId2. zero if they are the + * same event; null if we can't tell (either because we don't know about one + * of the events, or because they are in separate timelines which don't join + * up). + */ + compareEventOrdering(eventId1, eventId2) { + if (eventId1 == eventId2) { + // optimise this case + return 0; + } + const timeline1 = this._eventIdToTimeline.get(eventId1); + const timeline2 = this._eventIdToTimeline.get(eventId2); + if (timeline1 === undefined) { + return null; + } + if (timeline2 === undefined) { + return null; + } + if (timeline1 === timeline2) { + // both events are in the same timeline - figure out their relative indices + let idx1 = undefined; + let idx2 = undefined; + const events = timeline1.getEvents(); + for (let idx = 0; idx < events.length && (idx1 === undefined || idx2 === undefined); idx++) { + const evId = events[idx].getId(); + if (evId == eventId1) { + idx1 = idx; + } + if (evId == eventId2) { + idx2 = idx; + } + } + return idx1 - idx2; + } + // the events are in different timelines. Iterate through the + // linkedlist to see which comes first. + // first work forwards from timeline1 + let tl = timeline1; + while (tl) { + if (tl === timeline2) { + // timeline1 is before timeline2 + return -1; + } + tl = tl.getNeighbouringTimeline(event_timeline_1.EventTimeline.FORWARDS); + } + // now try backwards from timeline1 + tl = timeline1; + while (tl) { + if (tl === timeline2) { + // timeline2 is before timeline1 + return 1; + } + tl = tl.getNeighbouringTimeline(event_timeline_1.EventTimeline.BACKWARDS); + } + // the timelines are not contiguous. + return null; + } + /** + * Determine whether a given event can sanely be added to this event timeline set, + * for timeline sets relating to a thread, only return true for events in the same + * thread timeline, for timeline sets not relating to a thread only return true + * for events which should be shown in the main room timeline. + * Requires the `room` property to have been set at EventTimelineSet construction time. + * + * @param event - the event to check whether it belongs to this timeline set. + * @throws Error if `room` was not set when constructing this timeline set. + * @returns whether the event belongs to this timeline set. + */ + canContain(event) { + if (!this.room) { + throw new Error("Cannot call `EventTimelineSet::canContain without a `room` set. " + + "Set the room when creating the EventTimelineSet to call this method."); + } + const { threadId, shouldLiveInRoom } = this.room.eventShouldLiveIn(event); + if (this.thread) { + return this.thread.id === threadId; + } + return shouldLiveInRoom; + } +} +exports.EventTimelineSet = EventTimelineSet; + +},{"../logger":374,"./event-timeline":382,"./relations-container":387,"./room":392,"./typed-event-emitter":395}],382:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EventTimeline = exports.Direction = void 0; +const logger_1 = require("../logger"); +const room_state_1 = require("./room-state"); +const event_1 = require("../@types/event"); +var Direction; +(function (Direction) { + Direction["Backward"] = "b"; + Direction["Forward"] = "f"; +})(Direction = exports.Direction || (exports.Direction = {})); +class EventTimeline { + /** + * Static helper method to set sender and target properties + * + * @param event - the event whose metadata is to be set + * @param stateContext - the room state to be queried + * @param toStartOfTimeline - if true the event's forwardLooking flag is set false + */ + static setEventMetadata(event, stateContext, toStartOfTimeline) { + var _a, _b, _c, _d; + // When we try to generate a sentinel member before we have that member + // in the members object, we still generate a sentinel but it doesn't + // have a membership event, so test to see if events.member is set. We + // check this to avoid overriding non-sentinel members by sentinel ones + // when adding the event to a filtered timeline + if (!((_b = (_a = event.sender) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.member)) { + event.sender = stateContext.getSentinelMember(event.getSender()); + } + if (!((_d = (_c = event.target) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.member) && event.getType() === event_1.EventType.RoomMember) { + event.target = stateContext.getSentinelMember(event.getStateKey()); + } + if (event.isState()) { + // room state has no concept of 'old' or 'current', but we want the + // room state to regress back to previous values if toStartOfTimeline + // is set, which means inspecting prev_content if it exists. This + // is done by toggling the forwardLooking flag. + if (toStartOfTimeline) { + event.forwardLooking = false; + } + } + } + /** + * Construct a new EventTimeline + * + *

An EventTimeline represents a contiguous sequence of events in a room. + * + *

As well as keeping track of the events themselves, it stores the state of + * the room at the beginning and end of the timeline, and pagination tokens for + * going backwards and forwards in the timeline. + * + *

In order that clients can meaningfully maintain an index into a timeline, + * the EventTimeline object tracks a 'baseIndex'. This starts at zero, but is + * incremented when events are prepended to the timeline. The index of an event + * relative to baseIndex therefore remains constant. + * + *

Once a timeline joins up with its neighbour, they are linked together into a + * doubly-linked list. + * + * @param eventTimelineSet - the set of timelines this is part of + */ + constructor(eventTimelineSet) { + var _a, _b; + this.eventTimelineSet = eventTimelineSet; + this.events = []; + this.baseIndex = 0; + // If we have a roomId then we delegate pagination token storage to the room state objects `startState` and + // `endState`, but for things like the notification timeline which mix multiple rooms we store the tokens ourselves. + this.startToken = null; + this.endToken = null; + this.prevTimeline = null; + this.nextTimeline = null; + this.paginationRequests = { + [Direction.Backward]: null, + [Direction.Forward]: null, + }; + this.roomId = (_b = (_a = eventTimelineSet.room) === null || _a === void 0 ? void 0 : _a.roomId) !== null && _b !== void 0 ? _b : null; + if (this.roomId) { + this.startState = new room_state_1.RoomState(this.roomId); + this.endState = new room_state_1.RoomState(this.roomId); + } + // this is used by client.js + this.paginationRequests = { b: null, f: null }; + this.name = this.roomId + ":" + new Date().toISOString(); + } + /** + * Initialise the start and end state with the given events + * + *

This can only be called before any events are added. + * + * @param stateEvents - list of state events to initialise the + * state with. + * @throws Error if an attempt is made to call this after addEvent is called. + */ + initialiseState(stateEvents, { timelineWasEmpty } = {}) { + var _a, _b; + if (this.events.length > 0) { + throw new Error("Cannot initialise state after events are added"); + } + (_a = this.startState) === null || _a === void 0 ? void 0 : _a.setStateEvents(stateEvents, { timelineWasEmpty }); + (_b = this.endState) === null || _b === void 0 ? void 0 : _b.setStateEvents(stateEvents, { timelineWasEmpty }); + } + /** + * Forks the (live) timeline, taking ownership of the existing directional state of this timeline. + * All attached listeners will keep receiving state updates from the new live timeline state. + * The end state of this timeline gets replaced with an independent copy of the current RoomState, + * and will need a new pagination token if it ever needs to paginate forwards. + + * @param direction - EventTimeline.BACKWARDS to get the state at the + * start of the timeline; EventTimeline.FORWARDS to get the state at the end + * of the timeline. + * + * @returns the new timeline + */ + forkLive(direction) { + const forkState = this.getState(direction); + const timeline = new EventTimeline(this.eventTimelineSet); + timeline.startState = forkState === null || forkState === void 0 ? void 0 : forkState.clone(); + // Now clobber the end state of the new live timeline with that from the + // previous live timeline. It will be identical except that we'll keep + // using the same RoomMember objects for the 'live' set of members with any + // listeners still attached + timeline.endState = forkState; + // Firstly, we just stole the current timeline's end state, so it needs a new one. + // Make an immutable copy of the state so back pagination will get the correct sentinels. + this.endState = forkState === null || forkState === void 0 ? void 0 : forkState.clone(); + return timeline; + } + /** + * Creates an independent timeline, inheriting the directional state from this timeline. + * + * @param direction - EventTimeline.BACKWARDS to get the state at the + * start of the timeline; EventTimeline.FORWARDS to get the state at the end + * of the timeline. + * + * @returns the new timeline + */ + fork(direction) { + const forkState = this.getState(direction); + const timeline = new EventTimeline(this.eventTimelineSet); + timeline.startState = forkState === null || forkState === void 0 ? void 0 : forkState.clone(); + timeline.endState = forkState === null || forkState === void 0 ? void 0 : forkState.clone(); + return timeline; + } + /** + * Get the ID of the room for this timeline + * @returns room ID + */ + getRoomId() { + return this.roomId; + } + /** + * Get the filter for this timeline's timelineSet (if any) + * @returns filter + */ + getFilter() { + return this.eventTimelineSet.getFilter(); + } + /** + * Get the timelineSet for this timeline + * @returns timelineSet + */ + getTimelineSet() { + return this.eventTimelineSet; + } + /** + * Get the base index. + * + *

This is an index which is incremented when events are prepended to the + * timeline. An individual event therefore stays at the same index in the array + * relative to the base index (although note that a given event's index may + * well be less than the base index, thus giving that event a negative relative + * index). + */ + getBaseIndex() { + return this.baseIndex; + } + /** + * Get the list of events in this context + * + * @returns An array of MatrixEvents + */ + getEvents() { + return this.events; + } + /** + * Get the room state at the start/end of the timeline + * + * @param direction - EventTimeline.BACKWARDS to get the state at the + * start of the timeline; EventTimeline.FORWARDS to get the state at the end + * of the timeline. + * + * @returns state at the start/end of the timeline + */ + getState(direction) { + if (direction == EventTimeline.BACKWARDS) { + return this.startState; + } + else if (direction == EventTimeline.FORWARDS) { + return this.endState; + } + else { + throw new Error("Invalid direction '" + direction + "'"); + } + } + /** + * Get a pagination token + * + * @param direction - EventTimeline.BACKWARDS to get the pagination + * token for going backwards in time; EventTimeline.FORWARDS to get the + * pagination token for going forwards in time. + * + * @returns pagination token + */ + getPaginationToken(direction) { + if (this.roomId) { + return this.getState(direction).paginationToken; + } + else if (direction === Direction.Backward) { + return this.startToken; + } + else { + return this.endToken; + } + } + /** + * Set a pagination token + * + * @param token - pagination token + * + * @param direction - EventTimeline.BACKWARDS to set the pagination + * token for going backwards in time; EventTimeline.FORWARDS to set the + * pagination token for going forwards in time. + */ + setPaginationToken(token, direction) { + if (this.roomId) { + this.getState(direction).paginationToken = token; + } + else if (direction === Direction.Backward) { + this.startToken = token; + } + else { + this.endToken = token; + } + } + /** + * Get the next timeline in the series + * + * @param direction - EventTimeline.BACKWARDS to get the previous + * timeline; EventTimeline.FORWARDS to get the next timeline. + * + * @returns previous or following timeline, if they have been + * joined up. + */ + getNeighbouringTimeline(direction) { + if (direction == EventTimeline.BACKWARDS) { + return this.prevTimeline; + } + else if (direction == EventTimeline.FORWARDS) { + return this.nextTimeline; + } + else { + throw new Error("Invalid direction '" + direction + "'"); + } + } + /** + * Set the next timeline in the series + * + * @param neighbour - previous/following timeline + * + * @param direction - EventTimeline.BACKWARDS to set the previous + * timeline; EventTimeline.FORWARDS to set the next timeline. + * + * @throws Error if an attempt is made to set the neighbouring timeline when + * it is already set. + */ + setNeighbouringTimeline(neighbour, direction) { + if (this.getNeighbouringTimeline(direction)) { + throw new Error("timeline already has a neighbouring timeline - " + + "cannot reset neighbour (direction: " + + direction + + ")"); + } + if (direction == EventTimeline.BACKWARDS) { + this.prevTimeline = neighbour; + } + else if (direction == EventTimeline.FORWARDS) { + this.nextTimeline = neighbour; + } + else { + throw new Error("Invalid direction '" + direction + "'"); + } + // make sure we don't try to paginate this timeline + this.setPaginationToken(null, direction); + } + addEvent(event, toStartOfTimelineOrOpts, roomState) { + let toStartOfTimeline = !!toStartOfTimelineOrOpts; + let timelineWasEmpty; + if (typeof toStartOfTimelineOrOpts === "object") { + ({ toStartOfTimeline, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts); + } + else if (toStartOfTimelineOrOpts !== undefined) { + // Deprecation warning + // FIXME: Remove after 2023-06-01 (technical debt) + logger_1.logger.warn("Overload deprecated: " + + "`EventTimeline.addEvent(event, toStartOfTimeline, roomState?)` " + + "is deprecated in favor of the overload with `EventTimeline.addEvent(event, IAddEventOptions)`"); + } + if (!roomState) { + roomState = toStartOfTimeline ? this.startState : this.endState; + } + const timelineSet = this.getTimelineSet(); + if (timelineSet.room) { + EventTimeline.setEventMetadata(event, roomState, toStartOfTimeline); + // modify state but only on unfiltered timelineSets + if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) { + roomState === null || roomState === void 0 ? void 0 : roomState.setStateEvents([event], { timelineWasEmpty }); + // it is possible that the act of setting the state event means we + // can set more metadata (specifically sender/target props), so try + // it again if the prop wasn't previously set. It may also mean that + // the sender/target is updated (if the event set was a room member event) + // so we want to use the *updated* member (new avatar/name) instead. + // + // However, we do NOT want to do this on member events if we're going + // back in time, else we'll set the .sender value for BEFORE the given + // member event, whereas we want to set the .sender value for the ACTUAL + // member event itself. + if (!event.sender || (event.getType() === event_1.EventType.RoomMember && !toStartOfTimeline)) { + EventTimeline.setEventMetadata(event, roomState, toStartOfTimeline); + } + } + } + let insertIndex; + if (toStartOfTimeline) { + insertIndex = 0; + } + else { + insertIndex = this.events.length; + } + this.events.splice(insertIndex, 0, event); // insert element + if (toStartOfTimeline) { + this.baseIndex++; + } + } + /** + * Remove an event from the timeline + * + * @param eventId - ID of event to be removed + * @returns removed event, or null if not found + */ + removeEvent(eventId) { + for (let i = this.events.length - 1; i >= 0; i--) { + const ev = this.events[i]; + if (ev.getId() == eventId) { + this.events.splice(i, 1); + if (i < this.baseIndex) { + this.baseIndex--; + } + return ev; + } + } + return null; + } + /** + * Return a string to identify this timeline, for debugging + * + * @returns name for this timeline + */ + toString() { + return this.name; + } +} +exports.EventTimeline = EventTimeline; +/** + * Symbolic constant for methods which take a 'direction' argument: + * refers to the start of the timeline, or backwards in time. + */ +EventTimeline.BACKWARDS = Direction.Backward; +/** + * Symbolic constant for methods which take a 'direction' argument: + * refers to the end of the timeline, or forwards in time. + */ +EventTimeline.FORWARDS = Direction.Forward; + +},{"../@types/event":306,"../logger":374,"./room-state":390}],383:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MatrixEvent = exports.MatrixEventEvent = exports.EventStatus = void 0; +/** + * This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for + * the public classes. + */ +const matrix_events_sdk_1 = require("matrix-events-sdk"); +const logger_1 = require("../logger"); +const event_1 = require("../@types/event"); +const utils_1 = require("../utils"); +const thread_1 = require("./thread"); +const ReEmitter_1 = require("../ReEmitter"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const algorithms_1 = require("../crypto/algorithms"); +const OlmDevice_1 = require("../crypto/OlmDevice"); +var event_status_1 = require("./event-status"); +Object.defineProperty(exports, "EventStatus", { enumerable: true, get: function () { return event_status_1.EventStatus; } }); +// A singleton implementing `IMessageVisibilityVisible`. +const MESSAGE_VISIBLE = Object.freeze({ visible: true }); +var MatrixEventEvent; +(function (MatrixEventEvent) { + MatrixEventEvent["Decrypted"] = "Event.decrypted"; + MatrixEventEvent["BeforeRedaction"] = "Event.beforeRedaction"; + MatrixEventEvent["VisibilityChange"] = "Event.visibilityChange"; + MatrixEventEvent["LocalEventIdReplaced"] = "Event.localEventIdReplaced"; + MatrixEventEvent["Status"] = "Event.status"; + MatrixEventEvent["Replaced"] = "Event.replaced"; + MatrixEventEvent["RelationsCreated"] = "Event.relationsCreated"; +})(MatrixEventEvent = exports.MatrixEventEvent || (exports.MatrixEventEvent = {})); +class MatrixEvent extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct a Matrix Event object + * + * @param event - The raw (possibly encrypted) event. Do not access + * this property directly unless you absolutely have to. Prefer the getter + * methods defined on this class. Using the getter methods shields your app + * from changes to event JSON between Matrix versions. + */ + constructor(event = {}) { + var _a; + super(); + this.event = event; + this.pushActions = null; + this._replacingEvent = null; + this._localRedactionEvent = null; + this._isCancelled = false; + /* Message hiding, as specified by https://github.com/matrix-org/matrix-doc/pull/3531. + + Note: We're returning this object, so any value stored here MUST be frozen. + */ + this.visibility = MESSAGE_VISIBLE; + // Not all events will be extensible-event compatible, so cache a flag in + // addition to a falsy cached event value. We check the flag later on in + // a public getter to decide if the cache is valid. + this._hasCachedExtEv = false; + this._cachedExtEv = undefined; + /* curve25519 key which we believe belongs to the sender of the event. See + * getSenderKey() + */ + this.senderCurve25519Key = null; + /* ed25519 key which the sender of this event (for olm) or the creator of + * the megolm session (for megolm) claims to own. See getClaimedEd25519Key() + */ + this.claimedEd25519Key = null; + /* curve25519 keys of devices involved in telling us about the + * senderCurve25519Key and claimedEd25519Key. + * See getForwardingCurve25519KeyChain(). + */ + this.forwardingCurve25519KeyChain = []; + /* where the decryption key is untrusted + */ + this.untrusted = null; + /* if we have a process decrypting this event, a Promise which resolves + * when it is finished. Normally null. + */ + this.decryptionPromise = null; + /* flag to indicate if we should retry decrypting this event after the + * first attempt (eg, we have received new data which means that a second + * attempt may succeed) + */ + this.retryDecryption = false; + /* + * True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and + * the sender has disabled encrypting to unverified devices. + */ + this.encryptedDisabledForUnverifiedDevices = false; + /** + * The room member who sent this event, or null e.g. + * this is a presence event. This is only guaranteed to be set for events that + * appear in a timeline, ie. do not guarantee that it will be set on state + * events. + * @privateRemarks + * Should be read-only + */ + this.sender = null; + /** + * The room member who is the target of this event, e.g. + * the invitee, the person being banned, etc. + * @privateRemarks + * Should be read-only + */ + this.target = null; + /** + * The sending status of the event. + * @privateRemarks + * Should be read-only + */ + this.status = null; + /** + * most recent error associated with sending the event, if any + * @privateRemarks + * Should be read-only + */ + this.error = null; + /** + * True if this event is 'forward looking', meaning + * that getDirectionalContent() will return event.content and not event.prev_content. + * Only state events may be backwards looking + * Default: true. This property is experimental and may change. + * @privateRemarks + * Should be read-only + */ + this.forwardLooking = true; + // intern the values of matrix events to force share strings and reduce the + // amount of needless string duplication. This can save moderate amounts of + // memory (~10% on a 350MB heap). + // 'membership' at the event level (rather than the content level) is a legacy + // field that Element never otherwise looks at, but it will still take up a lot + // of space if we don't intern it. + ["state_key", "type", "sender", "room_id", "membership"].forEach((prop) => { + if (typeof event[prop] !== "string") + return; + event[prop] = (0, utils_1.internaliseString)(event[prop]); + }); + ["membership", "avatar_url", "displayname"].forEach((prop) => { + var _a; + if (typeof ((_a = event.content) === null || _a === void 0 ? void 0 : _a[prop]) !== "string") + return; + event.content[prop] = (0, utils_1.internaliseString)(event.content[prop]); + }); + ["rel_type"].forEach((prop) => { + var _a, _b; + if (typeof ((_b = (_a = event.content) === null || _a === void 0 ? void 0 : _a["m.relates_to"]) === null || _b === void 0 ? void 0 : _b[prop]) !== "string") + return; + event.content["m.relates_to"][prop] = (0, utils_1.internaliseString)(event.content["m.relates_to"][prop]); + }); + this.txnId = event.txn_id; + this.localTimestamp = Date.now() - ((_a = this.getAge()) !== null && _a !== void 0 ? _a : 0); + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + } + /** + * Unstable getter to try and get an extensible event. Note that this might + * return a falsy value if the event could not be parsed as an extensible + * event. + * + * @deprecated Use stable functions where possible. + */ + get unstableExtensibleEvent() { + if (!this._hasCachedExtEv) { + this._cachedExtEv = matrix_events_sdk_1.ExtensibleEvents.parse(this.getEffectiveEvent()); + } + return this._cachedExtEv; + } + invalidateExtensibleEvent() { + // just reset the flag - that'll trick the getter into parsing a new event + this._hasCachedExtEv = false; + } + /** + * Gets the event as though it would appear unencrypted. If the event is already not + * encrypted, it is simply returned as-is. + * @returns The event in wire format. + */ + getEffectiveEvent() { + const content = Object.assign({}, this.getContent()); // clone for mutation + if (this.getWireType() === event_1.EventType.RoomMessageEncrypted) { + // Encrypted events sometimes aren't symmetrical on the `content` so we'll copy + // that over too, but only for missing properties. We don't copy over mismatches + // between the plain and decrypted copies of `content` because we assume that the + // app is relying on the decrypted version, so we want to expose that as a source + // of truth here too. + for (const [key, value] of Object.entries(this.getWireContent())) { + // Skip fields from the encrypted event schema though - we don't want to leak + // these. + if (["algorithm", "ciphertext", "device_id", "sender_key", "session_id"].includes(key)) { + continue; + } + if (content[key] === undefined) + content[key] = value; + } + } + // clearEvent doesn't have all the fields, so we'll copy what we can from this.event. + // We also copy over our "fixed" content key. + return Object.assign({}, this.event, this.clearEvent, { content }); + } + /** + * Get the event_id for this event. + * @returns The event ID, e.g. $143350589368169JsLZx:localhost + * + */ + getId() { + return this.event.event_id; + } + /** + * Get the user_id for this event. + * @returns The user ID, e.g. `@alice:matrix.org` + */ + getSender() { + return this.event.sender || this.event.user_id; // v2 / v1 + } + /** + * Get the (decrypted, if necessary) type of event. + * + * @returns The event type, e.g. `m.room.message` + */ + getType() { + if (this.clearEvent) { + return this.clearEvent.type; + } + return this.event.type; + } + /** + * Get the (possibly encrypted) type of the event that will be sent to the + * homeserver. + * + * @returns The event type. + */ + getWireType() { + return this.event.type; + } + /** + * Get the room_id for this event. This will return `undefined` + * for `m.presence` events. + * @returns The room ID, e.g. !cURbafjkfsMDVwdRDQ:matrix.org + * + */ + getRoomId() { + return this.event.room_id; + } + /** + * Get the timestamp of this event. + * @returns The event timestamp, e.g. `1433502692297` + */ + getTs() { + return this.event.origin_server_ts; + } + /** + * Get the timestamp of this event, as a Date object. + * @returns The event date, e.g. `new Date(1433502692297)` + */ + getDate() { + return this.event.origin_server_ts ? new Date(this.event.origin_server_ts) : null; + } + /** + * Get a string containing details of this event + * + * This is intended for logging, to help trace errors. Example output: + * + * @example + * ``` + * id=$HjnOHV646n0SjLDAqFrgIjim7RCpB7cdMXFrekWYAn type=m.room.encrypted + * sender=@user:example.com room=!room:example.com ts=2022-10-25T17:30:28.404Z + * ``` + */ + getDetails() { + let details = `id=${this.getId()} type=${this.getWireType()} sender=${this.getSender()}`; + const room = this.getRoomId(); + if (room) { + details += ` room=${room}`; + } + const date = this.getDate(); + if (date) { + details += ` ts=${date.toISOString()}`; + } + return details; + } + /** + * Get the (decrypted, if necessary) event content JSON, even if the event + * was replaced by another event. + * + * @returns The event content JSON, or an empty object. + */ + getOriginalContent() { + if (this._localRedactionEvent) { + return {}; + } + if (this.clearEvent) { + return (this.clearEvent.content || {}); + } + return (this.event.content || {}); + } + /** + * Get the (decrypted, if necessary) event content JSON, + * or the content from the replacing event, if any. + * See `makeReplaced`. + * + * @returns The event content JSON, or an empty object. + */ + getContent() { + if (this._localRedactionEvent) { + return {}; + } + else if (this._replacingEvent) { + return this._replacingEvent.getContent()["m.new_content"] || {}; + } + else { + return this.getOriginalContent(); + } + } + /** + * Get the (possibly encrypted) event content JSON that will be sent to the + * homeserver. + * + * @returns The event content JSON, or an empty object. + */ + getWireContent() { + return this.event.content || {}; + } + /** + * Get the event ID of the thread head + */ + get threadRootId() { + var _a, _b; + const relatesTo = (_a = this.getWireContent()) === null || _a === void 0 ? void 0 : _a["m.relates_to"]; + if ((relatesTo === null || relatesTo === void 0 ? void 0 : relatesTo.rel_type) === thread_1.THREAD_RELATION_TYPE.name) { + return relatesTo.event_id; + } + else { + return ((_b = this.getThread()) === null || _b === void 0 ? void 0 : _b.id) || this.threadId; + } + } + /** + * A helper to check if an event is a thread's head or not + */ + get isThreadRoot() { + var _a; + const threadDetails = this.getServerAggregatedRelation(thread_1.THREAD_RELATION_TYPE.name); + // Bundled relationships only returned when the sync response is limited + // hence us having to check both bundled relation and inspect the thread + // model + return !!threadDetails || ((_a = this.getThread()) === null || _a === void 0 ? void 0 : _a.id) === this.getId(); + } + get replyEventId() { + var _a, _b; + return (_b = (_a = this.getWireContent()["m.relates_to"]) === null || _a === void 0 ? void 0 : _a["m.in_reply_to"]) === null || _b === void 0 ? void 0 : _b.event_id; + } + get relationEventId() { + var _a, _b; + return (_b = (_a = this.getWireContent()) === null || _a === void 0 ? void 0 : _a["m.relates_to"]) === null || _b === void 0 ? void 0 : _b.event_id; + } + /** + * Get the previous event content JSON. This will only return something for + * state events which exist in the timeline. + * @returns The previous event content JSON, or an empty object. + */ + getPrevContent() { + // v2 then v1 then default + return this.getUnsigned().prev_content || this.event.prev_content || {}; + } + /** + * Get either 'content' or 'prev_content' depending on if this event is + * 'forward-looking' or not. This can be modified via event.forwardLooking. + * In practice, this means we get the chronologically earlier content value + * for this event (this method should surely be called getEarlierContent) + * This method is experimental and may change. + * @returns event.content if this event is forward-looking, else + * event.prev_content. + */ + getDirectionalContent() { + return this.forwardLooking ? this.getContent() : this.getPrevContent(); + } + /** + * Get the age of this event. This represents the age of the event when the + * event arrived at the device, and not the age of the event when this + * function was called. + * Can only be returned once the server has echo'ed back + * @returns The age of this event in milliseconds. + */ + getAge() { + return this.getUnsigned().age || this.event.age; // v2 / v1 + } + /** + * Get the age of the event when this function was called. + * This is the 'age' field adjusted according to how long this client has + * had the event. + * @returns The age of this event in milliseconds. + */ + getLocalAge() { + return Date.now() - this.localTimestamp; + } + /** + * Get the event state_key if it has one. This will return undefined + * for message events. + * @returns The event's `state_key`. + */ + getStateKey() { + return this.event.state_key; + } + /** + * Check if this event is a state event. + * @returns True if this is a state event. + */ + isState() { + return this.event.state_key !== undefined; + } + /** + * Replace the content of this event with encrypted versions. + * (This is used when sending an event; it should not be used by applications). + * + * @internal + * + * @param cryptoType - type of the encrypted event - typically + * "m.room.encrypted" + * + * @param cryptoContent - raw 'content' for the encrypted event. + * + * @param senderCurve25519Key - curve25519 key to record for the + * sender of this event. + * See {@link MatrixEvent#getSenderKey}. + * + * @param claimedEd25519Key - claimed ed25519 key to record for the + * sender if this event. + * See {@link MatrixEvent#getClaimedEd25519Key} + */ + makeEncrypted(cryptoType, cryptoContent, senderCurve25519Key, claimedEd25519Key) { + // keep the plain-text data for 'view source' + this.clearEvent = { + type: this.event.type, + content: this.event.content, + }; + this.event.type = cryptoType; + this.event.content = cryptoContent; + this.senderCurve25519Key = senderCurve25519Key; + this.claimedEd25519Key = claimedEd25519Key; + } + /** + * Check if this event is currently being decrypted. + * + * @returns True if this event is currently being decrypted, else false. + */ + isBeingDecrypted() { + return this.decryptionPromise != null; + } + getDecryptionPromise() { + return this.decryptionPromise; + } + /** + * Check if this event is an encrypted event which we failed to decrypt + * + * (This implies that we might retry decryption at some point in the future) + * + * @returns True if this event is an encrypted event which we + * couldn't decrypt. + */ + isDecryptionFailure() { + var _a, _b; + return ((_b = (_a = this.clearEvent) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.msgtype) === "m.bad.encrypted"; + } + /* + * True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and + * the sender has disabled encrypting to unverified devices. + */ + get isEncryptedDisabledForUnverifiedDevices() { + return this.isDecryptionFailure() && this.encryptedDisabledForUnverifiedDevices; + } + shouldAttemptDecryption() { + if (this.isRedacted()) + return false; + if (this.isBeingDecrypted()) + return false; + if (this.clearEvent) + return false; + if (!this.isEncrypted()) + return false; + return true; + } + /** + * Start the process of trying to decrypt this event. + * + * (This is used within the SDK: it isn't intended for use by applications) + * + * @internal + * + * @param crypto - crypto module + * + * @returns promise which resolves (to undefined) when the decryption + * attempt is completed. + */ + attemptDecryption(crypto, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + // start with a couple of sanity checks. + if (!this.isEncrypted()) { + throw new Error("Attempt to decrypt event which isn't encrypted"); + } + const alreadyDecrypted = this.clearEvent && !this.isDecryptionFailure(); + const forceRedecrypt = options.forceRedecryptIfUntrusted && this.isKeySourceUntrusted(); + if (alreadyDecrypted && !forceRedecrypt) { + // we may want to just ignore this? let's start with rejecting it. + throw new Error("Attempt to decrypt event which has already been decrypted"); + } + // if we already have a decryption attempt in progress, then it may + // fail because it was using outdated info. We now have reason to + // succeed where it failed before, but we don't want to have multiple + // attempts going at the same time, so just set a flag that says we have + // new info. + // + if (this.decryptionPromise) { + logger_1.logger.log(`Event ${this.getId()} already being decrypted; queueing a retry`); + this.retryDecryption = true; + return this.decryptionPromise; + } + this.decryptionPromise = this.decryptionLoop(crypto, options); + return this.decryptionPromise; + }); + } + /** + * Cancel any room key request for this event and resend another. + * + * @param crypto - crypto module + * @param userId - the user who received this event + * + * @returns a promise that resolves when the request is queued + */ + cancelAndResendKeyRequest(crypto, userId) { + const wireContent = this.getWireContent(); + return crypto.requestRoomKey({ + algorithm: wireContent.algorithm, + room_id: this.getRoomId(), + session_id: wireContent.session_id, + sender_key: wireContent.sender_key, + }, this.getKeyRequestRecipients(userId), true); + } + /** + * Calculate the recipients for keyshare requests. + * + * @param userId - the user who received this event. + * + * @returns array of recipients + */ + getKeyRequestRecipients(userId) { + // send the request to all of our own devices + const recipients = [ + { + userId, + deviceId: "*", + }, + ]; + return recipients; + } + decryptionLoop(crypto, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + // make sure that this method never runs completely synchronously. + // (doing so would mean that we would clear decryptionPromise *before* + // it is set in attemptDecryption - and hence end up with a stuck + // `decryptionPromise`). + yield Promise.resolve(); + // eslint-disable-next-line no-constant-condition + while (true) { + this.retryDecryption = false; + let res; + let err = undefined; + try { + if (!crypto) { + res = this.badEncryptedMessage("Encryption not enabled"); + } + else { + res = yield crypto.decryptEvent(this); + if (options.isRetry === true) { + logger_1.logger.info(`Decrypted event on retry (${this.getDetails()})`); + } + } + } + catch (e) { + const detailedError = e instanceof algorithms_1.DecryptionError ? e.detailedString : String(e); + err = e; + // see if we have a retry queued. + // + // NB: make sure to keep this check in the same tick of the + // event loop as `decryptionPromise = null` below - otherwise we + // risk a race: + // + // * A: we check retryDecryption here and see that it is + // false + // * B: we get a second call to attemptDecryption, which sees + // that decryptionPromise is set so sets + // retryDecryption + // * A: we continue below, clear decryptionPromise, and + // never do the retry. + // + if (this.retryDecryption) { + // decryption error, but we have a retry queued. + logger_1.logger.log(`Error decrypting event (${this.getDetails()}), but retrying: ${detailedError}`); + continue; + } + // decryption error, no retries queued. Warn about the error and + // set it to m.bad.encrypted. + // + // the detailedString already includes the name and message of the error, and the stack isn't much use, + // so we don't bother to log `e` separately. + logger_1.logger.warn(`Error decrypting event (${this.getDetails()}): ${detailedError}`); + res = this.badEncryptedMessage(String(e)); + } + // at this point, we've either successfully decrypted the event, or have given up + // (and set res to a 'badEncryptedMessage'). Either way, we can now set the + // cleartext of the event and raise Event.decrypted. + // + // make sure we clear 'decryptionPromise' before sending the 'Event.decrypted' event, + // otherwise the app will be confused to see `isBeingDecrypted` still set when + // there isn't an `Event.decrypted` on the way. + // + // see also notes on retryDecryption above. + // + this.decryptionPromise = null; + this.retryDecryption = false; + this.setClearData(res); + // Before we emit the event, clear the push actions so that they can be recalculated + // by relevant code. We do this because the clear event has now changed, making it + // so that existing rules can be re-run over the applicable properties. Stuff like + // highlighting when the user's name is mentioned rely on this happening. We also want + // to set the push actions before emitting so that any notification listeners don't + // pick up the wrong contents. + this.setPushActions(null); + if (options.emit !== false) { + this.emit(MatrixEventEvent.Decrypted, this, err); + } + return; + } + }); + } + badEncryptedMessage(reason) { + return { + clearEvent: { + type: event_1.EventType.RoomMessage, + content: { + msgtype: "m.bad.encrypted", + body: "** Unable to decrypt: " + reason + " **", + }, + }, + encryptedDisabledForUnverifiedDevices: reason === `DecryptionError: ${OlmDevice_1.WITHHELD_MESSAGES["m.unverified"]}`, + }; + } + /** + * Update the cleartext data on this event. + * + * (This is used after decrypting an event; it should not be used by applications). + * + * @internal + * + * @param decryptionResult - the decryption result, including the plaintext and some key info + * + * @remarks + * Fires {@link MatrixEventEvent.Decrypted} + */ + setClearData(decryptionResult) { + var _a, _b; + this.clearEvent = decryptionResult.clearEvent; + this.senderCurve25519Key = (_a = decryptionResult.senderCurve25519Key) !== null && _a !== void 0 ? _a : null; + this.claimedEd25519Key = (_b = decryptionResult.claimedEd25519Key) !== null && _b !== void 0 ? _b : null; + this.forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain || []; + this.untrusted = decryptionResult.untrusted || false; + this.encryptedDisabledForUnverifiedDevices = decryptionResult.encryptedDisabledForUnverifiedDevices || false; + this.invalidateExtensibleEvent(); + } + /** + * Gets the cleartext content for this event. If the event is not encrypted, + * or encryption has not been completed, this will return null. + * + * @returns The cleartext (decrypted) content for the event + */ + getClearContent() { + return this.clearEvent ? this.clearEvent.content : null; + } + /** + * Check if the event is encrypted. + * @returns True if this event is encrypted. + */ + isEncrypted() { + return !this.isState() && this.event.type === event_1.EventType.RoomMessageEncrypted; + } + /** + * The curve25519 key for the device that we think sent this event + * + * For an Olm-encrypted event, this is inferred directly from the DH + * exchange at the start of the session: the curve25519 key is involved in + * the DH exchange, so only a device which holds the private part of that + * key can establish such a session. + * + * For a megolm-encrypted event, it is inferred from the Olm message which + * established the megolm session + */ + getSenderKey() { + return this.senderCurve25519Key; + } + /** + * The additional keys the sender of this encrypted event claims to possess. + * + * Just a wrapper for #getClaimedEd25519Key (q.v.) + */ + getKeysClaimed() { + if (!this.claimedEd25519Key) + return {}; + return { + ed25519: this.claimedEd25519Key, + }; + } + /** + * Get the ed25519 the sender of this event claims to own. + * + * For Olm messages, this claim is encoded directly in the plaintext of the + * event itself. For megolm messages, it is implied by the m.room_key event + * which established the megolm session. + * + * Until we download the device list of the sender, it's just a claim: the + * device list gives a proof that the owner of the curve25519 key used for + * this event (and returned by #getSenderKey) also owns the ed25519 key by + * signing the public curve25519 key with the ed25519 key. + * + * In general, applications should not use this method directly, but should + * instead use MatrixClient.getEventSenderDeviceInfo. + */ + getClaimedEd25519Key() { + return this.claimedEd25519Key; + } + /** + * Get the curve25519 keys of the devices which were involved in telling us + * about the claimedEd25519Key and sender curve25519 key. + * + * Normally this will be empty, but in the case of a forwarded megolm + * session, the sender keys are sent to us by another device (the forwarding + * device), which we need to trust to do this. In that case, the result will + * be a list consisting of one entry. + * + * If the device that sent us the key (A) got it from another device which + * it wasn't prepared to vouch for (B), the result will be [A, B]. And so on. + * + * @returns base64-encoded curve25519 keys, from oldest to newest. + */ + getForwardingCurve25519KeyChain() { + return this.forwardingCurve25519KeyChain; + } + /** + * Whether the decryption key was obtained from an untrusted source. If so, + * we cannot verify the authenticity of the message. + */ + isKeySourceUntrusted() { + return !!this.untrusted; + } + getUnsigned() { + return this.event.unsigned || {}; + } + setUnsigned(unsigned) { + this.event.unsigned = unsigned; + } + unmarkLocallyRedacted() { + const value = this._localRedactionEvent; + this._localRedactionEvent = null; + if (this.event.unsigned) { + this.event.unsigned.redacted_because = undefined; + } + return !!value; + } + markLocallyRedacted(redactionEvent) { + if (this._localRedactionEvent) + return; + this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent); + this._localRedactionEvent = redactionEvent; + if (!this.event.unsigned) { + this.event.unsigned = {}; + } + this.event.unsigned.redacted_because = redactionEvent.event; + } + /** + * Change the visibility of an event, as per https://github.com/matrix-org/matrix-doc/pull/3531 . + * + * @param visibilityChange - event holding a hide/unhide payload, or nothing + * if the event is being reset to its original visibility (presumably + * by a visibility event being redacted). + * + * @remarks + * Fires {@link MatrixEventEvent.VisibilityChange} if `visibilityEvent` + * caused a change in the actual visibility of this event, either by making it + * visible (if it was hidden), by making it hidden (if it was visible) or by + * changing the reason (if it was hidden). + */ + applyVisibilityEvent(visibilityChange) { + var _a, _b; + const visible = (_a = visibilityChange === null || visibilityChange === void 0 ? void 0 : visibilityChange.visible) !== null && _a !== void 0 ? _a : true; + const reason = (_b = visibilityChange === null || visibilityChange === void 0 ? void 0 : visibilityChange.reason) !== null && _b !== void 0 ? _b : null; + let change = false; + if (this.visibility.visible !== visible) { + change = true; + } + else if (!this.visibility.visible && this.visibility["reason"] !== reason) { + change = true; + } + if (change) { + if (visible) { + this.visibility = MESSAGE_VISIBLE; + } + else { + this.visibility = Object.freeze({ + visible: false, + reason, + }); + } + this.emit(MatrixEventEvent.VisibilityChange, this, visible); + } + } + /** + * Return instructions to display or hide the message. + * + * @returns Instructions determining whether the message + * should be displayed. + */ + messageVisibility() { + // Note: We may return `this.visibility` without fear, as + // this is a shallow frozen object. + return this.visibility; + } + /** + * Update the content of an event in the same way it would be by the server + * if it were redacted before it was sent to us + * + * @param redactionEvent - event causing the redaction + */ + makeRedacted(redactionEvent) { + // quick sanity-check + if (!redactionEvent.event) { + throw new Error("invalid redactionEvent in makeRedacted"); + } + this._localRedactionEvent = null; + this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent); + this._replacingEvent = null; + // we attempt to replicate what we would see from the server if + // the event had been redacted before we saw it. + // + // The server removes (most of) the content of the event, and adds a + // "redacted_because" key to the unsigned section containing the + // redacted event. + if (!this.event.unsigned) { + this.event.unsigned = {}; + } + this.event.unsigned.redacted_because = redactionEvent.event; + for (const key in this.event) { + if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) { + delete this.event[key]; + } + } + // If the event is encrypted prune the decrypted bits + if (this.isEncrypted()) { + this.clearEvent = undefined; + } + const keeps = this.getType() in REDACT_KEEP_CONTENT_MAP + ? REDACT_KEEP_CONTENT_MAP[this.getType()] + : {}; + const content = this.getContent(); + for (const key in content) { + if (content.hasOwnProperty(key) && !keeps[key]) { + delete content[key]; + } + } + this.invalidateExtensibleEvent(); + } + /** + * Check if this event has been redacted + * + * @returns True if this event has been redacted + */ + isRedacted() { + return Boolean(this.getUnsigned().redacted_because); + } + /** + * Check if this event is a redaction of another event + * + * @returns True if this event is a redaction + */ + isRedaction() { + return this.getType() === event_1.EventType.RoomRedaction; + } + /** + * Return the visibility change caused by this event, + * as per https://github.com/matrix-org/matrix-doc/pull/3531. + * + * @returns If the event is a well-formed visibility change event, + * an instance of `IVisibilityChange`, otherwise `null`. + */ + asVisibilityChange() { + if (!event_1.EVENT_VISIBILITY_CHANGE_TYPE.matches(this.getType())) { + // Not a visibility change event. + return null; + } + const relation = this.getRelation(); + if (!relation || relation.rel_type != "m.reference") { + // Ill-formed, ignore this event. + return null; + } + const eventId = relation.event_id; + if (!eventId) { + // Ill-formed, ignore this event. + return null; + } + const content = this.getWireContent(); + const visible = !!content.visible; + const reason = content.reason; + if (reason && typeof reason != "string") { + // Ill-formed, ignore this event. + return null; + } + // Well-formed visibility change event. + return { + visible, + reason, + eventId, + }; + } + /** + * Check if this event alters the visibility of another event, + * as per https://github.com/matrix-org/matrix-doc/pull/3531. + * + * @returns True if this event alters the visibility + * of another event. + */ + isVisibilityEvent() { + return event_1.EVENT_VISIBILITY_CHANGE_TYPE.matches(this.getType()); + } + /** + * Get the (decrypted, if necessary) redaction event JSON + * if event was redacted + * + * @returns The redaction event JSON, or an empty object + */ + getRedactionEvent() { + var _a, _b, _c, _d; + if (!this.isRedacted()) + return null; + if ((_a = this.clearEvent) === null || _a === void 0 ? void 0 : _a.unsigned) { + return (_c = (_b = this.clearEvent) === null || _b === void 0 ? void 0 : _b.unsigned.redacted_because) !== null && _c !== void 0 ? _c : null; + } + else if ((_d = this.event.unsigned) === null || _d === void 0 ? void 0 : _d.redacted_because) { + return this.event.unsigned.redacted_because; + } + else { + return {}; + } + } + /** + * Get the push actions, if known, for this event + * + * @returns push actions + */ + getPushActions() { + return this.pushActions; + } + /** + * Set the push actions for this event. + * + * @param pushActions - push actions + */ + setPushActions(pushActions) { + this.pushActions = pushActions; + } + /** + * Replace the `event` property and recalculate any properties based on it. + * @param event - the object to assign to the `event` property + */ + handleRemoteEcho(event) { + const oldUnsigned = this.getUnsigned(); + const oldId = this.getId(); + this.event = event; + // if this event was redacted before it was sent, it's locally marked as redacted. + // At this point, we've received the remote echo for the event, but not yet for + // the redaction that we are sending ourselves. Preserve the locally redacted + // state by copying over redacted_because so we don't get a flash of + // redacted, not-redacted, redacted as remote echos come in + if (oldUnsigned.redacted_because) { + if (!this.event.unsigned) { + this.event.unsigned = {}; + } + this.event.unsigned.redacted_because = oldUnsigned.redacted_because; + } + // successfully sent. + this.setStatus(null); + if (this.getId() !== oldId) { + // emit the event if it changed + this.emit(MatrixEventEvent.LocalEventIdReplaced, this); + } + this.localTimestamp = Date.now() - this.getAge(); + } + /** + * Whether the event is in any phase of sending, send failure, waiting for + * remote echo, etc. + */ + isSending() { + return !!this.status; + } + /** + * Update the event's sending status and emit an event as well. + * + * @param status - The new status + */ + setStatus(status) { + this.status = status; + this.emit(MatrixEventEvent.Status, this, status); + } + replaceLocalEventId(eventId) { + this.event.event_id = eventId; + this.emit(MatrixEventEvent.LocalEventIdReplaced, this); + } + /** + * Get whether the event is a relation event, and of a given type if + * `relType` is passed in. State events cannot be relation events + * + * @param relType - if given, checks that the relation is of the + * given type + */ + isRelation(relType) { + var _a; + // Relation info is lifted out of the encrypted content when sent to + // encrypted rooms, so we have to check `getWireContent` for this. + const relation = (_a = this.getWireContent()) === null || _a === void 0 ? void 0 : _a["m.relates_to"]; + if (this.isState() && (relation === null || relation === void 0 ? void 0 : relation.rel_type) === event_1.RelationType.Replace) { + // State events cannot be m.replace relations + return false; + } + return !!((relation === null || relation === void 0 ? void 0 : relation.rel_type) && relation.event_id && (relType ? relation.rel_type === relType : true)); + } + /** + * Get relation info for the event, if any. + */ + getRelation() { + var _a; + if (!this.isRelation()) { + return null; + } + return (_a = this.getWireContent()["m.relates_to"]) !== null && _a !== void 0 ? _a : null; + } + /** + * Set an event that replaces the content of this event, through an m.replace relation. + * + * @param newEvent - the event with the replacing content, if any. + * + * @remarks + * Fires {@link MatrixEventEvent.Replaced} + */ + makeReplaced(newEvent) { + // don't allow redacted events to be replaced. + // if newEvent is null we allow to go through though, + // as with local redaction, the replacing event might get + // cancelled, which should be reflected on the target event. + if (this.isRedacted() && newEvent) { + return; + } + // don't allow state events to be replaced using this mechanism as per MSC2676 + if (this.isState()) { + return; + } + if (this._replacingEvent !== newEvent) { + this._replacingEvent = newEvent !== null && newEvent !== void 0 ? newEvent : null; + this.emit(MatrixEventEvent.Replaced, this); + this.invalidateExtensibleEvent(); + } + } + /** + * Returns the status of any associated edit or redaction + * (not for reactions/annotations as their local echo doesn't affect the original event), + * or else the status of the event. + */ + getAssociatedStatus() { + if (this._replacingEvent) { + return this._replacingEvent.status; + } + else if (this._localRedactionEvent) { + return this._localRedactionEvent.status; + } + return this.status; + } + getServerAggregatedRelation(relType) { + var _a; + return (_a = this.getUnsigned()["m.relations"]) === null || _a === void 0 ? void 0 : _a[relType]; + } + /** + * Returns the event ID of the event replacing the content of this event, if any. + */ + replacingEventId() { + const replaceRelation = this.getServerAggregatedRelation(event_1.RelationType.Replace); + if (replaceRelation) { + return replaceRelation.event_id; + } + else if (this._replacingEvent) { + return this._replacingEvent.getId(); + } + } + /** + * Returns the event replacing the content of this event, if any. + * Replacements are aggregated on the server, so this would only + * return an event in case it came down the sync, or for local echo of edits. + */ + replacingEvent() { + return this._replacingEvent; + } + /** + * Returns the origin_server_ts of the event replacing the content of this event, if any. + */ + replacingEventDate() { + var _a; + const replaceRelation = this.getServerAggregatedRelation(event_1.RelationType.Replace); + if (replaceRelation) { + const ts = replaceRelation.origin_server_ts; + if (Number.isFinite(ts)) { + return new Date(ts); + } + } + else if (this._replacingEvent) { + return (_a = this._replacingEvent.getDate()) !== null && _a !== void 0 ? _a : undefined; + } + } + /** + * Returns the event that wants to redact this event, but hasn't been sent yet. + * @returns the event + */ + localRedactionEvent() { + return this._localRedactionEvent; + } + /** + * For relations and redactions, returns the event_id this event is referring to. + */ + getAssociatedId() { + const relation = this.getRelation(); + if (this.replyEventId) { + return this.replyEventId; + } + else if (relation) { + return relation.event_id; + } + else if (this.isRedaction()) { + return this.event.redacts; + } + } + /** + * Checks if this event is associated with another event. See `getAssociatedId`. + * @deprecated use hasAssociation instead. + */ + hasAssocation() { + return !!this.getAssociatedId(); + } + /** + * Checks if this event is associated with another event. See `getAssociatedId`. + */ + hasAssociation() { + return !!this.getAssociatedId(); + } + /** + * Update the related id with a new one. + * + * Used to replace a local id with remote one before sending + * an event with a related id. + * + * @param eventId - the new event id + */ + updateAssociatedId(eventId) { + const relation = this.getRelation(); + if (relation) { + relation.event_id = eventId; + } + else if (this.isRedaction()) { + this.event.redacts = eventId; + } + } + /** + * Flags an event as cancelled due to future conditions. For example, a verification + * request event in the same sync transaction may be flagged as cancelled to warn + * listeners that a cancellation event is coming down the same pipe shortly. + * @param cancelled - Whether the event is to be cancelled or not. + */ + flagCancelled(cancelled = true) { + this._isCancelled = cancelled; + } + /** + * Gets whether or not the event is flagged as cancelled. See flagCancelled() for + * more information. + * @returns True if the event is cancelled, false otherwise. + */ + isCancelled() { + return this._isCancelled; + } + /** + * Get a copy/snapshot of this event. The returned copy will be loosely linked + * back to this instance, though will have "frozen" event information. Other + * properties of this MatrixEvent instance will be copied verbatim, which can + * mean they are in reference to this instance despite being on the copy too. + * The reference the snapshot uses does not change, however members aside from + * the underlying event will not be deeply cloned, thus may be mutated internally. + * For example, the sender profile will be copied over at snapshot time, and + * the sender profile internally may mutate without notice to the consumer. + * + * This is meant to be used to snapshot the event details themselves, not the + * features (such as sender) surrounding the event. + * @returns A snapshot of this event. + */ + toSnapshot() { + const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event))); + for (const [p, v] of Object.entries(this)) { + if (p !== "event") { + // exclude the thing we just cloned + // @ts-ignore - XXX: this is just nasty + ev[p] = v; + } + } + return ev; + } + /** + * Determines if this event is equivalent to the given event. This only checks + * the event object itself, not the other properties of the event. Intended for + * use with toSnapshot() to identify events changing. + * @param otherEvent - The other event to check against. + * @returns True if the events are the same, false otherwise. + */ + isEquivalentTo(otherEvent) { + if (!otherEvent) + return false; + if (otherEvent === this) + return true; + const myProps = (0, utils_1.deepSortedObjectEntries)(this.event); + const theirProps = (0, utils_1.deepSortedObjectEntries)(otherEvent.event); + return JSON.stringify(myProps) === JSON.stringify(theirProps); + } + /** + * Summarise the event as JSON. This is currently used by React SDK's view + * event source feature and Seshat's event indexing, so take care when + * adjusting the output here. + * + * If encrypted, include both the decrypted and encrypted view of the event. + * + * This is named `toJSON` for use with `JSON.stringify` which checks objects + * for functions named `toJSON` and will call them to customise the output + * if they are defined. + */ + toJSON() { + const event = this.getEffectiveEvent(); + if (!this.isEncrypted()) { + return event; + } + return { + decrypted: event, + encrypted: this.event, + }; + } + setVerificationRequest(request) { + this.verificationRequest = request; + } + setTxnId(txnId) { + this.txnId = txnId; + } + getTxnId() { + return this.txnId; + } + /** + * Set the instance of a thread associated with the current event + * @param thread - the thread + */ + setThread(thread) { + if (this.thread) { + this.reEmitter.stopReEmitting(this.thread, [thread_1.ThreadEvent.Update]); + } + this.thread = thread; + this.setThreadId(thread === null || thread === void 0 ? void 0 : thread.id); + if (thread) { + this.reEmitter.reEmit(thread, [thread_1.ThreadEvent.Update]); + } + } + /** + * Get the instance of the thread associated with the current event + */ + getThread() { + return this.thread; + } + setThreadId(threadId) { + this.threadId = threadId; + } +} +exports.MatrixEvent = MatrixEvent; +/* REDACT_KEEP_KEYS gives the keys we keep when an event is redacted + * + * This is specified here: + * http://matrix.org/speculator/spec/HEAD/client_server/latest.html#redactions + * + * Also: + * - We keep 'unsigned' since that is created by the local server + * - We keep user_id for backwards-compat with v1 + */ +const REDACT_KEEP_KEYS = new Set([ + "event_id", + "type", + "room_id", + "user_id", + "sender", + "state_key", + "prev_state", + "content", + "unsigned", + "origin_server_ts", +]); +// a map from state event type to the .content keys we keep when an event is redacted +const REDACT_KEEP_CONTENT_MAP = { + [event_1.EventType.RoomMember]: { membership: 1 }, + [event_1.EventType.RoomCreate]: { creator: 1 }, + [event_1.EventType.RoomJoinRules]: { join_rule: 1 }, + [event_1.EventType.RoomPowerLevels]: { + ban: 1, + events: 1, + events_default: 1, + kick: 1, + redact: 1, + state_default: 1, + users: 1, + users_default: 1, + }, +}; + +},{"../@types/event":306,"../ReEmitter":317,"../crypto/OlmDevice":327,"../crypto/algorithms":333,"../logger":374,"../utils":416,"./event-status":380,"./thread":394,"./typed-event-emitter":395,"matrix-events-sdk":167}],384:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IgnoredInvites = exports.PolicyScope = exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY = exports.POLICIES_ACCOUNT_EVENT_TYPE = void 0; +const matrix_events_sdk_1 = require("matrix-events-sdk"); +const event_timeline_1 = require("./event-timeline"); +const partials_1 = require("../@types/partials"); +const utils_1 = require("../utils"); +/// The event type storing the user's individual policies. +/// +/// Exported for testing purposes. +exports.POLICIES_ACCOUNT_EVENT_TYPE = new matrix_events_sdk_1.UnstableValue("m.policies", "org.matrix.msc3847.policies"); +/// The key within the user's individual policies storing the user's ignored invites. +/// +/// Exported for testing purposes. +exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY = new matrix_events_sdk_1.UnstableValue("m.ignore.invites", "org.matrix.msc3847.ignore.invites"); +/// The types of recommendations understood. +var PolicyRecommendation; +(function (PolicyRecommendation) { + PolicyRecommendation["Ban"] = "m.ban"; +})(PolicyRecommendation || (PolicyRecommendation = {})); +/** + * The various scopes for policies. + */ +var PolicyScope; +(function (PolicyScope) { + /** + * The policy deals with an individual user, e.g. reject invites + * from this user. + */ + PolicyScope["User"] = "m.policy.user"; + /** + * The policy deals with a room, e.g. reject invites towards + * a specific room. + */ + PolicyScope["Room"] = "m.policy.room"; + /** + * The policy deals with a server, e.g. reject invites from + * this server. + */ + PolicyScope["Server"] = "m.policy.server"; +})(PolicyScope = exports.PolicyScope || (exports.PolicyScope = {})); +/** + * A container for ignored invites. + * + * # Performance + * + * This implementation is extremely naive. It expects that we are dealing + * with a very short list of sources (e.g. only one). If real-world + * applications turn out to require longer lists, we may need to rework + * our data structures. + */ +class IgnoredInvites { + constructor(client) { + this.client = client; + } + /** + * Add a new rule. + * + * @param scope - The scope for this rule. + * @param entity - The entity covered by this rule. Globs are supported. + * @param reason - A human-readable reason for introducing this new rule. + * @returns The event id for the new rule. + */ + addRule(scope, entity, reason) { + return __awaiter(this, void 0, void 0, function* () { + const target = yield this.getOrCreateTargetRoom(); + const response = yield this.client.sendStateEvent(target.roomId, scope, { + entity, + reason, + recommendation: PolicyRecommendation.Ban, + }); + return response.event_id; + }); + } + /** + * Remove a rule. + */ + removeRule(event) { + return __awaiter(this, void 0, void 0, function* () { + yield this.client.redactEvent(event.getRoomId(), event.getId()); + }); + } + /** + * Add a new room to the list of sources. If the user isn't a member of the + * room, attempt to join it. + * + * @param roomId - A valid room id. If this room is already in the list + * of sources, it will not be duplicated. + * @returns `true` if the source was added, `false` if it was already present. + * @throws If `roomId` isn't the id of a room that the current user is already + * member of or can join. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + addSource(roomId) { + return __awaiter(this, void 0, void 0, function* () { + // We attempt to join the room *before* calling + // `await this.getOrCreateSourceRooms()` to decrease the duration + // of the racy section. + yield this.client.joinRoom(roomId); + // Race starts. + const sources = (yield this.getOrCreateSourceRooms()).map((room) => room.roomId); + if (sources.includes(roomId)) { + return false; + } + sources.push(roomId); + yield this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => { + ignoreInvitesPolicies.sources = sources; + }); + // Race ends. + return true; + }); + } + /** + * Find out whether an invite should be ignored. + * + * @param sender - The user id for the user who issued the invite. + * @param roomId - The room to which the user is invited. + * @returns A rule matching the entity, if any was found, `null` otherwise. + */ + getRuleForInvite({ sender, roomId, }) { + return __awaiter(this, void 0, void 0, function* () { + // In this implementation, we perform a very naive lookup: + // - search in each policy room; + // - turn each (potentially glob) rule entity into a regexp. + // + // Real-world testing will tell us whether this is performant enough. + // In the (unfortunately likely) case it isn't, there are several manners + // in which we could optimize this: + // - match several entities per go; + // - pre-compile each rule entity into a regexp; + // - pre-compile entire rooms into a single regexp. + const policyRooms = yield this.getOrCreateSourceRooms(); + const senderServer = sender.split(":")[1]; + const roomServer = roomId.split(":")[1]; + for (const room of policyRooms) { + const state = room.getUnfilteredTimelineSet().getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + for (const { scope, entities } of [ + { scope: PolicyScope.Room, entities: [roomId] }, + { scope: PolicyScope.User, entities: [sender] }, + { scope: PolicyScope.Server, entities: [senderServer, roomServer] }, + ]) { + const events = state.getStateEvents(scope); + for (const event of events) { + const content = event.getContent(); + if ((content === null || content === void 0 ? void 0 : content.recommendation) != PolicyRecommendation.Ban) { + // Ignoring invites only looks at `m.ban` recommendations. + continue; + } + const glob = content === null || content === void 0 ? void 0 : content.entity; + if (!glob) { + // Invalid event. + continue; + } + let regexp; + try { + regexp = new RegExp((0, utils_1.globToRegexp)(glob, false)); + } + catch (ex) { + // Assume invalid event. + continue; + } + for (const entity of entities) { + if (entity && regexp.test(entity)) { + return event; + } + } + // No match. + } + } + } + return null; + }); + } + /** + * Get the target room, i.e. the room in which any new rule should be written. + * + * If there is no target room setup, a target room is created. + * + * Note: This method is public for testing reasons. Most clients should not need + * to call it directly. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + getOrCreateTargetRoom() { + return __awaiter(this, void 0, void 0, function* () { + const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies(); + let target = ignoreInvitesPolicies.target; + // Validate `target`. If it is invalid, trash out the current `target` + // and create a new room. + if (typeof target !== "string") { + target = null; + } + if (target) { + // Check that the room exists and is valid. + const room = this.client.getRoom(target); + if (room) { + return room; + } + else { + target = null; + } + } + // We need to create our own policy room for ignoring invites. + target = (yield this.client.createRoom({ + name: "Individual Policy Room", + preset: partials_1.Preset.PrivateChat, + })).room_id; + yield this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => { + ignoreInvitesPolicies.target = target; + }); + // Since we have just called `createRoom`, `getRoom` should not be `null`. + return this.client.getRoom(target); + }); + } + /** + * Get the list of source rooms, i.e. the rooms from which rules need to be read. + * + * If no source rooms are setup, the target room is used as sole source room. + * + * Note: This method is public for testing reasons. Most clients should not need + * to call it directly. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + getOrCreateSourceRooms() { + return __awaiter(this, void 0, void 0, function* () { + const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies(); + let sources = ignoreInvitesPolicies.sources; + // Validate `sources`. If it is invalid, trash out the current `sources` + // and create a new list of sources from `target`. + let hasChanges = false; + if (!Array.isArray(sources)) { + // `sources` could not be an array. + hasChanges = true; + sources = []; + } + let sourceRooms = sources + // `sources` could contain non-string / invalid room ids + .filter((roomId) => typeof roomId === "string") + .map((roomId) => this.client.getRoom(roomId)) + .filter((room) => !!room); + if (sourceRooms.length != sources.length) { + hasChanges = true; + } + if (sourceRooms.length == 0) { + // `sources` could be empty (possibly because we've removed + // invalid content) + const target = yield this.getOrCreateTargetRoom(); + hasChanges = true; + sourceRooms = [target]; + } + if (hasChanges) { + // Reload `policies`/`ignoreInvitesPolicies` in case it has been changed + // during or by our call to `this.getTargetRoom()`. + yield this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => { + ignoreInvitesPolicies.sources = sources; + }); + } + return sourceRooms; + }); + } + /** + * Fetch the `IGNORE_INVITES_POLICIES` object from account data. + * + * If both an unstable prefix version and a stable prefix version are available, + * it will return the stable prefix version preferentially. + * + * The result is *not* validated but is guaranteed to be a non-null object. + * + * @returns A non-null object. + */ + getIgnoreInvitesPolicies() { + return this.getPoliciesAndIgnoreInvitesPolicies().ignoreInvitesPolicies; + } + /** + * Modify in place the `IGNORE_INVITES_POLICIES` object from account data. + */ + withIgnoreInvitesPolicies(cb) { + return __awaiter(this, void 0, void 0, function* () { + const { policies, ignoreInvitesPolicies } = this.getPoliciesAndIgnoreInvitesPolicies(); + cb(ignoreInvitesPolicies); + policies[exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies; + yield this.client.setAccountData(exports.POLICIES_ACCOUNT_EVENT_TYPE.name, policies); + }); + } + /** + * As `getIgnoreInvitesPolicies` but also return the `POLICIES_ACCOUNT_EVENT_TYPE` + * object. + */ + getPoliciesAndIgnoreInvitesPolicies() { + var _a; + let policies = {}; + for (const key of [exports.POLICIES_ACCOUNT_EVENT_TYPE.name, exports.POLICIES_ACCOUNT_EVENT_TYPE.altName]) { + if (!key) { + continue; + } + const value = (_a = this.client.getAccountData(key)) === null || _a === void 0 ? void 0 : _a.getContent(); + if (value) { + policies = value; + break; + } + } + let ignoreInvitesPolicies = {}; + let hasIgnoreInvitesPolicies = false; + for (const key of [exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY.name, exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY.altName]) { + if (!key) { + continue; + } + const value = policies[key]; + if (value && typeof value == "object") { + ignoreInvitesPolicies = value; + hasIgnoreInvitesPolicies = true; + break; + } + } + if (!hasIgnoreInvitesPolicies) { + policies[exports.IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies; + } + return { policies, ignoreInvitesPolicies }; + } +} +exports.IgnoredInvites = IgnoredInvites; + +},{"../@types/partials":309,"../utils":416,"./event-timeline":382,"matrix-events-sdk":167}],385:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Poll = exports.PollEvent = void 0; +const polls_1 = require("../@types/polls"); +const relations_1 = require("./relations"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +var PollEvent; +(function (PollEvent) { + PollEvent["New"] = "Poll.new"; + PollEvent["End"] = "Poll.end"; + PollEvent["Update"] = "Poll.update"; + PollEvent["Responses"] = "Poll.Responses"; + PollEvent["Destroy"] = "Poll.Destroy"; + PollEvent["UndecryptableRelations"] = "Poll.UndecryptableRelations"; +})(PollEvent = exports.PollEvent || (exports.PollEvent = {})); +const filterResponseRelations = (relationEvents, pollEndTimestamp) => { + const responseEvents = relationEvents.filter((event) => { + if (event.isDecryptionFailure()) { + return; + } + return (polls_1.M_POLL_RESPONSE.matches(event.getType()) && + // From MSC3381: + // "Votes sent on or before the end event's timestamp are valid votes" + event.getTs() <= pollEndTimestamp); + }); + return { responseEvents }; +}; +class Poll extends typed_event_emitter_1.TypedEventEmitter { + constructor(rootEvent, matrixClient, room) { + super(); + this.rootEvent = rootEvent; + this.matrixClient = matrixClient; + this.room = room; + this._isFetchingResponses = false; + this.responses = null; + /** + * Keep track of undecryptable relations + * As incomplete result sets affect poll results + */ + this.undecryptableRelationEventIds = new Set(); + this.countUndecryptableEvents = (events) => { + const undecryptableEventIds = events + .filter((event) => event.isDecryptionFailure()) + .map((event) => event.getId()); + const previousCount = this.undecryptableRelationsCount; + this.undecryptableRelationEventIds = new Set([...this.undecryptableRelationEventIds, ...undecryptableEventIds]); + if (this.undecryptableRelationsCount !== previousCount) { + this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount); + } + }; + if (!this.rootEvent.getRoomId() || !this.rootEvent.getId()) { + throw new Error("Invalid poll start event."); + } + this.roomId = this.rootEvent.getRoomId(); + this.pollEvent = this.rootEvent.unstableExtensibleEvent; + } + get pollId() { + return this.rootEvent.getId(); + } + get endEventId() { + var _a; + return (_a = this.endEvent) === null || _a === void 0 ? void 0 : _a.getId(); + } + get isEnded() { + return !!this.endEvent; + } + get isFetchingResponses() { + return this._isFetchingResponses; + } + get undecryptableRelationsCount() { + return this.undecryptableRelationEventIds.size; + } + getResponses() { + return __awaiter(this, void 0, void 0, function* () { + // if we have already fetched some responses + // just return them + if (this.responses) { + return this.responses; + } + // if there is no fetching in progress + // start fetching + if (!this.isFetchingResponses) { + yield this.fetchResponses(); + } + // return whatever responses we got from the first page + return this.responses; + }); + } + /** + * + * @param event - event with a relation to the rootEvent + * @returns void + */ + onNewRelation(event) { + var _a; + if (polls_1.M_POLL_END.matches(event.getType()) && this.validateEndEvent(event)) { + this.endEvent = event; + this.refilterResponsesOnEnd(); + this.emit(PollEvent.End); + } + // wait for poll responses to be initialised + if (!this.responses) { + return; + } + const pollEndTimestamp = ((_a = this.endEvent) === null || _a === void 0 ? void 0 : _a.getTs()) || Number.MAX_SAFE_INTEGER; + const { responseEvents } = filterResponseRelations([event], pollEndTimestamp); + this.countUndecryptableEvents([event]); + if (responseEvents.length) { + responseEvents.forEach((event) => { + this.responses.addEvent(event); + }); + this.emit(PollEvent.Responses, this.responses); + } + } + fetchResponses() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + this._isFetchingResponses = true; + // we want: + // - stable and unstable M_POLL_RESPONSE + // - stable and unstable M_POLL_END + // so make one api call and filter by event type client side + const allRelations = yield this.matrixClient.relations(this.roomId, this.rootEvent.getId(), "m.reference", undefined, { + from: this.relationsNextBatch || undefined, + }); + yield Promise.all(allRelations.events.map((event) => this.matrixClient.decryptEventIfNeeded(event))); + const responses = this.responses || + new relations_1.Relations("m.reference", polls_1.M_POLL_RESPONSE.name, this.matrixClient, [polls_1.M_POLL_RESPONSE.altName]); + const pollEndEvent = allRelations.events.find((event) => polls_1.M_POLL_END.matches(event.getType())); + if (this.validateEndEvent(pollEndEvent)) { + this.endEvent = pollEndEvent; + this.refilterResponsesOnEnd(); + this.emit(PollEvent.End); + } + const pollCloseTimestamp = ((_a = this.endEvent) === null || _a === void 0 ? void 0 : _a.getTs()) || Number.MAX_SAFE_INTEGER; + const { responseEvents } = filterResponseRelations(allRelations.events, pollCloseTimestamp); + responseEvents.forEach((event) => { + responses.addEvent(event); + }); + this.relationsNextBatch = (_b = allRelations.nextBatch) !== null && _b !== void 0 ? _b : undefined; + this.responses = responses; + this.countUndecryptableEvents(allRelations.events); + // while there are more pages of relations + // fetch them + if (this.relationsNextBatch) { + // don't await + // we want to return the first page as soon as possible + this.fetchResponses(); + } + else { + // no more pages + this._isFetchingResponses = false; + } + // emit after updating _isFetchingResponses state + this.emit(PollEvent.Responses, this.responses); + }); + } + /** + * Only responses made before the poll ended are valid + * Refilter after an end event is recieved + * To ensure responses are valid + */ + refilterResponsesOnEnd() { + var _a; + if (!this.responses) { + return; + } + const pollEndTimestamp = ((_a = this.endEvent) === null || _a === void 0 ? void 0 : _a.getTs()) || Number.MAX_SAFE_INTEGER; + this.responses.getRelations().forEach((event) => { + var _a; + if (event.getTs() > pollEndTimestamp) { + (_a = this.responses) === null || _a === void 0 ? void 0 : _a.removeEvent(event); + } + }); + this.emit(PollEvent.Responses, this.responses); + } + validateEndEvent(endEvent) { + if (!endEvent) { + return false; + } + /** + * Repeated end events are ignored - + * only the first (valid) closure event by origin_server_ts is counted. + */ + if (this.endEvent && this.endEvent.getTs() < endEvent.getTs()) { + return false; + } + /** + * MSC3381 + * If a m.poll.end event is received from someone other than the poll creator or user with permission to redact + * others' messages in the room, the event must be ignored by clients due to being invalid. + */ + const roomCurrentState = this.room.currentState; + const endEventSender = endEvent.getSender(); + return (!!endEventSender && + (endEventSender === this.rootEvent.getSender() || + roomCurrentState.maySendRedactionForEvent(this.rootEvent, endEventSender))); + } +} +exports.Poll = Poll; + +},{"../@types/polls":310,"./relations":388,"./typed-event-emitter":395}],386:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ReadReceipt = exports.synthesizeReceipt = void 0; +const read_receipts_1 = require("../@types/read_receipts"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const utils = __importStar(require("../utils")); +const event_1 = require("./event"); +const event_2 = require("../@types/event"); +const utils_1 = require("../utils"); +const room_1 = require("./room"); +function synthesizeReceipt(userId, event, receiptType) { + var _a; + return new event_1.MatrixEvent({ + content: { + [event.getId()]: { + [receiptType]: { + [userId]: { + ts: event.getTs(), + thread_id: (_a = event.threadRootId) !== null && _a !== void 0 ? _a : read_receipts_1.MAIN_ROOM_TIMELINE, + }, + }, + }, + }, + type: event_2.EventType.Receipt, + room_id: event.getRoomId(), + }); +} +exports.synthesizeReceipt = synthesizeReceipt; +const ReceiptPairRealIndex = 0; +const ReceiptPairSyntheticIndex = 1; +class ReadReceipt extends typed_event_emitter_1.TypedEventEmitter { + constructor() { + super(...arguments); + // receipts should clobber based on receipt_type and user_id pairs hence + // the form of this structure. This is sub-optimal for the exposed APIs + // which pass in an event ID and get back some receipts, so we also store + // a pre-cached list for this purpose. + // Map: receipt type → user Id → receipt + this.receipts = new utils_1.MapWithDefault(() => new Map()); + this.receiptCacheByEventId = new Map(); + } + /** + * Gets the latest receipt for a given user in the room + * @param userId - The id of the user for which we want the receipt + * @param ignoreSynthesized - Whether to ignore synthesized receipts or not + * @param receiptType - Optional. The type of the receipt we want to get + * @returns the latest receipts of the chosen type for the chosen user + */ + getReadReceiptForUserId(userId, ignoreSynthesized = false, receiptType = read_receipts_1.ReceiptType.Read) { + var _a, _b; + const [realReceipt, syntheticReceipt] = (_b = (_a = this.receipts.get(receiptType)) === null || _a === void 0 ? void 0 : _a.get(userId)) !== null && _b !== void 0 ? _b : [null, null]; + if (ignoreSynthesized) { + return realReceipt; + } + return syntheticReceipt !== null && syntheticReceipt !== void 0 ? syntheticReceipt : realReceipt; + } + /** + * Get the ID of the event that a given user has read up to, or null if we + * have received no read receipts from them. + * @param userId - The user ID to get read receipt event ID for + * @param ignoreSynthesized - If true, return only receipts that have been + * sent by the server, not implicit ones generated + * by the JS SDK. + * @returns ID of the latest event that the given user has read, or null. + */ + getEventReadUpTo(userId, ignoreSynthesized = false) { + // XXX: This is very very ugly and I hope I won't have to ever add a new + // receipt type here again. IMHO this should be done by the server in + // some more intelligent manner or the client should just use timestamps + var _a, _b, _c, _d, _e, _f, _g; + const timelineSet = this.getUnfilteredTimelineSet(); + const publicReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, read_receipts_1.ReceiptType.Read); + const privateReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, read_receipts_1.ReceiptType.ReadPrivate); + // If we have both, compare them + let comparison; + if ((publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId) && (privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId)) { + comparison = timelineSet.compareEventOrdering(publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId, privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId); + } + // If we didn't get a comparison try to compare the ts of the receipts + if (!comparison && ((_a = publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.data) === null || _a === void 0 ? void 0 : _a.ts) && ((_b = privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.data) === null || _b === void 0 ? void 0 : _b.ts)) { + comparison = ((_c = publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.data) === null || _c === void 0 ? void 0 : _c.ts) - ((_d = privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.data) === null || _d === void 0 ? void 0 : _d.ts); + } + // The public receipt is more likely to drift out of date so the private + // one has precedence + if (!comparison) + return (_f = (_e = privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId) !== null && _e !== void 0 ? _e : publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId) !== null && _f !== void 0 ? _f : null; + // If public read receipt is older, return the private one + return (_g = (comparison < 0 ? privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId : publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId)) !== null && _g !== void 0 ? _g : null; + } + addReceiptToStructure(eventId, receiptType, userId, receipt, synthetic) { + var _a, _b, _c; + const receiptTypesMap = this.receipts.getOrCreate(receiptType); + let pair = receiptTypesMap.get(userId); + if (!pair) { + pair = [null, null]; + receiptTypesMap.set(userId, pair); + } + let existingReceipt = pair[ReceiptPairRealIndex]; + if (synthetic) { + existingReceipt = (_a = pair[ReceiptPairSyntheticIndex]) !== null && _a !== void 0 ? _a : pair[ReceiptPairRealIndex]; + } + if (existingReceipt) { + // we only want to add this receipt if we think it is later than the one we already have. + // This is managed server-side, but because we synthesize RRs locally we have to do it here too. + const ordering = this.getUnfilteredTimelineSet().compareEventOrdering(existingReceipt.eventId, eventId); + if (ordering !== null && ordering >= 0) { + return; + } + } + const wrappedReceipt = { + eventId, + data: receipt, + }; + const realReceipt = synthetic ? pair[ReceiptPairRealIndex] : wrappedReceipt; + const syntheticReceipt = synthetic ? wrappedReceipt : pair[ReceiptPairSyntheticIndex]; + let ordering = null; + if (realReceipt && syntheticReceipt) { + ordering = this.getUnfilteredTimelineSet().compareEventOrdering(realReceipt.eventId, syntheticReceipt.eventId); + } + const preferSynthetic = ordering === null || ordering < 0; + // we don't bother caching just real receipts by event ID as there's nothing that would read it. + // Take the current cached receipt before we overwrite the pair elements. + const cachedReceipt = (_b = pair[ReceiptPairSyntheticIndex]) !== null && _b !== void 0 ? _b : pair[ReceiptPairRealIndex]; + if (synthetic && preferSynthetic) { + pair[ReceiptPairSyntheticIndex] = wrappedReceipt; + } + else if (!synthetic) { + pair[ReceiptPairRealIndex] = wrappedReceipt; + if (!preferSynthetic) { + pair[ReceiptPairSyntheticIndex] = null; + } + } + const newCachedReceipt = (_c = pair[ReceiptPairSyntheticIndex]) !== null && _c !== void 0 ? _c : pair[ReceiptPairRealIndex]; + if (cachedReceipt === newCachedReceipt) + return; + // clean up any previous cache entry + if (cachedReceipt && this.receiptCacheByEventId.get(cachedReceipt.eventId)) { + const previousEventId = cachedReceipt.eventId; + // Remove the receipt we're about to clobber out of existence from the cache + this.receiptCacheByEventId.set(previousEventId, this.receiptCacheByEventId.get(previousEventId).filter((r) => { + return r.type !== receiptType || r.userId !== userId; + })); + if (this.receiptCacheByEventId.get(previousEventId).length < 1) { + this.receiptCacheByEventId.delete(previousEventId); // clean up the cache keys + } + } + // cache the new one + if (!this.receiptCacheByEventId.get(eventId)) { + this.receiptCacheByEventId.set(eventId, []); + } + this.receiptCacheByEventId.get(eventId).push({ + userId: userId, + type: receiptType, + data: receipt, + }); + } + /** + * Get a list of receipts for the given event. + * @param event - the event to get receipts for + * @returns A list of receipts with a userId, type and data keys or + * an empty list. + */ + getReceiptsForEvent(event) { + return this.receiptCacheByEventId.get(event.getId()) || []; + } + /** + * This issue should also be addressed on synapse's side and is tracked as part + * of https://github.com/matrix-org/synapse/issues/14837 + * + * Retrieves the read receipt for the logged in user and checks if it matches + * the last event in the room and whether that event originated from the logged + * in user. + * Under those conditions we can consider the context as read. This is useful + * because we never send read receipts against our own events + * @param userId - the logged in user + */ + fixupNotifications(userId) { + const receipt = this.getReadReceiptForUserId(userId, false); + const lastEvent = this.timeline[this.timeline.length - 1]; + if (lastEvent && (receipt === null || receipt === void 0 ? void 0 : receipt.eventId) === lastEvent.getId() && userId === lastEvent.getSender()) { + this.setUnread(room_1.NotificationCountType.Total, 0); + this.setUnread(room_1.NotificationCountType.Highlight, 0); + } + } + /** + * Add a temporary local-echo receipt to the room to reflect in the + * client the fact that we've sent one. + * @param userId - The user ID if the receipt sender + * @param e - The event that is to be acknowledged + * @param receiptType - The type of receipt + */ + addLocalEchoReceipt(userId, e, receiptType) { + this.addReceipt(synthesizeReceipt(userId, e, receiptType), true); + } + /** + * Get a list of user IDs who have read up to the given event. + * @param event - the event to get read receipts for. + * @returns A list of user IDs. + */ + getUsersReadUpTo(event) { + return this.getReceiptsForEvent(event) + .filter(function (receipt) { + return utils.isSupportedReceiptType(receipt.type); + }) + .map(function (receipt) { + return receipt.userId; + }); + } + /** + * Determines if the given user has read a particular event ID with the known + * history of the room. This is not a definitive check as it relies only on + * what is available to the room at the time of execution. + * @param userId - The user ID to check the read state of. + * @param eventId - The event ID to check if the user read. + * @returns True if the user has read the event, false otherwise. + */ + hasUserReadEvent(userId, eventId) { + var _a, _b; + const readUpToId = this.getEventReadUpTo(userId, false); + if (readUpToId === eventId) + return true; + if (((_a = this.timeline) === null || _a === void 0 ? void 0 : _a.length) && + this.timeline[this.timeline.length - 1].getSender() && + this.timeline[this.timeline.length - 1].getSender() === userId) { + // It doesn't matter where the event is in the timeline, the user has read + // it because they've sent the latest event. + return true; + } + for (let i = ((_b = this.timeline) === null || _b === void 0 ? void 0 : _b.length) - 1; i >= 0; --i) { + const ev = this.timeline[i]; + // If we encounter the target event first, the user hasn't read it + // however if we encounter the readUpToId first then the user has read + // it. These rules apply because we're iterating bottom-up. + if (ev.getId() === eventId) + return false; + if (ev.getId() === readUpToId) + return true; + } + // We don't know if the user has read it, so assume not. + return false; + } +} +exports.ReadReceipt = ReadReceipt; + +},{"../@types/event":306,"../@types/read_receipts":311,"../utils":416,"./event":383,"./room":392,"./typed-event-emitter":395}],387:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RelationsContainer = void 0; +const relations_1 = require("./relations"); +const event_1 = require("./event"); +class RelationsContainer { + constructor(client, room) { + this.client = client; + this.room = room; + // A tree of objects to access a set of related children for an event, as in: + // this.relations.get(parentEventId).get(relationType).get(relationEventType) + this.relations = new Map(); + } + /** + * Get a collection of child events to a given event in this timeline set. + * + * @param eventId - The ID of the event that you'd like to access child events for. + * For example, with annotations, this would be the ID of the event being annotated. + * @param relationType - The type of relationship involved, such as "m.annotation", "m.reference", "m.replace", etc. + * @param eventType - The relation event's type, such as "m.reaction", etc. + * @throws If `eventId, relationType or eventType` + * are not valid. + * + * @returns + * A container for relation events or undefined if there are no relation events for + * the relationType. + */ + getChildEventsForEvent(eventId, relationType, eventType) { + var _a, _b; + return (_b = (_a = this.relations.get(eventId)) === null || _a === void 0 ? void 0 : _a.get(relationType)) === null || _b === void 0 ? void 0 : _b.get(eventType); + } + getAllChildEventsForEvent(parentEventId) { + var _a; + const relationsForEvent = (_a = this.relations.get(parentEventId)) !== null && _a !== void 0 ? _a : new Map(); + const events = []; + for (const relationsRecord of relationsForEvent.values()) { + for (const relations of relationsRecord.values()) { + events.push(...relations.getRelations()); + } + } + return events; + } + /** + * Set an event as the target event if any Relations exist for it already. + * Child events can point to other child events as their parent, so this method may be + * called for events which are also logically child events. + * + * @param event - The event to check as relation target. + */ + aggregateParentEvent(event) { + const relationsForEvent = this.relations.get(event.getId()); + if (!relationsForEvent) + return; + for (const relationsWithRelType of relationsForEvent.values()) { + for (const relationsWithEventType of relationsWithRelType.values()) { + relationsWithEventType.setTargetEvent(event); + } + } + } + /** + * Add relation events to the relevant relation collection. + * + * @param event - The new child event to be aggregated. + * @param timelineSet - The event timeline set within which to search for the related event if any. + */ + aggregateChildEvent(event, timelineSet) { + var _a, _b, _c; + if (event.isRedacted() || event.status === event_1.EventStatus.CANCELLED) { + return; + } + const relation = event.getRelation(); + if (!relation) + return; + const onEventDecrypted = () => { + if (event.isDecryptionFailure()) { + // This could for example happen if the encryption keys are not yet available. + // The event may still be decrypted later. Register the listener again. + event.once(event_1.MatrixEventEvent.Decrypted, onEventDecrypted); + return; + } + this.aggregateChildEvent(event, timelineSet); + }; + // If the event is currently encrypted, wait until it has been decrypted. + if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { + event.once(event_1.MatrixEventEvent.Decrypted, onEventDecrypted); + return; + } + const { event_id: relatesToEventId, rel_type: relationType } = relation; + const eventType = event.getType(); + let relationsForEvent = this.relations.get(relatesToEventId); + if (!relationsForEvent) { + relationsForEvent = new Map(); + this.relations.set(relatesToEventId, relationsForEvent); + } + let relationsWithRelType = relationsForEvent.get(relationType); + if (!relationsWithRelType) { + relationsWithRelType = new Map(); + relationsForEvent.set(relationType, relationsWithRelType); + } + let relationsWithEventType = relationsWithRelType.get(eventType); + if (!relationsWithEventType) { + relationsWithEventType = new relations_1.Relations(relationType, eventType, this.client); + relationsWithRelType.set(eventType, relationsWithEventType); + const room = (_a = this.room) !== null && _a !== void 0 ? _a : timelineSet === null || timelineSet === void 0 ? void 0 : timelineSet.room; + const relatesToEvent = (_c = (_b = timelineSet === null || timelineSet === void 0 ? void 0 : timelineSet.findEventById(relatesToEventId)) !== null && _b !== void 0 ? _b : room === null || room === void 0 ? void 0 : room.findEventById(relatesToEventId)) !== null && _c !== void 0 ? _c : room === null || room === void 0 ? void 0 : room.getPendingEvent(relatesToEventId); + if (relatesToEvent) { + relationsWithEventType.setTargetEvent(relatesToEvent); + } + } + relationsWithEventType.addEvent(event); + } +} +exports.RelationsContainer = RelationsContainer; + +},{"./event":383,"./relations":388}],388:[function(require,module,exports){ +"use strict"; +/* +Copyright 2019, 2021, 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Relations = exports.RelationsEvent = void 0; +const event_1 = require("./event"); +const logger_1 = require("../logger"); +const event_2 = require("../@types/event"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const room_1 = require("./room"); +var RelationsEvent; +(function (RelationsEvent) { + RelationsEvent["Add"] = "Relations.add"; + RelationsEvent["Remove"] = "Relations.remove"; + RelationsEvent["Redaction"] = "Relations.redaction"; +})(RelationsEvent = exports.RelationsEvent || (exports.RelationsEvent = {})); +const matchesEventType = (eventType, targetEventType, altTargetEventTypes = []) => [targetEventType, ...altTargetEventTypes].includes(eventType); +/** + * A container for relation events that supports easy access to common ways of + * aggregating such events. Each instance holds events that of a single relation + * type and event type. All of the events also relate to the same original event. + * + * The typical way to get one of these containers is via + * EventTimelineSet#getRelationsForEvent. + */ +class Relations extends typed_event_emitter_1.TypedEventEmitter { + /** + * @param relationType - The type of relation involved, such as "m.annotation", "m.reference", "m.replace", etc. + * @param eventType - The relation event's type, such as "m.reaction", etc. + * @param client - The client which created this instance. For backwards compatibility also accepts a Room. + * @param altEventTypes - alt event types for relation events, for example to support unstable prefixed event types + */ + constructor(relationType, eventType, client, altEventTypes) { + super(); + this.relationType = relationType; + this.eventType = eventType; + this.altEventTypes = altEventTypes; + this.relationEventIds = new Set(); + this.relations = new Set(); + this.annotationsByKey = {}; + this.annotationsBySender = {}; + this.sortedAnnotationsByKey = []; + this.targetEvent = null; + this.creationEmitted = false; + /** + * Listens for event status changes to remove cancelled events. + * + * @param event - The event whose status has changed + * @param status - The new status + */ + this.onEventStatus = (event, status) => { + if (!event.isSending()) { + // Sending is done, so we don't need to listen anymore + event.removeListener(event_1.MatrixEventEvent.Status, this.onEventStatus); + return; + } + if (status !== event_1.EventStatus.CANCELLED) { + return; + } + // Event was cancelled, remove from the collection + event.removeListener(event_1.MatrixEventEvent.Status, this.onEventStatus); + this.removeEvent(event); + }; + /** + * For relations that have been redacted, we want to remove them from + * aggregation data sets and emit an update event. + * + * To do so, we listen for `Event.beforeRedaction`, which happens: + * - after the server accepted the redaction and remote echoed back to us + * - before the original event has been marked redacted in the client + * + * @param redactedEvent - The original relation event that is about to be redacted. + */ + this.onBeforeRedaction = (redactedEvent) => __awaiter(this, void 0, void 0, function* () { + if (!this.relations.has(redactedEvent)) { + return; + } + this.relations.delete(redactedEvent); + if (this.relationType === event_2.RelationType.Annotation) { + // Remove the redacted annotation from aggregation by key + this.removeAnnotationFromAggregation(redactedEvent); + } + else if (this.relationType === event_2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) { + const lastReplacement = yield this.getLastReplacement(); + this.targetEvent.makeReplaced(lastReplacement); + } + redactedEvent.removeListener(event_1.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); + this.emit(RelationsEvent.Redaction, redactedEvent); + }); + this.client = client instanceof room_1.Room ? client.client : client; + } + /** + * Add relation events to this collection. + * + * @param event - The new relation event to be added. + */ + addEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (this.relationEventIds.has(event.getId())) { + return; + } + const relation = event.getRelation(); + if (!relation) { + logger_1.logger.error("Event must have relation info"); + return; + } + const relationType = relation.rel_type; + const eventType = event.getType(); + if (this.relationType !== relationType || !matchesEventType(eventType, this.eventType, this.altEventTypes)) { + logger_1.logger.error("Event relation info doesn't match this container"); + return; + } + // If the event is in the process of being sent, listen for cancellation + // so we can remove the event from the collection. + if (event.isSending()) { + event.on(event_1.MatrixEventEvent.Status, this.onEventStatus); + } + this.relations.add(event); + this.relationEventIds.add(event.getId()); + if (this.relationType === event_2.RelationType.Annotation) { + this.addAnnotationToAggregation(event); + } + else if (this.relationType === event_2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) { + const lastReplacement = yield this.getLastReplacement(); + this.targetEvent.makeReplaced(lastReplacement); + } + event.on(event_1.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); + this.emit(RelationsEvent.Add, event); + this.maybeEmitCreated(); + }); + } + /** + * Remove relation event from this collection. + * + * @param event - The relation event to remove. + */ + removeEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.relations.has(event)) { + return; + } + this.relations.delete(event); + if (this.relationType === event_2.RelationType.Annotation) { + this.removeAnnotationFromAggregation(event); + } + else if (this.relationType === event_2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) { + const lastReplacement = yield this.getLastReplacement(); + this.targetEvent.makeReplaced(lastReplacement); + } + this.emit(RelationsEvent.Remove, event); + }); + } + /** + * Get all relation events in this collection. + * + * These are currently in the order of insertion to this collection, which + * won't match timeline order in the case of scrollback. + * TODO: Tweak `addEvent` to insert correctly for scrollback. + * + * Relation events in insertion order. + */ + getRelations() { + return [...this.relations]; + } + addAnnotationToAggregation(event) { + var _a; + const { key } = (_a = event.getRelation()) !== null && _a !== void 0 ? _a : {}; + if (!key) + return; + let eventsForKey = this.annotationsByKey[key]; + if (!eventsForKey) { + eventsForKey = this.annotationsByKey[key] = new Set(); + this.sortedAnnotationsByKey.push([key, eventsForKey]); + } + // Add the new event to the set for this key + eventsForKey.add(event); + // Re-sort the [key, events] pairs in descending order of event count + this.sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.size - aEvents.size; + }); + const sender = event.getSender(); + let eventsFromSender = this.annotationsBySender[sender]; + if (!eventsFromSender) { + eventsFromSender = this.annotationsBySender[sender] = new Set(); + } + // Add the new event to the set for this sender + eventsFromSender.add(event); + } + removeAnnotationFromAggregation(event) { + var _a; + const { key } = (_a = event.getRelation()) !== null && _a !== void 0 ? _a : {}; + if (!key) + return; + const eventsForKey = this.annotationsByKey[key]; + if (eventsForKey) { + eventsForKey.delete(event); + // Re-sort the [key, events] pairs in descending order of event count + this.sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.size - aEvents.size; + }); + } + const sender = event.getSender(); + const eventsFromSender = this.annotationsBySender[sender]; + if (eventsFromSender) { + eventsFromSender.delete(event); + } + } + /** + * Get all events in this collection grouped by key and sorted by descending + * event count in each group. + * + * This is currently only supported for the annotation relation type. + * + * An array of [key, events] pairs sorted by descending event count. + * The events are stored in a Set (which preserves insertion order). + */ + getSortedAnnotationsByKey() { + if (this.relationType !== event_2.RelationType.Annotation) { + // Other relation types are not grouped currently. + return null; + } + return this.sortedAnnotationsByKey; + } + /** + * Get all events in this collection grouped by sender. + * + * This is currently only supported for the annotation relation type. + * + * An object with each relation sender as a key and the matching Set of + * events for that sender as a value. + */ + getAnnotationsBySender() { + if (this.relationType !== event_2.RelationType.Annotation) { + // Other relation types are not grouped currently. + return null; + } + return this.annotationsBySender; + } + /** + * Returns the most recent (and allowed) m.replace relation, if any. + * + * This is currently only supported for the m.replace relation type, + * once the target event is known, see `addEvent`. + */ + getLastReplacement() { + return __awaiter(this, void 0, void 0, function* () { + if (this.relationType !== event_2.RelationType.Replace) { + // Aggregating on last only makes sense for this relation type + return null; + } + if (!this.targetEvent) { + // Don't know which replacements to accept yet. + // This method shouldn't be called before the original + // event is known anyway. + return null; + } + // the all-knowning server tells us that the event at some point had + // this timestamp for its replacement, so any following replacement should definitely not be less + const replaceRelation = this.targetEvent.getServerAggregatedRelation(event_2.RelationType.Replace); + const minTs = replaceRelation === null || replaceRelation === void 0 ? void 0 : replaceRelation.origin_server_ts; + const lastReplacement = this.getRelations().reduce((last, event) => { + if (event.getSender() !== this.targetEvent.getSender()) { + return last; + } + if (minTs && minTs > event.getTs()) { + return last; + } + if (last && last.getTs() > event.getTs()) { + return last; + } + return event; + }, null); + if ((lastReplacement === null || lastReplacement === void 0 ? void 0 : lastReplacement.shouldAttemptDecryption()) && this.client.isCryptoEnabled()) { + yield lastReplacement.attemptDecryption(this.client.crypto); + } + else if (lastReplacement === null || lastReplacement === void 0 ? void 0 : lastReplacement.isBeingDecrypted()) { + yield lastReplacement.getDecryptionPromise(); + } + return lastReplacement; + }); + } + /* + * @param targetEvent - the event the relations are related to. + */ + setTargetEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (this.targetEvent) { + return; + } + this.targetEvent = event; + if (this.relationType === event_2.RelationType.Replace && !this.targetEvent.isState()) { + const replacement = yield this.getLastReplacement(); + // this is the initial update, so only call it if we already have something + // to not emit Event.replaced needlessly + if (replacement) { + this.targetEvent.makeReplaced(replacement); + } + } + this.maybeEmitCreated(); + }); + } + maybeEmitCreated() { + if (this.creationEmitted) { + return; + } + // Only emit we're "created" once we have a target event instance _and_ + // at least one related event. + if (!this.targetEvent || !this.relations.size) { + return; + } + this.creationEmitted = true; + this.targetEvent.emit(event_1.MatrixEventEvent.RelationsCreated, this.relationType, this.eventType); + } +} +exports.Relations = Relations; + +},{"../@types/event":306,"../logger":374,"./event":383,"./room":392,"./typed-event-emitter":395}],389:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomMember = exports.RoomMemberEvent = void 0; +const content_repo_1 = require("../content-repo"); +const utils = __importStar(require("../utils")); +const logger_1 = require("../logger"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const event_1 = require("../@types/event"); +var RoomMemberEvent; +(function (RoomMemberEvent) { + RoomMemberEvent["Membership"] = "RoomMember.membership"; + RoomMemberEvent["Name"] = "RoomMember.name"; + RoomMemberEvent["PowerLevel"] = "RoomMember.powerLevel"; + RoomMemberEvent["Typing"] = "RoomMember.typing"; +})(RoomMemberEvent = exports.RoomMemberEvent || (exports.RoomMemberEvent = {})); +class RoomMember extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct a new room member. + * + * @param roomId - The room ID of the member. + * @param userId - The user ID of the member. + */ + constructor(roomId, userId) { + super(); + this.roomId = roomId; + this.userId = userId; + this._isOutOfBand = false; + this.modified = -1; + this.requestedProfileInfo = false; // used by sync.ts + // XXX these should be read-only + /** + * True if the room member is currently typing. + */ + this.typing = false; + /** + * The power level for this room member. + */ + this.powerLevel = 0; + /** + * The normalised power level (0-100) for this room member. + */ + this.powerLevelNorm = 0; + /** + * True if the member's name is disambiguated. + */ + this.disambiguate = false; + /** + * The events describing this RoomMember. + */ + this.events = {}; + this.name = userId; + this.rawDisplayName = userId; + this.updateModifiedTime(); + } + /** + * Mark the member as coming from a channel that is not sync + */ + markOutOfBand() { + this._isOutOfBand = true; + } + /** + * @returns does the member come from a channel that is not sync? + * This is used to store the member seperately + * from the sync state so it available across browser sessions. + */ + isOutOfBand() { + return this._isOutOfBand; + } + /** + * Update this room member's membership event. May fire "RoomMember.name" if + * this event updates this member's name. + * @param event - The `m.room.member` event + * @param roomState - Optional. The room state to take into account + * when calculating (e.g. for disambiguating users with the same name). + * + * @remarks + * Fires {@link RoomMemberEvent.Name} + * Fires {@link RoomMemberEvent.Membership} + */ + setMembershipEvent(event, roomState) { + var _a, _b; + const displayName = (_a = event.getDirectionalContent().displayname) !== null && _a !== void 0 ? _a : ""; + if (event.getType() !== event_1.EventType.RoomMember) { + return; + } + this._isOutOfBand = false; + this.events.member = event; + const oldMembership = this.membership; + this.membership = event.getDirectionalContent().membership; + if (this.membership === undefined) { + // logging to diagnose https://github.com/vector-im/element-web/issues/20962 + // (logs event content, although only of membership events) + logger_1.logger.trace(`membership event with membership undefined (forwardLooking: ${event.forwardLooking})!`, event.getContent(), `prevcontent is `, event.getPrevContent()); + } + this.disambiguate = shouldDisambiguate(this.userId, displayName, roomState); + const oldName = this.name; + this.name = calculateDisplayName(this.userId, displayName, this.disambiguate); + // not quite raw: we strip direction override chars so it can safely be inserted into + // blocks of text without breaking the text direction + this.rawDisplayName = utils.removeDirectionOverrideChars((_b = event.getDirectionalContent().displayname) !== null && _b !== void 0 ? _b : ""); + if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) { + this.rawDisplayName = this.userId; + } + if (oldMembership !== this.membership) { + this.updateModifiedTime(); + this.emit(RoomMemberEvent.Membership, event, this, oldMembership); + } + if (oldName !== this.name) { + this.updateModifiedTime(); + this.emit(RoomMemberEvent.Name, event, this, oldName); + } + } + /** + * Update this room member's power level event. May fire + * "RoomMember.powerLevel" if this event updates this member's power levels. + * @param powerLevelEvent - The `m.room.power_levels` event + * + * @remarks + * Fires {@link RoomMemberEvent.PowerLevel} + */ + setPowerLevelEvent(powerLevelEvent) { + if (powerLevelEvent.getType() !== event_1.EventType.RoomPowerLevels || powerLevelEvent.getStateKey() !== "") { + return; + } + const evContent = powerLevelEvent.getDirectionalContent(); + let maxLevel = evContent.users_default || 0; + const users = evContent.users || {}; + Object.values(users).forEach((lvl) => { + maxLevel = Math.max(maxLevel, lvl); + }); + const oldPowerLevel = this.powerLevel; + const oldPowerLevelNorm = this.powerLevelNorm; + if (users[this.userId] !== undefined && Number.isInteger(users[this.userId])) { + this.powerLevel = users[this.userId]; + } + else if (evContent.users_default !== undefined) { + this.powerLevel = evContent.users_default; + } + else { + this.powerLevel = 0; + } + this.powerLevelNorm = 0; + if (maxLevel > 0) { + this.powerLevelNorm = (this.powerLevel * 100) / maxLevel; + } + // emit for changes in powerLevelNorm as well (since the app will need to + // redraw everyone's level if the max has changed) + if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) { + this.updateModifiedTime(); + this.emit(RoomMemberEvent.PowerLevel, powerLevelEvent, this); + } + } + /** + * Update this room member's typing event. May fire "RoomMember.typing" if + * this event changes this member's typing state. + * @param event - The typing event + * + * @remarks + * Fires {@link RoomMemberEvent.Typing} + */ + setTypingEvent(event) { + if (event.getType() !== "m.typing") { + return; + } + const oldTyping = this.typing; + this.typing = false; + const typingList = event.getContent().user_ids; + if (!Array.isArray(typingList)) { + // malformed event :/ bail early. TODO: whine? + return; + } + if (typingList.indexOf(this.userId) !== -1) { + this.typing = true; + } + if (oldTyping !== this.typing) { + this.updateModifiedTime(); + this.emit(RoomMemberEvent.Typing, event, this); + } + } + /** + * Update the last modified time to the current time. + */ + updateModifiedTime() { + this.modified = Date.now(); + } + /** + * Get the timestamp when this RoomMember was last updated. This timestamp is + * updated when properties on this RoomMember are updated. + * It is updated before firing events. + * @returns The timestamp + */ + getLastModifiedTime() { + return this.modified; + } + isKicked() { + return (this.membership === "leave" && + this.events.member !== undefined && + this.events.member.getSender() !== this.events.member.getStateKey()); + } + /** + * If this member was invited with the is_direct flag set, return + * the user that invited this member + * @returns user id of the inviter + */ + getDMInviter() { + // when not available because that room state hasn't been loaded in, + // we don't really know, but more likely to not be a direct chat + if (this.events.member) { + // TODO: persist the is_direct flag on the member as more member events + // come in caused by displayName changes. + // the is_direct flag is set on the invite member event. + // This is copied on the prev_content section of the join member event + // when the invite is accepted. + const memberEvent = this.events.member; + let memberContent = memberEvent.getContent(); + let inviteSender = memberEvent.getSender(); + if (memberContent.membership === "join") { + memberContent = memberEvent.getPrevContent(); + inviteSender = memberEvent.getUnsigned().prev_sender; + } + if (memberContent.membership === "invite" && memberContent.is_direct) { + return inviteSender; + } + } + } + /** + * Get the avatar URL for a room member. + * @param baseUrl - The base homeserver URL See + * {@link MatrixClient#getHomeserverUrl}. + * @param width - The desired width of the thumbnail. + * @param height - The desired height of the thumbnail. + * @param resizeMethod - The thumbnail resize method to use, either + * "crop" or "scale". + * @param allowDefault - (optional) Passing false causes this method to + * return null if the user has no avatar image. Otherwise, a default image URL + * will be returned. Default: true. (Deprecated) + * @param allowDirectLinks - (optional) If true, the avatar URL will be + * returned even if it is a direct hyperlink rather than a matrix content URL. + * If false, any non-matrix content URLs will be ignored. Setting this option to + * true will expose URLs that, if fetched, will leak information about the user + * to anyone who they share a room with. + * @returns the avatar URL or null. + */ + getAvatarUrl(baseUrl, width, height, resizeMethod, allowDefault = true, allowDirectLinks) { + const rawUrl = this.getMxcAvatarUrl(); + if (!rawUrl && !allowDefault) { + return null; + } + const httpUrl = (0, content_repo_1.getHttpUriForMxc)(baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks); + if (httpUrl) { + return httpUrl; + } + return null; + } + /** + * get the mxc avatar url, either from a state event, or from a lazily loaded member + * @returns the mxc avatar url + */ + getMxcAvatarUrl() { + if (this.events.member) { + return this.events.member.getDirectionalContent().avatar_url; + } + else if (this.user) { + return this.user.avatarUrl; + } + } +} +exports.RoomMember = RoomMember; +const MXID_PATTERN = /@.+:.+/; +const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/; +function shouldDisambiguate(selfUserId, displayName, roomState) { + if (!displayName || displayName === selfUserId) + return false; + // First check if the displayname is something we consider truthy + // after stripping it of zero width characters and padding spaces + if (!utils.removeHiddenChars(displayName)) + return false; + if (!roomState) + return false; + // Next check if the name contains something that look like a mxid + // If it does, it may be someone trying to impersonate someone else + // Show full mxid in this case + if (MXID_PATTERN.test(displayName)) + return true; + // Also show mxid if the display name contains any LTR/RTL characters as these + // make it very difficult for us to find similar *looking* display names + // E.g "Mark" could be cloned by writing "kraM" but in RTL. + if (LTR_RTL_PATTERN.test(displayName)) + return true; + // Also show mxid if there are other people with the same or similar + // displayname, after hidden character removal. + const userIds = roomState.getUserIdsWithDisplayName(displayName); + if (userIds.some((u) => u !== selfUserId)) + return true; + return false; +} +function calculateDisplayName(selfUserId, displayName, disambiguate) { + if (!displayName || displayName === selfUserId) + return selfUserId; + if (disambiguate) + return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")"; + // First check if the displayname is something we consider truthy + // after stripping it of zero width characters and padding spaces + if (!utils.removeHiddenChars(displayName)) + return selfUserId; + // We always strip the direction override characters (LRO and RLO). + // These override the text direction for all subsequent characters + // in the paragraph so if display names contained these, they'd + // need to be wrapped in something to prevent this from leaking out + // (which we can do in HTML but not text) or we'd need to add + // control characters to the string to reset any overrides (eg. + // adding PDF characters at the end). As far as we can see, + // there should be no reason these would be necessary - rtl display + // names should flip into the correct direction automatically based on + // the characters, and you can still embed rtl in ltr or vice versa + // with the embed chars or marker chars. + return utils.removeDirectionOverrideChars(displayName); +} + +},{"../@types/event":306,"../content-repo":323,"../logger":374,"../utils":416,"./typed-event-emitter":395}],390:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomState = exports.RoomStateEvent = void 0; +const room_member_1 = require("./room-member"); +const logger_1 = require("../logger"); +const utils = __importStar(require("../utils")); +const event_1 = require("../@types/event"); +const event_2 = require("./event"); +const partials_1 = require("../@types/partials"); +const typed_event_emitter_1 = require("./typed-event-emitter"); +const beacon_1 = require("./beacon"); +const ReEmitter_1 = require("../ReEmitter"); +const beacon_2 = require("../@types/beacon"); +// possible statuses for out-of-band member loading +var OobStatus; +(function (OobStatus) { + OobStatus[OobStatus["NotStarted"] = 0] = "NotStarted"; + OobStatus[OobStatus["InProgress"] = 1] = "InProgress"; + OobStatus[OobStatus["Finished"] = 2] = "Finished"; +})(OobStatus || (OobStatus = {})); +var RoomStateEvent; +(function (RoomStateEvent) { + RoomStateEvent["Events"] = "RoomState.events"; + RoomStateEvent["Members"] = "RoomState.members"; + RoomStateEvent["NewMember"] = "RoomState.newMember"; + RoomStateEvent["Update"] = "RoomState.update"; + RoomStateEvent["BeaconLiveness"] = "RoomState.BeaconLiveness"; + RoomStateEvent["Marker"] = "RoomState.Marker"; +})(RoomStateEvent = exports.RoomStateEvent || (exports.RoomStateEvent = {})); +class RoomState extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct room state. + * + * Room State represents the state of the room at a given point. + * It can be mutated by adding state events to it. + * There are two types of room member associated with a state event: + * normal member objects (accessed via getMember/getMembers) which mutate + * with the state to represent the current state of that room/user, e.g. + * the object returned by `getMember('@bob:example.com')` will mutate to + * get a different display name if Bob later changes his display name + * in the room. + * There are also 'sentinel' members (accessed via getSentinelMember). + * These also represent the state of room members at the point in time + * represented by the RoomState object, but unlike objects from getMember, + * sentinel objects will always represent the room state as at the time + * getSentinelMember was called, so if Bob subsequently changes his display + * name, a room member object previously acquired with getSentinelMember + * will still have his old display name. Calling getSentinelMember again + * after the display name change will return a new RoomMember object + * with Bob's new display name. + * + * @param roomId - Optional. The ID of the room which has this state. + * If none is specified it just tracks paginationTokens, useful for notifTimelineSet + * @param oobMemberFlags - Optional. The state of loading out of bound members. + * As the timeline might get reset while they are loading, this state needs to be inherited + * and shared when the room state is cloned for the new timeline. + * This should only be passed from clone. + */ + constructor(roomId, oobMemberFlags = { status: OobStatus.NotStarted }) { + super(); + this.roomId = roomId; + this.oobMemberFlags = oobMemberFlags; + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + this.sentinels = {}; // userId: RoomMember + // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys) + this.displayNameToUserIds = new Map(); + this.userIdsToDisplayNames = {}; + this.tokenToInvite = {}; // 3pid invite state_key to m.room.member invite + this.joinedMemberCount = null; // cache of the number of joined members + // joined members count from summary api + // once set, we know the server supports the summary api + // and we should only trust that + // we could also only trust that before OOB members + // are loaded but doesn't seem worth the hassle atm + this.summaryJoinedMemberCount = null; + // same for invited member count + this.invitedMemberCount = null; + this.summaryInvitedMemberCount = null; + this.modified = -1; + // XXX: Should be read-only + // The room member dictionary, keyed on the user's ID. + this.members = {}; // userId: RoomMember + // The state events dictionary, keyed on the event type and then the state_key value. + this.events = new Map(); // Map> + // The pagination token for this state. + this.paginationToken = null; + this.beacons = new Map(); + this._liveBeaconIds = []; + this.updateModifiedTime(); + } + /** + * Returns the number of joined members in this room + * This method caches the result. + * @returns The number of members in this room whose membership is 'join' + */ + getJoinedMemberCount() { + if (this.summaryJoinedMemberCount !== null) { + return this.summaryJoinedMemberCount; + } + if (this.joinedMemberCount === null) { + this.joinedMemberCount = this.getMembers().reduce((count, m) => { + return m.membership === "join" ? count + 1 : count; + }, 0); + } + return this.joinedMemberCount; + } + /** + * Set the joined member count explicitly (like from summary part of the sync response) + * @param count - the amount of joined members + */ + setJoinedMemberCount(count) { + this.summaryJoinedMemberCount = count; + } + /** + * Returns the number of invited members in this room + * @returns The number of members in this room whose membership is 'invite' + */ + getInvitedMemberCount() { + if (this.summaryInvitedMemberCount !== null) { + return this.summaryInvitedMemberCount; + } + if (this.invitedMemberCount === null) { + this.invitedMemberCount = this.getMembers().reduce((count, m) => { + return m.membership === "invite" ? count + 1 : count; + }, 0); + } + return this.invitedMemberCount; + } + /** + * Set the amount of invited members in this room + * @param count - the amount of invited members + */ + setInvitedMemberCount(count) { + this.summaryInvitedMemberCount = count; + } + /** + * Get all RoomMembers in this room. + * @returns A list of RoomMembers. + */ + getMembers() { + return Object.values(this.members); + } + /** + * Get all RoomMembers in this room, excluding the user IDs provided. + * @param excludedIds - The user IDs to exclude. + * @returns A list of RoomMembers. + */ + getMembersExcept(excludedIds) { + return this.getMembers().filter((m) => !excludedIds.includes(m.userId)); + } + /** + * Get a room member by their user ID. + * @param userId - The room member's user ID. + * @returns The member or null if they do not exist. + */ + getMember(userId) { + return this.members[userId] || null; + } + /** + * Get a room member whose properties will not change with this room state. You + * typically want this if you want to attach a RoomMember to a MatrixEvent which + * may no longer be represented correctly by Room.currentState or Room.oldState. + * The term 'sentinel' refers to the fact that this RoomMember is an unchanging + * guardian for state at this particular point in time. + * @param userId - The room member's user ID. + * @returns The member or null if they do not exist. + */ + getSentinelMember(userId) { + if (!userId) + return null; + let sentinel = this.sentinels[userId]; + if (sentinel === undefined) { + sentinel = new room_member_1.RoomMember(this.roomId, userId); + const member = this.members[userId]; + if (member === null || member === void 0 ? void 0 : member.events.member) { + sentinel.setMembershipEvent(member.events.member, this); + } + this.sentinels[userId] = sentinel; + } + return sentinel; + } + getStateEvents(eventType, stateKey) { + if (!this.events.has(eventType)) { + // no match + return stateKey === undefined ? [] : null; + } + if (stateKey === undefined) { + // return all values + return Array.from(this.events.get(eventType).values()); + } + const event = this.events.get(eventType).get(stateKey); + return event ? event : null; + } + get hasLiveBeacons() { + var _a; + return !!((_a = this.liveBeaconIds) === null || _a === void 0 ? void 0 : _a.length); + } + get liveBeaconIds() { + return this._liveBeaconIds; + } + /** + * Creates a copy of this room state so that mutations to either won't affect the other. + * @returns the copy of the room state + */ + clone() { + const copy = new RoomState(this.roomId, this.oobMemberFlags); + // Ugly hack: because setStateEvents will mark + // members as susperseding future out of bound members + // if loading is in progress (through oobMemberFlags) + // since these are not new members, we're merely copying them + // set the status to not started + // after copying, we set back the status + const status = this.oobMemberFlags.status; + this.oobMemberFlags.status = OobStatus.NotStarted; + Array.from(this.events.values()).forEach((eventsByStateKey) => { + copy.setStateEvents(Array.from(eventsByStateKey.values())); + }); + // Ugly hack: see above + this.oobMemberFlags.status = status; + if (this.summaryInvitedMemberCount !== null) { + copy.setInvitedMemberCount(this.getInvitedMemberCount()); + } + if (this.summaryJoinedMemberCount !== null) { + copy.setJoinedMemberCount(this.getJoinedMemberCount()); + } + // copy out of band flags if needed + if (this.oobMemberFlags.status == OobStatus.Finished) { + // copy markOutOfBand flags + this.getMembers().forEach((member) => { + var _a; + if (member.isOutOfBand()) { + (_a = copy.getMember(member.userId)) === null || _a === void 0 ? void 0 : _a.markOutOfBand(); + } + }); + } + return copy; + } + /** + * Add previously unknown state events. + * When lazy loading members while back-paginating, + * the relevant room state for the timeline chunk at the end + * of the chunk can be set with this method. + * @param events - state events to prepend + */ + setUnknownStateEvents(events) { + const unknownStateEvents = events.filter((event) => { + return !this.events.has(event.getType()) || !this.events.get(event.getType()).has(event.getStateKey()); + }); + this.setStateEvents(unknownStateEvents); + } + /** + * Add an array of one or more state MatrixEvents, overwriting any existing + * state with the same `{type, stateKey}` tuple. Will fire "RoomState.events" + * for every event added. May fire "RoomState.members" if there are + * `m.room.member` events. May fire "RoomStateEvent.Marker" if there are + * `UNSTABLE_MSC2716_MARKER` events. + * @param stateEvents - a list of state events for this room. + * + * @remarks + * Fires {@link RoomStateEvent.Members} + * Fires {@link RoomStateEvent.NewMember} + * Fires {@link RoomStateEvent.Events} + * Fires {@link RoomStateEvent.Marker} + */ + setStateEvents(stateEvents, markerFoundOptions) { + this.updateModifiedTime(); + // update the core event dict + stateEvents.forEach((event) => { + var _a; + if (event.getRoomId() !== this.roomId || !event.isState()) + return; + if (beacon_2.M_BEACON_INFO.matches(event.getType())) { + this.setBeacon(event); + } + const lastStateEvent = this.getStateEventMatching(event); + this.setStateEvent(event); + if (event.getType() === event_1.EventType.RoomMember) { + this.updateDisplayNameCache(event.getStateKey(), (_a = event.getContent().displayname) !== null && _a !== void 0 ? _a : ""); + this.updateThirdPartyTokenCache(event); + } + this.emit(RoomStateEvent.Events, event, this, lastStateEvent); + }); + this.onBeaconLivenessChange(); + // update higher level data structures. This needs to be done AFTER the + // core event dict as these structures may depend on other state events in + // the given array (e.g. disambiguating display names in one go to do both + // clashing names rather than progressively which only catches 1 of them). + stateEvents.forEach((event) => { + if (event.getRoomId() !== this.roomId || !event.isState()) + return; + if (event.getType() === event_1.EventType.RoomMember) { + const userId = event.getStateKey(); + // leave events apparently elide the displayname or avatar_url, + // so let's fake one up so that we don't leak user ids + // into the timeline + if (event.getContent().membership === "leave" || event.getContent().membership === "ban") { + event.getContent().avatar_url = event.getContent().avatar_url || event.getPrevContent().avatar_url; + event.getContent().displayname = + event.getContent().displayname || event.getPrevContent().displayname; + } + const member = this.getOrCreateMember(userId, event); + member.setMembershipEvent(event, this); + this.updateMember(member); + this.emit(RoomStateEvent.Members, event, this, member); + } + else if (event.getType() === event_1.EventType.RoomPowerLevels) { + // events with unknown state keys should be ignored + // and should not aggregate onto members power levels + if (event.getStateKey() !== "") { + return; + } + const members = Object.values(this.members); + members.forEach((member) => { + // We only propagate `RoomState.members` event if the + // power levels has been changed + // large room suffer from large re-rendering especially when not needed + const oldLastModified = member.getLastModifiedTime(); + member.setPowerLevelEvent(event); + if (oldLastModified !== member.getLastModifiedTime()) { + this.emit(RoomStateEvent.Members, event, this, member); + } + }); + // assume all our sentinels are now out-of-date + this.sentinels = {}; + } + else if (event_1.UNSTABLE_MSC2716_MARKER.matches(event.getType())) { + this.emit(RoomStateEvent.Marker, event, markerFoundOptions); + } + }); + this.emit(RoomStateEvent.Update, this); + } + processBeaconEvents(events, matrixClient) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (!events.length || + // discard locations if we have no beacons + !this.beacons.size) { + return; + } + const beaconByEventIdDict = [...this.beacons.values()].reduce((dict, beacon) => { + dict[beacon.beaconInfoId] = beacon; + return dict; + }, {}); + const processBeaconRelation = (beaconInfoEventId, event) => { + if (!beacon_2.M_BEACON.matches(event.getType())) { + return; + } + const beacon = beaconByEventIdDict[beaconInfoEventId]; + if (beacon) { + beacon.addLocations([event]); + } + }; + for (const event of events) { + const relatedToEventId = (_a = event.getRelation()) === null || _a === void 0 ? void 0 : _a.event_id; + // not related to a beacon we know about; discard + if (!relatedToEventId || !beaconByEventIdDict[relatedToEventId]) + return; + if (!beacon_2.M_BEACON.matches(event.getType()) && !event.isEncrypted()) + return; + try { + yield matrixClient.decryptEventIfNeeded(event); + processBeaconRelation(relatedToEventId, event); + } + catch (_b) { + if (event.isDecryptionFailure()) { + // add an event listener for once the event is decrypted. + event.once(event_2.MatrixEventEvent.Decrypted, () => __awaiter(this, void 0, void 0, function* () { + processBeaconRelation(relatedToEventId, event); + })); + } + } + } + }); + } + /** + * Looks up a member by the given userId, and if it doesn't exist, + * create it and emit the `RoomState.newMember` event. + * This method makes sure the member is added to the members dictionary + * before emitting, as this is done from setStateEvents and setOutOfBandMember. + * @param userId - the id of the user to look up + * @param event - the membership event for the (new) member. Used to emit. + * @returns the member, existing or newly created. + * + * @remarks + * Fires {@link RoomStateEvent.NewMember} + */ + getOrCreateMember(userId, event) { + let member = this.members[userId]; + if (!member) { + member = new room_member_1.RoomMember(this.roomId, userId); + // add member to members before emitting any events, + // as event handlers often lookup the member + this.members[userId] = member; + this.emit(RoomStateEvent.NewMember, event, this, member); + } + return member; + } + setStateEvent(event) { + if (!this.events.has(event.getType())) { + this.events.set(event.getType(), new Map()); + } + this.events.get(event.getType()).set(event.getStateKey(), event); + } + /** + * @experimental + */ + setBeacon(event) { + var _a; + const beaconIdentifier = (0, beacon_1.getBeaconInfoIdentifier)(event); + if (this.beacons.has(beaconIdentifier)) { + const beacon = this.beacons.get(beaconIdentifier); + if (event.isRedacted()) { + if (beacon.beaconInfoId === ((_a = event.getRedactionEvent()) === null || _a === void 0 ? void 0 : _a.redacts)) { + beacon.destroy(); + this.beacons.delete(beaconIdentifier); + } + return; + } + return beacon.update(event); + } + if (event.isRedacted()) { + return; + } + const beacon = new beacon_1.Beacon(event); + this.reEmitter.reEmit(beacon, [ + beacon_1.BeaconEvent.New, + beacon_1.BeaconEvent.Update, + beacon_1.BeaconEvent.Destroy, + beacon_1.BeaconEvent.LivenessChange, + ]); + this.emit(beacon_1.BeaconEvent.New, event, beacon); + beacon.on(beacon_1.BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this)); + beacon.on(beacon_1.BeaconEvent.Destroy, this.onBeaconLivenessChange.bind(this)); + this.beacons.set(beacon.identifier, beacon); + } + /** + * @experimental + * Check liveness of room beacons + * emit RoomStateEvent.BeaconLiveness event + */ + onBeaconLivenessChange() { + this._liveBeaconIds = Array.from(this.beacons.values()) + .filter((beacon) => beacon.isLive) + .map((beacon) => beacon.identifier); + this.emit(RoomStateEvent.BeaconLiveness, this, this.hasLiveBeacons); + } + getStateEventMatching(event) { + var _a, _b; + return (_b = (_a = this.events.get(event.getType())) === null || _a === void 0 ? void 0 : _a.get(event.getStateKey())) !== null && _b !== void 0 ? _b : null; + } + updateMember(member) { + // this member may have a power level already, so set it. + const pwrLvlEvent = this.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + if (pwrLvlEvent) { + member.setPowerLevelEvent(pwrLvlEvent); + } + // blow away the sentinel which is now outdated + delete this.sentinels[member.userId]; + this.members[member.userId] = member; + this.joinedMemberCount = null; + this.invitedMemberCount = null; + } + /** + * Get the out-of-band members loading state, whether loading is needed or not. + * Note that loading might be in progress and hence isn't needed. + * @returns whether or not the members of this room need to be loaded + */ + needsOutOfBandMembers() { + return this.oobMemberFlags.status === OobStatus.NotStarted; + } + /** + * Check if loading of out-of-band-members has completed + * + * @returns true if the full membership list of this room has been loaded. False if it is not started or is in + * progress. + */ + outOfBandMembersReady() { + return this.oobMemberFlags.status === OobStatus.Finished; + } + /** + * Mark this room state as waiting for out-of-band members, + * ensuring it doesn't ask for them to be requested again + * through needsOutOfBandMembers + */ + markOutOfBandMembersStarted() { + if (this.oobMemberFlags.status !== OobStatus.NotStarted) { + return; + } + this.oobMemberFlags.status = OobStatus.InProgress; + } + /** + * Mark this room state as having failed to fetch out-of-band members + */ + markOutOfBandMembersFailed() { + if (this.oobMemberFlags.status !== OobStatus.InProgress) { + return; + } + this.oobMemberFlags.status = OobStatus.NotStarted; + } + /** + * Clears the loaded out-of-band members + */ + clearOutOfBandMembers() { + let count = 0; + Object.keys(this.members).forEach((userId) => { + const member = this.members[userId]; + if (member.isOutOfBand()) { + ++count; + delete this.members[userId]; + } + }); + logger_1.logger.log(`LL: RoomState removed ${count} members...`); + this.oobMemberFlags.status = OobStatus.NotStarted; + } + /** + * Sets the loaded out-of-band members. + * @param stateEvents - array of membership state events + */ + setOutOfBandMembers(stateEvents) { + logger_1.logger.log(`LL: RoomState about to set ${stateEvents.length} OOB members ...`); + if (this.oobMemberFlags.status !== OobStatus.InProgress) { + return; + } + logger_1.logger.log(`LL: RoomState put in finished state ...`); + this.oobMemberFlags.status = OobStatus.Finished; + stateEvents.forEach((e) => this.setOutOfBandMember(e)); + this.emit(RoomStateEvent.Update, this); + } + /** + * Sets a single out of band member, used by both setOutOfBandMembers and clone + * @param stateEvent - membership state event + */ + setOutOfBandMember(stateEvent) { + if (stateEvent.getType() !== event_1.EventType.RoomMember) { + return; + } + const userId = stateEvent.getStateKey(); + const existingMember = this.getMember(userId); + // never replace members received as part of the sync + if (existingMember && !existingMember.isOutOfBand()) { + return; + } + const member = this.getOrCreateMember(userId, stateEvent); + member.setMembershipEvent(stateEvent, this); + // needed to know which members need to be stored seperately + // as they are not part of the sync accumulator + // this is cleared by setMembershipEvent so when it's updated through /sync + member.markOutOfBand(); + this.updateDisplayNameCache(member.userId, member.name); + this.setStateEvent(stateEvent); + this.updateMember(member); + this.emit(RoomStateEvent.Members, stateEvent, this, member); + } + /** + * Set the current typing event for this room. + * @param event - The typing event + */ + setTypingEvent(event) { + Object.values(this.members).forEach(function (member) { + member.setTypingEvent(event); + }); + } + /** + * Get the m.room.member event which has the given third party invite token. + * + * @param token - The token + * @returns The m.room.member event or null + */ + getInviteForThreePidToken(token) { + return this.tokenToInvite[token] || null; + } + /** + * Update the last modified time to the current time. + */ + updateModifiedTime() { + this.modified = Date.now(); + } + /** + * Get the timestamp when this room state was last updated. This timestamp is + * updated when this object has received new state events. + * @returns The timestamp + */ + getLastModifiedTime() { + return this.modified; + } + /** + * Get user IDs with the specified or similar display names. + * @param displayName - The display name to get user IDs from. + * @returns An array of user IDs or an empty array. + */ + getUserIdsWithDisplayName(displayName) { + var _a; + return (_a = this.displayNameToUserIds.get(utils.removeHiddenChars(displayName))) !== null && _a !== void 0 ? _a : []; + } + /** + * Returns true if userId is in room, event is not redacted and either sender of + * mxEvent or has power level sufficient to redact events other than their own. + * @param mxEvent - The event to test permission for + * @param userId - The user ID of the user to test permission for + * @returns true if the given used ID can redact given event + */ + maySendRedactionForEvent(mxEvent, userId) { + const member = this.getMember(userId); + if (!member || member.membership === "leave") + return false; + if (mxEvent.status || mxEvent.isRedacted()) + return false; + // The user may have been the sender, but they can't redact their own message + // if redactions are blocked. + const canRedact = this.maySendEvent(event_1.EventType.RoomRedaction, userId); + if (mxEvent.getSender() === userId) + return canRedact; + return this.hasSufficientPowerLevelFor("redact", member.powerLevel); + } + /** + * Returns true if the given power level is sufficient for action + * @param action - The type of power level to check + * @param powerLevel - The power level of the member + * @returns true if the given power level is sufficient + */ + hasSufficientPowerLevelFor(action, powerLevel) { + const powerLevelsEvent = this.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + let powerLevels = {}; + if (powerLevelsEvent) { + powerLevels = powerLevelsEvent.getContent(); + } + let requiredLevel = 50; + if (utils.isNumber(powerLevels[action])) { + requiredLevel = powerLevels[action]; + } + return powerLevel >= requiredLevel; + } + /** + * Short-form for maySendEvent('m.room.message', userId) + * @param userId - The user ID of the user to test permission for + * @returns true if the given user ID should be permitted to send + * message events into the given room. + */ + maySendMessage(userId) { + return this.maySendEventOfType(event_1.EventType.RoomMessage, userId, false); + } + /** + * Returns true if the given user ID has permission to send a normal + * event of type `eventType` into this room. + * @param eventType - The type of event to test + * @param userId - The user ID of the user to test permission for + * @returns true if the given user ID should be permitted to send + * the given type of event into this room, + * according to the room's state. + */ + maySendEvent(eventType, userId) { + return this.maySendEventOfType(eventType, userId, false); + } + /** + * Returns true if the given MatrixClient has permission to send a state + * event of type `stateEventType` into this room. + * @param stateEventType - The type of state events to test + * @param cli - The client to test permission for + * @returns true if the given client should be permitted to send + * the given type of state event into this room, + * according to the room's state. + */ + mayClientSendStateEvent(stateEventType, cli) { + if (cli.isGuest() || !cli.credentials.userId) { + return false; + } + return this.maySendStateEvent(stateEventType, cli.credentials.userId); + } + /** + * Returns true if the given user ID has permission to send a state + * event of type `stateEventType` into this room. + * @param stateEventType - The type of state events to test + * @param userId - The user ID of the user to test permission for + * @returns true if the given user ID should be permitted to send + * the given type of state event into this room, + * according to the room's state. + */ + maySendStateEvent(stateEventType, userId) { + return this.maySendEventOfType(stateEventType, userId, true); + } + /** + * Returns true if the given user ID has permission to send a normal or state + * event of type `eventType` into this room. + * @param eventType - The type of event to test + * @param userId - The user ID of the user to test permission for + * @param state - If true, tests if the user may send a state + event of this type. Otherwise tests whether + they may send a regular event. + * @returns true if the given user ID should be permitted to send + * the given type of event into this room, + * according to the room's state. + */ + maySendEventOfType(eventType, userId, state) { + const powerLevelsEvent = this.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + let powerLevels; + let eventsLevels = {}; + let stateDefault = 0; + let eventsDefault = 0; + let powerLevel = 0; + if (powerLevelsEvent) { + powerLevels = powerLevelsEvent.getContent(); + eventsLevels = powerLevels.events || {}; + if (Number.isSafeInteger(powerLevels.state_default)) { + stateDefault = powerLevels.state_default; + } + else { + stateDefault = 50; + } + const userPowerLevel = powerLevels.users && powerLevels.users[userId]; + if (Number.isSafeInteger(userPowerLevel)) { + powerLevel = userPowerLevel; + } + else if (Number.isSafeInteger(powerLevels.users_default)) { + powerLevel = powerLevels.users_default; + } + if (Number.isSafeInteger(powerLevels.events_default)) { + eventsDefault = powerLevels.events_default; + } + } + let requiredLevel = state ? stateDefault : eventsDefault; + if (Number.isSafeInteger(eventsLevels[eventType])) { + requiredLevel = eventsLevels[eventType]; + } + return powerLevel >= requiredLevel; + } + /** + * Returns true if the given user ID has permission to trigger notification + * of type `notifLevelKey` + * @param notifLevelKey - The level of notification to test (eg. 'room') + * @param userId - The user ID of the user to test permission for + * @returns true if the given user ID has permission to trigger a + * notification of this type. + */ + mayTriggerNotifOfType(notifLevelKey, userId) { + const member = this.getMember(userId); + if (!member) { + return false; + } + const powerLevelsEvent = this.getStateEvents(event_1.EventType.RoomPowerLevels, ""); + let notifLevel = 50; + if (powerLevelsEvent && + powerLevelsEvent.getContent() && + powerLevelsEvent.getContent().notifications && + utils.isNumber(powerLevelsEvent.getContent().notifications[notifLevelKey])) { + notifLevel = powerLevelsEvent.getContent().notifications[notifLevelKey]; + } + return member.powerLevel >= notifLevel; + } + /** + * Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`. + * @returns the join_rule applied to this room + */ + getJoinRule() { + var _a; + const joinRuleEvent = this.getStateEvents(event_1.EventType.RoomJoinRules, ""); + const joinRuleContent = (_a = joinRuleEvent === null || joinRuleEvent === void 0 ? void 0 : joinRuleEvent.getContent()) !== null && _a !== void 0 ? _a : {}; + return joinRuleContent["join_rule"] || partials_1.JoinRule.Invite; + } + /** + * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`. + * @returns the history_visibility applied to this room + */ + getHistoryVisibility() { + var _a; + const historyVisibilityEvent = this.getStateEvents(event_1.EventType.RoomHistoryVisibility, ""); + const historyVisibilityContent = (_a = historyVisibilityEvent === null || historyVisibilityEvent === void 0 ? void 0 : historyVisibilityEvent.getContent()) !== null && _a !== void 0 ? _a : {}; + return historyVisibilityContent["history_visibility"] || partials_1.HistoryVisibility.Shared; + } + /** + * Returns the guest access based on the m.room.guest_access state event, defaulting to `shared`. + * @returns the guest_access applied to this room + */ + getGuestAccess() { + var _a; + const guestAccessEvent = this.getStateEvents(event_1.EventType.RoomGuestAccess, ""); + const guestAccessContent = (_a = guestAccessEvent === null || guestAccessEvent === void 0 ? void 0 : guestAccessEvent.getContent()) !== null && _a !== void 0 ? _a : {}; + return guestAccessContent["guest_access"] || partials_1.GuestAccess.Forbidden; + } + /** + * Find the predecessor room based on this room state. + * + * @param msc3946ProcessDynamicPredecessor - if true, look for an + * m.room.predecessor state event and use it if found (MSC3946). + * @returns null if this room has no predecessor. Otherwise, returns + * the roomId, last eventId and viaServers of the predecessor room. + * + * If msc3946ProcessDynamicPredecessor is true, use m.predecessor events + * as well as m.room.create events to find predecessors. + * + * Note: if an m.predecessor event is used, eventId may be undefined + * since last_known_event_id is optional. + * + * Note: viaServers may be undefined, and will definitely be undefined if + * this predecessor comes from a RoomCreate event (rather than a + * RoomPredecessor, which has the optional via_servers property). + */ + findPredecessor(msc3946ProcessDynamicPredecessor = false) { + // Note: the tests for this function are against Room.findPredecessor, + // which just calls through to here. + if (msc3946ProcessDynamicPredecessor) { + const predecessorEvent = this.getStateEvents(event_1.EventType.RoomPredecessor, ""); + if (predecessorEvent) { + const content = predecessorEvent.getContent(); + const roomId = content.predecessor_room_id; + let eventId = content.last_known_event_id; + if (typeof eventId !== "string") { + eventId = undefined; + } + let viaServers = content.via_servers; + if (!Array.isArray(viaServers)) { + viaServers = undefined; + } + if (typeof roomId === "string") { + return { roomId, eventId, viaServers }; + } + } + } + const createEvent = this.getStateEvents(event_1.EventType.RoomCreate, ""); + if (createEvent) { + const predecessor = createEvent.getContent()["predecessor"]; + if (predecessor) { + const roomId = predecessor["room_id"]; + if (typeof roomId === "string") { + let eventId = predecessor["event_id"]; + if (typeof eventId !== "string" || eventId === "") { + eventId = undefined; + } + return { roomId, eventId }; + } + } + } + return null; + } + updateThirdPartyTokenCache(memberEvent) { + if (!memberEvent.getContent().third_party_invite) { + return; + } + const token = (memberEvent.getContent().third_party_invite.signed || {}).token; + if (!token) { + return; + } + const threePidInvite = this.getStateEvents(event_1.EventType.RoomThirdPartyInvite, token); + if (!threePidInvite) { + return; + } + this.tokenToInvite[token] = memberEvent; + } + updateDisplayNameCache(userId, displayName) { + var _a; + const oldName = this.userIdsToDisplayNames[userId]; + delete this.userIdsToDisplayNames[userId]; + if (oldName) { + // Remove the old name from the cache. + // We clobber the user_id > name lookup but the name -> [user_id] lookup + // means we need to remove that user ID from that array rather than nuking + // the lot. + const strippedOldName = utils.removeHiddenChars(oldName); + const existingUserIds = this.displayNameToUserIds.get(strippedOldName); + if (existingUserIds) { + // remove this user ID from this array + const filteredUserIDs = existingUserIds.filter((id) => id !== userId); + this.displayNameToUserIds.set(strippedOldName, filteredUserIDs); + } + } + this.userIdsToDisplayNames[userId] = displayName; + const strippedDisplayname = displayName && utils.removeHiddenChars(displayName); + // an empty stripped displayname (undefined/'') will be set to MXID in room-member.js + if (strippedDisplayname) { + const arr = (_a = this.displayNameToUserIds.get(strippedDisplayname)) !== null && _a !== void 0 ? _a : []; + arr.push(userId); + this.displayNameToUserIds.set(strippedDisplayname, arr); + } + } +} +exports.RoomState = RoomState; + +},{"../@types/beacon":305,"../@types/event":306,"../@types/partials":309,"../ReEmitter":317,"../logger":374,"../utils":416,"./beacon":378,"./event":383,"./room-member":389,"./typed-event-emitter":395}],391:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomSummary = void 0; +/** + * Construct a new Room Summary. A summary can be used for display on a recent + * list, without having to load the entire room list into memory. + * @param roomId - Required. The ID of this room. + * @param info - Optional. The summary info. Additional keys are supported. + */ +class RoomSummary { + constructor(roomId, info) { + this.roomId = roomId; + } +} +exports.RoomSummary = RoomSummary; + +},{}],392:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoomNameType = exports.Room = exports.RoomEvent = exports.NotificationCountType = exports.KNOWN_SAFE_ROOM_VERSION = void 0; +const matrix_events_sdk_1 = require("matrix-events-sdk"); +const event_timeline_set_1 = require("./event-timeline-set"); +const event_timeline_1 = require("./event-timeline"); +const content_repo_1 = require("../content-repo"); +const utils = __importStar(require("../utils")); +const utils_1 = require("../utils"); +const event_1 = require("./event"); +const event_status_1 = require("./event-status"); +const room_member_1 = require("./room-member"); +const room_summary_1 = require("./room-summary"); +const logger_1 = require("../logger"); +const ReEmitter_1 = require("../ReEmitter"); +const event_2 = require("../@types/event"); +const client_1 = require("../client"); +const filter_1 = require("../filter"); +const room_state_1 = require("./room-state"); +const beacon_1 = require("./beacon"); +const thread_1 = require("./thread"); +const read_receipts_1 = require("../@types/read_receipts"); +const relations_container_1 = require("./relations-container"); +const read_receipt_1 = require("./read-receipt"); +const poll_1 = require("./poll"); +// These constants are used as sane defaults when the homeserver doesn't support +// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be +// the same as the common default room version whereas SAFE_ROOM_VERSIONS are the +// room versions which are considered okay for people to run without being asked +// to upgrade (ie: "stable"). Eventually, we should remove these when all homeservers +// return an m.room_versions capability. +exports.KNOWN_SAFE_ROOM_VERSION = "9"; +const SAFE_ROOM_VERSIONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; +// When inserting a visibility event affecting event `eventId`, we +// need to scan through existing visibility events for `eventId`. +// In theory, this could take an unlimited amount of time if: +// +// - the visibility event was sent by a moderator; and +// - `eventId` already has many visibility changes (usually, it should +// be 2 or less); and +// - for some reason, the visibility changes are received out of order +// (usually, this shouldn't happen at all). +// +// For this reason, we limit the number of events to scan through, +// expecting that a broken visibility change for a single event in +// an extremely uncommon case (possibly a DoS) is a small +// price to pay to keep matrix-js-sdk responsive. +const MAX_NUMBER_OF_VISIBILITY_EVENTS_TO_SCAN_THROUGH = 30; +var NotificationCountType; +(function (NotificationCountType) { + NotificationCountType["Highlight"] = "highlight"; + NotificationCountType["Total"] = "total"; +})(NotificationCountType = exports.NotificationCountType || (exports.NotificationCountType = {})); +var RoomEvent; +(function (RoomEvent) { + RoomEvent["MyMembership"] = "Room.myMembership"; + RoomEvent["Tags"] = "Room.tags"; + RoomEvent["AccountData"] = "Room.accountData"; + RoomEvent["Receipt"] = "Room.receipt"; + RoomEvent["Name"] = "Room.name"; + RoomEvent["Redaction"] = "Room.redaction"; + RoomEvent["RedactionCancelled"] = "Room.redactionCancelled"; + RoomEvent["LocalEchoUpdated"] = "Room.localEchoUpdated"; + RoomEvent["Timeline"] = "Room.timeline"; + RoomEvent["TimelineReset"] = "Room.timelineReset"; + RoomEvent["TimelineRefresh"] = "Room.TimelineRefresh"; + RoomEvent["OldStateUpdated"] = "Room.OldStateUpdated"; + RoomEvent["CurrentStateUpdated"] = "Room.CurrentStateUpdated"; + RoomEvent["HistoryImportedWithinTimeline"] = "Room.historyImportedWithinTimeline"; + RoomEvent["UnreadNotifications"] = "Room.UnreadNotifications"; +})(RoomEvent = exports.RoomEvent || (exports.RoomEvent = {})); +class Room extends read_receipt_1.ReadReceipt { + /** + * Construct a new Room. + * + *

For a room, we store an ordered sequence of timelines, which may or may not + * be continuous. Each timeline lists a series of events, as well as tracking + * the room state at the start and the end of the timeline. It also tracks + * forward and backward pagination tokens, as well as containing links to the + * next timeline in the sequence. + * + *

There is one special timeline - the 'live' timeline, which represents the + * timeline to which events are being added in real-time as they are received + * from the /sync API. Note that you should not retain references to this + * timeline - even if it is the current timeline right now, it may not remain + * so if the server gives us a timeline gap in /sync. + * + *

In order that we can find events from their ids later, we also maintain a + * map from event_id to timeline and index. + * + * @param roomId - Required. The ID of this room. + * @param client - Required. The client, used to lazy load members. + * @param myUserId - Required. The ID of the syncing user. + * @param opts - Configuration options + */ + constructor(roomId, client, myUserId, opts = {}) { + super(); + this.roomId = roomId; + this.client = client; + this.myUserId = myUserId; + this.opts = opts; + this.txnToEvent = new Map(); // Pending in-flight requests { string: MatrixEvent } + this.notificationCounts = {}; + this.threadNotifications = new Map(); + this.cachedThreadReadReceipts = new Map(); + // Useful to know at what point the current user has started using threads in this room + this.oldestThreadedReceiptTs = Infinity; + /** + * A record of the latest unthread receipts per user + * This is useful in determining whether a user has read a thread or not + */ + this.unthreadedReceipts = new Map(); + this.polls = new Map(); + this.threadsTimelineSets = []; + // any filtered timeline sets we're maintaining for this room + this.filteredTimelineSets = {}; // filter_id: timelineSet + this.timelineNeedsRefresh = false; + this.summaryHeroes = null; + // flags to stop logspam about missing m.room.create events + this.getTypeWarning = false; + this.getVersionWarning = false; + /** + * Dict of room tags; the keys are the tag name and the values + * are any metadata associated with the tag - e.g. `{ "fav" : { order: 1 } }` + */ + this.tags = {}; // $tagName: { $metadata: $value } + /** + * accountData Dict of per-room account_data events; the keys are the + * event type and the values are the events. + */ + this.accountData = new Map(); // $eventType: $event + /** + * The room summary. + */ + this.summary = null; + this.relations = new relations_container_1.RelationsContainer(this.client, this); + /** + * A collection of events known by the client + * This is not a comprehensive list of the threads that exist in this room + */ + this.threads = new Map(); + /** + * A mapping of eventId to all visibility changes to apply + * to the event, by chronological order, as per + * https://github.com/matrix-org/matrix-doc/pull/3531 + * + * # Invariants + * + * - within each list, all events are classed by + * chronological order; + * - all events are events such that + * `asVisibilityEvent()` returns a non-null `IVisibilityChange`; + * - within each list with key `eventId`, all events + * are in relation to `eventId`. + * + * @experimental + */ + this.visibilityEvents = new Map(); + this.threadTimelineSetsPromise = null; + this.threadsReady = false; + this.updateThreadRootEvents = (thread, toStartOfTimeline, recreateEvent) => { + var _a, _b; + if (thread.length) { + this.updateThreadRootEvent((_a = this.threadsTimelineSets) === null || _a === void 0 ? void 0 : _a[0], thread, toStartOfTimeline, recreateEvent); + if (thread.hasCurrentUserParticipated) { + this.updateThreadRootEvent((_b = this.threadsTimelineSets) === null || _b === void 0 ? void 0 : _b[1], thread, toStartOfTimeline, recreateEvent); + } + } + }; + this.updateThreadRootEvent = (timelineSet, thread, toStartOfTimeline, recreateEvent) => { + if (timelineSet && thread.rootEvent) { + if (recreateEvent) { + timelineSet.removeEvent(thread.id); + } + if (thread_1.Thread.hasServerSideSupport) { + timelineSet.addLiveEvent(thread.rootEvent, { + duplicateStrategy: event_timeline_set_1.DuplicateStrategy.Replace, + fromCache: false, + roomState: this.currentState, + }); + } + else { + timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), { toStartOfTimeline }); + } + } + }; + this.applyRedaction = (event) => { + if (event.isRedaction()) { + const redactId = event.event.redacts; + // if we know about this event, redact its contents now. + const redactedEvent = redactId ? this.findEventById(redactId) : undefined; + if (redactedEvent) { + redactedEvent.makeRedacted(event); + // If this is in the current state, replace it with the redacted version + if (redactedEvent.isState()) { + const currentStateEvent = this.currentState.getStateEvents(redactedEvent.getType(), redactedEvent.getStateKey()); + if ((currentStateEvent === null || currentStateEvent === void 0 ? void 0 : currentStateEvent.getId()) === redactedEvent.getId()) { + this.currentState.setStateEvents([redactedEvent]); + } + } + this.emit(RoomEvent.Redaction, event, this); + // TODO: we stash user displaynames (among other things) in + // RoomMember objects which are then attached to other events + // (in the sender and target fields). We should get those + // RoomMember objects to update themselves when the events that + // they are based on are changed. + // Remove any visibility change on this event. + this.visibilityEvents.delete(redactId); + // If this event is a visibility change event, remove it from the + // list of visibility changes and update any event affected by it. + if (redactedEvent.isVisibilityEvent()) { + this.redactVisibilityChangeEvent(event); + } + } + // FIXME: apply redactions to notification list + // NB: We continue to add the redaction event to the timeline so + // clients can say "so and so redacted an event" if they wish to. Also + // this may be needed to trigger an update. + } + }; + // In some cases, we add listeners for every displayed Matrix event, so it's + // common to have quite a few more than the default limit. + this.setMaxListeners(100); + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + opts.pendingEventOrdering = opts.pendingEventOrdering || client_1.PendingEventOrdering.Chronological; + this.name = roomId; + this.normalizedName = roomId; + // all our per-room timeline sets. the first one is the unfiltered ones; + // the subsequent ones are the filtered ones in no particular order. + this.timelineSets = [new event_timeline_set_1.EventTimelineSet(this, opts)]; + this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [RoomEvent.Timeline, RoomEvent.TimelineReset]); + this.fixUpLegacyTimelineFields(); + if (this.opts.pendingEventOrdering === client_1.PendingEventOrdering.Detached) { + this.pendingEventList = []; + this.client.store.getPendingEvents(this.roomId).then((events) => { + const mapper = this.client.getEventMapper({ + toDevice: false, + decrypt: false, + }); + events.forEach((serializedEvent) => __awaiter(this, void 0, void 0, function* () { + const event = mapper(serializedEvent); + yield client.decryptEventIfNeeded(event); + event.setStatus(event_status_1.EventStatus.NOT_SENT); + this.addPendingEvent(event, event.getTxnId()); + })); + }); + } + // awaited by getEncryptionTargetMembers while room members are loading + if (!this.opts.lazyLoadMembers) { + this.membersPromise = Promise.resolve(false); + } + else { + this.membersPromise = undefined; + } + } + createThreadsTimelineSets() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (this.threadTimelineSetsPromise) { + return this.threadTimelineSetsPromise; + } + if ((_a = this.client) === null || _a === void 0 ? void 0 : _a.supportsThreads()) { + try { + this.threadTimelineSetsPromise = Promise.all([ + this.createThreadTimelineSet(), + this.createThreadTimelineSet(thread_1.ThreadFilterType.My), + ]); + const timelineSets = yield this.threadTimelineSetsPromise; + this.threadsTimelineSets.push(...timelineSets); + return timelineSets; + } + catch (e) { + this.threadTimelineSetsPromise = null; + return null; + } + } + return null; + }); + } + /** + * Bulk decrypt critical events in a room + * + * Critical events represents the minimal set of events to decrypt + * for a typical UI to function properly + * + * - Last event of every room (to generate likely message preview) + * - All events up to the read receipt (to calculate an accurate notification count) + * + * @returns Signals when all events have been decrypted + */ + decryptCriticalEvents() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.client.isCryptoEnabled()) + return; + const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId(), true); + const events = this.getLiveTimeline().getEvents(); + const readReceiptTimelineIndex = events.findIndex((matrixEvent) => { + return matrixEvent.event.event_id === readReceiptEventId; + }); + const decryptionPromises = events + .slice(readReceiptTimelineIndex) + .reverse() + .map((event) => this.client.decryptEventIfNeeded(event, { isRetry: true })); + yield Promise.allSettled(decryptionPromises); + }); + } + /** + * Bulk decrypt events in a room + * + * @returns Signals when all events have been decrypted + */ + decryptAllEvents() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.client.isCryptoEnabled()) + return; + const decryptionPromises = this.getUnfilteredTimelineSet() + .getLiveTimeline() + .getEvents() + .slice(0) // copy before reversing + .reverse() + .map((event) => this.client.decryptEventIfNeeded(event, { isRetry: true })); + yield Promise.allSettled(decryptionPromises); + }); + } + /** + * Gets the creator of the room + * @returns The creator of the room, or null if it could not be determined + */ + getCreator() { + var _a; + const createEvent = this.currentState.getStateEvents(event_2.EventType.RoomCreate, ""); + return (_a = createEvent === null || createEvent === void 0 ? void 0 : createEvent.getContent()["creator"]) !== null && _a !== void 0 ? _a : null; + } + /** + * Gets the version of the room + * @returns The version of the room, or null if it could not be determined + */ + getVersion() { + var _a; + const createEvent = this.currentState.getStateEvents(event_2.EventType.RoomCreate, ""); + if (!createEvent) { + if (!this.getVersionWarning) { + logger_1.logger.warn("[getVersion] Room " + this.roomId + " does not have an m.room.create event"); + this.getVersionWarning = true; + } + return "1"; + } + return (_a = createEvent.getContent()["room_version"]) !== null && _a !== void 0 ? _a : "1"; + } + /** + * Determines whether this room needs to be upgraded to a new version + * @returns What version the room should be upgraded to, or null if + * the room does not require upgrading at this time. + * @deprecated Use #getRecommendedVersion() instead + */ + shouldUpgradeToVersion() { + // TODO: Remove this function. + // This makes assumptions about which versions are safe, and can easily + // be wrong. Instead, people are encouraged to use getRecommendedVersion + // which determines a safer value. This function doesn't use that function + // because this is not async-capable, and to avoid breaking the contract + // we're deprecating this. + if (!SAFE_ROOM_VERSIONS.includes(this.getVersion())) { + return exports.KNOWN_SAFE_ROOM_VERSION; + } + return null; + } + /** + * Determines the recommended room version for the room. This returns an + * object with 3 properties: `version` as the new version the + * room should be upgraded to (may be the same as the current version); + * `needsUpgrade` to indicate if the room actually can be + * upgraded (ie: does the current version not match?); and `urgent` + * to indicate if the new version patches a vulnerability in a previous + * version. + * @returns + * Resolves to the version the room should be upgraded to. + */ + getRecommendedVersion() { + return __awaiter(this, void 0, void 0, function* () { + const capabilities = yield this.client.getCapabilities(); + let versionCap = capabilities["m.room_versions"]; + if (!versionCap) { + versionCap = { + default: exports.KNOWN_SAFE_ROOM_VERSION, + available: {}, + }; + for (const safeVer of SAFE_ROOM_VERSIONS) { + versionCap.available[safeVer] = client_1.RoomVersionStability.Stable; + } + } + let result = this.checkVersionAgainstCapability(versionCap); + if (result.urgent && result.needsUpgrade) { + // Something doesn't feel right: we shouldn't need to update + // because the version we're on should be in the protocol's + // namespace. This usually means that the server was updated + // before the client was, making us think the newest possible + // room version is not stable. As a solution, we'll refresh + // the capability we're using to determine this. + logger_1.logger.warn("Refreshing room version capability because the server looks " + + "to be supporting a newer room version we don't know about."); + const caps = yield this.client.getCapabilities(true); + versionCap = caps["m.room_versions"]; + if (!versionCap) { + logger_1.logger.warn("No room version capability - assuming upgrade required."); + return result; + } + else { + result = this.checkVersionAgainstCapability(versionCap); + } + } + return result; + }); + } + checkVersionAgainstCapability(versionCap) { + const currentVersion = this.getVersion(); + logger_1.logger.log(`[${this.roomId}] Current version: ${currentVersion}`); + logger_1.logger.log(`[${this.roomId}] Version capability: `, versionCap); + const result = { + version: currentVersion, + needsUpgrade: false, + urgent: false, + }; + // If the room is on the default version then nothing needs to change + if (currentVersion === versionCap.default) + return result; + const stableVersions = Object.keys(versionCap.available).filter((v) => versionCap.available[v] === "stable"); + // Check if the room is on an unstable version. We determine urgency based + // off the version being in the Matrix spec namespace or not (if the version + // is in the current namespace and unstable, the room is probably vulnerable). + if (!stableVersions.includes(currentVersion)) { + result.version = versionCap.default; + result.needsUpgrade = true; + result.urgent = !!this.getVersion().match(/^[0-9]+[0-9.]*$/g); + if (result.urgent) { + logger_1.logger.warn(`URGENT upgrade required on ${this.roomId}`); + } + else { + logger_1.logger.warn(`Non-urgent upgrade required on ${this.roomId}`); + } + return result; + } + // The room is on a stable, but non-default, version by this point. + // No upgrade needed. + return result; + } + /** + * Determines whether the given user is permitted to perform a room upgrade + * @param userId - The ID of the user to test against + * @returns True if the given user is permitted to upgrade the room + */ + userMayUpgradeRoom(userId) { + return this.currentState.maySendStateEvent(event_2.EventType.RoomTombstone, userId); + } + /** + * Get the list of pending sent events for this room + * + * @returns A list of the sent events + * waiting for remote echo. + * + * @throws If `opts.pendingEventOrdering` was not 'detached' + */ + getPendingEvents() { + if (!this.pendingEventList) { + throw new Error("Cannot call getPendingEvents with pendingEventOrdering == " + this.opts.pendingEventOrdering); + } + return this.pendingEventList; + } + /** + * Removes a pending event for this room + * + * @returns True if an element was removed. + */ + removePendingEvent(eventId) { + if (!this.pendingEventList) { + throw new Error("Cannot call removePendingEvent with pendingEventOrdering == " + this.opts.pendingEventOrdering); + } + const removed = utils.removeElement(this.pendingEventList, function (ev) { + return ev.getId() == eventId; + }, false); + this.savePendingEvents(); + return removed; + } + /** + * Check whether the pending event list contains a given event by ID. + * If pending event ordering is not "detached" then this returns false. + * + * @param eventId - The event ID to check for. + */ + hasPendingEvent(eventId) { + var _a, _b; + return (_b = (_a = this.pendingEventList) === null || _a === void 0 ? void 0 : _a.some((event) => event.getId() === eventId)) !== null && _b !== void 0 ? _b : false; + } + /** + * Get a specific event from the pending event list, if configured, null otherwise. + * + * @param eventId - The event ID to check for. + */ + getPendingEvent(eventId) { + var _a, _b; + return (_b = (_a = this.pendingEventList) === null || _a === void 0 ? void 0 : _a.find((event) => event.getId() === eventId)) !== null && _b !== void 0 ? _b : null; + } + /** + * Get the live unfiltered timeline for this room. + * + * @returns live timeline + */ + getLiveTimeline() { + return this.getUnfilteredTimelineSet().getLiveTimeline(); + } + /** + * Get the timestamp of the last message in the room + * + * @returns the timestamp of the last message in the room + */ + getLastActiveTimestamp() { + const timeline = this.getLiveTimeline(); + const events = timeline.getEvents(); + if (events.length) { + const lastEvent = events[events.length - 1]; + return lastEvent.getTs(); + } + else { + return Number.MIN_SAFE_INTEGER; + } + } + /** + * @returns the membership type (join | leave | invite) for the logged in user + */ + getMyMembership() { + var _a; + return (_a = this.selfMembership) !== null && _a !== void 0 ? _a : "leave"; + } + /** + * If this room is a DM we're invited to, + * try to find out who invited us + * @returns user id of the inviter + */ + getDMInviter() { + var _a; + const me = this.getMember(this.myUserId); + if (me) { + return me.getDMInviter(); + } + if (this.selfMembership === "invite") { + // fall back to summary information + const memberCount = this.getInvitedAndJoinedMemberCount(); + if (memberCount === 2) { + return (_a = this.summaryHeroes) === null || _a === void 0 ? void 0 : _a[0]; + } + } + } + /** + * Assuming this room is a DM room, tries to guess with which user. + * @returns user id of the other member (could be syncing user) + */ + guessDMUserId() { + const me = this.getMember(this.myUserId); + if (me) { + const inviterId = me.getDMInviter(); + if (inviterId) { + return inviterId; + } + } + // Remember, we're assuming this room is a DM, so returning the first member we find should be fine + if (Array.isArray(this.summaryHeroes) && this.summaryHeroes.length) { + return this.summaryHeroes[0]; + } + const members = this.currentState.getMembers(); + const anyMember = members.find((m) => m.userId !== this.myUserId); + if (anyMember) { + return anyMember.userId; + } + // it really seems like I'm the only user in the room + // so I probably created a room with just me in it + // and marked it as a DM. Ok then + return this.myUserId; + } + getAvatarFallbackMember() { + const memberCount = this.getInvitedAndJoinedMemberCount(); + if (memberCount > 2) { + return; + } + const hasHeroes = Array.isArray(this.summaryHeroes) && this.summaryHeroes.length; + if (hasHeroes) { + const availableMember = this.summaryHeroes.map((userId) => { + return this.getMember(userId); + }).find((member) => !!member); + if (availableMember) { + return availableMember; + } + } + const members = this.currentState.getMembers(); + // could be different than memberCount + // as this includes left members + if (members.length <= 2) { + const availableMember = members.find((m) => { + return m.userId !== this.myUserId; + }); + if (availableMember) { + return availableMember; + } + } + // if all else fails, try falling back to a user, + // and create a one-off member for it + if (hasHeroes) { + const availableUser = this.summaryHeroes.map((userId) => { + return this.client.getUser(userId); + }).find((user) => !!user); + if (availableUser) { + const member = new room_member_1.RoomMember(this.roomId, availableUser.userId); + member.user = availableUser; + return member; + } + } + } + /** + * Sets the membership this room was received as during sync + * @param membership - join | leave | invite + */ + updateMyMembership(membership) { + const prevMembership = this.selfMembership; + this.selfMembership = membership; + if (prevMembership !== membership) { + if (membership === "leave") { + this.cleanupAfterLeaving(); + } + this.emit(RoomEvent.MyMembership, this, membership, prevMembership); + } + } + loadMembersFromServer() { + return __awaiter(this, void 0, void 0, function* () { + const lastSyncToken = this.client.store.getSyncToken(); + const response = yield this.client.members(this.roomId, undefined, "leave", lastSyncToken !== null && lastSyncToken !== void 0 ? lastSyncToken : undefined); + return response.chunk; + }); + } + loadMembers() { + return __awaiter(this, void 0, void 0, function* () { + // were the members loaded from the server? + let fromServer = false; + let rawMembersEvents = yield this.client.store.getOutOfBandMembers(this.roomId); + // If the room is encrypted, we always fetch members from the server at + // least once, in case the latest state wasn't persisted properly. Note + // that this function is only called once (unless loading the members + // fails), since loadMembersIfNeeded always returns this.membersPromise + // if set, which will be the result of the first (successful) call. + if (rawMembersEvents === null || (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId))) { + fromServer = true; + rawMembersEvents = yield this.loadMembersFromServer(); + logger_1.logger.log(`LL: got ${rawMembersEvents.length} ` + `members from server for room ${this.roomId}`); + } + const memberEvents = rawMembersEvents.filter(utils_1.noUnsafeEventProps).map(this.client.getEventMapper()); + return { memberEvents, fromServer }; + }); + } + /** + * Check if loading of out-of-band-members has completed + * + * @returns true if the full membership list of this room has been loaded (including if lazy-loading is disabled). + * False if the load is not started or is in progress. + */ + membersLoaded() { + if (!this.opts.lazyLoadMembers) { + return true; + } + return this.currentState.outOfBandMembersReady(); + } + /** + * Preloads the member list in case lazy loading + * of memberships is in use. Can be called multiple times, + * it will only preload once. + * @returns when preloading is done and + * accessing the members on the room will take + * all members in the room into account + */ + loadMembersIfNeeded() { + if (this.membersPromise) { + return this.membersPromise; + } + // mark the state so that incoming messages while + // the request is in flight get marked as superseding + // the OOB members + this.currentState.markOutOfBandMembersStarted(); + const inMemoryUpdate = this.loadMembers() + .then((result) => { + this.currentState.setOutOfBandMembers(result.memberEvents); + return result.fromServer; + }) + .catch((err) => { + // allow retries on fail + this.membersPromise = undefined; + this.currentState.markOutOfBandMembersFailed(); + throw err; + }); + // update members in storage, but don't wait for it + inMemoryUpdate + .then((fromServer) => { + if (fromServer) { + const oobMembers = this.currentState + .getMembers() + .filter((m) => m.isOutOfBand()) + .map((m) => { var _a; return (_a = m.events.member) === null || _a === void 0 ? void 0 : _a.event; }); + logger_1.logger.log(`LL: telling store to write ${oobMembers.length}` + ` members for room ${this.roomId}`); + const store = this.client.store; + return (store + .setOutOfBandMembers(this.roomId, oobMembers) + // swallow any IDB error as we don't want to fail + // because of this + .catch((err) => { + logger_1.logger.log("LL: storing OOB room members failed, oh well", err); + })); + } + }) + .catch((err) => { + // as this is not awaited anywhere, + // at least show the error in the console + logger_1.logger.error(err); + }); + this.membersPromise = inMemoryUpdate; + return this.membersPromise; + } + /** + * Removes the lazily loaded members from storage if needed + */ + clearLoadedMembersIfNeeded() { + return __awaiter(this, void 0, void 0, function* () { + if (this.opts.lazyLoadMembers && this.membersPromise) { + yield this.loadMembersIfNeeded(); + yield this.client.store.clearOutOfBandMembers(this.roomId); + this.currentState.clearOutOfBandMembers(); + this.membersPromise = undefined; + } + }); + } + /** + * called when sync receives this room in the leave section + * to do cleanup after leaving a room. Possibly called multiple times. + */ + cleanupAfterLeaving() { + this.clearLoadedMembersIfNeeded().catch((err) => { + logger_1.logger.error(`error after clearing loaded members from ` + `room ${this.roomId} after leaving`); + logger_1.logger.log(err); + }); + } + /** + * Empty out the current live timeline and re-request it. This is used when + * historical messages are imported into the room via MSC2716 `/batch_send` + * because the client may already have that section of the timeline loaded. + * We need to force the client to throw away their current timeline so that + * when they back paginate over the area again with the historical messages + * in between, it grabs the newly imported messages. We can listen for + * `UNSTABLE_MSC2716_MARKER`, in order to tell when historical messages are ready + * to be discovered in the room and the timeline needs a refresh. The SDK + * emits a `RoomEvent.HistoryImportedWithinTimeline` event when we detect a + * valid marker and can check the needs refresh status via + * `room.getTimelineNeedsRefresh()`. + */ + refreshLiveTimeline() { + return __awaiter(this, void 0, void 0, function* () { + const liveTimelineBefore = this.getLiveTimeline(); + const forwardPaginationToken = liveTimelineBefore.getPaginationToken(event_timeline_1.EventTimeline.FORWARDS); + const backwardPaginationToken = liveTimelineBefore.getPaginationToken(event_timeline_1.EventTimeline.BACKWARDS); + const eventsBefore = liveTimelineBefore.getEvents(); + const mostRecentEventInTimeline = eventsBefore[eventsBefore.length - 1]; + logger_1.logger.log(`[refreshLiveTimeline for ${this.roomId}] at ` + + `mostRecentEventInTimeline=${mostRecentEventInTimeline && mostRecentEventInTimeline.getId()} ` + + `liveTimelineBefore=${liveTimelineBefore.toString()} ` + + `forwardPaginationToken=${forwardPaginationToken} ` + + `backwardPaginationToken=${backwardPaginationToken}`); + // Get the main TimelineSet + const timelineSet = this.getUnfilteredTimelineSet(); + let newTimeline; + // If there isn't any event in the timeline, let's go fetch the latest + // event and construct a timeline from it. + // + // This should only really happen if the user ran into an error + // with refreshing the timeline before which left them in a blank + // timeline from `resetLiveTimeline`. + if (!mostRecentEventInTimeline) { + newTimeline = yield this.client.getLatestTimeline(timelineSet); + } + else { + // Empty out all of `this.timelineSets`. But we also need to keep the + // same `timelineSet` references around so the React code updates + // properly and doesn't ignore the room events we emit because it checks + // that the `timelineSet` references are the same. We need the + // `timelineSet` empty so that the `client.getEventTimeline(...)` call + // later, will call `/context` and create a new timeline instead of + // returning the same one. + this.resetLiveTimeline(null, null); + // Make the UI timeline show the new blank live timeline we just + // reset so that if the network fails below it's showing the + // accurate state of what we're working with instead of the + // disconnected one in the TimelineWindow which is just hanging + // around by reference. + this.emit(RoomEvent.TimelineRefresh, this, timelineSet); + // Use `client.getEventTimeline(...)` to construct a new timeline from a + // `/context` response state and events for the most recent event before + // we reset everything. The `timelineSet` we pass in needs to be empty + // in order for this function to call `/context` and generate a new + // timeline. + newTimeline = yield this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId()); + } + // If a racing `/sync` beat us to creating a new timeline, use that + // instead because it's the latest in the room and any new messages in + // the scrollback will include the history. + const liveTimeline = timelineSet.getLiveTimeline(); + if (!liveTimeline || + (liveTimeline.getPaginationToken(event_timeline_1.Direction.Forward) === null && + liveTimeline.getPaginationToken(event_timeline_1.Direction.Backward) === null && + liveTimeline.getEvents().length === 0)) { + logger_1.logger.log(`[refreshLiveTimeline for ${this.roomId}] using our new live timeline`); + // Set the pagination token back to the live sync token (`null`) instead + // of using the `/context` historical token (ex. `t12-13_0_0_0_0_0_0_0_0`) + // so that it matches the next response from `/sync` and we can properly + // continue the timeline. + newTimeline.setPaginationToken(forwardPaginationToken, event_timeline_1.EventTimeline.FORWARDS); + // Set our new fresh timeline as the live timeline to continue syncing + // forwards and back paginating from. + timelineSet.setLiveTimeline(newTimeline); + // Fixup `this.oldstate` so that `scrollback` has the pagination tokens + // available + this.fixUpLegacyTimelineFields(); + } + else { + logger_1.logger.log(`[refreshLiveTimeline for ${this.roomId}] \`/sync\` or some other request beat us to creating a new ` + + `live timeline after we reset it. We'll use that instead since any events in the scrollback from ` + + `this timeline will include the history.`); + } + // The timeline has now been refreshed ✅ + this.setTimelineNeedsRefresh(false); + // Emit an event which clients can react to and re-load the timeline + // from the SDK + this.emit(RoomEvent.TimelineRefresh, this, timelineSet); + }); + } + /** + * Reset the live timeline of all timelineSets, and start new ones. + * + *

This is used when /sync returns a 'limited' timeline. + * + * @param backPaginationToken - token for back-paginating the new timeline + * @param forwardPaginationToken - token for forward-paginating the old live timeline, + * if absent or null, all timelines are reset, removing old ones (including the previous live + * timeline which would otherwise be unable to paginate forwards without this token). + * Removing just the old live timeline whilst preserving previous ones is not supported. + */ + resetLiveTimeline(backPaginationToken, forwardPaginationToken) { + for (const timelineSet of this.timelineSets) { + timelineSet.resetLiveTimeline(backPaginationToken !== null && backPaginationToken !== void 0 ? backPaginationToken : undefined, forwardPaginationToken !== null && forwardPaginationToken !== void 0 ? forwardPaginationToken : undefined); + } + for (const thread of this.threads.values()) { + thread.resetLiveTimeline(backPaginationToken, forwardPaginationToken); + } + this.fixUpLegacyTimelineFields(); + } + /** + * Fix up this.timeline, this.oldState and this.currentState + * + * @internal + */ + fixUpLegacyTimelineFields() { + const previousOldState = this.oldState; + const previousCurrentState = this.currentState; + // maintain this.timeline as a reference to the live timeline, + // and this.oldState and this.currentState as references to the + // state at the start and end of that timeline. These are more + // for backwards-compatibility than anything else. + this.timeline = this.getLiveTimeline().getEvents(); + this.oldState = this.getLiveTimeline().getState(event_timeline_1.EventTimeline.BACKWARDS); + this.currentState = this.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + // Let people know to register new listeners for the new state + // references. The reference won't necessarily change every time so only + // emit when we see a change. + if (previousOldState !== this.oldState) { + this.emit(RoomEvent.OldStateUpdated, this, previousOldState, this.oldState); + } + if (previousCurrentState !== this.currentState) { + this.emit(RoomEvent.CurrentStateUpdated, this, previousCurrentState, this.currentState); + // Re-emit various events on the current room state + // TODO: If currentState really only exists for backwards + // compatibility, shouldn't we be doing this some other way? + this.reEmitter.stopReEmitting(previousCurrentState, [ + room_state_1.RoomStateEvent.Events, + room_state_1.RoomStateEvent.Members, + room_state_1.RoomStateEvent.NewMember, + room_state_1.RoomStateEvent.Update, + room_state_1.RoomStateEvent.Marker, + beacon_1.BeaconEvent.New, + beacon_1.BeaconEvent.Update, + beacon_1.BeaconEvent.Destroy, + beacon_1.BeaconEvent.LivenessChange, + ]); + this.reEmitter.reEmit(this.currentState, [ + room_state_1.RoomStateEvent.Events, + room_state_1.RoomStateEvent.Members, + room_state_1.RoomStateEvent.NewMember, + room_state_1.RoomStateEvent.Update, + room_state_1.RoomStateEvent.Marker, + beacon_1.BeaconEvent.New, + beacon_1.BeaconEvent.Update, + beacon_1.BeaconEvent.Destroy, + beacon_1.BeaconEvent.LivenessChange, + ]); + } + } + /** + * Returns whether there are any devices in the room that are unverified + * + * Note: Callers should first check if crypto is enabled on this device. If it is + * disabled, then we aren't tracking room devices at all, so we can't answer this, and an + * error will be thrown. + * + * @returns the result + */ + hasUnverifiedDevices() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.client.isRoomEncrypted(this.roomId)) { + return false; + } + const e2eMembers = yield this.getEncryptionTargetMembers(); + for (const member of e2eMembers) { + const devices = this.client.getStoredDevicesForUser(member.userId); + if (devices.some((device) => device.isUnverified())) { + return true; + } + } + return false; + }); + } + /** + * Return the timeline sets for this room. + * @returns array of timeline sets for this room + */ + getTimelineSets() { + return this.timelineSets; + } + /** + * Helper to return the main unfiltered timeline set for this room + * @returns room's unfiltered timeline set + */ + getUnfilteredTimelineSet() { + return this.timelineSets[0]; + } + /** + * Get the timeline which contains the given event from the unfiltered set, if any + * + * @param eventId - event ID to look for + * @returns timeline containing + * the given event, or null if unknown + */ + getTimelineForEvent(eventId) { + const event = this.findEventById(eventId); + const thread = this.findThreadForEvent(event); + if (thread) { + return thread.timelineSet.getTimelineForEvent(eventId); + } + else { + return this.getUnfilteredTimelineSet().getTimelineForEvent(eventId); + } + } + /** + * Add a new timeline to this room's unfiltered timeline set + * + * @returns newly-created timeline + */ + addTimeline() { + return this.getUnfilteredTimelineSet().addTimeline(); + } + /** + * Whether the timeline needs to be refreshed in order to pull in new + * historical messages that were imported. + * @param value - The value to set + */ + setTimelineNeedsRefresh(value) { + this.timelineNeedsRefresh = value; + } + /** + * Whether the timeline needs to be refreshed in order to pull in new + * historical messages that were imported. + * @returns . + */ + getTimelineNeedsRefresh() { + return this.timelineNeedsRefresh; + } + /** + * Get an event which is stored in our unfiltered timeline set, or in a thread + * + * @param eventId - event ID to look for + * @returns the given event, or undefined if unknown + */ + findEventById(eventId) { + let event = this.getUnfilteredTimelineSet().findEventById(eventId); + if (!event) { + const threads = this.getThreads(); + for (let i = 0; i < threads.length; i++) { + const thread = threads[i]; + event = thread.findEventById(eventId); + if (event) { + return event; + } + } + } + return event; + } + /** + * Get one of the notification counts for this room + * @param type - The type of notification count to get. default: 'total' + * @returns The notification count, or undefined if there is no count + * for this type. + */ + getUnreadNotificationCount(type = NotificationCountType.Total) { + var _a; + let count = this.getRoomUnreadNotificationCount(type); + for (const threadNotification of this.threadNotifications.values()) { + count += (_a = threadNotification[type]) !== null && _a !== void 0 ? _a : 0; + } + return count; + } + /** + * Get the notification for the event context (room or thread timeline) + */ + getUnreadCountForEventContext(type = NotificationCountType.Total, event) { + var _a; + const isThreadEvent = !!event.threadRootId && !event.isThreadRoot; + return ((_a = (isThreadEvent + ? this.getThreadUnreadNotificationCount(event.threadRootId, type) + : this.getRoomUnreadNotificationCount(type))) !== null && _a !== void 0 ? _a : 0); + } + /** + * Get one of the notification counts for this room + * @param type - The type of notification count to get. default: 'total' + * @returns The notification count, or undefined if there is no count + * for this type. + */ + getRoomUnreadNotificationCount(type = NotificationCountType.Total) { + var _a; + return (_a = this.notificationCounts[type]) !== null && _a !== void 0 ? _a : 0; + } + /** + * Get one of the notification counts for a thread + * @param threadId - the root event ID + * @param type - The type of notification count to get. default: 'total' + * @returns The notification count, or undefined if there is no count + * for this type. + */ + getThreadUnreadNotificationCount(threadId, type = NotificationCountType.Total) { + var _a, _b; + return (_b = (_a = this.threadNotifications.get(threadId)) === null || _a === void 0 ? void 0 : _a[type]) !== null && _b !== void 0 ? _b : 0; + } + /** + * Checks if the current room has unread thread notifications + * @returns + */ + hasThreadUnreadNotification() { + var _a, _b; + for (const notification of this.threadNotifications.values()) { + if (((_a = notification.highlight) !== null && _a !== void 0 ? _a : 0) > 0 || ((_b = notification.total) !== null && _b !== void 0 ? _b : 0) > 0) { + return true; + } + } + return false; + } + /** + * Swet one of the notification count for a thread + * @param threadId - the root event ID + * @param type - The type of notification count to get. default: 'total' + * @returns + */ + setThreadUnreadNotificationCount(threadId, type, count) { + var _a, _b; + const notification = Object.assign({ highlight: (_a = this.threadNotifications.get(threadId)) === null || _a === void 0 ? void 0 : _a.highlight, total: (_b = this.threadNotifications.get(threadId)) === null || _b === void 0 ? void 0 : _b.total }, { + [type]: count, + }); + this.threadNotifications.set(threadId, notification); + this.emit(RoomEvent.UnreadNotifications, notification, threadId); + } + /** + * @returns the notification count type for all the threads in the room + */ + get threadsAggregateNotificationType() { + var _a, _b; + let type = null; + for (const threadNotification of this.threadNotifications.values()) { + if (((_a = threadNotification.highlight) !== null && _a !== void 0 ? _a : 0) > 0) { + return NotificationCountType.Highlight; + } + else if (((_b = threadNotification.total) !== null && _b !== void 0 ? _b : 0) > 0 && !type) { + type = NotificationCountType.Total; + } + } + return type; + } + /** + * Resets the thread notifications for this room + */ + resetThreadUnreadNotificationCount(notificationsToKeep) { + if (notificationsToKeep) { + for (const [threadId] of this.threadNotifications) { + if (!notificationsToKeep.includes(threadId)) { + this.threadNotifications.delete(threadId); + } + } + } + else { + this.threadNotifications.clear(); + } + this.emit(RoomEvent.UnreadNotifications); + } + /** + * Set one of the notification counts for this room + * @param type - The type of notification count to set. + * @param count - The new count + */ + setUnreadNotificationCount(type, count) { + this.notificationCounts[type] = count; + this.emit(RoomEvent.UnreadNotifications, this.notificationCounts); + } + setUnread(type, count) { + return this.setUnreadNotificationCount(type, count); + } + setSummary(summary) { + const heroes = summary["m.heroes"]; + const joinedCount = summary["m.joined_member_count"]; + const invitedCount = summary["m.invited_member_count"]; + if (Number.isInteger(joinedCount)) { + this.currentState.setJoinedMemberCount(joinedCount); + } + if (Number.isInteger(invitedCount)) { + this.currentState.setInvitedMemberCount(invitedCount); + } + if (Array.isArray(heroes)) { + // be cautious about trusting server values, + // and make sure heroes doesn't contain our own id + // just to be sure + this.summaryHeroes = heroes.filter((userId) => { + return userId !== this.myUserId; + }); + } + } + /** + * Whether to send encrypted messages to devices within this room. + * @param value - true to blacklist unverified devices, null + * to use the global value for this room. + */ + setBlacklistUnverifiedDevices(value) { + this.blacklistUnverifiedDevices = value; + } + /** + * Whether to send encrypted messages to devices within this room. + * @returns true if blacklisting unverified devices, null + * if the global value should be used for this room. + */ + getBlacklistUnverifiedDevices() { + if (this.blacklistUnverifiedDevices === undefined) + return null; + return this.blacklistUnverifiedDevices; + } + /** + * Get the avatar URL for a room if one was set. + * @param baseUrl - The homeserver base URL. See + * {@link MatrixClient#getHomeserverUrl}. + * @param width - The desired width of the thumbnail. + * @param height - The desired height of the thumbnail. + * @param resizeMethod - The thumbnail resize method to use, either + * "crop" or "scale". + * @param allowDefault - True to allow an identicon for this room if an + * avatar URL wasn't explicitly set. Default: true. (Deprecated) + * @returns the avatar URL or null. + */ + getAvatarUrl(baseUrl, width, height, resizeMethod, allowDefault = true) { + const roomAvatarEvent = this.currentState.getStateEvents(event_2.EventType.RoomAvatar, ""); + if (!roomAvatarEvent && !allowDefault) { + return null; + } + const mainUrl = roomAvatarEvent ? roomAvatarEvent.getContent().url : null; + if (mainUrl) { + return (0, content_repo_1.getHttpUriForMxc)(baseUrl, mainUrl, width, height, resizeMethod); + } + return null; + } + /** + * Get the mxc avatar url for the room, if one was set. + * @returns the mxc avatar url or falsy + */ + getMxcAvatarUrl() { + var _a, _b; + return ((_b = (_a = this.currentState.getStateEvents(event_2.EventType.RoomAvatar, "")) === null || _a === void 0 ? void 0 : _a.getContent()) === null || _b === void 0 ? void 0 : _b.url) || null; + } + /** + * Get this room's canonical alias + * The alias returned by this function may not necessarily + * still point to this room. + * @returns The room's canonical alias, or null if there is none + */ + getCanonicalAlias() { + const canonicalAlias = this.currentState.getStateEvents(event_2.EventType.RoomCanonicalAlias, ""); + if (canonicalAlias) { + return canonicalAlias.getContent().alias || null; + } + return null; + } + /** + * Get this room's alternative aliases + * @returns The room's alternative aliases, or an empty array + */ + getAltAliases() { + const canonicalAlias = this.currentState.getStateEvents(event_2.EventType.RoomCanonicalAlias, ""); + if (canonicalAlias) { + return canonicalAlias.getContent().alt_aliases || []; + } + return []; + } + /** + * Add events to a timeline + * + *

Will fire "Room.timeline" for each event added. + * + * @param events - A list of events to add. + * + * @param toStartOfTimeline - True to add these events to the start + * (oldest) instead of the end (newest) of the timeline. If true, the oldest + * event will be the last element of 'events'. + * + * @param timeline - timeline to + * add events to. + * + * @param paginationToken - token for the next batch of events + * + * @remarks + * Fires {@link RoomEvent.Timeline} + */ + addEventsToTimeline(events, toStartOfTimeline, timeline, paginationToken) { + timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, timeline, paginationToken); + } + /** + * Get the instance of the thread associated with the current event + * @param eventId - the ID of the current event + * @returns a thread instance if known + */ + getThread(eventId) { + var _a; + return (_a = this.threads.get(eventId)) !== null && _a !== void 0 ? _a : null; + } + /** + * Get all the known threads in the room + */ + getThreads() { + return Array.from(this.threads.values()); + } + /** + * Get a member from the current room state. + * @param userId - The user ID of the member. + * @returns The member or `null`. + */ + getMember(userId) { + return this.currentState.getMember(userId); + } + /** + * Get all currently loaded members from the current + * room state. + * @returns Room members + */ + getMembers() { + return this.currentState.getMembers(); + } + /** + * Get a list of members whose membership state is "join". + * @returns A list of currently joined members. + */ + getJoinedMembers() { + return this.getMembersWithMembership("join"); + } + /** + * Returns the number of joined members in this room + * This method caches the result. + * This is a wrapper around the method of the same name in roomState, returning + * its result for the room's current state. + * @returns The number of members in this room whose membership is 'join' + */ + getJoinedMemberCount() { + return this.currentState.getJoinedMemberCount(); + } + /** + * Returns the number of invited members in this room + * @returns The number of members in this room whose membership is 'invite' + */ + getInvitedMemberCount() { + return this.currentState.getInvitedMemberCount(); + } + /** + * Returns the number of invited + joined members in this room + * @returns The number of members in this room whose membership is 'invite' or 'join' + */ + getInvitedAndJoinedMemberCount() { + return this.getInvitedMemberCount() + this.getJoinedMemberCount(); + } + /** + * Get a list of members with given membership state. + * @param membership - The membership state. + * @returns A list of members with the given membership state. + */ + getMembersWithMembership(membership) { + return this.currentState.getMembers().filter(function (m) { + return m.membership === membership; + }); + } + /** + * Get a list of members we should be encrypting for in this room + * @returns A list of members who + * we should encrypt messages for in this room. + */ + getEncryptionTargetMembers() { + return __awaiter(this, void 0, void 0, function* () { + yield this.loadMembersIfNeeded(); + let members = this.getMembersWithMembership("join"); + if (this.shouldEncryptForInvitedMembers()) { + members = members.concat(this.getMembersWithMembership("invite")); + } + return members; + }); + } + /** + * Determine whether we should encrypt messages for invited users in this room + * @returns if we should encrypt messages for invited users + */ + shouldEncryptForInvitedMembers() { + var _a; + const ev = this.currentState.getStateEvents(event_2.EventType.RoomHistoryVisibility, ""); + return ((_a = ev === null || ev === void 0 ? void 0 : ev.getContent()) === null || _a === void 0 ? void 0 : _a.history_visibility) !== "joined"; + } + /** + * Get the default room name (i.e. what a given user would see if the + * room had no m.room.name) + * @param userId - The userId from whose perspective we want + * to calculate the default name + * @returns The default room name + */ + getDefaultRoomName(userId) { + return this.calculateRoomName(userId, true); + } + /** + * Check if the given user_id has the given membership state. + * @param userId - The user ID to check. + * @param membership - The membership e.g. `'join'` + * @returns True if this user_id has the given membership state. + */ + hasMembershipState(userId, membership) { + const member = this.getMember(userId); + if (!member) { + return false; + } + return member.membership === membership; + } + /** + * Add a timelineSet for this room with the given filter + * @param filter - The filter to be applied to this timelineSet + * @param opts - Configuration options + * @returns The timelineSet + */ + getOrCreateFilteredTimelineSet(filter, { prepopulateTimeline = true, useSyncEvents = true, pendingEvents = true } = {}) { + if (this.filteredTimelineSets[filter.filterId]) { + return this.filteredTimelineSets[filter.filterId]; + } + const opts = Object.assign({ filter, pendingEvents }, this.opts); + const timelineSet = new event_timeline_set_1.EventTimelineSet(this, opts); + this.reEmitter.reEmit(timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]); + if (useSyncEvents) { + this.filteredTimelineSets[filter.filterId] = timelineSet; + this.timelineSets.push(timelineSet); + } + const unfilteredLiveTimeline = this.getLiveTimeline(); + // Not all filter are possible to replicate client-side only + // When that's the case we do not want to prepopulate from the live timeline + // as we would get incorrect results compared to what the server would send back + if (prepopulateTimeline) { + // populate up the new timelineSet with filtered events from our live + // unfiltered timeline. + // + // XXX: This is risky as our timeline + // may have grown huge and so take a long time to filter. + // see https://github.com/vector-im/vector-web/issues/2109 + unfilteredLiveTimeline.getEvents().forEach(function (event) { + timelineSet.addLiveEvent(event); + }); + // find the earliest unfiltered timeline + let timeline = unfilteredLiveTimeline; + while (timeline.getNeighbouringTimeline(event_timeline_1.EventTimeline.BACKWARDS)) { + timeline = timeline.getNeighbouringTimeline(event_timeline_1.EventTimeline.BACKWARDS); + } + timelineSet + .getLiveTimeline() + .setPaginationToken(timeline.getPaginationToken(event_timeline_1.EventTimeline.BACKWARDS), event_timeline_1.EventTimeline.BACKWARDS); + } + else if (useSyncEvents) { + const livePaginationToken = unfilteredLiveTimeline.getPaginationToken(event_timeline_1.Direction.Forward); + timelineSet.getLiveTimeline().setPaginationToken(livePaginationToken, event_timeline_1.Direction.Backward); + } + // alternatively, we could try to do something like this to try and re-paginate + // in the filtered events from nothing, but Mark says it's an abuse of the API + // to do so: + // + // timelineSet.resetLiveTimeline( + // unfilteredLiveTimeline.getPaginationToken(EventTimeline.FORWARDS) + // ); + return timelineSet; + } + getThreadListFilter(filterType = thread_1.ThreadFilterType.All) { + return __awaiter(this, void 0, void 0, function* () { + const myUserId = this.client.getUserId(); + const filter = new filter_1.Filter(myUserId); + const definition = { + room: { + timeline: { + [thread_1.FILTER_RELATED_BY_REL_TYPES.name]: [thread_1.THREAD_RELATION_TYPE.name], + }, + }, + }; + if (filterType === thread_1.ThreadFilterType.My) { + definition.room.timeline[thread_1.FILTER_RELATED_BY_SENDERS.name] = [myUserId]; + } + filter.setDefinition(definition); + const filterId = yield this.client.getOrCreateFilter(`THREAD_PANEL_${this.roomId}_${filterType}`, filter); + filter.filterId = filterId; + return filter; + }); + } + createThreadTimelineSet(filterType) { + return __awaiter(this, void 0, void 0, function* () { + let timelineSet; + if (thread_1.Thread.hasServerSideListSupport) { + timelineSet = new event_timeline_set_1.EventTimelineSet(this, Object.assign(Object.assign({}, this.opts), { pendingEvents: false }), undefined, undefined, filterType !== null && filterType !== void 0 ? filterType : thread_1.ThreadFilterType.All); + this.reEmitter.reEmit(timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]); + } + else if (thread_1.Thread.hasServerSideSupport) { + const filter = yield this.getThreadListFilter(filterType); + timelineSet = this.getOrCreateFilteredTimelineSet(filter, { + prepopulateTimeline: false, + useSyncEvents: false, + pendingEvents: false, + }); + } + else { + timelineSet = new event_timeline_set_1.EventTimelineSet(this, { + pendingEvents: false, + }); + Array.from(this.threads).forEach(([, thread]) => { + if (thread.length === 0) + return; + const currentUserParticipated = thread.timeline.some((event) => { + return event.getSender() === this.client.getUserId(); + }); + if (filterType !== thread_1.ThreadFilterType.My || currentUserParticipated) { + timelineSet.getLiveTimeline().addEvent(thread.rootEvent, { + toStartOfTimeline: false, + }); + } + }); + } + return timelineSet; + }); + } + /** + * Takes the given thread root events and creates threads for them. + */ + processThreadRoots(events, toStartOfTimeline) { + for (const rootEvent of events) { + event_timeline_1.EventTimeline.setEventMetadata(rootEvent, this.currentState, toStartOfTimeline); + if (!this.getThread(rootEvent.getId())) { + this.createThread(rootEvent.getId(), rootEvent, [], toStartOfTimeline); + } + } + } + /** + * Fetch the bare minimum of room threads required for the thread list to work reliably. + * With server support that means fetching one page. + * Without server support that means fetching as much at once as the server allows us to. + */ + fetchRoomThreads() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (this.threadsReady || !this.client.supportsThreads()) { + return; + } + if (thread_1.Thread.hasServerSideListSupport) { + yield Promise.all([ + this.fetchRoomThreadList(thread_1.ThreadFilterType.All), + this.fetchRoomThreadList(thread_1.ThreadFilterType.My), + ]); + } + else { + const allThreadsFilter = yield this.getThreadListFilter(); + const { chunk: events } = yield this.client.createMessagesRequest(this.roomId, "", Number.MAX_SAFE_INTEGER, event_timeline_1.Direction.Backward, allThreadsFilter); + if (!events.length) + return; + // Sorted by last_reply origin_server_ts + const threadRoots = events.map(this.client.getEventMapper()).sort((eventA, eventB) => { + /** + * `origin_server_ts` in a decentralised world is far from ideal + * but for lack of any better, we will have to use this + * Long term the sorting should be handled by homeservers and this + * is only meant as a short term patch + */ + const threadAMetadata = eventA.getServerAggregatedRelation(thread_1.THREAD_RELATION_TYPE.name); + const threadBMetadata = eventB.getServerAggregatedRelation(thread_1.THREAD_RELATION_TYPE.name); + return threadAMetadata.latest_event.origin_server_ts - threadBMetadata.latest_event.origin_server_ts; + }); + let latestMyThreadsRootEvent; + const roomState = this.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + for (const rootEvent of threadRoots) { + const opts = { + duplicateStrategy: event_timeline_set_1.DuplicateStrategy.Ignore, + fromCache: false, + roomState, + }; + (_a = this.threadsTimelineSets[0]) === null || _a === void 0 ? void 0 : _a.addLiveEvent(rootEvent, opts); + const threadRelationship = rootEvent.getServerAggregatedRelation(thread_1.THREAD_RELATION_TYPE.name); + if (threadRelationship === null || threadRelationship === void 0 ? void 0 : threadRelationship.current_user_participated) { + (_b = this.threadsTimelineSets[1]) === null || _b === void 0 ? void 0 : _b.addLiveEvent(rootEvent, opts); + latestMyThreadsRootEvent = rootEvent; + } + } + this.processThreadRoots(threadRoots, true); + this.client.decryptEventIfNeeded(threadRoots[threadRoots.length - 1]); + if (latestMyThreadsRootEvent) { + this.client.decryptEventIfNeeded(latestMyThreadsRootEvent); + } + } + this.on(thread_1.ThreadEvent.NewReply, this.onThreadNewReply); + this.on(thread_1.ThreadEvent.Delete, this.onThreadDelete); + this.threadsReady = true; + }); + } + processPollEvents(events) { + return __awaiter(this, void 0, void 0, function* () { + const processPollStartEvent = (event) => { + if (!matrix_events_sdk_1.M_POLL_START.matches(event.getType())) + return; + try { + const poll = new poll_1.Poll(event, this.client, this); + this.polls.set(event.getId(), poll); + this.emit(poll_1.PollEvent.New, poll); + } + catch (_a) { } + // poll creation can fail for malformed poll start events + }; + const processPollRelationEvent = (event) => { + const relationEventId = event.relationEventId; + if (relationEventId && this.polls.has(relationEventId)) { + const poll = this.polls.get(relationEventId); + poll === null || poll === void 0 ? void 0 : poll.onNewRelation(event); + } + }; + const processPollEvent = (event) => { + processPollStartEvent(event); + processPollRelationEvent(event); + }; + for (const event of events) { + try { + yield this.client.decryptEventIfNeeded(event); + processPollEvent(event); + } + catch (_a) { } + } + }); + } + /** + * Fetch a single page of threadlist messages for the specific thread filter + * @internal + */ + fetchRoomThreadList(filter) { + return __awaiter(this, void 0, void 0, function* () { + const timelineSet = filter === thread_1.ThreadFilterType.My ? this.threadsTimelineSets[1] : this.threadsTimelineSets[0]; + const { chunk: events, end } = yield this.client.createThreadListMessagesRequest(this.roomId, null, undefined, event_timeline_1.Direction.Backward, timelineSet.threadListType, timelineSet.getFilter()); + timelineSet.getLiveTimeline().setPaginationToken(end !== null && end !== void 0 ? end : null, event_timeline_1.Direction.Backward); + if (!events.length) + return; + const matrixEvents = events.map(this.client.getEventMapper()); + this.processThreadRoots(matrixEvents, true); + const roomState = this.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + for (const rootEvent of matrixEvents) { + timelineSet.addLiveEvent(rootEvent, { + duplicateStrategy: event_timeline_set_1.DuplicateStrategy.Replace, + fromCache: false, + roomState, + }); + } + }); + } + onThreadNewReply(thread) { + this.updateThreadRootEvents(thread, false, true); + } + onThreadDelete(thread) { + var _a; + this.threads.delete(thread.id); + const timeline = this.getTimelineForEvent(thread.id); + const roomEvent = (_a = timeline === null || timeline === void 0 ? void 0 : timeline.getEvents()) === null || _a === void 0 ? void 0 : _a.find((it) => it.getId() === thread.id); + if (roomEvent) { + thread.clearEventMetadata(roomEvent); + } + else { + logger_1.logger.debug("onThreadDelete: Could not find root event in room timeline"); + } + for (const timelineSet of this.threadsTimelineSets) { + timelineSet.removeEvent(thread.id); + } + } + /** + * Forget the timelineSet for this room with the given filter + * + * @param filter - the filter whose timelineSet is to be forgotten + */ + removeFilteredTimelineSet(filter) { + const timelineSet = this.filteredTimelineSets[filter.filterId]; + delete this.filteredTimelineSets[filter.filterId]; + const i = this.timelineSets.indexOf(timelineSet); + if (i > -1) { + this.timelineSets.splice(i, 1); + } + } + eventShouldLiveIn(event, events, roots) { + var _a, _b; + if (!((_a = this.client) === null || _a === void 0 ? void 0 : _a.supportsThreads())) { + return { + shouldLiveInRoom: true, + shouldLiveInThread: false, + }; + } + // A thread root is always shown in both timelines + if (event.isThreadRoot || (roots === null || roots === void 0 ? void 0 : roots.has(event.getId()))) { + return { + shouldLiveInRoom: true, + shouldLiveInThread: true, + threadId: event.getId(), + }; + } + // A thread relation is always only shown in a thread + if (event.isRelation(thread_1.THREAD_RELATION_TYPE.name)) { + return { + shouldLiveInRoom: false, + shouldLiveInThread: true, + threadId: event.threadRootId, + }; + } + const parentEventId = event.getAssociatedId(); + let parentEvent; + if (parentEventId) { + parentEvent = (_b = this.findEventById(parentEventId)) !== null && _b !== void 0 ? _b : events === null || events === void 0 ? void 0 : events.find((e) => e.getId() === parentEventId); + } + // Treat relations and redactions as extensions of their parents so evaluate parentEvent instead + if (parentEvent && (event.isRelation() || event.isRedaction())) { + return this.eventShouldLiveIn(parentEvent, events, roots); + } + // Edge case where we know the event is a relation but don't have the parentEvent + if (roots === null || roots === void 0 ? void 0 : roots.has(event.relationEventId)) { + return { + shouldLiveInRoom: true, + shouldLiveInThread: true, + threadId: event.relationEventId, + }; + } + // We've exhausted all scenarios, can safely assume that this event should live in the room timeline only + return { + shouldLiveInRoom: true, + shouldLiveInThread: false, + }; + } + findThreadForEvent(event) { + if (!event) + return null; + const { threadId } = this.eventShouldLiveIn(event); + return threadId ? this.getThread(threadId) : null; + } + addThreadedEvents(threadId, events, toStartOfTimeline = false) { + var _a; + let thread = this.getThread(threadId); + if (!thread) { + const rootEvent = (_a = this.findEventById(threadId)) !== null && _a !== void 0 ? _a : events.find((e) => e.getId() === threadId); + thread = this.createThread(threadId, rootEvent, events, toStartOfTimeline); + } + thread.addEvents(events, toStartOfTimeline); + } + /** + * Adds events to a thread's timeline. Will fire "Thread.update" + */ + processThreadedEvents(events, toStartOfTimeline) { + var _a; + events.forEach(this.applyRedaction); + const eventsByThread = {}; + for (const event of events) { + const { threadId, shouldLiveInThread } = this.eventShouldLiveIn(event); + if (shouldLiveInThread && !eventsByThread[threadId]) { + eventsByThread[threadId] = []; + } + (_a = eventsByThread[threadId]) === null || _a === void 0 ? void 0 : _a.push(event); + } + Object.entries(eventsByThread).map(([threadId, threadEvents]) => this.addThreadedEvents(threadId, threadEvents, toStartOfTimeline)); + } + createThread(threadId, rootEvent, events = [], toStartOfTimeline) { + var _a, _b, _c; + if (this.threads.has(threadId)) { + return this.threads.get(threadId); + } + if (rootEvent) { + const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId()); + if (relatedEvents === null || relatedEvents === void 0 ? void 0 : relatedEvents.length) { + // Include all relations of the root event, given it'll be visible in both timelines, + // except `m.replace` as that will already be applied atop the event using `MatrixEvent::makeReplaced` + events = events.concat(relatedEvents.filter((e) => !e.isRelation(event_2.RelationType.Replace))); + } + } + const thread = new thread_1.Thread(threadId, rootEvent, { + room: this, + client: this.client, + pendingEventOrdering: this.opts.pendingEventOrdering, + receipts: (_a = this.cachedThreadReadReceipts.get(threadId)) !== null && _a !== void 0 ? _a : [], + }); + // All read receipts should now come down from sync, we do not need to keep + // a reference to the cached receipts anymore. + this.cachedThreadReadReceipts.delete(threadId); + // If we managed to create a thread and figure out its `id` then we can use it + // This has to happen before thread.addEvents, because that adds events to the eventtimeline, and the + // eventtimeline sometimes looks up thread information via the room. + this.threads.set(thread.id, thread); + // This is necessary to be able to jump to events in threads: + // If we jump to an event in a thread where neither the event, nor the root, + // nor any thread event are loaded yet, we'll load the event as well as the thread root, create the thread, + // and pass the event through this. + thread.addEvents(events, false); + this.reEmitter.reEmit(thread, [ + thread_1.ThreadEvent.Delete, + thread_1.ThreadEvent.Update, + thread_1.ThreadEvent.NewReply, + RoomEvent.Timeline, + RoomEvent.TimelineReset, + ]); + const isNewer = ((_b = this.lastThread) === null || _b === void 0 ? void 0 : _b.rootEvent) && + (rootEvent === null || rootEvent === void 0 ? void 0 : rootEvent.localTimestamp) && + ((_c = this.lastThread.rootEvent) === null || _c === void 0 ? void 0 : _c.localTimestamp) < (rootEvent === null || rootEvent === void 0 ? void 0 : rootEvent.localTimestamp); + if (!this.lastThread || isNewer) { + this.lastThread = thread; + } + if (this.threadsReady) { + this.updateThreadRootEvents(thread, toStartOfTimeline, false); + } + this.emit(thread_1.ThreadEvent.New, thread, toStartOfTimeline); + return thread; + } + processLiveEvent(event) { + this.applyRedaction(event); + // Implement MSC3531: hiding messages. + if (event.isVisibilityEvent()) { + // This event changes the visibility of another event, record + // the visibility change, inform clients if necessary. + this.applyNewVisibilityEvent(event); + } + // If any pending visibility change is waiting for this (older) event, + this.applyPendingVisibilityEvents(event); + // Sliding Sync modifications: + // The proxy cannot guarantee every sent event will have a transaction_id field, so we need + // to check the event ID against the list of pending events if there is no transaction ID + // field. Only do this for events sent by us though as it's potentially expensive to loop + // the pending events map. + const txnId = event.getUnsigned().transaction_id; + if (!txnId && event.getSender() === this.myUserId) { + // check the txn map for a matching event ID + for (const [tid, localEvent] of this.txnToEvent) { + if (localEvent.getId() === event.getId()) { + logger_1.logger.debug("processLiveEvent: found sent event without txn ID: ", tid, event.getId()); + // update the unsigned field so we can re-use the same codepaths + const unsigned = event.getUnsigned(); + unsigned.transaction_id = tid; + event.setUnsigned(unsigned); + break; + } + } + } + } + /** + * Add an event to the end of this room's live timelines. Will fire + * "Room.timeline". + * + * @param event - Event to be added + * @param addLiveEventOptions - addLiveEvent options + * @internal + * + * @remarks + * Fires {@link RoomEvent.Timeline} + */ + addLiveEvent(event, addLiveEventOptions) { + const { duplicateStrategy, timelineWasEmpty, fromCache } = addLiveEventOptions; + // add to our timeline sets + for (const timelineSet of this.timelineSets) { + timelineSet.addLiveEvent(event, { + duplicateStrategy, + fromCache, + timelineWasEmpty, + }); + } + // synthesize and inject implicit read receipts + // Done after adding the event because otherwise the app would get a read receipt + // pointing to an event that wasn't yet in the timeline + // Don't synthesize RR for m.room.redaction as this causes the RR to go missing. + if (event.sender && event.getType() !== event_2.EventType.RoomRedaction) { + this.addReceipt((0, read_receipt_1.synthesizeReceipt)(event.sender.userId, event, read_receipts_1.ReceiptType.Read), true); + // Any live events from a user could be taken as implicit + // presence information: evidence that they are currently active. + // ...except in a world where we use 'user.currentlyActive' to reduce + // presence spam, this isn't very useful - we'll get a transition when + // they are no longer currently active anyway. So don't bother to + // reset the lastActiveAgo and lastPresenceTs from the RoomState's user. + } + } + /** + * Add a pending outgoing event to this room. + * + *

The event is added to either the pendingEventList, or the live timeline, + * depending on the setting of opts.pendingEventOrdering. + * + *

This is an internal method, intended for use by MatrixClient. + * + * @param event - The event to add. + * + * @param txnId - Transaction id for this outgoing event + * + * @throws if the event doesn't have status SENDING, or we aren't given a + * unique transaction id. + * + * @remarks + * Fires {@link RoomEvent.LocalEchoUpdated} + */ + addPendingEvent(event, txnId) { + if (event.status !== event_status_1.EventStatus.SENDING && event.status !== event_status_1.EventStatus.NOT_SENT) { + throw new Error("addPendingEvent called on an event with status " + event.status); + } + if (this.txnToEvent.get(txnId)) { + throw new Error("addPendingEvent called on an event with known txnId " + txnId); + } + // call setEventMetadata to set up event.sender etc + // as event is shared over all timelineSets, we set up its metadata based + // on the unfiltered timelineSet. + event_timeline_1.EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS), false); + this.txnToEvent.set(txnId, event); + if (this.pendingEventList) { + if (this.pendingEventList.some((e) => e.status === event_status_1.EventStatus.NOT_SENT)) { + logger_1.logger.warn("Setting event as NOT_SENT due to messages in the same state"); + event.setStatus(event_status_1.EventStatus.NOT_SENT); + } + this.pendingEventList.push(event); + this.savePendingEvents(); + if (event.isRelation()) { + // For pending events, add them to the relations collection immediately. + // (The alternate case below already covers this as part of adding to + // the timeline set.) + this.aggregateNonLiveRelation(event); + } + if (event.isRedaction()) { + const redactId = event.event.redacts; + let redactedEvent = this.pendingEventList.find((e) => e.getId() === redactId); + if (!redactedEvent && redactId) { + redactedEvent = this.findEventById(redactId); + } + if (redactedEvent) { + redactedEvent.markLocallyRedacted(event); + this.emit(RoomEvent.Redaction, event, this); + } + } + } + else { + for (const timelineSet of this.timelineSets) { + if (timelineSet.getFilter()) { + if (timelineSet.getFilter().filterRoomTimeline([event]).length) { + timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), { + toStartOfTimeline: false, + }); + } + } + else { + timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), { + toStartOfTimeline: false, + }); + } + } + } + this.emit(RoomEvent.LocalEchoUpdated, event, this); + } + /** + * Persists all pending events to local storage + * + * If the current room is encrypted only encrypted events will be persisted + * all messages that are not yet encrypted will be discarded + * + * This is because the flow of EVENT_STATUS transition is + * `queued => sending => encrypting => sending => sent` + * + * Steps 3 and 4 are skipped for unencrypted room. + * It is better to discard an unencrypted message rather than persisting + * it locally for everyone to read + */ + savePendingEvents() { + if (this.pendingEventList) { + const pendingEvents = this.pendingEventList + .map((event) => { + return Object.assign(Object.assign({}, event.event), { txn_id: event.getTxnId() }); + }) + .filter((event) => { + // Filter out the unencrypted messages if the room is encrypted + const isEventEncrypted = event.type === event_2.EventType.RoomMessageEncrypted; + const isRoomEncrypted = this.client.isRoomEncrypted(this.roomId); + return isEventEncrypted || !isRoomEncrypted; + }); + this.client.store.setPendingEvents(this.roomId, pendingEvents); + } + } + /** + * Used to aggregate the local echo for a relation, and also + * for re-applying a relation after it's redaction has been cancelled, + * as the local echo for the redaction of the relation would have + * un-aggregated the relation. Note that this is different from regular messages, + * which are just kept detached for their local echo. + * + * Also note that live events are aggregated in the live EventTimelineSet. + * @param event - the relation event that needs to be aggregated. + */ + aggregateNonLiveRelation(event) { + this.relations.aggregateChildEvent(event); + } + getEventForTxnId(txnId) { + return this.txnToEvent.get(txnId); + } + /** + * Deal with the echo of a message we sent. + * + *

We move the event to the live timeline if it isn't there already, and + * update it. + * + * @param remoteEvent - The event received from + * /sync + * @param localEvent - The local echo, which + * should be either in the pendingEventList or the timeline. + * + * @internal + * + * @remarks + * Fires {@link RoomEvent.LocalEchoUpdated} + */ + handleRemoteEcho(remoteEvent, localEvent) { + const oldEventId = localEvent.getId(); + const newEventId = remoteEvent.getId(); + const oldStatus = localEvent.status; + logger_1.logger.debug(`Got remote echo for event ${oldEventId} -> ${newEventId} old status ${oldStatus}`); + // no longer pending + this.txnToEvent.delete(remoteEvent.getUnsigned().transaction_id); + // if it's in the pending list, remove it + if (this.pendingEventList) { + this.removePendingEvent(oldEventId); + } + // replace the event source (this will preserve the plaintext payload if + // any, which is good, because we don't want to try decoding it again). + localEvent.handleRemoteEcho(remoteEvent.event); + const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(remoteEvent); + const thread = threadId ? this.getThread(threadId) : null; + thread === null || thread === void 0 ? void 0 : thread.setEventMetadata(localEvent); + thread === null || thread === void 0 ? void 0 : thread.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId); + if (shouldLiveInRoom) { + for (const timelineSet of this.timelineSets) { + // if it's already in the timeline, update the timeline map. If it's not, add it. + timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId); + } + } + this.emit(RoomEvent.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus); + } + /** + * Update the status / event id on a pending event, to reflect its transmission + * progress. + * + *

This is an internal method. + * + * @param event - local echo event + * @param newStatus - status to assign + * @param newEventId - new event id to assign. Ignored unless newStatus == EventStatus.SENT. + * + * @remarks + * Fires {@link RoomEvent.LocalEchoUpdated} + */ + updatePendingEvent(event, newStatus, newEventId) { + logger_1.logger.log(`setting pendingEvent status to ${newStatus} in ${event.getRoomId()} ` + + `event ID ${event.getId()} -> ${newEventId}`); + // if the message was sent, we expect an event id + if (newStatus == event_status_1.EventStatus.SENT && !newEventId) { + throw new Error("updatePendingEvent called with status=SENT, but no new event id"); + } + // SENT races against /sync, so we have to special-case it. + if (newStatus == event_status_1.EventStatus.SENT) { + const timeline = this.getTimelineForEvent(newEventId); + if (timeline) { + // we've already received the event via the event stream. + // nothing more to do here, assuming the transaction ID was correctly matched. + // Let's check that. + const remoteEvent = this.findEventById(newEventId); + const remoteTxnId = remoteEvent === null || remoteEvent === void 0 ? void 0 : remoteEvent.getUnsigned().transaction_id; + if (!remoteTxnId && remoteEvent) { + // This code path is mostly relevant for the Sliding Sync proxy. + // The remote event did not contain a transaction ID, so we did not handle + // the remote echo yet. Handle it now. + const unsigned = remoteEvent.getUnsigned(); + unsigned.transaction_id = event.getTxnId(); + remoteEvent.setUnsigned(unsigned); + // the remote event is _already_ in the timeline, so we need to remove it so + // we can convert the local event into the final event. + this.removeEvent(remoteEvent.getId()); + this.handleRemoteEcho(remoteEvent, event); + } + return; + } + } + const oldStatus = event.status; + const oldEventId = event.getId(); + if (!oldStatus) { + throw new Error("updatePendingEventStatus called on an event which is not a local echo."); + } + const allowed = ALLOWED_TRANSITIONS[oldStatus]; + if (!(allowed === null || allowed === void 0 ? void 0 : allowed.includes(newStatus))) { + throw new Error(`Invalid EventStatus transition ${oldStatus}->${newStatus}`); + } + event.setStatus(newStatus); + if (newStatus == event_status_1.EventStatus.SENT) { + // update the event id + event.replaceLocalEventId(newEventId); + const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event); + const thread = threadId ? this.getThread(threadId) : undefined; + thread === null || thread === void 0 ? void 0 : thread.setEventMetadata(event); + thread === null || thread === void 0 ? void 0 : thread.timelineSet.replaceEventId(oldEventId, newEventId); + if (shouldLiveInRoom) { + // if the event was already in the timeline (which will be the case if + // opts.pendingEventOrdering==chronological), we need to update the + // timeline map. + for (const timelineSet of this.timelineSets) { + timelineSet.replaceEventId(oldEventId, newEventId); + } + } + } + else if (newStatus == event_status_1.EventStatus.CANCELLED) { + // remove it from the pending event list, or the timeline. + if (this.pendingEventList) { + const removedEvent = this.getPendingEvent(oldEventId); + this.removePendingEvent(oldEventId); + if (removedEvent === null || removedEvent === void 0 ? void 0 : removedEvent.isRedaction()) { + this.revertRedactionLocalEcho(removedEvent); + } + } + this.removeEvent(oldEventId); + } + this.savePendingEvents(); + this.emit(RoomEvent.LocalEchoUpdated, event, this, oldEventId, oldStatus); + } + revertRedactionLocalEcho(redactionEvent) { + const redactId = redactionEvent.event.redacts; + if (!redactId) { + return; + } + const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId); + if (redactedEvent) { + redactedEvent.unmarkLocallyRedacted(); + // re-render after undoing redaction + this.emit(RoomEvent.RedactionCancelled, redactionEvent, this); + // reapply relation now redaction failed + if (redactedEvent.isRelation()) { + this.aggregateNonLiveRelation(redactedEvent); + } + } + } + addLiveEvents(events, duplicateStrategyOrOpts, fromCache = false) { + var _a; + let duplicateStrategy = duplicateStrategyOrOpts; + let timelineWasEmpty = false; + if (typeof duplicateStrategyOrOpts === "object") { + ({ + duplicateStrategy, + fromCache = false, + /* roomState, (not used here) */ + timelineWasEmpty, + } = duplicateStrategyOrOpts); + } + else if (duplicateStrategyOrOpts !== undefined) { + // Deprecation warning + // FIXME: Remove after 2023-06-01 (technical debt) + logger_1.logger.warn("Overload deprecated: " + + "`Room.addLiveEvents(events, duplicateStrategy?, fromCache?)` " + + "is deprecated in favor of the overload with `Room.addLiveEvents(events, IAddLiveEventOptions)`"); + } + if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) { + throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'"); + } + // sanity check that the live timeline is still live + for (let i = 0; i < this.timelineSets.length; i++) { + const liveTimeline = this.timelineSets[i].getLiveTimeline(); + if (liveTimeline.getPaginationToken(event_timeline_1.EventTimeline.FORWARDS)) { + throw new Error("live timeline " + + i + + " is no longer live - it has a pagination token " + + "(" + + liveTimeline.getPaginationToken(event_timeline_1.EventTimeline.FORWARDS) + + ")"); + } + if (liveTimeline.getNeighbouringTimeline(event_timeline_1.EventTimeline.FORWARDS)) { + throw new Error(`live timeline ${i} is no longer live - it has a neighbouring timeline`); + } + } + const threadRoots = this.findThreadRoots(events); + const eventsByThread = {}; + const options = { + duplicateStrategy, + fromCache, + timelineWasEmpty, + }; + for (const event of events) { + // TODO: We should have a filter to say "only add state event types X Y Z to the timeline". + this.processLiveEvent(event); + if (event.getUnsigned().transaction_id) { + const existingEvent = this.txnToEvent.get(event.getUnsigned().transaction_id); + if (existingEvent) { + // remote echo of an event we sent earlier + this.handleRemoteEcho(event, existingEvent); + continue; // we can skip adding the event to the timeline sets, it is already there + } + } + const { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn(event, events, threadRoots); + if (shouldLiveInThread && !eventsByThread[threadId !== null && threadId !== void 0 ? threadId : ""]) { + eventsByThread[threadId !== null && threadId !== void 0 ? threadId : ""] = []; + } + (_a = eventsByThread[threadId !== null && threadId !== void 0 ? threadId : ""]) === null || _a === void 0 ? void 0 : _a.push(event); + if (shouldLiveInRoom) { + this.addLiveEvent(event, options); + } + } + Object.entries(eventsByThread).forEach(([threadId, threadEvents]) => { + this.addThreadedEvents(threadId, threadEvents, false); + }); + } + partitionThreadedEvents(events) { + // Indices to the events array, for readability + const ROOM = 0; + const THREAD = 1; + if (this.client.supportsThreads()) { + const threadRoots = this.findThreadRoots(events); + return events.reduce((memo, event) => { + const { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn(event, events, threadRoots); + if (shouldLiveInRoom) { + memo[ROOM].push(event); + } + if (shouldLiveInThread) { + event.setThreadId(threadId !== null && threadId !== void 0 ? threadId : ""); + memo[THREAD].push(event); + } + return memo; + }, [[], []]); + } + else { + // When `experimentalThreadSupport` is disabled treat all events as timelineEvents + return [events, []]; + } + } + /** + * Given some events, find the IDs of all the thread roots that are referred to by them. + */ + findThreadRoots(events) { + var _a; + const threadRoots = new Set(); + for (const event of events) { + if (event.isRelation(thread_1.THREAD_RELATION_TYPE.name)) { + threadRoots.add((_a = event.relationEventId) !== null && _a !== void 0 ? _a : ""); + } + } + return threadRoots; + } + /** + * Add a receipt event to the room. + * @param event - The m.receipt event. + * @param synthetic - True if this event is implicit. + */ + addReceipt(event, synthetic = false) { + const content = event.getContent(); + Object.keys(content).forEach((eventId) => { + Object.keys(content[eventId]).forEach((receiptType) => { + Object.keys(content[eventId][receiptType]).forEach((userId) => { + var _a, _b, _c, _d; + const receipt = content[eventId][receiptType][userId]; + const receiptForMainTimeline = !receipt.thread_id || receipt.thread_id === read_receipts_1.MAIN_ROOM_TIMELINE; + const receiptDestination = receiptForMainTimeline + ? this + : this.threads.get((_a = receipt.thread_id) !== null && _a !== void 0 ? _a : ""); + if (receiptDestination) { + receiptDestination.addReceiptToStructure(eventId, receiptType, userId, receipt, synthetic); + // If the read receipt sent for the logged in user matches + // the last event of the live timeline, then we know for a fact + // that the user has read that message. + // We can mark the room as read and not wait for the local echo + // from synapse + // This needs to be done after the initial sync as we do not want this + // logic to run whilst the room is being initialised + if (this.client.isInitialSyncComplete() && userId === this.client.getUserId()) { + const lastEvent = receiptDestination.timeline[receiptDestination.timeline.length - 1]; + if (lastEvent && eventId === lastEvent.getId() && userId === lastEvent.getSender()) { + receiptDestination.setUnread(NotificationCountType.Total, 0); + receiptDestination.setUnread(NotificationCountType.Highlight, 0); + } + } + } + else { + // The thread does not exist locally, keep the read receipt + // in a cache locally, and re-apply the `addReceipt` logic + // when the thread is created + this.cachedThreadReadReceipts.set(receipt.thread_id, [ + ...((_b = this.cachedThreadReadReceipts.get(receipt.thread_id)) !== null && _b !== void 0 ? _b : []), + { eventId, receiptType, userId, receipt, synthetic }, + ]); + } + const me = this.client.getUserId(); + // Track the time of the current user's oldest threaded receipt in the room. + if (userId === me && !receiptForMainTimeline && receipt.ts < this.oldestThreadedReceiptTs) { + this.oldestThreadedReceiptTs = receipt.ts; + } + // Track each user's unthreaded read receipt. + if (!receipt.thread_id && receipt.ts > ((_d = (_c = this.unthreadedReceipts.get(userId)) === null || _c === void 0 ? void 0 : _c.ts) !== null && _d !== void 0 ? _d : 0)) { + this.unthreadedReceipts.set(userId, receipt); + } + }); + }); + }); + // send events after we've regenerated the structure & cache, otherwise things that + // listened for the event would read stale data. + this.emit(RoomEvent.Receipt, event, this); + } + /** + * Adds/handles ephemeral events such as typing notifications and read receipts. + * @param events - A list of events to process + */ + addEphemeralEvents(events) { + for (const event of events) { + if (event.getType() === event_2.EventType.Typing) { + this.currentState.setTypingEvent(event); + } + else if (event.getType() === event_2.EventType.Receipt) { + this.addReceipt(event); + } // else ignore - life is too short for us to care about these events + } + } + /** + * Removes events from this room. + * @param eventIds - A list of eventIds to remove. + */ + removeEvents(eventIds) { + for (const eventId of eventIds) { + this.removeEvent(eventId); + } + } + /** + * Removes a single event from this room. + * + * @param eventId - The id of the event to remove + * + * @returns true if the event was removed from any of the room's timeline sets + */ + removeEvent(eventId) { + let removedAny = false; + for (const timelineSet of this.timelineSets) { + const removed = timelineSet.removeEvent(eventId); + if (removed) { + if (removed.isRedaction()) { + this.revertRedactionLocalEcho(removed); + } + removedAny = true; + } + } + return removedAny; + } + /** + * Recalculate various aspects of the room, including the room name and + * room summary. Call this any time the room's current state is modified. + * May fire "Room.name" if the room name is updated. + * + * @remarks + * Fires {@link RoomEvent.Name} + */ + recalculate() { + // set fake stripped state events if this is an invite room so logic remains + // consistent elsewhere. + const membershipEvent = this.currentState.getStateEvents(event_2.EventType.RoomMember, this.myUserId); + if (membershipEvent) { + const membership = membershipEvent.getContent().membership; + this.updateMyMembership(membership); + if (membership === "invite") { + const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || []; + strippedStateEvents.forEach((strippedEvent) => { + const existingEvent = this.currentState.getStateEvents(strippedEvent.type, strippedEvent.state_key); + if (!existingEvent) { + // set the fake stripped event instead + this.currentState.setStateEvents([ + new event_1.MatrixEvent({ + type: strippedEvent.type, + state_key: strippedEvent.state_key, + content: strippedEvent.content, + event_id: "$fake" + Date.now(), + room_id: this.roomId, + user_id: this.myUserId, // technically a lie + }), + ]); + } + }); + } + } + const oldName = this.name; + this.name = this.calculateRoomName(this.myUserId); + this.normalizedName = (0, utils_1.normalize)(this.name); + this.summary = new room_summary_1.RoomSummary(this.roomId, { + title: this.name, + }); + if (oldName !== this.name) { + this.emit(RoomEvent.Name, this); + } + } + /** + * Update the room-tag event for the room. The previous one is overwritten. + * @param event - the m.tag event + */ + addTags(event) { + // event content looks like: + // content: { + // tags: { + // $tagName: { $metadata: $value }, + // $tagName: { $metadata: $value }, + // } + // } + // XXX: do we need to deep copy here? + this.tags = event.getContent().tags || {}; + // XXX: we could do a deep-comparison to see if the tags have really + // changed - but do we want to bother? + this.emit(RoomEvent.Tags, event, this); + } + /** + * Update the account_data events for this room, overwriting events of the same type. + * @param events - an array of account_data events to add + */ + addAccountData(events) { + for (const event of events) { + if (event.getType() === "m.tag") { + this.addTags(event); + } + const eventType = event.getType(); + const lastEvent = this.accountData.get(eventType); + this.accountData.set(eventType, event); + this.emit(RoomEvent.AccountData, event, this, lastEvent); + } + } + /** + * Access account_data event of given event type for this room + * @param type - the type of account_data event to be accessed + * @returns the account_data event in question + */ + getAccountData(type) { + return this.accountData.get(type); + } + /** + * Returns whether the syncing user has permission to send a message in the room + * @returns true if the user should be permitted to send + * message events into the room. + */ + maySendMessage() { + return (this.getMyMembership() === "join" && + (this.client.isRoomEncrypted(this.roomId) + ? this.currentState.maySendEvent(event_2.EventType.RoomMessageEncrypted, this.myUserId) + : this.currentState.maySendEvent(event_2.EventType.RoomMessage, this.myUserId))); + } + /** + * Returns whether the given user has permissions to issue an invite for this room. + * @param userId - the ID of the Matrix user to check permissions for + * @returns true if the user should be permitted to issue invites for this room. + */ + canInvite(userId) { + let canInvite = this.getMyMembership() === "join"; + const powerLevelsEvent = this.currentState.getStateEvents(event_2.EventType.RoomPowerLevels, ""); + const powerLevels = powerLevelsEvent && powerLevelsEvent.getContent(); + const me = this.getMember(userId); + if (powerLevels && me && powerLevels.invite > me.powerLevel) { + canInvite = false; + } + return canInvite; + } + /** + * Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`. + * @returns the join_rule applied to this room + */ + getJoinRule() { + return this.currentState.getJoinRule(); + } + /** + * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`. + * @returns the history_visibility applied to this room + */ + getHistoryVisibility() { + return this.currentState.getHistoryVisibility(); + } + /** + * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`. + * @returns the history_visibility applied to this room + */ + getGuestAccess() { + return this.currentState.getGuestAccess(); + } + /** + * Returns the type of the room from the `m.room.create` event content or undefined if none is set + * @returns the type of the room. + */ + getType() { + const createEvent = this.currentState.getStateEvents(event_2.EventType.RoomCreate, ""); + if (!createEvent) { + if (!this.getTypeWarning) { + logger_1.logger.warn("[getType] Room " + this.roomId + " does not have an m.room.create event"); + this.getTypeWarning = true; + } + return undefined; + } + return createEvent.getContent()[event_2.RoomCreateTypeField]; + } + /** + * Returns whether the room is a space-room as defined by MSC1772. + * @returns true if the room's type is RoomType.Space + */ + isSpaceRoom() { + return this.getType() === event_2.RoomType.Space; + } + /** + * Returns whether the room is a call-room as defined by MSC3417. + * @returns true if the room's type is RoomType.UnstableCall + */ + isCallRoom() { + return this.getType() === event_2.RoomType.UnstableCall; + } + /** + * Returns whether the room is a video room. + * @returns true if the room's type is RoomType.ElementVideo + */ + isElementVideoRoom() { + return this.getType() === event_2.RoomType.ElementVideo; + } + /** + * Find the predecessor of this room. + * + * @param msc3946ProcessDynamicPredecessor - if true, look for an + * m.room.predecessor state event and use it if found (MSC3946). + * @returns null if this room has no predecessor. Otherwise, returns + * the roomId, last eventId and viaServers of the predecessor room. + * + * If msc3946ProcessDynamicPredecessor is true, use m.predecessor events + * as well as m.room.create events to find predecessors. + * + * Note: if an m.predecessor event is used, eventId may be undefined + * since last_known_event_id is optional. + * + * Note: viaServers may be undefined, and will definitely be undefined if + * this predecessor comes from a RoomCreate event (rather than a + * RoomPredecessor, which has the optional via_servers property). + */ + findPredecessor(msc3946ProcessDynamicPredecessor = false) { + const currentState = this.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + if (!currentState) { + return null; + } + return currentState.findPredecessor(msc3946ProcessDynamicPredecessor); + } + roomNameGenerator(state) { + if (this.client.roomNameGenerator) { + const name = this.client.roomNameGenerator(this.roomId, state); + if (name !== null) { + return name; + } + } + switch (state.type) { + case RoomNameType.Actual: + return state.name; + case RoomNameType.Generated: + switch (state.subtype) { + case "Inviting": + return `Inviting ${memberNamesToRoomName(state.names, state.count)}`; + default: + return memberNamesToRoomName(state.names, state.count); + } + case RoomNameType.EmptyRoom: + if (state.oldName) { + return `Empty room (was ${state.oldName})`; + } + else { + return "Empty room"; + } + } + } + /** + * This is an internal method. Calculates the name of the room from the current + * room state. + * @param userId - The client's user ID. Used to filter room members + * correctly. + * @param ignoreRoomNameEvent - Return the implicit room name that we'd see if there + * was no m.room.name event. + * @returns The calculated room name. + */ + calculateRoomName(userId, ignoreRoomNameEvent = false) { + if (!ignoreRoomNameEvent) { + // check for an alias, if any. for now, assume first alias is the + // official one. + const mRoomName = this.currentState.getStateEvents(event_2.EventType.RoomName, ""); + if (mRoomName === null || mRoomName === void 0 ? void 0 : mRoomName.getContent().name) { + return this.roomNameGenerator({ + type: RoomNameType.Actual, + name: mRoomName.getContent().name, + }); + } + } + const alias = this.getCanonicalAlias(); + if (alias) { + return this.roomNameGenerator({ + type: RoomNameType.Actual, + name: alias, + }); + } + const joinedMemberCount = this.currentState.getJoinedMemberCount(); + const invitedMemberCount = this.currentState.getInvitedMemberCount(); + // -1 because these numbers include the syncing user + let inviteJoinCount = joinedMemberCount + invitedMemberCount - 1; + // get service members (e.g. helper bots) for exclusion + let excludedUserIds = []; + const mFunctionalMembers = this.currentState.getStateEvents(event_2.UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name, ""); + if (Array.isArray(mFunctionalMembers === null || mFunctionalMembers === void 0 ? void 0 : mFunctionalMembers.getContent().service_members)) { + excludedUserIds = mFunctionalMembers.getContent().service_members; + } + // get members that are NOT ourselves and are actually in the room. + let otherNames = []; + if (this.summaryHeroes) { + // if we have a summary, the member state events should be in the room state + this.summaryHeroes.forEach((userId) => { + // filter service members + if (excludedUserIds.includes(userId)) { + inviteJoinCount--; + return; + } + const member = this.getMember(userId); + otherNames.push(member ? member.name : userId); + }); + } + else { + let otherMembers = this.currentState.getMembers().filter((m) => { + return m.userId !== userId && (m.membership === "invite" || m.membership === "join"); + }); + otherMembers = otherMembers.filter(({ userId }) => { + // filter service members + if (excludedUserIds.includes(userId)) { + inviteJoinCount--; + return false; + } + return true; + }); + // make sure members have stable order + otherMembers.sort((a, b) => utils.compare(a.userId, b.userId)); + // only 5 first members, immitate summaryHeroes + otherMembers = otherMembers.slice(0, 5); + otherNames = otherMembers.map((m) => m.name); + } + if (inviteJoinCount) { + return this.roomNameGenerator({ + type: RoomNameType.Generated, + names: otherNames, + count: inviteJoinCount, + }); + } + const myMembership = this.getMyMembership(); + // if I have created a room and invited people through + // 3rd party invites + if (myMembership == "join") { + const thirdPartyInvites = this.currentState.getStateEvents(event_2.EventType.RoomThirdPartyInvite); + if (thirdPartyInvites === null || thirdPartyInvites === void 0 ? void 0 : thirdPartyInvites.length) { + const thirdPartyNames = thirdPartyInvites.map((i) => { + return i.getContent().display_name; + }); + return this.roomNameGenerator({ + type: RoomNameType.Generated, + subtype: "Inviting", + names: thirdPartyNames, + count: thirdPartyNames.length + 1, + }); + } + } + // let's try to figure out who was here before + let leftNames = otherNames; + // if we didn't have heroes, try finding them in the room state + if (!leftNames.length) { + leftNames = this.currentState + .getMembers() + .filter((m) => { + return m.userId !== userId && m.membership !== "invite" && m.membership !== "join"; + }) + .map((m) => m.name); + } + let oldName; + if (leftNames.length) { + oldName = this.roomNameGenerator({ + type: RoomNameType.Generated, + names: leftNames, + count: leftNames.length + 1, + }); + } + return this.roomNameGenerator({ + type: RoomNameType.EmptyRoom, + oldName, + }); + } + /** + * When we receive a new visibility change event: + * + * - store this visibility change alongside the timeline, in case we + * later need to apply it to an event that we haven't received yet; + * - if we have already received the event whose visibility has changed, + * patch it to reflect the visibility change and inform listeners. + */ + applyNewVisibilityEvent(event) { + const visibilityChange = event.asVisibilityChange(); + if (!visibilityChange) { + // The event is ill-formed. + return; + } + // Ignore visibility change events that are not emitted by moderators. + const userId = event.getSender(); + if (!userId) { + return; + } + const isPowerSufficient = (event_2.EVENT_VISIBILITY_CHANGE_TYPE.name && + this.currentState.maySendStateEvent(event_2.EVENT_VISIBILITY_CHANGE_TYPE.name, userId)) || + (event_2.EVENT_VISIBILITY_CHANGE_TYPE.altName && + this.currentState.maySendStateEvent(event_2.EVENT_VISIBILITY_CHANGE_TYPE.altName, userId)); + if (!isPowerSufficient) { + // Powerlevel is insufficient. + return; + } + // Record this change in visibility. + // If the event is not in our timeline and we only receive it later, + // we may need to apply the visibility change at a later date. + const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(visibilityChange.eventId); + if (visibilityEventsOnOriginalEvent) { + // It would be tempting to simply erase the latest visibility change + // but we need to record all of the changes in case the latest change + // is ever redacted. + // + // In practice, linear scans through `visibilityEvents` should be fast. + // However, to protect against a potential DoS attack, we limit the + // number of iterations in this loop. + let index = visibilityEventsOnOriginalEvent.length - 1; + const min = Math.max(0, visibilityEventsOnOriginalEvent.length - MAX_NUMBER_OF_VISIBILITY_EVENTS_TO_SCAN_THROUGH); + for (; index >= min; --index) { + const target = visibilityEventsOnOriginalEvent[index]; + if (target.getTs() < event.getTs()) { + break; + } + } + if (index === -1) { + visibilityEventsOnOriginalEvent.unshift(event); + } + else { + visibilityEventsOnOriginalEvent.splice(index + 1, 0, event); + } + } + else { + this.visibilityEvents.set(visibilityChange.eventId, [event]); + } + // Finally, let's check if the event is already in our timeline. + // If so, we need to patch it and inform listeners. + const originalEvent = this.findEventById(visibilityChange.eventId); + if (!originalEvent) { + return; + } + originalEvent.applyVisibilityEvent(visibilityChange); + } + redactVisibilityChangeEvent(event) { + // Sanity checks. + if (!event.isVisibilityEvent) { + throw new Error("expected a visibility change event"); + } + const relation = event.getRelation(); + const originalEventId = relation === null || relation === void 0 ? void 0 : relation.event_id; + const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId); + if (!visibilityEventsOnOriginalEvent) { + // No visibility changes on the original event. + // In particular, this change event was not recorded, + // most likely because it was ill-formed. + return; + } + const index = visibilityEventsOnOriginalEvent.findIndex((change) => change.getId() === event.getId()); + if (index === -1) { + // This change event was not recorded, most likely because + // it was ill-formed. + return; + } + // Remove visibility change. + visibilityEventsOnOriginalEvent.splice(index, 1); + // If we removed the latest visibility change event, propagate changes. + if (index === visibilityEventsOnOriginalEvent.length) { + const originalEvent = this.findEventById(originalEventId); + if (!originalEvent) { + return; + } + if (index === 0) { + // We have just removed the only visibility change event. + this.visibilityEvents.delete(originalEventId); + originalEvent.applyVisibilityEvent(); + } + else { + const newEvent = visibilityEventsOnOriginalEvent[visibilityEventsOnOriginalEvent.length - 1]; + const newVisibility = newEvent.asVisibilityChange(); + if (!newVisibility) { + // Event is ill-formed. + // This breaks our invariant. + throw new Error("at this stage, visibility changes should be well-formed"); + } + originalEvent.applyVisibilityEvent(newVisibility); + } + } + } + /** + * When we receive an event whose visibility has been altered by + * a (more recent) visibility change event, patch the event in + * place so that clients now not to display it. + * + * @param event - Any matrix event. If this event has at least one a + * pending visibility change event, apply the latest visibility + * change event. + */ + applyPendingVisibilityEvents(event) { + const visibilityEvents = this.visibilityEvents.get(event.getId()); + if (!visibilityEvents || visibilityEvents.length == 0) { + // No pending visibility change in store. + return; + } + const visibilityEvent = visibilityEvents[visibilityEvents.length - 1]; + const visibilityChange = visibilityEvent.asVisibilityChange(); + if (!visibilityChange) { + return; + } + if (visibilityChange.visible) { + // Events are visible by default, no need to apply a visibility change. + // Note that we need to keep the visibility changes in `visibilityEvents`, + // in case we later fetch an older visibility change event that is superseded + // by `visibilityChange`. + } + if (visibilityEvent.getTs() < event.getTs()) { + // Something is wrong, the visibility change cannot happen before the + // event. Presumably an ill-formed event. + return; + } + event.applyVisibilityEvent(visibilityChange); + } + /** + * Find when a client has gained thread capabilities by inspecting the oldest + * threaded receipt + * @returns the timestamp of the oldest threaded receipt + */ + getOldestThreadedReceiptTs() { + return this.oldestThreadedReceiptTs; + } + /** + * Returns the most recent unthreaded receipt for a given user + * @param userId - the MxID of the User + * @returns an unthreaded Receipt. Can be undefined if receipts have been disabled + * or a user chooses to use private read receipts (or we have simply not received + * a receipt from this user yet). + */ + getLastUnthreadedReceiptFor(userId) { + return this.unthreadedReceipts.get(userId); + } + /** + * This issue should also be addressed on synapse's side and is tracked as part + * of https://github.com/matrix-org/synapse/issues/14837 + * + * + * We consider a room fully read if the current user has sent + * the last event in the live timeline of that context and if the read receipt + * we have on record matches. + * This also detects all unread threads and applies the same logic to those + * contexts + */ + fixupNotifications(userId) { + super.fixupNotifications(userId); + const unreadThreads = this.getThreads().filter((thread) => this.getThreadUnreadNotificationCount(thread.id, NotificationCountType.Total) > 0); + for (const thread of unreadThreads) { + thread.fixupNotifications(userId); + } + } +} +exports.Room = Room; +// a map from current event status to a list of allowed next statuses +const ALLOWED_TRANSITIONS = { + [event_status_1.EventStatus.ENCRYPTING]: [event_status_1.EventStatus.SENDING, event_status_1.EventStatus.NOT_SENT, event_status_1.EventStatus.CANCELLED], + [event_status_1.EventStatus.SENDING]: [event_status_1.EventStatus.ENCRYPTING, event_status_1.EventStatus.QUEUED, event_status_1.EventStatus.NOT_SENT, event_status_1.EventStatus.SENT], + [event_status_1.EventStatus.QUEUED]: [event_status_1.EventStatus.SENDING, event_status_1.EventStatus.NOT_SENT, event_status_1.EventStatus.CANCELLED], + [event_status_1.EventStatus.SENT]: [], + [event_status_1.EventStatus.NOT_SENT]: [event_status_1.EventStatus.SENDING, event_status_1.EventStatus.QUEUED, event_status_1.EventStatus.CANCELLED], + [event_status_1.EventStatus.CANCELLED]: [], +}; +var RoomNameType; +(function (RoomNameType) { + RoomNameType[RoomNameType["EmptyRoom"] = 0] = "EmptyRoom"; + RoomNameType[RoomNameType["Generated"] = 1] = "Generated"; + RoomNameType[RoomNameType["Actual"] = 2] = "Actual"; +})(RoomNameType = exports.RoomNameType || (exports.RoomNameType = {})); +// Can be overriden by IMatrixClientCreateOpts::memberNamesToRoomNameFn +function memberNamesToRoomName(names, count) { + const countWithoutMe = count - 1; + if (!names.length) { + return "Empty room"; + } + else if (names.length === 1 && countWithoutMe <= 1) { + return names[0]; + } + else if (names.length === 2 && countWithoutMe <= 2) { + return `${names[0]} and ${names[1]}`; + } + else { + const plural = countWithoutMe > 1; + if (plural) { + return `${names[0]} and ${countWithoutMe} others`; + } + else { + return `${names[0]} and 1 other`; + } + } +} + +},{"../@types/event":306,"../@types/read_receipts":311,"../ReEmitter":317,"../client":321,"../content-repo":323,"../filter":364,"../logger":374,"../utils":416,"./beacon":378,"./event":383,"./event-status":380,"./event-timeline":382,"./event-timeline-set":381,"./poll":385,"./read-receipt":386,"./relations-container":387,"./room-member":389,"./room-state":390,"./room-summary":391,"./thread":394,"matrix-events-sdk":167}],393:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SearchResult = void 0; +const event_context_1 = require("./event-context"); +class SearchResult { + /** + * Create a SearchResponse from the response to /search + */ + static fromJson(jsonObj, eventMapper) { + const jsonContext = jsonObj.context || {}; + let eventsBefore = (jsonContext.events_before || []).map(eventMapper); + let eventsAfter = (jsonContext.events_after || []).map(eventMapper); + const context = new event_context_1.EventContext(eventMapper(jsonObj.result)); + // Filter out any contextual events which do not correspond to the same timeline (thread or room) + const threadRootId = context.ourEvent.threadRootId; + eventsBefore = eventsBefore.filter((e) => e.threadRootId === threadRootId); + eventsAfter = eventsAfter.filter((e) => e.threadRootId === threadRootId); + context.setPaginateToken(jsonContext.start, true); + context.addEvents(eventsBefore, true); + context.addEvents(eventsAfter, false); + context.setPaginateToken(jsonContext.end, false); + return new SearchResult(jsonObj.rank, context); + } + /** + * Construct a new SearchResult + * + * @param rank - where this SearchResult ranks in the results + * @param context - the matching event and its + * context + */ + constructor(rank, context) { + this.rank = rank; + this.context = context; + } +} +exports.SearchResult = SearchResult; + +},{"./event-context":379}],394:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.threadFilterTypeToFilter = exports.ThreadFilterType = exports.THREAD_RELATION_TYPE = exports.FILTER_RELATED_BY_REL_TYPES = exports.FILTER_RELATED_BY_SENDERS = exports.Thread = exports.determineFeatureSupport = exports.FeatureSupport = exports.ThreadEvent = void 0; +const client_1 = require("../client"); +const ReEmitter_1 = require("../ReEmitter"); +const event_1 = require("../@types/event"); +const event_2 = require("./event"); +const event_timeline_1 = require("./event-timeline"); +const event_timeline_set_1 = require("./event-timeline-set"); +const room_1 = require("./room"); +const NamespacedValue_1 = require("../NamespacedValue"); +const logger_1 = require("../logger"); +const read_receipt_1 = require("./read-receipt"); +const read_receipts_1 = require("../@types/read_receipts"); +var ThreadEvent; +(function (ThreadEvent) { + ThreadEvent["New"] = "Thread.new"; + ThreadEvent["Update"] = "Thread.update"; + ThreadEvent["NewReply"] = "Thread.newReply"; + ThreadEvent["ViewThread"] = "Thread.viewThread"; + ThreadEvent["Delete"] = "Thread.delete"; +})(ThreadEvent = exports.ThreadEvent || (exports.ThreadEvent = {})); +var FeatureSupport; +(function (FeatureSupport) { + FeatureSupport[FeatureSupport["None"] = 0] = "None"; + FeatureSupport[FeatureSupport["Experimental"] = 1] = "Experimental"; + FeatureSupport[FeatureSupport["Stable"] = 2] = "Stable"; +})(FeatureSupport = exports.FeatureSupport || (exports.FeatureSupport = {})); +function determineFeatureSupport(stable, unstable) { + if (stable) { + return FeatureSupport.Stable; + } + else if (unstable) { + return FeatureSupport.Experimental; + } + else { + return FeatureSupport.None; + } +} +exports.determineFeatureSupport = determineFeatureSupport; +class Thread extends read_receipt_1.ReadReceipt { + constructor(id, rootEvent, opts) { + var _a; + super(); + this.id = id; + this.rootEvent = rootEvent; + this.timeline = []; + this._currentUserParticipated = false; + this.replyCount = 0; + this.pendingReplyCount = 0; + this.initialEventsFetched = !Thread.hasServerSideSupport; + /** + * An array of events to add to the timeline once the thread has been initialised + * with server suppport. + */ + this.replayEvents = []; + this.onBeforeRedaction = (event, redaction) => { + if ((event === null || event === void 0 ? void 0 : event.isRelation(exports.THREAD_RELATION_TYPE.name)) && + this.room.eventShouldLiveIn(event).threadId === this.id && + event.getId() !== this.id && // the root event isn't counted in the length so ignore this redaction + !redaction.status // only respect it when it succeeds + ) { + this.replyCount--; + this.updatePendingReplyCount(); + this.emit(ThreadEvent.Update, this); + } + }; + this.onRedaction = (event) => __awaiter(this, void 0, void 0, function* () { + if (event.threadRootId !== this.id) + return; // ignore redactions for other timelines + if (this.replyCount <= 0) { + for (const threadEvent of this.timeline) { + this.clearEventMetadata(threadEvent); + } + this.lastEvent = this.rootEvent; + this._currentUserParticipated = false; + this.emit(ThreadEvent.Delete, this); + } + else { + yield this.updateThreadMetadata(); + } + }); + this.onTimelineEvent = (event, room, toStartOfTimeline) => { + // Add a synthesized receipt when paginating forward in the timeline + if (!toStartOfTimeline) { + room.addLocalEchoReceipt(event.getSender(), event, read_receipts_1.ReceiptType.Read); + } + this.onEcho(event, toStartOfTimeline !== null && toStartOfTimeline !== void 0 ? toStartOfTimeline : false); + }; + this.onLocalEcho = (event) => { + this.onEcho(event, false); + }; + this.onEcho = (event, toStartOfTimeline) => __awaiter(this, void 0, void 0, function* () { + if (event.threadRootId !== this.id) + return; // ignore echoes for other timelines + if (this.lastEvent === event) + return; // ignore duplicate events + yield this.updateThreadMetadata(); + if (!event.isRelation(exports.THREAD_RELATION_TYPE.name)) + return; // don't send a new reply event for reactions or edits + if (toStartOfTimeline) + return; // ignore messages added to the start of the timeline + this.emit(ThreadEvent.NewReply, this, event); + }); + if (!(opts === null || opts === void 0 ? void 0 : opts.room)) { + // Logging/debugging for https://github.com/vector-im/element-web/issues/22141 + // Hope is that we end up with a more obvious stack trace. + throw new Error("element-web#22141: A thread requires a room in order to function"); + } + this.room = opts.room; + this.client = opts.client; + this.pendingEventOrdering = (_a = opts.pendingEventOrdering) !== null && _a !== void 0 ? _a : client_1.PendingEventOrdering.Chronological; + this.timelineSet = new event_timeline_set_1.EventTimelineSet(this.room, { + timelineSupport: true, + pendingEvents: true, + }, this.client, this); + this.reEmitter = new ReEmitter_1.TypedReEmitter(this); + this.reEmitter.reEmit(this.timelineSet, [room_1.RoomEvent.Timeline, room_1.RoomEvent.TimelineReset]); + this.room.on(event_2.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); + this.room.on(room_1.RoomEvent.Redaction, this.onRedaction); + this.room.on(room_1.RoomEvent.LocalEchoUpdated, this.onLocalEcho); + this.timelineSet.on(room_1.RoomEvent.Timeline, this.onTimelineEvent); + this.processReceipts(opts.receipts); + // even if this thread is thought to be originating from this client, we initialise it as we may be in a + // gappy sync and a thread around this event may already exist. + this.updateThreadMetadata(); + this.setEventMetadata(this.rootEvent); + } + fetchRootEvent() { + return __awaiter(this, void 0, void 0, function* () { + this.rootEvent = this.room.findEventById(this.id); + // If the rootEvent does not exist in the local stores, then fetch it from the server. + try { + const eventData = yield this.client.fetchRoomEvent(this.roomId, this.id); + const mapper = this.client.getEventMapper(); + this.rootEvent = mapper(eventData); // will merge with existing event object if such is known + } + catch (e) { + logger_1.logger.error("Failed to fetch thread root to construct thread with", e); + } + yield this.processEvent(this.rootEvent); + }); + } + static setServerSideSupport(status) { + Thread.hasServerSideSupport = status; + if (status !== FeatureSupport.Stable) { + exports.FILTER_RELATED_BY_SENDERS.setPreferUnstable(true); + exports.FILTER_RELATED_BY_REL_TYPES.setPreferUnstable(true); + exports.THREAD_RELATION_TYPE.setPreferUnstable(true); + } + } + static setServerSideListSupport(status) { + Thread.hasServerSideListSupport = status; + } + static setServerSideFwdPaginationSupport(status) { + Thread.hasServerSideFwdPaginationSupport = status; + } + get roomState() { + return this.room.getLiveTimeline().getState(event_timeline_1.EventTimeline.FORWARDS); + } + addEventToTimeline(event, toStartOfTimeline) { + if (!this.findEventById(event.getId())) { + this.timelineSet.addEventToTimeline(event, this.liveTimeline, { + toStartOfTimeline, + fromCache: false, + roomState: this.roomState, + }); + this.timeline = this.events; + } + } + addEvents(events, toStartOfTimeline) { + events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false)); + this.updateThreadMetadata(); + } + /** + * Add an event to the thread and updates + * the tail/root references if needed + * Will fire "Thread.update" + * @param event - The event to add + * @param toStartOfTimeline - whether the event is being added + * to the start (and not the end) of the timeline. + * @param emit - whether to emit the Update event if the thread was updated or not. + */ + addEvent(event, toStartOfTimeline, emit = true) { + var _a, _b, _c; + return __awaiter(this, void 0, void 0, function* () { + this.setEventMetadata(event); + const lastReply = this.lastReply(); + const isNewestReply = !lastReply || event.localTimestamp >= lastReply.localTimestamp; + // Add all incoming events to the thread's timeline set when there's no server support + if (!Thread.hasServerSideSupport) { + // all the relevant membership info to hydrate events with a sender + // is held in the main room timeline + // We want to fetch the room state from there and pass it down to this thread + // timeline set to let it reconcile an event with its relevant RoomMember + this.addEventToTimeline(event, toStartOfTimeline); + this.client.decryptEventIfNeeded(event, {}); + } + else if (!toStartOfTimeline && this.initialEventsFetched && isNewestReply) { + this.addEventToTimeline(event, false); + this.fetchEditsWhereNeeded(event); + } + else if (event.isRelation(event_1.RelationType.Annotation) || event.isRelation(event_1.RelationType.Replace)) { + if (!this.initialEventsFetched) { + /** + * A thread can be fully discovered via a single sync response + * And when that's the case we still ask the server to do an initialisation + * as it's the safest to ensure we have everything. + * However when we are in that scenario we might loose annotation or edits + * + * This fix keeps a reference to those events and replay them once the thread + * has been initialised properly. + */ + (_a = this.replayEvents) === null || _a === void 0 ? void 0 : _a.push(event); + } + else { + this.addEventToTimeline(event, toStartOfTimeline); + } + // Apply annotations and replace relations to the relations of the timeline only + (_b = this.timelineSet.relations) === null || _b === void 0 ? void 0 : _b.aggregateParentEvent(event); + (_c = this.timelineSet.relations) === null || _c === void 0 ? void 0 : _c.aggregateChildEvent(event, this.timelineSet); + return; + } + // If no thread support exists we want to count all thread relation + // added as a reply. We can't rely on the bundled relationships count + if ((!Thread.hasServerSideSupport || !this.rootEvent) && event.isRelation(exports.THREAD_RELATION_TYPE.name)) { + this.replyCount++; + } + if (emit) { + this.emit(ThreadEvent.NewReply, this, event); + this.updateThreadMetadata(); + } + }); + } + processEvent(event) { + return __awaiter(this, void 0, void 0, function* () { + if (event) { + this.setEventMetadata(event); + yield this.fetchEditsWhereNeeded(event); + } + this.timeline = this.events; + }); + } + /** + * Processes the receipts that were caught during initial sync + * When clients become aware of a thread, they try to retrieve those read receipts + * and apply them to the current thread + * @param receipts - A collection of the receipts cached from initial sync + */ + processReceipts(receipts = []) { + for (const { eventId, receiptType, userId, receipt, synthetic } of receipts) { + this.addReceiptToStructure(eventId, receiptType, userId, receipt, synthetic); + } + } + getRootEventBundledRelationship(rootEvent = this.rootEvent) { + return rootEvent === null || rootEvent === void 0 ? void 0 : rootEvent.getServerAggregatedRelation(exports.THREAD_RELATION_TYPE.name); + } + processRootEvent() { + return __awaiter(this, void 0, void 0, function* () { + const bundledRelationship = this.getRootEventBundledRelationship(); + if (Thread.hasServerSideSupport && bundledRelationship) { + this.replyCount = bundledRelationship.count; + this._currentUserParticipated = !!bundledRelationship.current_user_participated; + const mapper = this.client.getEventMapper(); + // re-insert roomId + this.lastEvent = mapper(Object.assign(Object.assign({}, bundledRelationship.latest_event), { room_id: this.roomId })); + this.updatePendingReplyCount(); + yield this.processEvent(this.lastEvent); + } + }); + } + updatePendingReplyCount() { + const unfilteredPendingEvents = this.pendingEventOrdering === client_1.PendingEventOrdering.Detached ? this.room.getPendingEvents() : this.events; + const pendingEvents = unfilteredPendingEvents.filter((ev) => { + var _a; + return ev.threadRootId === this.id && + ev.isRelation(exports.THREAD_RELATION_TYPE.name) && + ev.status !== null && + ev.getId() !== ((_a = this.lastEvent) === null || _a === void 0 ? void 0 : _a.getId()); + }); + this.lastPendingEvent = pendingEvents.length ? pendingEvents[pendingEvents.length - 1] : undefined; + this.pendingReplyCount = pendingEvents.length; + } + /** + * Reset the live timeline of all timelineSets, and start new ones. + * + *

This is used when /sync returns a 'limited' timeline. 'Limited' means that there's a gap between the messages + * /sync returned, and the last known message in our timeline. In such a case, our live timeline isn't live anymore + * and has to be replaced by a new one. To make sure we can continue paginating our timelines correctly, we have to + * set new pagination tokens on the old and the new timeline. + * + * @param backPaginationToken - token for back-paginating the new timeline + * @param forwardPaginationToken - token for forward-paginating the old live timeline, + * if absent or null, all timelines are reset, removing old ones (including the previous live + * timeline which would otherwise be unable to paginate forwards without this token). + * Removing just the old live timeline whilst preserving previous ones is not supported. + */ + resetLiveTimeline(backPaginationToken, forwardPaginationToken) { + return __awaiter(this, void 0, void 0, function* () { + const oldLive = this.liveTimeline; + this.timelineSet.resetLiveTimeline(backPaginationToken !== null && backPaginationToken !== void 0 ? backPaginationToken : undefined, forwardPaginationToken !== null && forwardPaginationToken !== void 0 ? forwardPaginationToken : undefined); + const newLive = this.liveTimeline; + // FIXME: Remove the following as soon as https://github.com/matrix-org/synapse/issues/14830 is resolved. + // + // The pagination API for thread timelines currently can't handle the type of pagination tokens returned by sync + // + // To make this work anyway, we'll have to transform them into one of the types that the API can handle. + // One option is passing the tokens to /messages, which can handle sync tokens, and returns the right format. + // /messages does not return new tokens on requests with a limit of 0. + // This means our timelines might overlap a slight bit, but that's not an issue, as we deduplicate messages + // anyway. + let newBackward; + let oldForward; + if (backPaginationToken) { + const res = yield this.client.createMessagesRequest(this.roomId, backPaginationToken, 1, event_timeline_1.Direction.Forward); + newBackward = res.end; + } + if (forwardPaginationToken) { + const res = yield this.client.createMessagesRequest(this.roomId, forwardPaginationToken, 1, event_timeline_1.Direction.Backward); + oldForward = res.start; + } + // Only replace the token if we don't have paginated away from this position already. This situation doesn't + // occur today, but if the above issue is resolved, we'd have to go down this path. + if (forwardPaginationToken && oldLive.getPaginationToken(event_timeline_1.Direction.Forward) === forwardPaginationToken) { + oldLive.setPaginationToken(oldForward !== null && oldForward !== void 0 ? oldForward : null, event_timeline_1.Direction.Forward); + } + if (backPaginationToken && newLive.getPaginationToken(event_timeline_1.Direction.Backward) === backPaginationToken) { + newLive.setPaginationToken(newBackward !== null && newBackward !== void 0 ? newBackward : null, event_timeline_1.Direction.Backward); + } + }); + } + updateThreadMetadata() { + return __awaiter(this, void 0, void 0, function* () { + this.updatePendingReplyCount(); + if (Thread.hasServerSideSupport) { + // Ensure we show *something* as soon as possible, we'll update it as soon as we get better data, but we + // don't want the thread preview to be empty if we can avoid it + if (!this.initialEventsFetched) { + yield this.processRootEvent(); + } + yield this.fetchRootEvent(); + } + yield this.processRootEvent(); + if (!this.initialEventsFetched) { + this.initialEventsFetched = true; + // fetch initial event to allow proper pagination + try { + // if the thread has regular events, this will just load the last reply. + // if the thread is newly created, this will load the root event. + if (this.replyCount === 0 && this.rootEvent) { + this.timelineSet.addEventsToTimeline([this.rootEvent], true, this.liveTimeline, null); + this.liveTimeline.setPaginationToken(null, event_timeline_1.Direction.Backward); + } + else { + yield this.client.paginateEventTimeline(this.liveTimeline, { + backwards: true, + limit: Math.max(1, this.length), + }); + } + for (const event of this.replayEvents) { + this.addEvent(event, false); + } + this.replayEvents = null; + // just to make sure that, if we've created a timeline window for this thread before the thread itself + // existed (e.g. when creating a new thread), we'll make sure the panel is force refreshed correctly. + this.emit(room_1.RoomEvent.TimelineReset, this.room, this.timelineSet, true); + } + catch (e) { + logger_1.logger.error("Failed to load start of newly created thread: ", e); + this.initialEventsFetched = false; + } + } + this.emit(ThreadEvent.Update, this); + }); + } + // XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084 + fetchEditsWhereNeeded(...events) { + return __awaiter(this, void 0, void 0, function* () { + return Promise.all(events + .filter((e) => e.isEncrypted()) + .map((event) => { + if (event.isRelation()) + return; // skip - relations don't get edits + return this.client + .relations(this.roomId, event.getId(), event_1.RelationType.Replace, event.getType(), { + limit: 1, + }) + .then((relations) => { + if (relations.events.length) { + event.makeReplaced(relations.events[0]); + } + }) + .catch((e) => { + logger_1.logger.error("Failed to load edits for encrypted thread event", e); + }); + })); + }); + } + setEventMetadata(event) { + if (event) { + event_timeline_1.EventTimeline.setEventMetadata(event, this.roomState, false); + event.setThread(this); + } + } + clearEventMetadata(event) { + var _a, _b, _c; + if (event) { + event.setThread(undefined); + (_c = (_b = (_a = event.event) === null || _a === void 0 ? void 0 : _a.unsigned) === null || _b === void 0 ? void 0 : _b["m.relations"]) === null || _c === void 0 ? true : delete _c[exports.THREAD_RELATION_TYPE.name]; + } + } + /** + * Finds an event by ID in the current thread + */ + findEventById(eventId) { + return this.timelineSet.findEventById(eventId); + } + /** + * Return last reply to the thread, if known. + */ + lastReply(matches = () => true) { + for (let i = this.timeline.length - 1; i >= 0; i--) { + const event = this.timeline[i]; + if (matches(event)) { + return event; + } + } + return null; + } + get roomId() { + return this.room.roomId; + } + /** + * The number of messages in the thread + * Only count rel_type=m.thread as we want to + * exclude annotations from that number + */ + get length() { + return this.replyCount + this.pendingReplyCount; + } + /** + * A getter for the last event of the thread. + * This might be a synthesized event, if so, it will not emit any events to listeners. + */ + get replyToEvent() { + var _a, _b; + return (_b = (_a = this.lastPendingEvent) !== null && _a !== void 0 ? _a : this.lastEvent) !== null && _b !== void 0 ? _b : this.lastReply(); + } + get events() { + return this.liveTimeline.getEvents(); + } + has(eventId) { + return this.timelineSet.findEventById(eventId) instanceof event_2.MatrixEvent; + } + get hasCurrentUserParticipated() { + return this._currentUserParticipated; + } + get liveTimeline() { + return this.timelineSet.getLiveTimeline(); + } + getUnfilteredTimelineSet() { + return this.timelineSet; + } + addReceipt(event, synthetic) { + throw new Error("Unsupported function on the thread model"); + } + /** + * Get the ID of the event that a given user has read up to within this thread, + * or null if we have received no read receipt (at all) from them. + * @param userId - The user ID to get read receipt event ID for + * @param ignoreSynthesized - If true, return only receipts that have been + * sent by the server, not implicit ones generated + * by the JS SDK. + * @returns ID of the latest event that the given user has read, or null. + */ + getEventReadUpTo(userId, ignoreSynthesized) { + var _a, _b; + const isCurrentUser = userId === this.client.getUserId(); + const lastReply = this.timeline[this.timeline.length - 1]; + if (isCurrentUser && lastReply) { + // If the last activity in a thread is prior to the first threaded read receipt + // sent in the room (suggesting that it was sent before the user started + // using a client that supported threaded read receipts), we want to + // consider this thread as read. + const beforeFirstThreadedReceipt = lastReply.getTs() < this.room.getOldestThreadedReceiptTs(); + const lastReplyId = lastReply.getId(); + // Some unsent events do not have an ID, we do not want to consider them read + if (beforeFirstThreadedReceipt && lastReplyId) { + return lastReplyId; + } + } + const readUpToId = super.getEventReadUpTo(userId, ignoreSynthesized); + // Check whether the unthreaded read receipt for that user is more recent + // than the read receipt inside that thread. + if (lastReply) { + const unthreadedReceipt = this.room.getLastUnthreadedReceiptFor(userId); + if (!unthreadedReceipt) { + return readUpToId; + } + for (let i = ((_a = this.timeline) === null || _a === void 0 ? void 0 : _a.length) - 1; i >= 0; --i) { + const ev = this.timeline[i]; + // If we encounter the `readUpToId` we do not need to look further + // there is no "more recent" unthreaded read receipt + if (ev.getId() === readUpToId) + return readUpToId; + // Inspecting events from most recent to oldest, we're checking + // whether an unthreaded read receipt is more recent that the current event. + // We usually prefer relying on the order of the DAG but in this scenario + // it is not possible and we have to rely on timestamp + if (ev.getTs() < unthreadedReceipt.ts) + return (_b = ev.getId()) !== null && _b !== void 0 ? _b : readUpToId; + } + } + return readUpToId; + } + /** + * Determine if the given user has read a particular event. + * + * It is invalid to call this method with an event that is not part of this thread. + * + * This is not a definitive check as it only checks the events that have been + * loaded client-side at the time of execution. + * @param userId - The user ID to check the read state of. + * @param eventId - The event ID to check if the user read. + * @returns True if the user has read the event, false otherwise. + */ + hasUserReadEvent(userId, eventId) { + var _a, _b, _c, _d, _e, _f; + if (userId === this.client.getUserId()) { + // Consider an event read if it's part of a thread that is before the + // first threaded receipt sent in that room. It is likely that it is + // part of a thread that was created before MSC3771 was implemented. + // Or before the last unthreaded receipt for the logged in user + const beforeFirstThreadedReceipt = ((_b = (_a = this.lastReply()) === null || _a === void 0 ? void 0 : _a.getTs()) !== null && _b !== void 0 ? _b : 0) < this.room.getOldestThreadedReceiptTs(); + const unthreadedReceiptTs = (_d = (_c = this.room.getLastUnthreadedReceiptFor(userId)) === null || _c === void 0 ? void 0 : _c.ts) !== null && _d !== void 0 ? _d : 0; + const beforeLastUnthreadedReceipt = ((_f = (_e = this === null || this === void 0 ? void 0 : this.lastReply()) === null || _e === void 0 ? void 0 : _e.getTs()) !== null && _f !== void 0 ? _f : 0) < unthreadedReceiptTs; + if (beforeFirstThreadedReceipt || beforeLastUnthreadedReceipt) { + return true; + } + } + return super.hasUserReadEvent(userId, eventId); + } + setUnread(type, count) { + return this.room.setThreadUnreadNotificationCount(this.id, type, count); + } +} +exports.Thread = Thread; +Thread.hasServerSideSupport = FeatureSupport.None; +Thread.hasServerSideListSupport = FeatureSupport.None; +Thread.hasServerSideFwdPaginationSupport = FeatureSupport.None; +exports.FILTER_RELATED_BY_SENDERS = new NamespacedValue_1.ServerControlledNamespacedValue("related_by_senders", "io.element.relation_senders"); +exports.FILTER_RELATED_BY_REL_TYPES = new NamespacedValue_1.ServerControlledNamespacedValue("related_by_rel_types", "io.element.relation_types"); +exports.THREAD_RELATION_TYPE = new NamespacedValue_1.ServerControlledNamespacedValue("m.thread", "io.element.thread"); +var ThreadFilterType; +(function (ThreadFilterType) { + ThreadFilterType[ThreadFilterType["My"] = 0] = "My"; + ThreadFilterType[ThreadFilterType["All"] = 1] = "All"; +})(ThreadFilterType = exports.ThreadFilterType || (exports.ThreadFilterType = {})); +function threadFilterTypeToFilter(type) { + switch (type) { + case ThreadFilterType.My: + return "participated"; + default: + return "all"; + } +} +exports.threadFilterTypeToFilter = threadFilterTypeToFilter; + +},{"../@types/event":306,"../@types/read_receipts":311,"../NamespacedValue":316,"../ReEmitter":317,"../client":321,"../logger":374,"./event":383,"./event-timeline":382,"./event-timeline-set":381,"./read-receipt":386,"./room":392}],395:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TypedEventEmitter = exports.EventEmitterEvents = void 0; +// eslint-disable-next-line no-restricted-imports +const events_1 = require("events"); +var EventEmitterEvents; +(function (EventEmitterEvents) { + EventEmitterEvents["NewListener"] = "newListener"; + EventEmitterEvents["RemoveListener"] = "removeListener"; + EventEmitterEvents["Error"] = "error"; +})(EventEmitterEvents = exports.EventEmitterEvents || (exports.EventEmitterEvents = {})); +/** + * Typed Event Emitter class which can act as a Base Model for all our model + * and communication events. + * This makes it much easier for us to distinguish between events, as we now need + * to properly type this, so that our events are not stringly-based and prone + * to silly typos. + */ +class TypedEventEmitter extends events_1.EventEmitter { + addListener(event, listener) { + return super.addListener(event, listener); + } + emit(event, ...args) { + return super.emit(event, ...args); + } + eventNames() { + return super.eventNames(); + } + listenerCount(event) { + return super.listenerCount(event); + } + listeners(event) { + return super.listeners(event); + } + off(event, listener) { + return super.off(event, listener); + } + on(event, listener) { + return super.on(event, listener); + } + once(event, listener) { + return super.once(event, listener); + } + prependListener(event, listener) { + return super.prependListener(event, listener); + } + prependOnceListener(event, listener) { + return super.prependOnceListener(event, listener); + } + removeAllListeners(event) { + return super.removeAllListeners(event); + } + removeListener(event, listener) { + return super.removeListener(event, listener); + } + rawListeners(event) { + return super.rawListeners(event); + } +} +exports.TypedEventEmitter = TypedEventEmitter; + +},{"events":105}],396:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.User = exports.UserEvent = void 0; +const typed_event_emitter_1 = require("./typed-event-emitter"); +var UserEvent; +(function (UserEvent) { + UserEvent["DisplayName"] = "User.displayName"; + UserEvent["AvatarUrl"] = "User.avatarUrl"; + UserEvent["Presence"] = "User.presence"; + UserEvent["CurrentlyActive"] = "User.currentlyActive"; + UserEvent["LastPresenceTs"] = "User.lastPresenceTs"; +})(UserEvent = exports.UserEvent || (exports.UserEvent = {})); +class User extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct a new User. A User must have an ID and can optionally have extra information associated with it. + * @param userId - Required. The ID of this user. + */ + constructor(userId) { + super(); + this.userId = userId; + this.modified = -1; + /** + * The presence enum if known. + * @privateRemarks + * Should be read-only + */ + this.presence = "offline"; + /** + * Timestamp (ms since the epoch) for when we last received presence data for this user. + * We can subtract lastActiveAgo from this to approximate an absolute value for when a user was last active. + * @privateRemarks + * Should be read-only + */ + this.lastActiveAgo = 0; + /** + * The time elapsed in ms since the user interacted proactively with the server, + * or we saw a message from the user + * @privateRemarks + * Should be read-only + */ + this.lastPresenceTs = 0; + /** + * Whether we should consider lastActiveAgo to be an approximation + * and that the user should be seen as active 'now' + * @privateRemarks + * Should be read-only + */ + this.currentlyActive = false; + /** + * The events describing this user. + * @privateRemarks + * Should be read-only + */ + this.events = {}; + this.displayName = userId; + this.rawDisplayName = userId; + this.updateModifiedTime(); + } + /** + * Update this User with the given presence event. May fire "User.presence", + * "User.avatarUrl" and/or "User.displayName" if this event updates this user's + * properties. + * @param event - The `m.presence` event. + * + * @remarks + * Fires {@link UserEvent.Presence} + * Fires {@link UserEvent.DisplayName} + * Fires {@link UserEvent.AvatarUrl} + */ + setPresenceEvent(event) { + if (event.getType() !== "m.presence") { + return; + } + const firstFire = this.events.presence === null; + this.events.presence = event; + const eventsToFire = []; + if (event.getContent().presence !== this.presence || firstFire) { + eventsToFire.push(UserEvent.Presence); + } + if (event.getContent().avatar_url && event.getContent().avatar_url !== this.avatarUrl) { + eventsToFire.push(UserEvent.AvatarUrl); + } + if (event.getContent().displayname && event.getContent().displayname !== this.displayName) { + eventsToFire.push(UserEvent.DisplayName); + } + if (event.getContent().currently_active !== undefined && + event.getContent().currently_active !== this.currentlyActive) { + eventsToFire.push(UserEvent.CurrentlyActive); + } + this.presence = event.getContent().presence; + eventsToFire.push(UserEvent.LastPresenceTs); + if (event.getContent().status_msg) { + this.presenceStatusMsg = event.getContent().status_msg; + } + if (event.getContent().displayname) { + this.displayName = event.getContent().displayname; + } + if (event.getContent().avatar_url) { + this.avatarUrl = event.getContent().avatar_url; + } + this.lastActiveAgo = event.getContent().last_active_ago; + this.lastPresenceTs = Date.now(); + this.currentlyActive = event.getContent().currently_active; + this.updateModifiedTime(); + for (const eventToFire of eventsToFire) { + this.emit(eventToFire, event, this); + } + } + /** + * Manually set this user's display name. No event is emitted in response to this + * as there is no underlying MatrixEvent to emit with. + * @param name - The new display name. + */ + setDisplayName(name) { + const oldName = this.displayName; + this.displayName = name; + if (name !== oldName) { + this.updateModifiedTime(); + } + } + /** + * Manually set this user's non-disambiguated display name. No event is emitted + * in response to this as there is no underlying MatrixEvent to emit with. + * @param name - The new display name. + */ + setRawDisplayName(name) { + this.rawDisplayName = name; + } + /** + * Manually set this user's avatar URL. No event is emitted in response to this + * as there is no underlying MatrixEvent to emit with. + * @param url - The new avatar URL. + */ + setAvatarUrl(url) { + const oldUrl = this.avatarUrl; + this.avatarUrl = url; + if (url !== oldUrl) { + this.updateModifiedTime(); + } + } + /** + * Update the last modified time to the current time. + */ + updateModifiedTime() { + this.modified = Date.now(); + } + /** + * Get the timestamp when this User was last updated. This timestamp is + * updated when this User receives a new Presence event which has updated a + * property on this object. It is updated before firing events. + * @returns The timestamp + */ + getLastModifiedTime() { + return this.modified; + } + /** + * Get the absolute timestamp when this User was last known active on the server. + * It is *NOT* accurate if this.currentlyActive is true. + * @returns The timestamp + */ + getLastActiveTs() { + return this.lastPresenceTs - this.lastActiveAgo; + } +} +exports.User = User; + +},{"./typed-event-emitter":395}],397:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PushProcessor = void 0; +const utils_1 = require("./utils"); +const logger_1 = require("./logger"); +const PushRules_1 = require("./@types/PushRules"); +const event_1 = require("./@types/event"); +const RULEKINDS_IN_ORDER = [ + PushRules_1.PushRuleKind.Override, + PushRules_1.PushRuleKind.ContentSpecific, + PushRules_1.PushRuleKind.RoomSpecific, + PushRules_1.PushRuleKind.SenderSpecific, + PushRules_1.PushRuleKind.Underride, +]; +// The default override rules to apply to the push rules that arrive from the server. +// We do this for two reasons: +// 1. Synapse is unlikely to send us the push rule in an incremental sync - see +// https://github.com/matrix-org/synapse/pull/4867#issuecomment-481446072 for +// more details. +// 2. We often want to start using push rules ahead of the server supporting them, +// and so we can put them here. +const DEFAULT_OVERRIDE_RULES = [ + { + // For homeservers which don't support MSC2153 yet + rule_id: ".m.rule.reaction", + default: true, + enabled: true, + conditions: [ + { + kind: PushRules_1.ConditionKind.EventMatch, + key: "type", + pattern: "m.reaction", + }, + ], + actions: [PushRules_1.PushRuleActionName.DontNotify], + }, + { + rule_id: PushRules_1.RuleId.IsUserMention, + default: true, + enabled: true, + conditions: [ + { + kind: PushRules_1.ConditionKind.EventPropertyContains, + key: "content.org\\.matrix\\.msc3952\\.mentions.user_ids", + value: "", // The user ID is dynamically added in rewriteDefaultRules. + }, + ], + actions: [PushRules_1.PushRuleActionName.Notify, { set_tweak: PushRules_1.TweakName.Highlight }], + }, + { + rule_id: PushRules_1.RuleId.IsRoomMention, + default: true, + enabled: true, + conditions: [ + { + kind: PushRules_1.ConditionKind.EventPropertyIs, + key: "content.org\\.matrix\\.msc3952\\.mentions.room", + value: true, + }, + { + kind: PushRules_1.ConditionKind.SenderNotificationPermission, + key: "room", + }, + ], + actions: [PushRules_1.PushRuleActionName.Notify, { set_tweak: PushRules_1.TweakName.Highlight }], + }, + { + // For homeservers which don't support MSC3786 yet + rule_id: ".org.matrix.msc3786.rule.room.server_acl", + default: true, + enabled: true, + conditions: [ + { + kind: PushRules_1.ConditionKind.EventMatch, + key: "type", + pattern: event_1.EventType.RoomServerAcl, + }, + { + kind: PushRules_1.ConditionKind.EventMatch, + key: "state_key", + pattern: "", + }, + ], + actions: [], + }, +]; +const DEFAULT_UNDERRIDE_RULES = [ + { + // For homeservers which don't support MSC3914 yet + rule_id: ".org.matrix.msc3914.rule.room.call", + default: true, + enabled: true, + conditions: [ + { + kind: PushRules_1.ConditionKind.EventMatch, + key: "type", + pattern: "org.matrix.msc3401.call", + }, + { + kind: PushRules_1.ConditionKind.CallStarted, + }, + ], + actions: [PushRules_1.PushRuleActionName.Notify, { set_tweak: PushRules_1.TweakName.Sound, value: "default" }], + }, +]; +class PushProcessor { + /** + * Construct a Push Processor. + * @param client - The Matrix client object to use + */ + constructor(client) { + this.client = client; + /** + * Maps the original key from the push rules to a list of property names + * after unescaping. + */ + this.parsedKeys = new Map(); + } + /** + * Convert a list of actions into a object with the actions as keys and their values + * @example + * eg. `[ 'notify', { set_tweak: 'sound', value: 'default' } ]` + * becomes `{ notify: true, tweaks: { sound: 'default' } }` + * @param actionList - The actions list + * + * @returns A object with key 'notify' (true or false) and an object of actions + */ + static actionListToActionsObject(actionList) { + const actionObj = { notify: false, tweaks: {} }; + for (const action of actionList) { + if (action === PushRules_1.PushRuleActionName.Notify) { + actionObj.notify = true; + } + else if (typeof action === "object") { + if (action.value === undefined) { + action.value = true; + } + actionObj.tweaks[action.set_tweak] = action.value; + } + } + return actionObj; + } + /** + * Rewrites conditions on a client's push rules to match the defaults + * where applicable. Useful for upgrading push rules to more strict + * conditions when the server is falling behind on defaults. + * @param incomingRules - The client's existing push rules + * @param userId - The Matrix ID of the client. + * @returns The rewritten rules + */ + static rewriteDefaultRules(incomingRules, userId = undefined) { + var _a; + let newRules = JSON.parse(JSON.stringify(incomingRules)); // deep clone + // These lines are mostly to make the tests happy. We shouldn't run into these + // properties missing in practice. + if (!newRules) + newRules = {}; + if (!newRules.global) + newRules.global = {}; + if (!newRules.global.override) + newRules.global.override = []; + if (!newRules.global.underride) + newRules.global.underride = []; + // Merge the client-level defaults with the ones from the server + const globalOverrides = newRules.global.override; + for (const originalOverride of DEFAULT_OVERRIDE_RULES) { + const existingRule = globalOverrides.find((r) => r.rule_id === originalOverride.rule_id); + // Dynamically add the user ID as the value for the is_user_mention rule. + let override; + if (originalOverride.rule_id === PushRules_1.RuleId.IsUserMention) { + // If the user ID wasn't provided, skip the rule. + if (!userId) { + continue; + } + override = JSON.parse(JSON.stringify(originalOverride)); // deep clone + override.conditions[0].value = userId; + } + else { + override = originalOverride; + } + if (existingRule) { + // Copy over the actions, default, and conditions. Don't touch the user's preference. + existingRule.default = override.default; + existingRule.conditions = override.conditions; + existingRule.actions = override.actions; + } + else { + // Add the rule + const ruleId = override.rule_id; + logger_1.logger.warn(`Adding default global override for ${ruleId}`); + globalOverrides.push(override); + } + } + const globalUnderrides = (_a = newRules.global.underride) !== null && _a !== void 0 ? _a : []; + for (const underride of DEFAULT_UNDERRIDE_RULES) { + const existingRule = globalUnderrides.find((r) => r.rule_id === underride.rule_id); + if (existingRule) { + // Copy over the actions, default, and conditions. Don't touch the user's preference. + existingRule.default = underride.default; + existingRule.conditions = underride.conditions; + existingRule.actions = underride.actions; + } + else { + // Add the rule + const ruleId = underride.rule_id; + logger_1.logger.warn(`Adding default global underride for ${ruleId}`); + globalUnderrides.push(underride); + } + } + return newRules; + } + /** + * Pre-caches the parsed keys for push rules and cleans out any obsolete cache + * entries. Should be called after push rules are updated. + * @param newRules - The new push rules. + */ + updateCachedPushRuleKeys(newRules) { + // These lines are mostly to make the tests happy. We shouldn't run into these + // properties missing in practice. + if (!newRules) + newRules = {}; + if (!newRules.global) + newRules.global = {}; + if (!newRules.global.override) + newRules.global.override = []; + if (!newRules.global.room) + newRules.global.room = []; + if (!newRules.global.sender) + newRules.global.sender = []; + if (!newRules.global.underride) + newRules.global.underride = []; + // Process the 'key' property on event_match conditions pre-cache the + // values and clean-out any unused values. + const toRemoveKeys = new Set(this.parsedKeys.keys()); + for (const ruleset of [ + newRules.global.override, + newRules.global.room, + newRules.global.sender, + newRules.global.underride, + ]) { + for (const rule of ruleset) { + if (!rule.conditions) { + continue; + } + for (const condition of rule.conditions) { + if (condition.kind !== PushRules_1.ConditionKind.EventMatch) { + continue; + } + // Ensure we keep this key. + toRemoveKeys.delete(condition.key); + // Pre-process the key. + this.parsedKeys.set(condition.key, PushProcessor.partsForDottedKey(condition.key)); + } + } + } + // Any keys that were previously cached, but are no longer needed should + // be removed. + toRemoveKeys.forEach((k) => this.parsedKeys.delete(k)); + } + matchingRuleFromKindSet(ev, kindset) { + for (const kind of RULEKINDS_IN_ORDER) { + const ruleset = kindset[kind]; + if (!ruleset) { + continue; + } + for (const rule of ruleset) { + if (!rule.enabled) { + continue; + } + const rawrule = this.templateRuleToRaw(kind, rule); + if (!rawrule) { + continue; + } + if (this.ruleMatchesEvent(rawrule, ev)) { + return Object.assign(Object.assign({}, rule), { kind }); + } + } + } + return null; + } + templateRuleToRaw(kind, tprule) { + const rawrule = { + rule_id: tprule.rule_id, + actions: tprule.actions, + conditions: [], + }; + switch (kind) { + case PushRules_1.PushRuleKind.Underride: + case PushRules_1.PushRuleKind.Override: + rawrule.conditions = tprule.conditions; + break; + case PushRules_1.PushRuleKind.RoomSpecific: + if (!tprule.rule_id) { + return null; + } + rawrule.conditions.push({ + kind: PushRules_1.ConditionKind.EventMatch, + key: "room_id", + value: tprule.rule_id, + }); + break; + case PushRules_1.PushRuleKind.SenderSpecific: + if (!tprule.rule_id) { + return null; + } + rawrule.conditions.push({ + kind: PushRules_1.ConditionKind.EventMatch, + key: "user_id", + value: tprule.rule_id, + }); + break; + case PushRules_1.PushRuleKind.ContentSpecific: + if (!tprule.pattern) { + return null; + } + rawrule.conditions.push({ + kind: PushRules_1.ConditionKind.EventMatch, + key: "content.body", + pattern: tprule.pattern, + }); + break; + } + return rawrule; + } + eventFulfillsCondition(cond, ev) { + switch (cond.kind) { + case PushRules_1.ConditionKind.EventMatch: + return this.eventFulfillsEventMatchCondition(cond, ev); + case PushRules_1.ConditionKind.EventPropertyIs: + return this.eventFulfillsEventPropertyIsCondition(cond, ev); + case PushRules_1.ConditionKind.EventPropertyContains: + return this.eventFulfillsEventPropertyContains(cond, ev); + case PushRules_1.ConditionKind.ContainsDisplayName: + return this.eventFulfillsDisplayNameCondition(cond, ev); + case PushRules_1.ConditionKind.RoomMemberCount: + return this.eventFulfillsRoomMemberCountCondition(cond, ev); + case PushRules_1.ConditionKind.SenderNotificationPermission: + return this.eventFulfillsSenderNotifPermCondition(cond, ev); + case PushRules_1.ConditionKind.CallStarted: + case PushRules_1.ConditionKind.CallStartedPrefix: + return this.eventFulfillsCallStartedCondition(cond, ev); + } + // unknown conditions: we previously matched all unknown conditions, + // but given that rules can be added to the base rules on a server, + // it's probably better to not match unknown conditions. + return false; + } + eventFulfillsSenderNotifPermCondition(cond, ev) { + const notifLevelKey = cond["key"]; + if (!notifLevelKey) { + return false; + } + const room = this.client.getRoom(ev.getRoomId()); + if (!(room === null || room === void 0 ? void 0 : room.currentState)) { + return false; + } + // Note that this should not be the current state of the room but the state at + // the point the event is in the DAG. Unfortunately the js-sdk does not store + // this. + return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()); + } + eventFulfillsRoomMemberCountCondition(cond, ev) { + if (!cond.is) { + return false; + } + const room = this.client.getRoom(ev.getRoomId()); + if (!room || !room.currentState || !room.currentState.members) { + return false; + } + const memberCount = room.currentState.getJoinedMemberCount(); + const m = cond.is.match(/^([=<>]*)(\d*)$/); + if (!m) { + return false; + } + const ineq = m[1]; + const rhs = parseInt(m[2]); + if (isNaN(rhs)) { + return false; + } + switch (ineq) { + case "": + case "==": + return memberCount == rhs; + case "<": + return memberCount < rhs; + case ">": + return memberCount > rhs; + case "<=": + return memberCount <= rhs; + case ">=": + return memberCount >= rhs; + default: + return false; + } + } + eventFulfillsDisplayNameCondition(cond, ev) { + var _a; + let content = ev.getContent(); + if (ev.isEncrypted() && ev.getClearContent()) { + content = ev.getClearContent(); + } + if (!content || !content.body || typeof content.body != "string") { + return false; + } + const room = this.client.getRoom(ev.getRoomId()); + const member = (_a = room === null || room === void 0 ? void 0 : room.currentState) === null || _a === void 0 ? void 0 : _a.getMember(this.client.credentials.userId); + if (!member) { + return false; + } + const displayName = member.name; + // N.B. we can't use \b as it chokes on unicode. however \W seems to be okay + // as shorthand for [^0-9A-Za-z_]. + const pat = new RegExp("(^|\\W)" + (0, utils_1.escapeRegExp)(displayName) + "(\\W|$)", "i"); + return content.body.search(pat) > -1; + } + /** + * Check whether the given event matches the push rule condition by fetching + * the property from the event and comparing against the condition's glob-based + * pattern. + * @param cond - The push rule condition to check for a match. + * @param ev - The event to check for a match. + */ + eventFulfillsEventMatchCondition(cond, ev) { + if (!cond.key) { + return false; + } + const val = this.valueForDottedKey(cond.key, ev); + if (typeof val !== "string") { + return false; + } + // XXX This does not match in a case-insensitive manner. + // + // See https://spec.matrix.org/v1.5/client-server-api/#conditions-1 + if (cond.value) { + return cond.value === val; + } + if (typeof cond.pattern !== "string") { + return false; + } + const regex = cond.key === "content.body" + ? this.createCachedRegex("(^|\\W)", cond.pattern, "(\\W|$)") + : this.createCachedRegex("^", cond.pattern, "$"); + return !!val.match(regex); + } + /** + * Check whether the given event matches the push rule condition by fetching + * the property from the event and comparing exactly against the condition's + * value. + * @param cond - The push rule condition to check for a match. + * @param ev - The event to check for a match. + */ + eventFulfillsEventPropertyIsCondition(cond, ev) { + if (!cond.key || cond.value === undefined) { + return false; + } + return cond.value === this.valueForDottedKey(cond.key, ev); + } + /** + * Check whether the given event matches the push rule condition by fetching + * the property from the event and comparing exactly against the condition's + * value. + * @param cond - The push rule condition to check for a match. + * @param ev - The event to check for a match. + */ + eventFulfillsEventPropertyContains(cond, ev) { + if (!cond.key || cond.value === undefined) { + return false; + } + const val = this.valueForDottedKey(cond.key, ev); + if (!Array.isArray(val)) { + return false; + } + return val.includes(cond.value); + } + eventFulfillsCallStartedCondition(_cond, ev) { + // Since servers don't support properly sending push notification + // about MSC3401 call events, we do the handling ourselves + return (["m.ring", "m.prompt"].includes(ev.getContent()["m.intent"]) && + !("m.terminated" in ev.getContent()) && + (ev.getPrevContent()["m.terminated"] !== ev.getContent()["m.terminated"] || + (0, utils_1.deepCompare)(ev.getPrevContent(), {}))); + } + createCachedRegex(prefix, glob, suffix) { + if (PushProcessor.cachedGlobToRegex[glob]) { + return PushProcessor.cachedGlobToRegex[glob]; + } + PushProcessor.cachedGlobToRegex[glob] = new RegExp(prefix + (0, utils_1.globToRegexp)(glob) + suffix, "i"); + return PushProcessor.cachedGlobToRegex[glob]; + } + /** + * Parse the key into the separate fields to search by splitting on + * unescaped ".", and then removing any escape characters. + * + * @param str - The key of the push rule condition: a dotted field. + * @returns The unescaped parts to fetch. + * @internal + */ + static partsForDottedKey(str) { + const result = []; + // The current field and whether the previous character was the escape + // character (a backslash). + let part = ""; + let escaped = false; + // Iterate over each character, and decide whether to append to the current + // part (following the escape rules) or to start a new part (based on the + // field separator). + for (const c of str) { + // If the previous character was the escape character (a backslash) + // then decide what to append to the current part. + if (escaped) { + if (c === "\\" || c === ".") { + // An escaped backslash or dot just gets added. + part += c; + } + else { + // A character that shouldn't be escaped gets the backslash prepended. + part += "\\" + c; + } + // This always resets being escaped. + escaped = false; + continue; + } + if (c == ".") { + // The field separator creates a new part. + result.push(part); + part = ""; + } + else if (c == "\\") { + // A backslash adds no characters, but starts an escape sequence. + escaped = true; + } + else { + // Otherwise, just add the current character. + part += c; + } + } + // Ensure the final part is included. If there's an open escape sequence + // it should be included. + if (escaped) { + part += "\\"; + } + result.push(part); + return result; + } + /** + * For a dotted field and event, fetch the value at that position, if one + * exists. + * + * @param key - The key of the push rule condition: a dotted field to fetch. + * @param ev - The matrix event to fetch the field from. + * @returns The value at the dotted path given by key. + */ + valueForDottedKey(key, ev) { + // The key should already have been parsed via updateCachedPushRuleKeys, + // but if it hasn't (maybe via an old consumer of the SDK which hasn't + // been updated?) then lazily calculate it here. + let parts = this.parsedKeys.get(key); + if (parts === undefined) { + parts = PushProcessor.partsForDottedKey(key); + this.parsedKeys.set(key, parts); + } + let val; + // special-case the first component to deal with encrypted messages + const firstPart = parts[0]; + let currentIndex = 0; + if (firstPart === "content") { + val = ev.getContent(); + ++currentIndex; + } + else if (firstPart === "type") { + val = ev.getType(); + ++currentIndex; + } + else { + // use the raw event for any other fields + val = ev.event; + } + for (; currentIndex < parts.length; ++currentIndex) { + // The previous iteration resulted in null or undefined, bail (and + // avoid the type error of attempting to retrieve a property). + if ((0, utils_1.isNullOrUndefined)(val)) { + return undefined; + } + const thisPart = parts[currentIndex]; + val = val[thisPart]; + } + return val; + } + matchingRuleForEventWithRulesets(ev, rulesets) { + if (!rulesets) { + return null; + } + if (ev.getSender() === this.client.credentials.userId) { + return null; + } + return this.matchingRuleFromKindSet(ev, rulesets.global); + } + pushActionsForEventAndRulesets(ev, rulesets) { + const rule = this.matchingRuleForEventWithRulesets(ev, rulesets); + if (!rule) { + return {}; + } + const actionObj = PushProcessor.actionListToActionsObject(rule.actions); + // Some actions are implicit in some situations: we add those here + if (actionObj.tweaks.highlight === undefined) { + // if it isn't specified, highlight if it's a content + // rule but otherwise not + actionObj.tweaks.highlight = rule.kind == PushRules_1.PushRuleKind.ContentSpecific; + } + return actionObj; + } + ruleMatchesEvent(rule, ev) { + var _a; + // Disable the deprecated mentions push rules if the new mentions property exists. + if (this.client.supportsIntentionalMentions() && + ev.getContent()["org.matrix.msc3952.mentions"] !== undefined && + (rule.rule_id === PushRules_1.RuleId.ContainsUserName || + rule.rule_id === PushRules_1.RuleId.ContainsDisplayName || + rule.rule_id === PushRules_1.RuleId.AtRoomNotification)) { + return false; + } + return !((_a = rule.conditions) === null || _a === void 0 ? void 0 : _a.some((cond) => !this.eventFulfillsCondition(cond, ev))); + } + /** + * Get the user's push actions for the given event + */ + actionsForEvent(ev) { + return this.pushActionsForEventAndRulesets(ev, this.client.pushRules); + } + /** + * Get one of the users push rules by its ID + * + * @param ruleId - The ID of the rule to search for + * @returns The push rule, or null if no such rule was found + */ + getPushRuleById(ruleId) { + var _a; + const result = this.getPushRuleAndKindById(ruleId); + return (_a = result === null || result === void 0 ? void 0 : result.rule) !== null && _a !== void 0 ? _a : null; + } + /** + * Get one of the users push rules by its ID + * + * @param ruleId - The ID of the rule to search for + * @returns rule The push rule, or null if no such rule was found + * @returns kind - The PushRuleKind of the rule to search for + */ + getPushRuleAndKindById(ruleId) { + var _a; + for (const scope of ["global"]) { + if (((_a = this.client.pushRules) === null || _a === void 0 ? void 0 : _a[scope]) === undefined) + continue; + for (const kind of RULEKINDS_IN_ORDER) { + if (this.client.pushRules[scope][kind] === undefined) + continue; + for (const rule of this.client.pushRules[scope][kind]) { + if (rule.rule_id === ruleId) + return { rule, kind }; + } + } + } + return null; + } +} +exports.PushProcessor = PushProcessor; +PushProcessor.cachedGlobToRegex = {}; // $glob: RegExp + +},{"./@types/PushRules":304,"./@types/event":306,"./logger":374,"./utils":416}],398:[function(require,module,exports){ +"use strict"; +/* +Copyright 2018 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.randomUppercaseString = exports.randomLowercaseString = exports.randomString = void 0; +const LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; +const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const DIGITS = "0123456789"; +function randomString(len) { + return randomStringFrom(len, UPPERCASE + LOWERCASE + DIGITS); +} +exports.randomString = randomString; +function randomLowercaseString(len) { + return randomStringFrom(len, LOWERCASE); +} +exports.randomLowercaseString = randomLowercaseString; +function randomUppercaseString(len) { + return randomStringFrom(len, UPPERCASE); +} +exports.randomUppercaseString = randomUppercaseString; +function randomStringFrom(len, chars) { + let ret = ""; + for (let i = 0; i < len; ++i) { + ret += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return ret; +} + +},{}],399:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.clearTimeout = exports.setTimeout = void 0; +/* A re-implementation of the javascript callback functions (setTimeout, + * clearTimeout; setInterval and clearInterval are not yet implemented) which + * try to improve handling of large clock jumps (as seen when + * suspending/resuming the system). + * + * In particular, if a timeout would have fired while the system was suspended, + * it will instead fire as soon as possible after resume. + */ +const logger_1 = require("./logger"); +// we schedule a callback at least this often, to check if we've missed out on +// some wall-clock time due to being suspended. +const TIMER_CHECK_PERIOD_MS = 1000; +// counter, for making up ids to return from setTimeout +let count = 0; +// the key for our callback with the real global.setTimeout +let realCallbackKey; +// a sorted list of the callbacks to be run. +// each is an object with keys [runAt, func, params, key]. +const callbackList = []; +// var debuglog = logger.log.bind(logger); +/* istanbul ignore next */ +const debuglog = function (...params) { }; +/** + * reimplementation of window.setTimeout, which will call the callback if + * the wallclock time goes past the deadline. + * + * @param func - callback to be called after a delay + * @param delayMs - number of milliseconds to delay by + * + * @returns an identifier for this callback, which may be passed into + * clearTimeout later. + */ +function setTimeout(func, delayMs, ...params) { + delayMs = delayMs || 0; + if (delayMs < 0) { + delayMs = 0; + } + const runAt = Date.now() + delayMs; + const key = count++; + debuglog("setTimeout: scheduling cb", key, "at", runAt, "(delay", delayMs, ")"); + const data = { + runAt: runAt, + func: func, + params: params, + key: key, + }; + // figure out where it goes in the list + const idx = binarySearch(callbackList, function (el) { + return el.runAt - runAt; + }); + callbackList.splice(idx, 0, data); + scheduleRealCallback(); + return key; +} +exports.setTimeout = setTimeout; +/** + * reimplementation of window.clearTimeout, which mirrors setTimeout + * + * @param key - result from an earlier setTimeout call + */ +function clearTimeout(key) { + if (callbackList.length === 0) { + return; + } + // remove the element from the list + let i; + for (i = 0; i < callbackList.length; i++) { + const cb = callbackList[i]; + if (cb.key == key) { + callbackList.splice(i, 1); + break; + } + } + // iff it was the first one in the list, reschedule our callback. + if (i === 0) { + scheduleRealCallback(); + } +} +exports.clearTimeout = clearTimeout; +// use the real global.setTimeout to schedule a callback to runCallbacks. +function scheduleRealCallback() { + if (realCallbackKey) { + global.clearTimeout(realCallbackKey); + } + const first = callbackList[0]; + if (!first) { + debuglog("scheduleRealCallback: no more callbacks, not rescheduling"); + return; + } + const timestamp = Date.now(); + const delayMs = Math.min(first.runAt - timestamp, TIMER_CHECK_PERIOD_MS); + debuglog("scheduleRealCallback: now:", timestamp, "delay:", delayMs); + realCallbackKey = global.setTimeout(runCallbacks, delayMs); +} +function runCallbacks() { + const timestamp = Date.now(); + debuglog("runCallbacks: now:", timestamp); + // get the list of things to call + const callbacksToRun = []; + // eslint-disable-next-line + while (true) { + const first = callbackList[0]; + if (!first || first.runAt > timestamp) { + break; + } + const cb = callbackList.shift(); + debuglog("runCallbacks: popping", cb.key); + callbacksToRun.push(cb); + } + // reschedule the real callback before running our functions, to + // keep the codepaths the same whether or not our functions + // register their own setTimeouts. + scheduleRealCallback(); + for (const cb of callbacksToRun) { + try { + cb.func.apply(global, cb.params); + } + catch (e) { + logger_1.logger.error("Uncaught exception in callback function", e); + } + } +} +/* search in a sorted array. + * + * returns the index of the last element for which func returns + * greater than zero, or array.length if no such element exists. + */ +function binarySearch(array, func) { + // min is inclusive, max exclusive. + let min = 0; + let max = array.length; + while (min < max) { + const mid = (min + max) >> 1; + const res = func(array[mid]); + if (res > 0) { + // the element at 'mid' is too big; set it as the new max. + max = mid; + } + else { + // the element at 'mid' is too small. 'min' is inclusive, so +1. + min = mid + 1; + } + } + // presumably, min==max now. + return min; +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./logger":374}],400:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.initRustCrypto = void 0; +function initRustCrypto(_http, _userId, _deviceId) { + return __awaiter(this, void 0, void 0, function* () { + throw new Error("Rust crypto is not supported under browserify."); + }); +} +exports.initRustCrypto = initRustCrypto; + +},{}],401:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RUST_SDK_STORE_PREFIX = void 0; +/** The prefix used on indexeddbs created by rust-crypto */ +exports.RUST_SDK_STORE_PREFIX = "matrix-js-sdk"; + +},{}],402:[function(require,module,exports){ +module.exports = require('/home/runner/work/matrix-js-sdk/matrix-js-sdk/src/rust-crypto/browserify-index.ts'); +},{"/home/runner/work/matrix-js-sdk/matrix-js-sdk/src/rust-crypto/browserify-index.ts":400}],403:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MatrixScheduler = void 0; +/** + * This is an internal module which manages queuing, scheduling and retrying + * of requests. + */ +const utils = __importStar(require("./utils")); +const logger_1 = require("./logger"); +const event_1 = require("./@types/event"); +const http_api_1 = require("./http-api"); +const DEBUG = false; // set true to enable console logging. +// eslint-disable-next-line camelcase +class MatrixScheduler { + /** + * Retries events up to 4 times using exponential backoff. This produces wait + * times of 2, 4, 8, and 16 seconds (30s total) after which we give up. If the + * failure was due to a rate limited request, the time specified in the error is + * waited before being retried. + * @param attempts - Number of attempts that have been made, including the one that just failed (ie. starting at 1) + * @see retryAlgorithm + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + static RETRY_BACKOFF_RATELIMIT(event, attempts, err) { + if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) { + // client error; no amount of retrying with save you now. + return -1; + } + if (err instanceof http_api_1.ConnectionError) { + return -1; + } + // if event that we are trying to send is too large in any way then retrying won't help + if (err.name === "M_TOO_LARGE") { + return -1; + } + if (err.name === "M_LIMIT_EXCEEDED") { + const waitTime = err.data.retry_after_ms; + if (waitTime > 0) { + return waitTime; + } + } + if (attempts > 4) { + return -1; // give up + } + return 1000 * Math.pow(2, attempts); + } + /** + * Queues `m.room.message` events and lets other events continue + * concurrently. + * @see queueAlgorithm + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + static QUEUE_MESSAGES(event) { + // enqueue messages or events that associate with another event (redactions and relations) + if (event.getType() === event_1.EventType.RoomMessage || event.hasAssociation()) { + // put these events in the 'message' queue. + return "message"; + } + // allow all other events continue concurrently. + return null; + } + /** + * Construct a scheduler for Matrix. Requires + * {@link MatrixScheduler#setProcessFunction} to be provided + * with a way of processing events. + * @param retryAlgorithm - Optional. The retry + * algorithm to apply when determining when to try to send an event again. + * Defaults to {@link MatrixScheduler.RETRY_BACKOFF_RATELIMIT}. + * @param queueAlgorithm - Optional. The queuing + * algorithm to apply when determining which events should be sent before the + * given event. Defaults to {@link MatrixScheduler.QUEUE_MESSAGES}. + */ + constructor( + /** + * The retry algorithm to apply when retrying events. To stop retrying, return + * `-1`. If this event was part of a queue, it will be removed from + * the queue. + * @param event - The event being retried. + * @param attempts - The number of failed attempts. This will always be \>= 1. + * @param err - The most recent error message received when trying + * to send this event. + * @returns The number of milliseconds to wait before trying again. If + * this is 0, the request will be immediately retried. If this is + * `-1`, the event will be marked as + * {@link EventStatus.NOT_SENT} and will not be retried. + */ + retryAlgorithm = MatrixScheduler.RETRY_BACKOFF_RATELIMIT, + /** + * The queuing algorithm to apply to events. This function must be idempotent as + * it may be called multiple times with the same event. All queues created are + * serviced in a FIFO manner. To send the event ASAP, return `null` + * which will not put this event in a queue. Events that fail to send that form + * part of a queue will be removed from the queue and the next event in the + * queue will be sent. + * @param event - The event to be sent. + * @returns The name of the queue to put the event into. If a queue with + * this name does not exist, it will be created. If this is `null`, + * the event is not put into a queue and will be sent concurrently. + */ + queueAlgorithm = MatrixScheduler.QUEUE_MESSAGES) { + this.retryAlgorithm = retryAlgorithm; + this.queueAlgorithm = queueAlgorithm; + // queueName: [{ + // event: MatrixEvent, // event to send + // defer: Deferred, // defer to resolve/reject at the END of the retries + // attempts: Number // number of times we've called processFn + // }, ...] + this.queues = {}; + this.activeQueues = []; + this.procFn = null; + this.processQueue = (queueName) => { + // get head of queue + const obj = this.peekNextEvent(queueName); + if (!obj) { + this.disableQueue(queueName); + return; + } + debuglog("Queue '%s' has %s pending events", queueName, this.queues[queueName].length); + // fire the process function and if it resolves, resolve the deferred. Else + // invoke the retry algorithm. + // First wait for a resolved promise, so the resolve handlers for + // the deferred of the previously sent event can run. + // This way enqueued relations/redactions to enqueued events can receive + // the remove id of their target before being sent. + Promise.resolve() + .then(() => { + return this.procFn(obj.event); + }) + .then((res) => { + // remove this from the queue + this.removeNextEvent(queueName); + debuglog("Queue '%s' sent event %s", queueName, obj.event.getId()); + obj.defer.resolve(res); + // keep processing + this.processQueue(queueName); + }, (err) => { + obj.attempts += 1; + // ask the retry algorithm when/if we should try again + const waitTimeMs = this.retryAlgorithm(obj.event, obj.attempts, err); + debuglog("retry(%s) err=%s event_id=%s waitTime=%s", obj.attempts, err, obj.event.getId(), waitTimeMs); + if (waitTimeMs === -1) { + // give up (you quitter!) + debuglog("Queue '%s' giving up on event %s", queueName, obj.event.getId()); + // remove this from the queue + this.clearQueue(queueName, err); + } + else { + setTimeout(this.processQueue, waitTimeMs, queueName); + } + }); + }; + } + /** + * Retrieve a queue based on an event. The event provided does not need to be in + * the queue. + * @param event - An event to get the queue for. + * @returns A shallow copy of events in the queue or null. + * Modifying this array will not modify the list itself. Modifying events in + * this array will modify the underlying event in the queue. + * @see MatrixScheduler.removeEventFromQueue To remove an event from the queue. + */ + getQueueForEvent(event) { + const name = this.queueAlgorithm(event); + if (!name || !this.queues[name]) { + return null; + } + return this.queues[name].map(function (obj) { + return obj.event; + }); + } + /** + * Remove this event from the queue. The event is equal to another event if they + * have the same ID returned from event.getId(). + * @param event - The event to remove. + * @returns True if this event was removed. + */ + removeEventFromQueue(event) { + const name = this.queueAlgorithm(event); + if (!name || !this.queues[name]) { + return false; + } + let removed = false; + utils.removeElement(this.queues[name], (element) => { + if (element.event.getId() === event.getId()) { + // XXX we should probably reject the promise? + // https://github.com/matrix-org/matrix-js-sdk/issues/496 + removed = true; + return true; + } + return false; + }); + return removed; + } + /** + * Set the process function. Required for events in the queue to be processed. + * If set after events have been added to the queue, this will immediately start + * processing them. + * @param fn - The function that can process events + * in the queue. + */ + setProcessFunction(fn) { + this.procFn = fn; + this.startProcessingQueues(); + } + /** + * Queue an event if it is required and start processing queues. + * @param event - The event that may be queued. + * @returns A promise if the event was queued, which will be + * resolved or rejected in due time, else null. + */ + queueEvent(event) { + const queueName = this.queueAlgorithm(event); + if (!queueName) { + return null; + } + // add the event to the queue and make a deferred for it. + if (!this.queues[queueName]) { + this.queues[queueName] = []; + } + const defer = utils.defer(); + this.queues[queueName].push({ + event: event, + defer: defer, + attempts: 0, + }); + debuglog("Queue algorithm dumped event %s into queue '%s'", event.getId(), queueName); + this.startProcessingQueues(); + return defer.promise; + } + startProcessingQueues() { + if (!this.procFn) + return; + // for each inactive queue with events in them + Object.keys(this.queues) + .filter((queueName) => { + return this.activeQueues.indexOf(queueName) === -1 && this.queues[queueName].length > 0; + }) + .forEach((queueName) => { + // mark the queue as active + this.activeQueues.push(queueName); + // begin processing the head of the queue + debuglog("Spinning up queue: '%s'", queueName); + this.processQueue(queueName); + }); + } + disableQueue(queueName) { + // queue is empty. Mark as inactive and stop recursing. + const index = this.activeQueues.indexOf(queueName); + if (index >= 0) { + this.activeQueues.splice(index, 1); + } + debuglog("Stopping queue '%s' as it is now empty", queueName); + } + clearQueue(queueName, err) { + debuglog("clearing queue '%s'", queueName); + let obj; + while ((obj = this.removeNextEvent(queueName))) { + obj.defer.reject(err); + } + this.disableQueue(queueName); + } + peekNextEvent(queueName) { + const queue = this.queues[queueName]; + if (!Array.isArray(queue)) { + return undefined; + } + return queue[0]; + } + removeNextEvent(queueName) { + const queue = this.queues[queueName]; + if (!Array.isArray(queue)) { + return undefined; + } + return queue.shift(); + } +} +exports.MatrixScheduler = MatrixScheduler; +/* istanbul ignore next */ +function debuglog(...args) { + if (DEBUG) { + logger_1.logger.log(...args); + } +} + +},{"./@types/event":306,"./http-api":367,"./logger":374,"./utils":416}],404:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021-2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); + +},{}],405:[function(require,module,exports){ +"use strict"; +/* +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SERVICE_TYPES = void 0; +var SERVICE_TYPES; +(function (SERVICE_TYPES) { + SERVICE_TYPES["IS"] = "SERVICE_TYPE_IS"; + SERVICE_TYPES["IM"] = "SERVICE_TYPE_IM"; +})(SERVICE_TYPES = exports.SERVICE_TYPES || (exports.SERVICE_TYPES = {})); + +},{}],406:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SlidingSyncSdk = void 0; +const room_1 = require("./models/room"); +const logger_1 = require("./logger"); +const utils = __importStar(require("./utils")); +const event_timeline_1 = require("./models/event-timeline"); +const client_1 = require("./client"); +const sync_1 = require("./sync"); +const http_api_1 = require("./http-api"); +const sliding_sync_1 = require("./sliding-sync"); +const event_1 = require("./@types/event"); +const room_state_1 = require("./models/room-state"); +const room_member_1 = require("./models/room-member"); +// Number of consecutive failed syncs that will lead to a syncState of ERROR as opposed +// to RECONNECTING. This is needed to inform the client of server issues when the +// keepAlive is successful but the server /sync fails. +const FAILED_SYNC_ERROR_THRESHOLD = 3; +class ExtensionE2EE { + constructor(crypto) { + this.crypto = crypto; + } + name() { + return "e2ee"; + } + when() { + return sliding_sync_1.ExtensionState.PreProcess; + } + onRequest(isInitial) { + if (!isInitial) { + return undefined; + } + return { + enabled: true, // this is sticky so only send it on the initial request + }; + } + onResponse(data) { + return __awaiter(this, void 0, void 0, function* () { + // Handle device list updates + if (data["device_lists"]) { + yield this.crypto.handleDeviceListChanges({ + oldSyncToken: "yep", // XXX need to do this so the device list changes get processed :( + }, data["device_lists"]); + } + // Handle one_time_keys_count + if (data["device_one_time_keys_count"]) { + const currentCount = data["device_one_time_keys_count"].signed_curve25519 || 0; + this.crypto.updateOneTimeKeyCount(currentCount); + } + if (data["device_unused_fallback_key_types"] || data["org.matrix.msc2732.device_unused_fallback_key_types"]) { + // The presence of device_unused_fallback_key_types indicates that the + // server supports fallback keys. If there's no unused + // signed_curve25519 fallback key we need a new one. + const unusedFallbackKeys = data["device_unused_fallback_key_types"] || data["org.matrix.msc2732.device_unused_fallback_key_types"]; + this.crypto.setNeedsNewFallback(Array.isArray(unusedFallbackKeys) && !unusedFallbackKeys.includes("signed_curve25519")); + } + this.crypto.onSyncCompleted({}); + }); + } +} +class ExtensionToDevice { + constructor(client, cryptoCallbacks) { + this.client = client; + this.cryptoCallbacks = cryptoCallbacks; + this.nextBatch = null; + } + name() { + return "to_device"; + } + when() { + return sliding_sync_1.ExtensionState.PreProcess; + } + onRequest(isInitial) { + const extReq = { + since: this.nextBatch !== null ? this.nextBatch : undefined, + }; + if (isInitial) { + extReq["limit"] = 100; + extReq["enabled"] = true; + } + return extReq; + } + onResponse(data) { + return __awaiter(this, void 0, void 0, function* () { + const cancelledKeyVerificationTxns = []; + let events = data["events"] || []; + if (events.length > 0 && this.cryptoCallbacks) { + events = yield this.cryptoCallbacks.preprocessToDeviceMessages(events); + } + events + .map(this.client.getEventMapper()) + .map((toDeviceEvent) => { + // map is a cheap inline forEach + // We want to flag m.key.verification.start events as cancelled + // if there's an accompanying m.key.verification.cancel event, so + // we pull out the transaction IDs from the cancellation events + // so we can flag the verification events as cancelled in the loop + // below. + if (toDeviceEvent.getType() === "m.key.verification.cancel") { + const txnId = toDeviceEvent.getContent()["transaction_id"]; + if (txnId) { + cancelledKeyVerificationTxns.push(txnId); + } + } + // as mentioned above, .map is a cheap inline forEach, so return + // the unmodified event. + return toDeviceEvent; + }) + .forEach((toDeviceEvent) => { + const content = toDeviceEvent.getContent(); + if (toDeviceEvent.getType() == "m.room.message" && content.msgtype == "m.bad.encrypted") { + // the mapper already logged a warning. + logger_1.logger.log("Ignoring undecryptable to-device event from " + toDeviceEvent.getSender()); + return; + } + if (toDeviceEvent.getType() === "m.key.verification.start" || + toDeviceEvent.getType() === "m.key.verification.request") { + const txnId = content["transaction_id"]; + if (cancelledKeyVerificationTxns.includes(txnId)) { + toDeviceEvent.flagCancelled(); + } + } + this.client.emit(client_1.ClientEvent.ToDeviceEvent, toDeviceEvent); + }); + this.nextBatch = data.next_batch; + }); + } +} +class ExtensionAccountData { + constructor(client) { + this.client = client; + } + name() { + return "account_data"; + } + when() { + return sliding_sync_1.ExtensionState.PostProcess; + } + onRequest(isInitial) { + if (!isInitial) { + return undefined; + } + return { + enabled: true, + }; + } + onResponse(data) { + if (data.global && data.global.length > 0) { + this.processGlobalAccountData(data.global); + } + for (const roomId in data.rooms) { + const accountDataEvents = mapEvents(this.client, roomId, data.rooms[roomId]); + const room = this.client.getRoom(roomId); + if (!room) { + logger_1.logger.warn("got account data for room but room doesn't exist on client:", roomId); + continue; + } + room.addAccountData(accountDataEvents); + accountDataEvents.forEach((e) => { + this.client.emit(client_1.ClientEvent.Event, e); + }); + } + } + processGlobalAccountData(globalAccountData) { + const events = mapEvents(this.client, undefined, globalAccountData); + const prevEventsMap = events.reduce((m, c) => { + m[c.getType()] = this.client.store.getAccountData(c.getType()); + return m; + }, {}); + this.client.store.storeAccountDataEvents(events); + events.forEach((accountDataEvent) => { + // Honour push rules that come down the sync stream but also + // honour push rules that were previously cached. Base rules + // will be updated when we receive push rules via getPushRules + // (see sync) before syncing over the network. + if (accountDataEvent.getType() === event_1.EventType.PushRules) { + const rules = accountDataEvent.getContent(); + this.client.setPushRules(rules); + } + const prevEvent = prevEventsMap[accountDataEvent.getType()]; + this.client.emit(client_1.ClientEvent.AccountData, accountDataEvent, prevEvent); + return accountDataEvent; + }); + } +} +class ExtensionTyping { + constructor(client) { + this.client = client; + } + name() { + return "typing"; + } + when() { + return sliding_sync_1.ExtensionState.PostProcess; + } + onRequest(isInitial) { + if (!isInitial) { + return undefined; // don't send a JSON object for subsequent requests, we don't need to. + } + return { + enabled: true, + }; + } + onResponse(data) { + if (!(data === null || data === void 0 ? void 0 : data.rooms)) { + return; + } + for (const roomId in data.rooms) { + processEphemeralEvents(this.client, roomId, [data.rooms[roomId]]); + } + } +} +class ExtensionReceipts { + constructor(client) { + this.client = client; + } + name() { + return "receipts"; + } + when() { + return sliding_sync_1.ExtensionState.PostProcess; + } + onRequest(isInitial) { + if (isInitial) { + return { + enabled: true, + }; + } + return undefined; // don't send a JSON object for subsequent requests, we don't need to. + } + onResponse(data) { + if (!(data === null || data === void 0 ? void 0 : data.rooms)) { + return; + } + for (const roomId in data.rooms) { + processEphemeralEvents(this.client, roomId, [data.rooms[roomId]]); + } + } +} +/** + * A copy of SyncApi such that it can be used as a drop-in replacement for sync v2. For the actual + * sliding sync API, see sliding-sync.ts or the class SlidingSync. + */ +class SlidingSyncSdk { + constructor(slidingSync, client, opts, syncOpts) { + this.slidingSync = slidingSync; + this.client = client; + this.syncState = null; + this.lastPos = null; + this.failCount = 0; + this.notifEvents = []; // accumulator of sync events in the current sync response + this.opts = (0, sync_1.defaultClientOpts)(opts); + this.syncOpts = (0, sync_1.defaultSyncApiOpts)(syncOpts); + if (client.getNotifTimelineSet()) { + client.reEmitter.reEmit(client.getNotifTimelineSet(), [room_1.RoomEvent.Timeline, room_1.RoomEvent.TimelineReset]); + } + this.slidingSync.on(sliding_sync_1.SlidingSyncEvent.Lifecycle, this.onLifecycle.bind(this)); + this.slidingSync.on(sliding_sync_1.SlidingSyncEvent.RoomData, this.onRoomData.bind(this)); + const extensions = [ + new ExtensionToDevice(this.client, this.syncOpts.cryptoCallbacks), + new ExtensionAccountData(this.client), + new ExtensionTyping(this.client), + new ExtensionReceipts(this.client), + ]; + if (this.syncOpts.crypto) { + extensions.push(new ExtensionE2EE(this.syncOpts.crypto)); + } + extensions.forEach((ext) => { + this.slidingSync.registerExtension(ext); + }); + } + onRoomData(roomId, roomData) { + let room = this.client.store.getRoom(roomId); + if (!room) { + if (!roomData.initial) { + logger_1.logger.debug("initial flag not set but no stored room exists for room ", roomId, roomData); + return; + } + room = (0, sync_1._createAndReEmitRoom)(this.client, roomId, this.opts); + } + this.processRoomData(this.client, room, roomData); + } + onLifecycle(state, resp, err) { + if (err) { + logger_1.logger.debug("onLifecycle", state, err); + } + switch (state) { + case sliding_sync_1.SlidingSyncState.Complete: + this.purgeNotifications(); + if (!resp) { + break; + } + // Element won't stop showing the initial loading spinner unless we fire SyncState.Prepared + if (!this.lastPos) { + this.updateSyncState(sync_1.SyncState.Prepared, { + oldSyncToken: undefined, + nextSyncToken: resp.pos, + catchingUp: false, + fromCache: false, + }); + } + // Conversely, Element won't show the room list unless there is at least 1x SyncState.Syncing + // so hence for the very first sync we will fire prepared then immediately syncing. + this.updateSyncState(sync_1.SyncState.Syncing, { + oldSyncToken: this.lastPos, + nextSyncToken: resp.pos, + catchingUp: false, + fromCache: false, + }); + this.lastPos = resp.pos; + break; + case sliding_sync_1.SlidingSyncState.RequestFinished: + if (err) { + this.failCount += 1; + this.updateSyncState(this.failCount > FAILED_SYNC_ERROR_THRESHOLD ? sync_1.SyncState.Error : sync_1.SyncState.Reconnecting, { + error: new http_api_1.MatrixError(err), + }); + if (this.shouldAbortSync(new http_api_1.MatrixError(err))) { + return; // shouldAbortSync actually stops syncing too so we don't need to do anything. + } + } + else { + this.failCount = 0; + } + break; + } + } + /** + * Sync rooms the user has left. + * @returns Resolved when they've been added to the store. + */ + syncLeftRooms() { + return __awaiter(this, void 0, void 0, function* () { + return []; // TODO + }); + } + /** + * Peek into a room. This will result in the room in question being synced so it + * is accessible via getRooms(). Live updates for the room will be provided. + * @param roomId - The room ID to peek into. + * @returns A promise which resolves once the room has been added to the + * store. + */ + peek(_roomId) { + return __awaiter(this, void 0, void 0, function* () { + return null; // TODO + }); + } + /** + * Stop polling for updates in the peeked room. NOPs if there is no room being + * peeked. + */ + stopPeeking() { + // TODO + } + /** + * Returns the current state of this sync object + * @see MatrixClient#event:"sync" + */ + getSyncState() { + return this.syncState; + } + /** + * Returns the additional data object associated with + * the current sync state, or null if there is no + * such data. + * Sync errors, if available, are put in the 'error' key of + * this object. + */ + getSyncStateData() { + var _a; + return (_a = this.syncStateData) !== null && _a !== void 0 ? _a : null; + } + // Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts + createRoom(roomId) { + // XXX cargoculted from sync.ts + const { timelineSupport } = this.client; + const room = new room_1.Room(roomId, this.client, this.client.getUserId(), { + lazyLoadMembers: this.opts.lazyLoadMembers, + pendingEventOrdering: this.opts.pendingEventOrdering, + timelineSupport, + }); + this.client.reEmitter.reEmit(room, [ + room_1.RoomEvent.Name, + room_1.RoomEvent.Redaction, + room_1.RoomEvent.RedactionCancelled, + room_1.RoomEvent.Receipt, + room_1.RoomEvent.Tags, + room_1.RoomEvent.LocalEchoUpdated, + room_1.RoomEvent.AccountData, + room_1.RoomEvent.MyMembership, + room_1.RoomEvent.Timeline, + room_1.RoomEvent.TimelineReset, + ]); + this.registerStateListeners(room); + return room; + } + registerStateListeners(room) { + // XXX cargoculted from sync.ts + // we need to also re-emit room state and room member events, so hook it up + // to the client now. We need to add a listener for RoomState.members in + // order to hook them correctly. + this.client.reEmitter.reEmit(room.currentState, [ + room_state_1.RoomStateEvent.Events, + room_state_1.RoomStateEvent.Members, + room_state_1.RoomStateEvent.NewMember, + room_state_1.RoomStateEvent.Update, + ]); + room.currentState.on(room_state_1.RoomStateEvent.NewMember, (event, state, member) => { + var _a; + member.user = (_a = this.client.getUser(member.userId)) !== null && _a !== void 0 ? _a : undefined; + this.client.reEmitter.reEmit(member, [ + room_member_1.RoomMemberEvent.Name, + room_member_1.RoomMemberEvent.Typing, + room_member_1.RoomMemberEvent.PowerLevel, + room_member_1.RoomMemberEvent.Membership, + ]); + }); + } + /* + private deregisterStateListeners(room: Room): void { // XXX cargoculted from sync.ts + // could do with a better way of achieving this. + room.currentState.removeAllListeners(RoomStateEvent.Events); + room.currentState.removeAllListeners(RoomStateEvent.Members); + room.currentState.removeAllListeners(RoomStateEvent.NewMember); + } */ + shouldAbortSync(error) { + if (error.errcode === "M_UNKNOWN_TOKEN") { + // The logout already happened, we just need to stop. + logger_1.logger.warn("Token no longer valid - assuming logout"); + this.stop(); + this.updateSyncState(sync_1.SyncState.Error, { error }); + return true; + } + return false; + } + processRoomData(client, room, roomData) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + roomData = ensureNameEvent(client, room.roomId, roomData); + const stateEvents = mapEvents(this.client, room.roomId, roomData.required_state); + // Prevent events from being decrypted ahead of time + // this helps large account to speed up faster + // room::decryptCriticalEvent is in charge of decrypting all the events + // required for a client to function properly + let timelineEvents = mapEvents(this.client, room.roomId, roomData.timeline, false); + const ephemeralEvents = []; // TODO this.mapSyncEventsFormat(joinObj.ephemeral); + // TODO: handle threaded / beacon events + if (roomData.initial) { + // we should not know about any of these timeline entries if this is a genuinely new room. + // If we do, then we've effectively done scrollback (e.g requesting timeline_limit: 1 for + // this room, then timeline_limit: 50). + const knownEvents = new Set(); + room.getLiveTimeline() + .getEvents() + .forEach((e) => { + knownEvents.add(e.getId()); + }); + // all unknown events BEFORE a known event must be scrollback e.g: + // D E <-- what we know + // A B C D E F <-- what we just received + // means: + // A B C <-- scrollback + // D E <-- dupes + // F <-- new event + // We bucket events based on if we have seen a known event yet. + const oldEvents = []; + const newEvents = []; + let seenKnownEvent = false; + for (let i = timelineEvents.length - 1; i >= 0; i--) { + const recvEvent = timelineEvents[i]; + if (knownEvents.has(recvEvent.getId())) { + seenKnownEvent = true; + continue; // don't include this event, it's a dupe + } + if (seenKnownEvent) { + // old -> new + oldEvents.push(recvEvent); + } + else { + // old -> new + newEvents.unshift(recvEvent); + } + } + timelineEvents = newEvents; + if (oldEvents.length > 0) { + // old events are scrollback, insert them now + room.addEventsToTimeline(oldEvents, true, room.getLiveTimeline(), roomData.prev_batch); + } + } + const encrypted = this.client.isRoomEncrypted(room.roomId); + // we do this first so it's correct when any of the events fire + if (roomData.notification_count != null) { + room.setUnreadNotificationCount(room_1.NotificationCountType.Total, roomData.notification_count); + } + if (roomData.highlight_count != null) { + // We track unread notifications ourselves in encrypted rooms, so don't + // bother setting it here. We trust our calculations better than the + // server's for this case, and therefore will assume that our non-zero + // count is accurate. + if (!encrypted || (encrypted && room.getUnreadNotificationCount(room_1.NotificationCountType.Highlight) <= 0)) { + room.setUnreadNotificationCount(room_1.NotificationCountType.Highlight, roomData.highlight_count); + } + } + if (Number.isInteger(roomData.invited_count)) { + room.currentState.setInvitedMemberCount(roomData.invited_count); + } + if (Number.isInteger(roomData.joined_count)) { + room.currentState.setJoinedMemberCount(roomData.joined_count); + } + if (roomData.invite_state) { + const inviteStateEvents = mapEvents(this.client, room.roomId, roomData.invite_state); + this.injectRoomEvents(room, inviteStateEvents); + if (roomData.initial) { + room.recalculate(); + this.client.store.storeRoom(room); + this.client.emit(client_1.ClientEvent.Room, room); + } + inviteStateEvents.forEach((e) => { + this.client.emit(client_1.ClientEvent.Event, e); + }); + room.updateMyMembership("invite"); + return; + } + if (roomData.initial) { + // set the back-pagination token. Do this *before* adding any + // events so that clients can start back-paginating. + room.getLiveTimeline().setPaginationToken((_a = roomData.prev_batch) !== null && _a !== void 0 ? _a : null, event_timeline_1.EventTimeline.BACKWARDS); + } + /* TODO + else if (roomData.limited) { + + let limited = true; + + // we've got a limited sync, so we *probably* have a gap in the + // timeline, so should reset. But we might have been peeking or + // paginating and already have some of the events, in which + // case we just want to append any subsequent events to the end + // of the existing timeline. + // + // This is particularly important in the case that we already have + // *all* of the events in the timeline - in that case, if we reset + // the timeline, we'll end up with an entirely empty timeline, + // which we'll try to paginate but not get any new events (which + // will stop us linking the empty timeline into the chain). + // + for (let i = timelineEvents.length - 1; i >= 0; i--) { + const eventId = timelineEvents[i].getId(); + if (room.getTimelineForEvent(eventId)) { + logger.debug("Already have event " + eventId + " in limited " + + "sync - not resetting"); + limited = false; + + // we might still be missing some of the events before i; + // we don't want to be adding them to the end of the + // timeline because that would put them out of order. + timelineEvents.splice(0, i); + + // XXX: there's a problem here if the skipped part of the + // timeline modifies the state set in stateEvents, because + // we'll end up using the state from stateEvents rather + // than the later state from timelineEvents. We probably + // need to wind stateEvents forward over the events we're + // skipping. + break; + } + } + + if (limited) { + room.resetLiveTimeline( + roomData.prev_batch, + null, // TODO this.syncOpts.canResetEntireTimeline(room.roomId) ? null : syncEventData.oldSyncToken, + ); + + // We have to assume any gap in any timeline is + // reason to stop incrementally tracking notifications and + // reset the timeline. + this.client.resetNotifTimelineSet(); + this.registerStateListeners(room); + } + } */ + this.injectRoomEvents(room, stateEvents, timelineEvents, roomData.num_live); + // we deliberately don't add ephemeral events to the timeline + room.addEphemeralEvents(ephemeralEvents); + // local fields must be set before any async calls because call site assumes + // synchronous execution prior to emitting SlidingSyncState.Complete + room.updateMyMembership("join"); + room.recalculate(); + if (roomData.initial) { + client.store.storeRoom(room); + client.emit(client_1.ClientEvent.Room, room); + } + // check if any timeline events should bing and add them to the notifEvents array: + // we'll purge this once we've fully processed the sync response + this.addNotifications(timelineEvents); + const processRoomEvent = (e) => __awaiter(this, void 0, void 0, function* () { + client.emit(client_1.ClientEvent.Event, e); + if (e.isState() && e.getType() == event_1.EventType.RoomEncryption && this.syncOpts.cryptoCallbacks) { + yield this.syncOpts.cryptoCallbacks.onCryptoEvent(room, e); + } + }); + yield utils.promiseMapSeries(stateEvents, processRoomEvent); + yield utils.promiseMapSeries(timelineEvents, processRoomEvent); + ephemeralEvents.forEach(function (e) { + client.emit(client_1.ClientEvent.Event, e); + }); + // Decrypt only the last message in all rooms to make sure we can generate a preview + // And decrypt all events after the recorded read receipt to ensure an accurate + // notification count + room.decryptCriticalEvents(); + }); + } + /** + * Injects events into a room's model. + * @param stateEventList - A list of state events. This is the state + * at the *START* of the timeline list if it is supplied. + * @param timelineEventList - A list of timeline events. Lower index + * is earlier in time. Higher index is later. + * @param numLive - the number of events in timelineEventList which just happened, + * supplied from the server. + */ + injectRoomEvents(room, stateEventList, timelineEventList, numLive) { + timelineEventList = timelineEventList || []; + stateEventList = stateEventList || []; + numLive = numLive || 0; + // If there are no events in the timeline yet, initialise it with + // the given state events + const liveTimeline = room.getLiveTimeline(); + const timelineWasEmpty = liveTimeline.getEvents().length == 0; + if (timelineWasEmpty) { + // Passing these events into initialiseState will freeze them, so we need + // to compute and cache the push actions for them now, otherwise sync dies + // with an attempt to assign to read only property. + // XXX: This is pretty horrible and is assuming all sorts of behaviour from + // these functions that it shouldn't be. We should probably either store the + // push actions cache elsewhere so we can freeze MatrixEvents, or otherwise + // find some solution where MatrixEvents are immutable but allow for a cache + // field. + for (const ev of stateEventList) { + this.client.getPushActionsForEvent(ev); + } + liveTimeline.initialiseState(stateEventList); + } + // If the timeline wasn't empty, we process the state events here: they're + // defined as updates to the state before the start of the timeline, so this + // starts to roll the state forward. + // XXX: That's what we *should* do, but this can happen if we were previously + // peeking in a room, in which case we obviously do *not* want to add the + // state events here onto the end of the timeline. Historically, the js-sdk + // has just set these new state events on the old and new state. This seems + // very wrong because there could be events in the timeline that diverge the + // state, in which case this is going to leave things out of sync. However, + // for now I think it;s best to behave the same as the code has done previously. + if (!timelineWasEmpty) { + // XXX: As above, don't do this... + //room.addLiveEvents(stateEventList || []); + // Do this instead... + room.oldState.setStateEvents(stateEventList); + room.currentState.setStateEvents(stateEventList); + } + // the timeline is broken into 'live' events which just happened and normal timeline events + // which are still to be appended to the end of the live timeline but happened a while ago. + // The live events are marked as fromCache=false to ensure that downstream components know + // this is a live event, not historical (from a remote server cache). + let liveTimelineEvents = []; + if (numLive > 0) { + // last numLive events are live + liveTimelineEvents = timelineEventList.slice(-1 * numLive); + // everything else is not live + timelineEventList = timelineEventList.slice(0, -1 * liveTimelineEvents.length); + } + // execute the timeline events. This will continue to diverge the current state + // if the timeline has any state events in it. + // This also needs to be done before running push rules on the events as they need + // to be decorated with sender etc. + room.addLiveEvents(timelineEventList, { + fromCache: true, + }); + if (liveTimelineEvents.length > 0) { + room.addLiveEvents(liveTimelineEvents, { + fromCache: false, + }); + } + room.recalculate(); + // resolve invites now we have set the latest state + this.resolveInvites(room); + } + resolveInvites(room) { + if (!room || !this.opts.resolveInvitesToProfiles) { + return; + } + const client = this.client; + // For each invited room member we want to give them a displayname/avatar url + // if they have one (the m.room.member invites don't contain this). + room.getMembersWithMembership("invite").forEach(function (member) { + if (member.requestedProfileInfo) + return; + member.requestedProfileInfo = true; + // try to get a cached copy first. + const user = client.getUser(member.userId); + let promise; + if (user) { + promise = Promise.resolve({ + avatar_url: user.avatarUrl, + displayname: user.displayName, + }); + } + else { + promise = client.getProfileInfo(member.userId); + } + promise.then(function (info) { + // slightly naughty by doctoring the invite event but this means all + // the code paths remain the same between invite/join display name stuff + // which is a worthy trade-off for some minor pollution. + const inviteEvent = member.events.member; + if (inviteEvent.getContent().membership !== "invite") { + // between resolving and now they have since joined, so don't clobber + return; + } + inviteEvent.getContent().avatar_url = info.avatar_url; + inviteEvent.getContent().displayname = info.displayname; + // fire listeners + member.setMembershipEvent(inviteEvent, room.currentState); + }, function (_err) { + // OH WELL. + }); + }); + } + retryImmediately() { + return true; + } + /** + * Main entry point. Blocks until stop() is called. + */ + sync() { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.debug("Sliding sync init loop"); + // 1) We need to get push rules so we can check if events should bing as we get + // them from /sync. + while (!this.client.isGuest()) { + try { + logger_1.logger.debug("Getting push rules..."); + const result = yield this.client.getPushRules(); + logger_1.logger.debug("Got push rules"); + this.client.pushRules = result; + break; + } + catch (err) { + logger_1.logger.error("Getting push rules failed", err); + if (this.shouldAbortSync(err)) { + return; + } + } + } + // start syncing + yield this.slidingSync.start(); + }); + } + /** + * Stops the sync object from syncing. + */ + stop() { + logger_1.logger.debug("SyncApi.stop"); + this.slidingSync.stop(); + } + /** + * Sets the sync state and emits an event to say so + * @param newState - The new state string + * @param data - Object of additional data to emit in the event + */ + updateSyncState(newState, data) { + const old = this.syncState; + this.syncState = newState; + this.syncStateData = data; + this.client.emit(client_1.ClientEvent.Sync, this.syncState, old, data); + } + /** + * Takes a list of timelineEvents and adds and adds to notifEvents + * as appropriate. + * This must be called after the room the events belong to has been stored. + * + * @param timelineEventList - A list of timeline events. Lower index + * is earlier in time. Higher index is later. + */ + addNotifications(timelineEventList) { + // gather our notifications into this.notifEvents + if (!this.client.getNotifTimelineSet()) { + return; + } + for (const timelineEvent of timelineEventList) { + const pushActions = this.client.getPushActionsForEvent(timelineEvent); + if (pushActions && pushActions.notify && pushActions.tweaks && pushActions.tweaks.highlight) { + this.notifEvents.push(timelineEvent); + } + } + } + /** + * Purge any events in the notifEvents array. Used after a /sync has been complete. + * This should not be called at a per-room scope (e.g in onRoomData) because otherwise the ordering + * will be messed up e.g room A gets a bing, room B gets a newer bing, but both in the same /sync + * response. If we purge at a per-room scope then we could process room B before room A leading to + * room B appearing earlier in the notifications timeline, even though it has the higher origin_server_ts. + */ + purgeNotifications() { + this.notifEvents.sort(function (a, b) { + return a.getTs() - b.getTs(); + }); + this.notifEvents.forEach((event) => { + var _a; + (_a = this.client.getNotifTimelineSet()) === null || _a === void 0 ? void 0 : _a.addLiveEvent(event); + }); + this.notifEvents = []; + } +} +exports.SlidingSyncSdk = SlidingSyncSdk; +function ensureNameEvent(client, roomId, roomData) { + // make sure m.room.name is in required_state if there is a name, replacing anything previously + // there if need be. This ensures clients transparently 'calculate' the right room name. Native + // sliding sync clients should just read the "name" field. + if (!roomData.name) { + return roomData; + } + for (const stateEvent of roomData.required_state) { + if (stateEvent.type === event_1.EventType.RoomName && stateEvent.state_key === "") { + stateEvent.content = { + name: roomData.name, + }; + return roomData; + } + } + roomData.required_state.push({ + event_id: "$fake-sliding-sync-name-event-" + roomId, + state_key: "", + type: event_1.EventType.RoomName, + content: { + name: roomData.name, + }, + sender: client.getUserId(), + origin_server_ts: new Date().getTime(), + }); + return roomData; +} +// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts, +// just outside the class. +function mapEvents(client, roomId, events, decrypt = true) { + const mapper = client.getEventMapper({ decrypt }); + return events.map(function (e) { + e.room_id = roomId; + return mapper(e); + }); +} +function processEphemeralEvents(client, roomId, ephEvents) { + const ephemeralEvents = mapEvents(client, roomId, ephEvents); + const room = client.getRoom(roomId); + if (!room) { + logger_1.logger.warn("got ephemeral events for room but room doesn't exist on client:", roomId); + return; + } + room.addEphemeralEvents(ephemeralEvents); + ephemeralEvents.forEach((e) => { + client.emit(client_1.ClientEvent.Event, e); + }); +} + +},{"./@types/event":306,"./client":321,"./http-api":367,"./logger":374,"./models/event-timeline":382,"./models/room":392,"./models/room-member":389,"./models/room-state":390,"./sliding-sync":407,"./sync":414,"./utils":416}],407:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SlidingSync = exports.SlidingSyncEvent = exports.ExtensionState = exports.SlidingSyncState = exports.MSC3575_STATE_KEY_LAZY = exports.MSC3575_STATE_KEY_ME = exports.MSC3575_WILDCARD = void 0; +const logger_1 = require("./logger"); +const typed_event_emitter_1 = require("./models/typed-event-emitter"); +const utils_1 = require("./utils"); +// /sync requests allow you to set a timeout= but the request may continue +// beyond that and wedge forever, so we need to track how long we are willing +// to keep open the connection. This constant is *ADDED* to the timeout= value +// to determine the max time we're willing to wait. +const BUFFER_PERIOD_MS = 10 * 1000; +exports.MSC3575_WILDCARD = "*"; +exports.MSC3575_STATE_KEY_ME = "$ME"; +exports.MSC3575_STATE_KEY_LAZY = "$LAZY"; +var SlidingSyncState; +(function (SlidingSyncState) { + /** + * Fired by SlidingSyncEvent.Lifecycle event immediately before processing the response. + */ + SlidingSyncState["RequestFinished"] = "FINISHED"; + /** + * Fired by SlidingSyncEvent.Lifecycle event immediately after all room data listeners have been + * invoked, but before list listeners. + */ + SlidingSyncState["Complete"] = "COMPLETE"; +})(SlidingSyncState = exports.SlidingSyncState || (exports.SlidingSyncState = {})); +/** + * Internal Class. SlidingList represents a single list in sliding sync. The list can have filters, + * multiple sliding windows, and maintains the index-\>room_id mapping. + */ +class SlidingList { + /** + * Construct a new sliding list. + * @param list - The range, sort and filter values to use for this list. + */ + constructor(list) { + // returned data + this.roomIndexToRoomId = {}; + this.joinedCount = 0; + this.replaceList(list); + } + /** + * Mark this list as modified or not. Modified lists will return sticky params with calls to getList. + * This is useful for the first time the list is sent, or if the list has changed in some way. + * @param modified - True to mark this list as modified so all sticky parameters will be re-sent. + */ + setModified(modified) { + this.isModified = modified; + } + /** + * Update the list range for this list. Does not affect modified status as list ranges are non-sticky. + * @param newRanges - The new ranges for the list + */ + updateListRange(newRanges) { + this.list.ranges = JSON.parse(JSON.stringify(newRanges)); + } + /** + * Replace list parameters. All fields will be replaced with the new list parameters. + * @param list - The new list parameters + */ + replaceList(list) { + list.filters = list.filters || {}; + list.ranges = list.ranges || []; + this.list = JSON.parse(JSON.stringify(list)); + this.isModified = true; + // reset values as the join count may be very different (if filters changed) including the rooms + // (e.g. sort orders or sliding window ranges changed) + // the constantly changing sliding window ranges. Not an array for performance reasons + // E.g. tracking ranges 0-99, 500-599, we don't want to have a 600 element array + this.roomIndexToRoomId = {}; + // the total number of joined rooms according to the server, always >= len(roomIndexToRoomId) + this.joinedCount = 0; + } + /** + * Return a copy of the list suitable for a request body. + * @param forceIncludeAllParams - True to forcibly include all params even if the list + * hasn't been modified. Callers may want to do this if they are modifying the list prior to calling + * updateList. + */ + getList(forceIncludeAllParams) { + let list = { + ranges: JSON.parse(JSON.stringify(this.list.ranges)), + }; + if (this.isModified || forceIncludeAllParams) { + list = JSON.parse(JSON.stringify(this.list)); + } + return list; + } + /** + * Check if a given index is within the list range. This is required even though the /sync API + * provides explicit updates with index positions because of the following situation: + * 0 1 2 3 4 5 6 7 8 indexes + * a b c d e f COMMANDS: SYNC 0 2 a b c; SYNC 6 8 d e f; + * a b c d _ f COMMAND: DELETE 7; + * e a b c d f COMMAND: INSERT 0 e; + * c=3 is wrong as we are not tracking it, ergo we need to see if `i` is in range else drop it + * @param i - The index to check + * @returns True if the index is within a sliding window + */ + isIndexInRange(i) { + for (const r of this.list.ranges) { + if (r[0] <= i && i <= r[1]) { + return true; + } + } + return false; + } +} +/** + * When onResponse extensions should be invoked: before or after processing the main response. + */ +var ExtensionState; +(function (ExtensionState) { + // Call onResponse before processing the response body. This is useful when your extension is + // preparing the ground for the response body e.g. processing to-device messages before the + // encrypted event arrives. + ExtensionState["PreProcess"] = "ExtState.PreProcess"; + // Call onResponse after processing the response body. This is useful when your extension is + // decorating data from the client, and you rely on MatrixClient.getRoom returning the Room object + // e.g. room account data. + ExtensionState["PostProcess"] = "ExtState.PostProcess"; +})(ExtensionState = exports.ExtensionState || (exports.ExtensionState = {})); +/** + * Events which can be fired by the SlidingSync class. These are designed to provide different levels + * of information when processing sync responses. + * - RoomData: concerns rooms, useful for SlidingSyncSdk to update its knowledge of rooms. + * - Lifecycle: concerns callbacks at various well-defined points in the sync process. + * - List: concerns lists, useful for UI layers to re-render room lists. + * Specifically, the order of event invocation is: + * - Lifecycle (state=RequestFinished) + * - RoomData (N times) + * - Lifecycle (state=Complete) + * - List (at most once per list) + */ +var SlidingSyncEvent; +(function (SlidingSyncEvent) { + /** + * This event fires when there are updates for a room. Fired as and when rooms are encountered + * in the response. + */ + SlidingSyncEvent["RoomData"] = "SlidingSync.RoomData"; + /** + * This event fires at various points in the /sync loop lifecycle. + * - SlidingSyncState.RequestFinished: Fires after we receive a valid response but before the + * response has been processed. Perform any pre-process steps here. If there was a problem syncing, + * `err` will be set (e.g network errors). + * - SlidingSyncState.Complete: Fires after all SlidingSyncEvent.RoomData have been fired but before + * SlidingSyncEvent.List. + */ + SlidingSyncEvent["Lifecycle"] = "SlidingSync.Lifecycle"; + /** + * This event fires whenever there has been a change to this list index. It fires exactly once + * per list, even if there were multiple operations for the list. + * It fires AFTER Lifecycle and RoomData events. + */ + SlidingSyncEvent["List"] = "SlidingSync.List"; +})(SlidingSyncEvent = exports.SlidingSyncEvent || (exports.SlidingSyncEvent = {})); +/** + * SlidingSync is a high-level data structure which controls the majority of sliding sync. + * It has no hooks into JS SDK except for needing a MatrixClient to perform the HTTP request. + * This means this class (and everything it uses) can be used in isolation from JS SDK if needed. + * To hook this up with the JS SDK, you need to use SlidingSyncSdk. + */ +class SlidingSync extends typed_event_emitter_1.TypedEventEmitter { + /** + * Create a new sliding sync instance + * @param proxyBaseUrl - The base URL of the sliding sync proxy + * @param lists - The lists to use for sliding sync. + * @param roomSubscriptionInfo - The params to use for room subscriptions. + * @param client - The client to use for /sync calls. + * @param timeoutMS - The number of milliseconds to wait for a response. + */ + constructor(proxyBaseUrl, lists, roomSubscriptionInfo, client, timeoutMS) { + super(); + this.proxyBaseUrl = proxyBaseUrl; + this.roomSubscriptionInfo = roomSubscriptionInfo; + this.client = client; + this.timeoutMS = timeoutMS; + this.listModifiedCount = 0; + this.terminated = false; + // flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :( + this.needsResend = false; + // the txn_id to send with the next request. + this.txnId = null; + // a list (in chronological order of when they were sent) of objects containing the txn ID and + // a defer to resolve/reject depending on whether they were successfully sent or not. + this.txnIdDefers = []; + // map of extension name to req/resp handler + this.extensions = {}; + this.desiredRoomSubscriptions = new Set(); // the *desired* room subscriptions + this.confirmedRoomSubscriptions = new Set(); + // map of custom subscription name to the subscription + this.customSubscriptions = new Map(); + // map of room ID to custom subscription name + this.roomIdToCustomSubscription = new Map(); + this.lists = new Map(); + lists.forEach((list, key) => { + this.lists.set(key, new SlidingList(list)); + }); + } + /** + * Add a custom room subscription, referred to by an arbitrary name. If a subscription with this + * name already exists, it is replaced. No requests are sent by calling this method. + * @param name - The name of the subscription. Only used to reference this subscription in + * useCustomSubscription. + * @param sub - The subscription information. + */ + addCustomSubscription(name, sub) { + if (this.customSubscriptions.has(name)) { + logger_1.logger.warn(`addCustomSubscription: ${name} already exists as a custom subscription, ignoring.`); + return; + } + this.customSubscriptions.set(name, sub); + } + /** + * Use a custom subscription previously added via addCustomSubscription. No requests are sent + * by calling this method. Use modifyRoomSubscriptions to resend subscription information. + * @param roomId - The room to use the subscription in. + * @param name - The name of the subscription. If this name is unknown, the default subscription + * will be used. + */ + useCustomSubscription(roomId, name) { + // We already know about this custom subscription, as it is immutable, + // we don't need to unconfirm the subscription. + if (this.roomIdToCustomSubscription.get(roomId) === name) { + return; + } + this.roomIdToCustomSubscription.set(roomId, name); + // unconfirm this subscription so a resend() will send it up afresh. + this.confirmedRoomSubscriptions.delete(roomId); + } + /** + * Get the room index data for a list. + * @param key - The list key + * @returns The list data which contains the rooms in this list + */ + getListData(key) { + const data = this.lists.get(key); + if (!data) { + return null; + } + return { + joinedCount: data.joinedCount, + roomIndexToRoomId: Object.assign({}, data.roomIndexToRoomId), + }; + } + /** + * Get the full request list parameters for a list index. This function is provided for callers to use + * in conjunction with setList to update fields on an existing list. + * @param key - The list key to get the params for. + * @returns A copy of the list params or undefined. + */ + getListParams(key) { + const params = this.lists.get(key); + if (!params) { + return null; + } + return params.getList(true); + } + /** + * Set new ranges for an existing list. Calling this function when _only_ the ranges have changed + * is more efficient than calling setList(index,list) as this function won't resend sticky params, + * whereas setList always will. + * @param key - The list key to modify + * @param ranges - The new ranges to apply. + * @returns A promise which resolves to the transaction ID when it has been received down sync + * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled + * immediately after sending, in which case the action will be applied in the subsequent request) + */ + setListRanges(key, ranges) { + const list = this.lists.get(key); + if (!list) { + return Promise.reject(new Error("no list with key " + key)); + } + list.updateListRange(ranges); + return this.resend(); + } + /** + * Add or replace a list. Calling this function will interrupt the /sync request to resend new + * lists. + * @param key - The key to modify + * @param list - The new list parameters. + * @returns A promise which resolves to the transaction ID when it has been received down sync + * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled + * immediately after sending, in which case the action will be applied in the subsequent request) + */ + setList(key, list) { + const existingList = this.lists.get(key); + if (existingList) { + existingList.replaceList(list); + this.lists.set(key, existingList); + } + else { + this.lists.set(key, new SlidingList(list)); + } + this.listModifiedCount += 1; + return this.resend(); + } + /** + * Get the room subscriptions for the sync API. + * @returns A copy of the desired room subscriptions. + */ + getRoomSubscriptions() { + return new Set(Array.from(this.desiredRoomSubscriptions)); + } + /** + * Modify the room subscriptions for the sync API. Calling this function will interrupt the + * /sync request to resend new subscriptions. If the /sync stream has not started, this will + * prepare the room subscriptions for when start() is called. + * @param s - The new desired room subscriptions. + * @returns A promise which resolves to the transaction ID when it has been received down sync + * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled + * immediately after sending, in which case the action will be applied in the subsequent request) + */ + modifyRoomSubscriptions(s) { + this.desiredRoomSubscriptions = s; + return this.resend(); + } + /** + * Modify which events to retrieve for room subscriptions. Invalidates all room subscriptions + * such that they will be sent up afresh. + * @param rs - The new room subscription fields to fetch. + * @returns A promise which resolves to the transaction ID when it has been received down sync + * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled + * immediately after sending, in which case the action will be applied in the subsequent request) + */ + modifyRoomSubscriptionInfo(rs) { + this.roomSubscriptionInfo = rs; + this.confirmedRoomSubscriptions = new Set(); + return this.resend(); + } + /** + * Register an extension to send with the /sync request. + * @param ext - The extension to register. + */ + registerExtension(ext) { + if (this.extensions[ext.name()]) { + throw new Error(`registerExtension: ${ext.name()} already exists as an extension`); + } + this.extensions[ext.name()] = ext; + } + getExtensionRequest(isInitial) { + const ext = {}; + Object.keys(this.extensions).forEach((extName) => { + ext[extName] = this.extensions[extName].onRequest(isInitial); + }); + return ext; + } + onPreExtensionsResponse(ext) { + Object.keys(ext).forEach((extName) => { + if (this.extensions[extName].when() == ExtensionState.PreProcess) { + this.extensions[extName].onResponse(ext[extName]); + } + }); + } + onPostExtensionsResponse(ext) { + Object.keys(ext).forEach((extName) => { + if (this.extensions[extName].when() == ExtensionState.PostProcess) { + this.extensions[extName].onResponse(ext[extName]); + } + }); + } + /** + * Invoke all attached room data listeners. + * @param roomId - The room which received some data. + * @param roomData - The raw sliding sync response JSON. + */ + invokeRoomDataListeners(roomId, roomData) { + if (!roomData.required_state) { + roomData.required_state = []; + } + if (!roomData.timeline) { + roomData.timeline = []; + } + this.emit(SlidingSyncEvent.RoomData, roomId, roomData); + } + /** + * Invoke all attached lifecycle listeners. + * @param state - The Lifecycle state + * @param resp - The raw sync response JSON + * @param err - Any error that occurred when making the request e.g. network errors. + */ + invokeLifecycleListeners(state, resp, err) { + this.emit(SlidingSyncEvent.Lifecycle, state, resp, err); + } + shiftRight(listKey, hi, low) { + const list = this.lists.get(listKey); + if (!list) { + return; + } + // l h + // 0,1,2,3,4 <- before + // 0,1,2,2,3 <- after, hi is deleted and low is duplicated + for (let i = hi; i > low; i--) { + if (list.isIndexInRange(i)) { + list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i - 1]; + } + } + } + shiftLeft(listKey, hi, low) { + const list = this.lists.get(listKey); + if (!list) { + return; + } + // l h + // 0,1,2,3,4 <- before + // 0,1,3,4,4 <- after, low is deleted and hi is duplicated + for (let i = low; i < hi; i++) { + if (list.isIndexInRange(i)) { + list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i + 1]; + } + } + } + removeEntry(listKey, index) { + const list = this.lists.get(listKey); + if (!list) { + return; + } + // work out the max index + let max = -1; + for (const n in list.roomIndexToRoomId) { + if (Number(n) > max) { + max = Number(n); + } + } + if (max < 0 || index > max) { + return; + } + // Everything higher than the gap needs to be shifted left. + this.shiftLeft(listKey, max, index); + delete list.roomIndexToRoomId[max]; + } + addEntry(listKey, index) { + const list = this.lists.get(listKey); + if (!list) { + return; + } + // work out the max index + let max = -1; + for (const n in list.roomIndexToRoomId) { + if (Number(n) > max) { + max = Number(n); + } + } + if (max < 0 || index > max) { + return; + } + // Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element + this.shiftRight(listKey, max + 1, index); + } + processListOps(list, listKey) { + let gapIndex = -1; + const listData = this.lists.get(listKey); + if (!listData) { + return; + } + list.ops.forEach((op) => { + if (!listData) { + return; + } + switch (op.op) { + case "DELETE": { + logger_1.logger.debug("DELETE", listKey, op.index, ";"); + delete listData.roomIndexToRoomId[op.index]; + if (gapIndex !== -1) { + // we already have a DELETE operation to process, so process it. + this.removeEntry(listKey, gapIndex); + } + gapIndex = op.index; + break; + } + case "INSERT": { + logger_1.logger.debug("INSERT", listKey, op.index, op.room_id, ";"); + if (listData.roomIndexToRoomId[op.index]) { + // something is in this space, shift items out of the way + if (gapIndex < 0) { + // we haven't been told where to shift from, so make way for a new room entry. + this.addEntry(listKey, op.index); + } + else if (gapIndex > op.index) { + // the gap is further down the list, shift every element to the right + // starting at the gap so we can just shift each element in turn: + // [A,B,C,_] gapIndex=3, op.index=0 + // [A,B,C,C] i=3 + // [A,B,B,C] i=2 + // [A,A,B,C] i=1 + // Terminate. We'll assign into op.index next. + this.shiftRight(listKey, gapIndex, op.index); + } + else if (gapIndex < op.index) { + // the gap is further up the list, shift every element to the left + // starting at the gap so we can just shift each element in turn + this.shiftLeft(listKey, op.index, gapIndex); + } + } + // forget the gap, we don't need it anymore. This is outside the check for + // a room being present in this index position because INSERTs always universally + // forget the gap, not conditionally based on the presence of a room in the INSERT + // position. Without this, DELETE 0; INSERT 0; would do the wrong thing. + gapIndex = -1; + listData.roomIndexToRoomId[op.index] = op.room_id; + break; + } + case "INVALIDATE": { + const startIndex = op.range[0]; + for (let i = startIndex; i <= op.range[1]; i++) { + delete listData.roomIndexToRoomId[i]; + } + logger_1.logger.debug("INVALIDATE", listKey, op.range[0], op.range[1], ";"); + break; + } + case "SYNC": { + const startIndex = op.range[0]; + for (let i = startIndex; i <= op.range[1]; i++) { + const roomId = op.room_ids[i - startIndex]; + if (!roomId) { + break; // we are at the end of list + } + listData.roomIndexToRoomId[i] = roomId; + } + logger_1.logger.debug("SYNC", listKey, op.range[0], op.range[1], (op.room_ids || []).join(" "), ";"); + break; + } + } + }); + if (gapIndex !== -1) { + // we already have a DELETE operation to process, so process it + // Everything higher than the gap needs to be shifted left. + this.removeEntry(listKey, gapIndex); + } + } + /** + * Resend a Sliding Sync request. Used when something has changed in the request. Resolves with + * the transaction ID of this request on success. Rejects with the transaction ID of this request + * on failure. + */ + resend() { + var _a; + if (this.needsResend && this.txnIdDefers.length > 0) { + // we already have a resend queued, so just return the same promise + return this.txnIdDefers[this.txnIdDefers.length - 1].promise; + } + this.needsResend = true; + this.txnId = this.client.makeTxnId(); + const d = (0, utils_1.defer)(); + this.txnIdDefers.push(Object.assign(Object.assign({}, d), { txnId: this.txnId })); + (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort(); + this.abortController = new AbortController(); + return d.promise; + } + resolveTransactionDefers(txnId) { + if (!txnId) { + return; + } + // find the matching index + let txnIndex = -1; + for (let i = 0; i < this.txnIdDefers.length; i++) { + if (this.txnIdDefers[i].txnId === txnId) { + txnIndex = i; + break; + } + } + if (txnIndex === -1) { + // this shouldn't happen; we shouldn't be seeing txn_ids for things we don't know about, + // whine about it. + logger_1.logger.warn(`resolveTransactionDefers: seen ${txnId} but it isn't a pending txn, ignoring.`); + return; + } + // This list is sorted in time, so if the input txnId ACKs in the middle of this array, + // then everything before it that hasn't been ACKed yet never will and we should reject them. + for (let i = 0; i < txnIndex; i++) { + this.txnIdDefers[i].reject(this.txnIdDefers[i].txnId); + } + this.txnIdDefers[txnIndex].resolve(txnId); + // clear out settled promises, including the one we resolved. + this.txnIdDefers = this.txnIdDefers.slice(txnIndex + 1); + } + /** + * Stop syncing with the server. + */ + stop() { + var _a; + this.terminated = true; + (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort(); + // remove all listeners so things can be GC'd + this.removeAllListeners(SlidingSyncEvent.Lifecycle); + this.removeAllListeners(SlidingSyncEvent.List); + this.removeAllListeners(SlidingSyncEvent.RoomData); + } + /** + * Re-setup this connection e.g in the event of an expired session. + */ + resetup() { + var _a; + logger_1.logger.warn("SlidingSync: resetting connection info"); + // any pending txn ID defers will be forgotten already by the server, so clear them out + this.txnIdDefers.forEach((d) => { + d.reject(d.txnId); + }); + this.txnIdDefers = []; + // resend sticky params and de-confirm all subscriptions + this.lists.forEach((l) => { + l.setModified(true); + }); + this.confirmedRoomSubscriptions = new Set(); // leave desired ones alone though! + // reset the connection as we might be wedged + this.needsResend = true; + (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort(); + this.abortController = new AbortController(); + } + /** + * Start syncing with the server. Blocks until stopped. + */ + start() { + return __awaiter(this, void 0, void 0, function* () { + this.abortController = new AbortController(); + let currentPos; + while (!this.terminated) { + this.needsResend = false; + let doNotUpdateList = false; + let resp; + try { + const listModifiedCount = this.listModifiedCount; + const reqLists = {}; + this.lists.forEach((l, key) => { + reqLists[key] = l.getList(false); + }); + const reqBody = { + lists: reqLists, + pos: currentPos, + timeout: this.timeoutMS, + clientTimeout: this.timeoutMS + BUFFER_PERIOD_MS, + extensions: this.getExtensionRequest(currentPos === undefined), + }; + // check if we are (un)subscribing to a room and modify request this one time for it + const newSubscriptions = difference(this.desiredRoomSubscriptions, this.confirmedRoomSubscriptions); + const unsubscriptions = difference(this.confirmedRoomSubscriptions, this.desiredRoomSubscriptions); + if (unsubscriptions.size > 0) { + reqBody.unsubscribe_rooms = Array.from(unsubscriptions); + } + if (newSubscriptions.size > 0) { + reqBody.room_subscriptions = {}; + for (const roomId of newSubscriptions) { + const customSubName = this.roomIdToCustomSubscription.get(roomId); + let sub = this.roomSubscriptionInfo; + if (customSubName && this.customSubscriptions.has(customSubName)) { + sub = this.customSubscriptions.get(customSubName); + } + reqBody.room_subscriptions[roomId] = sub; + } + } + if (this.txnId) { + reqBody.txn_id = this.txnId; + this.txnId = null; + } + this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl, this.abortController.signal); + resp = yield this.pendingReq; + currentPos = resp.pos; + // update what we think we're subscribed to. + for (const roomId of newSubscriptions) { + this.confirmedRoomSubscriptions.add(roomId); + } + for (const roomId of unsubscriptions) { + this.confirmedRoomSubscriptions.delete(roomId); + } + if (listModifiedCount !== this.listModifiedCount) { + // the lists have been modified whilst we were waiting for 'await' to return, but the abort() + // call did nothing. It is NOT SAFE to modify the list array now. We'll process the response but + // not update list pointers. + logger_1.logger.debug("list modified during await call, not updating list"); + doNotUpdateList = true; + } + // mark all these lists as having been sent as sticky so we don't keep sending sticky params + this.lists.forEach((l) => { + l.setModified(false); + }); + // set default empty values so we don't need to null check + resp.lists = resp.lists || {}; + resp.rooms = resp.rooms || {}; + resp.extensions = resp.extensions || {}; + Object.keys(resp.lists).forEach((key) => { + const list = this.lists.get(key); + if (!list || !resp) { + return; + } + list.joinedCount = resp.lists[key].count; + }); + this.invokeLifecycleListeners(SlidingSyncState.RequestFinished, resp); + } + catch (err) { + if (err.httpStatus) { + this.invokeLifecycleListeners(SlidingSyncState.RequestFinished, null, err); + if (err.httpStatus === 400) { + // session probably expired TODO: assign an errcode + // so drop state and re-request + this.resetup(); + currentPos = undefined; + yield (0, utils_1.sleep)(50); // in case the 400 was for something else; don't tightloop + continue; + } // else fallthrough to generic error handling + } + else if (this.needsResend || err.name === "AbortError") { + continue; // don't sleep as we caused this error by abort()ing the request. + } + logger_1.logger.error(err); + yield (0, utils_1.sleep)(5000); + } + if (!resp) { + continue; + } + this.onPreExtensionsResponse(resp.extensions); + Object.keys(resp.rooms).forEach((roomId) => { + this.invokeRoomDataListeners(roomId, resp.rooms[roomId]); + }); + const listKeysWithUpdates = new Set(); + if (!doNotUpdateList) { + for (const [key, list] of Object.entries(resp.lists)) { + list.ops = list.ops || []; + if (list.ops.length > 0) { + listKeysWithUpdates.add(key); + } + this.processListOps(list, key); + } + } + this.invokeLifecycleListeners(SlidingSyncState.Complete, resp); + this.onPostExtensionsResponse(resp.extensions); + listKeysWithUpdates.forEach((listKey) => { + const list = this.lists.get(listKey); + if (!list) { + return; + } + this.emit(SlidingSyncEvent.List, listKey, list.joinedCount, Object.assign({}, list.roomIndexToRoomId)); + }); + this.resolveTransactionDefers(resp.txn_id); + } + }); + } +} +exports.SlidingSync = SlidingSync; +const difference = (setA, setB) => { + const diff = new Set(setA); + for (const elem of setB) { + diff.delete(elem); + } + return diff; +}; + +},{"./logger":374,"./models/typed-event-emitter":395,"./utils":416}],408:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LocalIndexedDBStoreBackend = void 0; +const sync_accumulator_1 = require("../sync-accumulator"); +const utils = __importStar(require("../utils")); +const IndexedDBHelpers = __importStar(require("../indexeddb-helpers")); +const logger_1 = require("../logger"); +const DB_MIGRATIONS = [ + (db) => { + // Make user store, clobber based on user ID. (userId property of User objects) + db.createObjectStore("users", { keyPath: ["userId"] }); + // Make account data store, clobber based on event type. + // (event.type property of MatrixEvent objects) + db.createObjectStore("accountData", { keyPath: ["type"] }); + // Make /sync store (sync tokens, room data, etc), always clobber (const key). + db.createObjectStore("sync", { keyPath: ["clobber"] }); + }, + (db) => { + const oobMembersStore = db.createObjectStore("oob_membership_events", { + keyPath: ["room_id", "state_key"], + }); + oobMembersStore.createIndex("room", "room_id"); + }, + (db) => { + db.createObjectStore("client_options", { keyPath: ["clobber"] }); + }, + (db) => { + db.createObjectStore("to_device_queue", { autoIncrement: true }); + }, + // Expand as needed. +]; +const VERSION = DB_MIGRATIONS.length; +/** + * Helper method to collect results from a Cursor and promiseify it. + * @param store - The store to perform openCursor on. + * @param keyRange - Optional key range to apply on the cursor. + * @param resultMapper - A function which is repeatedly called with a + * Cursor. + * Return the data you want to keep. + * @returns Promise which resolves to an array of whatever you returned from + * resultMapper. + */ +function selectQuery(store, keyRange, resultMapper) { + const query = store.openCursor(keyRange); + return new Promise((resolve, reject) => { + const results = []; + query.onerror = () => { + reject(new Error("Query failed: " + query.error)); + }; + // collect results + query.onsuccess = () => { + const cursor = query.result; + if (!cursor) { + resolve(results); + return; // end of results + } + results.push(resultMapper(cursor)); + cursor.continue(); + }; + }); +} +function txnAsPromise(txn) { + return new Promise((resolve, reject) => { + txn.oncomplete = function (event) { + resolve(event); + }; + txn.onerror = function () { + reject(txn.error); + }; + }); +} +function reqAsEventPromise(req) { + return new Promise((resolve, reject) => { + req.onsuccess = function (event) { + resolve(event); + }; + req.onerror = function () { + reject(req.error); + }; + }); +} +function reqAsPromise(req) { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req); + req.onerror = (err) => reject(err); + }); +} +function reqAsCursorPromise(req) { + return reqAsEventPromise(req).then((event) => req.result); +} +class LocalIndexedDBStoreBackend { + static exists(indexedDB, dbName) { + dbName = "matrix-js-sdk:" + (dbName || "default"); + return IndexedDBHelpers.exists(indexedDB, dbName); + } + /** + * Does the actual reading from and writing to the indexeddb + * + * Construct a new Indexed Database store backend. This requires a call to + * `connect()` before this store can be used. + * @param indexedDB - The Indexed DB interface e.g + * `window.indexedDB` + * @param dbName - Optional database name. The same name must be used + * to open the same database. + */ + constructor(indexedDB, dbName = "default") { + this.indexedDB = indexedDB; + this.disconnected = true; + this._isNewlyCreated = false; + this.pendingUserPresenceData = []; + this.dbName = "matrix-js-sdk:" + dbName; + this.syncAccumulator = new sync_accumulator_1.SyncAccumulator(); + } + /** + * Attempt to connect to the database. This can fail if the user does not + * grant permission. + * @returns Promise which resolves if successfully connected. + */ + connect(onClose) { + if (!this.disconnected) { + logger_1.logger.log(`LocalIndexedDBStoreBackend.connect: already connected or connecting`); + return Promise.resolve(); + } + this.disconnected = false; + logger_1.logger.log(`LocalIndexedDBStoreBackend.connect: connecting...`); + const req = this.indexedDB.open(this.dbName, VERSION); + req.onupgradeneeded = (ev) => { + const db = req.result; + const oldVersion = ev.oldVersion; + logger_1.logger.log(`LocalIndexedDBStoreBackend.connect: upgrading from ${oldVersion}`); + if (oldVersion < 1) { + // The database did not previously exist + this._isNewlyCreated = true; + } + DB_MIGRATIONS.forEach((migration, index) => { + if (oldVersion <= index) + migration(db); + }); + }; + req.onblocked = () => { + logger_1.logger.log(`can't yet open LocalIndexedDBStoreBackend because it is open elsewhere`); + }; + logger_1.logger.log(`LocalIndexedDBStoreBackend.connect: awaiting connection...`); + return reqAsEventPromise(req).then(() => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`LocalIndexedDBStoreBackend.connect: connected`); + this.db = req.result; + // add a poorly-named listener for when deleteDatabase is called + // so we can close our db connections. + this.db.onversionchange = () => { + var _a; + (_a = this.db) === null || _a === void 0 ? void 0 : _a.close(); // this does not call onclose + this.disconnected = true; + this.db = undefined; + onClose === null || onClose === void 0 ? void 0 : onClose(); + }; + this.db.onclose = () => { + this.disconnected = true; + this.db = undefined; + onClose === null || onClose === void 0 ? void 0 : onClose(); + }; + yield this.init(); + })); + } + /** @returns whether or not the database was newly created in this session. */ + isNewlyCreated() { + return Promise.resolve(this._isNewlyCreated); + } + /** + * Having connected, load initial data from the database and prepare for use + * @returns Promise which resolves on success + */ + init() { + return Promise.all([this.loadAccountData(), this.loadSyncData()]).then(([accountData, syncData]) => { + logger_1.logger.log(`LocalIndexedDBStoreBackend: loaded initial data`); + this.syncAccumulator.accumulate({ + next_batch: syncData.nextBatch, + rooms: syncData.roomsData, + account_data: { + events: accountData, + }, + }, true); + }); + } + /** + * Returns the out-of-band membership events for this room that + * were previously loaded. + * @returns the events, potentially an empty array if OOB loading didn't yield any new members + * @returns in case the members for this room haven't been stored yet + */ + getOutOfBandMembers(roomId) { + return new Promise((resolve, reject) => { + const tx = this.db.transaction(["oob_membership_events"], "readonly"); + const store = tx.objectStore("oob_membership_events"); + const roomIndex = store.index("room"); + const range = IDBKeyRange.only(roomId); + const request = roomIndex.openCursor(range); + const membershipEvents = []; + // did we encounter the oob_written marker object + // amongst the results? That means OOB member + // loading already happened for this room + // but there were no members to persist as they + // were all known already + let oobWritten = false; + request.onsuccess = () => { + const cursor = request.result; + if (!cursor) { + // Unknown room + if (!membershipEvents.length && !oobWritten) { + return resolve(null); + } + return resolve(membershipEvents); + } + const record = cursor.value; + if (record.oob_written) { + oobWritten = true; + } + else { + membershipEvents.push(record); + } + cursor.continue(); + }; + request.onerror = (err) => { + reject(err); + }; + }).then((events) => { + logger_1.logger.log(`LL: got ${events === null || events === void 0 ? void 0 : events.length} membershipEvents from storage for room ${roomId} ...`); + return events; + }); + } + /** + * Stores the out-of-band membership events for this room. Note that + * it still makes sense to store an empty array as the OOB status for the room is + * marked as fetched, and getOutOfBandMembers will return an empty array instead of null + * @param membershipEvents - the membership events to store + */ + setOutOfBandMembers(roomId, membershipEvents) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`LL: backend about to store ${membershipEvents.length}` + ` members for ${roomId}`); + const tx = this.db.transaction(["oob_membership_events"], "readwrite"); + const store = tx.objectStore("oob_membership_events"); + membershipEvents.forEach((e) => { + store.put(e); + }); + // aside from all the events, we also write a marker object to the store + // to mark the fact that OOB members have been written for this room. + // It's possible that 0 members need to be written as all where previously know + // but we still need to know whether to return null or [] from getOutOfBandMembers + // where null means out of band members haven't been stored yet for this room + const markerObject = { + room_id: roomId, + oob_written: true, + state_key: 0, + }; + store.put(markerObject); + yield txnAsPromise(tx); + logger_1.logger.log(`LL: backend done storing for ${roomId}!`); + }); + } + clearOutOfBandMembers(roomId) { + return __awaiter(this, void 0, void 0, function* () { + // the approach to delete all members for a room + // is to get the min and max state key from the index + // for that room, and then delete between those + // keys in the store. + // this should be way faster than deleting every member + // individually for a large room. + const readTx = this.db.transaction(["oob_membership_events"], "readonly"); + const store = readTx.objectStore("oob_membership_events"); + const roomIndex = store.index("room"); + const roomRange = IDBKeyRange.only(roomId); + const minStateKeyProm = reqAsCursorPromise(roomIndex.openKeyCursor(roomRange, "next")).then((cursor) => (cursor === null || cursor === void 0 ? void 0 : cursor.primaryKey)[1]); + const maxStateKeyProm = reqAsCursorPromise(roomIndex.openKeyCursor(roomRange, "prev")).then((cursor) => (cursor === null || cursor === void 0 ? void 0 : cursor.primaryKey)[1]); + const [minStateKey, maxStateKey] = yield Promise.all([minStateKeyProm, maxStateKeyProm]); + const writeTx = this.db.transaction(["oob_membership_events"], "readwrite"); + const writeStore = writeTx.objectStore("oob_membership_events"); + const membersKeyRange = IDBKeyRange.bound([roomId, minStateKey], [roomId, maxStateKey]); + logger_1.logger.log(`LL: Deleting all users + marker in storage for room ${roomId}, with key range:`, [roomId, minStateKey], [roomId, maxStateKey]); + yield reqAsPromise(writeStore.delete(membersKeyRange)); + }); + } + /** + * Clear the entire database. This should be used when logging out of a client + * to prevent mixing data between accounts. + * @returns Resolved when the database is cleared. + */ + clearDatabase() { + return new Promise((resolve) => { + logger_1.logger.log(`Removing indexeddb instance: ${this.dbName}`); + const req = this.indexedDB.deleteDatabase(this.dbName); + req.onblocked = () => { + logger_1.logger.log(`can't yet delete indexeddb ${this.dbName} because it is open elsewhere`); + }; + req.onerror = () => { + // in firefox, with indexedDB disabled, this fails with a + // DOMError. We treat this as non-fatal, so that we can still + // use the app. + logger_1.logger.warn(`unable to delete js-sdk store indexeddb: ${req.error}`); + resolve(); + }; + req.onsuccess = () => { + logger_1.logger.log(`Removed indexeddb instance: ${this.dbName}`); + resolve(); + }; + }); + } + /** + * @param copy - If false, the data returned is from internal + * buffers and must not be mutated. Otherwise, a copy is made before + * returning such that the data can be safely mutated. Default: true. + * + * @returns Promise which resolves with a sync response to restore the + * client state to where it was at the last save, or null if there + * is no saved sync data. + */ + getSavedSync(copy = true) { + const data = this.syncAccumulator.getJSON(); + if (!data.nextBatch) + return Promise.resolve(null); + if (copy) { + // We must deep copy the stored data so that the /sync processing code doesn't + // corrupt the internal state of the sync accumulator (it adds non-clonable keys) + return Promise.resolve(utils.deepCopy(data)); + } + else { + return Promise.resolve(data); + } + } + getNextBatchToken() { + return Promise.resolve(this.syncAccumulator.getNextBatchToken()); + } + setSyncData(syncData) { + return Promise.resolve().then(() => { + this.syncAccumulator.accumulate(syncData); + }); + } + /** + * Sync users and all accumulated sync data to the database. + * If a previous sync is in flight, the new data will be added to the + * next sync and the current sync's promise will be returned. + * @param userTuples - The user tuples + * @returns Promise which resolves if the data was persisted. + */ + syncToDatabase(userTuples) { + return __awaiter(this, void 0, void 0, function* () { + if (this.syncToDatabasePromise) { + logger_1.logger.warn("Skipping syncToDatabase() as persist already in flight"); + this.pendingUserPresenceData.push(...userTuples); + return this.syncToDatabasePromise; + } + userTuples.unshift(...this.pendingUserPresenceData); + this.syncToDatabasePromise = this.doSyncToDatabase(userTuples); + return this.syncToDatabasePromise; + }); + } + doSyncToDatabase(userTuples) { + return __awaiter(this, void 0, void 0, function* () { + try { + const syncData = this.syncAccumulator.getJSON(true); + yield Promise.all([ + this.persistUserPresenceEvents(userTuples), + this.persistAccountData(syncData.accountData), + this.persistSyncData(syncData.nextBatch, syncData.roomsData), + ]); + } + finally { + this.syncToDatabasePromise = undefined; + } + }); + } + /** + * Persist rooms /sync data along with the next batch token. + * @param nextBatch - The next_batch /sync value. + * @param roomsData - The 'rooms' /sync data from a SyncAccumulator + * @returns Promise which resolves if the data was persisted. + */ + persistSyncData(nextBatch, roomsData) { + logger_1.logger.log("Persisting sync data up to", nextBatch); + return utils.promiseTry(() => { + const txn = this.db.transaction(["sync"], "readwrite"); + const store = txn.objectStore("sync"); + store.put({ + clobber: "-", + nextBatch, + roomsData, + }); // put == UPSERT + return txnAsPromise(txn).then(() => { + logger_1.logger.log("Persisted sync data up to", nextBatch); + }); + }); + } + /** + * Persist a list of account data events. Events with the same 'type' will + * be replaced. + * @param accountData - An array of raw user-scoped account data events + * @returns Promise which resolves if the events were persisted. + */ + persistAccountData(accountData) { + return utils.promiseTry(() => { + const txn = this.db.transaction(["accountData"], "readwrite"); + const store = txn.objectStore("accountData"); + for (const event of accountData) { + store.put(event); // put == UPSERT + } + return txnAsPromise(txn).then(); + }); + } + /** + * Persist a list of [user id, presence event] they are for. + * Users with the same 'userId' will be replaced. + * Presence events should be the event in its raw form (not the Event + * object) + * @param tuples - An array of [userid, event] tuples + * @returns Promise which resolves if the users were persisted. + */ + persistUserPresenceEvents(tuples) { + return utils.promiseTry(() => { + const txn = this.db.transaction(["users"], "readwrite"); + const store = txn.objectStore("users"); + for (const tuple of tuples) { + store.put({ + userId: tuple[0], + event: tuple[1], + }); // put == UPSERT + } + return txnAsPromise(txn).then(); + }); + } + /** + * Load all user presence events from the database. This is not cached. + * FIXME: It would probably be more sensible to store the events in the + * sync. + * @returns A list of presence events in their raw form. + */ + getUserPresenceEvents() { + return utils.promiseTry(() => { + const txn = this.db.transaction(["users"], "readonly"); + const store = txn.objectStore("users"); + return selectQuery(store, undefined, (cursor) => { + return [cursor.value.userId, cursor.value.event]; + }); + }); + } + /** + * Load all the account data events from the database. This is not cached. + * @returns A list of raw global account events. + */ + loadAccountData() { + logger_1.logger.log(`LocalIndexedDBStoreBackend: loading account data...`); + return utils.promiseTry(() => { + const txn = this.db.transaction(["accountData"], "readonly"); + const store = txn.objectStore("accountData"); + return selectQuery(store, undefined, (cursor) => { + return cursor.value; + }).then((result) => { + logger_1.logger.log(`LocalIndexedDBStoreBackend: loaded account data`); + return result; + }); + }); + } + /** + * Load the sync data from the database. + * @returns An object with "roomsData" and "nextBatch" keys. + */ + loadSyncData() { + logger_1.logger.log(`LocalIndexedDBStoreBackend: loading sync data...`); + return utils.promiseTry(() => { + const txn = this.db.transaction(["sync"], "readonly"); + const store = txn.objectStore("sync"); + return selectQuery(store, undefined, (cursor) => { + return cursor.value; + }).then((results) => { + logger_1.logger.log(`LocalIndexedDBStoreBackend: loaded sync data`); + if (results.length > 1) { + logger_1.logger.warn("loadSyncData: More than 1 sync row found."); + } + return results.length > 0 ? results[0] : {}; + }); + }); + } + getClientOptions() { + return Promise.resolve().then(() => { + const txn = this.db.transaction(["client_options"], "readonly"); + const store = txn.objectStore("client_options"); + return selectQuery(store, undefined, (cursor) => { + var _a; + return (_a = cursor.value) === null || _a === void 0 ? void 0 : _a.options; + }).then((results) => results[0]); + }); + } + storeClientOptions(options) { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction(["client_options"], "readwrite"); + const store = txn.objectStore("client_options"); + store.put({ + clobber: "-", + options: options, + }); // put == UPSERT + yield txnAsPromise(txn); + }); + } + saveToDeviceBatches(batches) { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction(["to_device_queue"], "readwrite"); + const store = txn.objectStore("to_device_queue"); + for (const batch of batches) { + store.add(batch); + } + yield txnAsPromise(txn); + }); + } + getOldestToDeviceBatch() { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction(["to_device_queue"], "readonly"); + const store = txn.objectStore("to_device_queue"); + const cursor = yield reqAsCursorPromise(store.openCursor()); + if (!cursor) + return null; + const resultBatch = cursor.value; + return { + id: cursor.key, + txnId: resultBatch.txnId, + eventType: resultBatch.eventType, + batch: resultBatch.batch, + }; + }); + } + removeToDeviceBatch(id) { + return __awaiter(this, void 0, void 0, function* () { + const txn = this.db.transaction(["to_device_queue"], "readwrite"); + const store = txn.objectStore("to_device_queue"); + store.delete(id); + yield txnAsPromise(txn); + }); + } +} +exports.LocalIndexedDBStoreBackend = LocalIndexedDBStoreBackend; + +},{"../indexeddb-helpers":372,"../logger":374,"../sync-accumulator":413,"../utils":416}],409:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RemoteIndexedDBStoreBackend = void 0; +const logger_1 = require("../logger"); +const utils_1 = require("../utils"); +class RemoteIndexedDBStoreBackend { + /** + * An IndexedDB store backend where the actual backend sits in a web + * worker. + * + * Construct a new Indexed Database store backend. This requires a call to + * `connect()` before this store can be used. + * @param workerFactory - Factory which produces a Worker + * @param dbName - Optional database name. The same name must be used + * to open the same database. + */ + constructor(workerFactory, dbName) { + this.workerFactory = workerFactory; + this.dbName = dbName; + this.nextSeq = 0; + // The currently in-flight requests to the actual backend + this.inFlight = {}; // seq: promise + this.onWorkerMessage = (ev) => { + var _a; + const msg = ev.data; + if (msg.command == "closed") { + (_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this); + } + else if (msg.command == "cmd_success" || msg.command == "cmd_fail") { + if (msg.seq === undefined) { + logger_1.logger.error("Got reply from worker with no seq"); + return; + } + const def = this.inFlight[msg.seq]; + if (def === undefined) { + logger_1.logger.error("Got reply for unknown seq " + msg.seq); + return; + } + delete this.inFlight[msg.seq]; + if (msg.command == "cmd_success") { + def.resolve(msg.result); + } + else { + const error = new Error(msg.error.message); + error.name = msg.error.name; + def.reject(error); + } + } + else { + logger_1.logger.warn("Unrecognised message from worker: ", msg); + } + }; + } + /** + * Attempt to connect to the database. This can fail if the user does not + * grant permission. + * @returns Promise which resolves if successfully connected. + */ + connect(onClose) { + this.onClose = onClose; + return this.ensureStarted().then(() => this.doCmd("connect")); + } + /** + * Clear the entire database. This should be used when logging out of a client + * to prevent mixing data between accounts. + * @returns Resolved when the database is cleared. + */ + clearDatabase() { + return this.ensureStarted().then(() => this.doCmd("clearDatabase")); + } + /** @returns whether or not the database was newly created in this session. */ + isNewlyCreated() { + return this.doCmd("isNewlyCreated"); + } + /** + * @returns Promise which resolves with a sync response to restore the + * client state to where it was at the last save, or null if there + * is no saved sync data. + */ + getSavedSync() { + return this.doCmd("getSavedSync"); + } + getNextBatchToken() { + return this.doCmd("getNextBatchToken"); + } + setSyncData(syncData) { + return this.doCmd("setSyncData", [syncData]); + } + syncToDatabase(userTuples) { + return this.doCmd("syncToDatabase", [userTuples]); + } + /** + * Returns the out-of-band membership events for this room that + * were previously loaded. + * @returns the events, potentially an empty array if OOB loading didn't yield any new members + * @returns in case the members for this room haven't been stored yet + */ + getOutOfBandMembers(roomId) { + return this.doCmd("getOutOfBandMembers", [roomId]); + } + /** + * Stores the out-of-band membership events for this room. Note that + * it still makes sense to store an empty array as the OOB status for the room is + * marked as fetched, and getOutOfBandMembers will return an empty array instead of null + * @param membershipEvents - the membership events to store + * @returns when all members have been stored + */ + setOutOfBandMembers(roomId, membershipEvents) { + return this.doCmd("setOutOfBandMembers", [roomId, membershipEvents]); + } + clearOutOfBandMembers(roomId) { + return this.doCmd("clearOutOfBandMembers", [roomId]); + } + getClientOptions() { + return this.doCmd("getClientOptions"); + } + storeClientOptions(options) { + return this.doCmd("storeClientOptions", [options]); + } + /** + * Load all user presence events from the database. This is not cached. + * @returns A list of presence events in their raw form. + */ + getUserPresenceEvents() { + return this.doCmd("getUserPresenceEvents"); + } + saveToDeviceBatches(batches) { + return __awaiter(this, void 0, void 0, function* () { + return this.doCmd("saveToDeviceBatches", [batches]); + }); + } + getOldestToDeviceBatch() { + return __awaiter(this, void 0, void 0, function* () { + return this.doCmd("getOldestToDeviceBatch"); + }); + } + removeToDeviceBatch(id) { + return __awaiter(this, void 0, void 0, function* () { + return this.doCmd("removeToDeviceBatch", [id]); + }); + } + ensureStarted() { + if (!this.startPromise) { + this.worker = this.workerFactory(); + this.worker.onmessage = this.onWorkerMessage; + // tell the worker the db name. + this.startPromise = this.doCmd("setupWorker", [this.dbName]).then(() => { + logger_1.logger.log("IndexedDB worker is ready"); + }); + } + return this.startPromise; + } + doCmd(command, args) { + // wrap in a q so if the postMessage throws, + // the promise automatically gets rejected + return Promise.resolve().then(() => { + var _a; + const seq = this.nextSeq++; + const def = (0, utils_1.defer)(); + this.inFlight[seq] = def; + (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage({ command, seq, args }); + return def.promise; + }); + } +} +exports.RemoteIndexedDBStoreBackend = RemoteIndexedDBStoreBackend; + +},{"../logger":374,"../utils":416}],410:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 Vector Creations 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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IndexedDBStore = void 0; +/* eslint-disable @babel/no-invalid-this */ +const memory_1 = require("./memory"); +const indexeddb_local_backend_1 = require("./indexeddb-local-backend"); +const indexeddb_remote_backend_1 = require("./indexeddb-remote-backend"); +const user_1 = require("../models/user"); +const event_1 = require("../models/event"); +const logger_1 = require("../logger"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +/** + * This is an internal module. See {@link IndexedDBStore} for the public class. + */ +// If this value is too small we'll be writing very often which will cause +// noticeable stop-the-world pauses. If this value is too big we'll be writing +// so infrequently that the /sync size gets bigger on reload. Writing more +// often does not affect the length of the pause since the entire /sync +// response is persisted each time. +const WRITE_DELAY_MS = 1000 * 60 * 5; // once every 5 minutes +class IndexedDBStore extends memory_1.MemoryStore { + static exists(indexedDB, dbName) { + return indexeddb_local_backend_1.LocalIndexedDBStoreBackend.exists(indexedDB, dbName); + } + /** + * Construct a new Indexed Database store, which extends MemoryStore. + * + * This store functions like a MemoryStore except it periodically persists + * the contents of the store to an IndexedDB backend. + * + * All data is still kept in-memory but can be loaded from disk by calling + * `startup()`. This can make startup times quicker as a complete + * sync from the server is not required. This does not reduce memory usage as all + * the data is eagerly fetched when `startup()` is called. + * ``` + * let opts = { indexedDB: window.indexedDB, localStorage: window.localStorage }; + * let store = new IndexedDBStore(opts); + * await store.startup(); // load from indexed db + * let client = sdk.createClient({ + * store: store, + * }); + * client.startClient(); + * client.on("sync", function(state, prevState, data) { + * if (state === "PREPARED") { + * console.log("Started up, now with go faster stripes!"); + * } + * }); + * ``` + * + * @param opts - Options object. + */ + constructor(opts) { + super(opts); + this.startedUp = false; + this.syncTs = 0; + // Records the last-modified-time of each user at the last point we saved + // the database, such that we can derive the set if users that have been + // modified since we last saved. + this.userModifiedMap = {}; // user_id : timestamp + this.emitter = new typed_event_emitter_1.TypedEventEmitter(); + this.on = this.emitter.on.bind(this.emitter); + this.onClose = () => { + this.emitter.emit("closed"); + }; + /** + * @returns Promise which resolves with a sync response to restore the + * client state to where it was at the last save, or null if there + * is no saved sync data. + */ + this.getSavedSync = this.degradable(() => { + return this.backend.getSavedSync(); + }, "getSavedSync"); + /** @returns whether or not the database was newly created in this session. */ + this.isNewlyCreated = this.degradable(() => { + return this.backend.isNewlyCreated(); + }, "isNewlyCreated"); + /** + * @returns If there is a saved sync, the nextBatch token + * for this sync, otherwise null. + */ + this.getSavedSyncToken = this.degradable(() => { + return this.backend.getNextBatchToken(); + }, "getSavedSyncToken"); + /** + * Delete all data from this store. + * @returns Promise which resolves if the data was deleted from the database. + */ + this.deleteAllData = this.degradable(() => { + super.deleteAllData(); + return this.backend.clearDatabase().then(() => { + logger_1.logger.log("Deleted indexeddb data."); + }, (err) => { + logger_1.logger.error(`Failed to delete indexeddb data: ${err}`); + throw err; + }); + }); + this.reallySave = this.degradable(() => { + this.syncTs = Date.now(); // set now to guard against multi-writes + // work out changed users (this doesn't handle deletions but you + // can't 'delete' users as they are just presence events). + const userTuples = []; + for (const u of this.getUsers()) { + if (this.userModifiedMap[u.userId] === u.getLastModifiedTime()) + continue; + if (!u.events.presence) + continue; + userTuples.push([u.userId, u.events.presence.event]); + // note that we've saved this version of the user + this.userModifiedMap[u.userId] = u.getLastModifiedTime(); + } + return this.backend.syncToDatabase(userTuples); + }); + this.setSyncData = this.degradable((syncData) => { + return this.backend.setSyncData(syncData); + }, "setSyncData"); + /** + * Returns the out-of-band membership events for this room that + * were previously loaded. + * @returns the events, potentially an empty array if OOB loading didn't yield any new members + * @returns in case the members for this room haven't been stored yet + */ + this.getOutOfBandMembers = this.degradable((roomId) => { + return this.backend.getOutOfBandMembers(roomId); + }, "getOutOfBandMembers"); + /** + * Stores the out-of-band membership events for this room. Note that + * it still makes sense to store an empty array as the OOB status for the room is + * marked as fetched, and getOutOfBandMembers will return an empty array instead of null + * @param membershipEvents - the membership events to store + * @returns when all members have been stored + */ + this.setOutOfBandMembers = this.degradable((roomId, membershipEvents) => { + super.setOutOfBandMembers(roomId, membershipEvents); + return this.backend.setOutOfBandMembers(roomId, membershipEvents); + }, "setOutOfBandMembers"); + this.clearOutOfBandMembers = this.degradable((roomId) => { + super.clearOutOfBandMembers(roomId); + return this.backend.clearOutOfBandMembers(roomId); + }, "clearOutOfBandMembers"); + this.getClientOptions = this.degradable(() => { + return this.backend.getClientOptions(); + }, "getClientOptions"); + this.storeClientOptions = this.degradable((options) => { + super.storeClientOptions(options); + return this.backend.storeClientOptions(options); + }, "storeClientOptions"); + if (!opts.indexedDB) { + throw new Error("Missing required option: indexedDB"); + } + if (opts.workerFactory) { + this.backend = new indexeddb_remote_backend_1.RemoteIndexedDBStoreBackend(opts.workerFactory, opts.dbName); + } + else { + this.backend = new indexeddb_local_backend_1.LocalIndexedDBStoreBackend(opts.indexedDB, opts.dbName); + } + } + /** + * @returns Resolved when loaded from indexed db. + */ + startup() { + if (this.startedUp) { + logger_1.logger.log(`IndexedDBStore.startup: already started`); + return Promise.resolve(); + } + logger_1.logger.log(`IndexedDBStore.startup: connecting to backend`); + return this.backend + .connect(this.onClose) + .then(() => { + logger_1.logger.log(`IndexedDBStore.startup: loading presence events`); + return this.backend.getUserPresenceEvents(); + }) + .then((userPresenceEvents) => { + logger_1.logger.log(`IndexedDBStore.startup: processing presence events`); + userPresenceEvents.forEach(([userId, rawEvent]) => { + const u = new user_1.User(userId); + if (rawEvent) { + u.setPresenceEvent(new event_1.MatrixEvent(rawEvent)); + } + this.userModifiedMap[u.userId] = u.getLastModifiedTime(); + this.storeUser(u); + }); + this.startedUp = true; + }); + } + /** + * Whether this store would like to save its data + * Note that obviously whether the store wants to save or + * not could change between calling this function and calling + * save(). + * + * @returns True if calling save() will actually save + * (at the time this function is called). + */ + wantsSave() { + const now = Date.now(); + return now - this.syncTs > WRITE_DELAY_MS; + } + /** + * Possibly write data to the database. + * + * @param force - True to force a save to happen + * @returns Promise resolves after the write completes + * (or immediately if no write is performed) + */ + save(force = false) { + if (force || this.wantsSave()) { + return this.reallySave(); + } + return Promise.resolve(); + } + /** + * All member functions of `IndexedDBStore` that access the backend use this wrapper to + * watch for failures after initial store startup, including `QuotaExceededError` as + * free disk space changes, etc. + * + * When IndexedDB fails via any of these paths, we degrade this back to a `MemoryStore` + * in place so that the current operation and all future ones are in-memory only. + * + * @param func - The degradable work to do. + * @param fallback - The method name for fallback. + * @returns A wrapped member function. + */ + degradable(func, fallback) { + const fallbackFn = fallback ? super[fallback] : null; + return (...args) => __awaiter(this, void 0, void 0, function* () { + try { + return yield func.call(this, ...args); + } + catch (e) { + logger_1.logger.error("IndexedDBStore failure, degrading to MemoryStore", e); + this.emitter.emit("degraded", e); + try { + // We try to delete IndexedDB after degrading since this store is only a + // cache (the app will still function correctly without the data). + // It's possible that deleting repair IndexedDB for the next app load, + // potentially by making a little more space available. + logger_1.logger.log("IndexedDBStore trying to delete degraded data"); + yield this.backend.clearDatabase(); + logger_1.logger.log("IndexedDBStore delete after degrading succeeded"); + } + catch (e) { + logger_1.logger.warn("IndexedDBStore delete after degrading failed", e); + } + // Degrade the store from being an instance of `IndexedDBStore` to instead be + // an instance of `MemoryStore` so that future API calls use the memory path + // directly and skip IndexedDB entirely. This should be safe as + // `IndexedDBStore` already extends from `MemoryStore`, so we are making the + // store become its parent type in a way. The mutator methods of + // `IndexedDBStore` also maintain the state that `MemoryStore` uses (many are + // not overridden at all). + if (fallbackFn) { + return fallbackFn.call(this, ...args); + } + } + }); + } + // XXX: ideally these would be stored in indexeddb as part of the room but, + // we don't store rooms as such and instead accumulate entire sync responses atm. + getPendingEvents(roomId) { + const _super = Object.create(null, { + getPendingEvents: { get: () => super.getPendingEvents } + }); + return __awaiter(this, void 0, void 0, function* () { + if (!this.localStorage) + return _super.getPendingEvents.call(this, roomId); + const serialized = this.localStorage.getItem(pendingEventsKey(roomId)); + if (serialized) { + try { + return JSON.parse(serialized); + } + catch (e) { + logger_1.logger.error("Could not parse persisted pending events", e); + } + } + return []; + }); + } + setPendingEvents(roomId, events) { + const _super = Object.create(null, { + setPendingEvents: { get: () => super.setPendingEvents } + }); + return __awaiter(this, void 0, void 0, function* () { + if (!this.localStorage) + return _super.setPendingEvents.call(this, roomId, events); + if (events.length > 0) { + this.localStorage.setItem(pendingEventsKey(roomId), JSON.stringify(events)); + } + else { + this.localStorage.removeItem(pendingEventsKey(roomId)); + } + }); + } + saveToDeviceBatches(batches) { + return this.backend.saveToDeviceBatches(batches); + } + getOldestToDeviceBatch() { + return this.backend.getOldestToDeviceBatch(); + } + removeToDeviceBatch(id) { + return this.backend.removeToDeviceBatch(id); + } +} +exports.IndexedDBStore = IndexedDBStore; +/** + * @param roomId - ID of the current room + * @returns Storage key to retrieve pending events + */ +function pendingEventsKey(roomId) { + return `mx_pending_events_${roomId}`; +} + +},{"../logger":374,"../models/event":383,"../models/typed-event-emitter":395,"../models/user":396,"./indexeddb-local-backend":408,"./indexeddb-remote-backend":409,"./memory":411}],411:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MemoryStore = void 0; +const user_1 = require("../models/user"); +const room_state_1 = require("../models/room-state"); +const utils_1 = require("../utils"); +function isValidFilterId(filterId) { + const isValidStr = typeof filterId === "string" && + !!filterId && + filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before + filterId !== "null"; + return isValidStr || typeof filterId === "number"; +} +class MemoryStore { + /** + * Construct a new in-memory data store for the Matrix Client. + * @param opts - Config options + */ + constructor(opts = {}) { + this.rooms = {}; // roomId: Room + this.users = {}; // userId: User + this.syncToken = null; + // userId: { + // filterId: Filter + // } + this.filters = new utils_1.MapWithDefault(() => new Map()); + this.accountData = new Map(); // type: content + this.oobMembers = new Map(); // roomId: [member events] + this.pendingEvents = {}; + this.pendingToDeviceBatches = []; + this.nextToDeviceBatchId = 0; + /** + * Called when a room member in a room being tracked by this store has been + * updated. + */ + this.onRoomMember = (event, state, member) => { + if (member.membership === "invite") { + // We do NOT add invited members because people love to typo user IDs + // which would then show up in these lists (!) + return; + } + const user = this.users[member.userId] || new user_1.User(member.userId); + if (member.name) { + user.setDisplayName(member.name); + if (member.events.member) { + user.setRawDisplayName(member.events.member.getDirectionalContent().displayname); + } + } + if (member.events.member && member.events.member.getContent().avatar_url) { + user.setAvatarUrl(member.events.member.getContent().avatar_url); + } + this.users[user.userId] = user; + }; + this.localStorage = opts.localStorage; + } + /** + * Retrieve the token to stream from. + * @returns The token or null. + */ + getSyncToken() { + return this.syncToken; + } + /** @returns whether or not the database was newly created in this session. */ + isNewlyCreated() { + return Promise.resolve(true); + } + /** + * Set the token to stream from. + * @param token - The token to stream from. + */ + setSyncToken(token) { + this.syncToken = token; + } + /** + * Store the given room. + * @param room - The room to be stored. All properties must be stored. + */ + storeRoom(room) { + this.rooms[room.roomId] = room; + // add listeners for room member changes so we can keep the room member + // map up-to-date. + room.currentState.on(room_state_1.RoomStateEvent.Members, this.onRoomMember); + // add existing members + room.currentState.getMembers().forEach((m) => { + this.onRoomMember(null, room.currentState, m); + }); + } + /** + * Retrieve a room by its' room ID. + * @param roomId - The room ID. + * @returns The room or null. + */ + getRoom(roomId) { + return this.rooms[roomId] || null; + } + /** + * Retrieve all known rooms. + * @returns A list of rooms, which may be empty. + */ + getRooms() { + return Object.values(this.rooms); + } + /** + * Permanently delete a room. + */ + removeRoom(roomId) { + if (this.rooms[roomId]) { + this.rooms[roomId].currentState.removeListener(room_state_1.RoomStateEvent.Members, this.onRoomMember); + } + delete this.rooms[roomId]; + } + /** + * Retrieve a summary of all the rooms. + * @returns A summary of each room. + */ + getRoomSummaries() { + return Object.values(this.rooms).map(function (room) { + return room.summary; + }); + } + /** + * Store a User. + * @param user - The user to store. + */ + storeUser(user) { + this.users[user.userId] = user; + } + /** + * Retrieve a User by its' user ID. + * @param userId - The user ID. + * @returns The user or null. + */ + getUser(userId) { + return this.users[userId] || null; + } + /** + * Retrieve all known users. + * @returns A list of users, which may be empty. + */ + getUsers() { + return Object.values(this.users); + } + /** + * Retrieve scrollback for this room. + * @param room - The matrix room + * @param limit - The max number of old events to retrieve. + * @returns An array of objects which will be at most 'limit' + * length and at least 0. The objects are the raw event JSON. + */ + scrollback(room, limit) { + return []; + } + /** + * Store events for a room. The events have already been added to the timeline + * @param room - The room to store events for. + * @param events - The events to store. + * @param token - The token associated with these events. + * @param toStart - True if these are paginated results. + */ + storeEvents(room, events, token, toStart) { + // no-op because they've already been added to the room instance. + } + /** + * Store a filter. + */ + storeFilter(filter) { + if (!(filter === null || filter === void 0 ? void 0 : filter.userId) || !(filter === null || filter === void 0 ? void 0 : filter.filterId)) + return; + this.filters.getOrCreate(filter.userId).set(filter.filterId, filter); + } + /** + * Retrieve a filter. + * @returns A filter or null. + */ + getFilter(userId, filterId) { + var _a; + return ((_a = this.filters.get(userId)) === null || _a === void 0 ? void 0 : _a.get(filterId)) || null; + } + /** + * Retrieve a filter ID with the given name. + * @param filterName - The filter name. + * @returns The filter ID or null. + */ + getFilterIdByName(filterName) { + if (!this.localStorage) { + return null; + } + const key = "mxjssdk_memory_filter_" + filterName; + // XXX Storage.getItem doesn't throw ... + // or are we using something different + // than window.localStorage in some cases + // that does throw? + // that would be very naughty + try { + const value = this.localStorage.getItem(key); + if (isValidFilterId(value)) { + return value; + } + } + catch (e) { } + return null; + } + /** + * Set a filter name to ID mapping. + */ + setFilterIdByName(filterName, filterId) { + if (!this.localStorage) { + return; + } + const key = "mxjssdk_memory_filter_" + filterName; + try { + if (isValidFilterId(filterId)) { + this.localStorage.setItem(key, filterId); + } + else { + this.localStorage.removeItem(key); + } + } + catch (e) { } + } + /** + * Store user-scoped account data events. + * N.B. that account data only allows a single event per type, so multiple + * events with the same type will replace each other. + * @param events - The events to store. + */ + storeAccountDataEvents(events) { + events.forEach((event) => { + // MSC3391: an event with content of {} should be interpreted as deleted + const isDeleted = !Object.keys(event.getContent()).length; + if (isDeleted) { + this.accountData.delete(event.getType()); + } + else { + this.accountData.set(event.getType(), event); + } + }); + } + /** + * Get account data event by event type + * @param eventType - The event type being queried + * @returns the user account_data event of given type, if any + */ + getAccountData(eventType) { + return this.accountData.get(eventType); + } + /** + * setSyncData does nothing as there is no backing data store. + * + * @param syncData - The sync data + * @returns An immediately resolved promise. + */ + setSyncData(syncData) { + return Promise.resolve(); + } + /** + * We never want to save becase we have nothing to save to. + * + * @returns If the store wants to save + */ + wantsSave() { + return false; + } + /** + * Save does nothing as there is no backing data store. + * @param force - True to force a save (but the memory + * store still can't save anything) + */ + save(force) { } + /** + * Startup does nothing as this store doesn't require starting up. + * @returns An immediately resolved promise. + */ + startup() { + return Promise.resolve(); + } + /** + * @returns Promise which resolves with a sync response to restore the + * client state to where it was at the last save, or null if there + * is no saved sync data. + */ + getSavedSync() { + return Promise.resolve(null); + } + /** + * @returns If there is a saved sync, the nextBatch token + * for this sync, otherwise null. + */ + getSavedSyncToken() { + return Promise.resolve(null); + } + /** + * Delete all data from this store. + * @returns An immediately resolved promise. + */ + deleteAllData() { + this.rooms = { + // roomId: Room + }; + this.users = { + // userId: User + }; + this.syncToken = null; + this.filters = new utils_1.MapWithDefault(() => new Map()); + this.accountData = new Map(); // type : content + return Promise.resolve(); + } + /** + * Returns the out-of-band membership events for this room that + * were previously loaded. + * @returns the events, potentially an empty array if OOB loading didn't yield any new members + * @returns in case the members for this room haven't been stored yet + */ + getOutOfBandMembers(roomId) { + return Promise.resolve(this.oobMembers.get(roomId) || null); + } + /** + * Stores the out-of-band membership events for this room. Note that + * it still makes sense to store an empty array as the OOB status for the room is + * marked as fetched, and getOutOfBandMembers will return an empty array instead of null + * @param membershipEvents - the membership events to store + * @returns when all members have been stored + */ + setOutOfBandMembers(roomId, membershipEvents) { + this.oobMembers.set(roomId, membershipEvents); + return Promise.resolve(); + } + clearOutOfBandMembers(roomId) { + this.oobMembers.delete(roomId); + return Promise.resolve(); + } + getClientOptions() { + return Promise.resolve(this.clientOptions); + } + storeClientOptions(options) { + this.clientOptions = Object.assign({}, options); + return Promise.resolve(); + } + getPendingEvents(roomId) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + return (_a = this.pendingEvents[roomId]) !== null && _a !== void 0 ? _a : []; + }); + } + setPendingEvents(roomId, events) { + return __awaiter(this, void 0, void 0, function* () { + this.pendingEvents[roomId] = events; + }); + } + saveToDeviceBatches(batches) { + for (const batch of batches) { + this.pendingToDeviceBatches.push({ + id: this.nextToDeviceBatchId++, + eventType: batch.eventType, + txnId: batch.txnId, + batch: batch.batch, + }); + } + return Promise.resolve(); + } + getOldestToDeviceBatch() { + return __awaiter(this, void 0, void 0, function* () { + if (this.pendingToDeviceBatches.length === 0) + return null; + return this.pendingToDeviceBatches[0]; + }); + } + removeToDeviceBatch(id) { + this.pendingToDeviceBatches = this.pendingToDeviceBatches.filter((batch) => batch.id !== id); + return Promise.resolve(); + } +} +exports.MemoryStore = MemoryStore; + +},{"../models/room-state":390,"../models/user":396,"../utils":416}],412:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StubStore = void 0; +/** + * Construct a stub store. This does no-ops on most store methods. + */ +class StubStore { + constructor() { + this.accountData = new Map(); // stub + this.fromToken = null; + } + /** @returns whether or not the database was newly created in this session. */ + isNewlyCreated() { + return Promise.resolve(true); + } + /** + * Get the sync token. + */ + getSyncToken() { + return this.fromToken; + } + /** + * Set the sync token. + */ + setSyncToken(token) { + this.fromToken = token; + } + /** + * No-op. + */ + storeRoom(room) { } + /** + * No-op. + */ + getRoom(roomId) { + return null; + } + /** + * No-op. + * @returns An empty array. + */ + getRooms() { + return []; + } + /** + * Permanently delete a room. + */ + removeRoom(roomId) { + return; + } + /** + * No-op. + * @returns An empty array. + */ + getRoomSummaries() { + return []; + } + /** + * No-op. + */ + storeUser(user) { } + /** + * No-op. + */ + getUser(userId) { + return null; + } + /** + * No-op. + */ + getUsers() { + return []; + } + /** + * No-op. + */ + scrollback(room, limit) { + return []; + } + /** + * Store events for a room. + * @param room - The room to store events for. + * @param events - The events to store. + * @param token - The token associated with these events. + * @param toStart - True if these are paginated results. + */ + storeEvents(room, events, token, toStart) { } + /** + * Store a filter. + */ + storeFilter(filter) { } + /** + * Retrieve a filter. + * @returns A filter or null. + */ + getFilter(userId, filterId) { + return null; + } + /** + * Retrieve a filter ID with the given name. + * @param filterName - The filter name. + * @returns The filter ID or null. + */ + getFilterIdByName(filterName) { + return null; + } + /** + * Set a filter name to ID mapping. + */ + setFilterIdByName(filterName, filterId) { } + /** + * Store user-scoped account data events + * @param events - The events to store. + */ + storeAccountDataEvents(events) { } + /** + * Get account data event by event type + * @param eventType - The event type being queried + */ + getAccountData(eventType) { + return undefined; + } + /** + * setSyncData does nothing as there is no backing data store. + * + * @param syncData - The sync data + * @returns An immediately resolved promise. + */ + setSyncData(syncData) { + return Promise.resolve(); + } + /** + * We never want to save because we have nothing to save to. + * + * @returns If the store wants to save + */ + wantsSave() { + return false; + } + /** + * Save does nothing as there is no backing data store. + */ + save() { } + /** + * Startup does nothing. + * @returns An immediately resolved promise. + */ + startup() { + return Promise.resolve(); + } + /** + * @returns Promise which resolves with a sync response to restore the + * client state to where it was at the last save, or null if there + * is no saved sync data. + */ + getSavedSync() { + return Promise.resolve(null); + } + /** + * @returns If there is a saved sync, the nextBatch token + * for this sync, otherwise null. + */ + getSavedSyncToken() { + return Promise.resolve(null); + } + /** + * Delete all data from this store. Does nothing since this store + * doesn't store anything. + * @returns An immediately resolved promise. + */ + deleteAllData() { + return Promise.resolve(); + } + getOutOfBandMembers() { + return Promise.resolve(null); + } + setOutOfBandMembers(roomId, membershipEvents) { + return Promise.resolve(); + } + clearOutOfBandMembers() { + return Promise.resolve(); + } + getClientOptions() { + return Promise.resolve(undefined); + } + storeClientOptions(options) { + return Promise.resolve(); + } + getPendingEvents(roomId) { + return __awaiter(this, void 0, void 0, function* () { + return []; + }); + } + setPendingEvents(roomId, events) { + return Promise.resolve(); + } + saveToDeviceBatches(batch) { + return __awaiter(this, void 0, void 0, function* () { + return Promise.resolve(); + }); + } + getOldestToDeviceBatch() { + return Promise.resolve(null); + } + removeToDeviceBatch(id) { + return __awaiter(this, void 0, void 0, function* () { + return Promise.resolve(); + }); + } +} +exports.StubStore = StubStore; + +},{}],413:[function(require,module,exports){ +"use strict"; +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SyncAccumulator = exports.Category = void 0; +/** + * This is an internal module. See {@link SyncAccumulator} for the public class. + */ +const logger_1 = require("./logger"); +const utils_1 = require("./utils"); +const event_1 = require("./@types/event"); +const read_receipts_1 = require("./@types/read_receipts"); +const sync_1 = require("./@types/sync"); +/* eslint-enable camelcase */ +var Category; +(function (Category) { + Category["Invite"] = "invite"; + Category["Leave"] = "leave"; + Category["Join"] = "join"; +})(Category = exports.Category || (exports.Category = {})); +function isTaggedEvent(event) { + return "_localTs" in event && event["_localTs"] !== undefined; +} +/** + * The purpose of this class is to accumulate /sync responses such that a + * complete "initial" JSON response can be returned which accurately represents + * the sum total of the /sync responses accumulated to date. It only handles + * room data: that is, everything under the "rooms" top-level key. + * + * This class is used when persisting room data so a complete /sync response can + * be loaded from disk and incremental syncs can be performed on the server, + * rather than asking the server to do an initial sync on startup. + */ +class SyncAccumulator { + constructor(opts = {}) { + this.opts = opts; + this.accountData = {}; // $event_type: Object + this.inviteRooms = {}; // $roomId: { ... sync 'invite' json data ... } + this.joinRooms = {}; + // the /sync token which corresponds to the last time rooms were + // accumulated. We remember this so that any caller can obtain a + // coherent /sync response and know at what point they should be + // streaming from without losing events. + this.nextBatch = null; + this.opts.maxTimelineEntries = this.opts.maxTimelineEntries || 50; + } + accumulate(syncResponse, fromDatabase = false) { + this.accumulateRooms(syncResponse, fromDatabase); + this.accumulateAccountData(syncResponse); + this.nextBatch = syncResponse.next_batch; + } + accumulateAccountData(syncResponse) { + if (!syncResponse.account_data || !syncResponse.account_data.events) { + return; + } + // Clobbers based on event type. + syncResponse.account_data.events.forEach((e) => { + this.accountData[e.type] = e; + }); + } + /** + * Accumulate incremental /sync room data. + * @param syncResponse - the complete /sync JSON + * @param fromDatabase - True if the sync response is one saved to the database + */ + accumulateRooms(syncResponse, fromDatabase = false) { + if (!syncResponse.rooms) { + return; + } + if (syncResponse.rooms.invite) { + Object.keys(syncResponse.rooms.invite).forEach((roomId) => { + this.accumulateRoom(roomId, Category.Invite, syncResponse.rooms.invite[roomId], fromDatabase); + }); + } + if (syncResponse.rooms.join) { + Object.keys(syncResponse.rooms.join).forEach((roomId) => { + this.accumulateRoom(roomId, Category.Join, syncResponse.rooms.join[roomId], fromDatabase); + }); + } + if (syncResponse.rooms.leave) { + Object.keys(syncResponse.rooms.leave).forEach((roomId) => { + this.accumulateRoom(roomId, Category.Leave, syncResponse.rooms.leave[roomId], fromDatabase); + }); + } + } + accumulateRoom(roomId, category, data, fromDatabase = false) { + // Valid /sync state transitions + // +--------+ <======+ 1: Accept an invite + // +== | INVITE | | (5) 2: Leave a room + // | +--------+ =====+ | 3: Join a public room previously + // |(1) (4) | | left (handle as if new room) + // V (2) V | 4: Reject an invite + // +------+ ========> +--------+ 5: Invite to a room previously + // | JOIN | (3) | LEAVE* | left (handle as if new room) + // +------+ <======== +--------+ + // + // * equivalent to "no state" + switch (category) { + case Category.Invite: // (5) + this.accumulateInviteState(roomId, data); + break; + case Category.Join: + if (this.inviteRooms[roomId]) { + // (1) + // was previously invite, now join. We expect /sync to give + // the entire state and timeline on 'join', so delete previous + // invite state + delete this.inviteRooms[roomId]; + } + // (3) + this.accumulateJoinState(roomId, data, fromDatabase); + break; + case Category.Leave: + if (this.inviteRooms[roomId]) { + // (4) + delete this.inviteRooms[roomId]; + } + else { + // (2) + delete this.joinRooms[roomId]; + } + break; + default: + logger_1.logger.error("Unknown cateogory: ", category); + } + } + accumulateInviteState(roomId, data) { + if (!data.invite_state || !data.invite_state.events) { + // no new data + return; + } + if (!this.inviteRooms[roomId]) { + this.inviteRooms[roomId] = { + invite_state: data.invite_state, + }; + return; + } + // accumulate extra keys for invite->invite transitions + // clobber based on event type / state key + // We expect invite_state to be small, so just loop over the events + const currentData = this.inviteRooms[roomId]; + data.invite_state.events.forEach((e) => { + let hasAdded = false; + for (let i = 0; i < currentData.invite_state.events.length; i++) { + const current = currentData.invite_state.events[i]; + if (current.type === e.type && current.state_key == e.state_key) { + currentData.invite_state.events[i] = e; // update + hasAdded = true; + } + } + if (!hasAdded) { + currentData.invite_state.events.push(e); + } + }); + } + // Accumulate timeline and state events in a room. + accumulateJoinState(roomId, data, fromDatabase = false) { + // We expect this function to be called a lot (every /sync) so we want + // this to be fast. /sync stores events in an array but we often want + // to clobber based on type/state_key. Rather than convert arrays to + // maps all the time, just keep private maps which contain + // the actual current accumulated sync state, and array-ify it when + // getJSON() is called. + var _a, _b, _c, _d, _e, _f, _g, _h; + // State resolution: + // The 'state' key is the delta from the previous sync (or start of time + // if no token was supplied), to the START of the timeline. To obtain + // the current state, we need to "roll forward" state by reading the + // timeline. We want to store the current state so we can drop events + // out the end of the timeline based on opts.maxTimelineEntries. + // + // 'state' 'timeline' current state + // |-------x<======================>x + // T I M E + // + // When getJSON() is called, we 'roll back' the current state by the + // number of entries in the timeline to work out what 'state' should be. + // Back-pagination: + // On an initial /sync, the server provides a back-pagination token for + // the start of the timeline. When /sync deltas come down, they also + // include back-pagination tokens for the start of the timeline. This + // means not all events in the timeline have back-pagination tokens, as + // it is only the ones at the START of the timeline which have them. + // In order for us to have a valid timeline (and back-pagination token + // to match), we need to make sure that when we remove old timeline + // events, that we roll forward to an event which has a back-pagination + // token. This means we can't keep a strict sliding-window based on + // opts.maxTimelineEntries, and we may have a few less. We should never + // have more though, provided that the /sync limit is less than or equal + // to opts.maxTimelineEntries. + if (!this.joinRooms[roomId]) { + // Create truly empty objects so event types of 'hasOwnProperty' and co + // don't cause this code to break. + this.joinRooms[roomId] = { + _currentState: Object.create(null), + _timeline: [], + _accountData: Object.create(null), + _unreadNotifications: {}, + _unreadThreadNotifications: {}, + _summary: {}, + _readReceipts: {}, + _threadReadReceipts: {}, + }; + } + const currentData = this.joinRooms[roomId]; + if (data.account_data && data.account_data.events) { + // clobber based on type + data.account_data.events.forEach((e) => { + currentData._accountData[e.type] = e; + }); + } + // these probably clobber, spec is unclear. + if (data.unread_notifications) { + currentData._unreadNotifications = data.unread_notifications; + } + currentData._unreadThreadNotifications = + (_b = (_a = data[sync_1.UNREAD_THREAD_NOTIFICATIONS.stable]) !== null && _a !== void 0 ? _a : data[sync_1.UNREAD_THREAD_NOTIFICATIONS.unstable]) !== null && _b !== void 0 ? _b : undefined; + if (data.summary) { + const HEROES_KEY = "m.heroes"; + const INVITED_COUNT_KEY = "m.invited_member_count"; + const JOINED_COUNT_KEY = "m.joined_member_count"; + const acc = currentData._summary; + const sum = data.summary; + acc[HEROES_KEY] = sum[HEROES_KEY] || acc[HEROES_KEY]; + acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] || acc[JOINED_COUNT_KEY]; + acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] || acc[INVITED_COUNT_KEY]; + } + (_d = (_c = data.ephemeral) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.forEach((e) => { + // We purposefully do not persist m.typing events. + // Technically you could refresh a browser before the timer on a + // typing event is up, so it'll look like you aren't typing when + // you really still are. However, the alternative is worse. If + // we do persist typing events, it will look like people are + // typing forever until someone really does start typing (which + // will prompt Synapse to send down an actual m.typing event to + // clobber the one we persisted). + if (e.type !== event_1.EventType.Receipt || !e.content) { + // This means we'll drop unknown ephemeral events but that + // seems okay. + return; + } + // Handle m.receipt events. They clobber based on: + // (user_id, receipt_type) + // but they are keyed in the event as: + // content:{ $event_id: { $receipt_type: { $user_id: {json} }}} + // so store them in the former so we can accumulate receipt deltas + // quickly and efficiently (we expect a lot of them). Fold the + // receipt type into the key name since we only have 1 at the + // moment (m.read) and nested JSON objects are slower and more + // of a hassle to work with. We'll inflate this back out when + // getJSON() is called. + Object.keys(e.content).forEach((eventId) => { + Object.entries(e.content[eventId]).forEach(([key, value]) => { + var _a; + if (!(0, utils_1.isSupportedReceiptType)(key)) + return; + for (const userId of Object.keys(value)) { + const data = e.content[eventId][key][userId]; + const receipt = { + data: e.content[eventId][key][userId], + type: key, + eventId: eventId, + }; + if (!data.thread_id || data.thread_id === read_receipts_1.MAIN_ROOM_TIMELINE) { + currentData._readReceipts[userId] = receipt; + } + else { + currentData._threadReadReceipts = Object.assign(Object.assign({}, currentData._threadReadReceipts), { [data.thread_id]: Object.assign(Object.assign({}, ((_a = currentData._threadReadReceipts[data.thread_id]) !== null && _a !== void 0 ? _a : {})), { [userId]: receipt }) }); + } + } + }); + }); + }); + // if we got a limited sync, we need to remove all timeline entries or else + // we will have gaps in the timeline. + if (data.timeline && data.timeline.limited) { + currentData._timeline = []; + } + // Work out the current state. The deltas need to be applied in the order: + // - existing state which didn't come down /sync. + // - State events under the 'state' key. + // - State events in the 'timeline'. + (_f = (_e = data.state) === null || _e === void 0 ? void 0 : _e.events) === null || _f === void 0 ? void 0 : _f.forEach((e) => { + setState(currentData._currentState, e); + }); + (_h = (_g = data.timeline) === null || _g === void 0 ? void 0 : _g.events) === null || _h === void 0 ? void 0 : _h.forEach((e, index) => { + var _a; + // this nops if 'e' isn't a state event + setState(currentData._currentState, e); + // append the event to the timeline. The back-pagination token + // corresponds to the first event in the timeline + let transformedEvent; + if (!fromDatabase) { + transformedEvent = Object.assign({}, e); + if (transformedEvent.unsigned !== undefined) { + transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned); + } + const age = e.unsigned ? e.unsigned.age : e.age; + if (age !== undefined) + transformedEvent._localTs = Date.now() - age; + } + else { + transformedEvent = e; + } + currentData._timeline.push({ + event: transformedEvent, + token: index === 0 ? (_a = data.timeline.prev_batch) !== null && _a !== void 0 ? _a : null : null, + }); + }); + // attempt to prune the timeline by jumping between events which have + // pagination tokens. + if (currentData._timeline.length > this.opts.maxTimelineEntries) { + const startIndex = currentData._timeline.length - this.opts.maxTimelineEntries; + for (let i = startIndex; i < currentData._timeline.length; i++) { + if (currentData._timeline[i].token) { + // keep all events after this, including this one + currentData._timeline = currentData._timeline.slice(i, currentData._timeline.length); + break; + } + } + } + } + /** + * Return everything under the 'rooms' key from a /sync response which + * represents all room data that should be stored. This should be paired + * with the sync token which represents the most recent /sync response + * provided to accumulate(). + * @param forDatabase - True to generate a sync to be saved to storage + * @returns An object with a "nextBatch", "roomsData" and "accountData" + * keys. + * The "nextBatch" key is a string which represents at what point in the + * /sync stream the accumulator reached. This token should be used when + * restarting a /sync stream at startup. Failure to do so can lead to missing + * events. The "roomsData" key is an Object which represents the entire + * /sync response from the 'rooms' key onwards. The "accountData" key is + * a list of raw events which represent global account data. + */ + getJSON(forDatabase = false) { + const data = { + join: {}, + invite: {}, + // always empty. This is set by /sync when a room was previously + // in 'invite' or 'join'. On fresh startup, the client won't know + // about any previous room being in 'invite' or 'join' so we can + // just omit mentioning it at all, even if it has previously come + // down /sync. + // The notable exception is when a client is kicked or banned: + // we may want to hold onto that room so the client can clearly see + // why their room has disappeared. We don't persist it though because + // it is unclear *when* we can safely remove the room from the DB. + // Instead, we assume that if you're loading from the DB, you've + // refreshed the page, which means you've seen the kick/ban already. + leave: {}, + }; + Object.keys(this.inviteRooms).forEach((roomId) => { + data.invite[roomId] = this.inviteRooms[roomId]; + }); + Object.keys(this.joinRooms).forEach((roomId) => { + const roomData = this.joinRooms[roomId]; + const roomJson = { + ephemeral: { events: [] }, + account_data: { events: [] }, + state: { events: [] }, + timeline: { + events: [], + prev_batch: null, + }, + unread_notifications: roomData._unreadNotifications, + unread_thread_notifications: roomData._unreadThreadNotifications, + summary: roomData._summary, + }; + // Add account data + Object.keys(roomData._accountData).forEach((evType) => { + roomJson.account_data.events.push(roomData._accountData[evType]); + }); + // Add receipt data + const receiptEvent = { + type: event_1.EventType.Receipt, + room_id: roomId, + content: { + // $event_id: { "m.read": { $user_id: $json } } + }, + }; + const receiptEventContent = new utils_1.MapWithDefault(() => new utils_1.MapWithDefault(() => new Map())); + for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) { + receiptEventContent + .getOrCreate(receiptData.eventId) + .getOrCreate(receiptData.type) + .set(userId, receiptData.data); + } + for (const threadReceipts of Object.values(roomData._threadReadReceipts)) { + for (const [userId, receiptData] of Object.entries(threadReceipts)) { + receiptEventContent + .getOrCreate(receiptData.eventId) + .getOrCreate(receiptData.type) + .set(userId, receiptData.data); + } + } + receiptEvent.content = (0, utils_1.recursiveMapToObject)(receiptEventContent); + // add only if we have some receipt data + if (receiptEventContent.size > 0) { + roomJson.ephemeral.events.push(receiptEvent); + } + // Add timeline data + roomData._timeline.forEach((msgData) => { + if (!roomJson.timeline.prev_batch) { + // the first event we add to the timeline MUST match up to + // the prev_batch token. + if (!msgData.token) { + return; // this shouldn't happen as we prune constantly. + } + roomJson.timeline.prev_batch = msgData.token; + } + let transformedEvent; + if (!forDatabase && isTaggedEvent(msgData.event)) { + // This means we have to copy each event, so we can fix it up to + // set a correct 'age' parameter whilst keeping the local timestamp + // on our stored event. If this turns out to be a bottleneck, it could + // be optimised either by doing this in the main process after the data + // has been structured-cloned to go between the worker & main process, + // or special-casing data from saved syncs to read the local timestamp + // directly rather than turning it into age to then immediately be + // transformed back again into a local timestamp. + transformedEvent = Object.assign({}, msgData.event); + if (transformedEvent.unsigned !== undefined) { + transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned); + } + delete transformedEvent._localTs; + transformedEvent.unsigned = transformedEvent.unsigned || {}; + transformedEvent.unsigned.age = Date.now() - msgData.event._localTs; + } + else { + transformedEvent = msgData.event; + } + roomJson.timeline.events.push(transformedEvent); + }); + // Add state data: roll back current state to the start of timeline, + // by "reverse clobbering" from the end of the timeline to the start. + // Convert maps back into arrays. + const rollBackState = Object.create(null); + for (let i = roomJson.timeline.events.length - 1; i >= 0; i--) { + const timelineEvent = roomJson.timeline.events[i]; + if (timelineEvent.state_key === null || + timelineEvent.state_key === undefined) { + continue; // not a state event + } + // since we're going back in time, we need to use the previous + // state value else we'll break causality. We don't have the + // complete previous state event, so we need to create one. + const prevStateEvent = (0, utils_1.deepCopy)(timelineEvent); + if (prevStateEvent.unsigned) { + if (prevStateEvent.unsigned.prev_content) { + prevStateEvent.content = prevStateEvent.unsigned.prev_content; + } + if (prevStateEvent.unsigned.prev_sender) { + prevStateEvent.sender = prevStateEvent.unsigned.prev_sender; + } + } + setState(rollBackState, prevStateEvent); + } + Object.keys(roomData._currentState).forEach((evType) => { + Object.keys(roomData._currentState[evType]).forEach((stateKey) => { + let ev = roomData._currentState[evType][stateKey]; + if (rollBackState[evType] && rollBackState[evType][stateKey]) { + // use the reverse clobbered event instead. + ev = rollBackState[evType][stateKey]; + } + roomJson.state.events.push(ev); + }); + }); + data.join[roomId] = roomJson; + }); + // Add account data + const accData = []; + Object.keys(this.accountData).forEach((evType) => { + accData.push(this.accountData[evType]); + }); + return { + nextBatch: this.nextBatch, + roomsData: data, + accountData: accData, + }; + } + getNextBatchToken() { + return this.nextBatch; + } +} +exports.SyncAccumulator = SyncAccumulator; +function setState(eventMap, event) { + if (event.state_key === null || event.state_key === undefined || !event.type) { + return; + } + if (!eventMap[event.type]) { + eventMap[event.type] = Object.create(null); + } + eventMap[event.type][event.state_key] = event; +} + +},{"./@types/event":306,"./@types/read_receipts":311,"./@types/sync":314,"./logger":374,"./utils":416}],414:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; +/* +Copyright 2015 - 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports._createAndReEmitRoom = exports.SyncApi = exports.defaultSyncApiOpts = exports.defaultClientOpts = exports.SyncState = void 0; +const user_1 = require("./models/user"); +const room_1 = require("./models/room"); +const utils = __importStar(require("./utils")); +const utils_1 = require("./utils"); +const filter_1 = require("./filter"); +const event_timeline_1 = require("./models/event-timeline"); +const logger_1 = require("./logger"); +const errors_1 = require("./errors"); +const client_1 = require("./client"); +const http_api_1 = require("./http-api"); +const event_1 = require("./@types/event"); +const room_state_1 = require("./models/room-state"); +const room_member_1 = require("./models/room-member"); +const beacon_1 = require("./models/beacon"); +const sync_1 = require("./@types/sync"); +const feature_1 = require("./feature"); +const DEBUG = true; +// /sync requests allow you to set a timeout= but the request may continue +// beyond that and wedge forever, so we need to track how long we are willing +// to keep open the connection. This constant is *ADDED* to the timeout= value +// to determine the max time we're willing to wait. +const BUFFER_PERIOD_MS = 80 * 1000; +// Number of consecutive failed syncs that will lead to a syncState of ERROR as opposed +// to RECONNECTING. This is needed to inform the client of server issues when the +// keepAlive is successful but the server /sync fails. +const FAILED_SYNC_ERROR_THRESHOLD = 3; +var SyncState; +(function (SyncState) { + /** Emitted after we try to sync more than `FAILED_SYNC_ERROR_THRESHOLD` + * times and are still failing. Or when we enounter a hard error like the + * token being invalid. */ + SyncState["Error"] = "ERROR"; + /** Emitted after the first sync events are ready (this could even be sync + * events from the cache) */ + SyncState["Prepared"] = "PREPARED"; + /** Emitted when the sync loop is no longer running */ + SyncState["Stopped"] = "STOPPED"; + /** Emitted after each sync request happens */ + SyncState["Syncing"] = "SYNCING"; + /** Emitted after a connectivity error and we're ready to start syncing again */ + SyncState["Catchup"] = "CATCHUP"; + /** Emitted for each time we try reconnecting. Will switch to `Error` after + * we reach the `FAILED_SYNC_ERROR_THRESHOLD` + */ + SyncState["Reconnecting"] = "RECONNECTING"; +})(SyncState = exports.SyncState || (exports.SyncState = {})); +// Room versions where "insertion", "batch", and "marker" events are controlled +// by power-levels. MSC2716 is supported in existing room versions but they +// should only have special meaning when the room creator sends them. +const MSC2716_ROOM_VERSIONS = ["org.matrix.msc2716v3"]; +function getFilterName(userId, suffix) { + // scope this on the user ID because people may login on many accounts + // and they all need to be stored! + return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : ""); +} +/* istanbul ignore next */ +function debuglog(...params) { + if (!DEBUG) + return; + logger_1.logger.log(...params); +} +var SetPresence; +(function (SetPresence) { + SetPresence["Offline"] = "offline"; + SetPresence["Online"] = "online"; + SetPresence["Unavailable"] = "unavailable"; +})(SetPresence || (SetPresence = {})); +/** add default settings to an IStoredClientOpts */ +function defaultClientOpts(opts) { + return Object.assign({ initialSyncLimit: 8, resolveInvitesToProfiles: false, pollTimeout: 30 * 1000, pendingEventOrdering: client_1.PendingEventOrdering.Chronological, threadSupport: false }, opts); +} +exports.defaultClientOpts = defaultClientOpts; +function defaultSyncApiOpts(syncOpts) { + return Object.assign({ canResetEntireTimeline: (_roomId) => false }, syncOpts); +} +exports.defaultSyncApiOpts = defaultSyncApiOpts; +class SyncApi { + /** + * Construct an entity which is able to sync with a homeserver. + * @param client - The matrix client instance to use. + * @param opts - client config options + * @param syncOpts - sync-specific options passed by the client + * @internal + */ + constructor(client, opts, syncOpts) { + this.client = client; + this._peekRoom = null; + this.syncState = null; + this.catchingUp = false; + this.running = false; + this.notifEvents = []; // accumulator of sync events in the current sync response + this.failedSyncCount = 0; // Number of consecutive failed /sync requests + this.storeIsInvalid = false; // flag set if the store needs to be cleared before we can start + this.getPushRules = () => __awaiter(this, void 0, void 0, function* () { + try { + debuglog("Getting push rules..."); + const result = yield this.client.getPushRules(); + debuglog("Got push rules"); + this.client.pushRules = result; + } + catch (err) { + logger_1.logger.error("Getting push rules failed", err); + if (this.shouldAbortSync(err)) + return; + // wait for saved sync to complete before doing anything else, + // otherwise the sync state will end up being incorrect + debuglog("Waiting for saved sync before retrying push rules..."); + yield this.recoverFromSyncStartupError(this.savedSyncPromise, err); + return this.getPushRules(); // try again + } + }); + this.buildDefaultFilter = () => { + const filter = new filter_1.Filter(this.client.credentials.userId); + if (this.client.canSupport.get(feature_1.Feature.ThreadUnreadNotifications) !== feature_1.ServerSupport.Unsupported) { + filter.setUnreadThreadNotifications(true); + } + return filter; + }; + this.checkLazyLoadStatus = () => __awaiter(this, void 0, void 0, function* () { + var _a; + debuglog("Checking lazy load status..."); + if (this.opts.lazyLoadMembers && this.client.isGuest()) { + this.opts.lazyLoadMembers = false; + } + if (this.opts.lazyLoadMembers) { + debuglog("Checking server lazy load support..."); + const supported = yield this.client.doesServerSupportLazyLoading(); + if (supported) { + debuglog("Enabling lazy load on sync filter..."); + if (!this.opts.filter) { + this.opts.filter = this.buildDefaultFilter(); + } + this.opts.filter.setLazyLoadMembers(true); + } + else { + debuglog("LL: lazy loading requested but not supported " + "by server, so disabling"); + this.opts.lazyLoadMembers = false; + } + } + // need to vape the store when enabling LL and wasn't enabled before + debuglog("Checking whether lazy loading has changed in store..."); + const shouldClear = yield this.wasLazyLoadingToggled(this.opts.lazyLoadMembers); + if (shouldClear) { + this.storeIsInvalid = true; + const error = new errors_1.InvalidStoreError(errors_1.InvalidStoreState.ToggledLazyLoading, !!this.opts.lazyLoadMembers); + this.updateSyncState(SyncState.Error, { error }); + // bail out of the sync loop now: the app needs to respond to this error. + // we leave the state as 'ERROR' which isn't great since this normally means + // we're retrying. The client must be stopped before clearing the stores anyway + // so the app should stop the client, clear the store and start it again. + logger_1.logger.warn("InvalidStoreError: store is not usable: stopping sync."); + return; + } + if (this.opts.lazyLoadMembers) { + (_a = this.syncOpts.crypto) === null || _a === void 0 ? void 0 : _a.enableLazyLoading(); + } + try { + debuglog("Storing client options..."); + yield this.client.storeClientOptions(); + debuglog("Stored client options"); + } + catch (err) { + logger_1.logger.error("Storing client options failed", err); + throw err; + } + }); + this.getFilter = () => __awaiter(this, void 0, void 0, function* () { + debuglog("Getting filter..."); + let filter; + if (this.opts.filter) { + filter = this.opts.filter; + } + else { + filter = this.buildDefaultFilter(); + } + let filterId; + try { + filterId = yield this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter); + } + catch (err) { + logger_1.logger.error("Getting filter failed", err); + if (this.shouldAbortSync(err)) + return {}; + // wait for saved sync to complete before doing anything else, + // otherwise the sync state will end up being incorrect + debuglog("Waiting for saved sync before retrying filter..."); + yield this.recoverFromSyncStartupError(this.savedSyncPromise, err); + return this.getFilter(); // try again + } + return { filter, filterId }; + }); + /** + * Event handler for the 'online' event + * This event is generally unreliable and precise behaviour + * varies between browsers, so we poll for connectivity too, + * but this might help us reconnect a little faster. + */ + this.onOnline = () => { + debuglog("Browser thinks we are back online"); + this.startKeepAlives(0); + }; + this.opts = defaultClientOpts(opts); + this.syncOpts = defaultSyncApiOpts(syncOpts); + if (client.getNotifTimelineSet()) { + client.reEmitter.reEmit(client.getNotifTimelineSet(), [room_1.RoomEvent.Timeline, room_1.RoomEvent.TimelineReset]); + } + } + createRoom(roomId) { + const room = _createAndReEmitRoom(this.client, roomId, this.opts); + room.on(room_state_1.RoomStateEvent.Marker, (markerEvent, markerFoundOptions) => { + this.onMarkerStateEvent(room, markerEvent, markerFoundOptions); + }); + return room; + } + /** When we see the marker state change in the room, we know there is some + * new historical messages imported by MSC2716 `/batch_send` somewhere in + * the room and we need to throw away the timeline to make sure the + * historical messages are shown when we paginate `/messages` again. + * @param room - The room where the marker event was sent + * @param markerEvent - The new marker event + * @param setStateOptions - When `timelineWasEmpty` is set + * as `true`, the given marker event will be ignored + */ + onMarkerStateEvent(room, markerEvent, { timelineWasEmpty } = {}) { + // We don't need to refresh the timeline if it was empty before the + // marker arrived. This could be happen in a variety of cases: + // 1. From the initial sync + // 2. If it's from the first state we're seeing after joining the room + // 3. Or whether it's coming from `syncFromCache` + if (timelineWasEmpty) { + logger_1.logger.debug(`MarkerState: Ignoring markerEventId=${markerEvent.getId()} in roomId=${room.roomId} ` + + `because the timeline was empty before the marker arrived which means there is nothing to refresh.`); + return; + } + const isValidMsc2716Event = + // Check whether the room version directly supports MSC2716, in + // which case, "marker" events are already auth'ed by + // power_levels + MSC2716_ROOM_VERSIONS.includes(room.getVersion()) || + // MSC2716 is also supported in all existing room versions but + // special meaning should only be given to "insertion", "batch", + // and "marker" events when they come from the room creator + markerEvent.getSender() === room.getCreator(); + // It would be nice if we could also specifically tell whether the + // historical messages actually affected the locally cached client + // timeline or not. The problem is we can't see the prev_events of + // the base insertion event that the marker was pointing to because + // prev_events aren't available in the client API's. In most cases, + // the history won't be in people's locally cached timelines in the + // client, so we don't need to bother everyone about refreshing + // their timeline. This works for a v1 though and there are use + // cases like initially bootstrapping your bridged room where people + // are likely to encounter the historical messages affecting their + // current timeline (think someone signing up for Beeper and + // importing their Whatsapp history). + if (isValidMsc2716Event) { + // Saw new marker event, let's let the clients know they should + // refresh the timeline. + logger_1.logger.debug(`MarkerState: Timeline needs to be refreshed because ` + + `a new markerEventId=${markerEvent.getId()} was sent in roomId=${room.roomId}`); + room.setTimelineNeedsRefresh(true); + room.emit(room_1.RoomEvent.HistoryImportedWithinTimeline, markerEvent, room); + } + else { + logger_1.logger.debug(`MarkerState: Ignoring markerEventId=${markerEvent.getId()} in roomId=${room.roomId} because ` + + `MSC2716 is not supported in the room version or for any room version, the marker wasn't sent ` + + `by the room creator.`); + } + } + /** + * Sync rooms the user has left. + * @returns Resolved when they've been added to the store. + */ + syncLeftRooms() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const client = this.client; + // grab a filter with limit=1 and include_leave=true + const filter = new filter_1.Filter(this.client.credentials.userId); + filter.setTimelineLimit(1); + filter.setIncludeLeaveRooms(true); + const localTimeoutMs = this.opts.pollTimeout + BUFFER_PERIOD_MS; + const filterId = yield client.getOrCreateFilter(getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter); + const qps = { + timeout: 0, + filter: filterId, + }; + const data = yield client.http.authedRequest(http_api_1.Method.Get, "/sync", qps, undefined, { + localTimeoutMs, + }); + let leaveRooms = []; + if ((_a = data.rooms) === null || _a === void 0 ? void 0 : _a.leave) { + leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave); + } + const rooms = yield Promise.all(leaveRooms.map((leaveObj) => __awaiter(this, void 0, void 0, function* () { + const room = leaveObj.room; + if (!leaveObj.isBrandNewRoom) { + // the intention behind syncLeftRooms is to add in rooms which were + // *omitted* from the initial /sync. Rooms the user were joined to + // but then left whilst the app is running will appear in this list + // and we do not want to bother with them since they will have the + // current state already (and may get dupe messages if we add + // yet more timeline events!), so skip them. + // NB: When we persist rooms to localStorage this will be more + // complicated... + return; + } + leaveObj.timeline = leaveObj.timeline || { + prev_batch: null, + events: [], + }; + const events = this.mapSyncEventsFormat(leaveObj.timeline, room); + const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room); + // set the back-pagination token. Do this *before* adding any + // events so that clients can start back-paginating. + room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, event_timeline_1.EventTimeline.BACKWARDS); + yield this.injectRoomEvents(room, stateEvents, events); + room.recalculate(); + client.store.storeRoom(room); + client.emit(client_1.ClientEvent.Room, room); + this.processEventsForNotifs(room, events); + return room; + }))); + return rooms.filter(Boolean); + }); + } + /** + * Peek into a room. This will result in the room in question being synced so it + * is accessible via getRooms(). Live updates for the room will be provided. + * @param roomId - The room ID to peek into. + * @returns A promise which resolves once the room has been added to the + * store. + */ + peek(roomId) { + var _a; + if (((_a = this._peekRoom) === null || _a === void 0 ? void 0 : _a.roomId) === roomId) { + return Promise.resolve(this._peekRoom); + } + const client = this.client; + this._peekRoom = this.createRoom(roomId); + return this.client.roomInitialSync(roomId, 20).then((response) => { + // make sure things are init'd + response.messages = response.messages || { chunk: [] }; + response.messages.chunk = response.messages.chunk || []; + response.state = response.state || []; + // FIXME: Mostly duplicated from injectRoomEvents but not entirely + // because "state" in this API is at the BEGINNING of the chunk + const oldStateEvents = utils.deepCopy(response.state).map(client.getEventMapper()); + const stateEvents = response.state.map(client.getEventMapper()); + const messages = response.messages.chunk.map(client.getEventMapper()); + // XXX: copypasted from /sync until we kill off this minging v1 API stuff) + // handle presence events (User objects) + if (Array.isArray(response.presence)) { + response.presence.map(client.getEventMapper()).forEach(function (presenceEvent) { + let user = client.store.getUser(presenceEvent.getContent().user_id); + if (user) { + user.setPresenceEvent(presenceEvent); + } + else { + user = createNewUser(client, presenceEvent.getContent().user_id); + user.setPresenceEvent(presenceEvent); + client.store.storeUser(user); + } + client.emit(client_1.ClientEvent.Event, presenceEvent); + }); + } + // set the pagination token before adding the events in case people + // fire off pagination requests in response to the Room.timeline + // events. + if (response.messages.start) { + this._peekRoom.oldState.paginationToken = response.messages.start; + } + // set the state of the room to as it was after the timeline executes + this._peekRoom.oldState.setStateEvents(oldStateEvents); + this._peekRoom.currentState.setStateEvents(stateEvents); + this.resolveInvites(this._peekRoom); + this._peekRoom.recalculate(); + // roll backwards to diverge old state. addEventsToTimeline + // will overwrite the pagination token, so make sure it overwrites + // it with the right thing. + this._peekRoom.addEventsToTimeline(messages.reverse(), true, this._peekRoom.getLiveTimeline(), response.messages.start); + client.store.storeRoom(this._peekRoom); + client.emit(client_1.ClientEvent.Room, this._peekRoom); + this.peekPoll(this._peekRoom); + return this._peekRoom; + }); + } + /** + * Stop polling for updates in the peeked room. NOPs if there is no room being + * peeked. + */ + stopPeeking() { + this._peekRoom = null; + } + /** + * Do a peek room poll. + * @param token - from= token + */ + peekPoll(peekRoom, token) { + var _a; + if (this._peekRoom !== peekRoom) { + debuglog("Stopped peeking in room %s", peekRoom.roomId); + return; + } + // FIXME: gut wrenching; hard-coded timeout values + this.client.http + .authedRequest(http_api_1.Method.Get, "/events", { + room_id: peekRoom.roomId, + timeout: String(30 * 1000), + from: token, + }, undefined, { + localTimeoutMs: 50 * 1000, + abortSignal: (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal, + }) + .then((res) => { + if (this._peekRoom !== peekRoom) { + debuglog("Stopped peeking in room %s", peekRoom.roomId); + return; + } + // We have a problem that we get presence both from /events and /sync + // however, /sync only returns presence for users in rooms + // you're actually joined to. + // in order to be sure to get presence for all of the users in the + // peeked room, we handle presence explicitly here. This may result + // in duplicate presence events firing for some users, which is a + // performance drain, but such is life. + // XXX: copypasted from /sync until we can kill this minging v1 stuff. + res.chunk + .filter(function (e) { + return e.type === "m.presence"; + }) + .map(this.client.getEventMapper()) + .forEach((presenceEvent) => { + let user = this.client.store.getUser(presenceEvent.getContent().user_id); + if (user) { + user.setPresenceEvent(presenceEvent); + } + else { + user = createNewUser(this.client, presenceEvent.getContent().user_id); + user.setPresenceEvent(presenceEvent); + this.client.store.storeUser(user); + } + this.client.emit(client_1.ClientEvent.Event, presenceEvent); + }); + // strip out events which aren't for the given room_id (e.g presence) + // and also ephemeral events (which we're assuming is anything without + // and event ID because the /events API doesn't separate them). + const events = res.chunk + .filter(function (e) { + return e.room_id === peekRoom.roomId && e.event_id; + }) + .map(this.client.getEventMapper()); + peekRoom.addLiveEvents(events); + this.peekPoll(peekRoom, res.end); + }, (err) => { + logger_1.logger.error("[%s] Peek poll failed: %s", peekRoom.roomId, err); + setTimeout(() => { + this.peekPoll(peekRoom, token); + }, 30 * 1000); + }); + } + /** + * Returns the current state of this sync object + * @see MatrixClient#event:"sync" + */ + getSyncState() { + return this.syncState; + } + /** + * Returns the additional data object associated with + * the current sync state, or null if there is no + * such data. + * Sync errors, if available, are put in the 'error' key of + * this object. + */ + getSyncStateData() { + var _a; + return (_a = this.syncStateData) !== null && _a !== void 0 ? _a : null; + } + recoverFromSyncStartupError(savedSyncPromise, error) { + return __awaiter(this, void 0, void 0, function* () { + // Wait for the saved sync to complete - we send the pushrules and filter requests + // before the saved sync has finished so they can run in parallel, but only process + // the results after the saved sync is done. Equivalently, we wait for it to finish + // before reporting failures from these functions. + yield savedSyncPromise; + const keepaliveProm = this.startKeepAlives(); + this.updateSyncState(SyncState.Error, { error }); + yield keepaliveProm; + }); + } + /** + * Is the lazy loading option different than in previous session? + * @param lazyLoadMembers - current options for lazy loading + * @returns whether or not the option has changed compared to the previous session */ + wasLazyLoadingToggled(lazyLoadMembers = false) { + return __awaiter(this, void 0, void 0, function* () { + // assume it was turned off before + // if we don't know any better + let lazyLoadMembersBefore = false; + const isStoreNewlyCreated = yield this.client.store.isNewlyCreated(); + if (!isStoreNewlyCreated) { + const prevClientOptions = yield this.client.store.getClientOptions(); + if (prevClientOptions) { + lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers; + } + return lazyLoadMembersBefore !== lazyLoadMembers; + } + return false; + }); + } + shouldAbortSync(error) { + if (error.errcode === "M_UNKNOWN_TOKEN") { + // The logout already happened, we just need to stop. + logger_1.logger.warn("Token no longer valid - assuming logout"); + this.stop(); + this.updateSyncState(SyncState.Error, { error }); + return true; + } + return false; + } + /** + * Main entry point + */ + sync() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + this.running = true; + this.abortController = new AbortController(); + (_b = (_a = global.window) === null || _a === void 0 ? void 0 : _a.addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, "online", this.onOnline, false); + if (this.client.isGuest()) { + // no push rules for guests, no access to POST filter for guests. + return this.doSync({}); + } + // Pull the saved sync token out first, before the worker starts sending + // all the sync data which could take a while. This will let us send our + // first incremental sync request before we've processed our saved data. + debuglog("Getting saved sync token..."); + const savedSyncTokenPromise = this.client.store.getSavedSyncToken().then((tok) => { + debuglog("Got saved sync token"); + return tok; + }); + this.savedSyncPromise = this.client.store + .getSavedSync() + .then((savedSync) => { + debuglog(`Got reply from saved sync, exists? ${!!savedSync}`); + if (savedSync) { + return this.syncFromCache(savedSync); + } + }) + .catch((err) => { + logger_1.logger.error("Getting saved sync failed", err); + }); + // We need to do one-off checks before we can begin the /sync loop. + // These are: + // 1) We need to get push rules so we can check if events should bing as we get + // them from /sync. + // 2) We need to get/create a filter which we can use for /sync. + // 3) We need to check the lazy loading option matches what was used in the + // stored sync. If it doesn't, we can't use the stored sync. + // Now start the first incremental sync request: this can also + // take a while so if we set it going now, we can wait for it + // to finish while we process our saved sync data. + yield this.getPushRules(); + yield this.checkLazyLoadStatus(); + const { filterId, filter } = yield this.getFilter(); + if (!filter) + return; // bail, getFilter failed + // reset the notifications timeline to prepare it to paginate from + // the current point in time. + // The right solution would be to tie /sync pagination tokens into + // /notifications API somehow. + this.client.resetNotifTimelineSet(); + if (!this.currentSyncRequest) { + let firstSyncFilter = filterId; + const savedSyncToken = yield savedSyncTokenPromise; + if (savedSyncToken) { + debuglog("Sending first sync request..."); + } + else { + debuglog("Sending initial sync request..."); + const initialFilter = this.buildDefaultFilter(); + initialFilter.setDefinition(filter.getDefinition()); + initialFilter.setTimelineLimit(this.opts.initialSyncLimit); + // Use an inline filter, no point uploading it for a single usage + firstSyncFilter = JSON.stringify(initialFilter.getDefinition()); + } + // Send this first sync request here so we can then wait for the saved + // sync data to finish processing before we process the results of this one. + this.currentSyncRequest = this.doSyncRequest({ filter: firstSyncFilter }, savedSyncToken); + } + // Now wait for the saved sync to finish... + debuglog("Waiting for saved sync before starting sync processing..."); + yield this.savedSyncPromise; + // process the first sync request and continue syncing with the normal filterId + return this.doSync({ filter: filterId }); + }); + } + /** + * Stops the sync object from syncing. + */ + stop() { + var _a, _b, _c; + debuglog("SyncApi.stop"); + // It is necessary to check for the existance of + // global.window AND global.window.removeEventListener. + // Some platforms (e.g. React Native) register global.window, + // but do not have global.window.removeEventListener. + (_b = (_a = global.window) === null || _a === void 0 ? void 0 : _a.removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, "online", this.onOnline, false); + this.running = false; + (_c = this.abortController) === null || _c === void 0 ? void 0 : _c.abort(); + if (this.keepAliveTimer) { + clearTimeout(this.keepAliveTimer); + this.keepAliveTimer = undefined; + } + } + /** + * Retry a backed off syncing request immediately. This should only be used when + * the user explicitly attempts to retry their lost connection. + * @returns True if this resulted in a request being retried. + */ + retryImmediately() { + if (!this.connectionReturnedDefer) { + return false; + } + this.startKeepAlives(0); + return true; + } + /** + * Process a single set of cached sync data. + * @param savedSync - a saved sync that was persisted by a store. This + * should have been acquired via client.store.getSavedSync(). + */ + syncFromCache(savedSync) { + return __awaiter(this, void 0, void 0, function* () { + debuglog("sync(): not doing HTTP hit, instead returning stored /sync data"); + const nextSyncToken = savedSync.nextBatch; + // Set sync token for future incremental syncing + this.client.store.setSyncToken(nextSyncToken); + // No previous sync, set old token to null + const syncEventData = { + nextSyncToken, + catchingUp: false, + fromCache: true, + }; + const data = { + next_batch: nextSyncToken, + rooms: savedSync.roomsData, + account_data: { + events: savedSync.accountData, + }, + }; + try { + yield this.processSyncResponse(syncEventData, data); + } + catch (e) { + logger_1.logger.error("Error processing cached sync", e); + } + // Don't emit a prepared if we've bailed because the store is invalid: + // in this case the client will not be usable until stopped & restarted + // so this would be useless and misleading. + if (!this.storeIsInvalid) { + this.updateSyncState(SyncState.Prepared, syncEventData); + } + }); + } + /** + * Invoke me to do /sync calls + */ + doSync(syncOptions) { + return __awaiter(this, void 0, void 0, function* () { + while (this.running) { + const syncToken = this.client.store.getSyncToken(); + let data; + try { + if (!this.currentSyncRequest) { + this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken); + } + data = yield this.currentSyncRequest; + } + catch (e) { + const abort = yield this.onSyncError(e); + if (abort) + return; + continue; + } + finally { + this.currentSyncRequest = undefined; + } + // set the sync token NOW *before* processing the events. We do this so + // if something barfs on an event we can skip it rather than constantly + // polling with the same token. + this.client.store.setSyncToken(data.next_batch); + // Reset after a successful sync + this.failedSyncCount = 0; + yield this.client.store.setSyncData(data); + const syncEventData = { + oldSyncToken: syncToken !== null && syncToken !== void 0 ? syncToken : undefined, + nextSyncToken: data.next_batch, + catchingUp: this.catchingUp, + }; + if (this.syncOpts.crypto) { + // tell the crypto module we're about to process a sync + // response + yield this.syncOpts.crypto.onSyncWillProcess(syncEventData); + } + try { + yield this.processSyncResponse(syncEventData, data); + } + catch (e) { + // log the exception with stack if we have it, else fall back + // to the plain description + logger_1.logger.error("Caught /sync error", e); + // Emit the exception for client handling + this.client.emit(client_1.ClientEvent.SyncUnexpectedError, e); + } + // update this as it may have changed + syncEventData.catchingUp = this.catchingUp; + // emit synced events + if (!syncOptions.hasSyncedBefore) { + this.updateSyncState(SyncState.Prepared, syncEventData); + syncOptions.hasSyncedBefore = true; + } + // tell the crypto module to do its processing. It may block (to do a + // /keys/changes request). + if (this.syncOpts.cryptoCallbacks) { + yield this.syncOpts.cryptoCallbacks.onSyncCompleted(syncEventData); + } + // keep emitting SYNCING -> SYNCING for clients who want to do bulk updates + this.updateSyncState(SyncState.Syncing, syncEventData); + if (this.client.store.wantsSave()) { + // We always save the device list (if it's dirty) before saving the sync data: + // this means we know the saved device list data is at least as fresh as the + // stored sync data which means we don't have to worry that we may have missed + // device changes. We can also skip the delay since we're not calling this very + // frequently (and we don't really want to delay the sync for it). + if (this.syncOpts.crypto) { + yield this.syncOpts.crypto.saveDeviceList(0); + } + // tell databases that everything is now in a consistent state and can be saved. + this.client.store.save(); + } + } + if (!this.running) { + debuglog("Sync no longer running: exiting."); + if (this.connectionReturnedDefer) { + this.connectionReturnedDefer.reject(); + this.connectionReturnedDefer = undefined; + } + this.updateSyncState(SyncState.Stopped); + } + }); + } + doSyncRequest(syncOptions, syncToken) { + var _a; + const qps = this.getSyncParams(syncOptions, syncToken); + return this.client.http.authedRequest(http_api_1.Method.Get, "/sync", qps, undefined, { + localTimeoutMs: qps.timeout + BUFFER_PERIOD_MS, + abortSignal: (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal, + }); + } + getSyncParams(syncOptions, syncToken) { + let timeout = this.opts.pollTimeout; + if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) { + // unless we are happily syncing already, we want the server to return + // as quickly as possible, even if there are no events queued. This + // serves two purposes: + // + // * When the connection dies, we want to know asap when it comes back, + // so that we can hide the error from the user. (We don't want to + // have to wait for an event or a timeout). + // + // * We want to know if the server has any to_device messages queued up + // for us. We do that by calling it with a zero timeout until it + // doesn't give us any more to_device messages. + this.catchingUp = true; + timeout = 0; + } + let filter = syncOptions.filter; + if (this.client.isGuest() && !filter) { + filter = this.getGuestFilter(); + } + const qps = { filter, timeout }; + if (this.opts.disablePresence) { + qps.set_presence = SetPresence.Offline; + } + if (syncToken) { + qps.since = syncToken; + } + else { + // use a cachebuster for initialsyncs, to make sure that + // we don't get a stale sync + // (https://github.com/vector-im/vector-web/issues/1354) + qps._cacheBuster = Date.now(); + } + if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState())) { + // we think the connection is dead. If it comes back up, we won't know + // about it till /sync returns. If the timeout= is high, this could + // be a long time. Set it to 0 when doing retries so we don't have to wait + // for an event or a timeout before emiting the SYNCING event. + qps.timeout = 0; + } + return qps; + } + onSyncError(err) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.running) { + debuglog("Sync no longer running: exiting"); + if (this.connectionReturnedDefer) { + this.connectionReturnedDefer.reject(); + this.connectionReturnedDefer = undefined; + } + this.updateSyncState(SyncState.Stopped); + return true; // abort + } + logger_1.logger.error("/sync error %s", err); + if (this.shouldAbortSync(err)) { + return true; // abort + } + this.failedSyncCount++; + logger_1.logger.log("Number of consecutive failed sync requests:", this.failedSyncCount); + debuglog("Starting keep-alive"); + // Note that we do *not* mark the sync connection as + // lost yet: we only do this if a keepalive poke + // fails, since long lived HTTP connections will + // go away sometimes and we shouldn't treat this as + // erroneous. We set the state to 'reconnecting' + // instead, so that clients can observe this state + // if they wish. + const keepAlivePromise = this.startKeepAlives(); + this.currentSyncRequest = undefined; + // Transition from RECONNECTING to ERROR after a given number of failed syncs + this.updateSyncState(this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? SyncState.Error : SyncState.Reconnecting, { error: err }); + const connDidFail = yield keepAlivePromise; + // Only emit CATCHUP if we detected a connectivity error: if we didn't, + // it's quite likely the sync will fail again for the same reason and we + // want to stay in ERROR rather than keep flip-flopping between ERROR + // and CATCHUP. + if (connDidFail && this.getSyncState() === SyncState.Error) { + this.updateSyncState(SyncState.Catchup, { + catchingUp: true, + }); + } + return false; + }); + } + /** + * Process data returned from a sync response and propagate it + * into the model objects + * + * @param syncEventData - Object containing sync tokens associated with this sync + * @param data - The response from /sync + */ + processSyncResponse(syncEventData, data) { + var _a, _b, _c, _d; + return __awaiter(this, void 0, void 0, function* () { + const client = this.client; + // data looks like: + // { + // next_batch: $token, + // presence: { events: [] }, + // account_data: { events: [] }, + // device_lists: { changed: ["@user:server", ... ]}, + // to_device: { events: [] }, + // device_one_time_keys_count: { signed_curve25519: 42 }, + // rooms: { + // invite: { + // $roomid: { + // invite_state: { events: [] } + // } + // }, + // join: { + // $roomid: { + // state: { events: [] }, + // timeline: { events: [], prev_batch: $token, limited: true }, + // ephemeral: { events: [] }, + // summary: { + // m.heroes: [ $user_id ], + // m.joined_member_count: $count, + // m.invited_member_count: $count + // }, + // account_data: { events: [] }, + // unread_notifications: { + // highlight_count: 0, + // notification_count: 0, + // } + // } + // }, + // leave: { + // $roomid: { + // state: { events: [] }, + // timeline: { events: [], prev_batch: $token } + // } + // } + // } + // } + // TODO-arch: + // - Each event we pass through needs to be emitted via 'event', can we + // do this in one place? + // - The isBrandNewRoom boilerplate is boilerplatey. + // handle presence events (User objects) + if (Array.isArray((_a = data.presence) === null || _a === void 0 ? void 0 : _a.events)) { + data.presence.events.filter(utils_1.noUnsafeEventProps) + .map(client.getEventMapper()) + .forEach(function (presenceEvent) { + let user = client.store.getUser(presenceEvent.getSender()); + if (user) { + user.setPresenceEvent(presenceEvent); + } + else { + user = createNewUser(client, presenceEvent.getSender()); + user.setPresenceEvent(presenceEvent); + client.store.storeUser(user); + } + client.emit(client_1.ClientEvent.Event, presenceEvent); + }); + } + // handle non-room account_data + if (Array.isArray((_b = data.account_data) === null || _b === void 0 ? void 0 : _b.events)) { + const events = data.account_data.events.filter(utils_1.noUnsafeEventProps).map(client.getEventMapper()); + const prevEventsMap = events.reduce((m, c) => { + m[c.getType()] = client.store.getAccountData(c.getType()); + return m; + }, {}); + client.store.storeAccountDataEvents(events); + events.forEach(function (accountDataEvent) { + // Honour push rules that come down the sync stream but also + // honour push rules that were previously cached. Base rules + // will be updated when we receive push rules via getPushRules + // (see sync) before syncing over the network. + if (accountDataEvent.getType() === event_1.EventType.PushRules) { + const rules = accountDataEvent.getContent(); + client.setPushRules(rules); + } + const prevEvent = prevEventsMap[accountDataEvent.getType()]; + client.emit(client_1.ClientEvent.AccountData, accountDataEvent, prevEvent); + return accountDataEvent; + }); + } + // handle to-device events + if (data.to_device && Array.isArray(data.to_device.events) && data.to_device.events.length > 0) { + let toDeviceMessages = data.to_device.events.filter(utils_1.noUnsafeEventProps); + if (this.syncOpts.cryptoCallbacks) { + toDeviceMessages = yield this.syncOpts.cryptoCallbacks.preprocessToDeviceMessages(toDeviceMessages); + } + const cancelledKeyVerificationTxns = []; + toDeviceMessages + .map(client.getEventMapper({ toDevice: true })) + .map((toDeviceEvent) => { + // map is a cheap inline forEach + // We want to flag m.key.verification.start events as cancelled + // if there's an accompanying m.key.verification.cancel event, so + // we pull out the transaction IDs from the cancellation events + // so we can flag the verification events as cancelled in the loop + // below. + if (toDeviceEvent.getType() === "m.key.verification.cancel") { + const txnId = toDeviceEvent.getContent()["transaction_id"]; + if (txnId) { + cancelledKeyVerificationTxns.push(txnId); + } + } + // as mentioned above, .map is a cheap inline forEach, so return + // the unmodified event. + return toDeviceEvent; + }) + .forEach(function (toDeviceEvent) { + const content = toDeviceEvent.getContent(); + if (toDeviceEvent.getType() == "m.room.message" && content.msgtype == "m.bad.encrypted") { + // the mapper already logged a warning. + logger_1.logger.log("Ignoring undecryptable to-device event from " + toDeviceEvent.getSender()); + return; + } + if (toDeviceEvent.getType() === "m.key.verification.start" || + toDeviceEvent.getType() === "m.key.verification.request") { + const txnId = content["transaction_id"]; + if (cancelledKeyVerificationTxns.includes(txnId)) { + toDeviceEvent.flagCancelled(); + } + } + client.emit(client_1.ClientEvent.ToDeviceEvent, toDeviceEvent); + }); + } + else { + // no more to-device events: we can stop polling with a short timeout. + this.catchingUp = false; + } + // the returned json structure is a bit crap, so make it into a + // nicer form (array) after applying sanity to make sure we don't fail + // on missing keys (on the off chance) + let inviteRooms = []; + let joinRooms = []; + let leaveRooms = []; + if (data.rooms) { + if (data.rooms.invite) { + inviteRooms = this.mapSyncResponseToRoomArray(data.rooms.invite); + } + if (data.rooms.join) { + joinRooms = this.mapSyncResponseToRoomArray(data.rooms.join); + } + if (data.rooms.leave) { + leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave); + } + } + this.notifEvents = []; + // Handle invites + yield utils.promiseMapSeries(inviteRooms, (inviteObj) => __awaiter(this, void 0, void 0, function* () { + var _e; + const room = inviteObj.room; + const stateEvents = this.mapSyncEventsFormat(inviteObj.invite_state, room); + yield this.injectRoomEvents(room, stateEvents); + const inviter = (_e = room.currentState.getStateEvents(event_1.EventType.RoomMember, client.getUserId())) === null || _e === void 0 ? void 0 : _e.getSender(); + const crypto = client.crypto; + if (crypto) { + const parkedHistory = yield crypto.cryptoStore.takeParkedSharedHistory(room.roomId); + for (const parked of parkedHistory) { + if (parked.senderId === inviter) { + yield crypto.olmDevice.addInboundGroupSession(room.roomId, parked.senderKey, parked.forwardingCurve25519KeyChain, parked.sessionId, parked.sessionKey, parked.keysClaimed, true, { sharedHistory: true, untrusted: true }); + } + } + } + if (inviteObj.isBrandNewRoom) { + room.recalculate(); + client.store.storeRoom(room); + client.emit(client_1.ClientEvent.Room, room); + } + else { + // Update room state for invite->reject->invite cycles + room.recalculate(); + } + stateEvents.forEach(function (e) { + client.emit(client_1.ClientEvent.Event, e); + }); + })); + // Handle joins + yield utils.promiseMapSeries(joinRooms, (joinObj) => __awaiter(this, void 0, void 0, function* () { + var _f, _g, _h, _j, _k, _l; + const room = joinObj.room; + const stateEvents = this.mapSyncEventsFormat(joinObj.state, room); + // Prevent events from being decrypted ahead of time + // this helps large account to speed up faster + // room::decryptCriticalEvent is in charge of decrypting all the events + // required for a client to function properly + const events = this.mapSyncEventsFormat(joinObj.timeline, room, false); + const ephemeralEvents = this.mapSyncEventsFormat(joinObj.ephemeral); + const accountDataEvents = this.mapSyncEventsFormat(joinObj.account_data); + const encrypted = client.isRoomEncrypted(room.roomId); + // We store the server-provided value first so it's correct when any of the events fire. + if (joinObj.unread_notifications) { + /** + * We track unread notifications ourselves in encrypted rooms, so don't + * bother setting it here. We trust our calculations better than the + * server's for this case, and therefore will assume that our non-zero + * count is accurate. + * + * @see import("./client").fixNotificationCountOnDecryption + */ + if (!encrypted || joinObj.unread_notifications.notification_count === 0) { + // In an encrypted room, if the room has notifications enabled then it's typical for + // the server to flag all new messages as notifying. However, some push rules calculate + // events as ignored based on their event contents (e.g. ignoring msgtype=m.notice messages) + // so we want to calculate this figure on the client in all cases. + room.setUnreadNotificationCount(room_1.NotificationCountType.Total, (_f = joinObj.unread_notifications.notification_count) !== null && _f !== void 0 ? _f : 0); + } + if (!encrypted || room.getUnreadNotificationCount(room_1.NotificationCountType.Highlight) <= 0) { + // If the locally stored highlight count is zero, use the server provided value. + room.setUnreadNotificationCount(room_1.NotificationCountType.Highlight, (_g = joinObj.unread_notifications.highlight_count) !== null && _g !== void 0 ? _g : 0); + } + } + const unreadThreadNotifications = (_h = joinObj[sync_1.UNREAD_THREAD_NOTIFICATIONS.name]) !== null && _h !== void 0 ? _h : joinObj[sync_1.UNREAD_THREAD_NOTIFICATIONS.altName]; + if (unreadThreadNotifications) { + // Only partially reset unread notification + // We want to keep the client-generated count. Particularly important + // for encrypted room that refresh their notification count on event + // decryption + room.resetThreadUnreadNotificationCount(Object.keys(unreadThreadNotifications)); + for (const [threadId, unreadNotification] of Object.entries(unreadThreadNotifications)) { + if (!encrypted || unreadNotification.notification_count === 0) { + room.setThreadUnreadNotificationCount(threadId, room_1.NotificationCountType.Total, (_j = unreadNotification.notification_count) !== null && _j !== void 0 ? _j : 0); + } + const hasNoNotifications = room.getThreadUnreadNotificationCount(threadId, room_1.NotificationCountType.Highlight) <= 0; + if (!encrypted || (encrypted && hasNoNotifications)) { + room.setThreadUnreadNotificationCount(threadId, room_1.NotificationCountType.Highlight, (_k = unreadNotification.highlight_count) !== null && _k !== void 0 ? _k : 0); + } + } + } + else { + room.resetThreadUnreadNotificationCount(); + } + joinObj.timeline = joinObj.timeline || {}; + if (joinObj.isBrandNewRoom) { + // set the back-pagination token. Do this *before* adding any + // events so that clients can start back-paginating. + if (joinObj.timeline.prev_batch !== null) { + room.getLiveTimeline().setPaginationToken(joinObj.timeline.prev_batch, event_timeline_1.EventTimeline.BACKWARDS); + } + } + else if (joinObj.timeline.limited) { + let limited = true; + // we've got a limited sync, so we *probably* have a gap in the + // timeline, so should reset. But we might have been peeking or + // paginating and already have some of the events, in which + // case we just want to append any subsequent events to the end + // of the existing timeline. + // + // This is particularly important in the case that we already have + // *all* of the events in the timeline - in that case, if we reset + // the timeline, we'll end up with an entirely empty timeline, + // which we'll try to paginate but not get any new events (which + // will stop us linking the empty timeline into the chain). + // + for (let i = events.length - 1; i >= 0; i--) { + const eventId = events[i].getId(); + if (room.getTimelineForEvent(eventId)) { + debuglog(`Already have event ${eventId} in limited sync - not resetting`); + limited = false; + // we might still be missing some of the events before i; + // we don't want to be adding them to the end of the + // timeline because that would put them out of order. + events.splice(0, i); + // XXX: there's a problem here if the skipped part of the + // timeline modifies the state set in stateEvents, because + // we'll end up using the state from stateEvents rather + // than the later state from timelineEvents. We probably + // need to wind stateEvents forward over the events we're + // skipping. + break; + } + } + if (limited) { + room.resetLiveTimeline(joinObj.timeline.prev_batch, this.syncOpts.canResetEntireTimeline(room.roomId) ? null : (_l = syncEventData.oldSyncToken) !== null && _l !== void 0 ? _l : null); + // We have to assume any gap in any timeline is + // reason to stop incrementally tracking notifications and + // reset the timeline. + client.resetNotifTimelineSet(); + } + } + // process any crypto events *before* emitting the RoomStateEvent events. This + // avoids a race condition if the application tries to send a message after the + // state event is processed, but before crypto is enabled, which then causes the + // crypto layer to complain. + if (this.syncOpts.cryptoCallbacks) { + for (const e of stateEvents.concat(events)) { + if (e.isState() && e.getType() === event_1.EventType.RoomEncryption && e.getStateKey() === "") { + yield this.syncOpts.cryptoCallbacks.onCryptoEvent(room, e); + } + } + } + try { + yield this.injectRoomEvents(room, stateEvents, events, syncEventData.fromCache); + } + catch (e) { + logger_1.logger.error(`Failed to process events on room ${room.roomId}:`, e); + } + // set summary after processing events, + // because it will trigger a name calculation + // which needs the room state to be up to date + if (joinObj.summary) { + room.setSummary(joinObj.summary); + } + // we deliberately don't add ephemeral events to the timeline + room.addEphemeralEvents(ephemeralEvents); + // we deliberately don't add accountData to the timeline + room.addAccountData(accountDataEvents); + room.recalculate(); + if (joinObj.isBrandNewRoom) { + client.store.storeRoom(room); + client.emit(client_1.ClientEvent.Room, room); + } + this.processEventsForNotifs(room, events); + const emitEvent = (e) => client.emit(client_1.ClientEvent.Event, e); + stateEvents.forEach(emitEvent); + events.forEach(emitEvent); + ephemeralEvents.forEach(emitEvent); + accountDataEvents.forEach(emitEvent); + // Decrypt only the last message in all rooms to make sure we can generate a preview + // And decrypt all events after the recorded read receipt to ensure an accurate + // notification count + room.decryptCriticalEvents(); + })); + // Handle leaves (e.g. kicked rooms) + yield utils.promiseMapSeries(leaveRooms, (leaveObj) => __awaiter(this, void 0, void 0, function* () { + const room = leaveObj.room; + const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room); + const events = this.mapSyncEventsFormat(leaveObj.timeline, room); + const accountDataEvents = this.mapSyncEventsFormat(leaveObj.account_data); + yield this.injectRoomEvents(room, stateEvents, events); + room.addAccountData(accountDataEvents); + room.recalculate(); + if (leaveObj.isBrandNewRoom) { + client.store.storeRoom(room); + client.emit(client_1.ClientEvent.Room, room); + } + this.processEventsForNotifs(room, events); + stateEvents.forEach(function (e) { + client.emit(client_1.ClientEvent.Event, e); + }); + events.forEach(function (e) { + client.emit(client_1.ClientEvent.Event, e); + }); + accountDataEvents.forEach(function (e) { + client.emit(client_1.ClientEvent.Event, e); + }); + })); + // update the notification timeline, if appropriate. + // we only do this for live events, as otherwise we can't order them sanely + // in the timeline relative to ones paginated in by /notifications. + // XXX: we could fix this by making EventTimeline support chronological + // ordering... but it doesn't, right now. + if (syncEventData.oldSyncToken && this.notifEvents.length) { + this.notifEvents.sort(function (a, b) { + return a.getTs() - b.getTs(); + }); + this.notifEvents.forEach(function (event) { + var _a; + (_a = client.getNotifTimelineSet()) === null || _a === void 0 ? void 0 : _a.addLiveEvent(event); + }); + } + // Handle device list updates + if (data.device_lists) { + if (this.syncOpts.crypto) { + yield this.syncOpts.crypto.handleDeviceListChanges(syncEventData, data.device_lists); + } + else { + // FIXME if we *don't* have a crypto module, we still need to + // invalidate the device lists. But that would require a + // substantial bit of rework :/. + } + } + // Handle one_time_keys_count + if (data.device_one_time_keys_count) { + const map = new Map(Object.entries(data.device_one_time_keys_count)); + (_c = this.syncOpts.cryptoCallbacks) === null || _c === void 0 ? void 0 : _c.preprocessOneTimeKeyCounts(map); + } + if (data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"]) { + // The presence of device_unused_fallback_key_types indicates that the + // server supports fallback keys. If there's no unused + // signed_curve25519 fallback key we need a new one. + const unusedFallbackKeys = data.device_unused_fallback_key_types || data["org.matrix.msc2732.device_unused_fallback_key_types"]; + (_d = this.syncOpts.cryptoCallbacks) === null || _d === void 0 ? void 0 : _d.preprocessUnusedFallbackKeys(new Set(unusedFallbackKeys || null)); + } + }); + } + /** + * Starts polling the connectivity check endpoint + * @param delay - How long to delay until the first poll. + * defaults to a short, randomised interval (to prevent + * tight-looping if /versions succeeds but /sync etc. fail). + * @returns which resolves once the connection returns + */ + startKeepAlives(delay) { + if (delay === undefined) { + delay = 2000 + Math.floor(Math.random() * 5000); + } + if (this.keepAliveTimer !== null) { + clearTimeout(this.keepAliveTimer); + } + if (delay > 0) { + this.keepAliveTimer = setTimeout(this.pokeKeepAlive.bind(this), delay); + } + else { + this.pokeKeepAlive(); + } + if (!this.connectionReturnedDefer) { + this.connectionReturnedDefer = utils.defer(); + } + return this.connectionReturnedDefer.promise; + } + /** + * Make a dummy call to /_matrix/client/versions, to see if the HS is + * reachable. + * + * On failure, schedules a call back to itself. On success, resolves + * this.connectionReturnedDefer. + * + * @param connDidFail - True if a connectivity failure has been detected. Optional. + */ + pokeKeepAlive(connDidFail = false) { + var _a; + const success = () => { + clearTimeout(this.keepAliveTimer); + if (this.connectionReturnedDefer) { + this.connectionReturnedDefer.resolve(connDidFail); + this.connectionReturnedDefer = undefined; + } + }; + this.client.http + .request(http_api_1.Method.Get, "/_matrix/client/versions", undefined, // queryParams + undefined, // data + { + prefix: "", + localTimeoutMs: 15 * 1000, + abortSignal: (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal, + }) + .then(() => { + success(); + }, (err) => { + if (err.httpStatus == 400 || err.httpStatus == 404) { + // treat this as a success because the server probably just doesn't + // support /versions: point is, we're getting a response. + // We wait a short time though, just in case somehow the server + // is in a mode where it 400s /versions responses and sync etc. + // responses fail, this will mean we don't hammer in a loop. + this.keepAliveTimer = setTimeout(success, 2000); + } + else { + connDidFail = true; + this.keepAliveTimer = setTimeout(this.pokeKeepAlive.bind(this, connDidFail), 5000 + Math.floor(Math.random() * 5000)); + // A keepalive has failed, so we emit the + // error state (whether or not this is the + // first failure). + // Note we do this after setting the timer: + // this lets the unit tests advance the mock + // clock when they get the error. + this.updateSyncState(SyncState.Error, { error: err }); + } + }); + } + mapSyncResponseToRoomArray(obj) { + // Maps { roomid: {stuff}, roomid: {stuff} } + // to + // [{stuff+Room+isBrandNewRoom}, {stuff+Room+isBrandNewRoom}] + const client = this.client; + return Object.keys(obj) + .filter((k) => !(0, utils_1.unsafeProp)(k)) + .map((roomId) => { + const arrObj = obj[roomId]; + let room = client.store.getRoom(roomId); + let isBrandNewRoom = false; + if (!room) { + room = this.createRoom(roomId); + isBrandNewRoom = true; + } + arrObj.room = room; + arrObj.isBrandNewRoom = isBrandNewRoom; + return arrObj; + }); + } + mapSyncEventsFormat(obj, room, decrypt = true) { + if (!obj || !Array.isArray(obj.events)) { + return []; + } + const mapper = this.client.getEventMapper({ decrypt }); + return obj.events.filter(utils_1.noUnsafeEventProps).map(function (e) { + if (room) { + e.room_id = room.roomId; + } + return mapper(e); + }); + } + /** + */ + resolveInvites(room) { + if (!room || !this.opts.resolveInvitesToProfiles) { + return; + } + const client = this.client; + // For each invited room member we want to give them a displayname/avatar url + // if they have one (the m.room.member invites don't contain this). + room.getMembersWithMembership("invite").forEach(function (member) { + if (member.requestedProfileInfo) + return; + member.requestedProfileInfo = true; + // try to get a cached copy first. + const user = client.getUser(member.userId); + let promise; + if (user) { + promise = Promise.resolve({ + avatar_url: user.avatarUrl, + displayname: user.displayName, + }); + } + else { + promise = client.getProfileInfo(member.userId); + } + promise.then(function (info) { + // slightly naughty by doctoring the invite event but this means all + // the code paths remain the same between invite/join display name stuff + // which is a worthy trade-off for some minor pollution. + const inviteEvent = member.events.member; + if ((inviteEvent === null || inviteEvent === void 0 ? void 0 : inviteEvent.getContent().membership) !== "invite") { + // between resolving and now they have since joined, so don't clobber + return; + } + inviteEvent.getContent().avatar_url = info.avatar_url; + inviteEvent.getContent().displayname = info.displayname; + // fire listeners + member.setMembershipEvent(inviteEvent, room.currentState); + }, function (err) { + // OH WELL. + }); + }); + } + /** + * Injects events into a room's model. + * @param stateEventList - A list of state events. This is the state + * at the *START* of the timeline list if it is supplied. + * @param timelineEventList - A list of timeline events, including threaded. Lower index + * is earlier in time. Higher index is later. + * @param fromCache - whether the sync response came from cache + */ + injectRoomEvents(room, stateEventList, timelineEventList, fromCache = false) { + return __awaiter(this, void 0, void 0, function* () { + // If there are no events in the timeline yet, initialise it with + // the given state events + const liveTimeline = room.getLiveTimeline(); + const timelineWasEmpty = liveTimeline.getEvents().length == 0; + if (timelineWasEmpty) { + // Passing these events into initialiseState will freeze them, so we need + // to compute and cache the push actions for them now, otherwise sync dies + // with an attempt to assign to read only property. + // XXX: This is pretty horrible and is assuming all sorts of behaviour from + // these functions that it shouldn't be. We should probably either store the + // push actions cache elsewhere so we can freeze MatrixEvents, or otherwise + // find some solution where MatrixEvents are immutable but allow for a cache + // field. + for (const ev of stateEventList) { + this.client.getPushActionsForEvent(ev); + } + liveTimeline.initialiseState(stateEventList, { + timelineWasEmpty, + }); + } + this.resolveInvites(room); + // recalculate the room name at this point as adding events to the timeline + // may make notifications appear which should have the right name. + // XXX: This looks suspect: we'll end up recalculating the room once here + // and then again after adding events (processSyncResponse calls it after + // calling us) even if no state events were added. It also means that if + // one of the room events in timelineEventList is something that needs + // a recalculation (like m.room.name) we won't recalculate until we've + // finished adding all the events, which will cause the notification to have + // the old room name rather than the new one. + room.recalculate(); + // If the timeline wasn't empty, we process the state events here: they're + // defined as updates to the state before the start of the timeline, so this + // starts to roll the state forward. + // XXX: That's what we *should* do, but this can happen if we were previously + // peeking in a room, in which case we obviously do *not* want to add the + // state events here onto the end of the timeline. Historically, the js-sdk + // has just set these new state events on the old and new state. This seems + // very wrong because there could be events in the timeline that diverge the + // state, in which case this is going to leave things out of sync. However, + // for now I think it;s best to behave the same as the code has done previously. + if (!timelineWasEmpty) { + // XXX: As above, don't do this... + //room.addLiveEvents(stateEventList || []); + // Do this instead... + room.oldState.setStateEvents(stateEventList || []); + room.currentState.setStateEvents(stateEventList || []); + } + // Execute the timeline events. This will continue to diverge the current state + // if the timeline has any state events in it. + // This also needs to be done before running push rules on the events as they need + // to be decorated with sender etc. + room.addLiveEvents(timelineEventList || [], { + fromCache, + timelineWasEmpty, + }); + this.client.processBeaconEvents(room, timelineEventList); + }); + } + /** + * Takes a list of timelineEvents and adds and adds to notifEvents + * as appropriate. + * This must be called after the room the events belong to has been stored. + * + * @param timelineEventList - A list of timeline events. Lower index + * is earlier in time. Higher index is later. + */ + processEventsForNotifs(room, timelineEventList) { + var _a; + // gather our notifications into this.notifEvents + if (this.client.getNotifTimelineSet()) { + for (const event of timelineEventList) { + const pushActions = this.client.getPushActionsForEvent(event); + if ((pushActions === null || pushActions === void 0 ? void 0 : pushActions.notify) && ((_a = pushActions.tweaks) === null || _a === void 0 ? void 0 : _a.highlight)) { + this.notifEvents.push(event); + } + } + } + } + getGuestFilter() { + // Dev note: This used to be conditional to return a filter of 20 events maximum, but + // the condition never went to the other branch. This is now hardcoded. + return "{}"; + } + /** + * Sets the sync state and emits an event to say so + * @param newState - The new state string + * @param data - Object of additional data to emit in the event + */ + updateSyncState(newState, data) { + const old = this.syncState; + this.syncState = newState; + this.syncStateData = data; + this.client.emit(client_1.ClientEvent.Sync, this.syncState, old, data); + } +} +exports.SyncApi = SyncApi; +function createNewUser(client, userId) { + const user = new user_1.User(userId); + client.reEmitter.reEmit(user, [ + user_1.UserEvent.AvatarUrl, + user_1.UserEvent.DisplayName, + user_1.UserEvent.Presence, + user_1.UserEvent.CurrentlyActive, + user_1.UserEvent.LastPresenceTs, + ]); + return user; +} +// /!\ This function is not intended for public use! It's only exported from +// here in order to share some common logic with sliding-sync-sdk.ts. +function _createAndReEmitRoom(client, roomId, opts) { + const { timelineSupport } = client; + const room = new room_1.Room(roomId, client, client.getUserId(), { + lazyLoadMembers: opts.lazyLoadMembers, + pendingEventOrdering: opts.pendingEventOrdering, + timelineSupport, + }); + client.reEmitter.reEmit(room, [ + room_1.RoomEvent.Name, + room_1.RoomEvent.Redaction, + room_1.RoomEvent.RedactionCancelled, + room_1.RoomEvent.Receipt, + room_1.RoomEvent.Tags, + room_1.RoomEvent.LocalEchoUpdated, + room_1.RoomEvent.AccountData, + room_1.RoomEvent.MyMembership, + room_1.RoomEvent.Timeline, + room_1.RoomEvent.TimelineReset, + room_state_1.RoomStateEvent.Events, + room_state_1.RoomStateEvent.Members, + room_state_1.RoomStateEvent.NewMember, + room_state_1.RoomStateEvent.Update, + beacon_1.BeaconEvent.New, + beacon_1.BeaconEvent.Update, + beacon_1.BeaconEvent.Destroy, + beacon_1.BeaconEvent.LivenessChange, + ]); + // We need to add a listener for RoomState.members in order to hook them + // correctly. + room.on(room_state_1.RoomStateEvent.NewMember, (event, state, member) => { + var _a; + member.user = (_a = client.getUser(member.userId)) !== null && _a !== void 0 ? _a : undefined; + client.reEmitter.reEmit(member, [ + room_member_1.RoomMemberEvent.Name, + room_member_1.RoomMemberEvent.Typing, + room_member_1.RoomMemberEvent.PowerLevel, + room_member_1.RoomMemberEvent.Membership, + ]); + }); + return room; +} +exports._createAndReEmitRoom = _createAndReEmitRoom; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"./@types/event":306,"./@types/sync":314,"./client":321,"./errors":359,"./feature":362,"./filter":364,"./http-api":367,"./logger":374,"./models/beacon":378,"./models/event-timeline":382,"./models/room":392,"./models/room-member":389,"./models/room-state":390,"./models/user":396,"./utils":416}],415:[function(require,module,exports){ +"use strict"; +/* +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TimelineIndex = exports.TimelineWindow = void 0; +const event_timeline_1 = require("./models/event-timeline"); +const logger_1 = require("./logger"); +/** + * @internal + */ +const DEBUG = false; +/** + * @internal + */ +/* istanbul ignore next */ +const debuglog = DEBUG ? logger_1.logger.log.bind(logger_1.logger) : function () { }; +/** + * the number of times we ask the server for more events before giving up + * + * @internal + */ +const DEFAULT_PAGINATE_LOOP_LIMIT = 5; +class TimelineWindow { + /** + * Construct a TimelineWindow. + * + *

This abstracts the separate timelines in a Matrix {@link Room} into a single iterable thing. + * It keeps track of the start and endpoints of the window, which can be advanced with the help + * of pagination requests. + * + *

Before the window is useful, it must be initialised by calling {@link TimelineWindow#load}. + * + *

Note that the window will not automatically extend itself when new events + * are received from /sync; you should arrange to call {@link TimelineWindow#paginate} + * on {@link RoomEvent.Timeline} events. + * + * @param client - MatrixClient to be used for context/pagination + * requests. + * + * @param timelineSet - The timelineSet to track + * + * @param opts - Configuration options for this window + */ + constructor(client, timelineSet, opts = {}) { + this.client = client; + this.timelineSet = timelineSet; + this.eventCount = 0; + this.windowLimit = opts.windowLimit || 1000; + } + /** + * Initialise the window to point at a given event, or the live timeline + * + * @param initialEventId - If given, the window will contain the + * given event + * @param initialWindowSize - Size of the initial window + */ + load(initialEventId, initialWindowSize = 20) { + // given an EventTimeline, find the event we were looking for, and initialise our + // fields so that the event in question is in the middle of the window. + const initFields = (timeline) => { + if (!timeline) { + throw new Error("No timeline given to initFields"); + } + let eventIndex; + const events = timeline.getEvents(); + if (!initialEventId) { + // we were looking for the live timeline: initialise to the end + eventIndex = events.length; + } + else { + eventIndex = events.findIndex((e) => e.getId() === initialEventId); + if (eventIndex < 0) { + throw new Error("getEventTimeline result didn't include requested event"); + } + } + const endIndex = Math.min(events.length, eventIndex + Math.ceil(initialWindowSize / 2)); + const startIndex = Math.max(0, endIndex - initialWindowSize); + this.start = new TimelineIndex(timeline, startIndex - timeline.getBaseIndex()); + this.end = new TimelineIndex(timeline, endIndex - timeline.getBaseIndex()); + this.eventCount = endIndex - startIndex; + }; + // We avoid delaying the resolution of the promise by a reactor tick if we already have the data we need, + // which is important to keep room-switching feeling snappy. + if (this.timelineSet.getTimelineForEvent(initialEventId)) { + initFields(this.timelineSet.getTimelineForEvent(initialEventId)); + return Promise.resolve(); + } + else if (initialEventId) { + return this.client.getEventTimeline(this.timelineSet, initialEventId).then(initFields); + } + else { + initFields(this.timelineSet.getLiveTimeline()); + return Promise.resolve(); + } + } + /** + * Get the TimelineIndex of the window in the given direction. + * + * @param direction - EventTimeline.BACKWARDS to get the TimelineIndex + * at the start of the window; EventTimeline.FORWARDS to get the TimelineIndex at + * the end. + * + * @returns The requested timeline index if one exists, null + * otherwise. + */ + getTimelineIndex(direction) { + var _a, _b; + if (direction == event_timeline_1.EventTimeline.BACKWARDS) { + return (_a = this.start) !== null && _a !== void 0 ? _a : null; + } + else if (direction == event_timeline_1.EventTimeline.FORWARDS) { + return (_b = this.end) !== null && _b !== void 0 ? _b : null; + } + else { + throw new Error("Invalid direction '" + direction + "'"); + } + } + /** + * Try to extend the window using events that are already in the underlying + * TimelineIndex. + * + * @param direction - EventTimeline.BACKWARDS to try extending it + * backwards; EventTimeline.FORWARDS to try extending it forwards. + * @param size - number of events to try to extend by. + * + * @returns true if the window was extended, false otherwise. + */ + extend(direction, size) { + const tl = this.getTimelineIndex(direction); + if (!tl) { + debuglog("TimelineWindow: no timeline yet"); + return false; + } + const count = direction == event_timeline_1.EventTimeline.BACKWARDS ? tl.retreat(size) : tl.advance(size); + if (count) { + this.eventCount += count; + debuglog("TimelineWindow: increased cap by " + count + " (now " + this.eventCount + ")"); + // remove some events from the other end, if necessary + const excess = this.eventCount - this.windowLimit; + if (excess > 0) { + this.unpaginate(excess, direction != event_timeline_1.EventTimeline.BACKWARDS); + } + return true; + } + return false; + } + /** + * Check if this window can be extended + * + *

This returns true if we either have more events, or if we have a + * pagination token which means we can paginate in that direction. It does not + * necessarily mean that there are more events available in that direction at + * this time. + * + * @param direction - EventTimeline.BACKWARDS to check if we can + * paginate backwards; EventTimeline.FORWARDS to check if we can go forwards + * + * @returns true if we can paginate in the given direction + */ + canPaginate(direction) { + const tl = this.getTimelineIndex(direction); + if (!tl) { + debuglog("TimelineWindow: no timeline yet"); + return false; + } + if (direction == event_timeline_1.EventTimeline.BACKWARDS) { + if (tl.index > tl.minIndex()) { + return true; + } + } + else { + if (tl.index < tl.maxIndex()) { + return true; + } + } + const hasNeighbouringTimeline = tl.timeline.getNeighbouringTimeline(direction); + const paginationToken = tl.timeline.getPaginationToken(direction); + return Boolean(hasNeighbouringTimeline) || Boolean(paginationToken); + } + /** + * Attempt to extend the window + * + * @param direction - EventTimeline.BACKWARDS to extend the window + * backwards (towards older events); EventTimeline.FORWARDS to go forwards. + * + * @param size - number of events to try to extend by. If fewer than this + * number are immediately available, then we return immediately rather than + * making an API call. + * + * @param makeRequest - whether we should make API calls to + * fetch further events if we don't have any at all. (This has no effect if + * the room already knows about additional events in the relevant direction, + * even if there are fewer than 'size' of them, as we will just return those + * we already know about.) + * + * @param requestLimit - limit for the number of API requests we + * should make. + * + * @returns Promise which resolves to a boolean which is true if more events + * were successfully retrieved. + */ + paginate(direction, size, makeRequest = true, requestLimit = DEFAULT_PAGINATE_LOOP_LIMIT) { + return __awaiter(this, void 0, void 0, function* () { + // Either wind back the message cap (if there are enough events in the + // timeline to do so), or fire off a pagination request. + const tl = this.getTimelineIndex(direction); + if (!tl) { + debuglog("TimelineWindow: no timeline yet"); + return false; + } + if (tl.pendingPaginate) { + return tl.pendingPaginate; + } + // try moving the cap + if (this.extend(direction, size)) { + return true; + } + if (!makeRequest || requestLimit === 0) { + // todo: should we return something different to indicate that there + // might be more events out there, but we haven't found them yet? + return false; + } + // try making a pagination request + const token = tl.timeline.getPaginationToken(direction); + if (!token) { + debuglog("TimelineWindow: no token"); + return false; + } + debuglog("TimelineWindow: starting request"); + const prom = this.client + .paginateEventTimeline(tl.timeline, { + backwards: direction == event_timeline_1.EventTimeline.BACKWARDS, + limit: size, + }) + .finally(function () { + tl.pendingPaginate = undefined; + }) + .then((r) => { + debuglog("TimelineWindow: request completed with result " + r); + if (!r) { + return this.paginate(direction, size, false, 0); + } + // recurse to advance the index into the results. + // + // If we don't get any new events, we want to make sure we keep asking + // the server for events for as long as we have a valid pagination + // token. In particular, we want to know if we've actually hit the + // start of the timeline, or if we just happened to know about all of + // the events thanks to https://matrix.org/jira/browse/SYN-645. + // + // On the other hand, we necessarily want to wait forever for the + // server to make its mind up about whether there are other events, + // because it gives a bad user experience + // (https://github.com/vector-im/vector-web/issues/1204). + return this.paginate(direction, size, true, requestLimit - 1); + }); + tl.pendingPaginate = prom; + return prom; + }); + } + /** + * Remove `delta` events from the start or end of the timeline. + * + * @param delta - number of events to remove from the timeline + * @param startOfTimeline - if events should be removed from the start + * of the timeline. + */ + unpaginate(delta, startOfTimeline) { + const tl = startOfTimeline ? this.start : this.end; + if (!tl) { + throw new Error(`Attempting to unpaginate startOfTimeline=${startOfTimeline} but don't have this direction`); + } + // sanity-check the delta + if (delta > this.eventCount || delta < 0) { + throw new Error(`Attemting to unpaginate ${delta} events, but only have ${this.eventCount} in the timeline`); + } + while (delta > 0) { + const count = startOfTimeline ? tl.advance(delta) : tl.retreat(delta); + if (count <= 0) { + // sadness. This shouldn't be possible. + throw new Error("Unable to unpaginate any further, but still have " + this.eventCount + " events"); + } + delta -= count; + this.eventCount -= count; + debuglog("TimelineWindow.unpaginate: dropped " + count + " (now " + this.eventCount + ")"); + } + } + /** + * Get a list of the events currently in the window + * + * @returns the events in the window + */ + getEvents() { + var _a, _b; + if (!this.start) { + // not yet loaded + return []; + } + const result = []; + // iterate through each timeline between this.start and this.end + // (inclusive). + let timeline = this.start.timeline; + // eslint-disable-next-line no-constant-condition + while (true) { + const events = timeline.getEvents(); + // For the first timeline in the chain, we want to start at + // this.start.index. For the last timeline in the chain, we want to + // stop before this.end.index. Otherwise, we want to copy all of the + // events in the timeline. + // + // (Note that both this.start.index and this.end.index are relative + // to their respective timelines' BaseIndex). + // + let startIndex = 0; + let endIndex = events.length; + if (timeline === this.start.timeline) { + startIndex = this.start.index + timeline.getBaseIndex(); + } + if (timeline === ((_a = this.end) === null || _a === void 0 ? void 0 : _a.timeline)) { + endIndex = this.end.index + timeline.getBaseIndex(); + } + for (let i = startIndex; i < endIndex; i++) { + result.push(events[i]); + } + // if we're not done, iterate to the next timeline. + if (timeline === ((_b = this.end) === null || _b === void 0 ? void 0 : _b.timeline)) { + break; + } + else { + timeline = timeline.getNeighbouringTimeline(event_timeline_1.EventTimeline.FORWARDS); + } + } + return result; + } +} +exports.TimelineWindow = TimelineWindow; +/** + * A thing which contains a timeline reference, and an index into it. + * @internal + */ +class TimelineIndex { + // index: the indexes are relative to BaseIndex, so could well be negative. + constructor(timeline, index) { + this.timeline = timeline; + this.index = index; + } + /** + * @returns the minimum possible value for the index in the current + * timeline + */ + minIndex() { + return this.timeline.getBaseIndex() * -1; + } + /** + * @returns the maximum possible value for the index in the current + * timeline (exclusive - ie, it actually returns one more than the index + * of the last element). + */ + maxIndex() { + return this.timeline.getEvents().length - this.timeline.getBaseIndex(); + } + /** + * Try move the index forward, or into the neighbouring timeline + * + * @param delta - number of events to advance by + * @returns number of events successfully advanced by + */ + advance(delta) { + if (!delta) { + return 0; + } + // first try moving the index in the current timeline. See if there is room + // to do so. + let cappedDelta; + if (delta < 0) { + // we want to wind the index backwards. + // + // (this.minIndex() - this.index) is a negative number whose magnitude + // is the amount of room we have to wind back the index in the current + // timeline. We cap delta to this quantity. + cappedDelta = Math.max(delta, this.minIndex() - this.index); + if (cappedDelta < 0) { + this.index += cappedDelta; + return cappedDelta; + } + } + else { + // we want to wind the index forwards. + // + // (this.maxIndex() - this.index) is a (positive) number whose magnitude + // is the amount of room we have to wind forward the index in the current + // timeline. We cap delta to this quantity. + cappedDelta = Math.min(delta, this.maxIndex() - this.index); + if (cappedDelta > 0) { + this.index += cappedDelta; + return cappedDelta; + } + } + // the index is already at the start/end of the current timeline. + // + // next see if there is a neighbouring timeline to switch to. + const neighbour = this.timeline.getNeighbouringTimeline(delta < 0 ? event_timeline_1.EventTimeline.BACKWARDS : event_timeline_1.EventTimeline.FORWARDS); + if (neighbour) { + this.timeline = neighbour; + if (delta < 0) { + this.index = this.maxIndex(); + } + else { + this.index = this.minIndex(); + } + debuglog("paginate: switched to new neighbour"); + // recurse, using the next timeline + return this.advance(delta); + } + return 0; + } + /** + * Try move the index backwards, or into the neighbouring timeline + * + * @param delta - number of events to retreat by + * @returns number of events successfully retreated by + */ + retreat(delta) { + return this.advance(delta * -1) * -1; + } +} +exports.TimelineIndex = TimelineIndex; + +},{"./logger":374,"./models/event-timeline":382}],416:[function(require,module,exports){ +(function (setImmediate){(function (){ +"use strict"; +/* +Copyright 2015, 2016, 2019, 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MapWithDefault = exports.noUnsafeEventProps = exports.safeSet = exports.unsafeProp = exports.recursiveMapToObject = exports.mapsEqual = exports.isSupportedReceiptType = exports.sortEventsByLatestContentTimestamp = exports.recursivelyAssign = exports.compare = exports.lexicographicCompare = exports.prevString = exports.nextString = exports.averageBetweenStrings = exports.stringToBase = exports.baseToString = exports.alphabetPad = exports.DEFAULT_ALPHABET = exports.simpleRetryOperation = exports.chunkPromises = exports.promiseTry = exports.promiseMapSeries = exports.defer = exports.isNullOrUndefined = exports.immediate = exports.sleep = exports.ensureNoTrailingSlash = exports.globToRegexp = exports.escapeRegExp = exports.normalize = exports.removeDirectionOverrideChars = exports.removeHiddenChars = exports.isNumber = exports.deepSortedObjectEntries = exports.deepCompare = exports.deepCopy = exports.checkObjectHasKeys = exports.isFunction = exports.removeElement = exports.encodeUri = exports.decodeParams = exports.replaceParam = exports.encodeParams = exports.internaliseString = void 0; +/** + * This is an internal module. + */ +const unhomoglyph_1 = __importDefault(require("unhomoglyph")); +const p_retry_1 = __importDefault(require("p-retry")); +const location_1 = require("./@types/location"); +const read_receipts_1 = require("./@types/read_receipts"); +const interns = new Map(); +/** + * Internalises a string, reusing a known pointer or storing the pointer + * if needed for future strings. + * @param str - The string to internalise. + * @returns The internalised string. + */ +function internaliseString(str) { + // Unwrap strings before entering the map, if we somehow got a wrapped + // string as our input. This should only happen from tests. + if (str instanceof String) { + str = str.toString(); + } + // Check the map to see if we can store the value + if (!interns.has(str)) { + interns.set(str, str); + } + // Return any cached string reference + return interns.get(str); +} +exports.internaliseString = internaliseString; +/** + * Encode a dictionary of query parameters. + * Omits any undefined/null values. + * @param params - A dict of key/values to encode e.g. + * `{"foo": "bar", "baz": "taz"}` + * @returns The encoded string e.g. foo=bar&baz=taz + */ +function encodeParams(params, urlSearchParams) { + const searchParams = urlSearchParams !== null && urlSearchParams !== void 0 ? urlSearchParams : new URLSearchParams(); + for (const [key, val] of Object.entries(params)) { + if (val !== undefined && val !== null) { + if (Array.isArray(val)) { + val.forEach((v) => { + searchParams.append(key, String(v)); + }); + } + else { + searchParams.append(key, String(val)); + } + } + } + return searchParams; +} +exports.encodeParams = encodeParams; +/** + * Replace a stable parameter with the unstable naming for params + */ +function replaceParam(stable, unstable, dict) { + const result = Object.assign(Object.assign({}, dict), { [unstable]: dict[stable] }); + delete result[stable]; + return result; +} +exports.replaceParam = replaceParam; +/** + * Decode a query string in `application/x-www-form-urlencoded` format. + * @param query - A query string to decode e.g. + * foo=bar&via=server1&server2 + * @returns The decoded object, if any keys occurred multiple times + * then the value will be an array of strings, else it will be an array. + * This behaviour matches Node's qs.parse but is built on URLSearchParams + * for native web compatibility + */ +function decodeParams(query) { + const o = {}; + const params = new URLSearchParams(query); + for (const key of params.keys()) { + const val = params.getAll(key); + o[key] = val.length === 1 ? val[0] : val; + } + return o; +} +exports.decodeParams = decodeParams; +/** + * Encodes a URI according to a set of template variables. Variables will be + * passed through encodeURIComponent. + * @param pathTemplate - The path with template variables e.g. '/foo/$bar'. + * @param variables - The key/value pairs to replace the template + * variables with. E.g. `{ "$bar": "baz" }`. + * @returns The result of replacing all template variables e.g. '/foo/baz'. + */ +function encodeUri(pathTemplate, variables) { + for (const key in variables) { + if (!variables.hasOwnProperty(key)) { + continue; + } + const value = variables[key]; + if (value === undefined || value === null) { + continue; + } + pathTemplate = pathTemplate.replace(key, encodeURIComponent(value)); + } + return pathTemplate; +} +exports.encodeUri = encodeUri; +/** + * The removeElement() method removes the first element in the array that + * satisfies (returns true) the provided testing function. + * @param array - The array. + * @param fn - Function to execute on each value in the array, with the + * function signature `fn(element, index, array)`. Return true to + * remove this element and break. + * @param reverse - True to search in reverse order. + * @returns True if an element was removed. + */ +function removeElement(array, fn, reverse) { + let i; + if (reverse) { + for (i = array.length - 1; i >= 0; i--) { + if (fn(array[i], i, array)) { + array.splice(i, 1); + return true; + } + } + } + else { + for (i = 0; i < array.length; i++) { + if (fn(array[i], i, array)) { + array.splice(i, 1); + return true; + } + } + } + return false; +} +exports.removeElement = removeElement; +/** + * Checks if the given thing is a function. + * @param value - The thing to check. + * @returns True if it is a function. + */ +function isFunction(value) { + return Object.prototype.toString.call(value) === "[object Function]"; +} +exports.isFunction = isFunction; +/** + * Checks that the given object has the specified keys. + * @param obj - The object to check. + * @param keys - The list of keys that 'obj' must have. + * @throws If the object is missing keys. + */ +// note using 'keys' here would shadow the 'keys' function defined above +function checkObjectHasKeys(obj, keys) { + for (const key of keys) { + if (!obj.hasOwnProperty(key)) { + throw new Error("Missing required key: " + key); + } + } +} +exports.checkObjectHasKeys = checkObjectHasKeys; +/** + * Deep copy the given object. The object MUST NOT have circular references and + * MUST NOT have functions. + * @param obj - The object to deep copy. + * @returns A copy of the object without any references to the original. + */ +function deepCopy(obj) { + return JSON.parse(JSON.stringify(obj)); +} +exports.deepCopy = deepCopy; +/** + * Compare two objects for equality. The objects MUST NOT have circular references. + * + * @param x - The first object to compare. + * @param y - The second object to compare. + * + * @returns true if the two objects are equal + */ +function deepCompare(x, y) { + // Inspired by + // http://stackoverflow.com/questions/1068834/object-comparison-in-javascript#1144249 + // Compare primitives and functions. + // Also check if both arguments link to the same object. + if (x === y) { + return true; + } + if (typeof x !== typeof y) { + return false; + } + // special-case NaN (since NaN !== NaN) + if (typeof x === "number" && isNaN(x) && isNaN(y)) { + return true; + } + // special-case null (since typeof null == 'object', but null.constructor + // throws) + if (x === null || y === null) { + return x === y; + } + // everything else is either an unequal primitive, or an object + if (!(x instanceof Object)) { + return false; + } + // check they are the same type of object + if (x.constructor !== y.constructor || x.prototype !== y.prototype) { + return false; + } + // special-casing for some special types of object + if (x instanceof RegExp || x instanceof Date) { + return x.toString() === y.toString(); + } + // the object algorithm works for Array, but it's sub-optimal. + if (Array.isArray(x)) { + if (x.length !== y.length) { + return false; + } + for (let i = 0; i < x.length; i++) { + if (!deepCompare(x[i], y[i])) { + return false; + } + } + } + else { + // check that all of y's direct keys are in x + for (const p in y) { + if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { + return false; + } + } + // finally, compare each of x's keys with y + for (const p in x) { + if (y.hasOwnProperty(p) !== x.hasOwnProperty(p) || !deepCompare(x[p], y[p])) { + return false; + } + } + } + return true; +} +exports.deepCompare = deepCompare; +// Dev note: This returns an array of tuples, but jsdoc doesn't like that. https://github.com/jsdoc/jsdoc/issues/1703 +/** + * Creates an array of object properties/values (entries) then + * sorts the result by key, recursively. The input object must + * ensure it does not have loops. If the input is not an object + * then it will be returned as-is. + * @param obj - The object to get entries of + * @returns The entries, sorted by key. + */ +function deepSortedObjectEntries(obj) { + if (typeof obj !== "object") + return obj; + // Apparently these are object types... + if (obj === null || obj === undefined || Array.isArray(obj)) + return obj; + const pairs = []; + for (const [k, v] of Object.entries(obj)) { + pairs.push([k, deepSortedObjectEntries(v)]); + } + // lexicographicCompare is faster than localeCompare, so let's use that. + pairs.sort((a, b) => lexicographicCompare(a[0], b[0])); + return pairs; +} +exports.deepSortedObjectEntries = deepSortedObjectEntries; +/** + * Returns whether the given value is a finite number without type-coercion + * + * @param value - the value to test + * @returns whether or not value is a finite number without type-coercion + */ +function isNumber(value) { + return typeof value === "number" && isFinite(value); +} +exports.isNumber = isNumber; +/** + * Removes zero width chars, diacritics and whitespace from the string + * Also applies an unhomoglyph on the string, to prevent similar looking chars + * @param str - the string to remove hidden characters from + * @returns a string with the hidden characters removed + */ +function removeHiddenChars(str) { + if (typeof str === "string") { + return (0, unhomoglyph_1.default)(str.normalize("NFD").replace(removeHiddenCharsRegex, "")); + } + return ""; +} +exports.removeHiddenChars = removeHiddenChars; +/** + * Removes the direction override characters from a string + * @returns string with chars removed + */ +function removeDirectionOverrideChars(str) { + if (typeof str === "string") { + return str.replace(/[\u202d-\u202e]/g, ""); + } + return ""; +} +exports.removeDirectionOverrideChars = removeDirectionOverrideChars; +function normalize(str) { + // Note: we have to match the filter with the removeHiddenChars() because the + // function strips spaces and other characters (M becomes RN for example, in lowercase). + return (removeHiddenChars(str.toLowerCase()) + // Strip all punctuation + .replace(/[\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~\u2000-\u206f\u2e00-\u2e7f]/g, "") + // We also doubly convert to lowercase to work around oddities of the library. + .toLowerCase()); +} +exports.normalize = normalize; +// Regex matching bunch of unicode control characters and otherwise misleading/invisible characters. +// Includes: +// various width spaces U+2000 - U+200D +// LTR and RTL marks U+200E and U+200F +// LTR/RTL and other directional formatting marks U+202A - U+202F +// Arabic Letter RTL mark U+061C +// Combining characters U+0300 - U+036F +// Zero width no-break space (BOM) U+FEFF +// Blank/invisible characters (U2800, U2062-U2063) +// eslint-disable-next-line no-misleading-character-class +const removeHiddenCharsRegex = /[\u2000-\u200F\u202A-\u202F\u0300-\u036F\uFEFF\u061C\u2800\u2062-\u2063\s]/g; +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +exports.escapeRegExp = escapeRegExp; +function globToRegexp(glob, extended = false) { + // From + // https://github.com/matrix-org/synapse/blob/abbee6b29be80a77e05730707602f3bbfc3f38cb/synapse/push/__init__.py#L132 + // Because micromatch is about 130KB with dependencies, + // and minimatch is not much better. + const replacements = [ + [/\\\*/g, ".*"], + [/\?/g, "."], + ]; + if (!extended) { + replacements.push([ + /\\\[(!|)(.*)\\]/g, + (_match, neg, pat) => ["[", neg ? "^" : "", pat.replace(/\\-/, "-"), "]"].join(""), + ]); + } + return replacements.reduce( + // https://github.com/microsoft/TypeScript/issues/30134 + (pat, args) => (args ? pat.replace(args[0], args[1]) : pat), escapeRegExp(glob)); +} +exports.globToRegexp = globToRegexp; +function ensureNoTrailingSlash(url) { + if (url === null || url === void 0 ? void 0 : url.endsWith("/")) { + return url.slice(0, -1); + } + else { + return url; + } +} +exports.ensureNoTrailingSlash = ensureNoTrailingSlash; +/** + * Returns a promise which resolves with a given value after the given number of ms + */ +function sleep(ms, value) { + return new Promise((resolve) => { + setTimeout(resolve, ms, value); + }); +} +exports.sleep = sleep; +/** + * Promise/async version of {@link setImmediate}. + */ +function immediate() { + return new Promise(setImmediate); +} +exports.immediate = immediate; +function isNullOrUndefined(val) { + return val === null || val === undefined; +} +exports.isNullOrUndefined = isNullOrUndefined; +// Returns a Deferred +function defer() { + let resolve; + let reject; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + return { resolve, reject, promise }; +} +exports.defer = defer; +function promiseMapSeries(promises, fn) { + return __awaiter(this, void 0, void 0, function* () { + for (const o of promises) { + yield fn(yield o); + } + }); +} +exports.promiseMapSeries = promiseMapSeries; +function promiseTry(fn) { + return Promise.resolve(fn()); +} +exports.promiseTry = promiseTry; +// Creates and awaits all promises, running no more than `chunkSize` at the same time +function chunkPromises(fns, chunkSize) { + return __awaiter(this, void 0, void 0, function* () { + const results = []; + for (let i = 0; i < fns.length; i += chunkSize) { + results.push(...(yield Promise.all(fns.slice(i, i + chunkSize).map((fn) => fn())))); + } + return results; + }); +} +exports.chunkPromises = chunkPromises; +/** + * Retries the function until it succeeds or is interrupted. The given function must return + * a promise which throws/rejects on error, otherwise the retry will assume the request + * succeeded. The promise chain returned will contain the successful promise. The given function + * should always return a new promise. + * @param promiseFn - The function to call to get a fresh promise instance. Takes an + * attempt count as an argument, for logging/debugging purposes. + * @returns The promise for the retried operation. + */ +function simpleRetryOperation(promiseFn) { + return (0, p_retry_1.default)((attempt) => { + return promiseFn(attempt); + }, { + forever: true, + factor: 2, + minTimeout: 3000, + maxTimeout: 15000, // ms + }); +} +exports.simpleRetryOperation = simpleRetryOperation; +// String averaging inspired by https://stackoverflow.com/a/2510816 +// Dev note: We make the alphabet a string because it's easier to write syntactically +// than arrays. Thankfully, strings implement the useful parts of the Array interface +// anyhow. +/** + * The default alphabet used by string averaging in this SDK. This matches + * all usefully printable ASCII characters (0x20-0x7E, inclusive). + */ +exports.DEFAULT_ALPHABET = (() => { + let str = ""; + for (let c = 0x20; c <= 0x7e; c++) { + str += String.fromCharCode(c); + } + return str; +})(); +/** + * Pads a string using the given alphabet as a base. The returned string will be + * padded at the end with the first character in the alphabet. + * + * This is intended for use with string averaging. + * @param s - The string to pad. + * @param n - The length to pad to. + * @param alphabet - The alphabet to use as a single string. + * @returns The padded string. + */ +function alphabetPad(s, n, alphabet = exports.DEFAULT_ALPHABET) { + return s.padEnd(n, alphabet[0]); +} +exports.alphabetPad = alphabetPad; +/** + * Converts a baseN number to a string, where N is the alphabet's length. + * + * This is intended for use with string averaging. + * @param n - The baseN number. + * @param alphabet - The alphabet to use as a single string. + * @returns The baseN number encoded as a string from the alphabet. + */ +function baseToString(n, alphabet = exports.DEFAULT_ALPHABET) { + // Developer note: the stringToBase() function offsets the character set by 1 so that repeated + // characters (ie: "aaaaaa" in a..z) don't come out as zero. We have to reverse this here as + // otherwise we'll be wrong in our conversion. Undoing a +1 before an exponent isn't very fun + // though, so we rely on a lengthy amount of `x - 1` and integer division rules to reach a + // sane state. This also means we have to do rollover detection: see below. + var _a; + const len = BigInt(alphabet.length); + if (n <= len) { + return (_a = alphabet[Number(n) - 1]) !== null && _a !== void 0 ? _a : ""; + } + let d = n / len; + let r = Number(n % len) - 1; + // Rollover detection: if the remainder is negative, it means that the string needs + // to roll over by 1 character downwards (ie: in a..z, the previous to "aaa" would be + // "zz"). + if (r < 0) { + d -= BigInt(Math.abs(r)); // abs() is just to be clear what we're doing. Could also `+= r`. + r = Number(len) - 1; + } + return baseToString(d, alphabet) + alphabet[r]; +} +exports.baseToString = baseToString; +/** + * Converts a string to a baseN number, where N is the alphabet's length. + * + * This is intended for use with string averaging. + * @param s - The string to convert to a number. + * @param alphabet - The alphabet to use as a single string. + * @returns The baseN number. + */ +function stringToBase(s, alphabet = exports.DEFAULT_ALPHABET) { + const len = BigInt(alphabet.length); + // In our conversion to baseN we do a couple performance optimizations to avoid using + // excess CPU and such. To create baseN numbers, the input string needs to be reversed + // so the exponents stack up appropriately, as the last character in the unreversed + // string has less impact than the first character (in "abc" the A is a lot more important + // for lexicographic sorts). We also do a trick with the character codes to optimize the + // alphabet lookup, avoiding an index scan of `alphabet.indexOf(reversedStr[i])` - we know + // that the alphabet and (theoretically) the input string are constrained on character sets + // and thus can do simple subtraction to end up with the same result. + // Developer caution: we carefully cast to BigInt here to avoid losing precision. We cannot + // rely on Math.pow() (for example) to be capable of handling our insane numbers. + let result = BigInt(0); + for (let i = s.length - 1, j = BigInt(0); i >= 0; i--, j++) { + const charIndex = s.charCodeAt(i) - alphabet.charCodeAt(0); + // We add 1 to the char index to offset the whole numbering scheme. We unpack this in + // the baseToString() function. + result += BigInt(1 + charIndex) * len ** j; + } + return result; +} +exports.stringToBase = stringToBase; +/** + * Averages two strings, returning the midpoint between them. This is accomplished by + * converting both to baseN numbers (where N is the alphabet's length) then averaging + * those before re-encoding as a string. + * @param a - The first string. + * @param b - The second string. + * @param alphabet - The alphabet to use as a single string. + * @returns The midpoint between the strings, as a string. + */ +function averageBetweenStrings(a, b, alphabet = exports.DEFAULT_ALPHABET) { + const padN = Math.max(a.length, b.length); + const baseA = stringToBase(alphabetPad(a, padN, alphabet), alphabet); + const baseB = stringToBase(alphabetPad(b, padN, alphabet), alphabet); + const avg = (baseA + baseB) / BigInt(2); + // Detect integer division conflicts. This happens when two numbers are divided too close so + // we lose a .5 precision. We need to add a padding character in these cases. + if (avg === baseA || avg == baseB) { + return baseToString(avg, alphabet) + alphabet[0]; + } + return baseToString(avg, alphabet); +} +exports.averageBetweenStrings = averageBetweenStrings; +/** + * Finds the next string using the alphabet provided. This is done by converting the + * string to a baseN number, where N is the alphabet's length, then adding 1 before + * converting back to a string. + * @param s - The string to start at. + * @param alphabet - The alphabet to use as a single string. + * @returns The string which follows the input string. + */ +function nextString(s, alphabet = exports.DEFAULT_ALPHABET) { + return baseToString(stringToBase(s, alphabet) + BigInt(1), alphabet); +} +exports.nextString = nextString; +/** + * Finds the previous string using the alphabet provided. This is done by converting the + * string to a baseN number, where N is the alphabet's length, then subtracting 1 before + * converting back to a string. + * @param s - The string to start at. + * @param alphabet - The alphabet to use as a single string. + * @returns The string which precedes the input string. + */ +function prevString(s, alphabet = exports.DEFAULT_ALPHABET) { + return baseToString(stringToBase(s, alphabet) - BigInt(1), alphabet); +} +exports.prevString = prevString; +/** + * Compares strings lexicographically as a sort-safe function. + * @param a - The first (reference) string. + * @param b - The second (compare) string. + * @returns Negative if the reference string is before the compare string; + * positive if the reference string is after; and zero if equal. + */ +function lexicographicCompare(a, b) { + // Dev note: this exists because I'm sad that you can use math operators on strings, so I've + // hidden the operation in this function. + if (a < b) { + return -1; + } + else if (a > b) { + return 1; + } + else { + return 0; + } +} +exports.lexicographicCompare = lexicographicCompare; +const collator = new Intl.Collator(); +/** + * Performant language-sensitive string comparison + * @param a - the first string to compare + * @param b - the second string to compare + */ +function compare(a, b) { + return collator.compare(a, b); +} +exports.compare = compare; +/** + * This function is similar to Object.assign() but it assigns recursively and + * allows you to ignore nullish values from the source + * + * @returns the target object + */ +function recursivelyAssign(target, source, ignoreNullish = false) { + for (const [sourceKey, sourceValue] of Object.entries(source)) { + if (target[sourceKey] instanceof Object && sourceValue) { + recursivelyAssign(target[sourceKey], sourceValue); + continue; + } + if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) { + target[sourceKey] = sourceValue; + continue; + } + } + return target; +} +exports.recursivelyAssign = recursivelyAssign; +function getContentTimestampWithFallback(event) { + var _a; + return (_a = location_1.M_TIMESTAMP.findIn(event.getContent())) !== null && _a !== void 0 ? _a : -1; +} +/** + * Sort events by their content m.ts property + * Latest timestamp first + */ +function sortEventsByLatestContentTimestamp(left, right) { + return getContentTimestampWithFallback(right) - getContentTimestampWithFallback(left); +} +exports.sortEventsByLatestContentTimestamp = sortEventsByLatestContentTimestamp; +function isSupportedReceiptType(receiptType) { + return [read_receipts_1.ReceiptType.Read, read_receipts_1.ReceiptType.ReadPrivate].includes(receiptType); +} +exports.isSupportedReceiptType = isSupportedReceiptType; +/** + * Determines whether two maps are equal. + * @param eq - The equivalence relation to compare values by. Defaults to strict equality. + */ +function mapsEqual(x, y, eq = (v1, v2) => v1 === v2) { + if (x.size !== y.size) + return false; + for (const [k, v1] of x) { + const v2 = y.get(k); + if (v2 === undefined || !eq(v1, v2)) + return false; + } + return true; +} +exports.mapsEqual = mapsEqual; +function processMapToObjectValue(value) { + if (value instanceof Map) { + // Value is a Map. Recursively map it to an object. + return recursiveMapToObject(value); + } + else if (Array.isArray(value)) { + // Value is an Array. Recursively map the value (e.g. to cover Array of Arrays). + return value.map((v) => processMapToObjectValue(v)); + } + else { + return value; + } +} +/** + * Recursively converts Maps to plain objects. + * Also supports sub-lists of Maps. + */ +function recursiveMapToObject(map) { + const targetMap = new Map(); + for (const [key, value] of map) { + targetMap.set(key, processMapToObjectValue(value)); + } + return Object.fromEntries(targetMap.entries()); +} +exports.recursiveMapToObject = recursiveMapToObject; +function unsafeProp(prop) { + return prop === "__proto__" || prop === "prototype" || prop === "constructor"; +} +exports.unsafeProp = unsafeProp; +function safeSet(obj, prop, value) { + if (unsafeProp(prop)) { + throw new Error("Trying to modify prototype or constructor"); + } + obj[prop] = value; +} +exports.safeSet = safeSet; +function noUnsafeEventProps(event) { + return !(unsafeProp(event.room_id) || + unsafeProp(event.sender) || + unsafeProp(event.user_id) || + unsafeProp(event.event_id)); +} +exports.noUnsafeEventProps = noUnsafeEventProps; +class MapWithDefault extends Map { + constructor(createDefault) { + super(); + this.createDefault = createDefault; + } + /** + * Returns the value if the key already exists. + * If not, it creates a new value under that key using the ctor callback and returns it. + */ + getOrCreate(key) { + if (!this.has(key)) { + this.set(key, this.createDefault()); + } + return this.get(key); + } +} +exports.MapWithDefault = MapWithDefault; + +}).call(this)}).call(this,require("timers").setImmediate) + +},{"./@types/location":308,"./@types/read_receipts":311,"p-retry":225,"timers":280,"unhomoglyph":282}],417:[function(require,module,exports){ +"use strict"; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.releaseContext = exports.acquireContext = void 0; +let audioContext = null; +let refCount = 0; +/** + * Acquires a reference to the shared AudioContext. + * It's highly recommended to reuse this AudioContext rather than creating your + * own, because multiple AudioContexts can be problematic in some browsers. + * Make sure to call releaseContext when you're done using it. + * @returns The shared AudioContext + */ +const acquireContext = () => { + if (audioContext === null) + audioContext = new AudioContext(); + refCount++; + return audioContext; +}; +exports.acquireContext = acquireContext; +/** + * Signals that one of the references to the shared AudioContext has been + * released, allowing the context and associated hardware resources to be + * cleaned up if nothing else is using it. + */ +const releaseContext = () => { + refCount--; + if (refCount === 0) { + audioContext === null || audioContext === void 0 ? void 0 : audioContext.close(); + audioContext = null; + } +}; +exports.releaseContext = releaseContext; + +},{}],418:[function(require,module,exports){ +(function (process){(function (){ +"use strict"; +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2022 Šimon Brandner + +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. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createNewMatrixCall = exports.supportsMatrixCall = exports.setTracksEnabled = exports.MatrixCall = exports.genCallID = exports.CallError = exports.CallErrorCode = exports.CallEvent = exports.CallParty = exports.CallDirection = exports.CallType = exports.CallState = void 0; +/** + * This is an internal module. See {@link createNewMatrixCall} for the public API. + */ +const uuid_1 = require("uuid"); +const sdp_transform_1 = require("sdp-transform"); +const logger_1 = require("../logger"); +const utils = __importStar(require("../utils")); +const event_1 = require("../@types/event"); +const randomstring_1 = require("../randomstring"); +const callEventTypes_1 = require("./callEventTypes"); +const callFeed_1 = require("./callFeed"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const deviceinfo_1 = require("../crypto/deviceinfo"); +const groupCall_1 = require("./groupCall"); +const http_api_1 = require("../http-api"); +var MediaType; +(function (MediaType) { + MediaType["AUDIO"] = "audio"; + MediaType["VIDEO"] = "video"; +})(MediaType || (MediaType = {})); +var CodecName; +(function (CodecName) { + CodecName["OPUS"] = "opus"; + // add more as needed +})(CodecName || (CodecName = {})); +var CallState; +(function (CallState) { + CallState["Fledgling"] = "fledgling"; + CallState["InviteSent"] = "invite_sent"; + CallState["WaitLocalMedia"] = "wait_local_media"; + CallState["CreateOffer"] = "create_offer"; + CallState["CreateAnswer"] = "create_answer"; + CallState["Connecting"] = "connecting"; + CallState["Connected"] = "connected"; + CallState["Ringing"] = "ringing"; + CallState["Ended"] = "ended"; +})(CallState = exports.CallState || (exports.CallState = {})); +var CallType; +(function (CallType) { + CallType["Voice"] = "voice"; + CallType["Video"] = "video"; +})(CallType = exports.CallType || (exports.CallType = {})); +var CallDirection; +(function (CallDirection) { + CallDirection["Inbound"] = "inbound"; + CallDirection["Outbound"] = "outbound"; +})(CallDirection = exports.CallDirection || (exports.CallDirection = {})); +var CallParty; +(function (CallParty) { + CallParty["Local"] = "local"; + CallParty["Remote"] = "remote"; +})(CallParty = exports.CallParty || (exports.CallParty = {})); +var CallEvent; +(function (CallEvent) { + CallEvent["Hangup"] = "hangup"; + CallEvent["State"] = "state"; + CallEvent["Error"] = "error"; + CallEvent["Replaced"] = "replaced"; + // The value of isLocalOnHold() has changed + CallEvent["LocalHoldUnhold"] = "local_hold_unhold"; + // The value of isRemoteOnHold() has changed + CallEvent["RemoteHoldUnhold"] = "remote_hold_unhold"; + // backwards compat alias for LocalHoldUnhold: remove in a major version bump + CallEvent["HoldUnhold"] = "hold_unhold"; + // Feeds have changed + CallEvent["FeedsChanged"] = "feeds_changed"; + CallEvent["AssertedIdentityChanged"] = "asserted_identity_changed"; + CallEvent["LengthChanged"] = "length_changed"; + CallEvent["DataChannel"] = "datachannel"; + CallEvent["SendVoipEvent"] = "send_voip_event"; +})(CallEvent = exports.CallEvent || (exports.CallEvent = {})); +var CallErrorCode; +(function (CallErrorCode) { + /** The user chose to end the call */ + CallErrorCode["UserHangup"] = "user_hangup"; + /** An error code when the local client failed to create an offer. */ + CallErrorCode["LocalOfferFailed"] = "local_offer_failed"; + /** + * An error code when there is no local mic/camera to use. This may be because + * the hardware isn't plugged in, or the user has explicitly denied access. + */ + CallErrorCode["NoUserMedia"] = "no_user_media"; + /** + * Error code used when a call event failed to send + * because unknown devices were present in the room + */ + CallErrorCode["UnknownDevices"] = "unknown_devices"; + /** + * Error code used when we fail to send the invite + * for some reason other than there being unknown devices + */ + CallErrorCode["SendInvite"] = "send_invite"; + /** + * An answer could not be created + */ + CallErrorCode["CreateAnswer"] = "create_answer"; + /** + * An offer could not be created + */ + CallErrorCode["CreateOffer"] = "create_offer"; + /** + * Error code used when we fail to send the answer + * for some reason other than there being unknown devices + */ + CallErrorCode["SendAnswer"] = "send_answer"; + /** + * The session description from the other side could not be set + */ + CallErrorCode["SetRemoteDescription"] = "set_remote_description"; + /** + * The session description from this side could not be set + */ + CallErrorCode["SetLocalDescription"] = "set_local_description"; + /** + * A different device answered the call + */ + CallErrorCode["AnsweredElsewhere"] = "answered_elsewhere"; + /** + * No media connection could be established to the other party + */ + CallErrorCode["IceFailed"] = "ice_failed"; + /** + * The invite timed out whilst waiting for an answer + */ + CallErrorCode["InviteTimeout"] = "invite_timeout"; + /** + * The call was replaced by another call + */ + CallErrorCode["Replaced"] = "replaced"; + /** + * Signalling for the call could not be sent (other than the initial invite) + */ + CallErrorCode["SignallingFailed"] = "signalling_timeout"; + /** + * The remote party is busy + */ + CallErrorCode["UserBusy"] = "user_busy"; + /** + * We transferred the call off to somewhere else + */ + CallErrorCode["Transferred"] = "transferred"; + /** + * A call from the same user was found with a new session id + */ + CallErrorCode["NewSession"] = "new_session"; +})(CallErrorCode = exports.CallErrorCode || (exports.CallErrorCode = {})); +/** + * The version field that we set in m.call.* events + */ +const VOIP_PROTO_VERSION = "1"; +/** The fallback ICE server to use for STUN or TURN protocols. */ +const FALLBACK_ICE_SERVER = "stun:turn.matrix.org"; +/** The length of time a call can be ringing for. */ +const CALL_TIMEOUT_MS = 60 * 1000; // ms +/** The time after which we increment callLength */ +const CALL_LENGTH_INTERVAL = 1000; // ms +/** The time after which we end the call, if ICE got disconnected */ +const ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms +class CallError extends Error { + constructor(code, msg, err) { + // Still don't think there's any way to have proper nested errors + super(msg + ": " + err); + this.code = code; + } +} +exports.CallError = CallError; +function genCallID() { + return Date.now().toString() + (0, randomstring_1.randomString)(16); +} +exports.genCallID = genCallID; +function getCodecParamMods(isPtt) { + const mods = [ + { + mediaType: "audio", + codec: "opus", + enableDtx: true, + maxAverageBitrate: isPtt ? 12000 : undefined, + }, + ]; + return mods; +} +// generates keys for the map of transceivers +// kind is unfortunately a string rather than MediaType as this is the type of +// track.kind +function getTransceiverKey(purpose, kind) { + return purpose + ":" + kind; +} +class MatrixCall extends typed_event_emitter_1.TypedEventEmitter { + /** + * Construct a new Matrix Call. + * @param opts - Config options. + */ + constructor(opts) { + var _a; + super(); + this.toDeviceSeq = 0; + // whether this call should have push-to-talk semantics + // This should be set by the consumer on incoming & outgoing calls. + this.isPtt = false; + this._state = CallState.Fledgling; + // A queue for candidates waiting to go out. + // We try to amalgamate candidates into a single candidate message where + // possible + this.candidateSendQueue = []; + this.candidateSendTries = 0; + this.candidatesEnded = false; + this.feeds = []; + // our transceivers for each purpose and type of media + this.transceivers = new Map(); + this.inviteOrAnswerSent = false; + this.waitForLocalAVStream = false; + this.removeTrackListeners = new Map(); + // The logic of when & if a call is on hold is nontrivial and explained in is*OnHold + // This flag represents whether we want the other party to be on hold + this.remoteOnHold = false; + // Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example + this.makingOffer = false; + this.ignoreOffer = false; + // If candidates arrive before we've picked an opponent (which, in particular, + // will happen if the opponent sends candidates eagerly before the user answers + // the call) we buffer them up here so we can then add the ones from the party we pick + this.remoteCandidateBuffer = new Map(); + /** + * Internal + */ + this.gotLocalIceCandidate = (event) => { + if (event.candidate) { + if (this.candidatesEnded) { + logger_1.logger.warn(`Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended - ignoring!`); + return; + } + logger_1.logger.debug(`Call ${this.callId} got local ICE ${event.candidate.sdpMid} ${event.candidate.candidate}`); + if (this.callHasEnded()) + return; + // As with the offer, note we need to make a copy of this object, not + // pass the original: that broke in Chrome ~m43. + if (event.candidate.candidate === "") { + this.queueCandidate(null); + } + else { + this.queueCandidate(event.candidate); + } + } + }; + this.onIceGatheringStateChange = (event) => { + var _a; + logger_1.logger.debug(`Call ${this.callId} onIceGatheringStateChange() ice gathering state changed to ${this.peerConn.iceGatheringState}`); + if (((_a = this.peerConn) === null || _a === void 0 ? void 0 : _a.iceGatheringState) === "complete") { + this.queueCandidate(null); + } + }; + this.getLocalOfferFailed = (err) => { + logger_1.logger.error(`Call ${this.callId} getLocalOfferFailed() running`, err); + this.emit(CallEvent.Error, new CallError(CallErrorCode.LocalOfferFailed, "Failed to get local offer!", err), this); + this.terminate(CallParty.Local, CallErrorCode.LocalOfferFailed, false); + }; + this.getUserMediaFailed = (err) => { + if (this.successor) { + this.successor.getUserMediaFailed(err); + return; + } + logger_1.logger.warn(`Call ${this.callId} getUserMediaFailed() failed to get user media - ending call`, err); + this.emit(CallEvent.Error, new CallError(CallErrorCode.NoUserMedia, "Couldn't start capturing media! Is your microphone set up and " + "does this app have permission?", err), this); + this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false); + }; + this.onIceConnectionStateChanged = () => { + var _a, _b, _c, _d, _e, _f; + if (this.callHasEnded()) { + return; // because ICE can still complete as we're ending the call + } + logger_1.logger.debug(`Call ${this.callId} onIceConnectionStateChanged() running (state=${(_a = this.peerConn) === null || _a === void 0 ? void 0 : _a.iceConnectionState})`); + // ideally we'd consider the call to be connected when we get media but + // chrome doesn't implement any of the 'onstarted' events yet + if (["connected", "completed"].includes((_c = (_b = this.peerConn) === null || _b === void 0 ? void 0 : _b.iceConnectionState) !== null && _c !== void 0 ? _c : "")) { + clearTimeout(this.iceDisconnectedTimeout); + this.iceDisconnectedTimeout = undefined; + this.state = CallState.Connected; + if (!this.callLengthInterval && !this.callStartTime) { + this.callStartTime = Date.now(); + this.callLengthInterval = setInterval(() => { + this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime) / 1000), this); + }, CALL_LENGTH_INTERVAL); + } + } + else if (((_d = this.peerConn) === null || _d === void 0 ? void 0 : _d.iceConnectionState) == "failed") { + // Firefox for Android does not yet have support for restartIce() + // (the types say it's always defined though, so we have to cast + // to prevent typescript from warning). + if ((_e = this.peerConn) === null || _e === void 0 ? void 0 : _e.restartIce) { + this.candidatesEnded = false; + this.peerConn.restartIce(); + } + else { + logger_1.logger.info(`Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE failed and no ICE restart method)`); + this.hangup(CallErrorCode.IceFailed, false); + } + } + else if (((_f = this.peerConn) === null || _f === void 0 ? void 0 : _f.iceConnectionState) == "disconnected") { + this.iceDisconnectedTimeout = setTimeout(() => { + logger_1.logger.info(`Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE disconnected for too long)`); + this.hangup(CallErrorCode.IceFailed, false); + }, ICE_DISCONNECTED_TIMEOUT); + this.state = CallState.Connecting; + } + // In PTT mode, override feed status to muted when we lose connection to + // the peer, since we don't want to block the line if they're not saying anything. + // Experimenting in Chrome, this happens after 5 or 6 seconds, which is probably + // fast enough. + if (this.isPtt && ["failed", "disconnected"].includes(this.peerConn.iceConnectionState)) { + for (const feed of this.getRemoteFeeds()) { + feed.setAudioVideoMuted(true, true); + } + } + }; + this.onSignallingStateChanged = () => { + var _a; + logger_1.logger.debug(`Call ${this.callId} onSignallingStateChanged() running (state=${(_a = this.peerConn) === null || _a === void 0 ? void 0 : _a.signalingState})`); + }; + this.onTrack = (ev) => { + if (ev.streams.length === 0) { + logger_1.logger.warn(`Call ${this.callId} onTrack() called with streamless track streamless (kind=${ev.track.kind})`); + return; + } + const stream = ev.streams[0]; + this.pushRemoteFeed(stream); + if (!this.removeTrackListeners.has(stream)) { + const onRemoveTrack = () => { + if (stream.getTracks().length === 0) { + logger_1.logger.info(`Call ${this.callId} onTrack() removing track (streamId=${stream.id})`); + this.deleteFeedByStream(stream); + stream.removeEventListener("removetrack", onRemoveTrack); + this.removeTrackListeners.delete(stream); + } + }; + stream.addEventListener("removetrack", onRemoveTrack); + this.removeTrackListeners.set(stream, onRemoveTrack); + } + }; + this.onDataChannel = (ev) => { + this.emit(CallEvent.DataChannel, ev.channel, this); + }; + this.onNegotiationNeeded = () => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info(`Call ${this.callId} onNegotiationNeeded() negotiation is needed!`); + if (this.state !== CallState.CreateOffer && this.opponentVersion === 0) { + logger_1.logger.info(`Call ${this.callId} onNegotiationNeeded() opponent does not support renegotiation: ignoring negotiationneeded event`); + return; + } + this.queueGotLocalOffer(); + }); + this.onHangupReceived = (msg) => { + logger_1.logger.debug(`Call ${this.callId} onHangupReceived() running`); + // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen + // a partner yet but we're treating the hangup as a reject as per VoIP v0) + if (this.partyIdMatches(msg) || this.state === CallState.Ringing) { + // default reason is user_hangup + this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true); + } + else { + logger_1.logger.info(`Call ${this.callId} onHangupReceived() ignoring message from party ID ${msg.party_id}: our partner is ${this.opponentPartyId}`); + } + }; + this.onRejectReceived = (msg) => { + logger_1.logger.debug(`Call ${this.callId} onRejectReceived() running`); + // No need to check party_id for reject because if we'd received either + // an answer or reject, we wouldn't be in state InviteSent + const shouldTerminate = + // reject events also end the call if it's ringing: it's another of + // our devices rejecting the call. + [CallState.InviteSent, CallState.Ringing].includes(this.state) || + // also if we're in the init state and it's an inbound call, since + // this means we just haven't entered the ringing state yet + (this.state === CallState.Fledgling && this.direction === CallDirection.Inbound); + if (shouldTerminate) { + this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true); + } + else { + logger_1.logger.debug(`Call ${this.callId} onRejectReceived() called in wrong state (state=${this.state})`); + } + }; + this.onAnsweredElsewhere = (msg) => { + logger_1.logger.debug(`Call ${this.callId} onAnsweredElsewhere() running`); + this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true); + }; + this.roomId = opts.roomId; + this.invitee = opts.invitee; + this.client = opts.client; + if (!this.client.deviceId) + throw new Error("Client must have a device ID to start calls"); + this.forceTURN = (_a = opts.forceTURN) !== null && _a !== void 0 ? _a : false; + this.ourPartyId = this.client.deviceId; + this.opponentDeviceId = opts.opponentDeviceId; + this.opponentSessionId = opts.opponentSessionId; + this.groupCallId = opts.groupCallId; + // Array of Objects with urls, username, credential keys + this.turnServers = opts.turnServers || []; + if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) { + this.turnServers.push({ + urls: [FALLBACK_ICE_SERVER], + }); + } + for (const server of this.turnServers) { + utils.checkObjectHasKeys(server, ["urls"]); + } + this.callId = genCallID(); + // If the Client provides calls without audio and video we need a datachannel for a webrtc connection + this.isOnlyDataChannelAllowed = this.client.isVoipWithNoMediaAllowed; + } + /** + * Place a voice call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + placeVoiceCall() { + return __awaiter(this, void 0, void 0, function* () { + yield this.placeCall(true, false); + }); + } + /** + * Place a video call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + placeVideoCall() { + return __awaiter(this, void 0, void 0, function* () { + yield this.placeCall(true, true); + }); + } + /** + * Create a datachannel using this call's peer connection. + * @param label - A human readable label for this datachannel + * @param options - An object providing configuration options for the data channel. + */ + createDataChannel(label, options) { + const dataChannel = this.peerConn.createDataChannel(label, options); + this.emit(CallEvent.DataChannel, dataChannel, this); + return dataChannel; + } + getOpponentMember() { + return this.opponentMember; + } + getOpponentDeviceId() { + return this.opponentDeviceId; + } + getOpponentSessionId() { + return this.opponentSessionId; + } + opponentCanBeTransferred() { + return Boolean(this.opponentCaps && this.opponentCaps["m.call.transferee"]); + } + opponentSupportsDTMF() { + return Boolean(this.opponentCaps && this.opponentCaps["m.call.dtmf"]); + } + getRemoteAssertedIdentity() { + return this.remoteAssertedIdentity; + } + get state() { + return this._state; + } + set state(state) { + const oldState = this._state; + this._state = state; + this.emit(CallEvent.State, state, oldState, this); + } + get type() { + // we may want to look for a video receiver here rather than a track to match the + // sender behaviour, although in practice they should be the same thing + return this.hasUserMediaVideoSender || this.hasRemoteUserMediaVideoTrack ? CallType.Video : CallType.Voice; + } + get hasLocalUserMediaVideoTrack() { + var _a; + return !!((_a = this.localUsermediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().length); + } + get hasRemoteUserMediaVideoTrack() { + return this.getRemoteFeeds().some((feed) => { + var _a; + return feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia && ((_a = feed.stream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().length); + }); + } + get hasLocalUserMediaAudioTrack() { + var _a; + return !!((_a = this.localUsermediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().length); + } + get hasRemoteUserMediaAudioTrack() { + return this.getRemoteFeeds().some((feed) => { + var _a; + return feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia && !!((_a = feed.stream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().length); + }); + } + get hasUserMediaAudioSender() { + var _a; + return Boolean((_a = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, "audio"))) === null || _a === void 0 ? void 0 : _a.sender); + } + get hasUserMediaVideoSender() { + var _a; + return Boolean((_a = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _a === void 0 ? void 0 : _a.sender); + } + get localUsermediaFeed() { + return this.getLocalFeeds().find((feed) => feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia); + } + get localScreensharingFeed() { + return this.getLocalFeeds().find((feed) => feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare); + } + get localUsermediaStream() { + var _a; + return (_a = this.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.stream; + } + get localScreensharingStream() { + var _a; + return (_a = this.localScreensharingFeed) === null || _a === void 0 ? void 0 : _a.stream; + } + get remoteUsermediaFeed() { + return this.getRemoteFeeds().find((feed) => feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia); + } + get remoteScreensharingFeed() { + return this.getRemoteFeeds().find((feed) => feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare); + } + get remoteUsermediaStream() { + var _a; + return (_a = this.remoteUsermediaFeed) === null || _a === void 0 ? void 0 : _a.stream; + } + get remoteScreensharingStream() { + var _a; + return (_a = this.remoteScreensharingFeed) === null || _a === void 0 ? void 0 : _a.stream; + } + getFeedByStreamId(streamId) { + return this.getFeeds().find((feed) => feed.stream.id === streamId); + } + /** + * Returns an array of all CallFeeds + * @returns CallFeeds + */ + getFeeds() { + return this.feeds; + } + /** + * Returns an array of all local CallFeeds + * @returns local CallFeeds + */ + getLocalFeeds() { + return this.feeds.filter((feed) => feed.isLocal()); + } + /** + * Returns an array of all remote CallFeeds + * @returns remote CallFeeds + */ + getRemoteFeeds() { + return this.feeds.filter((feed) => !feed.isLocal()); + } + initOpponentCrypto() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (!this.opponentDeviceId) + return; + if (!this.client.getUseE2eForGroupCall()) + return; + // It's possible to want E2EE and yet not have the means to manage E2EE + // ourselves (for example if the client is a RoomWidgetClient) + if (!this.client.isCryptoEnabled()) { + // All we know is the device ID + this.opponentDeviceInfo = new deviceinfo_1.DeviceInfo(this.opponentDeviceId); + return; + } + // if we've got to this point, we do want to init crypto, so throw if we can't + if (!this.client.crypto) + throw new Error("Crypto is not initialised."); + const userId = this.invitee || ((_a = this.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId); + if (!userId) + throw new Error("Couldn't find opponent user ID to init crypto"); + const deviceInfoMap = yield this.client.crypto.deviceList.downloadKeys([userId], false); + this.opponentDeviceInfo = (_b = deviceInfoMap.get(userId)) === null || _b === void 0 ? void 0 : _b.get(this.opponentDeviceId); + if (this.opponentDeviceInfo === undefined) { + throw new groupCall_1.GroupCallUnknownDeviceError(userId); + } + }); + } + /** + * Generates and returns localSDPStreamMetadata + * @returns localSDPStreamMetadata + */ + getLocalSDPStreamMetadata(updateStreamIds = false) { + const metadata = {}; + for (const localFeed of this.getLocalFeeds()) { + if (updateStreamIds) { + localFeed.sdpMetadataStreamId = localFeed.stream.id; + } + metadata[localFeed.sdpMetadataStreamId] = { + purpose: localFeed.purpose, + audio_muted: localFeed.isAudioMuted(), + video_muted: localFeed.isVideoMuted(), + }; + } + return metadata; + } + /** + * Returns true if there are no incoming feeds, + * otherwise returns false + * @returns no incoming feeds + */ + noIncomingFeeds() { + return !this.feeds.some((feed) => !feed.isLocal()); + } + pushRemoteFeed(stream) { + // Fallback to old behavior if the other side doesn't support SDPStreamMetadata + if (!this.opponentSupportsSDPStreamMetadata()) { + this.pushRemoteFeedWithoutMetadata(stream); + return; + } + const userId = this.getOpponentMember().userId; + const purpose = this.remoteSDPStreamMetadata[stream.id].purpose; + const audioMuted = this.remoteSDPStreamMetadata[stream.id].audio_muted; + const videoMuted = this.remoteSDPStreamMetadata[stream.id].video_muted; + if (!purpose) { + logger_1.logger.warn(`Call ${this.callId} pushRemoteFeed() ignoring stream because we didn't get any metadata about it (streamId=${stream.id})`); + return; + } + if (this.getFeedByStreamId(stream.id)) { + logger_1.logger.warn(`Call ${this.callId} pushRemoteFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.feeds.push(new callFeed_1.CallFeed({ + client: this.client, + call: this, + roomId: this.roomId, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose, + audioMuted, + videoMuted, + })); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + logger_1.logger.info(`Call ${this.callId} pushRemoteFeed() pushed stream (streamId=${stream.id}, active=${stream.active}, purpose=${purpose})`); + } + /** + * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata + */ + pushRemoteFeedWithoutMetadata(stream) { + var _a; + const userId = this.getOpponentMember().userId; + // We can guess the purpose here since the other client can only send one stream + const purpose = callEventTypes_1.SDPStreamMetadataPurpose.Usermedia; + const oldRemoteStream = (_a = this.feeds.find((feed) => !feed.isLocal())) === null || _a === void 0 ? void 0 : _a.stream; + // Note that we check by ID and always set the remote stream: Chrome appears + // to make new stream objects when transceiver directionality is changed and the 'active' + // status of streams change - Dave + // If we already have a stream, check this stream has the same id + if (oldRemoteStream && stream.id !== oldRemoteStream.id) { + logger_1.logger.warn(`Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring new stream because we already have stream (streamId=${stream.id})`); + return; + } + if (this.getFeedByStreamId(stream.id)) { + logger_1.logger.warn(`Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.feeds.push(new callFeed_1.CallFeed({ + client: this.client, + call: this, + roomId: this.roomId, + audioMuted: false, + videoMuted: false, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose, + })); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + logger_1.logger.info(`Call ${this.callId} pushRemoteFeedWithoutMetadata() pushed stream (streamId=${stream.id}, active=${stream.active})`); + } + pushNewLocalFeed(stream, purpose, addToPeerConnection = true) { + const userId = this.client.getUserId(); + // Tracks don't always start off enabled, eg. chrome will give a disabled + // audio track if you ask for user media audio and already had one that + // you'd set to disabled (presumably because it clones them internally). + setTracksEnabled(stream.getAudioTracks(), true); + setTracksEnabled(stream.getVideoTracks(), true); + if (this.getFeedByStreamId(stream.id)) { + logger_1.logger.warn(`Call ${this.callId} pushNewLocalFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.pushLocalFeed(new callFeed_1.CallFeed({ + client: this.client, + roomId: this.roomId, + audioMuted: false, + videoMuted: false, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose, + }), addToPeerConnection); + } + /** + * Pushes supplied feed to the call + * @param callFeed - to push + * @param addToPeerConnection - whether to add the tracks to the peer connection + */ + pushLocalFeed(callFeed, addToPeerConnection = true) { + if (this.feeds.some((feed) => callFeed.stream.id === feed.stream.id)) { + logger_1.logger.info(`Call ${this.callId} pushLocalFeed() ignoring duplicate local stream (streamId=${callFeed.stream.id})`); + return; + } + this.feeds.push(callFeed); + if (addToPeerConnection) { + for (const track of callFeed.stream.getTracks()) { + logger_1.logger.info(`Call ${this.callId} pushLocalFeed() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${callFeed.stream.id}, streamPurpose=${callFeed.purpose}, enabled=${track.enabled})`); + const tKey = getTransceiverKey(callFeed.purpose, track.kind); + if (this.transceivers.has(tKey)) { + // we already have a sender, so we re-use it. We try to re-use transceivers as much + // as possible because they can't be removed once added, so otherwise they just + // accumulate which makes the SDP very large very quickly: in fact it only takes + // about 6 video tracks to exceed the maximum size of an Olm-encrypted + // Matrix event. + const transceiver = this.transceivers.get(tKey); + transceiver.sender.replaceTrack(track); + // set the direction to indicate we're going to start sending again + // (this will trigger the re-negotiation) + transceiver.direction = transceiver.direction === "inactive" ? "sendonly" : "sendrecv"; + } + else { + // create a new one. We need to use addTrack rather addTransceiver for this because firefox + // doesn't yet implement RTCRTPSender.setStreams() + // (https://bugzilla.mozilla.org/show_bug.cgi?id=1510802) so we'd have no way to group the + // two tracks together into a stream. + const newSender = this.peerConn.addTrack(track, callFeed.stream); + // now go & fish for the new transceiver + const newTransceiver = this.peerConn.getTransceivers().find((t) => t.sender === newSender); + if (newTransceiver) { + this.transceivers.set(tKey, newTransceiver); + } + else { + logger_1.logger.warn(`Call ${this.callId} pushLocalFeed() didn't find a matching transceiver after adding track!`); + } + } + } + } + logger_1.logger.info(`Call ${this.callId} pushLocalFeed() pushed stream (id=${callFeed.stream.id}, active=${callFeed.stream.active}, purpose=${callFeed.purpose})`); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + /** + * Removes local call feed from the call and its tracks from the peer + * connection + * @param callFeed - to remove + */ + removeLocalFeed(callFeed) { + const audioTransceiverKey = getTransceiverKey(callFeed.purpose, "audio"); + const videoTransceiverKey = getTransceiverKey(callFeed.purpose, "video"); + for (const transceiverKey of [audioTransceiverKey, videoTransceiverKey]) { + // this is slightly mixing the track and transceiver API but is basically just shorthand. + // There is no way to actually remove a transceiver, so this just sets it to inactive + // (or recvonly) and replaces the source with nothing. + if (this.transceivers.has(transceiverKey)) { + const transceiver = this.transceivers.get(transceiverKey); + if (transceiver.sender) + this.peerConn.removeTrack(transceiver.sender); + } + } + if (callFeed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare) { + this.client.getMediaHandler().stopScreensharingStream(callFeed.stream); + } + this.deleteFeed(callFeed); + } + deleteAllFeeds() { + for (const feed of this.feeds) { + if (!feed.isLocal() || !this.groupCallId) { + feed.dispose(); + } + } + this.feeds = []; + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + deleteFeedByStream(stream) { + const feed = this.getFeedByStreamId(stream.id); + if (!feed) { + logger_1.logger.warn(`Call ${this.callId} deleteFeedByStream() didn't find the feed to delete (streamId=${stream.id})`); + return; + } + this.deleteFeed(feed); + } + deleteFeed(feed) { + feed.dispose(); + this.feeds.splice(this.feeds.indexOf(feed), 1); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + // The typescript definitions have this type as 'any' :( + getCurrentCallStats() { + return __awaiter(this, void 0, void 0, function* () { + if (this.callHasEnded()) { + return this.callStatsAtEnd; + } + return this.collectCallStats(); + }); + } + collectCallStats() { + return __awaiter(this, void 0, void 0, function* () { + // This happens when the call fails before it starts. + // For example when we fail to get capture sources + if (!this.peerConn) + return; + const statsReport = yield this.peerConn.getStats(); + const stats = []; + statsReport.forEach((item) => { + stats.push(item); + }); + return stats; + }); + } + /** + * Configure this call from an invite event. Used by MatrixClient. + * @param event - The m.call.invite event + */ + initWithInvite(event) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const invite = event.getContent(); + this.direction = CallDirection.Inbound; + // make sure we have valid turn creds. Unless something's gone wrong, it should + // poll and keep the credentials valid so this should be instant. + const haveTurnCreds = yield this.client.checkTurnServers(); + if (!haveTurnCreds) { + logger_1.logger.warn(`Call ${this.callId} initWithInvite() failed to get TURN credentials! Proceeding with call anyway...`); + } + const sdpStreamMetadata = invite[callEventTypes_1.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } + else { + logger_1.logger.debug(`Call ${this.callId} initWithInvite() did not get any SDPStreamMetadata! Can not send/receive multiple streams`); + } + this.peerConn = this.createPeerConnection(); + // we must set the party ID before await-ing on anything: the call event + // handler will start giving us more call events (eg. candidates) so if + // we haven't set the party ID, we'll ignore them. + this.chooseOpponent(event); + yield this.initOpponentCrypto(); + try { + yield this.peerConn.setRemoteDescription(invite.offer); + yield this.addBufferedIceCandidates(); + } + catch (e) { + logger_1.logger.debug(`Call ${this.callId} initWithInvite() failed to set remote description`, e); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + const remoteStream = (_a = this.feeds.find((feed) => !feed.isLocal())) === null || _a === void 0 ? void 0 : _a.stream; + // According to previous comments in this file, firefox at some point did not + // add streams until media started arriving on them. Testing latest firefox + // (81 at time of writing), this is no longer a problem, so let's do it the correct way. + // + // For example in case of no media webrtc connections like screen share only call we have to allow webrtc + // connections without remote media. In this case we always use a data channel. At the moment we allow as well + // only data channel as media in the WebRTC connection with this setup here. + if (!this.isOnlyDataChannelAllowed && (!remoteStream || remoteStream.getTracks().length === 0)) { + logger_1.logger.error(`Call ${this.callId} initWithInvite() no remote stream or no tracks after setting remote description!`); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + this.state = CallState.Ringing; + if (event.getLocalAge()) { + // Time out the call if it's ringing for too long + const ringingTimer = setTimeout(() => { + var _a; + if (this.state == CallState.Ringing) { + logger_1.logger.debug(`Call ${this.callId} initWithInvite() invite has expired. Hanging up.`); + this.hangupParty = CallParty.Remote; // effectively + this.state = CallState.Ended; + this.stopAllMedia(); + if (this.peerConn.signalingState != "closed") { + this.peerConn.close(); + } + (_a = this.stats) === null || _a === void 0 ? void 0 : _a.removeStatsReportGatherer(this.callId); + this.emit(CallEvent.Hangup, this); + } + }, invite.lifetime - event.getLocalAge()); + const onState = (state) => { + if (state !== CallState.Ringing) { + clearTimeout(ringingTimer); + this.off(CallEvent.State, onState); + } + }; + this.on(CallEvent.State, onState); + } + }); + } + /** + * Configure this call from a hangup or reject event. Used by MatrixClient. + * @param event - The m.call.hangup event + */ + initWithHangup(event) { + // perverse as it may seem, sometimes we want to instantiate a call with a + // hangup message (because when getting the state of the room on load, events + // come in reverse order and we want to remember that a call has been hung up) + this.state = CallState.Ended; + } + shouldAnswerWithMediaType(wantedValue, valueOfTheOtherSide, type) { + if (wantedValue && !valueOfTheOtherSide) { + // TODO: Figure out how to do this + logger_1.logger.warn(`Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type} because the other side isn't sending it either.`); + return false; + } + else if (!utils.isNullOrUndefined(wantedValue) && + wantedValue !== valueOfTheOtherSide && + !this.opponentSupportsSDPStreamMetadata()) { + logger_1.logger.warn(`Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type}=${wantedValue} because the other side doesn't support it. Answering with ${type}=${valueOfTheOtherSide}.`); + return valueOfTheOtherSide; + } + return wantedValue !== null && wantedValue !== void 0 ? wantedValue : valueOfTheOtherSide; + } + /** + * Answer a call. + */ + answer(audio, video) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (this.inviteOrAnswerSent) + return; + // TODO: Figure out how to do this + if (audio === false && video === false) + throw new Error("You CANNOT answer a call without media"); + if (!this.localUsermediaStream && !this.waitForLocalAVStream) { + const prevState = this.state; + const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, "audio"); + const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, "video"); + this.state = CallState.WaitLocalMedia; + this.waitForLocalAVStream = true; + try { + const stream = yield this.client.getMediaHandler().getUserMediaStream(answerWithAudio, answerWithVideo); + this.waitForLocalAVStream = false; + const usermediaFeed = new callFeed_1.CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.client.getUserId(), + deviceId: (_a = this.client.getDeviceId()) !== null && _a !== void 0 ? _a : undefined, + stream, + purpose: callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, + audioMuted: false, + videoMuted: false, + }); + const feeds = [usermediaFeed]; + if (this.localScreensharingFeed) { + feeds.push(this.localScreensharingFeed); + } + this.answerWithCallFeeds(feeds); + } + catch (e) { + if (answerWithVideo) { + // Try to answer without video + logger_1.logger.warn(`Call ${this.callId} answer() failed to getUserMedia(), trying to getUserMedia() without video`); + this.state = prevState; + this.waitForLocalAVStream = false; + yield this.answer(answerWithAudio, false); + } + else { + this.getUserMediaFailed(e); + return; + } + } + } + else if (this.waitForLocalAVStream) { + this.state = CallState.WaitLocalMedia; + } + }); + } + answerWithCallFeeds(callFeeds) { + if (this.inviteOrAnswerSent) + return; + this.queueGotCallFeedsForAnswer(callFeeds); + } + /** + * Replace this call with a new call, e.g. for glare resolution. Used by + * MatrixClient. + * @param newCall - The new call. + */ + replacedBy(newCall) { + logger_1.logger.debug(`Call ${this.callId} replacedBy() running (newCallId=${newCall.callId})`); + if (this.state === CallState.WaitLocalMedia) { + logger_1.logger.debug(`Call ${this.callId} replacedBy() telling new call to wait for local media (newCallId=${newCall.callId})`); + newCall.waitForLocalAVStream = true; + } + else if ([CallState.CreateOffer, CallState.InviteSent].includes(this.state)) { + if (newCall.direction === CallDirection.Outbound) { + newCall.queueGotCallFeedsForAnswer([]); + } + else { + logger_1.logger.debug(`Call ${this.callId} replacedBy() handing local stream to new call(newCallId=${newCall.callId})`); + newCall.queueGotCallFeedsForAnswer(this.getLocalFeeds().map((feed) => feed.clone())); + } + } + this.successor = newCall; + this.emit(CallEvent.Replaced, newCall, this); + this.hangup(CallErrorCode.Replaced, true); + } + /** + * Hangup a call. + * @param reason - The reason why the call is being hung up. + * @param suppressEvent - True to suppress emitting an event. + */ + hangup(reason, suppressEvent) { + if (this.callHasEnded()) + return; + logger_1.logger.debug(`Call ${this.callId} hangup() ending call (reason=${reason})`); + this.terminate(CallParty.Local, reason, !suppressEvent); + // We don't want to send hangup here if we didn't even get to sending an invite + if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) + return; + const content = {}; + // Don't send UserHangup reason to older clients + if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) { + content["reason"] = reason; + } + this.sendVoipEvent(event_1.EventType.CallHangup, content); + } + /** + * Reject a call + * This used to be done by calling hangup, but is a separate method and protocol + * event as of MSC2746. + */ + reject() { + if (this.state !== CallState.Ringing) { + throw Error("Call must be in 'ringing' state to reject!"); + } + if (this.opponentVersion === 0) { + logger_1.logger.info(`Call ${this.callId} reject() opponent version is less than 1: sending hangup instead of reject (opponentVersion=${this.opponentVersion})`); + this.hangup(CallErrorCode.UserHangup, true); + return; + } + logger_1.logger.debug("Rejecting call: " + this.callId); + this.terminate(CallParty.Local, CallErrorCode.UserHangup, true); + this.sendVoipEvent(event_1.EventType.CallReject, {}); + } + /** + * Adds an audio and/or video track - upgrades the call + * @param audio - should add an audio track + * @param video - should add an video track + */ + upgradeCall(audio, video) { + return __awaiter(this, void 0, void 0, function* () { + // We don't do call downgrades + if (!audio && !video) + return; + if (!this.opponentSupportsSDPStreamMetadata()) + return; + try { + logger_1.logger.debug(`Call ${this.callId} upgradeCall() upgrading call (audio=${audio}, video=${video})`); + const getAudio = audio || this.hasLocalUserMediaAudioTrack; + const getVideo = video || this.hasLocalUserMediaVideoTrack; + // updateLocalUsermediaStream() will take the tracks, use them as + // replacement and throw the stream away, so it isn't reusable + const stream = yield this.client.getMediaHandler().getUserMediaStream(getAudio, getVideo, false); + yield this.updateLocalUsermediaStream(stream, audio, video); + } + catch (error) { + logger_1.logger.error(`Call ${this.callId} upgradeCall() failed to upgrade the call`, error); + this.emit(CallEvent.Error, new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", error), this); + } + }); + } + /** + * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false + * @returns can screenshare + */ + opponentSupportsSDPStreamMetadata() { + return Boolean(this.remoteSDPStreamMetadata); + } + /** + * If there is a screensharing stream returns true, otherwise returns false + * @returns is screensharing + */ + isScreensharing() { + return Boolean(this.localScreensharingStream); + } + /** + * Starts/stops screensharing + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + setScreensharingEnabled(enabled, opts) { + return __awaiter(this, void 0, void 0, function* () { + // Skip if there is nothing to do + if (enabled && this.isScreensharing()) { + logger_1.logger.warn(`Call ${this.callId} setScreensharingEnabled() there is already a screensharing stream - there is nothing to do!`); + return true; + } + else if (!enabled && !this.isScreensharing()) { + logger_1.logger.warn(`Call ${this.callId} setScreensharingEnabled() there already isn't a screensharing stream - there is nothing to do!`); + return false; + } + // Fallback to replaceTrack() + if (!this.opponentSupportsSDPStreamMetadata()) { + return this.setScreensharingEnabledWithoutMetadataSupport(enabled, opts); + } + logger_1.logger.debug(`Call ${this.callId} setScreensharingEnabled() running (enabled=${enabled})`); + if (enabled) { + try { + const stream = yield this.client.getMediaHandler().getScreensharingStream(opts); + if (!stream) + return false; + this.pushNewLocalFeed(stream, callEventTypes_1.SDPStreamMetadataPurpose.Screenshare); + return true; + } + catch (err) { + logger_1.logger.error(`Call ${this.callId} setScreensharingEnabled() failed to get screen-sharing stream:`, err); + return false; + } + } + else { + const audioTransceiver = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Screenshare, "audio")); + const videoTransceiver = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Screenshare, "video")); + for (const transceiver of [audioTransceiver, videoTransceiver]) { + // this is slightly mixing the track and transceiver API but is basically just shorthand + // for removing the sender. + if (transceiver && transceiver.sender) + this.peerConn.removeTrack(transceiver.sender); + } + this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream); + this.deleteFeedByStream(this.localScreensharingStream); + return false; + } + }); + } + /** + * Starts/stops screensharing + * Should be used ONLY if the opponent doesn't support SDPStreamMetadata + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + setScreensharingEnabledWithoutMetadataSupport(enabled, opts) { + var _a, _b, _c; + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.debug(`Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() running (enabled=${enabled})`); + if (enabled) { + try { + const stream = yield this.client.getMediaHandler().getScreensharingStream(opts); + if (!stream) + return false; + const track = stream.getTracks().find((track) => track.kind === "video"); + const sender = (_a = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _a === void 0 ? void 0 : _a.sender; + sender === null || sender === void 0 ? void 0 : sender.replaceTrack(track !== null && track !== void 0 ? track : null); + this.pushNewLocalFeed(stream, callEventTypes_1.SDPStreamMetadataPurpose.Screenshare, false); + return true; + } + catch (err) { + logger_1.logger.error(`Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() failed to get screen-sharing stream:`, err); + return false; + } + } + else { + const track = (_b = this.localUsermediaStream) === null || _b === void 0 ? void 0 : _b.getTracks().find((track) => track.kind === "video"); + const sender = (_c = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _c === void 0 ? void 0 : _c.sender; + sender === null || sender === void 0 ? void 0 : sender.replaceTrack(track !== null && track !== void 0 ? track : null); + this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream); + this.deleteFeedByStream(this.localScreensharingStream); + return false; + } + }); + } + /** + * Replaces/adds the tracks from the passed stream to the localUsermediaStream + * @param stream - to use a replacement for the local usermedia stream + */ + updateLocalUsermediaStream(stream, forceAudio = false, forceVideo = false) { + return __awaiter(this, void 0, void 0, function* () { + const callFeed = this.localUsermediaFeed; + const audioEnabled = forceAudio || (!callFeed.isAudioMuted() && !this.remoteOnHold); + const videoEnabled = forceVideo || (!callFeed.isVideoMuted() && !this.remoteOnHold); + logger_1.logger.log(`Call ${this.callId} updateLocalUsermediaStream() running (streamId=${stream.id}, audio=${audioEnabled}, video=${videoEnabled})`); + setTracksEnabled(stream.getAudioTracks(), audioEnabled); + setTracksEnabled(stream.getVideoTracks(), videoEnabled); + // We want to keep the same stream id, so we replace the tracks rather + // than the whole stream. + // Firstly, we replace the tracks in our localUsermediaStream. + for (const track of this.localUsermediaStream.getTracks()) { + this.localUsermediaStream.removeTrack(track); + track.stop(); + } + for (const track of stream.getTracks()) { + this.localUsermediaStream.addTrack(track); + } + // Then replace the old tracks, if possible. + for (const track of stream.getTracks()) { + const tKey = getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, track.kind); + const transceiver = this.transceivers.get(tKey); + const oldSender = transceiver === null || transceiver === void 0 ? void 0 : transceiver.sender; + let added = false; + if (oldSender) { + try { + logger_1.logger.info(`Call ${this.callId} updateLocalUsermediaStream() replacing track (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`); + yield oldSender.replaceTrack(track); + // Set the direction to indicate we're going to be sending. + // This is only necessary in the cases where we're upgrading + // the call to video after downgrading it. + transceiver.direction = transceiver.direction === "inactive" ? "sendonly" : "sendrecv"; + added = true; + } + catch (error) { + logger_1.logger.warn(`Call ${this.callId} updateLocalUsermediaStream() replaceTrack failed: adding new transceiver instead`, error); + } + } + if (!added) { + logger_1.logger.info(`Call ${this.callId} updateLocalUsermediaStream() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`); + const newSender = this.peerConn.addTrack(track, this.localUsermediaStream); + const newTransceiver = this.peerConn.getTransceivers().find((t) => t.sender === newSender); + if (newTransceiver) { + this.transceivers.set(tKey, newTransceiver); + } + else { + logger_1.logger.warn(`Call ${this.callId} updateLocalUsermediaStream() couldn't find matching transceiver for newly added track!`); + } + } + } + }); + } + /** + * Set whether our outbound video should be muted or not. + * @param muted - True to mute the outbound video. + * @returns the new mute state + */ + setLocalVideoMuted(muted) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`Call ${this.callId} setLocalVideoMuted() running ${muted}`); + // if we were still thinking about stopping and removing the video + // track: don't, because we want it back. + if (!muted && this.stopVideoTrackTimer !== undefined) { + clearTimeout(this.stopVideoTrackTimer); + this.stopVideoTrackTimer = undefined; + } + if (!(yield this.client.getMediaHandler().hasVideoDevice())) { + return this.isLocalVideoMuted(); + } + if (!this.hasUserMediaVideoSender && !muted) { + (_a = this.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.setAudioVideoMuted(null, muted); + yield this.upgradeCall(false, true); + return this.isLocalVideoMuted(); + } + // we may not have a video track - if not, re-request usermedia + if (!muted && this.localUsermediaStream.getVideoTracks().length === 0) { + const stream = yield this.client.getMediaHandler().getUserMediaStream(true, true); + yield this.updateLocalUsermediaStream(stream); + } + (_b = this.localUsermediaFeed) === null || _b === void 0 ? void 0 : _b.setAudioVideoMuted(null, muted); + this.updateMuteStatus(); + yield this.sendMetadataUpdate(); + // if we're muting video, set a timeout to stop & remove the video track so we release + // the camera. We wait a short time to do this because when we disable a track, WebRTC + // will send black video for it. If we just stop and remove it straight away, the video + // will just freeze which means that when we unmute video, the other side will briefly + // get a static frame of us from before we muted. This way, the still frame is just black. + // A very small delay is not always enough so the theory here is that it needs to be long + // enough for WebRTC to encode a frame: 120ms should be long enough even if we're only + // doing 10fps. + if (muted) { + this.stopVideoTrackTimer = setTimeout(() => { + for (const t of this.localUsermediaStream.getVideoTracks()) { + t.stop(); + this.localUsermediaStream.removeTrack(t); + } + }, 120); + } + return this.isLocalVideoMuted(); + }); + } + /** + * Check if local video is muted. + * + * If there are multiple video tracks, all of the tracks need to be muted + * for this to return true. This means if there are no video tracks, this will + * return true. + * @returns True if the local preview video is muted, else false + * (including if the call is not set up yet). + */ + isLocalVideoMuted() { + var _a, _b; + return (_b = (_a = this.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.isVideoMuted()) !== null && _b !== void 0 ? _b : false; + } + /** + * Set whether the microphone should be muted or not. + * @param muted - True to mute the mic. + * @returns the new mute state + */ + setMicrophoneMuted(muted) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`Call ${this.callId} setMicrophoneMuted() running ${muted}`); + if (!(yield this.client.getMediaHandler().hasAudioDevice())) { + return this.isMicrophoneMuted(); + } + if (!muted && (!this.hasUserMediaAudioSender || !this.hasLocalUserMediaAudioTrack)) { + yield this.upgradeCall(true, false); + return this.isMicrophoneMuted(); + } + (_a = this.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.setAudioVideoMuted(muted, null); + this.updateMuteStatus(); + yield this.sendMetadataUpdate(); + return this.isMicrophoneMuted(); + }); + } + /** + * Check if the microphone is muted. + * + * If there are multiple audio tracks, all of the tracks need to be muted + * for this to return true. This means if there are no audio tracks, this will + * return true. + * @returns True if the mic is muted, else false (including if the call + * is not set up yet). + */ + isMicrophoneMuted() { + var _a, _b; + return (_b = (_a = this.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.isAudioMuted()) !== null && _b !== void 0 ? _b : false; + } + /** + * @returns true if we have put the party on the other side of the call on hold + * (that is, we are signalling to them that we are not listening) + */ + isRemoteOnHold() { + return this.remoteOnHold; + } + setRemoteOnHold(onHold) { + if (this.isRemoteOnHold() === onHold) + return; + this.remoteOnHold = onHold; + for (const transceiver of this.peerConn.getTransceivers()) { + // We don't send hold music or anything so we're not actually + // sending anything, but sendrecv is fairly standard for hold and + // it makes it a lot easier to figure out who's put who on hold. + transceiver.direction = onHold ? "sendonly" : "sendrecv"; + } + this.updateMuteStatus(); + this.sendMetadataUpdate(); + this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold, this); + } + /** + * Indicates whether we are 'on hold' to the remote party (ie. if true, + * they cannot hear us). + * @returns true if the other party has put us on hold + */ + isLocalOnHold() { + if (this.state !== CallState.Connected) + return false; + let callOnHold = true; + // We consider a call to be on hold only if *all* the tracks are on hold + // (is this the right thing to do?) + for (const transceiver of this.peerConn.getTransceivers()) { + const trackOnHold = ["inactive", "recvonly"].includes(transceiver.currentDirection); + if (!trackOnHold) + callOnHold = false; + } + return callOnHold; + } + /** + * Sends a DTMF digit to the other party + * @param digit - The digit (nb. string - '#' and '*' are dtmf too) + */ + sendDtmfDigit(digit) { + var _a; + for (const sender of this.peerConn.getSenders()) { + if (((_a = sender.track) === null || _a === void 0 ? void 0 : _a.kind) === "audio" && sender.dtmf) { + sender.dtmf.insertDTMF(digit); + return; + } + } + throw new Error("Unable to find a track to send DTMF on"); + } + updateMuteStatus() { + const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold; + const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold; + logger_1.logger.log(`Call ${this.callId} updateMuteStatus stream ${this.localUsermediaStream.id} micShouldBeMuted ${micShouldBeMuted} vidShouldBeMuted ${vidShouldBeMuted}`); + setTracksEnabled(this.localUsermediaStream.getAudioTracks(), !micShouldBeMuted); + setTracksEnabled(this.localUsermediaStream.getVideoTracks(), !vidShouldBeMuted); + } + sendMetadataUpdate() { + return __awaiter(this, void 0, void 0, function* () { + yield this.sendVoipEvent(event_1.EventType.CallSDPStreamMetadataChangedPrefix, { + [callEventTypes_1.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(), + }); + }); + } + gotCallFeedsForInvite(callFeeds, requestScreenshareFeed = false) { + if (this.successor) { + this.successor.queueGotCallFeedsForAnswer(callFeeds); + return; + } + if (this.callHasEnded()) { + this.stopAllMedia(); + return; + } + for (const feed of callFeeds) { + this.pushLocalFeed(feed); + } + if (requestScreenshareFeed) { + this.peerConn.addTransceiver("video", { + direction: "recvonly", + }); + } + this.state = CallState.CreateOffer; + logger_1.logger.debug(`Call ${this.callId} gotUserMediaForInvite() run`); + // Now we wait for the negotiationneeded event + } + sendAnswer() { + return __awaiter(this, void 0, void 0, function* () { + const answerContent = { + answer: { + sdp: this.peerConn.localDescription.sdp, + // type is now deprecated as of Matrix VoIP v1, but + // required to still be sent for backwards compat + type: this.peerConn.localDescription.type, + }, + [callEventTypes_1.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true), + }; + answerContent.capabilities = { + "m.call.transferee": this.client.supportsCallTransfer, + "m.call.dtmf": false, + }; + // We have just taken the local description from the peerConn which will + // contain all the local candidates added so far, so we can discard any candidates + // we had queued up because they'll be in the answer. + const discardCount = this.discardDuplicateCandidates(); + logger_1.logger.info(`Call ${this.callId} sendAnswer() discarding ${discardCount} candidates that will be sent in answer`); + try { + yield this.sendVoipEvent(event_1.EventType.CallAnswer, answerContent); + // If this isn't the first time we've tried to send the answer, + // we may have candidates queued up, so send them now. + this.inviteOrAnswerSent = true; + } + catch (error) { + // We've failed to answer: back to the ringing state + this.state = CallState.Ringing; + if (error instanceof http_api_1.MatrixError && error.event) + this.client.cancelPendingEvent(error.event); + let code = CallErrorCode.SendAnswer; + let message = "Failed to send answer"; + if (error.name == "UnknownDeviceError") { + code = CallErrorCode.UnknownDevices; + message = "Unknown devices present in the room"; + } + this.emit(CallEvent.Error, new CallError(code, message, error), this); + throw error; + } + // error handler re-throws so this won't happen on error, but + // we don't want the same error handling on the candidate queue + this.sendCandidateQueue(); + }); + } + queueGotCallFeedsForAnswer(callFeeds) { + // Ensure only one negotiate/answer event is being processed at a time. + if (this.responsePromiseChain) { + this.responsePromiseChain = this.responsePromiseChain.then(() => this.gotCallFeedsForAnswer(callFeeds)); + } + else { + this.responsePromiseChain = this.gotCallFeedsForAnswer(callFeeds); + } + } + // Enables DTX (discontinuous transmission) on the given session to reduce + // bandwidth when transmitting silence + mungeSdp(description, mods) { + // The only way to enable DTX at this time is through SDP munging + const sdp = (0, sdp_transform_1.parse)(description.sdp); + sdp.media.forEach((media) => { + const payloadTypeToCodecMap = new Map(); + const codecToPayloadTypeMap = new Map(); + for (const rtp of media.rtp) { + payloadTypeToCodecMap.set(rtp.payload, rtp.codec); + codecToPayloadTypeMap.set(rtp.codec, rtp.payload); + } + for (const mod of mods) { + if (mod.mediaType !== media.type) + continue; + if (!codecToPayloadTypeMap.has(mod.codec)) { + logger_1.logger.info(`Call ${this.callId} mungeSdp() ignoring SDP modifications for ${mod.codec} as it's not present.`); + continue; + } + const extraConfig = []; + if (mod.enableDtx !== undefined) { + extraConfig.push(`usedtx=${mod.enableDtx ? "1" : "0"}`); + } + if (mod.maxAverageBitrate !== undefined) { + extraConfig.push(`maxaveragebitrate=${mod.maxAverageBitrate}`); + } + let found = false; + for (const fmtp of media.fmtp) { + if (payloadTypeToCodecMap.get(fmtp.payload) === mod.codec) { + found = true; + fmtp.config += ";" + extraConfig.join(";"); + } + } + if (!found) { + media.fmtp.push({ + payload: codecToPayloadTypeMap.get(mod.codec), + config: extraConfig.join(";"), + }); + } + } + }); + description.sdp = (0, sdp_transform_1.write)(sdp); + } + createOffer() { + return __awaiter(this, void 0, void 0, function* () { + const offer = yield this.peerConn.createOffer(); + this.mungeSdp(offer, getCodecParamMods(this.isPtt)); + return offer; + }); + } + createAnswer() { + return __awaiter(this, void 0, void 0, function* () { + const answer = yield this.peerConn.createAnswer(); + this.mungeSdp(answer, getCodecParamMods(this.isPtt)); + return answer; + }); + } + gotCallFeedsForAnswer(callFeeds) { + return __awaiter(this, void 0, void 0, function* () { + if (this.callHasEnded()) + return; + this.waitForLocalAVStream = false; + for (const feed of callFeeds) { + this.pushLocalFeed(feed); + } + this.state = CallState.CreateAnswer; + let answer; + try { + this.getRidOfRTXCodecs(); + answer = yield this.createAnswer(); + } + catch (err) { + logger_1.logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() failed to create answer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true); + return; + } + try { + yield this.peerConn.setLocalDescription(answer); + // make sure we're still going + if (this.callHasEnded()) + return; + this.state = CallState.Connecting; + // Allow a short time for initial candidates to be gathered + yield new Promise((resolve) => { + setTimeout(resolve, 200); + }); + // make sure the call hasn't ended before we continue + if (this.callHasEnded()) + return; + this.sendAnswer(); + } + catch (err) { + logger_1.logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() error setting local description!`, err); + this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true); + return; + } + }); + } + onRemoteIceCandidatesReceived(ev) { + return __awaiter(this, void 0, void 0, function* () { + if (this.callHasEnded()) { + //debuglog("Ignoring remote ICE candidate because call has ended"); + return; + } + const content = ev.getContent(); + const candidates = content.candidates; + if (!candidates) { + logger_1.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates event with no candidates!`); + return; + } + const fromPartyId = content.version === 0 ? null : content.party_id || null; + if (this.opponentPartyId === undefined) { + // we haven't picked an opponent yet so save the candidates + if (fromPartyId) { + logger_1.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() buffering ${candidates.length} candidates until we pick an opponent`); + const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || []; + bufferedCandidates.push(...candidates); + this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates); + } + return; + } + if (!this.partyIdMatches(content)) { + logger_1.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates from party ID ${content.party_id}: we have chosen party ID ${this.opponentPartyId}`); + return; + } + yield this.addIceCandidates(candidates); + }); + } + /** + * Used by MatrixClient. + */ + onAnswerReceived(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + logger_1.logger.debug(`Call ${this.callId} onAnswerReceived() running (hangupParty=${content.party_id})`); + if (this.callHasEnded()) { + logger_1.logger.debug(`Call ${this.callId} onAnswerReceived() ignoring answer because call has ended`); + return; + } + if (this.opponentPartyId !== undefined) { + logger_1.logger.info(`Call ${this.callId} onAnswerReceived() ignoring answer from party ID ${content.party_id}: we already have an answer/reject from ${this.opponentPartyId}`); + return; + } + this.chooseOpponent(event); + yield this.addBufferedIceCandidates(); + this.state = CallState.Connecting; + const sdpStreamMetadata = content[callEventTypes_1.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } + else { + logger_1.logger.warn(`Call ${this.callId} onAnswerReceived() did not get any SDPStreamMetadata! Can not send/receive multiple streams`); + } + try { + yield this.peerConn.setRemoteDescription(content.answer); + } + catch (e) { + logger_1.logger.debug(`Call ${this.callId} onAnswerReceived() failed to set remote description`, e); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + // If the answer we selected has a party_id, send a select_answer event + // We do this after setting the remote description since otherwise we'd block + // call setup on it + if (this.opponentPartyId !== null) { + try { + yield this.sendVoipEvent(event_1.EventType.CallSelectAnswer, { + selected_party_id: this.opponentPartyId, + }); + } + catch (err) { + // This isn't fatal, and will just mean that if another party has raced to answer + // the call, they won't know they got rejected, so we carry on & don't retry. + logger_1.logger.warn(`Call ${this.callId} onAnswerReceived() failed to send select_answer event`, err); + } + } + }); + } + onSelectAnswerReceived(event) { + return __awaiter(this, void 0, void 0, function* () { + if (this.direction !== CallDirection.Inbound) { + logger_1.logger.warn(`Call ${this.callId} onSelectAnswerReceived() got select_answer for an outbound call: ignoring`); + return; + } + const selectedPartyId = event.getContent().selected_party_id; + if (selectedPartyId === undefined || selectedPartyId === null) { + logger_1.logger.warn(`Call ${this.callId} onSelectAnswerReceived() got nonsensical select_answer with null/undefined selected_party_id: ignoring`); + return; + } + if (selectedPartyId !== this.ourPartyId) { + logger_1.logger.info(`Call ${this.callId} onSelectAnswerReceived() got select_answer for party ID ${selectedPartyId}: we are party ID ${this.ourPartyId}.`); + // The other party has picked somebody else's answer + yield this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true); + } + }); + } + onNegotiateReceived(event) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + const description = content.description; + if (!description || !description.sdp || !description.type) { + logger_1.logger.info(`Call ${this.callId} onNegotiateReceived() ignoring invalid m.call.negotiate event`); + return; + } + // Politeness always follows the direction of the call: in a glare situation, + // we pick either the inbound or outbound call, so one side will always be + // inbound and one outbound + const polite = this.direction === CallDirection.Inbound; + // Here we follow the perfect negotiation logic from + // https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation + const offerCollision = description.type === "offer" && (this.makingOffer || this.peerConn.signalingState !== "stable"); + this.ignoreOffer = !polite && offerCollision; + if (this.ignoreOffer) { + logger_1.logger.info(`Call ${this.callId} onNegotiateReceived() ignoring colliding negotiate event because we're impolite`); + return; + } + const prevLocalOnHold = this.isLocalOnHold(); + const sdpStreamMetadata = content[callEventTypes_1.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } + else { + logger_1.logger.warn(`Call ${this.callId} onNegotiateReceived() received negotiation event without SDPStreamMetadata!`); + } + try { + yield this.peerConn.setRemoteDescription(description); + if (description.type === "offer") { + let answer; + try { + this.getRidOfRTXCodecs(); + answer = yield this.createAnswer(); + } + catch (err) { + logger_1.logger.debug(`Call ${this.callId} onNegotiateReceived() failed to create answer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true); + return; + } + yield this.peerConn.setLocalDescription(answer); + this.sendVoipEvent(event_1.EventType.CallNegotiate, { + description: (_a = this.peerConn.localDescription) === null || _a === void 0 ? void 0 : _a.toJSON(), + [callEventTypes_1.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true), + }); + } + } + catch (err) { + logger_1.logger.warn(`Call ${this.callId} onNegotiateReceived() failed to complete negotiation`, err); + } + const newLocalOnHold = this.isLocalOnHold(); + if (prevLocalOnHold !== newLocalOnHold) { + this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold, this); + // also this one for backwards compat + this.emit(CallEvent.HoldUnhold, newLocalOnHold); + } + }); + } + updateRemoteSDPStreamMetadata(metadata) { + var _a; + this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true); + for (const feed of this.getRemoteFeeds()) { + const streamId = feed.stream.id; + const metadata = this.remoteSDPStreamMetadata[streamId]; + feed.setAudioVideoMuted(metadata === null || metadata === void 0 ? void 0 : metadata.audio_muted, metadata === null || metadata === void 0 ? void 0 : metadata.video_muted); + feed.purpose = (_a = this.remoteSDPStreamMetadata[streamId]) === null || _a === void 0 ? void 0 : _a.purpose; + } + } + onSDPStreamMetadataChangedReceived(event) { + const content = event.getContent(); + const metadata = content[callEventTypes_1.SDPStreamMetadataKey]; + this.updateRemoteSDPStreamMetadata(metadata); + } + onAssertedIdentityReceived(event) { + return __awaiter(this, void 0, void 0, function* () { + const content = event.getContent(); + if (!content.asserted_identity) + return; + this.remoteAssertedIdentity = { + id: content.asserted_identity.id, + displayName: content.asserted_identity.display_name, + }; + this.emit(CallEvent.AssertedIdentityChanged, this); + }); + } + callHasEnded() { + // This exists as workaround to typescript trying to be clever and erroring + // when putting if (this.state === CallState.Ended) return; twice in the same + // function, even though that function is async. + return this.state === CallState.Ended; + } + queueGotLocalOffer() { + // Ensure only one negotiate/answer event is being processed at a time. + if (this.responsePromiseChain) { + this.responsePromiseChain = this.responsePromiseChain.then(() => this.wrappedGotLocalOffer()); + } + else { + this.responsePromiseChain = this.wrappedGotLocalOffer(); + } + } + wrappedGotLocalOffer() { + return __awaiter(this, void 0, void 0, function* () { + this.makingOffer = true; + try { + // XXX: in what situations do we believe gotLocalOffer actually throws? It appears + // to handle most of its exceptions itself and terminate the call. I'm not entirely + // sure it would ever throw, so I can't add a test for these lines. + // Also the tense is different between "gotLocalOffer" and "getLocalOfferFailed" so + // it's not entirely clear whether getLocalOfferFailed is just misnamed or whether + // they've been cross-polinated somehow at some point. + yield this.gotLocalOffer(); + } + catch (e) { + this.getLocalOfferFailed(e); + return; + } + finally { + this.makingOffer = false; + } + }); + } + gotLocalOffer() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.debug(`Call ${this.callId} gotLocalOffer() running`); + if (this.callHasEnded()) { + logger_1.logger.debug(`Call ${this.callId} gotLocalOffer() ignoring newly created offer because the call has ended"`); + return; + } + let offer; + try { + this.getRidOfRTXCodecs(); + offer = yield this.createOffer(); + } + catch (err) { + logger_1.logger.debug(`Call ${this.callId} gotLocalOffer() failed to create offer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateOffer, true); + return; + } + try { + yield this.peerConn.setLocalDescription(offer); + } + catch (err) { + logger_1.logger.debug(`Call ${this.callId} gotLocalOffer() error setting local description!`, err); + this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true); + return; + } + if (this.peerConn.iceGatheringState === "gathering") { + // Allow a short time for initial candidates to be gathered + yield new Promise((resolve) => { + setTimeout(resolve, 200); + }); + } + if (this.callHasEnded()) + return; + const eventType = this.state === CallState.CreateOffer ? event_1.EventType.CallInvite : event_1.EventType.CallNegotiate; + const content = { + lifetime: CALL_TIMEOUT_MS, + }; + if (eventType === event_1.EventType.CallInvite && this.invitee) { + content.invitee = this.invitee; + } + // clunky because TypeScript can't follow the types through if we use an expression as the key + if (this.state === CallState.CreateOffer) { + content.offer = (_a = this.peerConn.localDescription) === null || _a === void 0 ? void 0 : _a.toJSON(); + } + else { + content.description = (_b = this.peerConn.localDescription) === null || _b === void 0 ? void 0 : _b.toJSON(); + } + content.capabilities = { + "m.call.transferee": this.client.supportsCallTransfer, + "m.call.dtmf": false, + }; + content[callEventTypes_1.SDPStreamMetadataKey] = this.getLocalSDPStreamMetadata(true); + // Get rid of any candidates waiting to be sent: they'll be included in the local + // description we just got and will send in the offer. + const discardCount = this.discardDuplicateCandidates(); + logger_1.logger.info(`Call ${this.callId} gotLocalOffer() discarding ${discardCount} candidates that will be sent in offer`); + try { + yield this.sendVoipEvent(eventType, content); + } + catch (error) { + logger_1.logger.error(`Call ${this.callId} gotLocalOffer() failed to send invite`, error); + if (error instanceof http_api_1.MatrixError && error.event) + this.client.cancelPendingEvent(error.event); + let code = CallErrorCode.SignallingFailed; + let message = "Signalling failed"; + if (this.state === CallState.CreateOffer) { + code = CallErrorCode.SendInvite; + message = "Failed to send invite"; + } + if (error.name == "UnknownDeviceError") { + code = CallErrorCode.UnknownDevices; + message = "Unknown devices present in the room"; + } + this.emit(CallEvent.Error, new CallError(code, message, error), this); + this.terminate(CallParty.Local, code, false); + // no need to carry on & send the candidate queue, but we also + // don't want to rethrow the error + return; + } + this.sendCandidateQueue(); + if (this.state === CallState.CreateOffer) { + this.inviteOrAnswerSent = true; + this.state = CallState.InviteSent; + this.inviteTimeout = setTimeout(() => { + this.inviteTimeout = undefined; + if (this.state === CallState.InviteSent) { + this.hangup(CallErrorCode.InviteTimeout, false); + } + }, CALL_TIMEOUT_MS); + } + }); + } + /** + * This method removes all video/rtx codecs from screensharing video + * transceivers. This is necessary since they can cause problems. Without + * this the following steps should produce an error: + * Chromium calls Firefox + * Firefox answers + * Firefox starts screen-sharing + * Chromium starts screen-sharing + * Call crashes for Chromium with: + * [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list. + * [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs. + * [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER) + */ + getRidOfRTXCodecs() { + // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF + if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) + return; + const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs; + const sendCodecs = RTCRtpSender.getCapabilities("video").codecs; + const codecs = [...sendCodecs, ...recvCodecs]; + for (const codec of codecs) { + if (codec.mimeType === "video/rtx") { + const rtxCodecIndex = codecs.indexOf(codec); + codecs.splice(rtxCodecIndex, 1); + } + } + const screenshareVideoTransceiver = this.transceivers.get(getTransceiverKey(callEventTypes_1.SDPStreamMetadataPurpose.Screenshare, "video")); + if (screenshareVideoTransceiver) + screenshareVideoTransceiver.setCodecPreferences(codecs); + } + /** + * @internal + */ + sendVoipEvent(eventType, content) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const realContent = Object.assign({}, content, { + version: VOIP_PROTO_VERSION, + call_id: this.callId, + party_id: this.ourPartyId, + conf_id: this.groupCallId, + }); + if (this.opponentDeviceId) { + const toDeviceSeq = this.toDeviceSeq++; + const content = Object.assign(Object.assign({}, realContent), { device_id: this.client.deviceId, sender_session_id: this.client.getSessionId(), dest_session_id: this.opponentSessionId, seq: toDeviceSeq, [event_1.ToDeviceMessageId]: (0, uuid_1.v4)() }); + this.emit(CallEvent.SendVoipEvent, { + type: "toDevice", + eventType, + userId: this.invitee || ((_a = this.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId), + opponentDeviceId: this.opponentDeviceId, + content, + }, this); + const userId = this.invitee || this.getOpponentMember().userId; + if (this.client.getUseE2eForGroupCall()) { + if (!this.opponentDeviceInfo) { + logger_1.logger.warn(`Call ${this.callId} sendVoipEvent() failed: we do not have opponentDeviceInfo`); + return; + } + yield this.client.encryptAndSendToDevices([ + { + userId, + deviceInfo: this.opponentDeviceInfo, + }, + ], { + type: eventType, + content, + }); + } + else { + yield this.client.sendToDevice(eventType, new Map([[userId, new Map([[this.opponentDeviceId, content]])]])); + } + } + else { + this.emit(CallEvent.SendVoipEvent, { + type: "sendEvent", + eventType, + roomId: this.roomId, + content: realContent, + userId: this.invitee || ((_b = this.getOpponentMember()) === null || _b === void 0 ? void 0 : _b.userId), + }, this); + yield this.client.sendEvent(this.roomId, eventType, realContent); + } + }); + } + /** + * Queue a candidate to be sent + * @param content - The candidate to queue up, or null if candidates have finished being generated + * and end-of-candidates should be signalled + */ + queueCandidate(content) { + // We partially de-trickle candidates by waiting for `delay` before sending them + // amalgamated, in order to avoid sending too many m.call.candidates events and hitting + // rate limits in Matrix. + // In practice, it'd be better to remove rate limits for m.call.* + // N.B. this deliberately lets you queue and send blank candidates, which MSC2746 + // currently proposes as the way to indicate that candidate gathering is complete. + // This will hopefully be changed to an explicit rather than implicit notification + // shortly. + if (content) { + this.candidateSendQueue.push(content); + } + else { + this.candidatesEnded = true; + } + // Don't send the ICE candidates yet if the call is in the ringing state: this + // means we tried to pick (ie. started generating candidates) and then failed to + // send the answer and went back to the ringing state. Queue up the candidates + // to send if we successfully send the answer. + // Equally don't send if we haven't yet sent the answer because we can send the + // first batch of candidates along with the answer + if (this.state === CallState.Ringing || !this.inviteOrAnswerSent) + return; + // MSC2746 recommends these values (can be quite long when calling because the + // callee will need a while to answer the call) + const delay = this.direction === CallDirection.Inbound ? 500 : 2000; + if (this.candidateSendTries === 0) { + setTimeout(() => { + this.sendCandidateQueue(); + }, delay); + } + } + // Discard all non-end-of-candidates messages + // Return the number of candidate messages that were discarded. + // Call this method before sending an invite or answer message + discardDuplicateCandidates() { + let discardCount = 0; + const newQueue = []; + for (let i = 0; i < this.candidateSendQueue.length; i++) { + const candidate = this.candidateSendQueue[i]; + if (candidate.candidate === "") { + newQueue.push(candidate); + } + else { + discardCount++; + } + } + this.candidateSendQueue = newQueue; + return discardCount; + } + /* + * Transfers this call to another user + */ + transfer(targetUserId) { + return __awaiter(this, void 0, void 0, function* () { + // Fetch the target user's global profile info: their room avatar / displayname + // could be different in whatever room we share with them. + const profileInfo = yield this.client.getProfileInfo(targetUserId); + const replacementId = genCallID(); + const body = { + replacement_id: genCallID(), + target_user: { + id: targetUserId, + display_name: profileInfo.displayname, + avatar_url: profileInfo.avatar_url, + }, + create_call: replacementId, + }; + yield this.sendVoipEvent(event_1.EventType.CallReplaces, body); + yield this.terminate(CallParty.Local, CallErrorCode.Transferred, true); + }); + } + /* + * Transfers this call to the target call, effectively 'joining' the + * two calls (so the remote parties on each call are connected together). + */ + transferToCall(transferTargetCall) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const targetUserId = (_a = transferTargetCall.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId; + const targetProfileInfo = targetUserId ? yield this.client.getProfileInfo(targetUserId) : undefined; + const opponentUserId = (_b = this.getOpponentMember()) === null || _b === void 0 ? void 0 : _b.userId; + const transfereeProfileInfo = opponentUserId ? yield this.client.getProfileInfo(opponentUserId) : undefined; + const newCallId = genCallID(); + const bodyToTransferTarget = { + // the replacements on each side have their own ID, and it's distinct from the + // ID of the new call (but we can use the same function to generate it) + replacement_id: genCallID(), + target_user: { + id: opponentUserId, + display_name: transfereeProfileInfo === null || transfereeProfileInfo === void 0 ? void 0 : transfereeProfileInfo.displayname, + avatar_url: transfereeProfileInfo === null || transfereeProfileInfo === void 0 ? void 0 : transfereeProfileInfo.avatar_url, + }, + await_call: newCallId, + }; + yield transferTargetCall.sendVoipEvent(event_1.EventType.CallReplaces, bodyToTransferTarget); + const bodyToTransferee = { + replacement_id: genCallID(), + target_user: { + id: targetUserId, + display_name: targetProfileInfo === null || targetProfileInfo === void 0 ? void 0 : targetProfileInfo.displayname, + avatar_url: targetProfileInfo === null || targetProfileInfo === void 0 ? void 0 : targetProfileInfo.avatar_url, + }, + create_call: newCallId, + }; + yield this.sendVoipEvent(event_1.EventType.CallReplaces, bodyToTransferee); + yield this.terminate(CallParty.Local, CallErrorCode.Transferred, true); + yield transferTargetCall.terminate(CallParty.Local, CallErrorCode.Transferred, true); + }); + } + terminate(hangupParty, hangupReason, shouldEmit) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (this.callHasEnded()) + return; + this.hangupParty = hangupParty; + this.hangupReason = hangupReason; + this.state = CallState.Ended; + if (this.inviteTimeout) { + clearTimeout(this.inviteTimeout); + this.inviteTimeout = undefined; + } + if (this.iceDisconnectedTimeout !== undefined) { + clearTimeout(this.iceDisconnectedTimeout); + this.iceDisconnectedTimeout = undefined; + } + if (this.callLengthInterval) { + clearInterval(this.callLengthInterval); + this.callLengthInterval = undefined; + } + if (this.stopVideoTrackTimer !== undefined) { + clearTimeout(this.stopVideoTrackTimer); + this.stopVideoTrackTimer = undefined; + } + for (const [stream, listener] of this.removeTrackListeners) { + stream.removeEventListener("removetrack", listener); + } + this.removeTrackListeners.clear(); + this.callStatsAtEnd = yield this.collectCallStats(); + // Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds() + this.stopAllMedia(); + this.deleteAllFeeds(); + if (this.peerConn && this.peerConn.signalingState !== "closed") { + this.peerConn.close(); + } + (_a = this.stats) === null || _a === void 0 ? void 0 : _a.removeStatsReportGatherer(this.callId); + if (shouldEmit) { + this.emit(CallEvent.Hangup, this); + } + this.client.callEventHandler.calls.delete(this.callId); + }); + } + stopAllMedia() { + logger_1.logger.debug(`Call ${this.callId} stopAllMedia() running`); + for (const feed of this.feeds) { + // Slightly awkward as local feed need to go via the correct method on + // the MediaHandler so they get removed from MediaHandler (remote tracks + // don't) + // NB. We clone local streams when passing them to individual calls in a group + // call, so we can (and should) stop the clones once we no longer need them: + // the other clones will continue fine. + if (feed.isLocal() && feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia) { + this.client.getMediaHandler().stopUserMediaStream(feed.stream); + } + else if (feed.isLocal() && feed.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare) { + this.client.getMediaHandler().stopScreensharingStream(feed.stream); + } + else if (!feed.isLocal()) { + logger_1.logger.debug(`Call ${this.callId} stopAllMedia() stopping stream (streamId=${feed.stream.id})`); + for (const track of feed.stream.getTracks()) { + track.stop(); + } + } + } + } + checkForErrorListener() { + if (this.listeners(typed_event_emitter_1.EventEmitterEvents.Error).length === 0) { + throw new Error("You MUST attach an error listener using call.on('error', function() {})"); + } + } + sendCandidateQueue() { + return __awaiter(this, void 0, void 0, function* () { + if (this.candidateSendQueue.length === 0 || this.callHasEnded()) { + return; + } + const candidates = this.candidateSendQueue; + this.candidateSendQueue = []; + ++this.candidateSendTries; + const content = { candidates: candidates.map((candidate) => candidate.toJSON()) }; + if (this.candidatesEnded) { + // If there are no more candidates, signal this by adding an empty string candidate + content.candidates.push({ + candidate: "", + }); + } + logger_1.logger.debug(`Call ${this.callId} sendCandidateQueue() attempting to send ${candidates.length} candidates`); + try { + yield this.sendVoipEvent(event_1.EventType.CallCandidates, content); + // reset our retry count if we have successfully sent our candidates + // otherwise queueCandidate() will refuse to try to flush the queue + this.candidateSendTries = 0; + // Try to send candidates again just in case we received more candidates while sending. + this.sendCandidateQueue(); + } + catch (error) { + // don't retry this event: we'll send another one later as we might + // have more candidates by then. + if (error instanceof http_api_1.MatrixError && error.event) + this.client.cancelPendingEvent(error.event); + // put all the candidates we failed to send back in the queue + this.candidateSendQueue.push(...candidates); + if (this.candidateSendTries > 5) { + logger_1.logger.debug(`Call ${this.callId} sendCandidateQueue() failed to send candidates on attempt ${this.candidateSendTries}. Giving up on this call.`, error); + const code = CallErrorCode.SignallingFailed; + const message = "Signalling failed"; + this.emit(CallEvent.Error, new CallError(code, message, error), this); + this.hangup(code, false); + return; + } + const delayMs = 500 * Math.pow(2, this.candidateSendTries); + ++this.candidateSendTries; + logger_1.logger.debug(`Call ${this.callId} sendCandidateQueue() failed to send candidates. Retrying in ${delayMs}ms`, error); + setTimeout(() => { + this.sendCandidateQueue(); + }, delayMs); + } + }); + } + /** + * Place a call to this room. + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + placeCall(audio, video) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + if (!audio) { + throw new Error("You CANNOT start a call without audio"); + } + this.state = CallState.WaitLocalMedia; + try { + const stream = yield this.client.getMediaHandler().getUserMediaStream(audio, video); + // make sure all the tracks are enabled (same as pushNewLocalFeed - + // we probably ought to just have one code path for adding streams) + setTracksEnabled(stream.getAudioTracks(), true); + setTracksEnabled(stream.getVideoTracks(), true); + const callFeed = new callFeed_1.CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.client.getUserId(), + deviceId: (_a = this.client.getDeviceId()) !== null && _a !== void 0 ? _a : undefined, + stream, + purpose: callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, + audioMuted: false, + videoMuted: false, + }); + yield this.placeCallWithCallFeeds([callFeed]); + } + catch (e) { + this.getUserMediaFailed(e); + return; + } + }); + } + /** + * Place a call to this room with call feed. + * @param callFeeds - to use + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + placeCallWithCallFeeds(callFeeds, requestScreenshareFeed = false) { + return __awaiter(this, void 0, void 0, function* () { + this.checkForErrorListener(); + this.direction = CallDirection.Outbound; + yield this.initOpponentCrypto(); + // XXX Find a better way to do this + this.client.callEventHandler.calls.set(this.callId, this); + // make sure we have valid turn creds. Unless something's gone wrong, it should + // poll and keep the credentials valid so this should be instant. + const haveTurnCreds = yield this.client.checkTurnServers(); + if (!haveTurnCreds) { + logger_1.logger.warn(`Call ${this.callId} placeCallWithCallFeeds() failed to get TURN credentials! Proceeding with call anyway...`); + } + // create the peer connection now so it can be gathering candidates while we get user + // media (assuming a candidate pool size is configured) + this.peerConn = this.createPeerConnection(); + this.gotCallFeedsForInvite(callFeeds, requestScreenshareFeed); + }); + } + createPeerConnection() { + var _a; + const pc = new window.RTCPeerConnection({ + iceTransportPolicy: this.forceTURN ? "relay" : undefined, + iceServers: this.turnServers, + iceCandidatePoolSize: this.client.iceCandidatePoolSize, + bundlePolicy: "max-bundle", + }); + // 'connectionstatechange' would be better, but firefox doesn't implement that. + pc.addEventListener("iceconnectionstatechange", this.onIceConnectionStateChanged); + pc.addEventListener("signalingstatechange", this.onSignallingStateChanged); + pc.addEventListener("icecandidate", this.gotLocalIceCandidate); + pc.addEventListener("icegatheringstatechange", this.onIceGatheringStateChange); + pc.addEventListener("track", this.onTrack); + pc.addEventListener("negotiationneeded", this.onNegotiationNeeded); + pc.addEventListener("datachannel", this.onDataChannel); + (_a = this.stats) === null || _a === void 0 ? void 0 : _a.addStatsReportGatherer(this.callId, "unknown", pc); + return pc; + } + partyIdMatches(msg) { + // They must either match or both be absent (in which case opponentPartyId will be null) + // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same + // here and use null if the version is 0 (woe betide any opponent sending messages in the + // same call with different versions) + const msgPartyId = msg.version === 0 ? null : msg.party_id || null; + return msgPartyId === this.opponentPartyId; + } + // Commits to an opponent for the call + // ev: An invite or answer event + chooseOpponent(ev) { + var _a; + // I choo-choo-choose you + const msg = ev.getContent(); + logger_1.logger.debug(`Call ${this.callId} chooseOpponent() running (partyId=${msg.party_id})`); + this.opponentVersion = msg.version; + if (this.opponentVersion === 0) { + // set to null to indicate that we've chosen an opponent, but because + // they're v0 they have no party ID (even if they sent one, we're ignoring it) + this.opponentPartyId = null; + } + else { + // set to their party ID, or if they're naughty and didn't send one despite + // not being v0, set it to null to indicate we picked an opponent with no + // party ID + this.opponentPartyId = msg.party_id || null; + } + this.opponentCaps = msg.capabilities || {}; + this.opponentMember = (_a = this.client.getRoom(this.roomId).getMember(ev.getSender())) !== null && _a !== void 0 ? _a : undefined; + } + addBufferedIceCandidates() { + return __awaiter(this, void 0, void 0, function* () { + const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId); + if (bufferedCandidates) { + logger_1.logger.info(`Call ${this.callId} addBufferedIceCandidates() adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`); + yield this.addIceCandidates(bufferedCandidates); + } + this.remoteCandidateBuffer.clear(); + }); + } + addIceCandidates(candidates) { + return __awaiter(this, void 0, void 0, function* () { + for (const candidate of candidates) { + if ((candidate.sdpMid === null || candidate.sdpMid === undefined) && + (candidate.sdpMLineIndex === null || candidate.sdpMLineIndex === undefined)) { + logger_1.logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE end-of-candidates`); + } + else { + logger_1.logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE candidate (sdpMid=${candidate.sdpMid}, candidate=${candidate.candidate})`); + } + try { + yield this.peerConn.addIceCandidate(candidate); + } + catch (err) { + if (!this.ignoreOffer) { + logger_1.logger.info(`Call ${this.callId} addIceCandidates() failed to add remote ICE candidate`, err); + } + } + } + }); + } + get hasPeerConnection() { + return Boolean(this.peerConn); + } + initStats(stats, peerId = "unknown") { + this.stats = stats; + this.stats.start(); + } +} +exports.MatrixCall = MatrixCall; +function setTracksEnabled(tracks, enabled) { + for (const track of tracks) { + track.enabled = enabled; + } +} +exports.setTracksEnabled = setTracksEnabled; +function supportsMatrixCall() { + // typeof prevents Node from erroring on an undefined reference + if (typeof window === "undefined" || typeof document === "undefined") { + // NB. We don't log here as apps try to create a call object as a test for + // whether calls are supported, so we shouldn't fill the logs up. + return false; + } + // Firefox throws on so little as accessing the RTCPeerConnection when operating in a secure mode. + // There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616 though the concern + // is that the browser throwing a SecurityError will brick the client creation process. + try { + const supported = Boolean(window.RTCPeerConnection || + window.RTCSessionDescription || + window.RTCIceCandidate || + navigator.mediaDevices); + if (!supported) { + /* istanbul ignore if */ // Adds a lot of noise to test runs, so disable logging there. + if (process.env.NODE_ENV !== "test") { + logger_1.logger.error("WebRTC is not supported in this browser / environment"); + } + return false; + } + } + catch (e) { + logger_1.logger.error("Exception thrown when trying to access WebRTC", e); + return false; + } + return true; +} +exports.supportsMatrixCall = supportsMatrixCall; +/** + * DEPRECATED + * Use client.createCall() + * + * Create a new Matrix call for the browser. + * @param client - The client instance to use. + * @param roomId - The room the call is in. + * @param options - DEPRECATED optional options map. + * @returns the call or null if the browser doesn't support calling. + */ +function createNewMatrixCall(client, roomId, options) { + if (!supportsMatrixCall()) + return null; + const optionsForceTURN = options ? options.forceTURN : false; + const opts = { + client: client, + roomId: roomId, + invitee: options === null || options === void 0 ? void 0 : options.invitee, + turnServers: client.getTurnServers(), + // call level options + forceTURN: client.forceTURN || optionsForceTURN, + opponentDeviceId: options === null || options === void 0 ? void 0 : options.opponentDeviceId, + opponentSessionId: options === null || options === void 0 ? void 0 : options.opponentSessionId, + groupCallId: options === null || options === void 0 ? void 0 : options.groupCallId, + }; + const call = new MatrixCall(opts); + client.reEmitter.reEmit(call, Object.values(CallEvent)); + return call; +} +exports.createNewMatrixCall = createNewMatrixCall; + +}).call(this)}).call(this,require('_process')) + +},{"../@types/event":306,"../crypto/deviceinfo":340,"../http-api":367,"../logger":374,"../models/typed-event-emitter":395,"../randomstring":398,"../utils":416,"./callEventTypes":420,"./callFeed":421,"./groupCall":422,"_process":237,"sdp-transform":253,"uuid":287}],419:[function(require,module,exports){ +"use strict"; +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CallEventHandler = exports.CallEventHandlerEvent = void 0; +const logger_1 = require("../logger"); +const call_1 = require("./call"); +const event_1 = require("../@types/event"); +const client_1 = require("../client"); +const groupCall_1 = require("./groupCall"); +const room_1 = require("../models/room"); +// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some +// time to press the 'accept' button +const RING_GRACE_PERIOD = 3000; +var CallEventHandlerEvent; +(function (CallEventHandlerEvent) { + CallEventHandlerEvent["Incoming"] = "Call.incoming"; +})(CallEventHandlerEvent = exports.CallEventHandlerEvent || (exports.CallEventHandlerEvent = {})); +class CallEventHandler { + constructor(client) { + this.nextSeqByCall = new Map(); + this.toDeviceEventBuffers = new Map(); + this.onSync = () => { + // Process the current event buffer and start queuing into a new one. + const currentEventBuffer = this.callEventBuffer; + this.callEventBuffer = []; + // Ensure correct ordering by only processing this queue after the previous one has finished processing + if (this.eventBufferPromiseChain) { + this.eventBufferPromiseChain = this.eventBufferPromiseChain.then(() => this.evaluateEventBuffer(currentEventBuffer)); + } + else { + this.eventBufferPromiseChain = this.evaluateEventBuffer(currentEventBuffer); + } + }; + this.onRoomTimeline = (event) => { + this.callEventBuffer.push(event); + }; + this.onToDeviceEvent = (event) => { + const content = event.getContent(); + if (!content.call_id) { + this.callEventBuffer.push(event); + return; + } + if (!this.nextSeqByCall.has(content.call_id)) { + this.nextSeqByCall.set(content.call_id, 0); + } + if (content.seq === undefined) { + this.callEventBuffer.push(event); + return; + } + const nextSeq = this.nextSeqByCall.get(content.call_id) || 0; + if (content.seq !== nextSeq) { + if (!this.toDeviceEventBuffers.has(content.call_id)) { + this.toDeviceEventBuffers.set(content.call_id, []); + } + const buffer = this.toDeviceEventBuffers.get(content.call_id); + const index = buffer.findIndex((e) => e.getContent().seq > content.seq); + if (index === -1) { + buffer.push(event); + } + else { + buffer.splice(index, 0, event); + } + } + else { + const callId = content.call_id; + this.callEventBuffer.push(event); + this.nextSeqByCall.set(callId, content.seq + 1); + const buffer = this.toDeviceEventBuffers.get(callId); + let nextEvent = buffer && buffer.shift(); + while (nextEvent && nextEvent.getContent().seq === this.nextSeqByCall.get(callId)) { + this.callEventBuffer.push(nextEvent); + this.nextSeqByCall.set(callId, nextEvent.getContent().seq + 1); + nextEvent = buffer.shift(); + } + } + }; + this.client = client; + this.calls = new Map(); + // The sync code always emits one event at a time, so it will patiently + // wait for us to finish processing a call invite before delivering the + // next event, even if that next event is a hangup. We therefore accumulate + // all our call events and then process them on the 'sync' event, ie. + // each time a sync has completed. This way, we can avoid emitting incoming + // call events if we get both the invite and answer/hangup in the same sync. + // This happens quite often, eg. replaying sync from storage, catchup sync + // after loading and after we've been offline for a bit. + this.callEventBuffer = []; + this.candidateEventsByCall = new Map(); + } + start() { + this.client.on(client_1.ClientEvent.Sync, this.onSync); + this.client.on(room_1.RoomEvent.Timeline, this.onRoomTimeline); + this.client.on(client_1.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + } + stop() { + this.client.removeListener(client_1.ClientEvent.Sync, this.onSync); + this.client.removeListener(room_1.RoomEvent.Timeline, this.onRoomTimeline); + this.client.removeListener(client_1.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + } + evaluateEventBuffer(eventBuffer) { + return __awaiter(this, void 0, void 0, function* () { + yield Promise.all(eventBuffer.map((event) => this.client.decryptEventIfNeeded(event))); + const callEvents = eventBuffer.filter((event) => { + const eventType = event.getType(); + return eventType.startsWith("m.call.") || eventType.startsWith("org.matrix.call."); + }); + const ignoreCallIds = new Set(); + // inspect the buffer and mark all calls which have been answered + // or hung up before passing them to the call event handler. + for (const event of callEvents) { + const eventType = event.getType(); + if (eventType === event_1.EventType.CallAnswer || eventType === event_1.EventType.CallHangup) { + ignoreCallIds.add(event.getContent().call_id); + } + } + // Process call events in the order that they were received + for (const event of callEvents) { + const eventType = event.getType(); + const callId = event.getContent().call_id; + if (eventType === event_1.EventType.CallInvite && ignoreCallIds.has(callId)) { + // This call has previously been answered or hung up: ignore it + continue; + } + try { + yield this.handleCallEvent(event); + } + catch (e) { + logger_1.logger.error("CallEventHandler evaluateEventBuffer() caught exception handling call event", e); + } + } + }); + } + handleCallEvent(event) { + var _a, _b, _c, _d, _e, _f; + return __awaiter(this, void 0, void 0, function* () { + this.client.emit(client_1.ClientEvent.ReceivedVoipEvent, event); + const content = event.getContent(); + const callRoomId = event.getRoomId() || ((_b = (_a = this.client.groupCallEventHandler.getGroupCallById(content.conf_id)) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.roomId); + const groupCallId = content.conf_id; + const type = event.getType(); + const senderId = event.getSender(); + let call = content.call_id ? this.calls.get(content.call_id) : undefined; + let opponentDeviceId; + let groupCall; + if (groupCallId) { + groupCall = this.client.groupCallEventHandler.getGroupCallById(groupCallId); + if (!groupCall) { + logger_1.logger.warn(`CallEventHandler handleCallEvent() could not find a group call - ignoring event (groupCallId=${groupCallId}, type=${type})`); + return; + } + opponentDeviceId = content.device_id; + if (!opponentDeviceId) { + logger_1.logger.warn(`CallEventHandler handleCallEvent() could not find a device id - ignoring event (senderId=${senderId})`); + groupCall.emit(groupCall_1.GroupCallEvent.Error, new groupCall_1.GroupCallUnknownDeviceError(senderId)); + return; + } + if (content.dest_session_id !== this.client.getSessionId()) { + logger_1.logger.warn("CallEventHandler handleCallEvent() call event does not match current session id - ignoring"); + return; + } + } + const weSentTheEvent = senderId === this.client.credentials.userId && + (opponentDeviceId === undefined || opponentDeviceId === this.client.getDeviceId()); + if (!callRoomId) + return; + if (type === event_1.EventType.CallInvite) { + // ignore invites you send + if (weSentTheEvent) + return; + // expired call + if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) + return; + // stale/old invite event + if (call && call.state === call_1.CallState.Ended) + return; + if (call) { + logger_1.logger.warn(`CallEventHandler handleCallEvent() already has a call but got an invite - clobbering (callId=${content.call_id})`); + } + if (content.invitee && content.invitee !== this.client.getUserId()) { + return; // This invite was meant for another user in the room + } + const timeUntilTurnCresExpire = ((_c = this.client.getTurnServersExpiry()) !== null && _c !== void 0 ? _c : 0) - Date.now(); + logger_1.logger.info("CallEventHandler handleCallEvent() current turn creds expire in " + timeUntilTurnCresExpire + " ms"); + call = + (_d = (0, call_1.createNewMatrixCall)(this.client, callRoomId, { + forceTURN: this.client.forceTURN, + opponentDeviceId, + groupCallId, + opponentSessionId: content.sender_session_id, + })) !== null && _d !== void 0 ? _d : undefined; + if (!call) { + logger_1.logger.log(`CallEventHandler handleCallEvent() this client does not support WebRTC (callId=${content.call_id})`); + // don't hang up the call: there could be other clients + // connected that do support WebRTC and declining the + // the call on their behalf would be really annoying. + return; + } + call.callId = content.call_id; + const stats = groupCall === null || groupCall === void 0 ? void 0 : groupCall.getGroupCallStats(); + if (stats) { + call.initStats(stats); + } + try { + yield call.initWithInvite(event); + } + catch (e) { + if (e instanceof call_1.CallError) { + if (e.code === groupCall_1.GroupCallErrorCode.UnknownDevice) { + groupCall === null || groupCall === void 0 ? void 0 : groupCall.emit(groupCall_1.GroupCallEvent.Error, e); + } + else { + logger_1.logger.error(e); + } + } + } + this.calls.set(call.callId, call); + // if we stashed candidate events for that call ID, play them back now + if (this.candidateEventsByCall.get(call.callId)) { + for (const ev of this.candidateEventsByCall.get(call.callId)) { + call.onRemoteIceCandidatesReceived(ev); + } + } + // Were we trying to call that user (room)? + let existingCall; + for (const thisCall of this.calls.values()) { + const isCalling = [call_1.CallState.WaitLocalMedia, call_1.CallState.CreateOffer, call_1.CallState.InviteSent].includes(thisCall.state); + if (call.roomId === thisCall.roomId && + thisCall.direction === call_1.CallDirection.Outbound && + ((_e = call.getOpponentMember()) === null || _e === void 0 ? void 0 : _e.userId) === thisCall.invitee && + isCalling) { + existingCall = thisCall; + break; + } + } + if (existingCall) { + if (existingCall.callId > call.callId) { + logger_1.logger.log(`CallEventHandler handleCallEvent() detected glare - answering incoming call and canceling outgoing call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`); + existingCall.replacedBy(call); + } + else { + logger_1.logger.log(`CallEventHandler handleCallEvent() detected glare - hanging up incoming call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`); + call.hangup(call_1.CallErrorCode.Replaced, true); + } + } + else { + this.client.emit(CallEventHandlerEvent.Incoming, call); + } + return; + } + else if (type === event_1.EventType.CallCandidates) { + if (weSentTheEvent) + return; + if (!call) { + // store the candidates; we may get a call eventually. + if (!this.candidateEventsByCall.has(content.call_id)) { + this.candidateEventsByCall.set(content.call_id, []); + } + this.candidateEventsByCall.get(content.call_id).push(event); + } + else { + call.onRemoteIceCandidatesReceived(event); + } + return; + } + else if ([event_1.EventType.CallHangup, event_1.EventType.CallReject].includes(type)) { + // Note that we also observe our own hangups here so we can see + // if we've already rejected a call that would otherwise be valid + if (!call) { + // if not live, store the fact that the call has ended because + // we're probably getting events backwards so + // the hangup will come before the invite + call = + (_f = (0, call_1.createNewMatrixCall)(this.client, callRoomId, { + opponentDeviceId, + opponentSessionId: content.sender_session_id, + })) !== null && _f !== void 0 ? _f : undefined; + if (call) { + call.callId = content.call_id; + call.initWithHangup(event); + this.calls.set(content.call_id, call); + } + } + else { + if (call.state !== call_1.CallState.Ended) { + if (type === event_1.EventType.CallHangup) { + call.onHangupReceived(content); + } + else { + call.onRejectReceived(content); + } + // @ts-expect-error typescript thinks the state can't be 'ended' because we're + // inside the if block where it wasn't, but it could have changed because + // on[Hangup|Reject]Received are side-effecty. + if (call.state === call_1.CallState.Ended) + this.calls.delete(content.call_id); + } + } + return; + } + // The following events need a call and a peer connection + if (!call || !call.hasPeerConnection) { + logger_1.logger.info(`CallEventHandler handleCallEvent() discarding possible call event as we don't have a call (type=${type})`); + return; + } + // Ignore remote echo + if (event.getContent().party_id === call.ourPartyId) + return; + switch (type) { + case event_1.EventType.CallAnswer: + if (weSentTheEvent) { + if (call.state === call_1.CallState.Ringing) { + call.onAnsweredElsewhere(content); + } + } + else { + call.onAnswerReceived(event); + } + break; + case event_1.EventType.CallSelectAnswer: + call.onSelectAnswerReceived(event); + break; + case event_1.EventType.CallNegotiate: + call.onNegotiateReceived(event); + break; + case event_1.EventType.CallAssertedIdentity: + case event_1.EventType.CallAssertedIdentityPrefix: + call.onAssertedIdentityReceived(event); + break; + case event_1.EventType.CallSDPStreamMetadataChanged: + case event_1.EventType.CallSDPStreamMetadataChangedPrefix: + call.onSDPStreamMetadataChangedReceived(event); + break; + } + }); + } +} +exports.CallEventHandler = CallEventHandler; + +},{"../@types/event":306,"../client":321,"../logger":374,"../models/room":392,"./call":418,"./groupCall":422}],420:[function(require,module,exports){ +"use strict"; +// allow non-camelcase as these are events type that go onto the wire +/* eslint-disable camelcase */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SDPStreamMetadataPurpose = exports.SDPStreamMetadataKey = void 0; +// TODO: Change to "sdp_stream_metadata" when MSC3077 is merged +exports.SDPStreamMetadataKey = "org.matrix.msc3077.sdp_stream_metadata"; +var SDPStreamMetadataPurpose; +(function (SDPStreamMetadataPurpose) { + SDPStreamMetadataPurpose["Usermedia"] = "m.usermedia"; + SDPStreamMetadataPurpose["Screenshare"] = "m.screenshare"; +})(SDPStreamMetadataPurpose = exports.SDPStreamMetadataPurpose || (exports.SDPStreamMetadataPurpose = {})); +/* eslint-enable camelcase */ + +},{}],421:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 Šimon Brandner + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CallFeed = exports.CallFeedEvent = exports.SPEAKING_THRESHOLD = void 0; +const callEventTypes_1 = require("./callEventTypes"); +const audioContext_1 = require("./audioContext"); +const logger_1 = require("../logger"); +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const call_1 = require("./call"); +const POLLING_INTERVAL = 200; // ms +exports.SPEAKING_THRESHOLD = -60; // dB +const SPEAKING_SAMPLE_COUNT = 8; // samples +var CallFeedEvent; +(function (CallFeedEvent) { + CallFeedEvent["NewStream"] = "new_stream"; + CallFeedEvent["MuteStateChanged"] = "mute_state_changed"; + CallFeedEvent["LocalVolumeChanged"] = "local_volume_changed"; + CallFeedEvent["VolumeChanged"] = "volume_changed"; + CallFeedEvent["ConnectedChanged"] = "connected_changed"; + CallFeedEvent["Speaking"] = "speaking"; + CallFeedEvent["Disposed"] = "disposed"; +})(CallFeedEvent = exports.CallFeedEvent || (exports.CallFeedEvent = {})); +class CallFeed extends typed_event_emitter_1.TypedEventEmitter { + constructor(opts) { + super(); + this.localVolume = 1; + this.measuringVolumeActivity = false; + this.speakingThreshold = exports.SPEAKING_THRESHOLD; + this.speaking = false; + this._disposed = false; + this._connected = false; + this.onAddTrack = () => { + this.emit(CallFeedEvent.NewStream, this.stream); + }; + this.onCallState = (state) => { + if (state === call_1.CallState.Connected) { + this.connected = true; + } + else if (state === call_1.CallState.Connecting) { + this.connected = false; + } + }; + this.volumeLooper = () => { + if (!this.analyser) + return; + if (!this.measuringVolumeActivity) + return; + this.analyser.getFloatFrequencyData(this.frequencyBinCount); + let maxVolume = -Infinity; + for (const volume of this.frequencyBinCount) { + if (volume > maxVolume) { + maxVolume = volume; + } + } + this.speakingVolumeSamples.shift(); + this.speakingVolumeSamples.push(maxVolume); + this.emit(CallFeedEvent.VolumeChanged, maxVolume); + let newSpeaking = false; + for (const volume of this.speakingVolumeSamples) { + if (volume > this.speakingThreshold) { + newSpeaking = true; + break; + } + } + if (this.speaking !== newSpeaking) { + this.speaking = newSpeaking; + this.emit(CallFeedEvent.Speaking, this.speaking); + } + this.volumeLooperTimeout = setTimeout(this.volumeLooper, POLLING_INTERVAL); + }; + this.client = opts.client; + this.call = opts.call; + this.roomId = opts.roomId; + this.userId = opts.userId; + this.deviceId = opts.deviceId; + this.purpose = opts.purpose; + this.audioMuted = opts.audioMuted; + this.videoMuted = opts.videoMuted; + this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity); + this.sdpMetadataStreamId = opts.stream.id; + this.updateStream(null, opts.stream); + this.stream = opts.stream; // updateStream does this, but this makes TS happier + if (this.hasAudioTrack) { + this.initVolumeMeasuring(); + } + if (opts.call) { + opts.call.addListener(call_1.CallEvent.State, this.onCallState); + this.onCallState(opts.call.state); + } + } + get connected() { + // Local feeds are always considered connected + return this.isLocal() || this._connected; + } + set connected(connected) { + this._connected = connected; + this.emit(CallFeedEvent.ConnectedChanged, this.connected); + } + get hasAudioTrack() { + return this.stream.getAudioTracks().length > 0; + } + updateStream(oldStream, newStream) { + if (newStream === oldStream) + return; + if (oldStream) { + oldStream.removeEventListener("addtrack", this.onAddTrack); + this.measureVolumeActivity(false); + } + this.stream = newStream; + newStream.addEventListener("addtrack", this.onAddTrack); + if (this.hasAudioTrack) { + this.initVolumeMeasuring(); + } + else { + this.measureVolumeActivity(false); + } + this.emit(CallFeedEvent.NewStream, this.stream); + } + initVolumeMeasuring() { + if (!this.hasAudioTrack) + return; + if (!this.audioContext) + this.audioContext = (0, audioContext_1.acquireContext)(); + this.analyser = this.audioContext.createAnalyser(); + this.analyser.fftSize = 512; + this.analyser.smoothingTimeConstant = 0.1; + const mediaStreamAudioSourceNode = this.audioContext.createMediaStreamSource(this.stream); + mediaStreamAudioSourceNode.connect(this.analyser); + this.frequencyBinCount = new Float32Array(this.analyser.frequencyBinCount); + } + /** + * Returns callRoom member + * @returns member of the callRoom + */ + getMember() { + var _a; + const callRoom = this.client.getRoom(this.roomId); + return (_a = callRoom === null || callRoom === void 0 ? void 0 : callRoom.getMember(this.userId)) !== null && _a !== void 0 ? _a : null; + } + /** + * Returns true if CallFeed is local, otherwise returns false + * @returns is local? + */ + isLocal() { + return (this.userId === this.client.getUserId() && + (this.deviceId === undefined || this.deviceId === this.client.getDeviceId())); + } + /** + * Returns true if audio is muted or if there are no audio + * tracks, otherwise returns false + * @returns is audio muted? + */ + isAudioMuted() { + return this.stream.getAudioTracks().length === 0 || this.audioMuted; + } + /** + * Returns true video is muted or if there are no video + * tracks, otherwise returns false + * @returns is video muted? + */ + isVideoMuted() { + // We assume only one video track + return this.stream.getVideoTracks().length === 0 || this.videoMuted; + } + isSpeaking() { + return this.speaking; + } + /** + * Replaces the current MediaStream with a new one. + * The stream will be different and new stream as remote parties are + * concerned, but this can be used for convenience locally to set up + * volume listeners automatically on the new stream etc. + * @param newStream - new stream with which to replace the current one + */ + setNewStream(newStream) { + this.updateStream(this.stream, newStream); + } + /** + * Set one or both of feed's internal audio and video video mute state + * Either value may be null to leave it as-is + * @param audioMuted - is the feed's audio muted? + * @param videoMuted - is the feed's video muted? + */ + setAudioVideoMuted(audioMuted, videoMuted) { + if (audioMuted !== null) { + if (this.audioMuted !== audioMuted) { + this.speakingVolumeSamples.fill(-Infinity); + } + this.audioMuted = audioMuted; + } + if (videoMuted !== null) + this.videoMuted = videoMuted; + this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted); + } + /** + * Starts emitting volume_changed events where the emitter value is in decibels + * @param enabled - emit volume changes + */ + measureVolumeActivity(enabled) { + if (enabled) { + if (!this.analyser || !this.frequencyBinCount || !this.hasAudioTrack) + return; + this.measuringVolumeActivity = true; + this.volumeLooper(); + } + else { + this.measuringVolumeActivity = false; + this.speakingVolumeSamples.fill(-Infinity); + this.emit(CallFeedEvent.VolumeChanged, -Infinity); + } + } + setSpeakingThreshold(threshold) { + this.speakingThreshold = threshold; + } + clone() { + const mediaHandler = this.client.getMediaHandler(); + const stream = this.stream.clone(); + logger_1.logger.log(`CallFeed clone() cloning stream (originalStreamId=${this.stream.id}, newStreamId${stream.id})`); + if (this.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Usermedia) { + mediaHandler.userMediaStreams.push(stream); + } + else { + mediaHandler.screensharingStreams.push(stream); + } + return new CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.userId, + deviceId: this.deviceId, + stream, + purpose: this.purpose, + audioMuted: this.audioMuted, + videoMuted: this.videoMuted, + }); + } + dispose() { + var _a, _b; + clearTimeout(this.volumeLooperTimeout); + (_a = this.stream) === null || _a === void 0 ? void 0 : _a.removeEventListener("addtrack", this.onAddTrack); + (_b = this.call) === null || _b === void 0 ? void 0 : _b.removeListener(call_1.CallEvent.State, this.onCallState); + if (this.audioContext) { + this.audioContext = undefined; + this.analyser = undefined; + (0, audioContext_1.releaseContext)(); + } + this._disposed = true; + this.emit(CallFeedEvent.Disposed); + } + get disposed() { + return this._disposed; + } + set disposed(value) { + this._disposed = value; + } + getLocalVolume() { + return this.localVolume; + } + setLocalVolume(localVolume) { + this.localVolume = localVolume; + this.emit(CallFeedEvent.LocalVolumeChanged, localVolume); + } +} +exports.CallFeed = CallFeed; + +},{"../logger":374,"../models/typed-event-emitter":395,"./audioContext":417,"./call":418,"./callEventTypes":420}],422:[function(require,module,exports){ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GroupCall = exports.GroupCallState = exports.OtherUserSpeakingError = exports.GroupCallUnknownDeviceError = exports.GroupCallError = exports.GroupCallErrorCode = exports.GroupCallStatsReportEvent = exports.GroupCallEvent = exports.GroupCallTerminationReason = exports.GroupCallType = exports.GroupCallIntent = void 0; +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const callFeed_1 = require("./callFeed"); +const call_1 = require("./call"); +const room_state_1 = require("../models/room-state"); +const logger_1 = require("../logger"); +const ReEmitter_1 = require("../ReEmitter"); +const callEventTypes_1 = require("./callEventTypes"); +const event_1 = require("../@types/event"); +const callEventHandler_1 = require("./callEventHandler"); +const groupCallEventHandler_1 = require("./groupCallEventHandler"); +const utils_1 = require("../utils"); +const groupCallStats_1 = require("./stats/groupCallStats"); +const statsReport_1 = require("./stats/statsReport"); +var GroupCallIntent; +(function (GroupCallIntent) { + GroupCallIntent["Ring"] = "m.ring"; + GroupCallIntent["Prompt"] = "m.prompt"; + GroupCallIntent["Room"] = "m.room"; +})(GroupCallIntent = exports.GroupCallIntent || (exports.GroupCallIntent = {})); +var GroupCallType; +(function (GroupCallType) { + GroupCallType["Video"] = "m.video"; + GroupCallType["Voice"] = "m.voice"; +})(GroupCallType = exports.GroupCallType || (exports.GroupCallType = {})); +var GroupCallTerminationReason; +(function (GroupCallTerminationReason) { + GroupCallTerminationReason["CallEnded"] = "call_ended"; +})(GroupCallTerminationReason = exports.GroupCallTerminationReason || (exports.GroupCallTerminationReason = {})); +/** + * Because event names are just strings, they do need + * to be unique over all event types of event emitter. + * Some objects could emit more then one set of events. + */ +var GroupCallEvent; +(function (GroupCallEvent) { + GroupCallEvent["GroupCallStateChanged"] = "group_call_state_changed"; + GroupCallEvent["ActiveSpeakerChanged"] = "active_speaker_changed"; + GroupCallEvent["CallsChanged"] = "calls_changed"; + GroupCallEvent["UserMediaFeedsChanged"] = "user_media_feeds_changed"; + GroupCallEvent["ScreenshareFeedsChanged"] = "screenshare_feeds_changed"; + GroupCallEvent["LocalScreenshareStateChanged"] = "local_screenshare_state_changed"; + GroupCallEvent["LocalMuteStateChanged"] = "local_mute_state_changed"; + GroupCallEvent["ParticipantsChanged"] = "participants_changed"; + GroupCallEvent["Error"] = "group_call_error"; +})(GroupCallEvent = exports.GroupCallEvent || (exports.GroupCallEvent = {})); +var GroupCallStatsReportEvent; +(function (GroupCallStatsReportEvent) { + GroupCallStatsReportEvent["ConnectionStats"] = "GroupCall.connection_stats"; + GroupCallStatsReportEvent["ByteSentStats"] = "GroupCall.byte_sent_stats"; +})(GroupCallStatsReportEvent = exports.GroupCallStatsReportEvent || (exports.GroupCallStatsReportEvent = {})); +var GroupCallErrorCode; +(function (GroupCallErrorCode) { + GroupCallErrorCode["NoUserMedia"] = "no_user_media"; + GroupCallErrorCode["UnknownDevice"] = "unknown_device"; + GroupCallErrorCode["PlaceCallFailed"] = "place_call_failed"; +})(GroupCallErrorCode = exports.GroupCallErrorCode || (exports.GroupCallErrorCode = {})); +class GroupCallError extends Error { + constructor(code, msg, err) { + // Still don't think there's any way to have proper nested errors + if (err) { + super(msg + ": " + err); + } + else { + super(msg); + } + this.code = code; + } +} +exports.GroupCallError = GroupCallError; +class GroupCallUnknownDeviceError extends GroupCallError { + constructor(userId) { + super(GroupCallErrorCode.UnknownDevice, "No device found for " + userId); + this.userId = userId; + } +} +exports.GroupCallUnknownDeviceError = GroupCallUnknownDeviceError; +class OtherUserSpeakingError extends Error { + constructor() { + super("Cannot unmute: another user is speaking"); + } +} +exports.OtherUserSpeakingError = OtherUserSpeakingError; +var GroupCallState; +(function (GroupCallState) { + GroupCallState["LocalCallFeedUninitialized"] = "local_call_feed_uninitialized"; + GroupCallState["InitializingLocalCallFeed"] = "initializing_local_call_feed"; + GroupCallState["LocalCallFeedInitialized"] = "local_call_feed_initialized"; + GroupCallState["Entered"] = "entered"; + GroupCallState["Ended"] = "ended"; +})(GroupCallState = exports.GroupCallState || (exports.GroupCallState = {})); +const DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour +function getCallUserId(call) { + var _a; + return ((_a = call.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId) || call.invitee || null; +} +class GroupCall extends typed_event_emitter_1.TypedEventEmitter { + constructor(client, room, type, isPtt, intent, groupCallId, dataChannelsEnabled, dataChannelOptions, isCallWithoutVideoAndAudio) { + var _a, _b; + super(); + this.client = client; + this.room = room; + this.type = type; + this.isPtt = isPtt; + this.intent = intent; + this.dataChannelsEnabled = dataChannelsEnabled; + this.dataChannelOptions = dataChannelOptions; + // Config + this.activeSpeakerInterval = 1000; + this.retryCallInterval = 5000; + this.participantTimeout = 1000 * 15; + this.pttMaxTransmitTime = 1000 * 20; + this.userMediaFeeds = []; + this.screenshareFeeds = []; + this.calls = new Map(); // user_id -> device_id -> MatrixCall + this.callHandlers = new Map(); // user_id -> device_id -> ICallHandlers + this.retryCallCounts = new Map(); // user_id -> device_id -> count + this.transmitTimer = null; + this.participantsExpirationTimer = null; + this.resendMemberStateTimer = null; + this.initWithAudioMuted = false; + this.initWithVideoMuted = false; + this.onConnectionStats = (report) => { + // @TODO: Implement data argumentation + this.emit(GroupCallStatsReportEvent.ConnectionStats, { report }); + }; + this.onByteSentStats = (report) => { + // @TODO: Implement data argumentation + this.emit(GroupCallStatsReportEvent.ByteSentStats, { report }); + }; + this._state = GroupCallState.LocalCallFeedUninitialized; + this._participants = new Map(); + this._creationTs = null; + this._enteredViaAnotherSession = false; + /* + * Call Setup + * + * There are two different paths for calls to be created: + * 1. Incoming calls triggered by the Call.incoming event. + * 2. Outgoing calls to the initial members of a room or new members + * as they are observed by the RoomState.members event. + */ + this.onIncomingCall = (newCall) => { + var _a, _b; + // The incoming calls may be for another room, which we will ignore. + if (newCall.roomId !== this.room.roomId) { + return; + } + if (newCall.state !== call_1.CallState.Ringing) { + logger_1.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`); + return; + } + if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) { + logger_1.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`); + newCall.reject(); + return; + } + const opponentUserId = (_a = newCall.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId; + if (opponentUserId === undefined) { + logger_1.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`); + return; + } + const deviceMap = (_b = this.calls.get(opponentUserId)) !== null && _b !== void 0 ? _b : new Map(); + const prevCall = deviceMap.get(newCall.getOpponentDeviceId()); + if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.callId) === newCall.callId) + return; + logger_1.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`); + if (prevCall) + prevCall.hangup(call_1.CallErrorCode.Replaced, false); + this.initCall(newCall); + const feeds = this.getLocalFeeds().map((feed) => feed.clone()); + if (!this.callExpected(newCall)) { + // Disable our tracks for users not explicitly participating in the + // call but trying to receive the feeds + for (const feed of feeds) { + (0, call_1.setTracksEnabled)(feed.stream.getAudioTracks(), false); + (0, call_1.setTracksEnabled)(feed.stream.getVideoTracks(), false); + } + } + newCall.answerWithCallFeeds(feeds); + deviceMap.set(newCall.getOpponentDeviceId(), newCall); + this.calls.set(opponentUserId, deviceMap); + this.emit(GroupCallEvent.CallsChanged, this.calls); + }; + this.onRetryCallLoop = () => { + var _a; + let needsRetry = false; + for (const [{ userId }, participantMap] of this.participants) { + const callMap = this.calls.get(userId); + let retriesMap = this.retryCallCounts.get(userId); + for (const [deviceId, participant] of participantMap) { + const call = callMap === null || callMap === void 0 ? void 0 : callMap.get(deviceId); + const retries = (_a = retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.get(deviceId)) !== null && _a !== void 0 ? _a : 0; + if ((call === null || call === void 0 ? void 0 : call.getOpponentSessionId()) !== participant.sessionId && + this.wantsOutgoingCall(userId, deviceId) && + retries < 3) { + if (retriesMap === undefined) { + retriesMap = new Map(); + this.retryCallCounts.set(userId, retriesMap); + } + retriesMap.set(deviceId, retries + 1); + needsRetry = true; + } + } + } + if (needsRetry) + this.placeOutgoingCalls(); + }; + this.onCallFeedsChanged = (call) => { + const opponentMemberId = getCallUserId(call); + const opponentDeviceId = call.getOpponentDeviceId(); + if (!opponentMemberId) { + throw new Error("Cannot change call feeds without user id"); + } + const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId); + const remoteUsermediaFeed = call.remoteUsermediaFeed; + const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed; + if (remoteFeedChanged) { + if (!currentUserMediaFeed && remoteUsermediaFeed) { + this.addUserMediaFeed(remoteUsermediaFeed); + } + else if (currentUserMediaFeed && remoteUsermediaFeed) { + this.replaceUserMediaFeed(currentUserMediaFeed, remoteUsermediaFeed); + } + else if (currentUserMediaFeed && !remoteUsermediaFeed) { + this.removeUserMediaFeed(currentUserMediaFeed); + } + } + const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId); + const remoteScreensharingFeed = call.remoteScreensharingFeed; + const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed; + if (remoteScreenshareFeedChanged) { + if (!currentScreenshareFeed && remoteScreensharingFeed) { + this.addScreenshareFeed(remoteScreensharingFeed); + } + else if (currentScreenshareFeed && remoteScreensharingFeed) { + this.replaceScreenshareFeed(currentScreenshareFeed, remoteScreensharingFeed); + } + else if (currentScreenshareFeed && !remoteScreensharingFeed) { + this.removeScreenshareFeed(currentScreenshareFeed); + } + } + }; + this.onCallStateChanged = (call, state, _oldState) => { + var _a; + if (state === call_1.CallState.Ended) + return; + const audioMuted = this.localCallFeed.isAudioMuted(); + if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) { + call.setMicrophoneMuted(audioMuted); + } + const videoMuted = this.localCallFeed.isVideoMuted(); + if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) { + call.setLocalVideoMuted(videoMuted); + } + const opponentUserId = (_a = call.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId; + if (state === call_1.CallState.Connected && opponentUserId) { + const retriesMap = this.retryCallCounts.get(opponentUserId); + retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.delete(call.getOpponentDeviceId()); + if ((retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.size) === 0) + this.retryCallCounts.delete(opponentUserId); + } + }; + this.onCallHangup = (call) => { + var _a, _b; + if (call.hangupReason === call_1.CallErrorCode.Replaced) + return; + const opponentUserId = (_b = (_a = call.getOpponentMember()) === null || _a === void 0 ? void 0 : _a.userId) !== null && _b !== void 0 ? _b : this.room.getMember(call.invitee).userId; + const deviceMap = this.calls.get(opponentUserId); + // Sanity check that this call is in fact in the map + if ((deviceMap === null || deviceMap === void 0 ? void 0 : deviceMap.get(call.getOpponentDeviceId())) === call) { + this.disposeCall(call, call.hangupReason); + deviceMap.delete(call.getOpponentDeviceId()); + if (deviceMap.size === 0) + this.calls.delete(opponentUserId); + this.emit(GroupCallEvent.CallsChanged, this.calls); + } + }; + this.onCallReplaced = (prevCall, newCall) => { + const opponentUserId = prevCall.getOpponentMember().userId; + let deviceMap = this.calls.get(opponentUserId); + if (deviceMap === undefined) { + deviceMap = new Map(); + this.calls.set(opponentUserId, deviceMap); + } + prevCall.hangup(call_1.CallErrorCode.Replaced, false); + this.initCall(newCall); + deviceMap.set(prevCall.getOpponentDeviceId(), newCall); + this.emit(GroupCallEvent.CallsChanged, this.calls); + }; + this.onActiveSpeakerLoop = () => { + let topAvg = undefined; + let nextActiveSpeaker = undefined; + for (const callFeed of this.userMediaFeeds) { + if (callFeed.isLocal() && this.userMediaFeeds.length > 1) + continue; + const total = callFeed.speakingVolumeSamples.reduce((acc, volume) => acc + Math.max(volume, callFeed_1.SPEAKING_THRESHOLD)); + const avg = total / callFeed.speakingVolumeSamples.length; + if (!topAvg || avg > topAvg) { + topAvg = avg; + nextActiveSpeaker = callFeed; + } + } + if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > callFeed_1.SPEAKING_THRESHOLD) { + this.activeSpeaker = nextActiveSpeaker; + this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker); + } + }; + this.onRoomState = () => this.updateParticipants(); + this.onParticipantsChanged = () => { + // Re-run setTracksEnabled on all calls, so that participants that just + // left get denied access to our media, and participants that just + // joined get granted access + this.forEachCall((call) => { + const expected = this.callExpected(call); + for (const feed of call.getLocalFeeds()) { + (0, call_1.setTracksEnabled)(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected); + (0, call_1.setTracksEnabled)(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected); + } + }); + if (this.state === GroupCallState.Entered) + this.placeOutgoingCalls(); + }; + this.onStateChanged = (newState, oldState) => { + if (newState === GroupCallState.Entered || + oldState === GroupCallState.Entered || + newState === GroupCallState.Ended) { + // We either entered, left, or ended the call + this.updateParticipants(); + this.updateMemberState().catch((e) => logger_1.logger.error(`GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices"`, e)); + } + }; + this.onLocalFeedsChanged = () => { + if (this.state === GroupCallState.Entered) { + this.updateMemberState().catch((e) => logger_1.logger.error(`GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`, e)); + } + }; + this.reEmitter = new ReEmitter_1.ReEmitter(this); + this.groupCallId = groupCallId !== null && groupCallId !== void 0 ? groupCallId : (0, call_1.genCallID)(); + this.creationTs = + (_b = (_a = room.currentState.getStateEvents(event_1.EventType.GroupCallPrefix, this.groupCallId)) === null || _a === void 0 ? void 0 : _a.getTs()) !== null && _b !== void 0 ? _b : null; + this.updateParticipants(); + room.on(room_state_1.RoomStateEvent.Update, this.onRoomState); + this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged); + this.on(GroupCallEvent.GroupCallStateChanged, this.onStateChanged); + this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged); + this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio; + const userID = this.client.getUserId() || "unknown"; + this.stats = new groupCallStats_1.GroupCallStats(this.groupCallId, userID); + this.stats.reports.on(statsReport_1.StatsReport.CONNECTION_STATS, this.onConnectionStats); + this.stats.reports.on(statsReport_1.StatsReport.BYTE_SENT_STATS, this.onByteSentStats); + } + create() { + return __awaiter(this, void 0, void 0, function* () { + this.creationTs = Date.now(); + this.client.groupCallEventHandler.groupCalls.set(this.room.roomId, this); + this.client.emit(groupCallEventHandler_1.GroupCallEventHandlerEvent.Outgoing, this); + const groupCallState = { + "m.intent": this.intent, + "m.type": this.type, + "io.element.ptt": this.isPtt, + // TODO: Specify data-channels better + "dataChannelsEnabled": this.dataChannelsEnabled, + "dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined, + }; + yield this.client.sendStateEvent(this.room.roomId, event_1.EventType.GroupCallPrefix, groupCallState, this.groupCallId); + return this; + }); + } + /** + * The group call's state. + */ + get state() { + return this._state; + } + set state(value) { + const prevValue = this._state; + if (value !== prevValue) { + this._state = value; + this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue); + } + } + /** + * The current participants in the call, as a map from members to device IDs + * to participant info. + */ + get participants() { + return this._participants; + } + set participants(value) { + const prevValue = this._participants; + const participantStateEqual = (x, y) => x.sessionId === y.sessionId && x.screensharing === y.screensharing; + const deviceMapsEqual = (x, y) => (0, utils_1.mapsEqual)(x, y, participantStateEqual); + // Only update if the map actually changed + if (!(0, utils_1.mapsEqual)(value, prevValue, deviceMapsEqual)) { + this._participants = value; + this.emit(GroupCallEvent.ParticipantsChanged, value); + } + } + /** + * The timestamp at which the call was created, or null if it has not yet + * been created. + */ + get creationTs() { + return this._creationTs; + } + set creationTs(value) { + this._creationTs = value; + } + /** + * Whether the local device has entered this call via another session, such + * as a widget. + */ + get enteredViaAnotherSession() { + return this._enteredViaAnotherSession; + } + set enteredViaAnotherSession(value) { + this._enteredViaAnotherSession = value; + this.updateParticipants(); + } + /** + * Executes the given callback on all calls in this group call. + * @param f - The callback. + */ + forEachCall(f) { + for (const deviceMap of this.calls.values()) { + for (const call of deviceMap.values()) + f(call); + } + } + getLocalFeeds() { + const feeds = []; + if (this.localCallFeed) + feeds.push(this.localCallFeed); + if (this.localScreenshareFeed) + feeds.push(this.localScreenshareFeed); + return feeds; + } + hasLocalParticipant() { + var _a, _b; + return ((_b = (_a = this.participants.get(this.room.getMember(this.client.getUserId()))) === null || _a === void 0 ? void 0 : _a.has(this.client.getDeviceId())) !== null && _b !== void 0 ? _b : false); + } + /** + * Determines whether the given call is one that we were expecting to exist + * given our knowledge of who is participating in the group call. + */ + callExpected(call) { + var _a; + const userId = getCallUserId(call); + const member = userId === null ? null : this.room.getMember(userId); + const deviceId = call.getOpponentDeviceId(); + return member !== null && deviceId !== undefined && ((_a = this.participants.get(member)) === null || _a === void 0 ? void 0 : _a.get(deviceId)) !== undefined; + } + initLocalCallFeed() { + return __awaiter(this, void 0, void 0, function* () { + if (this.state !== GroupCallState.LocalCallFeedUninitialized) { + throw new Error(`Cannot initialize local call feed in the "${this.state}" state.`); + } + this.state = GroupCallState.InitializingLocalCallFeed; + // wraps the real method to serialise calls, because we don't want to try starting + // multiple call feeds at once + if (this.initCallFeedPromise) + return this.initCallFeedPromise; + try { + this.initCallFeedPromise = this.initLocalCallFeedInternal(); + yield this.initCallFeedPromise; + } + finally { + this.initCallFeedPromise = undefined; + } + }); + } + initLocalCallFeedInternal() { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`); + let stream; + try { + stream = yield this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video); + } + catch (error) { + // If is allowed to join a call without a media stream, then we + // don't throw an error here. But we need an empty Local Feed to establish + // a connection later. + if (this.allowCallWithoutVideoAndAudio) { + stream = new MediaStream(); + } + else { + this.state = GroupCallState.LocalCallFeedUninitialized; + throw error; + } + } + // The call could've been disposed while we were waiting, and could + // also have been started back up again (hello, React 18) so if we're + // still in this 'initializing' state, carry on, otherwise bail. + if (this._state !== GroupCallState.InitializingLocalCallFeed) { + this.client.getMediaHandler().stopUserMediaStream(stream); + throw new Error("Group call disposed while gathering media stream"); + } + const callFeed = new callFeed_1.CallFeed({ + client: this.client, + roomId: this.room.roomId, + userId: this.client.getUserId(), + deviceId: this.client.getDeviceId(), + stream, + purpose: callEventTypes_1.SDPStreamMetadataPurpose.Usermedia, + audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt, + videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0, + }); + (0, call_1.setTracksEnabled)(stream.getAudioTracks(), !callFeed.isAudioMuted()); + (0, call_1.setTracksEnabled)(stream.getVideoTracks(), !callFeed.isVideoMuted()); + this.localCallFeed = callFeed; + this.addUserMediaFeed(callFeed); + this.state = GroupCallState.LocalCallFeedInitialized; + }); + } + updateLocalUsermediaStream(stream) { + return __awaiter(this, void 0, void 0, function* () { + if (this.localCallFeed) { + const oldStream = this.localCallFeed.stream; + this.localCallFeed.setNewStream(stream); + const micShouldBeMuted = this.localCallFeed.isAudioMuted(); + const vidShouldBeMuted = this.localCallFeed.isVideoMuted(); + logger_1.logger.log(`GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`); + (0, call_1.setTracksEnabled)(stream.getAudioTracks(), !micShouldBeMuted); + (0, call_1.setTracksEnabled)(stream.getVideoTracks(), !vidShouldBeMuted); + this.client.getMediaHandler().stopUserMediaStream(oldStream); + } + }); + } + enter() { + return __awaiter(this, void 0, void 0, function* () { + if (this.state === GroupCallState.LocalCallFeedUninitialized) { + yield this.initLocalCallFeed(); + } + else if (this.state !== GroupCallState.LocalCallFeedInitialized) { + throw new Error(`Cannot enter call in the "${this.state}" state`); + } + logger_1.logger.log(`GroupCall ${this.groupCallId} enter() running`); + this.state = GroupCallState.Entered; + this.client.on(callEventHandler_1.CallEventHandlerEvent.Incoming, this.onIncomingCall); + for (const call of this.client.callEventHandler.calls.values()) { + this.onIncomingCall(call); + } + this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval); + this.activeSpeaker = undefined; + this.onActiveSpeakerLoop(); + this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval); + }); + } + dispose() { + if (this.localCallFeed) { + this.removeUserMediaFeed(this.localCallFeed); + this.localCallFeed = undefined; + } + if (this.localScreenshareFeed) { + this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream); + this.removeScreenshareFeed(this.localScreenshareFeed); + this.localScreenshareFeed = undefined; + this.localDesktopCapturerSourceId = undefined; + } + this.client.getMediaHandler().stopAllStreams(); + if (this.transmitTimer !== null) { + clearTimeout(this.transmitTimer); + this.transmitTimer = null; + } + if (this.retryCallLoopInterval !== undefined) { + clearInterval(this.retryCallLoopInterval); + this.retryCallLoopInterval = undefined; + } + if (this.participantsExpirationTimer !== null) { + clearTimeout(this.participantsExpirationTimer); + this.participantsExpirationTimer = null; + } + if (this.state !== GroupCallState.Entered) { + return; + } + this.forEachCall((call) => call.hangup(call_1.CallErrorCode.UserHangup, false)); + this.activeSpeaker = undefined; + clearInterval(this.activeSpeakerLoopInterval); + this.retryCallCounts.clear(); + clearInterval(this.retryCallLoopInterval); + this.client.removeListener(callEventHandler_1.CallEventHandlerEvent.Incoming, this.onIncomingCall); + this.stats.stop(); + } + leave() { + this.dispose(); + this.state = GroupCallState.LocalCallFeedUninitialized; + } + terminate(emitStateEvent = true) { + return __awaiter(this, void 0, void 0, function* () { + this.dispose(); + this.room.off(room_state_1.RoomStateEvent.Update, this.onRoomState); + this.client.groupCallEventHandler.groupCalls.delete(this.room.roomId); + this.client.emit(groupCallEventHandler_1.GroupCallEventHandlerEvent.Ended, this); + this.state = GroupCallState.Ended; + if (emitStateEvent) { + const existingStateEvent = this.room.currentState.getStateEvents(event_1.EventType.GroupCallPrefix, this.groupCallId); + yield this.client.sendStateEvent(this.room.roomId, event_1.EventType.GroupCallPrefix, Object.assign(Object.assign({}, existingStateEvent.getContent()), { "m.terminated": GroupCallTerminationReason.CallEnded }), this.groupCallId); + } + }); + } + /* + * Local Usermedia + */ + isLocalVideoMuted() { + if (this.localCallFeed) { + return this.localCallFeed.isVideoMuted(); + } + return true; + } + isMicrophoneMuted() { + if (this.localCallFeed) { + return this.localCallFeed.isAudioMuted(); + } + return true; + } + /** + * Sets the mute state of the local participants's microphone. + * @param muted - Whether to mute the microphone + * @returns Whether muting/unmuting was successful + */ + setMicrophoneMuted(muted) { + return __awaiter(this, void 0, void 0, function* () { + // hasAudioDevice can block indefinitely if the window has lost focus, + // and it doesn't make much sense to keep a device from being muted, so + // we always allow muted = true changes to go through + if (!muted && !(yield this.client.getMediaHandler().hasAudioDevice())) { + return false; + } + const sendUpdatesBefore = !muted && this.isPtt; + // set a timer for the maximum transmit time on PTT calls + if (this.isPtt) { + // Set or clear the max transmit timer + if (!muted && this.isMicrophoneMuted()) { + this.transmitTimer = setTimeout(() => { + this.setMicrophoneMuted(true); + }, this.pttMaxTransmitTime); + } + else if (muted && !this.isMicrophoneMuted()) { + if (this.transmitTimer !== null) + clearTimeout(this.transmitTimer); + this.transmitTimer = null; + } + } + this.forEachCall((call) => { var _a; return (_a = call.localUsermediaFeed) === null || _a === void 0 ? void 0 : _a.setAudioVideoMuted(muted, null); }); + const sendUpdates = () => __awaiter(this, void 0, void 0, function* () { + const updates = []; + this.forEachCall((call) => updates.push(call.sendMetadataUpdate())); + yield Promise.all(updates).catch((e) => logger_1.logger.info(`GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`, e)); + }); + if (sendUpdatesBefore) + yield sendUpdates(); + if (this.localCallFeed) { + logger_1.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`); + // We needed this here to avoid an error in case user join a call without a device. + // I can not use .then .catch functions because linter :-( + try { + if (!muted) { + const stream = yield this.client + .getMediaHandler() + .getUserMediaStream(true, !this.localCallFeed.isVideoMuted()); + if (stream === null) { + // if case permission denied to get a stream stop this here + /* istanbul ignore next */ + logger_1.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`); + return false; + } + } + } + catch (e) { + /* istanbul ignore next */ + logger_1.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`); + return false; + } + this.localCallFeed.setAudioVideoMuted(muted, null); + // I don't believe its actually necessary to enable these tracks: they + // are the one on the GroupCall's own CallFeed and are cloned before being + // given to any of the actual calls, so these tracks don't actually go + // anywhere. Let's do it anyway to avoid confusion. + (0, call_1.setTracksEnabled)(this.localCallFeed.stream.getAudioTracks(), !muted); + } + else { + logger_1.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`); + this.initWithAudioMuted = muted; + } + this.forEachCall((call) => (0, call_1.setTracksEnabled)(call.localUsermediaFeed.stream.getAudioTracks(), !muted && this.callExpected(call))); + this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted()); + if (!sendUpdatesBefore) + yield sendUpdates(); + return true; + }); + } + /** + * Sets the mute state of the local participants's video. + * @param muted - Whether to mute the video + * @returns Whether muting/unmuting was successful + */ + setLocalVideoMuted(muted) { + return __awaiter(this, void 0, void 0, function* () { + // hasAudioDevice can block indefinitely if the window has lost focus, + // and it doesn't make much sense to keep a device from being muted, so + // we always allow muted = true changes to go through + if (!muted && !(yield this.client.getMediaHandler().hasVideoDevice())) { + return false; + } + if (this.localCallFeed) { + /* istanbul ignore next */ + logger_1.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`); + try { + const stream = yield this.client.getMediaHandler().getUserMediaStream(true, !muted); + yield this.updateLocalUsermediaStream(stream); + this.localCallFeed.setAudioVideoMuted(null, muted); + (0, call_1.setTracksEnabled)(this.localCallFeed.stream.getVideoTracks(), !muted); + } + catch (_) { + // No permission to video device + /* istanbul ignore next */ + logger_1.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`); + return false; + } + } + else { + logger_1.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`); + this.initWithVideoMuted = muted; + } + const updates = []; + this.forEachCall((call) => updates.push(call.setLocalVideoMuted(muted))); + yield Promise.all(updates); + // We setTracksEnabled again, independently from the call doing it + // internally, since we might not be expecting the call + this.forEachCall((call) => (0, call_1.setTracksEnabled)(call.localUsermediaFeed.stream.getVideoTracks(), !muted && this.callExpected(call))); + this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted); + return true; + }); + } + setScreensharingEnabled(enabled, opts = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (enabled === this.isScreensharing()) { + return enabled; + } + if (enabled) { + try { + logger_1.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`); + const stream = yield this.client.getMediaHandler().getScreensharingStream(opts); + for (const track of stream.getTracks()) { + const onTrackEnded = () => { + this.setScreensharingEnabled(false); + track.removeEventListener("ended", onTrackEnded); + }; + track.addEventListener("ended", onTrackEnded); + } + logger_1.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`); + this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId; + this.localScreenshareFeed = new callFeed_1.CallFeed({ + client: this.client, + roomId: this.room.roomId, + userId: this.client.getUserId(), + deviceId: this.client.getDeviceId(), + stream, + purpose: callEventTypes_1.SDPStreamMetadataPurpose.Screenshare, + audioMuted: false, + videoMuted: false, + }); + this.addScreenshareFeed(this.localScreenshareFeed); + this.emit(GroupCallEvent.LocalScreenshareStateChanged, true, this.localScreenshareFeed, this.localDesktopCapturerSourceId); + // TODO: handle errors + this.forEachCall((call) => call.pushLocalFeed(this.localScreenshareFeed.clone())); + return true; + } + catch (error) { + if (opts.throwOnFail) + throw error; + logger_1.logger.error(`GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`, error); + this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error)); + return false; + } + } + else { + this.forEachCall((call) => { + if (call.localScreensharingFeed) + call.removeLocalFeed(call.localScreensharingFeed); + }); + this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream); + this.removeScreenshareFeed(this.localScreenshareFeed); + this.localScreenshareFeed = undefined; + this.localDesktopCapturerSourceId = undefined; + this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined); + return false; + } + }); + } + isScreensharing() { + return !!this.localScreenshareFeed; + } + /** + * Determines whether a given participant expects us to call them (versus + * them calling us). + * @param userId - The participant's user ID. + * @param deviceId - The participant's device ID. + * @returns Whether we need to place an outgoing call to the participant. + */ + wantsOutgoingCall(userId, deviceId) { + const localUserId = this.client.getUserId(); + const localDeviceId = this.client.getDeviceId(); + return ( + // If a user's ID is less than our own, they'll call us + userId >= localUserId && + // If this is another one of our devices, compare device IDs to tell whether it'll call us + (userId !== localUserId || deviceId > localDeviceId)); + } + /** + * Places calls to all participants that we're responsible for calling. + */ + placeOutgoingCalls() { + var _a; + let callsChanged = false; + for (const [{ userId }, participantMap] of this.participants) { + const callMap = (_a = this.calls.get(userId)) !== null && _a !== void 0 ? _a : new Map(); + for (const [deviceId, participant] of participantMap) { + const prevCall = callMap.get(deviceId); + if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.getOpponentSessionId()) !== participant.sessionId && + this.wantsOutgoingCall(userId, deviceId)) { + callsChanged = true; + if (prevCall !== undefined) { + logger_1.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`); + prevCall.hangup(call_1.CallErrorCode.NewSession, false); + } + const newCall = (0, call_1.createNewMatrixCall)(this.client, this.room.roomId, { + invitee: userId, + opponentDeviceId: deviceId, + opponentSessionId: participant.sessionId, + groupCallId: this.groupCallId, + }); + if (newCall === null) { + logger_1.logger.error(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`); + callMap.delete(deviceId); + } + else { + this.initCall(newCall); + callMap.set(deviceId, newCall); + logger_1.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`); + newCall + .placeCallWithCallFeeds(this.getLocalFeeds().map((feed) => feed.clone()), participant.screensharing) + .then(() => { + if (this.dataChannelsEnabled) { + newCall.createDataChannel("datachannel", this.dataChannelOptions); + } + }) + .catch((e) => { + logger_1.logger.warn(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`, e); + if (e instanceof call_1.CallError && e.code === GroupCallErrorCode.UnknownDevice) { + this.emit(GroupCallEvent.Error, e); + } + else { + this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.PlaceCallFailed, `Failed to place call to ${userId}`)); + } + newCall.hangup(call_1.CallErrorCode.SignallingFailed, false); + if (callMap.get(deviceId) === newCall) + callMap.delete(deviceId); + }); + } + } + } + if (callMap.size > 0) { + this.calls.set(userId, callMap); + } + else { + this.calls.delete(userId); + } + } + if (callsChanged) + this.emit(GroupCallEvent.CallsChanged, this.calls); + } + getMemberStateEvents(userId) { + return userId === undefined + ? this.room.currentState.getStateEvents(event_1.EventType.GroupCallMemberPrefix) + : this.room.currentState.getStateEvents(event_1.EventType.GroupCallMemberPrefix, userId); + } + initCall(call) { + const opponentMemberId = getCallUserId(call); + if (!opponentMemberId) { + throw new Error("Cannot init call without user id"); + } + const onCallFeedsChanged = () => this.onCallFeedsChanged(call); + const onCallStateChanged = (state, oldState) => this.onCallStateChanged(call, state, oldState); + const onCallHangup = this.onCallHangup; + const onCallReplaced = (newCall) => this.onCallReplaced(call, newCall); + let deviceMap = this.callHandlers.get(opponentMemberId); + if (deviceMap === undefined) { + deviceMap = new Map(); + this.callHandlers.set(opponentMemberId, deviceMap); + } + deviceMap.set(call.getOpponentDeviceId(), { + onCallFeedsChanged, + onCallStateChanged, + onCallHangup, + onCallReplaced, + }); + call.on(call_1.CallEvent.FeedsChanged, onCallFeedsChanged); + call.on(call_1.CallEvent.State, onCallStateChanged); + call.on(call_1.CallEvent.Hangup, onCallHangup); + call.on(call_1.CallEvent.Replaced, onCallReplaced); + call.isPtt = this.isPtt; + this.reEmitter.reEmit(call, Object.values(call_1.CallEvent)); + call.initStats(this.stats); + onCallFeedsChanged(); + } + disposeCall(call, hangupReason) { + const opponentMemberId = getCallUserId(call); + const opponentDeviceId = call.getOpponentDeviceId(); + if (!opponentMemberId) { + throw new Error("Cannot dispose call without user id"); + } + const deviceMap = this.callHandlers.get(opponentMemberId); + const { onCallFeedsChanged, onCallStateChanged, onCallHangup, onCallReplaced } = deviceMap.get(opponentDeviceId); + call.removeListener(call_1.CallEvent.FeedsChanged, onCallFeedsChanged); + call.removeListener(call_1.CallEvent.State, onCallStateChanged); + call.removeListener(call_1.CallEvent.Hangup, onCallHangup); + call.removeListener(call_1.CallEvent.Replaced, onCallReplaced); + deviceMap.delete(opponentMemberId); + if (deviceMap.size === 0) + this.callHandlers.delete(opponentMemberId); + if (call.hangupReason === call_1.CallErrorCode.Replaced) { + return; + } + const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId); + if (usermediaFeed) { + this.removeUserMediaFeed(usermediaFeed); + } + const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId); + if (screenshareFeed) { + this.removeScreenshareFeed(screenshareFeed); + } + } + /* + * UserMedia CallFeed Event Handlers + */ + getUserMediaFeed(userId, deviceId) { + return this.userMediaFeeds.find((f) => f.userId === userId && f.deviceId === deviceId); + } + addUserMediaFeed(callFeed) { + this.userMediaFeeds.push(callFeed); + callFeed.measureVolumeActivity(true); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + } + replaceUserMediaFeed(existingFeed, replacementFeed) { + const feedIndex = this.userMediaFeeds.findIndex((f) => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find user media feed to replace"); + } + this.userMediaFeeds.splice(feedIndex, 1, replacementFeed); + existingFeed.dispose(); + replacementFeed.measureVolumeActivity(true); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + } + removeUserMediaFeed(callFeed) { + const feedIndex = this.userMediaFeeds.findIndex((f) => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find user media feed to remove"); + } + this.userMediaFeeds.splice(feedIndex, 1); + callFeed.dispose(); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + if (this.activeSpeaker === callFeed) { + this.activeSpeaker = this.userMediaFeeds[0]; + this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker); + } + } + /* + * Screenshare Call Feed Event Handlers + */ + getScreenshareFeed(userId, deviceId) { + return this.screenshareFeeds.find((f) => f.userId === userId && f.deviceId === deviceId); + } + addScreenshareFeed(callFeed) { + this.screenshareFeeds.push(callFeed); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + replaceScreenshareFeed(existingFeed, replacementFeed) { + const feedIndex = this.screenshareFeeds.findIndex((f) => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find screenshare feed to replace"); + } + this.screenshareFeeds.splice(feedIndex, 1, replacementFeed); + existingFeed.dispose(); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + removeScreenshareFeed(callFeed) { + const feedIndex = this.screenshareFeeds.findIndex((f) => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find screenshare feed to remove"); + } + this.screenshareFeeds.splice(feedIndex, 1); + callFeed.dispose(); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + /** + * Recalculates and updates the participant map to match the room state. + */ + updateParticipants() { + const localMember = this.room.getMember(this.client.getUserId()); + if (!localMember) { + // The client hasn't fetched enough of the room state to get our own member + // event. This probably shouldn't happen, but sanity check & exit for now. + logger_1.logger.warn(`GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`); + return; + } + if (this.participantsExpirationTimer !== null) { + clearTimeout(this.participantsExpirationTimer); + this.participantsExpirationTimer = null; + } + if (this.state === GroupCallState.Ended) { + this.participants = new Map(); + return; + } + const participants = new Map(); + const now = Date.now(); + const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession; + let nextExpiration = Infinity; + for (const e of this.getMemberStateEvents()) { + const member = this.room.getMember(e.getStateKey()); + const content = e.getContent(); + const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : []; + const call = calls.find((call) => call["m.call_id"] === this.groupCallId); + const devices = Array.isArray(call === null || call === void 0 ? void 0 : call["m.devices"]) ? call["m.devices"] : []; + // Filter out invalid and expired devices + let validDevices = devices.filter((d) => typeof d.device_id === "string" && + typeof d.session_id === "string" && + typeof d.expires_ts === "number" && + d.expires_ts > now && + Array.isArray(d.feeds)); + // Apply local echo for the unentered case + if (!entered && (member === null || member === void 0 ? void 0 : member.userId) === this.client.getUserId()) { + validDevices = validDevices.filter((d) => d.device_id !== this.client.getDeviceId()); + } + // Must have a connected device and be joined to the room + if (validDevices.length > 0 && (member === null || member === void 0 ? void 0 : member.membership) === "join") { + const deviceMap = new Map(); + participants.set(member, deviceMap); + for (const d of validDevices) { + deviceMap.set(d.device_id, { + sessionId: d.session_id, + screensharing: d.feeds.some((f) => f.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare), + }); + if (d.expires_ts < nextExpiration) + nextExpiration = d.expires_ts; + } + } + } + // Apply local echo for the entered case + if (entered) { + let deviceMap = participants.get(localMember); + if (deviceMap === undefined) { + deviceMap = new Map(); + participants.set(localMember, deviceMap); + } + if (!deviceMap.has(this.client.getDeviceId())) { + deviceMap.set(this.client.getDeviceId(), { + sessionId: this.client.getSessionId(), + screensharing: this.getLocalFeeds().some((f) => f.purpose === callEventTypes_1.SDPStreamMetadataPurpose.Screenshare), + }); + } + } + this.participants = participants; + if (nextExpiration < Infinity) { + this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), nextExpiration - now); + } + } + /** + * Updates the local user's member state with the devices returned by the given function. + * @param fn - A function from the current devices to the new devices. If it + * returns null, the update will be skipped. + * @param keepAlive - Whether the request should outlive the window. + */ + updateDevices(fn, keepAlive = false) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const now = Date.now(); + const localUserId = this.client.getUserId(); + const event = this.getMemberStateEvents(localUserId); + const content = (_a = event === null || event === void 0 ? void 0 : event.getContent()) !== null && _a !== void 0 ? _a : {}; + const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : []; + let call = null; + const otherCalls = []; + for (const c of calls) { + if (c["m.call_id"] === this.groupCallId) { + call = c; + } + else { + otherCalls.push(c); + } + } + if (call === null) + call = {}; + const devices = Array.isArray(call["m.devices"]) ? call["m.devices"] : []; + // Filter out invalid and expired devices + const validDevices = devices.filter((d) => typeof d.device_id === "string" && + typeof d.session_id === "string" && + typeof d.expires_ts === "number" && + d.expires_ts > now && + Array.isArray(d.feeds)); + const newDevices = fn(validDevices); + if (newDevices === null) + return; + const newCalls = [...otherCalls]; + if (newDevices.length > 0) { + newCalls.push(Object.assign(Object.assign({}, call), { "m.call_id": this.groupCallId, "m.devices": newDevices })); + } + const newContent = { "m.calls": newCalls }; + yield this.client.sendStateEvent(this.room.roomId, event_1.EventType.GroupCallMemberPrefix, newContent, localUserId, { + keepAlive, + }); + }); + } + addDeviceToMemberState() { + return __awaiter(this, void 0, void 0, function* () { + yield this.updateDevices((devices) => [ + ...devices.filter((d) => d.device_id !== this.client.getDeviceId()), + { + device_id: this.client.getDeviceId(), + session_id: this.client.getSessionId(), + expires_ts: Date.now() + DEVICE_TIMEOUT, + feeds: this.getLocalFeeds().map((feed) => ({ purpose: feed.purpose })), + // TODO: Add data channels + }, + ]); + }); + } + updateMemberState() { + return __awaiter(this, void 0, void 0, function* () { + // Clear the old update interval before proceeding + if (this.resendMemberStateTimer !== null) { + clearInterval(this.resendMemberStateTimer); + this.resendMemberStateTimer = null; + } + if (this.state === GroupCallState.Entered) { + // Add the local device + yield this.addDeviceToMemberState(); + // Resend the state event every so often so it doesn't become stale + this.resendMemberStateTimer = setInterval(() => __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state"`); + try { + yield this.addDeviceToMemberState(); + } + catch (e) { + logger_1.logger.error(`GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`, e); + } + }), (DEVICE_TIMEOUT * 3) / 4); + } + else { + // Remove the local device + yield this.updateDevices((devices) => devices.filter((d) => d.device_id !== this.client.getDeviceId()), true); + } + }); + } + /** + * Cleans up our member state by filtering out logged out devices, inactive + * devices, and our own device (if we know we haven't entered). + */ + cleanMemberState() { + return __awaiter(this, void 0, void 0, function* () { + const { devices: myDevices } = yield this.client.getDevices(); + const deviceMap = new Map(myDevices.map((d) => [d.device_id, d])); + // updateDevices takes care of filtering out inactive devices for us + yield this.updateDevices((devices) => { + const newDevices = devices.filter((d) => { + const device = deviceMap.get(d.device_id); + return ((device === null || device === void 0 ? void 0 : device.last_seen_ts) !== undefined && + !(d.device_id === this.client.getDeviceId() && + this.state !== GroupCallState.Entered && + !this.enteredViaAnotherSession)); + }); + // Skip the update if the devices are unchanged + return newDevices.length === devices.length ? null : newDevices; + }); + }); + } + getGroupCallStats() { + return this.stats; + } +} +exports.GroupCall = GroupCall; + +},{"../@types/event":306,"../ReEmitter":317,"../logger":374,"../models/room-state":390,"../models/typed-event-emitter":395,"../utils":416,"./call":418,"./callEventHandler":419,"./callEventTypes":420,"./callFeed":421,"./groupCallEventHandler":423,"./stats/groupCallStats":427,"./stats/statsReport":432}],423:[function(require,module,exports){ +"use strict"; +/* +Copyright 2021 Šimon Brandner + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GroupCallEventHandler = exports.GroupCallEventHandlerEvent = void 0; +const client_1 = require("../client"); +const groupCall_1 = require("./groupCall"); +const room_state_1 = require("../models/room-state"); +const logger_1 = require("../logger"); +const event_1 = require("../@types/event"); +const sync_1 = require("../sync"); +var GroupCallEventHandlerEvent; +(function (GroupCallEventHandlerEvent) { + GroupCallEventHandlerEvent["Incoming"] = "GroupCall.incoming"; + GroupCallEventHandlerEvent["Outgoing"] = "GroupCall.outgoing"; + GroupCallEventHandlerEvent["Ended"] = "GroupCall.ended"; + GroupCallEventHandlerEvent["Participants"] = "GroupCall.participants"; +})(GroupCallEventHandlerEvent = exports.GroupCallEventHandlerEvent || (exports.GroupCallEventHandlerEvent = {})); +class GroupCallEventHandler { + constructor(client) { + this.client = client; + this.groupCalls = new Map(); // roomId -> GroupCall + // All rooms we know about and whether we've seen a 'Room' event + // for them. The promise will be fulfilled once we've processed that + // event which means we're "up to date" on what calls are in a room + // and get + this.roomDeferreds = new Map(); + this.onRoomsChanged = (room) => { + this.createGroupCallForRoom(room); + }; + this.onRoomStateChanged = (event, state) => { + const eventType = event.getType(); + if (eventType === event_1.EventType.GroupCallPrefix) { + const groupCallId = event.getStateKey(); + const content = event.getContent(); + const currentGroupCall = this.groupCalls.get(state.roomId); + if (!currentGroupCall && !content["m.terminated"] && !event.isRedacted()) { + this.createGroupCallFromRoomStateEvent(event); + } + else if (currentGroupCall && currentGroupCall.groupCallId === groupCallId) { + if (content["m.terminated"] || event.isRedacted()) { + currentGroupCall.terminate(false); + } + else if (content["m.type"] !== currentGroupCall.type) { + // TODO: Handle the callType changing when the room state changes + logger_1.logger.warn(`GroupCallEventHandler onRoomStateChanged() currently does not support changing type (roomId=${state.roomId})`); + } + } + else if (currentGroupCall && currentGroupCall.groupCallId !== groupCallId) { + // TODO: Handle new group calls and multiple group calls + logger_1.logger.warn(`GroupCallEventHandler onRoomStateChanged() currently does not support multiple calls (roomId=${state.roomId})`); + } + } + }; + } + start() { + return __awaiter(this, void 0, void 0, function* () { + // We wait until the client has started syncing for real. + // This is because we only support one call at a time, and want + // the latest. We therefore want the latest state of the room before + // we create a group call for the room so we can be fairly sure that + // the group call we create is really the latest one. + if (this.client.getSyncState() !== sync_1.SyncState.Syncing) { + logger_1.logger.debug("GroupCallEventHandler start() waiting for client to start syncing"); + yield new Promise((resolve) => { + const onSync = () => { + if (this.client.getSyncState() === sync_1.SyncState.Syncing) { + this.client.off(client_1.ClientEvent.Sync, onSync); + return resolve(); + } + }; + this.client.on(client_1.ClientEvent.Sync, onSync); + }); + } + const rooms = this.client.getRooms(); + for (const room of rooms) { + this.createGroupCallForRoom(room); + } + this.client.on(client_1.ClientEvent.Room, this.onRoomsChanged); + this.client.on(room_state_1.RoomStateEvent.Events, this.onRoomStateChanged); + }); + } + stop() { + this.client.removeListener(room_state_1.RoomStateEvent.Events, this.onRoomStateChanged); + } + getRoomDeferred(roomId) { + let deferred = this.roomDeferreds.get(roomId); + if (deferred === undefined) { + let resolveFunc; + deferred = { + prom: new Promise((resolve) => { + resolveFunc = resolve; + }), + }; + deferred.resolve = resolveFunc; + this.roomDeferreds.set(roomId, deferred); + } + return deferred; + } + waitUntilRoomReadyForGroupCalls(roomId) { + return this.getRoomDeferred(roomId).prom; + } + getGroupCallById(groupCallId) { + return [...this.groupCalls.values()].find((groupCall) => groupCall.groupCallId === groupCallId); + } + createGroupCallForRoom(room) { + const callEvents = room.currentState.getStateEvents(event_1.EventType.GroupCallPrefix); + const sortedCallEvents = callEvents.sort((a, b) => b.getTs() - a.getTs()); + for (const callEvent of sortedCallEvents) { + const content = callEvent.getContent(); + if (content["m.terminated"] || callEvent.isRedacted()) { + continue; + } + logger_1.logger.debug(`GroupCallEventHandler createGroupCallForRoom() choosing group call from possible calls (stateKey=${callEvent.getStateKey()}, ts=${callEvent.getTs()}, roomId=${room.roomId}, numOfPossibleCalls=${callEvents.length})`); + this.createGroupCallFromRoomStateEvent(callEvent); + break; + } + logger_1.logger.info(`GroupCallEventHandler createGroupCallForRoom() processed room (roomId=${room.roomId})`); + this.getRoomDeferred(room.roomId).resolve(); + } + createGroupCallFromRoomStateEvent(event) { + const roomId = event.getRoomId(); + const content = event.getContent(); + const room = this.client.getRoom(roomId); + if (!room) { + logger_1.logger.warn(`GroupCallEventHandler createGroupCallFromRoomStateEvent() couldn't find room for call (roomId=${roomId})`); + return; + } + const groupCallId = event.getStateKey(); + const callType = content["m.type"]; + if (!Object.values(groupCall_1.GroupCallType).includes(callType)) { + logger_1.logger.warn(`GroupCallEventHandler createGroupCallFromRoomStateEvent() received invalid call type (type=${callType}, roomId=${roomId})`); + return; + } + const callIntent = content["m.intent"]; + if (!Object.values(groupCall_1.GroupCallIntent).includes(callIntent)) { + logger_1.logger.warn(`Received invalid group call intent (type=${callType}, roomId=${roomId})`); + return; + } + const isPtt = Boolean(content["io.element.ptt"]); + let dataChannelOptions; + if ((content === null || content === void 0 ? void 0 : content.dataChannelsEnabled) && (content === null || content === void 0 ? void 0 : content.dataChannelOptions)) { + // Pull out just the dataChannelOptions we want to support. + const { ordered, maxPacketLifeTime, maxRetransmits, protocol } = content.dataChannelOptions; + dataChannelOptions = { ordered, maxPacketLifeTime, maxRetransmits, protocol }; + } + const groupCall = new groupCall_1.GroupCall(this.client, room, callType, isPtt, callIntent, groupCallId, + // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a + // no media WebRTC connection anyway. + (content === null || content === void 0 ? void 0 : content.dataChannelsEnabled) || this.client.isVoipWithNoMediaAllowed, dataChannelOptions, this.client.isVoipWithNoMediaAllowed); + this.groupCalls.set(room.roomId, groupCall); + this.client.emit(GroupCallEventHandlerEvent.Incoming, groupCall); + return groupCall; + } +} +exports.GroupCallEventHandler = GroupCallEventHandler; + +},{"../@types/event":306,"../client":321,"../logger":374,"../models/room-state":390,"../sync":414,"./groupCall":422}],424:[function(require,module,exports){ +"use strict"; +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2022 Šimon Brandner + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaHandler = exports.MediaHandlerEvent = void 0; +const typed_event_emitter_1 = require("../models/typed-event-emitter"); +const groupCall_1 = require("../webrtc/groupCall"); +const logger_1 = require("../logger"); +var MediaHandlerEvent; +(function (MediaHandlerEvent) { + MediaHandlerEvent["LocalStreamsChanged"] = "local_streams_changed"; +})(MediaHandlerEvent = exports.MediaHandlerEvent || (exports.MediaHandlerEvent = {})); +class MediaHandler extends typed_event_emitter_1.TypedEventEmitter { + constructor(client) { + super(); + this.client = client; + this.userMediaStreams = []; + this.screensharingStreams = []; + } + restoreMediaSettings(audioInput, videoInput) { + this.audioInput = audioInput; + this.videoInput = videoInput; + } + /** + * Set an audio input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + setAudioInput(deviceId) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info(`MediaHandler setAudioInput() running (deviceId=${deviceId})`); + if (this.audioInput === deviceId) + return; + this.audioInput = deviceId; + yield this.updateLocalUsermediaStreams(); + }); + } + /** + * Set audio settings for MatrixCalls + * @param opts - audio options to set + */ + setAudioSettings(opts) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info(`MediaHandler setAudioSettings() running (opts=${JSON.stringify(opts)})`); + this.audioSettings = Object.assign({}, opts); + yield this.updateLocalUsermediaStreams(); + }); + } + /** + * Set a video input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + setVideoInput(deviceId) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.info(`MediaHandler setVideoInput() running (deviceId=${deviceId})`); + if (this.videoInput === deviceId) + return; + this.videoInput = deviceId; + yield this.updateLocalUsermediaStreams(); + }); + } + /** + * Set media input devices to use for MatrixCalls + * @param audioInput - the identifier for the audio device + * @param videoInput - the identifier for the video device + * undefined treated as unset + */ + setMediaInputs(audioInput, videoInput) { + return __awaiter(this, void 0, void 0, function* () { + logger_1.logger.log(`MediaHandler setMediaInputs() running (audioInput: ${audioInput} videoInput: ${videoInput})`); + this.audioInput = audioInput; + this.videoInput = videoInput; + yield this.updateLocalUsermediaStreams(); + }); + } + /* + * Requests new usermedia streams and replace the old ones + */ + updateLocalUsermediaStreams() { + return __awaiter(this, void 0, void 0, function* () { + if (this.userMediaStreams.length === 0) + return; + const callMediaStreamParams = new Map(); + for (const call of this.client.callEventHandler.calls.values()) { + callMediaStreamParams.set(call.callId, { + audio: call.hasLocalUserMediaAudioTrack, + video: call.hasLocalUserMediaVideoTrack, + }); + } + for (const stream of this.userMediaStreams) { + logger_1.logger.log(`MediaHandler updateLocalUsermediaStreams() stopping all tracks (streamId=${stream.id})`); + for (const track of stream.getTracks()) { + track.stop(); + } + } + this.userMediaStreams = []; + this.localUserMediaStream = undefined; + for (const call of this.client.callEventHandler.calls.values()) { + if (call.callHasEnded() || !callMediaStreamParams.has(call.callId)) { + continue; + } + const { audio, video } = callMediaStreamParams.get(call.callId); + logger_1.logger.log(`MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (callId=${call.callId})`); + const stream = yield this.getUserMediaStream(audio, video); + if (call.callHasEnded()) { + continue; + } + yield call.updateLocalUsermediaStream(stream); + } + for (const groupCall of this.client.groupCallEventHandler.groupCalls.values()) { + if (!groupCall.localCallFeed) { + continue; + } + logger_1.logger.log(`MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (groupCallId=${groupCall.groupCallId})`); + const stream = yield this.getUserMediaStream(true, groupCall.type === groupCall_1.GroupCallType.Video); + if (groupCall.state === groupCall_1.GroupCallState.Ended) { + continue; + } + yield groupCall.updateLocalUsermediaStream(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + }); + } + hasAudioDevice() { + return __awaiter(this, void 0, void 0, function* () { + try { + const devices = yield navigator.mediaDevices.enumerateDevices(); + return devices.filter((device) => device.kind === "audioinput").length > 0; + } + catch (err) { + logger_1.logger.log(`MediaHandler hasAudioDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } + }); + } + hasVideoDevice() { + return __awaiter(this, void 0, void 0, function* () { + try { + const devices = yield navigator.mediaDevices.enumerateDevices(); + return devices.filter((device) => device.kind === "videoinput").length > 0; + } + catch (err) { + logger_1.logger.log(`MediaHandler hasVideoDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } + }); + } + /** + * @param audio - should have an audio track + * @param video - should have a video track + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + getUserMediaStream(audio, video, reusable = true) { + return __awaiter(this, void 0, void 0, function* () { + // Serialise calls, othertwise we can't sensibly re-use the stream + if (this.getMediaStreamPromise) { + this.getMediaStreamPromise = this.getMediaStreamPromise.then(() => { + return this.getUserMediaStreamInternal(audio, video, reusable); + }); + } + else { + this.getMediaStreamPromise = this.getUserMediaStreamInternal(audio, video, reusable); + } + return this.getMediaStreamPromise; + }); + } + getUserMediaStreamInternal(audio, video, reusable) { + var _a, _b, _c, _d, _e; + return __awaiter(this, void 0, void 0, function* () { + const shouldRequestAudio = audio && (yield this.hasAudioDevice()); + const shouldRequestVideo = video && (yield this.hasVideoDevice()); + let stream; + let canReuseStream = true; + if (this.localUserMediaStream) { + // This figures out if we can reuse the current localUsermediaStream + // based on whether or not the "mute state" (presence of tracks of a + // given kind) matches what is being requested + if (shouldRequestAudio !== this.localUserMediaStream.getAudioTracks().length > 0) { + canReuseStream = false; + } + if (shouldRequestVideo !== this.localUserMediaStream.getVideoTracks().length > 0) { + canReuseStream = false; + } + // This code checks that the device ID is the same as the localUserMediaStream stream, but we update + // the localUserMediaStream whenever the device ID changes (apart from when restoring) so it's not + // clear why this would ever be different, unless there's a race. + if (shouldRequestAudio && + ((_b = (_a = this.localUserMediaStream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings()) === null || _b === void 0 ? void 0 : _b.deviceId) !== this.audioInput) { + canReuseStream = false; + } + if (shouldRequestVideo && + ((_d = (_c = this.localUserMediaStream.getVideoTracks()[0]) === null || _c === void 0 ? void 0 : _c.getSettings()) === null || _d === void 0 ? void 0 : _d.deviceId) !== this.videoInput) { + canReuseStream = false; + } + } + else { + canReuseStream = false; + } + if (!canReuseStream) { + const constraints = this.getUserMediaContraints(shouldRequestAudio, shouldRequestVideo); + stream = yield navigator.mediaDevices.getUserMedia(constraints); + logger_1.logger.log(`MediaHandler getUserMediaStreamInternal() calling getUserMediaStream (streamId=${stream.id}, shouldRequestAudio=${shouldRequestAudio}, shouldRequestVideo=${shouldRequestVideo}, constraints=${JSON.stringify(constraints)})`); + for (const track of stream.getTracks()) { + const settings = track.getSettings(); + if (track.kind === "audio") { + this.audioInput = settings.deviceId; + } + else if (track.kind === "video") { + this.videoInput = settings.deviceId; + } + } + if (reusable) { + this.localUserMediaStream = stream; + } + } + else { + stream = this.localUserMediaStream.clone(); + logger_1.logger.log(`MediaHandler getUserMediaStreamInternal() cloning (oldStreamId=${(_e = this.localUserMediaStream) === null || _e === void 0 ? void 0 : _e.id} newStreamId=${stream.id} shouldRequestAudio=${shouldRequestAudio} shouldRequestVideo=${shouldRequestVideo})`); + if (!shouldRequestAudio) { + for (const track of stream.getAudioTracks()) { + stream.removeTrack(track); + } + } + if (!shouldRequestVideo) { + for (const track of stream.getVideoTracks()) { + stream.removeTrack(track); + } + } + } + if (reusable) { + this.userMediaStreams.push(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + return stream; + }); + } + /** + * Stops all tracks on the provided usermedia stream + */ + stopUserMediaStream(mediaStream) { + logger_1.logger.log(`MediaHandler stopUserMediaStream() stopping (streamId=${mediaStream.id})`); + for (const track of mediaStream.getTracks()) { + track.stop(); + } + const index = this.userMediaStreams.indexOf(mediaStream); + if (index !== -1) { + logger_1.logger.debug(`MediaHandler stopUserMediaStream() splicing usermedia stream out stream array (streamId=${mediaStream.id})`, mediaStream.id); + this.userMediaStreams.splice(index, 1); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + if (this.localUserMediaStream === mediaStream) { + this.localUserMediaStream = undefined; + } + } + /** + * @param desktopCapturerSourceId - sourceId for Electron DesktopCapturer + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + getScreensharingStream(opts = {}, reusable = true) { + return __awaiter(this, void 0, void 0, function* () { + let stream; + if (this.screensharingStreams.length === 0) { + const screenshareConstraints = this.getScreenshareContraints(opts); + if (opts.desktopCapturerSourceId) { + // We are using Electron + logger_1.logger.debug(`MediaHandler getScreensharingStream() calling getUserMedia() (opts=${JSON.stringify(opts)})`); + stream = yield navigator.mediaDevices.getUserMedia(screenshareConstraints); + } + else { + // We are not using Electron + logger_1.logger.debug(`MediaHandler getScreensharingStream() calling getDisplayMedia() (opts=${JSON.stringify(opts)})`); + stream = yield navigator.mediaDevices.getDisplayMedia(screenshareConstraints); + } + } + else { + const matchingStream = this.screensharingStreams[this.screensharingStreams.length - 1]; + logger_1.logger.log(`MediaHandler getScreensharingStream() cloning (streamId=${matchingStream.id})`); + stream = matchingStream.clone(); + } + if (reusable) { + this.screensharingStreams.push(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + return stream; + }); + } + /** + * Stops all tracks on the provided screensharing stream + */ + stopScreensharingStream(mediaStream) { + logger_1.logger.debug(`MediaHandler stopScreensharingStream() stopping stream (streamId=${mediaStream.id})`); + for (const track of mediaStream.getTracks()) { + track.stop(); + } + const index = this.screensharingStreams.indexOf(mediaStream); + if (index !== -1) { + logger_1.logger.debug(`MediaHandler stopScreensharingStream() splicing stream out (streamId=${mediaStream.id})`); + this.screensharingStreams.splice(index, 1); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + } + /** + * Stops all local media tracks + */ + stopAllStreams() { + for (const stream of this.userMediaStreams) { + logger_1.logger.log(`MediaHandler stopAllStreams() stopping (streamId=${stream.id})`); + for (const track of stream.getTracks()) { + track.stop(); + } + } + for (const stream of this.screensharingStreams) { + for (const track of stream.getTracks()) { + track.stop(); + } + } + this.userMediaStreams = []; + this.screensharingStreams = []; + this.localUserMediaStream = undefined; + this.emit(MediaHandlerEvent.LocalStreamsChanged); + } + getUserMediaContraints(audio, video) { + const isWebkit = !!navigator.webkitGetUserMedia; + return { + audio: audio + ? { + deviceId: this.audioInput ? { ideal: this.audioInput } : undefined, + autoGainControl: this.audioSettings ? { ideal: this.audioSettings.autoGainControl } : undefined, + echoCancellation: this.audioSettings ? { ideal: this.audioSettings.echoCancellation } : undefined, + noiseSuppression: this.audioSettings ? { ideal: this.audioSettings.noiseSuppression } : undefined, + } + : false, + video: video + ? { + deviceId: this.videoInput ? { ideal: this.videoInput } : undefined, + /* We want 640x360. Chrome will give it only if we ask exactly, + FF refuses entirely if we ask exactly, so have to ask for ideal + instead + XXX: Is this still true? + */ + width: isWebkit ? { exact: 640 } : { ideal: 640 }, + height: isWebkit ? { exact: 360 } : { ideal: 360 }, + } + : false, + }; + } + getScreenshareContraints(opts) { + const { desktopCapturerSourceId, audio } = opts; + if (desktopCapturerSourceId) { + return { + audio: audio !== null && audio !== void 0 ? audio : false, + video: { + mandatory: { + chromeMediaSource: "desktop", + chromeMediaSourceId: desktopCapturerSourceId, + }, + }, + }; + } + else { + return { + audio: audio !== null && audio !== void 0 ? audio : false, + video: true, + }; + } + } +} +exports.MediaHandler = MediaHandler; + +},{"../logger":374,"../models/typed-event-emitter":395,"../webrtc/groupCall":422}],425:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConnectionStats = void 0; +class ConnectionStats { + constructor() { + this.bandwidth = {}; + this.bitrate = {}; + this.packetLoss = {}; + this.transport = []; + } +} +exports.ConnectionStats = ConnectionStats; + +},{}],426:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConnectionStatsReporter = void 0; +class ConnectionStatsReporter { + static buildBandwidthReport(now) { + const availableIncomingBitrate = now.availableIncomingBitrate; + const availableOutgoingBitrate = now.availableOutgoingBitrate; + return { + download: availableIncomingBitrate ? Math.round(availableIncomingBitrate / 1000) : 0, + upload: availableOutgoingBitrate ? Math.round(availableOutgoingBitrate / 1000) : 0, + }; + } +} +exports.ConnectionStatsReporter = ConnectionStatsReporter; + +},{}],427:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GroupCallStats = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +const statsReportGatherer_1 = require("./statsReportGatherer"); +const statsReportEmitter_1 = require("./statsReportEmitter"); +class GroupCallStats { + constructor(groupCallId, userId, interval = 10000) { + this.groupCallId = groupCallId; + this.userId = userId; + this.interval = interval; + this.gatherers = new Map(); + this.reports = new statsReportEmitter_1.StatsReportEmitter(); + } + start() { + if (this.timer === undefined) { + this.timer = setInterval(() => { + this.processStats(); + }, this.interval); + } + } + stop() { + if (this.timer !== undefined) { + clearInterval(this.timer); + this.gatherers.forEach((c) => c.stopProcessingStats()); + } + } + hasStatsReportGatherer(callId) { + return this.gatherers.has(callId); + } + addStatsReportGatherer(callId, userId, peerConnection) { + if (this.hasStatsReportGatherer(callId)) { + return false; + } + this.gatherers.set(callId, new statsReportGatherer_1.StatsReportGatherer(callId, userId, peerConnection, this.reports)); + return true; + } + removeStatsReportGatherer(callId) { + return this.gatherers.delete(callId); + } + getStatsReportGatherer(callId) { + return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined; + } + processStats() { + this.gatherers.forEach((c) => c.processStats(this.groupCallId, this.userId)); + } +} +exports.GroupCallStats = GroupCallStats; + +},{"./statsReportEmitter":434,"./statsReportGatherer":435}],428:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaSsrcHandler = void 0; +const sdp_transform_1 = require("sdp-transform"); +class MediaSsrcHandler { + constructor() { + this.ssrcToMid = { local: new Map(), remote: new Map() }; + } + findMidBySsrc(ssrc, type) { + let mid; + this.ssrcToMid[type].forEach((ssrcs, m) => { + if (ssrcs.find((s) => s == ssrc)) { + mid = m; + return; + } + }); + return mid; + } + parse(description, type) { + const sdp = (0, sdp_transform_1.parse)(description); + const ssrcToMid = new Map(); + sdp.media.forEach((m) => { + var _a; + if ((!!m.mid && m.type === "video") || m.type === "audio") { + const ssrcs = []; + (_a = m.ssrcs) === null || _a === void 0 ? void 0 : _a.forEach((ssrc) => { + if (ssrc.attribute === "cname") { + ssrcs.push(`${ssrc.id}`); + } + }); + ssrcToMid.set(`${m.mid}`, ssrcs); + } + }); + this.ssrcToMid[type] = ssrcToMid; + } + getSsrcToMidMap(type) { + return this.ssrcToMid[type]; + } +} +exports.MediaSsrcHandler = MediaSsrcHandler; + +},{"sdp-transform":253}],429:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaTrackHandler = void 0; +class MediaTrackHandler { + constructor(pc) { + this.pc = pc; + } + getLocalTracks(kind) { + const isNotNullAndKind = (track) => { + return track !== null && track.kind === kind; + }; + // @ts-ignore The linter don't get it + return this.pc + .getTransceivers() + .filter((t) => t.currentDirection === "sendonly" || t.currentDirection === "sendrecv") + .filter((t) => t.sender !== null) + .map((t) => t.sender) + .map((s) => s.track) + .filter(isNotNullAndKind); + } + getTackById(trackId) { + return this.pc + .getTransceivers() + .map((t) => { + if ((t === null || t === void 0 ? void 0 : t.sender.track) !== null && t.sender.track.id === trackId) { + return t.sender.track; + } + if ((t === null || t === void 0 ? void 0 : t.receiver.track) !== null && t.receiver.track.id === trackId) { + return t.receiver.track; + } + return undefined; + }) + .find((t) => t !== undefined); + } + getLocalTrackIdByMid(mid) { + const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid); + if (transceiver !== undefined && !!transceiver.sender && !!transceiver.sender.track) { + return transceiver.sender.track.id; + } + return undefined; + } + getRemoteTrackIdByMid(mid) { + const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid); + if (transceiver !== undefined && !!transceiver.receiver && !!transceiver.receiver.track) { + return transceiver.receiver.track.id; + } + return undefined; + } + getActiveSimulcastStreams() { + //@TODO implement this right.. Check how many layer configured + return 3; + } +} +exports.MediaTrackHandler = MediaTrackHandler; + +},{}],430:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaTrackStats = void 0; +class MediaTrackStats { + constructor(trackId, type, kind) { + this.trackId = trackId; + this.type = type; + this.kind = kind; + this.loss = { packetsTotal: 0, packetsLost: 0, isDownloadStream: false }; + this.bitrate = { download: 0, upload: 0 }; + this.resolution = { width: -1, height: -1 }; + this.framerate = 0; + this.codec = ""; + } + getType() { + return this.type; + } + setLoss(loos) { + this.loss = loos; + } + getLoss() { + return this.loss; + } + setResolution(resolution) { + this.resolution = resolution; + } + getResolution() { + return this.resolution; + } + setFramerate(framerate) { + this.framerate = framerate; + } + getFramerate() { + return this.framerate; + } + setBitrate(bitrate) { + this.bitrate = bitrate; + } + getBitrate() { + return this.bitrate; + } + setCodec(codecShortType) { + this.codec = codecShortType; + return true; + } + getCodec() { + return this.codec; + } + resetBitrate() { + this.bitrate = { download: 0, upload: 0 }; + } +} +exports.MediaTrackStats = MediaTrackStats; + +},{}],431:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MediaTrackStatsHandler = void 0; +const mediaTrackStats_1 = require("./mediaTrackStats"); +class MediaTrackStatsHandler { + constructor(mediaSsrcHandler, mediaTrackHandler) { + this.mediaSsrcHandler = mediaSsrcHandler; + this.mediaTrackHandler = mediaTrackHandler; + this.track2stats = new Map(); + } + /** + * Find tracks by rtc stats + * Argument report is any because the stats api is not consistent: + * For example `trackIdentifier`, `mid` not existing in every implementations + * https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats + * https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats + */ + findTrack2Stats(report, type) { + let trackID; + if (report.trackIdentifier) { + trackID = report.trackIdentifier; + } + else if (report.mid) { + trackID = + type === "remote" + ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid) + : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid); + } + else if (report.ssrc) { + const mid = this.mediaSsrcHandler.findMidBySsrc(report.ssrc, type); + if (!mid) { + return undefined; + } + trackID = + type === "remote" + ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid) + : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid); + } + if (!trackID) { + return undefined; + } + let trackStats = this.track2stats.get(trackID); + if (!trackStats) { + const track = this.mediaTrackHandler.getTackById(trackID); + if (track !== undefined) { + const kind = track.kind === "audio" ? track.kind : "video"; + trackStats = new mediaTrackStats_1.MediaTrackStats(trackID, type, kind); + this.track2stats.set(trackID, trackStats); + } + else { + return undefined; + } + } + return trackStats; + } + findLocalVideoTrackStats(report) { + const localVideoTracks = this.mediaTrackHandler.getLocalTracks("video"); + if (localVideoTracks.length === 0) { + return undefined; + } + return this.findTrack2Stats(report, "local"); + } + getTrack2stats() { + return this.track2stats; + } +} +exports.MediaTrackStatsHandler = MediaTrackStatsHandler; + +},{"./mediaTrackStats":430}],432:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsReport = void 0; +var StatsReport; +(function (StatsReport) { + StatsReport["CONNECTION_STATS"] = "StatsReport.connection_stats"; + StatsReport["BYTE_SENT_STATS"] = "StatsReport.byte_sent_stats"; +})(StatsReport = exports.StatsReport || (exports.StatsReport = {})); + +},{}],433:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsReportBuilder = void 0; +class StatsReportBuilder { + static build(stats) { + const report = {}; + // process stats + const totalPackets = { + download: 0, + upload: 0, + }; + const lostPackets = { + download: 0, + upload: 0, + }; + let bitrateDownload = 0; + let bitrateUpload = 0; + const resolutions = { + local: new Map(), + remote: new Map(), + }; + const framerates = { local: new Map(), remote: new Map() }; + const codecs = { local: new Map(), remote: new Map() }; + let audioBitrateDownload = 0; + let audioBitrateUpload = 0; + let videoBitrateDownload = 0; + let videoBitrateUpload = 0; + for (const [trackId, trackStats] of stats) { + // process packet loss stats + const loss = trackStats.getLoss(); + const type = loss.isDownloadStream ? "download" : "upload"; + totalPackets[type] += loss.packetsTotal; + lostPackets[type] += loss.packetsLost; + // process bitrate stats + bitrateDownload += trackStats.getBitrate().download; + bitrateUpload += trackStats.getBitrate().upload; + // collect resolutions and framerates + if (trackStats.kind === "audio") { + audioBitrateDownload += trackStats.getBitrate().download; + audioBitrateUpload += trackStats.getBitrate().upload; + } + else { + videoBitrateDownload += trackStats.getBitrate().download; + videoBitrateUpload += trackStats.getBitrate().upload; + } + resolutions[trackStats.getType()].set(trackId, trackStats.getResolution()); + framerates[trackStats.getType()].set(trackId, trackStats.getFramerate()); + codecs[trackStats.getType()].set(trackId, trackStats.getCodec()); + trackStats.resetBitrate(); + } + report.bitrate = { + upload: bitrateUpload, + download: bitrateDownload, + }; + report.bitrate.audio = { + upload: audioBitrateUpload, + download: audioBitrateDownload, + }; + report.bitrate.video = { + upload: videoBitrateUpload, + download: videoBitrateDownload, + }; + report.packetLoss = { + total: StatsReportBuilder.calculatePacketLoss(lostPackets.download + lostPackets.upload, totalPackets.download + totalPackets.upload), + download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download), + upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload), + }; + report.framerate = framerates; + report.resolution = resolutions; + report.codec = codecs; + return report; + } + static calculatePacketLoss(lostPackets, totalPackets) { + if (!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0) { + return 0; + } + return Math.round((lostPackets / totalPackets) * 100); + } +} +exports.StatsReportBuilder = StatsReportBuilder; + +},{}],434:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsReportEmitter = void 0; +const typed_event_emitter_1 = require("../../models/typed-event-emitter"); +const statsReport_1 = require("./statsReport"); +class StatsReportEmitter extends typed_event_emitter_1.TypedEventEmitter { + emitByteSendReport(byteSentStats) { + this.emit(statsReport_1.StatsReport.BYTE_SENT_STATS, byteSentStats); + } + emitConnectionStatsReport(report) { + this.emit(statsReport_1.StatsReport.CONNECTION_STATS, report); + } +} +exports.StatsReportEmitter = StatsReportEmitter; + +},{"../../models/typed-event-emitter":395,"./statsReport":432}],435:[function(require,module,exports){ +"use strict"; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsReportGatherer = void 0; +const connectionStats_1 = require("./connectionStats"); +const connectionStatsReporter_1 = require("./connectionStatsReporter"); +const transportStatsReporter_1 = require("./transportStatsReporter"); +const mediaSsrcHandler_1 = require("./media/mediaSsrcHandler"); +const mediaTrackHandler_1 = require("./media/mediaTrackHandler"); +const mediaTrackStatsHandler_1 = require("./media/mediaTrackStatsHandler"); +const trackStatsReporter_1 = require("./trackStatsReporter"); +const statsReportBuilder_1 = require("./statsReportBuilder"); +const statsValueFormatter_1 = require("./statsValueFormatter"); +class StatsReportGatherer { + // private readonly ssrcToMid = { local: new Map(), remote: new Map() }; + constructor(callId, remoteUserId, pc, emitter, isFocus = true) { + this.callId = callId; + this.remoteUserId = remoteUserId; + this.pc = pc; + this.emitter = emitter; + this.isFocus = isFocus; + this.isActive = true; + this.connectionStats = new connectionStats_1.ConnectionStats(); + pc.addEventListener("signalingstatechange", this.onSignalStateChange.bind(this)); + this.trackStats = new mediaTrackStatsHandler_1.MediaTrackStatsHandler(new mediaSsrcHandler_1.MediaSsrcHandler(), new mediaTrackHandler_1.MediaTrackHandler(pc)); + } + processStats(groupCallId, localUserId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.isActive) { + const statsPromise = this.pc.getStats(); + if (typeof (statsPromise === null || statsPromise === void 0 ? void 0 : statsPromise.then) === "function") { + return statsPromise + .then((report) => { + // @ts-ignore + this.currentStatsReport = typeof (report === null || report === void 0 ? void 0 : report.result) === "function" ? report.result() : report; + try { + this.processStatsReport(groupCallId, localUserId); + } + catch (error) { + this.isActive = false; + return false; + } + this.previousStatsReport = this.currentStatsReport; + return true; + }) + .catch((error) => { + this.handleError(error); + return false; + }); + } + this.isActive = false; + } + return Promise.resolve(false); + }); + } + processStatsReport(groupCallId, localUserId) { + var _a; + const byteSentStats = new Map(); + (_a = this.currentStatsReport) === null || _a === void 0 ? void 0 : _a.forEach((now) => { + const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null; + // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict* + if (now.type === "candidate-pair" && now.nominated && now.state === "succeeded") { + this.connectionStats.bandwidth = connectionStatsReporter_1.ConnectionStatsReporter.buildBandwidthReport(now); + this.connectionStats.transport = transportStatsReporter_1.TransportStatsReporter.buildReport(this.currentStatsReport, now, this.connectionStats.transport, this.isFocus); + // RTCReceivedRtpStreamStats + // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict* + // RTCSentRtpStreamStats + // https://w3c.github.io/webrtc-stats/#sentrtpstats-dict* + } + else if (now.type === "inbound-rtp" || now.type === "outbound-rtp") { + const trackStats = this.trackStats.findTrack2Stats(now, now.type === "inbound-rtp" ? "remote" : "local"); + if (!trackStats) { + return; + } + if (before) { + trackStatsReporter_1.TrackStatsReporter.buildPacketsLost(trackStats, now, before); + } + // Get the resolution and framerate for only remote video sources here. For the local video sources, + // 'track' stats will be used since they have the updated resolution based on the simulcast streams + // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be + // more calculations needed to determine what is the highest resolution stream sent by the client if the + // 'outbound-rtp' stats are used. + if (now.type === "inbound-rtp") { + trackStatsReporter_1.TrackStatsReporter.buildFramerateResolution(trackStats, now); + if (before) { + trackStatsReporter_1.TrackStatsReporter.buildBitrateReceived(trackStats, now, before); + } + } + else if (before) { + byteSentStats.set(trackStats.trackId, statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(now.bytesSent)); + trackStatsReporter_1.TrackStatsReporter.buildBitrateSend(trackStats, now, before); + } + trackStatsReporter_1.TrackStatsReporter.buildCodec(this.currentStatsReport, trackStats, now); + } + else if (now.type === "track" && now.kind === "video" && !now.remoteSource) { + const trackStats = this.trackStats.findLocalVideoTrackStats(now); + if (!trackStats) { + return; + } + trackStatsReporter_1.TrackStatsReporter.buildFramerateResolution(trackStats, now); + trackStatsReporter_1.TrackStatsReporter.calculateSimulcastFramerate(trackStats, now, before, this.trackStats.mediaTrackHandler.getActiveSimulcastStreams()); + } + }); + this.emitter.emitByteSendReport(byteSentStats); + this.processAndEmitReport(); + } + setActive(isActive) { + this.isActive = isActive; + } + getActive() { + return this.isActive; + } + handleError(_) { + this.isActive = false; + } + processAndEmitReport() { + const report = statsReportBuilder_1.StatsReportBuilder.build(this.trackStats.getTrack2stats()); + this.connectionStats.bandwidth = report.bandwidth; + this.connectionStats.bitrate = report.bitrate; + this.connectionStats.packetLoss = report.packetLoss; + this.emitter.emitConnectionStatsReport(Object.assign(Object.assign({}, report), { transport: this.connectionStats.transport })); + this.connectionStats.transport = []; + } + stopProcessingStats() { } + onSignalStateChange() { + if (this.pc.signalingState === "stable") { + if (this.pc.currentRemoteDescription) { + this.trackStats.mediaSsrcHandler.parse(this.pc.currentRemoteDescription.sdp, "remote"); + } + if (this.pc.currentLocalDescription) { + this.trackStats.mediaSsrcHandler.parse(this.pc.currentLocalDescription.sdp, "local"); + } + } + } +} +exports.StatsReportGatherer = StatsReportGatherer; + +},{"./connectionStats":425,"./connectionStatsReporter":426,"./media/mediaSsrcHandler":428,"./media/mediaTrackHandler":429,"./media/mediaTrackStatsHandler":431,"./statsReportBuilder":433,"./statsValueFormatter":436,"./trackStatsReporter":437,"./transportStatsReporter":438}],436:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsValueFormatter = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. +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. +*/ +class StatsValueFormatter { + static getNonNegativeValue(imput) { + let value = imput; + if (typeof value !== "number") { + value = Number(value); + } + if (isNaN(value)) { + return 0; + } + return Math.max(0, value); + } +} +exports.StatsValueFormatter = StatsValueFormatter; + +},{}],437:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TrackStatsReporter = void 0; +const statsValueFormatter_1 = require("./statsValueFormatter"); +class TrackStatsReporter { + static buildFramerateResolution(trackStats, now) { + const resolution = { + height: now.frameHeight, + width: now.frameWidth, + }; + const frameRate = now.framesPerSecond; + if (resolution.height && resolution.width) { + trackStats.setResolution(resolution); + } + trackStats.setFramerate(Math.round(frameRate || 0)); + } + static calculateSimulcastFramerate(trackStats, now, before, layer) { + let frameRate = trackStats.getFramerate(); + if (!frameRate) { + if (before) { + const timeMs = now.timestamp - before.timestamp; + if (timeMs > 0 && now.framesSent) { + const numberOfFramesSinceBefore = now.framesSent - before.framesSent; + frameRate = (numberOfFramesSinceBefore / timeMs) * 1000; + } + } + if (!frameRate) { + return; + } + } + // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n. + frameRate = layer ? Math.round(frameRate / layer) : 0; + trackStats.setFramerate(frameRate); + } + static buildCodec(report, trackStats, now) { + const codec = report === null || report === void 0 ? void 0 : report.get(now.codecId); + if (codec) { + /** + * The mime type has the following form: video/VP8 or audio/ISAC, + * so we what to keep just the type after the '/', audio and video + * keys will be added on the processing side. + */ + const codecShortType = codec.mimeType.split("/")[1]; + codecShortType && trackStats.setCodec(codecShortType); + } + } + static buildBitrateReceived(trackStats, now, before) { + trackStats.setBitrate({ + download: TrackStatsReporter.calculateBitrate(now.bytesReceived, before.bytesReceived, now.timestamp, before.timestamp), + upload: 0, + }); + } + static buildBitrateSend(trackStats, now, before) { + trackStats.setBitrate({ + download: 0, + upload: this.calculateBitrate(now.bytesSent, before.bytesSent, now.timestamp, before.timestamp), + }); + } + static buildPacketsLost(trackStats, now, before) { + const key = now.type === "outbound-rtp" ? "packetsSent" : "packetsReceived"; + let packetsNow = now[key]; + if (!packetsNow || packetsNow < 0) { + packetsNow = 0; + } + const packetsBefore = statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(before[key]); + const packetsDiff = Math.max(0, packetsNow - packetsBefore); + const packetsLostNow = statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(now.packetsLost); + const packetsLostBefore = statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(before.packetsLost); + const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore); + trackStats.setLoss({ + packetsTotal: packetsDiff + packetsLostDiff, + packetsLost: packetsLostDiff, + isDownloadStream: now.type !== "outbound-rtp", + }); + } + static calculateBitrate(bytesNowAny, bytesBeforeAny, nowTimestamp, beforeTimestamp) { + const bytesNow = statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(bytesNowAny); + const bytesBefore = statsValueFormatter_1.StatsValueFormatter.getNonNegativeValue(bytesBeforeAny); + const bytesProcessed = Math.max(0, bytesNow - bytesBefore); + const timeMs = nowTimestamp - beforeTimestamp; + let bitrateKbps = 0; + if (timeMs > 0) { + // TODO is there any reason to round here? + bitrateKbps = Math.round((bytesProcessed * 8) / timeMs); + } + return bitrateKbps; + } +} +exports.TrackStatsReporter = TrackStatsReporter; + +},{"./statsValueFormatter":436}],438:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TransportStatsReporter = void 0; +class TransportStatsReporter { + static buildReport(report, now, conferenceStatsTransport, isFocus) { + const localUsedCandidate = report === null || report === void 0 ? void 0 : report.get(now.localCandidateId); + const remoteUsedCandidate = report === null || report === void 0 ? void 0 : report.get(now.remoteCandidateId); + // RTCIceCandidateStats + // https://w3c.github.io/webrtc-stats/#icecandidate-dict* + if (remoteUsedCandidate && localUsedCandidate) { + const remoteIpAddress = remoteUsedCandidate.ip !== undefined ? remoteUsedCandidate.ip : remoteUsedCandidate.address; + const remotePort = remoteUsedCandidate.port; + const ip = `${remoteIpAddress}:${remotePort}`; + const localIpAddress = localUsedCandidate.ip !== undefined ? localUsedCandidate.ip : localUsedCandidate.address; + const localPort = localUsedCandidate.port; + const localIp = `${localIpAddress}:${localPort}`; + const type = remoteUsedCandidate.protocol; + // Save the address unless it has been saved already. + if (!conferenceStatsTransport.some((t) => t.ip === ip && t.type === type && t.localIp === localIp)) { + conferenceStatsTransport.push({ + ip, + type, + localIp, + isFocus, + localCandidateType: localUsedCandidate.candidateType, + remoteCandidateType: remoteUsedCandidate.candidateType, + networkType: localUsedCandidate.networkType, + rtt: now.currentRoundTripTime ? now.currentRoundTripTime * 1000 : NaN, + }); + } + } + return conferenceStatsTransport; + } +} +exports.TransportStatsReporter = TransportStatsReporter; + +},{}]},{},[320]) +//# sourceMappingURL=browser-matrix.js.map -- cgit