diff --git a/.eslintrc.yaml b/.eslintrc.yaml index f29ef809e31..ed073426ebe 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -7,12 +7,16 @@ extends: "eslint:recommended" globals: + _: true $: true amplify: true ko: true + LRUCache: true + wire: true z: true rules: + brace-style: [2, 1tbs] comma-dangle: [2, always-multiline] handle-callback-err: 2 indent: [2, 2, {SwitchCase: 1}] diff --git a/app/page/auth.html b/app/page/auth.html index 994f5211e81..b83200e884b 100644 --- a/app/page/auth.html +++ b/app/page/auth.html @@ -71,8 +71,8 @@
- - + +
- - diff --git a/app/script/announce/AnnounceRepository.js b/app/script/announce/AnnounceRepository.js index 5ce0a77211a..c2d6aefb1be 100644 --- a/app/script/announce/AnnounceRepository.js +++ b/app/script/announce/AnnounceRepository.js @@ -50,7 +50,7 @@ check_version() { return this.announce_service.get_version().then((server_version) => { - this.logger.info(`Found new version ${server_version}`); + this.logger.info(`Checking current webapp version. Server '${server_version}' vs. local '${z.util.Environment.version(false, true)}'`); if (server_version > z.util.Environment.version(false, true)) { amplify.publish(z.event.WebApp.LIFECYCLE.UPDATE, z.announce.UPDATE_SOURCE.WEBAPP); diff --git a/app/script/assets/Asset.coffee b/app/script/assets/Asset.coffee deleted file mode 100644 index 18c9a567091..00000000000 --- a/app/script/assets/Asset.coffee +++ /dev/null @@ -1,52 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Asset entity for the asset service. -class z.assets.Asset - ### - Construct a new asset for the asset service. - - @param config [Object] Asset configuration - ### - constructor: (config) -> - @correlation_id = config.correlation_id or z.util.create_random_uuid() - @content_type = config.content_type - @array_buffer = config.array_buffer - @payload = - conv_id: config.conversation_id - correlation_id: @correlation_id - public: config.public or false - tag: config.tag or 'medium' - inline: config.inline or false - nonce: @correlation_id - md5: config.md5 - width: config.width - height: config.height - original_width: config.original_width or config.width - original_height: config.original_height or config.width - native_push: config.native_push or false - - # Create the content disposition header for the asset. - get_content_disposition: -> - payload = ['zasset'] - for key, value of @payload - payload.push "#{key}=#{value}" - return payload.join ';' diff --git a/app/script/assets/Asset.js b/app/script/assets/Asset.js new file mode 100644 index 00000000000..dc12310acb1 --- /dev/null +++ b/app/script/assets/Asset.js @@ -0,0 +1,65 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.Asset = class Asset { + + /* + Construct a new asset for the asset service. + + @deprecated + @param {Object} config - Asset configuration + */ + constructor(config) { + this.correlation_id = config.correlation_id || z.util.create_random_uuid(); + this.content_type = config.content_type; + this.array_buffer = config.array_buffer; + this.payload = { + conv_id: config.conversation_id, + correlation_id: this.correlation_id, + public: config.public || false, + tag: config.tag || 'medium', + inline: config.inline || false, + nonce: this.correlation_id, + md5: config.md5, + width: config.width, + height: config.height, + original_width: config.original_width || config.width, + original_height: config.original_height || config.width, + native_push: config.native_push || false, + }; + } + + /* + Create the content disposition header for the asset. + */ + get_content_disposition() { + const payload = ['zasset']; + for (let key in this.payload) { + const value = this.payload[key]; + payload.push(`${key}=${value}`); + } + return payload.join(';'); + } + +}; diff --git a/app/script/assets/AssetCrypto.coffee b/app/script/assets/AssetCrypto.coffee deleted file mode 100644 index 8cb2e5e4951..00000000000 --- a/app/script/assets/AssetCrypto.coffee +++ /dev/null @@ -1,76 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -z.assets.AssetCrypto = - ### - @param plaintext [ArrayBuffer] - - @return key_bytes [ArrayBuffer] AES key used for encryption - @return computed_sha256 [ArrayBuffer] SHA-256 checksum of the ciphertext - @return ciphertext [ArrayBuffer] Encrypted plaintext - ### - encrypt_aes_asset: (plaintext) -> - key = null - iv_ciphertext = null - computed_sha256 = null - iv = new Uint8Array 16 - - window.crypto.getRandomValues iv - - return window.crypto.subtle.generateKey {name: 'AES-CBC', length: 256}, true, ['encrypt'] - .then (ckey) -> - key = ckey - - return window.crypto.subtle.encrypt {name: 'AES-CBC', iv: iv.buffer}, key, plaintext - .then (ciphertext) -> - iv_ciphertext = new Uint8Array(ciphertext.byteLength + iv.byteLength) - iv_ciphertext.set iv, 0 - iv_ciphertext.set new Uint8Array(ciphertext), iv.byteLength - - return window.crypto.subtle.digest 'SHA-256', iv_ciphertext - .then (digest) -> - computed_sha256 = digest - - return window.crypto.subtle.exportKey 'raw', key - .then (key_bytes) -> - return [key_bytes, computed_sha256, iv_ciphertext.buffer] - - ### - @param key_bytes [ArrayBuffer] AES key used for encryption - @param computed_sha256 [ArrayBuffer] SHA-256 checksum of the ciphertext - @param ciphertext [ArrayBuffer] Encrypted plaintext - - @param [ArrayBuffer] - ### - decrypt_aes_asset: (ciphertext, key_bytes, reference_sha256) -> - return window.crypto.subtle.digest 'SHA-256', ciphertext - .then (computed_sha256) -> - a = new Uint32Array reference_sha256 - b = new Uint32Array computed_sha256 - - if a.length is b.length and a.every((x, i) -> x is b[i]) - return window.crypto.subtle.importKey 'raw', key_bytes, 'AES-CBC', false, ['decrypt'] - - throw new Error 'Encrypted asset does not match its SHA-256 hash' - .then (key) -> - iv = ciphertext.slice 0, 16 - img_ciphertext = ciphertext.slice 16 - return window.crypto.subtle.decrypt {name: 'AES-CBC', iv: iv}, key, img_ciphertext diff --git a/app/script/assets/AssetCrypto.js b/app/script/assets/AssetCrypto.js new file mode 100644 index 00000000000..3fdc8f580c8 --- /dev/null +++ b/app/script/assets/AssetCrypto.js @@ -0,0 +1,81 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetCrypto = { + + /* + @param {ArrayBuffer} key_bytes - AES key used for encryption + @param {ArrayBuffer} computed_sha256 - SHA-256 checksum of the ciphertext + @param {ArrayBuffer} ciphertext - Encrypted plaintext + */ + encrypt_aes_asset(plaintext) { + const iv = new Uint8Array(16); + let key = null; + let iv_ciphertext = null; + let computed_sha256 = null; + + window.crypto.getRandomValues(iv); + + return window.crypto.subtle.generateKey({name: 'AES-CBC', length: 256}, true, ['encrypt']) + .then(function(ckey) { + key = ckey; + + return window.crypto.subtle.encrypt({name: 'AES-CBC', iv: iv.buffer}, key, plaintext); + }).then(function(ciphertext) { + iv_ciphertext = new Uint8Array(ciphertext.byteLength + iv.byteLength); + iv_ciphertext.set(iv, 0); + iv_ciphertext.set(new Uint8Array(ciphertext), iv.byteLength); + + return window.crypto.subtle.digest('SHA-256', iv_ciphertext); + }).then(function(digest) { + computed_sha256 = digest; + + return window.crypto.subtle.exportKey('raw', key); + }).then(key_bytes => [key_bytes, computed_sha256, iv_ciphertext.buffer]); + }, + + /* + @param {ArrayBuffer} key_bytes - AES key used for encryption + @param {ArrayBuffer} computed_sha256 - SHA-256 checksum of the ciphertext + @param {ArrayBuffer} ciphertext - Encrypted plaintext + */ + decrypt_aes_asset(ciphertext, key_bytes, reference_sha256) { + return window.crypto.subtle.digest('SHA-256', ciphertext) + .then(function(computed_sha256) { + const a = new Uint32Array(reference_sha256); + const b = new Uint32Array(computed_sha256); + + if ((a.length === b.length) && a.every((x, i) => x === b[i])) { + return window.crypto.subtle.importKey('raw', key_bytes, 'AES-CBC', false, ['decrypt']); + } + + throw new Error('Encrypted asset does not match its SHA-256 hash'); + }).then(function(key) { + const iv = ciphertext.slice(0, 16); + const img_ciphertext = ciphertext.slice(16); + return window.crypto.subtle.decrypt({name: 'AES-CBC', iv}, key, img_ciphertext); + }); + }, + +}; diff --git a/app/script/assets/AssetMetaDataBuilder.coffee b/app/script/assets/AssetMetaDataBuilder.coffee deleted file mode 100644 index 09508e173e3..00000000000 --- a/app/script/assets/AssetMetaDataBuilder.coffee +++ /dev/null @@ -1,95 +0,0 @@ -# -# Wire -# Copyright (C) 2017 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Builder for creating all kinds of asset metadata -z.assets.AssetMetaDataBuilder = - - ### - Constructs corresponding asset metadata depending on the given file type - - @param file [File] the file to generate metadata for - @return metadata [ImageMetaData, VideoMetaData, AudioMetaData] - ### - build_metadata: (file) -> - @logger = new z.util.Logger 'z.assets.AssetMetaDataBuilder', z.config.LOGGER.OPTIONS - if @is_video file - return @_build_video_metdadata file - else if @is_audio file - return @_build_audio_metdadata file - else if @is_image file - return @_build_image_metdadata file - else - return Promise.resolve() - - is_video: (file) -> - file?.type?.startsWith 'video' - - is_audio: (file) -> - file?.type?.startsWith 'audio' - - is_image: (file) -> - file?.type?.startsWith 'image' - - _build_video_metdadata: (videofile) -> - return new Promise (resolve, reject) -> - url = window.URL.createObjectURL videofile - videoElement = document.createElement 'video' - videoElement.onloadedmetadata = -> - resolve new z.proto.Asset.VideoMetaData videoElement.videoWidth, videoElement.videoHeight, videoElement.duration - window.URL.revokeObjectURL url - videoElement.onerror = (error) -> - reject error - window.URL.revokeObjectURL url - videoElement.src = url - - _build_image_metdadata: (imagefile) -> - return new Promise (resolve, reject) -> - url = window.URL.createObjectURL imagefile - img = new Image() - img.onload = -> - resolve new z.proto.Asset.ImageMetaData img.width, img.height - window.URL.revokeObjectURL url - img.onerror = (error) -> - reject error - window.URL.revokeObjectURL url - img.src = url - - _build_audio_metdadata: (audiofile) -> - z.util.load_file_buffer audiofile - .then (buffer) -> - audioContext = new AudioContext() - audioContext.close() - audioContext.decodeAudioData buffer - .then (audio_buffer) -> - return new z.proto.Asset.AudioMetaData audio_buffer.duration * 1000, z.assets.AssetMetaDataBuilder._normalise_loudness audio_buffer - - _normalise_loudness: (audio_buffer) -> - MAX_SAMPLES = 200 - AMPLIFIER = 700 # in favour of iterating all samples before we interpolate them - preview = [0..MAX_SAMPLES] - for channel_index in [0..audio_buffer.numberOfChannels] - channel = Array.from audio_buffer.getChannelData channel_index - bucket_size = parseInt channel.length / MAX_SAMPLES - buckets = z.util.ArrayUtil.chunk channel, bucket_size - for bucket, bucket_index in buckets - preview[bucket_index] = z.util.NumberUtil.cap_to_byte AMPLIFIER * z.util.NumberUtil.root_mean_square bucket - break # only select first channel - return new Uint8Array preview diff --git a/app/script/assets/AssetMetaDataBuilder.js b/app/script/assets/AssetMetaDataBuilder.js new file mode 100644 index 00000000000..4e5978791a2 --- /dev/null +++ b/app/script/assets/AssetMetaDataBuilder.js @@ -0,0 +1,118 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +// Builder for creating all kinds of asset metadata +z.assets.AssetMetaDataBuilder = { + + /* + Constructs corresponding asset metadata depending on the given file type + + @param {File|Blob} file - the file to generate metadata for + @returns {ImageMetaData|VideoMetaData|AudioMetaData} + */ + build_metadata(file) { + if (!(file instanceof Blob)) { + throw new Error('Expected file to be type of Blob'); + } + + if (this.is_video(file)) { + return this._build_video_metdadata(file); + } else if (this.is_audio(file)) { + return this._build_audio_metdadata(file); + } else if (this.is_image(file)) { + return this._build_image_metdadata(file); + } else { + return Promise.resolve(); + } + }, + + is_audio(file) { + return file && file.type.startsWith('audio'); + }, + + is_video(file) { + return file && file.type.startsWith('video'); + }, + + is_image(file) { + return file && file.type.startsWith('image'); + }, + + _build_video_metdadata(videofile) { + return new Promise((resolve, reject) => { + const url = window.URL.createObjectURL(videofile); + const video = document.createElement('video'); + video.onloadedmetadata = () => { + resolve(new z.proto.Asset.VideoMetaData(video.videoWidth, video.videoHeight, video.duration)); + window.URL.revokeObjectURL(url); + }; + video.onerror = (error) => { + reject(error); + window.URL.revokeObjectURL(url); + }; + video.src = url; + }); + }, + + _build_image_metdadata(imagefile) { + return new Promise((resolve, reject) =>{ + const url = window.URL.createObjectURL(imagefile); + const img = new Image(); + img.onload = () => { + resolve(new z.proto.Asset.ImageMetaData(img.width, img.height)); + window.URL.revokeObjectURL(url); + }; + img.onerror = (error) => { + reject(error); + window.URL.revokeObjectURL(url); + }; + img.src = url; + }); + }, + + _build_audio_metdadata(audiofile) { + return z.util.load_file_buffer(audiofile).then((buffer) => { + const audioContext = new AudioContext(); + audioContext.close(); + return audioContext.decodeAudioData(buffer); + }).then(audio_buffer => { + return new z.proto.Asset.AudioMetaData(audio_buffer.duration * 1000, z.assets.AssetMetaDataBuilder._normalise_loudness(audio_buffer)); + }); + }, + + _normalise_loudness(audio_buffer) { + const MAX_SAMPLES = 200; + const AMPLIFIER = 700; // in favour of iterating all samples before we interpolate them + const channel = audio_buffer.getChannelData(0); + const bucket_size = parseInt(channel.length / MAX_SAMPLES); + const buckets = z.util.ArrayUtil.chunk(channel, bucket_size); + + const preview = buckets.map((bucket) => { + return z.util.NumberUtil.cap_to_byte(AMPLIFIER * z.util.NumberUtil.root_mean_square(bucket)); + }); + + return new Uint8Array(preview); + }, + +}; diff --git a/app/script/assets/AssetRemoteData.coffee b/app/script/assets/AssetRemoteData.coffee deleted file mode 100644 index 6164166f975..00000000000 --- a/app/script/assets/AssetRemoteData.coffee +++ /dev/null @@ -1,112 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -class z.assets.AssetRemoteData - - ### - Use either z.assets.AssetRemoteData.v2 or z.assets.AssetRemoteData.v3 - to initialize. - - @param otr_key [Uint8Array] - @param sha256 [Uint8Array] - ### - constructor: (@otr_key, @sha256) -> - @download_progress = ko.observable() - @cancel_download = undefined - @generate_url = undefined - @identifier = undefined - - ### - Static initializer for v3 assets - - @param asset_key [String] - @param otr_key [Uint8Array] - @param sha256 [Uint8Array] - @param asset_token [String] token is optional - @param force_caching [Boolean] - ### - @v3: (asset_key, otr_key, sha256, asset_token, force_caching = false) -> - remote_data = new z.assets.AssetRemoteData otr_key, sha256 - remote_data.generate_url = -> wire.app.service.asset.generate_asset_url_v3 asset_key, asset_token, force_caching - remote_data.identifier = "#{asset_key}" - return remote_data - - ### - Static initializer for v2 assets - - @param conversation_id [String] - @param asset_id [String] - @param otr_key [Uint8Array] - @param sha256 [Uint8Array] - @param force_caching [Boolean] - ### - @v2: (conversation_id, asset_id, otr_key, sha256, force_caching = false) -> - remote_data = new z.assets.AssetRemoteData otr_key, sha256 - remote_data.generate_url = -> wire.app.service.asset.generate_asset_url_v2 asset_id, conversation_id, force_caching - remote_data.identifier = "#{conversation_id}#{asset_id}" - return remote_data - - ### - Static initializer for v1 assets - - @deprecated - @param conversation_id [String] - @param asset_id [String] - @param force_caching [Boolean] - ### - @v1: (conversation_id, asset_id, force_caching = false) -> - remote_data = new z.assets.AssetRemoteData() - remote_data.generate_url = -> wire.app.service.asset.generate_asset_url asset_id, conversation_id, force_caching - remote_data.identifier = "#{conversation_id}#{asset_id}" - return remote_data - - ### - Loads and decrypts stored asset - - @returns [Blob] - ### - load: => - type = undefined - - @_load_buffer() - .then (data) => - [buffer, type] = data - if @otr_key? and @sha256? - return z.assets.AssetCrypto.decrypt_aes_asset buffer, @otr_key.buffer, @sha256.buffer - return buffer - .then (buffer) -> - return new Blob [new Uint8Array buffer], type: type - - ### - Get object url for asset remote data. URLs are cached in memory - - @returns [String] url - ### - get_object_url: => - object_url = z.assets.AssetURLCache.get_url @identifier - return Promise.resolve object_url if object_url? - - @load().then (blob) => z.assets.AssetURLCache.set_url @identifier, window.URL.createObjectURL(blob) - - _load_buffer: => - z.util.load_url_buffer @generate_url(), (xhr) => - xhr.onprogress = (event) => @download_progress Math.round event.loaded / event.total * 100 - @cancel_download = -> xhr.abort.call xhr diff --git a/app/script/assets/AssetRemoteData.js b/app/script/assets/AssetRemoteData.js new file mode 100644 index 00000000000..b63e0950013 --- /dev/null +++ b/app/script/assets/AssetRemoteData.js @@ -0,0 +1,128 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetRemoteData = class AssetRemoteData { + + /* + Use either z.assets.AssetRemoteData.v2 or z.assets.AssetRemoteData.v3 + to initialize. + + @param {Uint8Array} otr_key + @param {Uint8Array} sha256 + */ + constructor(otr_key, sha256) { + this.otr_key = otr_key; + this.sha256 = sha256; + this.download_progress = ko.observable(); + this.cancel_download = undefined; + this.generate_url = undefined; + this.identifier = undefined; + } + + /* + Static initializer for v3 assets + + @param {string} asset_key + @param {Uint8Array} [otr_key] + @param {Uint8Array} [sha256] + @param {string} [asset_token] + @param {string} [force_caching=false] + */ + static v3(asset_key, otr_key, sha256, asset_token, force_caching = false) { + const remote_data = new z.assets.AssetRemoteData(otr_key, sha256); + remote_data.generate_url = () => wire.app.service.asset.generate_asset_url_v3(asset_key, asset_token, force_caching); + remote_data.identifier = `${asset_key}`; + return remote_data; + } + + /* + Static initializer for v2 assets + + @param {string} conversation_id + @param {string} asset_id + @param {Uint8Array} otr_key + @param {Uint8Array} sha256 + @param {string} [force_caching=false] + */ + static v2(conversation_id, asset_id, otr_key, sha256, force_caching = false) { + const remote_data = new z.assets.AssetRemoteData(otr_key, sha256); + remote_data.generate_url = () => wire.app.service.asset.generate_asset_url_v2(asset_id, conversation_id, force_caching); + remote_data.identifier = `${conversation_id}${asset_id}`; + return remote_data; + } + + /* + Static initializer for v1 assets + + @deprecated + @param {string} conversation_id + @param {string} asset_id + @param {string} [force_caching=false] + */ + static v1(conversation_id, asset_id, force_caching = false) { + const remote_data = new z.assets.AssetRemoteData(); + remote_data.generate_url = () => wire.app.service.asset.generate_asset_url(asset_id, conversation_id, force_caching); + remote_data.identifier = `${conversation_id}${asset_id}`; + return remote_data; + } + + /* + Loads and decrypts stored asset + + @returns {Blob} + */ + load() { + let mime_type; + + return this._load_buffer() + .then(([buffer, type]) => { + mime_type = type; + if ((this.otr_key != null) && (this.sha256 != null)) { + return z.assets.AssetCrypto.decrypt_aes_asset(buffer, this.otr_key.buffer, this.sha256.buffer); + } + return buffer; + }).then(plaintext => new Blob([new Uint8Array(plaintext)], {mime_type})); + } + + /* + Get object url for asset remote data. URLs are cached in memory + + @returns {String} + */ + get_object_url() { + const object_url = z.assets.AssetURLCache.get_url(this.identifier); + if (object_url != null) { + return Promise.resolve(object_url); + } + + return this.load().then(blob => z.assets.AssetURLCache.set_url(this.identifier, window.URL.createObjectURL(blob))); + } + + _load_buffer() { + return z.util.load_url_buffer(this.generate_url(), xhr => { + xhr.onprogress = event => this.download_progress(Math.round((event.loaded / event.total) * 100)); + return this.cancel_download = () => xhr.abort.call(xhr); + }); + } +}; diff --git a/app/script/assets/AssetRetentionPolicy.coffee b/app/script/assets/AssetRetentionPolicy.coffee deleted file mode 100644 index dd90f4bb382..00000000000 --- a/app/script/assets/AssetRetentionPolicy.coffee +++ /dev/null @@ -1,25 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -z.assets.AssetRetentionPolicy = - ETERNAL: 'eternal' - PERSISTENT: 'persistent' - VOLATILE: 'volatile' diff --git a/app/script/assets/AssetRetentionPolicy.js b/app/script/assets/AssetRetentionPolicy.js new file mode 100644 index 00000000000..96b1cfe8635 --- /dev/null +++ b/app/script/assets/AssetRetentionPolicy.js @@ -0,0 +1,29 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetRetentionPolicy = { + ETERNAL: 'eternal', + PERSISTENT: 'persistent', + VOLATILE: 'volatile', +}; diff --git a/app/script/assets/AssetService.coffee b/app/script/assets/AssetService.coffee deleted file mode 100644 index 7d8557f3041..00000000000 --- a/app/script/assets/AssetService.coffee +++ /dev/null @@ -1,405 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# AssetService for all asset handling and the calls to the backend REST API. -class z.assets.AssetService - ### - Construct a new Asset Service. - - @param client [z.service.Client] Client for the API calls - ### - constructor: (@client) -> - @logger = new z.util.Logger 'z.assets.AssetService', z.config.LOGGER.OPTIONS - @BOUNDARY = 'frontier' - @pending_uploads = {} - - ### - Upload any asset to the backend using asset api v1. - - @deprecated - @param config [Object] Configuration object containing the jQuery call settings - @option config [Object] data - @option config [String] contentType - @option config [String] contentDisposition - ### - post_asset: (config) -> - @client.send_request - type: 'POST' - url: @client.create_url '/assets' - data: config.data - processData: false # otherwise jquery will convert it to a query string - contentType: config.contentType - headers: - 'Content-Disposition': config.contentDisposition - - ### - Upload any asset pair to the backend using asset api v1. - - @deprecated - @param small [z.assets.Asset] Small asset for upload - @param medium [z.assets.Asset] Medium asset for upload - ### - post_asset_pair: (small, medium) -> - Promise.all [ - @post_asset - contentType: small.content_type - contentDisposition: small.get_content_disposition() - data: small.array_buffer - @post_asset - contentType: medium.content_type - contentDisposition: medium.get_content_disposition() - data: medium.array_buffer - ] - - ### - Update the user profile image by first making it usable, transforming it and then uploading the asset pair. - - @deprecated - @param conversation_id [String] ID of self conversation - @param image [File, Blob] - ### - upload_profile_image: (conversation_id, image) -> - Promise.all([ - @_compress_profile_image image - @_compress_image image - ]).then ([small, medium]) => - [small_image, small_image_bytes] = small - [medium_image, medium_image_bytes] = medium - - medium_asset = new z.assets.Asset - array_buffer: medium_image_bytes - content_type: 'image/jpg' - conversation_id: conversation_id - md5: z.util.array_to_md5_base64 medium_image_bytes - width: medium_image.height - height: medium_image.height - public: true - - small_profile_asset = $.extend true, {}, medium_asset - small_profile_asset.array_buffer = small_image_bytes - small_profile_asset.payload.width = small_image.width - small_profile_asset.payload.height = small_image.height - small_profile_asset.payload.md5 = z.util.array_to_md5_base64 small_image_bytes - small_profile_asset.payload.tag = z.assets.ImageSizeType.SMALL_PROFILE - - @post_asset_pair small_profile_asset, medium_asset - .then ([small_response, medium_response]) -> - return [small_response.data, medium_response.data] - - ### - Update the user profile image by first making it usable, transforming it and then uploading the asset pair. - - @param conversation_id [String] ID of self conversation - @param image [File, Blob] - ### - upload_profile_image_v3: (image) -> - Promise.all([ - @_compress_profile_image image - @_compress_image image - ]).then ([small, medium]) => - [small_image, small_image_bytes] = small - [medium_image, medium_image_bytes] = medium - - return Promise.all([ - @post_asset_v3 small_image_bytes, {public: true} - @post_asset_v3 medium_image_bytes, {public: true} - ]) - .then ([small_credentials, medium_credentials]) -> - return [small_credentials.key, medium_credentials.key] - - ### - Upload arbitrary binary data using the new asset api v3. - The data is AES encrypted before uploading. - - @param bytes [Uint8Array] asset binary data - @param options [Object] - @option public [Boolean] - @option retention [z.assets.AssetRetentionPolicy] - @param xhr_accessor_function [Function] Function will get a reference to the underlying XMLHTTPRequest - ### - _upload_asset: (bytes, options, xhr_accessor_function) -> - z.assets.AssetCrypto.encrypt_aes_asset bytes - .then ([key_bytes, sha256, ciphertext]) => - return @post_asset_v3 ciphertext, options, xhr_accessor_function - .then ({key, token}) -> - return [key_bytes, sha256, key, token] - - ### - Upload file using the new asset api v3. Promise will resolve with z.proto.Asset instance. - In case of an successful upload the uploaded property is set. - - @param file [File, Blob] - @param options [Object] - @option public [Boolean] - @option retention [z.assets.AssetRetentionPolicy] - @param xhr_accessor_function [Function] Function will get a reference to the underlying XMLHTTPRequest - ### - upload_asset: (file, options, xhr_accessor_function) -> - z.util.load_file_buffer file - .then (buffer) => - @_upload_asset buffer, options, xhr_accessor_function - .then ([key_bytes, sha256, key, token]) -> - asset = new z.proto.Asset() - asset.set 'uploaded', new z.proto.Asset.RemoteData key_bytes, sha256, key, token - return asset - - ### - Upload image using the new asset api v3. Promise will resolve with z.proto.Asset instance. - In case of an successful upload the uploaded property is set. - - @param image [File, Blob] - @param options [Object] - @option public [Boolean] - @option retention [z.assets.AssetRetentionPolicy] - ### - upload_image_asset: (image, options) -> - @_compress_image image - .then ([compressed_image, compressed_bytes]) => - @_upload_asset compressed_bytes, options - .then ([key_bytes, sha256, key, token]) -> - image_meta_data = new z.proto.Asset.ImageMetaData compressed_image.width, compressed_image.height - asset = new z.proto.Asset() - asset.set 'original', new z.proto.Asset.Original image.type, compressed_bytes.length, null, image_meta_data - asset.set 'uploaded', new z.proto.Asset.RemoteData key_bytes, sha256, key, token - return asset - - ### - Generates the URL an asset can be downloaded from. - - @deprecated - @param asset_id [String] ID of the asset - @param conversation_id [String] ID of the conversation the asset belongs to - @param force_caching [Boolean] - @return [String] Asset URL - ### - generate_asset_url: (asset_id, conversation_id, force_caching) -> - url = @client.create_url "/assets/#{asset_id}" - asset_url = "#{url}?access_token=#{@client.access_token}&conv_id=#{conversation_id}" - asset_url = "#{asset_url}&forceCaching=true" if force_caching - return asset_url - - ### - Generates the URL for asset api v2. - - @deprecated - @param asset_id [String] ID of the asset - @param conversation_id [String] ID of the conversation the asset belongs to - @param force_caching [Boolean] - @return [String] Asset URL - ### - generate_asset_url_v2: (asset_id, conversation_id, force_caching) -> - url = @client.create_url "/conversations/#{conversation_id}/otr/assets/#{asset_id}" - asset_url = "#{url}?access_token=#{@client.access_token}" - asset_url = "#{asset_url}&forceCaching=true" if force_caching - return asset_url - - ### - Generates the URL for asset api v3. - - @param asset_key [String] - @param asset_token [String] - @param force_caching [Boolean] - @return [String] Asset URL - ### - generate_asset_url_v3: (asset_key, asset_token, force_caching) -> - url = @client.create_url "/assets/v3/#{asset_key}/" - asset_url = "#{url}?access_token=#{@client.access_token}" - asset_url = "#{asset_url}&asset_token=#{asset_token}" if asset_token - asset_url = "#{asset_url}&forceCaching=true" if force_caching - return asset_url - - ### - Create request data for asset upload. - - @param asset_data [UInt8Array|ArrayBuffer] Asset data - @param metadata [Object] image meta data - ### - _create_asset_multipart_body: (asset_data, metadata) -> - metadata = JSON.stringify metadata - asset_data_md5 = z.util.array_to_md5_base64 asset_data - - body = '' - body += '--' + @BOUNDARY + '\r\n' - body += 'Content-Type: application/json; charset=utf-8\r\n' - body += "Content-length: #{metadata.length}\r\n" - body += '\r\n' - body += metadata + '\r\n' - body += '--' + @BOUNDARY + '\r\n' - body += 'Content-Type: application/octet-stream\r\n' - body += "Content-length: #{asset_data.length}\r\n" - body += "Content-MD5: #{asset_data_md5}\r\n" - body += '\r\n' - - footer = '\r\n--' + @BOUNDARY + '--\r\n' - - return new Blob [body, asset_data, footer] - - ### - Post assets to a conversation. - - @deprecated - @param conversation_id [String] ID of the self conversation - @param json_payload [Object] First part of the multipart message - @param image_data [Uint8Array|ArrayBuffer] encrypted image data - @param precondition_option [Array|Boolean] Level that backend checks for missing clients - @param upload_id [String] Identifies the upload request - ### - post_asset_v2: (conversation_id, json_payload, image_data, precondition_option, upload_id) -> - return new Promise (resolve, reject) => - url = @client.create_url "/conversations/#{conversation_id}/otr/assets" - - if _.isArray precondition_option - url = "#{url}?report_missing=#{precondition_option.join ','}" - else if precondition_option - url = "#{url}?ignore_missing=true" - - image_data = new Uint8Array image_data - data = @_create_asset_multipart_body image_data, json_payload - pending_uploads = @pending_uploads - - xhr = new XMLHttpRequest() - xhr.open 'POST', url - xhr.setRequestHeader 'Content-Type', 'multipart/mixed; boundary=' + @BOUNDARY - xhr.setRequestHeader 'Authorization', "#{@client.access_token_type} #{@client.access_token}" - xhr.onload = (event) -> - if @status is 201 - resolve [JSON.parse(@response), @getResponseHeader 'Location'] - else if @status is 412 - reject JSON.parse @response - else - reject event - delete pending_uploads[upload_id] - xhr.onerror = (error) -> - reject error - delete pending_uploads[upload_id] - xhr.upload.onprogress = (event) -> - if upload_id - # we use amplify due to the fact that Promise API lacks progress support - percentage_progress = Math.round(event.loaded / event.total * 100) - amplify.publish 'upload' + upload_id, percentage_progress - xhr.send data - - pending_uploads[upload_id] = xhr - - ### - Post assets using asset api v3. - - @param asset_data [Uint8Array|ArrayBuffer] - @param metadata [Object] - @option public [Boolean] Default is false - @option retention [z.assets.AssetRetentionPolicy] Default is z.assets.AssetRetentionPolicy.PERSISTENT - @param xhr_accessor_function [Function] Function will get a reference to the underlying XMLHTTPRequest - ### - post_asset_v3: (asset_data, metadata, xhr_accessor_function) -> - return new Promise (resolve, reject) => - metadata = $.extend - public: false - retention: z.assets.AssetRetentionPolicy.PERSISTENT - , metadata - - xhr = new XMLHttpRequest() - xhr.open 'POST', @client.create_url '/assets/v3' - xhr.setRequestHeader 'Content-Type', 'multipart/mixed; boundary=' + @BOUNDARY - xhr.setRequestHeader 'Authorization', "#{@client.access_token_type} #{@client.access_token}" - xhr.onload = (event) -> if @status is 201 then resolve JSON.parse(@response) else reject event - xhr.onerror = reject - xhr_accessor_function? xhr - xhr.send @_create_asset_multipart_body new Uint8Array(asset_data), metadata - - ### - Cancel an asset upload. - - @param upload_id [String] Identifies the upload request - ### - cancel_asset_upload: (upload_id) => - xhr = @pending_uploads[upload_id] - if xhr? - xhr.abort() - delete @pending_uploads[upload_id] - - ### - Create image proto message. - - @deprecated - @param image [File, Blob] - ### - create_image_proto: (image) -> - @_compress_image image - .then ([compressed_image, compressed_bytes]) -> - return z.assets.AssetCrypto.encrypt_aes_asset compressed_bytes - .then ([key_bytes, sha256, ciphertext]) -> - image_asset = new z.proto.ImageAsset() - image_asset.set_tag z.assets.ImageSizeType.MEDIUM - image_asset.set_width compressed_image.width - image_asset.set_height compressed_image.height - image_asset.set_original_width compressed_image.width - image_asset.set_original_height compressed_image.height - image_asset.set_mime_type image.type - image_asset.set_size compressed_bytes.length - image_asset.set_otr_key key_bytes - image_asset.set_sha256 sha256 - return [image_asset, new Uint8Array ciphertext] - - ### - Create asset proto message. - - @deprecated - @param asset [File, Blob] - ### - create_asset_proto: (asset) -> - z.util.load_file_buffer asset - .then (file_bytes) -> - return z.assets.AssetCrypto.encrypt_aes_asset file_bytes - .then ([key_bytes, sha256, ciphertext]) -> - asset = new z.proto.Asset() - asset.set 'uploaded', new z.proto.Asset.RemoteData key_bytes, sha256 - return [asset, ciphertext] - - ### - Compress image. - @param image [File, Blob] - ### - _compress_image: (image) -> - @_compress_image_with_worker 'worker/image-worker.js', image, -> image.type is 'image/gif' - - ### - Compress profile image. - @param image [File, Blob] - ### - _compress_profile_image: (image) -> - @_compress_image_with_worker 'worker/profile-image-worker.js', image - - ### - Compress image using given worker. - @param worker [String] path to worker file - @param image [File, Blob] - @param filter [Function] skips compression if function returns true - ### - _compress_image_with_worker: (worker, image, filter) -> - z.util.load_file_buffer image - .then (buffer) -> - return buffer if filter?() - image_worker = new z.util.Worker worker - return image_worker.post buffer - .then (compressed_bytes) -> - return z.util.load_image new Blob [new Uint8Array compressed_bytes], 'type': image.type - .then (compressed_image) -> - return [compressed_image, new Uint8Array compressed_bytes] diff --git a/app/script/assets/AssetService.js b/app/script/assets/AssetService.js new file mode 100644 index 00000000000..81156f5a2fe --- /dev/null +++ b/app/script/assets/AssetService.js @@ -0,0 +1,474 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +// AssetService for all asset handling and the calls to the backend REST API. +z.assets.AssetService = class AssetService { + + /* + Construct a new Asset Service. + + @param {z.service.Client} client - Client for the API calls + */ + constructor(client) { + this.cancel_asset_upload = this.cancel_asset_upload.bind(this); + this.client = client; + this.logger = new z.util.Logger('z.assets.AssetService', z.config.LOGGER.OPTIONS); + this.BOUNDARY = 'frontier'; + this.pending_uploads = {}; + } + + /* + Upload any asset to the backend using asset api v1. + + @deprecated + @param {Object} config - Configuration object containing the jQuery call settings + @param {String} config.data + @param {String} config.contentDisposition + @param {String} config.contentType + */ + post_asset(config) { + return this.client.send_request({ + type: 'POST', + url: this.client.create_url('/assets'), + data: config.data, + processData: false, // otherwise jquery will convert it to a query string + contentType: config.contentType, + headers: { + 'Content-Disposition': config.contentDisposition, + }, + }); + } + + /* + Upload any asset pair to the backend using asset api v1. + + @deprecated + @param {z.assets.Asset} small + @param {z.assets.Asset} medium + */ + post_asset_pair(small, medium) { + return Promise.all([ + this.post_asset({ + contentType: small.content_type, + contentDisposition: small.get_content_disposition(), + data: small.array_buffer, + }), + this.post_asset({ + contentType: medium.content_type, + contentDisposition: medium.get_content_disposition(), + data: medium.array_buffer, + }), + ]); + } + + /* + Update the user profile image by first making it usable, transforming it and then uploading the asset pair. + + @deprecated + @param {string} conversation_id + @param {File|Blob} image + */ + upload_profile_image(conversation_id, image) { + return Promise.all([ + this._compress_profile_image(image), + this._compress_image(image), + ]).then(([small, medium]) => { + const [small_image, small_image_bytes] = small; + const [medium_image, medium_image_bytes] = medium; + + const medium_asset = new z.assets.Asset({ + array_buffer: medium_image_bytes, + content_type: 'image/jpg', + conversation_id, + md5: z.util.array_to_md5_base64(medium_image_bytes), + width: medium_image.width, + height: medium_image.height, + public: true, + }); + + const small_profile_asset = $.extend(true, {}, medium_asset); + small_profile_asset.__proto__ = z.assets.Asset.prototype; + small_profile_asset.array_buffer = small_image_bytes; + small_profile_asset.payload.width = small_image.width; + small_profile_asset.payload.height = small_image.height; + small_profile_asset.payload.md5 = z.util.array_to_md5_base64(small_image_bytes); + small_profile_asset.payload.tag = z.assets.ImageSizeType.SMALL_PROFILE; + + return this.post_asset_pair(small_profile_asset, medium_asset); + }).then(([small_response, medium_response]) => { + return [small_response.data, medium_response.data]; + }); + } + + /* + Update the user profile image by first making it usable, transforming it and then uploading the asset pair. + + @param {File|Blob} image + */ + upload_profile_image_v3(image) { + return Promise.all([ + this._compress_profile_image(image), + this._compress_image(image), + ]).then(([small, medium]) => { + const [, small_image_bytes] = small; + const [, medium_image_bytes] = medium; + + return Promise.all([ + this.post_asset_v3(small_image_bytes, {public: true}), + this.post_asset_v3(medium_image_bytes, {public: true}), + ]); + }).then(([small_credentials, medium_credentials]) => { + return [small_credentials.key, medium_credentials.key]; + }); + } + + /* + Upload arbitrary binary data using the new asset api v3. + The data is AES encrypted before uploading. + + @param {Uint8Array} bytes - asset binary data + @param {Object} options + @param {Boolean} config.public + @param {z.assets.AssetRetentionPolicy} config.retention + @param {Function} xhr_accessor_function - Function will get a reference to the underlying XMLHTTPRequest + */ + _upload_asset(bytes, options, xhr_accessor_function) { + return z.assets.AssetCrypto.encrypt_aes_asset(bytes) + .then(([key_bytes, sha256, ciphertext]) => { + return this.post_asset_v3(ciphertext, options, xhr_accessor_function) + .then(({key, token}) => [key_bytes, sha256, key, token]); + }); + } + + /* + Upload file using the new asset api v3. Promise will resolve with z.proto.Asset instance. + In case of an successful upload the uploaded property is set. + + @param {Blob|File} file + @param {Object} options + @param {Boolean} config.public + @param {z.assets.AssetRetentionPolicy} config.retention + @param {Function} xhr_accessor_function - Function will get a reference to the underlying XMLHTTPRequest + */ + upload_asset(file, options, xhr_accessor_function) { + return z.util.load_file_buffer(file) + .then(buffer => { + return this._upload_asset(buffer, options, xhr_accessor_function); + }).then(function([key_bytes, sha256, key, token]) { + const asset = new z.proto.Asset(); + asset.set('uploaded', new z.proto.Asset.RemoteData(key_bytes, sha256, key, token)); + return asset; + }); + } + + /* + Upload image using the new asset api v3. Promise will resolve with z.proto.Asset instance. + In case of an successful upload the uploaded property is set. + + @param {Blob|File} file + @param {Object} options + @param {Boolean} config.public + @param {z.assets.AssetRetentionPolicy} config.retention + */ + upload_image_asset(image, options) { + return this._compress_image(image) + .then(([compressed_image, compressed_bytes]) => { + return this._upload_asset(compressed_bytes, options) + .then(function([key_bytes, sha256, key, token]) { + const image_meta_data = new z.proto.Asset.ImageMetaData(compressed_image.width, compressed_image.height); + const asset = new z.proto.Asset(); + asset.set('original', new z.proto.Asset.Original(image.type, compressed_bytes.length, null, image_meta_data)); + asset.set('uploaded', new z.proto.Asset.RemoteData(key_bytes, sha256, key, token)); + return asset; + }); + } + ); + } + + /* + Generates the URL an asset can be downloaded from. + + @deprecated + @param {string} asset_id + @param {string} conversation_id + @param {Boolean} force_caching + @returns {String} + */ + generate_asset_url(asset_id, conversation_id, force_caching) { + const url = this.client.create_url(`/assets/${asset_id}`); + let asset_url = `${url}?access_token=${this.client.access_token}&conv_id=${conversation_id}`; + if (force_caching) { + asset_url = `${asset_url}&forceCaching=true`; + } + return asset_url; + } + + /* + Generates the URL for asset api v2. + + @deprecated + @param {string} asset_id + @param {string} conversation_id + @param {Boolean} force_caching + @returns {String} + */ + generate_asset_url_v2(asset_id, conversation_id, force_caching) { + const url = this.client.create_url(`/conversations/${conversation_id}/otr/assets/${asset_id}`); + let asset_url = `${url}?access_token=${this.client.access_token}`; + if (force_caching) { + asset_url = `${asset_url}&forceCaching=true`; + } + return asset_url; + } + + /* + Generates the URL for asset api v3. + + @param {string} asset_key + @param {string} asset_token + @param {Boolean} force_caching + @returns {String} + */ + generate_asset_url_v3(asset_key, asset_token, force_caching) { + const url = this.client.create_url(`/assets/v3/${asset_key}/`); + let asset_url = `${url}?access_token=${this.client.access_token}`; + if (asset_token) { + asset_url = `${asset_url}&asset_token=${asset_token}`; + } + if (force_caching) { + asset_url = `${asset_url}&forceCaching=true`; + } + return asset_url; + } + + /* + Create request data for asset upload. + + @param {UInt8Array|ArrayBuffer} asset_data + @param {Object} metadata + */ + _create_asset_multipart_body(asset_data, metadata) { + metadata = JSON.stringify(metadata); + const asset_data_md5 = z.util.array_to_md5_base64(asset_data); + + let body = ''; + body += `--${this.BOUNDARY}\r\n`; + body += 'Content-Type: application/json; charset=utf-8\r\n'; + body += `Content-length: ${metadata.length}\r\n`; + body += '\r\n'; + body += metadata + '\r\n'; + body += `--${this.BOUNDARY}\r\n`; + body += 'Content-Type: application/octet-stream\r\n'; + body += `Content-length: ${asset_data.length}\r\n`; + body += `Content-MD5: ${asset_data_md5}\r\n`; + body += '\r\n'; + + const footer = `\r\n--${this.BOUNDARY}--\r\n`; + + return new Blob([body, asset_data, footer]); + } + + /* + Post assets to a conversation. + + @deprecated + @param {string} conversation_id + @param {Object} json_payload + @param {Uint8Array|ArrayBuffer} image_data + @param {Array|Boolean} precondition_option - Level that backend checks for missing clients + @param {String} upload_id + */ + post_asset_v2(conversation_id, json_payload, image_data, precondition_option, upload_id) { + return new Promise((resolve, reject) => { + let url = this.client.create_url(`/conversations/${conversation_id}/otr/assets`); + + if (Array.isArray(precondition_option)) { + url = `${url}?report_missing=${precondition_option.join(',')}`; + } else if (precondition_option) { + url = `${url}?ignore_missing=true`; + } + + image_data = new Uint8Array(image_data); + let data = this._create_asset_multipart_body(image_data, json_payload); + let { pending_uploads } = this; + + let xhr = new XMLHttpRequest(); + xhr.open('POST', url); + xhr.setRequestHeader('Content-Type', `multipart/mixed; boundary=${this.BOUNDARY}`); + xhr.setRequestHeader('Authorization', `${this.client.access_token_type} ${this.client.access_token}`); + xhr.onload = function(event) { + if (this.status === 201) { + resolve([JSON.parse(this.response), this.getResponseHeader('Location')]); + } else if (this.status === 412) { + reject(JSON.parse(this.response)); + } else { + reject(event); + } + delete pending_uploads[upload_id]; + }; + xhr.onerror = function(error) { + reject(error); + delete pending_uploads[upload_id]; + }; + xhr.upload.onprogress = function(event) { + if (upload_id) { + // we use amplify due to the fact that Promise API lacks progress support + const percentage_progress = Math.round((event.loaded / event.total) * 100); + return amplify.publish(`upload${upload_id}`, percentage_progress); + } + }; + xhr.send(data); + + return pending_uploads[upload_id] = xhr; + }); + } + + /* + Post assets using asset api v3. + + @param {Uint8Array|ArrayBuffer} asset_data + @param {Object} metadata + @param {Boolean} metadata.public + @param {z.assets.AssetRetentionPolicy} metadata.retention + @param {Function} xhr_accessor_function - Function will get a reference to the underlying XMLHTTPRequest + */ + post_asset_v3(asset_data, metadata, xhr_accessor_function) { + return new Promise((resolve, reject) => { + metadata = Object.assign({ + public: false, + retention: z.assets.AssetRetentionPolicy.PERSISTENT, + }, metadata); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', this.client.create_url('/assets/v3')); + xhr.setRequestHeader('Content-Type', `multipart/mixed; boundary=${this.BOUNDARY}`); + xhr.setRequestHeader('Authorization', `${this.client.access_token_type} ${this.client.access_token}`); + xhr.onload = function(event) { + if (this.status === 201) { + return resolve(JSON.parse(this.response)); + } + return reject(event); + }; + xhr.onerror = reject; + + if (typeof xhr_accessor_function === 'function') { + xhr_accessor_function(xhr); + } + + xhr.send(this._create_asset_multipart_body(new Uint8Array(asset_data), metadata)); + }); + } + + /* + Cancel an asset upload. + + @param {string} upload_id - Identifies the upload request + */ + cancel_asset_upload(upload_id) { + let xhr = this.pending_uploads[upload_id]; + if (xhr != null) { + xhr.abort(); + delete this.pending_uploads[upload_id]; + } + } + + /* + Create image proto message. + + @deprecated + @param {File|Blob} image + */ + create_image_proto(image) { + return this._compress_image(image) + .then(([compressed_image, compressed_bytes]) => { + return z.assets.AssetCrypto.encrypt_aes_asset(compressed_bytes) + .then(([key_bytes, sha256, ciphertext]) => { + let image_asset = new z.proto.ImageAsset(); + image_asset.set_tag(z.assets.ImageSizeType.MEDIUM); + image_asset.set_width(compressed_image.width); + image_asset.set_height(compressed_image.height); + image_asset.set_original_width(compressed_image.width); + image_asset.set_original_height(compressed_image.height); + image_asset.set_mime_type(image.type); + image_asset.set_size(compressed_bytes.length); + image_asset.set_otr_key(key_bytes); + image_asset.set_sha256(sha256); + return [image_asset, new Uint8Array(ciphertext)]; + }); + }); + } + + /* + Create asset proto message. + + @deprecated + @param {File|Blob} assets + */ + create_asset_proto(asset) { + return z.util.load_file_buffer(asset) + .then(file_bytes => z.assets.AssetCrypto.encrypt_aes_asset(file_bytes)) + .then(([key_bytes, sha256, ciphertext]) => { + asset = new z.proto.Asset(); + asset.set('uploaded', new z.proto.Asset.RemoteData(key_bytes, sha256)); + return [asset, ciphertext]; + }); + } + + /* + Compress image. + @param {File|Blob} image + */ + _compress_image(image) { + return this._compress_image_with_worker('worker/image-worker.js', image, () => image.type === 'image/gif'); + } + + /* + Compress profile image. + @param {File|Blob} image + */ + _compress_profile_image(image) { + return this._compress_image_with_worker('worker/profile-image-worker.js', image); + } + + /* + Compress image using given worker. + @param {string} worker - path to worker file + @param {File|Blob} image + @param {Function} filter - + */ + _compress_image_with_worker(worker, image, filter) { + return z.util.load_file_buffer(image) + .then((buffer) => { + if (typeof filter === 'function' ? filter() : undefined) { + return new Uint8Array(buffer); + } + return new z.util.Worker(worker).post(buffer); + }).then(compressed_bytes => { + return Promise.all([ + z.util.load_image(new Blob([compressed_bytes], {'type': image.type})), + compressed_bytes, + ]); + }); + } +}; diff --git a/app/script/assets/AssetTransferState.coffee b/app/script/assets/AssetTransferState.coffee deleted file mode 100644 index ed90b5bd2fb..00000000000 --- a/app/script/assets/AssetTransferState.coffee +++ /dev/null @@ -1,28 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Enum of different asset upload status. -z.assets.AssetTransferState = - UPLOADING: 'uploading' - UPLOADED: 'uploaded' - UPLOAD_FAILED: 'upload-failed' - UPLOAD_CANCELED: 'upload-canceled' - DOWNLOADING: 'downloading' diff --git a/app/script/assets/AssetTransferState.js b/app/script/assets/AssetTransferState.js new file mode 100644 index 00000000000..92695d6ed1b --- /dev/null +++ b/app/script/assets/AssetTransferState.js @@ -0,0 +1,31 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetTransferState = { + UPLOADING: 'uploading', + UPLOADED: 'uploaded', + UPLOAD_FAILED: 'upload-failed', + UPLOAD_CANCELED: 'upload-canceled', + DOWNLOADING: 'downloading', +}; diff --git a/app/script/assets/AssetType.coffee b/app/script/assets/AssetType.coffee deleted file mode 100644 index 72bb705124a..00000000000 --- a/app/script/assets/AssetType.coffee +++ /dev/null @@ -1,27 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Enum of different asset types. -z.assets.AssetType = - FILE: 'File' - LOCATION: 'Location' - IMAGE: 'Image' - TEXT: 'Text' diff --git a/app/script/assets/AssetType.js b/app/script/assets/AssetType.js new file mode 100644 index 00000000000..ba1fe42e214 --- /dev/null +++ b/app/script/assets/AssetType.js @@ -0,0 +1,30 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetType = { + FILE: 'File', + LOCATION: 'Location', + IMAGE: 'Image', + TEXT: 'Text', +}; diff --git a/app/script/assets/AssetURLCache.coffee b/app/script/assets/AssetURLCache.coffee deleted file mode 100644 index 25691e99932..00000000000 --- a/app/script/assets/AssetURLCache.coffee +++ /dev/null @@ -1,43 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -z.assets.AssetURLCache = do -> - lru_cache = new LRUCache 100 - - set_url = (identifier, url) -> - existing_url = get_url identifier - if existing_url - window.URL.revokeObjectURL url - return existing_url - - outdated_url = lru_cache.set identifier, url - if outdated_url? - window.URL.revokeObjectURL outdated_url - - return url - - get_url = (identifier) -> - return lru_cache.get identifier - - return { - get_url: get_url - set_url: set_url - } diff --git a/app/script/assets/AssetURLCache.js b/app/script/assets/AssetURLCache.js new file mode 100644 index 00000000000..da3347ec32b --- /dev/null +++ b/app/script/assets/AssetURLCache.js @@ -0,0 +1,51 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetURLCache = (function() { + const lru_cache = new LRUCache(100); + + const set_url = function(identifier, url) { + const existing_url = get_url(identifier); + + if (existing_url) { + window.URL.revokeObjectURL(url); + return existing_url; + } + + const outdated_url = lru_cache.set(identifier, url); + + if (outdated_url != null) { + window.URL.revokeObjectURL(outdated_url); + } + + return url; + }; + + const get_url = identifier => lru_cache.get(identifier); + + return { + get_url, + set_url, + }; +})(); diff --git a/app/script/assets/AssetUploadFailedReason.coffee b/app/script/assets/AssetUploadFailedReason.coffee deleted file mode 100644 index 61e514bcf1c..00000000000 --- a/app/script/assets/AssetUploadFailedReason.coffee +++ /dev/null @@ -1,25 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Enum of different asset upload status. -z.assets.AssetUploadFailedReason = - FAILED: 1 - CANCELLED: 0 diff --git a/app/script/assets/AssetUploadFailedReason.js b/app/script/assets/AssetUploadFailedReason.js new file mode 100644 index 00000000000..5b369318a3b --- /dev/null +++ b/app/script/assets/AssetUploadFailedReason.js @@ -0,0 +1,28 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.AssetUploadFailedReason = { + CANCELLED: 0, + FAILED: 1, +}; diff --git a/app/script/assets/ImageSizeType.coffee b/app/script/assets/ImageSizeType.coffee deleted file mode 100644 index 119f335cbc8..00000000000 --- a/app/script/assets/ImageSizeType.coffee +++ /dev/null @@ -1,26 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.assets ?= {} - -# Enum of different image size types. -z.assets.ImageSizeType = - MEDIUM: 'medium' - PREVIEW: 'preview' - SMALL_PROFILE: 'smallProfile' diff --git a/app/script/assets/ImageSizeType.js b/app/script/assets/ImageSizeType.js new file mode 100644 index 00000000000..cc0af923bd0 --- /dev/null +++ b/app/script/assets/ImageSizeType.js @@ -0,0 +1,28 @@ +// +// Wire +// Copyright (C) 2016 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +'use strict'; + +window.z = window.z || {}; +window.z.assets = z.assets || {}; + +z.assets.ImageSizeType = { + MEDIUM: 'medium', + PREVIEW: 'preview', + SMALL_PROFILE: 'smallProfile', +}; diff --git a/app/script/audio/AudioError.coffee b/app/script/audio/AudioError.coffee deleted file mode 100644 index ca5d4d2eac2..00000000000 --- a/app/script/audio/AudioError.coffee +++ /dev/null @@ -1,47 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.audio ?= {} - -class z.audio.AudioError - constructor: (type) -> - @name = @constructor.name - @stack = (new Error()).stack - @type = type or z.audio.AudioError::TYPE.UNKNOWN - - @message = switch @type - when z.audio.AudioError::TYPE.ALREADY_PLAYING - 'Sound is already playing' - when z.audio.AudioError::TYPE.FAILED_TO_PLAY - 'Failed to play sound' - when z.audio.AudioError::TYPE.IGNORED_SOUND - 'Ignored request to play sound' - when z.audio.AudioError::TYPE.NOT_FOUND - 'AudioElement or ID not found' - else - 'Unknown AudioError' - - @:: = new Error() - @::constructor = @ - @::TYPE = - ALREADY_PLAYING: 'z.audio.AudioError::TYPE.ALREADY_PLAYING' - FAILED_TO_PLAY: 'z.audio.AudioError::TYPE.FAILED_TO_PLAY' - IGNORED_SOUND: 'z.audio.AudioError::TYPE.IGNORED_SOUND' - NOT_FOUND: 'z.audio.AudioError::TYPE.NOT_FOUND' - UNKNOWN: 'z.audio.AudioError::TYPE.UNKNOWN' diff --git a/app/script/audio/AudioError.js b/app/script/audio/AudioError.js new file mode 100644 index 00000000000..05606e88ac4 --- /dev/null +++ b/app/script/audio/AudioError.js @@ -0,0 +1,60 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.audio = z.audio || {}; + +window.z.audio.AudioError = class AudioError extends Error { + constructor(type) { + super(); + this.name = this.constructor.name; + this.stack = (new Error()).stack; + this.type = type || z.audio.AudioError.TYPE.UNKNOWN; + switch (this.type) { + case z.audio.AudioError.TYPE.ALREADY_PLAYING: + this.message = 'Sound is already playing'; + break; + case z.audio.AudioError.TYPE.FAILED_TO_PLAY: + this.message = 'Failed to play sound'; + break; + case z.audio.AudioError.TYPE.IGNORED_SOUND: + this.message = 'Ignored request to play sound'; + break; + case z.audio.AudioError.TYPE.NOT_FOUND: + this.message = 'AudioElement or ID not found'; + break; + default: + this.message = 'Unknown AudioError'; + } + } + + static get TYPE() { + return { + ALREADY_PLAYING: 'z.audio.AudioError.TYPE.ALREADY_PLAYING', + FAILED_TO_PLAY: 'z.audio.AudioError.TYPE.FAILED_TO_PLAY', + IGNORED_SOUND: 'z.audio.AudioError.TYPE.IGNORED_SOUND', + NOT_FOUND: 'z.audio.AudioError.TYPE.NOT_FOUND', + UNKNOWN: 'z.audio.AudioError.TYPE.UNKNOWN', + }; + } +}; + + diff --git a/app/script/audio/AudioPlayingType.coffee b/app/script/audio/AudioPlayingType.coffee deleted file mode 100644 index 09699e589bf..00000000000 --- a/app/script/audio/AudioPlayingType.coffee +++ /dev/null @@ -1,40 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.audio ?= {} - -# Enum of sounds playing for different sound settings. -z.audio.AudioPlayingType = - NONE: [ - z.audio.AudioType.CALL_DROP - z.audio.AudioType.NETWORK_INTERRUPTION - z.audio.AudioType.OUTGOING_CALL - z.audio.AudioType.READY_TO_TALK - z.audio.AudioType.TALK_LATER - ] - SOME: [ - z.audio.AudioType.CALL_DROP - z.audio.AudioType.INCOMING_CALL - z.audio.AudioType.INCOMING_PING - z.audio.AudioType.NETWORK_INTERRUPTION - z.audio.AudioType.OUTGOING_CALL - z.audio.AudioType.OUTGOING_PING - z.audio.AudioType.READY_TO_TALK - z.audio.AudioType.TALK_LATER - ] diff --git a/app/script/audio/AudioPlayingType.js b/app/script/audio/AudioPlayingType.js new file mode 100644 index 00000000000..c6c532c703e --- /dev/null +++ b/app/script/audio/AudioPlayingType.js @@ -0,0 +1,43 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.audio = z.audio || {}; + +z.audio.AudioPlayingType = { + NONE: [ + z.audio.AudioType.CALL_DROP, + z.audio.AudioType.NETWORK_INTERRUPTION, + z.audio.AudioType.OUTGOING_CALL, + z.audio.AudioType.READY_TO_TALK, + z.audio.AudioType.TALK_LATER, + ], + SOME: [ + z.audio.AudioType.CALL_DROP, + z.audio.AudioType.INCOMING_CALL, + z.audio.AudioType.INCOMING_PING, + z.audio.AudioType.NETWORK_INTERRUPTION, + z.audio.AudioType.OUTGOING_CALL, + z.audio.AudioType.OUTGOING_PING, + z.audio.AudioType.READY_TO_TALK, + z.audio.AudioType.TALK_LATER, + ], +}; diff --git a/app/script/audio/AudioPreference.coffee b/app/script/audio/AudioPreference.coffee deleted file mode 100644 index bbabd630189..00000000000 --- a/app/script/audio/AudioPreference.coffee +++ /dev/null @@ -1,26 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.audio ?= {} - -# Enum of audio preferences. -z.audio.AudioPreference = - ALL: 'all' - NONE: 'none' - SOME: 'some' diff --git a/app/script/audio/AudioPreference.js b/app/script/audio/AudioPreference.js new file mode 100644 index 00000000000..409cbe30dc3 --- /dev/null +++ b/app/script/audio/AudioPreference.js @@ -0,0 +1,29 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.audio = z.audio || {}; + +z.audio.AudioPreference = { + ALL: 'all', + NONE: 'none', + SOME: 'some', +}; diff --git a/app/script/audio/AudioRepository.coffee b/app/script/audio/AudioRepository.coffee deleted file mode 100644 index 71620939a89..00000000000 --- a/app/script/audio/AudioRepository.coffee +++ /dev/null @@ -1,195 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.audio ?= {} - -# Audio repository for all audio interactions. -class z.audio.AudioRepository - AUDIO_PATH: '/audio' - - # Construct a new Audio Repository. - constructor: -> - @logger = new z.util.Logger 'z.audio.AudioRepository', z.config.LOGGER.OPTIONS - - @audio_elements = {} - @currently_looping = {} - - @audio_preference = ko.observable z.audio.AudioPreference.ALL - @audio_preference.subscribe (audio_preference) => - @_stop_all() if audio_preference is z.audio.AudioPreference.NONE - - @_subscribe_to_audio_properties() - - ### - Initialize the repository. - @param pre_load [Boolean] Should sounds be pre-loaded with false as default - ### - init: (pre_load = false) => - @_init_sounds() - @_subscribe_to_audio_events() - @_preload() if pre_load - - ### - Start playback of a sound in a loop. - @note Prevent playing multiples instances of looping sounds - @param audio_id [z.audio.AudioType] Sound identifier - ### - loop: (audio_id) => - @play audio_id, true - - ### - Start playback of a sound. - @param audio_id [z.audio.AudioType] Sound identifier - @param play_in_loop [Boolean] Play sound in loop - ### - play: (audio_id, play_in_loop = false) => - @_check_sound_setting audio_id - .then => - @_get_sound_by_id audio_id - .then (audio_element) => - @_play audio_id, audio_element, play_in_loop - .then (audio_element) => - @logger.info "Playing sound '#{audio_id}' (loop: '#{play_in_loop}')", audio_element - .catch (error) => - if error not instanceof z.audio.AudioError - @logger.error "Failed playing sound '#{audio_id}': #{error.message}" - throw error - - ### - Stop playback of a sound. - @param audio_id [z.audio.AudioType] Sound identifier - ### - stop: (audio_id) => - @_get_sound_by_id audio_id - .then (audio_element) => - if not audio_element.paused - @logger.info "Stopping sound '#{audio_id}'", audio_element - audio_element.pause() - delete @currently_looping[audio_id] if @currently_looping[audio_id] - .catch (error) => - @logger.error "Failed stopping sound '#{audio_id}': #{error.message}", audio_element - throw error - - ### - Check if sound should be played with current setting. - @private - @param audio_id [z.audio.AudioType] Sound identifier - @param [Promise] Resolves if the sound should be played - ### - _check_sound_setting: (audio_id) -> - return new Promise (resolve, reject) => - if @audio_preference() is z.audio.AudioPreference.NONE and audio_id not in z.audio.AudioPlayingType.NONE - reject new z.audio.AudioError z.audio.AudioError::TYPE.IGNORED_SOUND - else if @audio_preference() is z.audio.AudioPreference.SOME and audio_id not in z.audio.AudioPlayingType.SOME - reject new z.audio.AudioError z.audio.AudioError::TYPE.IGNORED_SOUND - else - resolve() - - ### - Create HTMLAudioElement. - @param source_path [String] Source for HTMLAudioElement - @param [HTMLAudioElement] - ### - _create_audio_element: (source_path) -> - audio_element = new Audio() - audio_element.preload = 'none' - audio_element.src = source_path - return audio_element - - ### - Get the sound object - @private - @param audio_id [z.audio.AudioType] Sound identifier - @return [Promise] Resolves with the HTMLAudioElement - ### - _get_sound_by_id: (audio_id) => - return new Promise (resolve, reject) => - if @audio_elements[audio_id] - resolve @audio_elements[audio_id] - else - reject new z.audio.AudioError z.audio.AudioError::TYPE.NOT_FOUND - - ### - Initialize all sounds. - @private - ### - _init_sounds: -> - @audio_elements[audio_id] = @_create_audio_element "#{@AUDIO_PATH}/#{audio_id}.mp3" for type, audio_id of z.audio.AudioType - @logger.info 'Initialized sounds' - - ### - Start playback of a sound. - @private - @param audio_id [z.audio.AudioType] Sound identifier - @param audio_element [HTMLAudioElement] AudioElement to play - @param play_in_loop [Boolean] Play sound in loop - @return [Promise] Resolves with the HTMLAudioElement - ### - _play: (audio_id, audio_element, play_in_loop = false) -> - if not audio_id or not audio_element - return Promise.reject new z.audio.AudioError z.audio.AudioError::TYPE.NOT_FOUND - - return new Promise (resolve, reject) => - if audio_element.paused - audio_element.loop = play_in_loop - audio_element.currentTime = 0 if audio_element.currentTime isnt 0 - play_promise = audio_element.play() - - _play_success = => - @currently_looping[audio_id] = audio_id if play_in_loop - resolve audio_element - - if play_promise - play_promise.then(_play_success).catch -> - reject new z.audio.AudioError z.audio.AudioError::TYPE.FAILED_TO_PLAY - else - _play_success() - else - reject new z.audio.AudioError z.audio.AudioError::TYPE.ALREADY_PLAYING - - ### - Preload all sounds for immediate playback. - @private - ### - _preload: => - for audio_id, audio_element of @audio_elements - audio_element.preload = 'auto' - audio_element.load() - @logger.info 'Pre-loading audio files for immediate playback' - - ### - Stop all sounds playing in loop. - @private - ### - _stop_all: -> - @stop audio_id for audio_id of @currently_looping - - # Use Amplify to subscribe to all audio playback related events. - _subscribe_to_audio_events: -> - amplify.subscribe z.event.WebApp.AUDIO.PLAY, @play - amplify.subscribe z.event.WebApp.AUDIO.PLAY_IN_LOOP, @loop - amplify.subscribe z.event.WebApp.AUDIO.STOP, @stop - - # Use Amplify to subscribe to all audio properties related events. - _subscribe_to_audio_properties: -> - amplify.subscribe z.event.WebApp.PROPERTIES.UPDATED, (properties) => - @audio_preference properties.settings.sound.alerts - - amplify.subscribe z.event.WebApp.PROPERTIES.UPDATE.SOUND_ALERTS, (audio_preference) => - @audio_preference audio_preference diff --git a/app/script/audio/AudioRepository.js b/app/script/audio/AudioRepository.js new file mode 100644 index 00000000000..3b1fa1ea3e6 --- /dev/null +++ b/app/script/audio/AudioRepository.js @@ -0,0 +1,248 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.audio = z.audio || {}; + +window.z.audio.AudioRepository = class AudioRepository { + constructor() { + this.logger = new z.util.Logger('z.audio.AudioRepository', z.config.LOGGER.OPTIONS); + this.audio_elements = {}; + this.currently_looping = {}; + this.audio_preference = ko.observable(z.audio.AudioPreference.ALL); + this.audio_preference.subscribe((audio_preference) => { + if (audio_preference === z.audio.AudioPreference.NONE) { + this._stop_all(); + } + }); + this._subscribe_to_audio_properties(); + } + + /** + * Check if sound should be played with current setting. + * @private + * @param {z.audio.AudioType} audio_id - Sound identifier + * @returns {Promise} Resolves if the sound should be played. + */ + _check_sound_setting(audio_id) { + return new Promise((resolve, reject) => { + if (this.audio_preference() === z.audio.AudioPreference.NONE && !(z.audio.AudioPlayingType.NONE.includes(audio_id))) { + reject(new z.audio.AudioError(z.audio.AudioError.TYPE.IGNORED_SOUND)); + } else if (this.audio_preference() === z.audio.AudioPreference.SOME && !(z.audio.AudioPlayingType.SOME.includes(audio_id))) { + reject(new z.audio.AudioError(z.audio.AudioError.TYPE.IGNORED_SOUND)); + } else { + resolve(); + } + }); + } + + /** + * Create HTMLAudioElement. + * @private + * @param {string} source_path - Source for HTMLAudioElement + * @returns {Audio} Returns the audio element. + */ + _create_audio_element(source_path) { + const audio_element = new Audio(); + audio_element.preload = 'none'; + audio_element.src = source_path; + return audio_element; + } + + /** + * Get the sound object + * @private + * @param {z.audio.AudioType} audio_id - Sound identifier + * @returns {Promise} Resolves with the HTMLAudioElement. + */ + _get_sound_by_id(audio_id) { + return new Promise((resolve, reject) => { + if (this.audio_elements[audio_id]) { + resolve(this.audio_elements[audio_id]); + } else { + reject(new z.audio.AudioError(z.audio.AudioError.TYPE.NOT_FOUND)); + } + }); + } + + /** + * Initialize all sounds. + * @private + */ + _init_sounds() { + for (let type in z.audio.AudioType) { + const audio_id = z.audio.AudioType[type]; + this.audio_elements[audio_id] = this._create_audio_element(`/audio/${audio_id}.mp3`); + } + this.logger.info('Initialized sounds'); + } + + /** + * Start playback of a sound. + * @private + * @param {z.audio.AudioType} audio_id - Sound identifier + * @param {HTMLAudioElement} audio_element - AudioElement to play + * @param {Boolean} play_in_loop - Play sound in loop + * @return {Promise} Resolves with the HTMLAudioElement + */ + _play(audio_id, audio_element, play_in_loop = false) { + if (!audio_id || !audio_element) { + return Promise.reject(new z.audio.AudioError(z.audio.AudioError.TYPE.NOT_FOUND)); + } + + return new Promise((resolve, reject) => { + if (audio_element.paused) { + audio_element.loop = play_in_loop; + + if (audio_element.currentTime !== 0) { + audio_element.currentTime = 0; + } + + const _play_success = () => { + if (play_in_loop) { + this.currently_looping[audio_id] = audio_id; + } + resolve(audio_element); + }; + + const play_promise = audio_element.play(); + + if (play_promise) { + play_promise.then(_play_success).catch(() => { + reject(new z.audio.AudioError(z.audio.AudioError.TYPE.FAILED_TO_PLAY)); + }); + } else { + _play_success(); + } + } else { + reject(new z.audio.AudioError(z.audio.AudioError.TYPE.ALREADY_PLAYING)); + } + }); + } + + /** + * Preload all sounds for immediate playback. + * @private + */ + _preload() { + for (let audio_id in this.audio_elements) { + const audio_element = this.audio_elements[audio_id]; + audio_element.preload = 'auto'; + audio_element.load(); + } + this.logger.info('Pre-loading audio files for immediate playback'); + } + + /** + * Stop all sounds playing in loop. + * @private + */ + _stop_all() { + for (let audio_id in this.currently_looping) { + this.stop(audio_id); + } + } + + /** + * Use Amplify to subscribe to all audio playback related events. + * @private + */ + _subscribe_to_audio_events() { + amplify.subscribe(z.event.WebApp.AUDIO.PLAY, this, this.play); + amplify.subscribe(z.event.WebApp.AUDIO.PLAY_IN_LOOP, this, this.loop); + amplify.subscribe(z.event.WebApp.AUDIO.STOP, this, this.stop); + } + + /** + * Use Amplify to subscribe to all audio properties related events. + * @private + */ + _subscribe_to_audio_properties() { + amplify.subscribe(z.event.WebApp.PROPERTIES.UPDATED, this, (properties) => { + this.audio_preference(properties.settings.sound.alerts); + }); + + amplify.subscribe(z.event.WebApp.PROPERTIES.UPDATE.SOUND_ALERTS, this, (audio_preference) => { + this.audio_preference(audio_preference); + }); + } + + /** + * Initialize the repository. + * @param {boolean} pre_load - Should sounds be pre-loaded with false as default + */ + init(pre_load = false) { + this._init_sounds(); + this._subscribe_to_audio_events(); + if (pre_load) { + this._preload(); + } + } + + /** + * Start playback of a sound in a loop. + * @note Prevent playing multiples instances of looping sounds + * @param {z.audio.AudioType} audio_id - Sound identifier + */ + loop(audio_id) { + this.play(audio_id, true); + } + + /** + * Start playback of a sound. + * @param {z.audio.AudioType} audio_id - Sound identifier + * @param {boolean} play_in_loop - Play sound in loop + */ + play(audio_id, play_in_loop = false) { + return this._check_sound_setting(audio_id).then(() => { + return this._get_sound_by_id(audio_id); + }).then((audio_element) => { + return this._play(audio_id, audio_element, play_in_loop); + }).then((audio_element) => { + return this.logger.info(`Playing sound '${audio_id}' (loop: '${play_in_loop}')`, audio_element); + }).catch((error) => { + if (!(error instanceof z.audio.AudioError)) { + this.logger.error(`Failed playing sound '${audio_id}': ${error.message}`); + throw error; + } + }); + } + + /** + * Stop playback of a sound. + * @param {z.audio.AudioType} audio_id - Sound identifier + */ + stop(audio_id) { + return this._get_sound_by_id(audio_id).then((audio_element) => { + if (!audio_element.paused) { + this.logger.info(`Stopping sound '${audio_id}'`, audio_element); + audio_element.pause(); + } + + if (this.currently_looping[audio_id]) { + delete this.currently_looping[audio_id]; + } + }).catch((error) => { + this.logger.error(`Failed stopping sound '${audio_id}': ${error.message}`); + throw error; + }); + } +}; diff --git a/app/script/audio/AudioType.coffee b/app/script/audio/AudioType.coffee deleted file mode 100644 index e78b9cbd740..00000000000 --- a/app/script/audio/AudioType.coffee +++ /dev/null @@ -1,33 +0,0 @@ -# -# Wire -# Copyright (C) 2016 Wire Swiss GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# - -window.z ?= {} -z.audio ?= {} - -# Enum of different supported sounds. -z.audio.AudioType = - ALERT: 'alert' - CALL_DROP: 'call_drop' - INCOMING_CALL: 'ringing_from_them' - INCOMING_PING: 'ping_from_them' - NETWORK_INTERRUPTION: 'nw_interruption' - NEW_MESSAGE: 'new_message' - OUTGOING_CALL: 'ringing_from_me' - OUTGOING_PING: 'ping_from_me' - READY_TO_TALK: 'ready_to_talk' - TALK_LATER: 'talk_later' diff --git a/app/script/audio/AudioType.js b/app/script/audio/AudioType.js new file mode 100644 index 00000000000..815c8193e13 --- /dev/null +++ b/app/script/audio/AudioType.js @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2016 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +'use strict'; + +window.z = window.z || {}; +window.z.announce = z.announce || {}; + +z.audio.AudioType = { + ALERT: 'alert', + CALL_DROP: 'call_drop', + INCOMING_CALL: 'ringing_from_them', + INCOMING_PING: 'ping_from_them', + NETWORK_INTERRUPTION: 'nw_interruption', + NEW_MESSAGE: 'new_message', + OUTGOING_CALL: 'ringing_from_me', + OUTGOING_PING: 'ping_from_me', + READY_TO_TALK: 'ready_to_talk', + TALK_LATER: 'talk_later', +}; diff --git a/app/script/calling/entities/Call.coffee b/app/script/calling/entities/Call.coffee index c3b77725c99..166db71e1c4 100644 --- a/app/script/calling/entities/Call.coffee +++ b/app/script/calling/entities/Call.coffee @@ -76,9 +76,7 @@ class z.calling.entities.Call @interrupted_participants = ko.observableArray [] # Media - @local_stream_audio = @v2_call_center.media_stream_handler.local_media_streams.audio - @local_stream_video = @v2_call_center.media_stream_handler.local_media_streams.video - + @local_media_stream = @v2_call_center.media_stream_handler.local_media_stream @local_media_type = @v2_call_center.media_stream_handler.local_media_type @remote_media_type = ko.observable z.media.MediaType.NONE diff --git a/app/script/calling/entities/ECall.coffee b/app/script/calling/entities/ECall.coffee index 2f61bf94e21..2f74d99f398 100644 --- a/app/script/calling/entities/ECall.coffee +++ b/app/script/calling/entities/ECall.coffee @@ -66,9 +66,7 @@ class z.calling.entities.ECall @interrupted_participants = ko.observableArray [] # Media - @local_stream_audio = @v3_call_center.media_stream_handler.local_media_streams.audio - @local_stream_video = @v3_call_center.media_stream_handler.local_media_streams.video - + @local_media_stream = @v3_call_center.media_stream_handler.local_media_stream @local_media_type = @v3_call_center.media_stream_handler.local_media_type @remote_media_type = ko.observable z.media.MediaType.NONE diff --git a/app/script/calling/entities/EFlow.coffee b/app/script/calling/entities/EFlow.coffee index f28bff5de18..d6b4f9d85d5 100644 --- a/app/script/calling/entities/EFlow.coffee +++ b/app/script/calling/entities/EFlow.coffee @@ -22,6 +22,7 @@ z.calling.entities ?= {} E_FLOW_CONFIG = RTC_DATA_CHANNEL_LABEL: 'calling-3.0' + SDP_SEND_TIMEOUT_RENEGOTIATION: 50 SDP_SEND_TIMEOUT_RESET: 1000 SDP_SEND_TIMEOUT: 5000 @@ -67,8 +68,7 @@ class z.calling.entities.EFlow @pc_initialized.subscribe (is_initialized) => @telemetry.set_peer_connection @peer_connection if is_initialized - @audio_stream = @e_call_et.local_stream_audio - @video_stream = @e_call_et.local_stream_video + @media_stream = @e_call_et.local_media_stream @data_channels = {} @connection_state = ko.observable z.calling.rtc.ICEConnectionState.NEW @@ -114,7 +114,7 @@ class z.calling.entities.EFlow when z.calling.rtc.SignalingState.CLOSED @logger.debug "PeerConnection with '#{@remote_user.name()}' was closed" @e_call_et.delete_e_participant @e_participant_et - @_remove_media_streams() + @_remove_media_stream @media_stream() when z.calling.rtc.SignalingState.REMOTE_OFFER @negotiation_needed true @@ -226,22 +226,24 @@ class z.calling.entities.EFlow return @save_remote_sdp e_call_message_et @is_answer false - start_negotiation: => - @audio.hookup true - @_create_peer_connection() - @_add_media_streams() - @_set_sdp_states() - @negotiation_needed true - @pc_initialized true - - restart_negotiation: (negotiation_mode, is_answer) => + restart_negotiation: (negotiation_mode, is_answer, media_stream) => @logger.debug "Negotiation restart triggered by '#{negotiation_mode}'" - @negotiation_mode negotiation_mode + @_close_peer_connection() + @_clear_send_sdp_timeout() + @_reset_signaling_states() @is_answer is_answer @local_sdp undefined @remote_sdp undefined + @start_negotiation negotiation_mode, media_stream + + start_negotiation: (negotiation_mode = z.calling.enum.SDP_NEGOTIATION_MODE.DEFAULT, media_stream = @media_stream()) => + @audio.hookup true + @_create_peer_connection() + @_add_media_stream media_stream @_set_sdp_states() + @negotiation_mode negotiation_mode @negotiation_needed true + @pc_initialized true _set_sdp_states: -> @should_set_remote_sdp true @@ -285,7 +287,7 @@ class z.calling.entities.EFlow @peer_connection = new window.RTCPeerConnection @_create_peer_connection_configuration() @telemetry.time_step z.telemetry.calling.CallSetupSteps.PEER_CONNECTION_CREATED @signaling_state @peer_connection.signalingState - @logger.debug "PeerConnection with '#{@remote_user.name()}' created - is_answer' #{@is_answer()}", @e_call_et.config().ice_servers + @logger.debug "PeerConnection with '#{@remote_user.name()}' created - is_answer '#{@is_answer()}'", @e_call_et.config().ice_servers @peer_connection.onaddstream = @_on_add_stream @peer_connection.ontrack = @_on_track @@ -416,8 +418,9 @@ class z.calling.entities.EFlow .then ([remote_sdp, ice_candidates]) => if remote_sdp.type is z.calling.rtc.SDPType.OFFER if @signaling_state() is z.calling.rtc.SignalingState.LOCAL_OFFER - return @_solve_colliding_states() - else if e_call_message_et.type is z.calling.enum.E_CALL_MESSAGE_TYPE.UPDATE + return if @_solve_colliding_states() + + if e_call_message_et.type is z.calling.enum.E_CALL_MESSAGE_TYPE.UPDATE @restart_negotiation z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE, true @remote_sdp remote_sdp @@ -433,9 +436,10 @@ class z.calling.entities.EFlow .then ([local_sdp, ice_candidates]) => @local_sdp local_sdp - if sending_on_timeout and not @_contains_relay_candidate ice_candidates - @logger.warn "Local SDP does not contain any relay ICE candidates, resetting timeout\n#{ice_candidates}", ice_candidates - return @_set_send_sdp_timeout false + if sending_on_timeout and @negotiation_mode() is z.calling.enum.SDP_NEGOTIATION_MODE.DEFAULT + unless @_contains_relay_candidate ice_candidates + @logger.warn "Local SDP does not contain any relay ICE candidates, resetting timeout\n#{ice_candidates}", ice_candidates + return @_set_send_sdp_timeout false @logger.info "Sending local '#{@local_sdp().type}' SDP containing '#{ice_candidates.length}' ICE candidates for flow with '#{@remote_user.name()}'\n#{@local_sdp().sdp}" @should_send_local_sdp false @@ -520,7 +524,6 @@ class z.calling.entities.EFlow @call_et.telemetry.track_event z.tracking.EventName.CALLING.FAILED_RTC, undefined, attributes amplify.publish z.event.WebApp.CALL.STATE.LEAVE, @e_call_et.id, z.calling.enum.TERMINATION_REASON.SDP_FAILED - ### Sets the local Session Description Protocol on the PeerConnection. @private @@ -532,9 +535,10 @@ class z.calling.entities.EFlow @logger.debug "Setting local '#{@local_sdp().type}' SDP successful", @peer_connection.localDescription @telemetry.time_step z.telemetry.calling.CallSetupSteps.LOCAL_SDP_SET @should_set_local_sdp false - if @negotiation_mode() is z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE - return @send_local_sdp() - @_set_send_sdp_timeout() + switch @negotiation_mode() + when z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE then @send_local_sdp() + when z.calling.enum.SDP_NEGOTIATION_MODE.ICE_RESTART then @_set_gathering_state_timeout() + else @_set_send_sdp_timeout() .catch (error) => @logger.error "Setting local '#{@local_sdp().type}' SDP failed: #{error.name} - #{error.message}", error attributes = {cause: error.name, step: 'set_sdp', location: 'local', type: @local_sdp()?.type} @@ -558,6 +562,18 @@ class z.calling.entities.EFlow @call_et.telemetry.track_event z.tracking.EventName.CALLING.FAILED_RTC, undefined, attributes amplify.publish z.event.WebApp.CALL.STATE.LEAVE, @e_call_et.id, z.calling.enum.TERMINATION_REASON.SDP_FAILED + ### + Set timeout to check for ICE gathering state on renegotiation. + @note If renegotiation was triggered by remote end, we do not gather new candidates and can send SDP almost immediately. + ### + _set_gathering_state_timeout: -> + @send_sdp_timeout = window.setTimeout => + if @gathering_state() is z.calling.rtc.ICEGatheringState.COMPLETE + @logger.debug 'Sending local SDP as ICE gathering state did not change' + return @send_local_sdp true + @_set_send_sdp_timeout() + , E_FLOW_CONFIG.SDP_SEND_TIMEOUT_RENEGOTIATION + ### Set the SDP send timeout. @private @@ -583,13 +599,10 @@ class z.calling.entities.EFlow _solve_colliding_states: -> if @self_user_id < @remote_user_id @logger.warn "We need to switch SDP state of flow with '#{@remote_user.name()}' to answer." - @_close_peer_connection() - @_clear_send_sdp_timeout() - @_reset_signaling_states() - @is_answer true - @local_sdp undefined - return @start_negotiation() + @restart_negotiation z.calling.enum.SDP_NEGOTIATION_MODE.ICE_RESTART, true + return false @logger.warn "Remote side needs to switch SDP state of flow with '#{@remote_user.name()}' to answer." + return true ############################################################################### @@ -632,23 +645,6 @@ class z.calling.entities.EFlow @logger.info "Added local '#{media_stream.type}' MediaStream to PeerConnection", {stream: media_stream, audio_tracks: media_stream.getAudioTracks(), video_tracks: media_stream.getVideoTracks()} - ### - Adds the local MediaStreams to the PeerConnection. - @private - ### - _add_media_streams: -> - media_streams_identical = @_compare_local_media_streams() - - @_add_media_stream @audio_stream() if @audio_stream() - @_add_media_stream @video_stream() if @video_stream() and not media_streams_identical - - ### - Compare whether local audio and video streams are identical. - @private - ### - _compare_local_media_streams: -> - return @audio_stream() and @video_stream() and @audio_stream().id is @video_stream().id - ### Replace the MediaStream attached to the PeerConnection. @private @@ -657,27 +653,17 @@ class z.calling.entities.EFlow _replace_media_stream: (media_stream_info) -> Promise.resolve() .then => - return @_remove_media_streams media_stream_info.type + return @_remove_media_stream @media_stream() .then => @_upgrade_media_stream media_stream_info.stream, media_stream_info.type - .then (media_stream) => - @_add_media_stream media_stream - @logger.info "Replaced the MediaStream to update '#{media_stream_info.type}' successfully", media_stream - @restart_negotiation z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE, false - return media_stream_info + .then (updated_media_stream) => + @logger.info "Upgraded the MediaStream to update '#{media_stream_info.type}' successfully", updated_media_stream + @restart_negotiation z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE, false, updated_media_stream + return updated_media_stream .catch (error) => @logger.error "Failed to replace local MediaStream: #{error.message}", error throw error - _upgrade_media_stream: (new_media_stream, media_type) -> - active_media_stream = if media_type is z.media.MediaType.AUDIO then @audio_stream() else @video_stream() - - if active_media_stream - active_media_stream.removeTrack media_stream_track for media_stream_track in z.media.MediaStreamHandler.get_media_tracks active_media_stream, media_type - active_media_stream.addTrack media_stream_track for media_stream_track in z.media.MediaStreamHandler.get_media_tracks new_media_stream, media_type - return active_media_stream - return new_media_stream - ### Replace the a MediaStreamTrack attached to the MediaStream of the PeerConnection. @private @@ -723,21 +709,25 @@ class z.calling.entities.EFlow {stream: media_stream, audio_tracks: media_stream.getAudioTracks(), video_tracks: media_stream.getVideoTracks()} ### - Reset the flows MediaStream and media elements. + Upgrade the local MediaStream with new MediaStreamTracks + @private - @param media_type [z.media.MediaType] Optional media type of MediaStreams to be removed + @param new_media_stream [MediaStream] MediaStream containing new MediaStreamTracks + @param media_type [z.media.MediaType] Type of tracks to update + @return [MediaStream] New MediaStream to be used ### - _remove_media_streams: (media_type = z.media.MediaType.AUDIO_VIDEO) -> - switch media_type - when z.media.MediaType.AUDIO_VIDEO - media_streams_identical = @_compare_local_media_streams() + _upgrade_media_stream: (new_media_stream, media_type) -> + if @media_stream() + for media_stream_track in z.media.MediaStreamHandler.get_media_tracks @media_stream(), media_type + @media_stream().removeTrack media_stream_track + media_stream_track.stop() + @logger.info "Stopping MediaStreamTrack of kind '#{media_stream_track.kind}' successful", media_stream_track - @_remove_media_stream @audio_stream() if @audio_stream() - @_remove_media_stream @video_stream() if @video_stream() and not media_streams_identical - when z.media.MediaType.AUDIO - @_remove_media_stream @audio_stream() if @audio_stream() - when z.media.MediaType.VIDEO - @_remove_media_stream @video_stream() if @video_stream() + media_stream = @media_stream().clone() + + media_stream.addTrack media_stream_track for media_stream_track in z.media.MediaStreamHandler.get_media_tracks new_media_stream, media_type + return z.media.MediaStreamHandler.detect_media_stream_type media_stream + return new_media_stream ############################################################################### @@ -752,7 +742,7 @@ class z.calling.entities.EFlow @_clear_send_sdp_timeout() @telemetry.reset_statistics() @logger.info "Resetting flow with user '#{@remote_user.id}'" - @_remove_media_streams() + @_remove_media_stream @media_stream() if @media_stream() @_close_peer_connection() if @peer_connection?.signalingState isnt z.calling.rtc.SignalingState.CLOSED @_reset_signaling_states() @pc_initialized false diff --git a/app/script/calling/entities/Flow.coffee b/app/script/calling/entities/Flow.coffee index cdc4758f4bf..a1f07cfa5be 100644 --- a/app/script/calling/entities/Flow.coffee +++ b/app/script/calling/entities/Flow.coffee @@ -83,10 +83,7 @@ class z.calling.entities.Flow @telemetry.set_peer_connection @peer_connection @telemetry.schedule_check 5000 if is_initialized - @audio_stream = @call_et.local_stream_audio - @video_stream = @call_et.local_stream_video - - @has_media_stream = ko.pureComputed => return @video_stream()? or @audio_stream()? + @media_stream = @call_et.local_media_stream @connection_state = ko.observable z.calling.rtc.ICEConnectionState.NEW @gathering_state = ko.observable z.calling.rtc.ICEGatheringState.NEW @@ -113,7 +110,7 @@ class z.calling.entities.Flow @participant_et.is_connected false @call_et.delete_participant @participant_et return if @call_et.participants().length - termination_reason = if @e_call_et.is_connected() then z.calling.enum.TERMINATION_REASON.CONNECTION_DROP else z.calling.enum.TERMINATION_REASON.CONNECTION_FAILED + termination_reason = if @call_et.is_connected() then z.calling.enum.TERMINATION_REASON.CONNECTION_DROP else z.calling.enum.TERMINATION_REASON.CONNECTION_FAILED amplify.publish z.event.WebApp.CALL.STATE.LEAVE, @call_et.id, termination_reason when z.calling.rtc.ICEConnectionState.CLOSED @@ -126,7 +123,7 @@ class z.calling.entities.Flow return if @converted_own_sdp_state() @logger.debug "PeerConnection with '#{@remote_user.name()}' was closed" @call_et.delete_participant @participant_et - @_remove_media_streams() + @_remove_media_stream @media_stream() when z.calling.rtc.SignalingState.REMOTE_OFFER @negotiation_needed true @@ -245,7 +242,7 @@ class z.calling.entities.Flow @_create_offer() @can_initialize_peer_connection = ko.pureComputed => - can_initialize = @has_media_stream() and @payload() and not @pc_initialized() + can_initialize = @media_stream() and @payload() and not @pc_initialized() return can_initialize @can_initialize_peer_connection.subscribe (can_initialize) => @@ -298,6 +295,7 @@ class z.calling.entities.Flow _rewrite_payload: (payload) -> for ice_server in payload.ice_servers when not ice_server.urls ice_server.urls = [ice_server.url] + delete ice_server.url return payload @@ -352,8 +350,9 @@ class z.calling.entities.Flow # Initialize the PeerConnection. _initialize_peer_connection: -> @_create_peer_connection() - @_add_media_streams() + @_add_media_stream @media_stream() @pc_initialized true + @negotiation_needed true ### A MediaStream was added to the PeerConnection. @@ -708,25 +707,6 @@ class z.calling.entities.Flow @logger.info "Added local '#{media_stream.type}' MediaStream to PeerConnection", {stream: media_stream, audio_tracks: media_stream.getAudioTracks(), video_tracks: media_stream.getVideoTracks()} - ### - Adds the local MediaStreams to the PeerConnection. - @private - ### - _add_media_streams: -> - media_streams_identical = @_compare_local_media_streams() - - @_add_media_stream @audio_stream() if @audio_stream() - @_add_media_stream @video_stream() if @video_stream() and not media_streams_identical - - @negotiation_needed true - - ### - Compare whether local audio and video streams are identical. - @private - ### - _compare_local_media_streams: -> - return @audio_stream() and @video_stream() and @audio_stream().id is @video_stream().id - ### Replace the MediaStream attached to the PeerConnection. @private @@ -735,14 +715,16 @@ class z.calling.entities.Flow _replace_media_stream: (media_stream_info) -> Promise.resolve() .then => - return @_remove_media_streams media_stream_info.type + return @_remove_media_stream @media_stream() .then => + @_upgrade_media_stream media_stream_info.stream, media_stream_info.type + .then (updated_media_stream) => @negotiation_mode z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE - @_add_media_stream media_stream_info.stream + @_add_media_stream updated_media_stream @is_answer false @negotiation_needed true @logger.info 'Replaced the MediaStream successfully', media_stream_info.stream - return media_stream_info + return updated_media_stream .catch (error) => @logger.error "Failed to replace local MediaStream: #{error.message}", error throw error @@ -792,21 +774,25 @@ class z.calling.entities.Flow {stream: media_stream, audio_tracks: media_stream.getAudioTracks(), video_tracks: media_stream.getVideoTracks()} ### - Reset the flows MediaStream and media elements. + Upgrade the local MediaStream with new MediaStreamTracks + @private - @param media_type [z.media.MediaType] Optional media type of MediaStreams to be removed + @param new_media_stream [MediaStream] MediaStream containing new MediaStreamTracks + @param media_type [z.media.MediaType] Type of tracks to update + @return [MediaStream] New MediaStream to be used ### - _remove_media_streams: (media_type = z.media.MediaType.AUDIO_VIDEO) -> - switch media_type - when z.media.MediaType.AUDIO_VIDEO - media_streams_identical = @_compare_local_media_streams() + _upgrade_media_stream: (new_media_stream, media_type) -> + if @media_stream() + for media_stream_track in z.media.MediaStreamHandler.get_media_tracks @media_stream(), media_type + @media_stream().removeTrack media_stream_track + media_stream_track.stop() + @logger.info "Stopping MediaStreamTrack of kind '#{media_stream_track.kind}' successful", media_stream_track + + media_stream = @media_stream().clone() - @_remove_media_stream @audio_stream() if @audio_stream() - @_remove_media_stream @video_stream() if @video_stream() and not media_streams_identical - when z.media.MediaType.AUDIO - @_remove_media_stream @audio_stream() if @audio_stream() - when z.media.MediaType.VIDEO - @_remove_media_stream @video_stream() if @video_stream() + media_stream.addTrack media_stream_track for media_stream_track in z.media.MediaStreamHandler.get_media_tracks new_media_stream, media_type + return z.media.MediaStreamHandler.detect_media_stream_type media_stream + return new_media_stream ############################################################################### @@ -831,7 +817,7 @@ class z.calling.entities.Flow @_close_peer_connection() catch error @logger.error "We caught the #{error.name}: #{error.message}", error - @_remove_media_streams() + @_remove_media_stream @media_stream() @_reset_signaling_states() @ice_candidates_cache = [] @payload undefined diff --git a/app/script/calling/handler/CallStateHandler.coffee b/app/script/calling/handler/CallStateHandler.coffee index 297ebd12b8f..eaa82a8e413 100644 --- a/app/script/calling/handler/CallStateHandler.coffee +++ b/app/script/calling/handler/CallStateHandler.coffee @@ -226,7 +226,7 @@ class z.calling.handler.CallStateHandler @logger.error "PUTting the state to '#{payload.state}' in '#{conversation_id}' failed: #{error.message}", error attributes = {cause: error.label or error.name, method: 'put', request: 'state', video: payload.videod} @v2_call_center.telemetry.track_event z.tracking.EventName.CALLING.FAILED_REQUEST, undefined, attributes - @v2_call_center.media_stream_handler.release_media_streams() + @v2_call_center.media_stream_handler.release_media_stream() switch error.label when z.service.BackendClientError::LABEL.CONVERSATION_TOO_BIG amplify.publish z.event.WebApp.WARNING.MODAL, z.ViewModel.ModalType.CALL_FULL_CONVERSATION, @@ -318,7 +318,7 @@ class z.calling.handler.CallStateHandler call_et.state z.calling.enum.CallState.ENDED call_et.reset_call() @_remove_call call_et.id - @v2_call_center.media_stream_handler.reset_media_streams() + @v2_call_center.media_stream_handler.reset_media_stream() .catch (error) => @logger.warn "No call found in conversation '#{conversation_id}' to delete", error @@ -331,7 +331,7 @@ class z.calling.handler.CallStateHandler .then (call_et) => call_et.ignore() @logger.info "Call in '#{conversation_id}' ignored" - @v2_call_center.media_stream_handler.reset_media_streams() + @v2_call_center.media_stream_handler.reset_media_stream() .catch (error) => @logger.warn "No call found in conversation '#{conversation_id}' to ignore", error @@ -353,8 +353,8 @@ class z.calling.handler.CallStateHandler with_bot: conversation_et.is_with_bot() .then => @v2_call_center.timings = new z.telemetry.calling.CallSetupTimings conversation_id - if @v2_call_center.media_stream_handler.has_media_streams() - @logger.info 'MediaStream has already been initialized', @v2_call_center.media_stream_handler.local_media_streams + if @v2_call_center.media_stream_handler.local_media_stream() + @logger.info 'MediaStream has already been initialized', @v2_call_center.media_stream_handler.local_media_stream else return @v2_call_center.media_stream_handler.initiate_media_stream conversation_id, is_videod .then => @@ -369,7 +369,7 @@ class z.calling.handler.CallStateHandler @param termination_reason [z.calling.enum.TERMINATION_REASON] Optional on reason for call termination ### leave_call: (conversation_id, termination_reason) => - @v2_call_center.media_stream_handler.release_media_streams() + @v2_call_center.media_stream_handler.release_media_stream() @v2_call_center.get_call_by_id conversation_id .then (call_et) => call_et.state z.calling.enum.CallState.DISCONNECTING @@ -479,7 +479,7 @@ class z.calling.handler.CallStateHandler call_et.state z.calling.enum.CallState.ONGOING if call_et.participants_count() >= 2 if call_et.is_remote_video_send() and call_et.is_ongoing_on_another_client() - @v2_call_center.media_stream_handler.release_media_streams() + @v2_call_center.media_stream_handler.release_media_stream() ############################################################################### diff --git a/app/script/calling/mapper/SDPMapper.coffee b/app/script/calling/mapper/SDPMapper.coffee index acd03be2d49..d3e7e186984 100644 --- a/app/script/calling/mapper/SDPMapper.coffee +++ b/app/script/calling/mapper/SDPMapper.coffee @@ -39,20 +39,7 @@ z.calling.mapper.SDPMapper = type: if e_call_message_et.response is true then z.calling.rtc.SDPType.ANSWER else z.calling.rtc.SDPType.OFFER map_event_to_object: (event) -> - _convert_fingerprint_to_uppercase = (sdp_string) -> - sdp_lines = sdp_string.split '\r\n' - - for sdp_line, index in sdp_lines when sdp_line.startsWith 'a=fingerprint' - sdp_line_parts = sdp_line.split ' ' - sdp_lines[index] = "#{sdp_line_parts[0]} #{sdp_line_parts[1].toUpperCase()}" - - return sdp_lines.join '\r\n' - - remote_sdp = - sdp: _convert_fingerprint_to_uppercase event.sdp - type: event.state - - return new window.RTCSessionDescription remote_sdp + return new window.RTCSessionDescription sdp: event.sdp, type: event.state ### Rewrite the SDP for compatibility reasons. @@ -84,13 +71,9 @@ z.calling.mapper.SDPMapper = else if sdp_line.startsWith 'a=candidate' ice_candidates.push sdp_line - else if sdp_line.startsWith 'a=group' - if flow_et.negotiation_mode() is z.calling.enum.SDP_NEGOTIATION_MODE.STREAM_CHANGE and sdp_source is z.calling.enum.SDPSource.LOCAL - sdp_lines.push 'a=x-streamchange' - # Remove once obsolete due to high uptake of clients based on AVS build 3.3.11 containing fix for AUDIO-1215 else if sdp_line.startsWith 'a=mid' - if rtc_sdp.type is z.calling.rtc.SDPType.ANSWER and z.util.Environment.browser.firefox and sdp_source is z.calling.enum.SDPSource.REMOTE + if sdp_source is z.calling.enum.SDPSource.REMOTE and z.util.Environment.browser.firefox and rtc_sdp.type is z.calling.rtc.SDPType.ANSWER outline = 'a=mid:sdparta_2' if sdp_line is 'a=mid:data' # Code to nail in bit-rate and ptime settings for improved performance and experience diff --git a/app/script/calling/mapper/SDPRewriteMapper.coffee b/app/script/calling/mapper/SDPRewriteMapper.coffee deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/script/calling/v3/CallCenter.coffee b/app/script/calling/v3/CallCenter.coffee index e954539716f..1f71f981ec7 100644 --- a/app/script/calling/v3/CallCenter.coffee +++ b/app/script/calling/v3/CallCenter.coffee @@ -341,7 +341,7 @@ class z.calling.v3.CallCenter e_call_et.state z.calling.enum.CallState.ENDED e_call_et.reset_call() @e_calls.remove (e_call_et) -> e_call_et.id is conversation_id - @media_stream_handler.reset_media_streams() + @media_stream_handler.reset_media_stream() return undefined .catch (error) -> throw error unless error.type is z.calling.v3.CallError::TYPE.NOT_FOUND @@ -355,7 +355,7 @@ class z.calling.v3.CallCenter .then (e_call_et) => @logger.debug "Ignoring e-call in conversation '#{conversation_id}'", e_call_et e_call_et.state z.calling.enum.CallState.IGNORED - @media_stream_handler.reset_media_streams() + @media_stream_handler.reset_media_stream() .catch (error) -> throw error unless error.type is z.calling.v3.CallError::TYPE.NOT_FOUND @@ -378,7 +378,7 @@ class z.calling.v3.CallCenter e_call_et = e_call @logger.debug "Joining e-call in conversation '#{conversation_id}'", e_call_et e_call_et.start_timings() - if not @media_stream_handler.has_media_streams() + if not @media_stream_handler.local_media_stream() @media_stream_handler.initiate_media_stream conversation_id, video_send .then => e_call_et.timings.time_step z.telemetry.calling.CallSetupSteps.STREAM_RECEIVED @@ -404,7 +404,7 @@ class z.calling.v3.CallCenter @get_e_call_by_id conversation_id .then (e_call_et) => @logger.debug "Leaving e-call in conversation '#{conversation_id}'", e_call_et - @media_stream_handler.release_media_streams() + @media_stream_handler.release_media_stream() e_call_et.state z.calling.enum.CallState.DISCONNECTING e_call_et.termination_reason = termination_reason if termination_reason and not e_call_et.termination_reason @@ -491,7 +491,7 @@ class z.calling.v3.CallCenter @telemetry.track_event z.tracking.EventName.CALLING.RECEIVED_CALL, e_call_et @_distribute_activation_event e_call_message_et .catch (error) => - @delete_call e_call_et.conversation_et.id + @delete_call e_call_message_et.conversation_id throw error unless error instanceof z.media.MediaError ### diff --git a/app/script/components/userInput.coffee b/app/script/components/userInput.coffee index c28b368743a..6d83b8956e3 100644 --- a/app/script/components/userInput.coffee +++ b/app/script/components/userInput.coffee @@ -62,7 +62,8 @@ ko.components.register 'user-input', - + +
diff --git a/app/script/entity/message/Asset.coffee b/app/script/entity/message/Asset.coffee index 181cdd71ef7..e5251d4f65c 100644 --- a/app/script/entity/message/Asset.coffee +++ b/app/script/entity/message/Asset.coffee @@ -70,11 +70,11 @@ class z.entity.Asset ### is_video: -> is_video_asset = @type is z.assets.AssetType.FILE and @file_type?.startsWith 'video' - is_supported_browser = not (z.util.Environment.browser.firefox and platform.version < 49) - if is_video_asset and is_supported_browser + if is_video_asset can_play = document.createElement('video').canPlayType @file_type return true if can_play isnt '' return false + ### Check if asset is a audio. diff --git a/app/script/localization/strings-cs.coffee b/app/script/localization/strings-cs.coffee index 5c1f54f64dd..4c834da9fe0 100644 --- a/app/script/localization/strings-cs.coffee +++ b/app/script/localization/strings-cs.coffee @@ -198,11 +198,6 @@ z.string.cs.modal_session_reset_message_2 = 'nás.' # Too many members in conversation z.string.cs.modal_too_many_members_headline = 'Přeplněné kupé' z.string.cs.modal_too_many_members_message = 'Ke konverzaci se může připojit az %max účastníků. Je zde ještě místo pro %no.' -# Whitelist screensharing -z.string.cs.modal_whitelist_screensharing_headline = 'Wire potřebuje oprávnění sdílet vaši obrazovku' -z.string.cs.modal_whitelist_screensharing_message_1 = 'Otevřete about:config a přidejte *.wire.com do seznamu domén s právy sdílet obrazovku. Otevřete' -z.string.cs.modal_whitelist_screensharing_message_link = 'Časté dotazy' -z.string.cs.modal_whitelist_screensharing_message_2 = 'pro více informací.' # Parallel uploads z.string.cs.modal_uploads_parallel = 'Najednou můžete poslat až %no souborů.' diff --git a/app/script/localization/strings-da.coffee b/app/script/localization/strings-da.coffee index d0ee5f2d481..efba34436c0 100644 --- a/app/script/localization/strings-da.coffee +++ b/app/script/localization/strings-da.coffee @@ -208,11 +208,6 @@ z.string.da.modal_session_reset_message_2 = 'os.' # Too many members in conversation z.string.da.modal_too_many_members_headline = 'Fuldt hus' z.string.da.modal_too_many_members_message = 'Op til %max personer kan deltage i en samtale. Der er plads til %no personer mere herinde.' -# Whitelist screensharing -z.string.da.modal_whitelist_screensharing_headline = 'Wire skal have tilladelse til at dele din skærm' -z.string.da.modal_whitelist_screensharing_message_1 = 'Åben about:config og tilføj *.wire.com til listen over tilladte domæner for skærmdeling. Se' -z.string.da.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.da.modal_whitelist_screensharing_message_2 = 'for detaljer.' # Parallel uploads z.string.da.modal_uploads_parallel = 'Du kan sende op til %no filer på én gang.' diff --git a/app/script/localization/strings-de.coffee b/app/script/localization/strings-de.coffee index 23c4ff0baf2..e6a5fd947da 100644 --- a/app/script/localization/strings-de.coffee +++ b/app/script/localization/strings-de.coffee @@ -209,11 +209,6 @@ z.string.de.modal_session_reset_message_2 = 'uns.' # Too many members in conversation z.string.de.modal_too_many_members_headline = 'Volles Haus' z.string.de.modal_too_many_members_message = 'An einer Unterhaltung für eine Gruppe können bis zu %max Personen teilnehmen. Hier ist noch Platz für %no Personen.' -# Whitelist screensharing -z.string.de.modal_whitelist_screensharing_headline = 'Wire braucht die Berechtigung deinen Bildschirm zu teilen' -z.string.de.modal_whitelist_screensharing_message_1 = 'Öffne about:config und füge *.wire.com zur Liste der für Screensharing zugelassenen Domains hinzu. Lies den' -z.string.de.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.de.modal_whitelist_screensharing_message_2 = 'für weitere Informationen.' # Parallel uploads z.string.de.modal_uploads_parallel = 'Du kannst bis zu %no Dateien auf einmal senden.' diff --git a/app/script/localization/strings-es.coffee b/app/script/localization/strings-es.coffee index 1b3ac18dd90..d72b448dd9a 100644 --- a/app/script/localization/strings-es.coffee +++ b/app/script/localization/strings-es.coffee @@ -208,11 +208,6 @@ z.string.es.modal_session_reset_message_2 = 'nosotros.' # Too many members in conversation z.string.es.modal_too_many_members_headline = 'Llamada llena' z.string.es.modal_too_many_members_message = 'Hasta %max personas pueden unirse a una conversación. Hay espacio para %no más personas aquí.' -# Whitelist screensharing -z.string.es.modal_whitelist_screensharing_headline = 'Wire necesita permiso para compartir su pantalla' -z.string.es.modal_whitelist_screensharing_message_1 = 'Abrir about:config y agregar *.wire.com a la lista de dominios que pueden compartir la pantalla. Ver' -z.string.es.modal_whitelist_screensharing_message_link = 'Preguntas más Frecuentes' -z.string.es.modal_whitelist_screensharing_message_2 = 'para mayor detalle.' # Parallel uploads z.string.es.modal_uploads_parallel = 'Puede enviar hasta %no archivos a la vez.' diff --git a/app/script/localization/strings-fi.coffee b/app/script/localization/strings-fi.coffee index 2768a3ff8b6..4caf7a339ad 100644 --- a/app/script/localization/strings-fi.coffee +++ b/app/script/localization/strings-fi.coffee @@ -200,11 +200,6 @@ z.string.fi.modal_session_reset_message_2 = 'meihin.' # Too many members in conversation z.string.fi.modal_too_many_members_headline = 'Talo täynnä' z.string.fi.modal_too_many_members_message = 'Maksimissaan %max henkilöä voi liittyä keskusteluun. Tässä keskustelussa on tilaa %no hengelle.' -# Whitelist screensharing -z.string.fi.modal_whitelist_screensharing_headline = 'Wire tarvitsee luvan jakaa näyttösi' -z.string.fi.modal_whitelist_screensharing_message_1 = 'Avaa about:config ja lisää *.wire.com näytön jakamisen sallivaan verkkotunnus-listaan' -z.string.fi.modal_whitelist_screensharing_message_link = 'UKK' -z.string.fi.modal_whitelist_screensharing_message_2 = 'lisätiedoille.' # Parallel uploads z.string.fi.modal_uploads_parallel = 'Voit lähettää jopa %no tiedostoa samaan aikaan.' diff --git a/app/script/localization/strings-fr.coffee b/app/script/localization/strings-fr.coffee index e640ad6c595..ac5da404714 100644 --- a/app/script/localization/strings-fr.coffee +++ b/app/script/localization/strings-fr.coffee @@ -208,11 +208,6 @@ z.string.fr.modal_session_reset_message_2 = '-nous.' # Too many members in conversation z.string.fr.modal_too_many_members_headline = 'Maison pleine' z.string.fr.modal_too_many_members_message = 'Jusqu’à %max personnes peuvent se joindre à une conversation. Il y a encore de la place pour %no gens ici.' -# Whitelist screensharing -z.string.fr.modal_whitelist_screensharing_headline = 'Wire a besoin de votre autorisation pour partager votre écran' -z.string.fr.modal_whitelist_screensharing_message_1 = 'Ouvrez about:config et ajoutez *.wire.com à la liste des domaines autorisés à partager l’écran. Allez voir la' -z.string.fr.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.fr.modal_whitelist_screensharing_message_2 = 'pour plus de détails.' # Parallel uploads z.string.fr.modal_uploads_parallel = 'Vous pouvez envoyer jusqu’à %no fichiers à la fois.' diff --git a/app/script/localization/strings-hr.coffee b/app/script/localization/strings-hr.coffee index 66b514830f8..6d95cf32af6 100644 --- a/app/script/localization/strings-hr.coffee +++ b/app/script/localization/strings-hr.coffee @@ -198,11 +198,6 @@ z.string.hr.modal_session_reset_message_2 = 'nas.' # Too many members in conversation z.string.hr.modal_too_many_members_headline = 'Puna kuća' z.string.hr.modal_too_many_members_message = 'Do %max ljudi može pristupiti razgovoru. Ima mjesta za još %no ljudi ovdje.' -# Whitelist screensharing -z.string.hr.modal_whitelist_screensharing_headline = 'Wire treba dozvolu za zajedničko korištenje vašeg zaslona' -z.string.hr.modal_whitelist_screensharing_message_1 = 'Otvorite about:config i dodajte *. wire.com na popis dopuštenih domena za screensharing. Vidjeti' -z.string.hr.modal_whitelist_screensharing_message_link = 'Česta pitanja' -z.string.hr.modal_whitelist_screensharing_message_2 = 'detalje.' # Parallel uploads z.string.hr.modal_uploads_parallel = 'Možete poslati %no datoteke odjednom.' diff --git a/app/script/localization/strings-hu.coffee b/app/script/localization/strings-hu.coffee index bdb3fee412f..21614652e09 100644 --- a/app/script/localization/strings-hu.coffee +++ b/app/script/localization/strings-hu.coffee @@ -208,11 +208,6 @@ z.string.hu.modal_session_reset_message_2 = 'minket.' # Too many members in conversation z.string.hu.modal_too_many_members_headline = 'Telt ház' z.string.hu.modal_too_many_members_message = 'Legfeljebb %max partner tud csatlakozni a beszélgetéshez. Még %no partner számára van hely a csoportban.' -# Whitelist screensharing -z.string.hu.modal_whitelist_screensharing_headline = 'Böngészőjének engedélyre van szüksége, hogy megossza képernyőjét' -z.string.hu.modal_whitelist_screensharing_message_1 = 'Nyissa meg a beállításokat és adja hozzá a *.wire.com-ot, az engedélyezett domainekhez a képernyőmegosztási beállításokban. Lásd' -z.string.hu.modal_whitelist_screensharing_message_link = 'GY. I. K' -z.string.hu.modal_whitelist_screensharing_message_2 = 'a részletekért.' # Parallel uploads z.string.hu.modal_uploads_parallel = 'Egyszerre %no fájt küldhet.' diff --git a/app/script/localization/strings-it.coffee b/app/script/localization/strings-it.coffee index 7b91605d1af..f530d3c09b8 100644 --- a/app/script/localization/strings-it.coffee +++ b/app/script/localization/strings-it.coffee @@ -208,11 +208,6 @@ z.string.it.modal_session_reset_message_2 = 'Wire.' # Too many members in conversation z.string.it.modal_too_many_members_headline = 'Chiamata piena' z.string.it.modal_too_many_members_message = 'Fino a %max persone possono partecipare a una conversazione. C’è spazio per ancora %no persone qui dentro.' -# Whitelist screensharing -z.string.it.modal_whitelist_screensharing_headline = 'Wire richiede l’autorizzazione per condividere il tuo schermo' -z.string.it.modal_whitelist_screensharing_message_1 = 'Aprire about:config e aggiungere *.wire.com alla lista dei domini consentiti per la condivisione dello schermo. Vedere' -z.string.it.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.it.modal_whitelist_screensharing_message_2 = 'per ulteriori dettagli.' # Parallel uploads z.string.it.modal_uploads_parallel = 'È possibile inviare fino a %no file in una sola volta.' diff --git a/app/script/localization/strings-pt.coffee b/app/script/localization/strings-pt.coffee index 2f9e86de006..2e9c7ab6987 100644 --- a/app/script/localization/strings-pt.coffee +++ b/app/script/localization/strings-pt.coffee @@ -208,11 +208,6 @@ z.string.pt.modal_session_reset_message_2 = 'contate.' # Too many members in conversation z.string.pt.modal_too_many_members_headline = 'Chamada cheia' z.string.pt.modal_too_many_members_message = 'Até %max pessoas podem participar de uma conversa. Há espaço para %no pessoas aqui.' -# Whitelist screensharing -z.string.pt.modal_whitelist_screensharing_headline = 'Wire precisa de permissão para compartilhar sua tela' -z.string.pt.modal_whitelist_screensharing_message_1 = 'Abrir about:config e adicione *. wire.com à lista de domínios permitidos para compartilhamento de tela. Consulte' -z.string.pt.modal_whitelist_screensharing_message_link = 'o FAQ' -z.string.pt.modal_whitelist_screensharing_message_2 = 'para detalhes.' # Parallel uploads z.string.pt.modal_uploads_parallel = 'Você pode enviar até %no arquivos de uma só vez.' diff --git a/app/script/localization/strings-ro.coffee b/app/script/localization/strings-ro.coffee index 86cbadf86f9..5cc2912531b 100644 --- a/app/script/localization/strings-ro.coffee +++ b/app/script/localization/strings-ro.coffee @@ -208,11 +208,6 @@ z.string.ro.modal_session_reset_message_2 = 'ne.' # Too many members in conversation z.string.ro.modal_too_many_members_headline = 'Conversație plină' z.string.ro.modal_too_many_members_message = 'Maxim %max persoane se pot alătura conversației. Mai este loc pentru %no persoane aici.' -# Whitelist screensharing -z.string.ro.modal_whitelist_screensharing_headline = 'Wire are nevoie de permisiuni pentru a partaja ecranul' -z.string.ro.modal_whitelist_screensharing_message_1 = 'Deschide about:config și adaugă *.wire.com în lista de domenii permise pentru partajarea ecranului. Vezi' -z.string.ro.modal_whitelist_screensharing_message_link = 'Întrebări frecvente' -z.string.ro.modal_whitelist_screensharing_message_2 = 'pentru detalii.' # Parallel uploads z.string.ro.modal_uploads_parallel = 'Poți trimite maxim %no fișiere simultan.' diff --git a/app/script/localization/strings-ru.coffee b/app/script/localization/strings-ru.coffee index 8703067f126..5b282222184 100644 --- a/app/script/localization/strings-ru.coffee +++ b/app/script/localization/strings-ru.coffee @@ -208,11 +208,6 @@ z.string.ru.modal_session_reset_message_2 = 'с нами.' # Too many members in conversation z.string.ru.modal_too_many_members_headline = 'Канал переполнен' z.string.ru.modal_too_many_members_message = 'К разговору может присоединиться до %max человек. Здесь хватит места ещё для %no человек.' -# Whitelist screensharing -z.string.ru.modal_whitelist_screensharing_headline = 'Wire требуется разрешение для демонстрации экрана' -z.string.ru.modal_whitelist_screensharing_message_1 = 'Откройте about:config и добавьте *.wire.com в список разрешённых доменов для демонстрации экрана. Смотрите' -z.string.ru.modal_whitelist_screensharing_message_link = 'Вопросы и ответы' -z.string.ru.modal_whitelist_screensharing_message_2 = 'для уточнения деталей.' # Parallel uploads z.string.ru.modal_uploads_parallel = 'Вы можете отправить до %no файлов за раз.' diff --git a/app/script/localization/strings-sk.coffee b/app/script/localization/strings-sk.coffee index b9e8048add6..a561bfa0dc6 100644 --- a/app/script/localization/strings-sk.coffee +++ b/app/script/localization/strings-sk.coffee @@ -208,11 +208,6 @@ z.string.sk.modal_session_reset_message_2 = 'nás.' # Too many members in conversation z.string.sk.modal_too_many_members_headline = 'Priveľa účastníkov' z.string.sk.modal_too_many_members_message = 'Do rozhovoru sa môže zapojiť %max ľudí. ' -# Whitelist screensharing -z.string.sk.modal_whitelist_screensharing_headline = 'Wire potrebuje povolenie na zdieľanie obrazovky' -z.string.sk.modal_whitelist_screensharing_message_1 = 'Otvorte about:config a pridajte *.wire.com do zoznamu povolených domén pre zdieľanie obrazovky.' -z.string.sk.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.sk.modal_whitelist_screensharing_message_2 = 'pre viac informácií.' # Parallel uploads z.string.sk.modal_uploads_parallel = 'Súčasne môžete poslať až %no súborov.' diff --git a/app/script/localization/strings-sl.coffee b/app/script/localization/strings-sl.coffee index 2aae842b769..2e52e2af136 100644 --- a/app/script/localization/strings-sl.coffee +++ b/app/script/localization/strings-sl.coffee @@ -208,11 +208,6 @@ z.string.sl.modal_session_reset_message_2 = 'nas.' # Too many members in conversation z.string.sl.modal_too_many_members_headline = 'Polna hiša' z.string.sl.modal_too_many_members_message = 'Do %max oseb se lahko pridruži pogovoru. Tukaj je prostora še za %no oseb.' -# Whitelist screensharing -z.string.sl.modal_whitelist_screensharing_headline = 'Wire potrebuje dovoljenje za deljenje vašega zaslona' -z.string.sl.modal_whitelist_screensharing_message_1 = 'Odpri about:config in dodaj *.wire.com na seznam dovoljenih domen za deljenje zaslona. Glej' -z.string.sl.modal_whitelist_screensharing_message_link = 'Pogosto zastavljena vprašanja' -z.string.sl.modal_whitelist_screensharing_message_2 = 'za podrobnosti.' # Parallel uploads z.string.sl.modal_uploads_parallel = 'Lahko pošljete do %no zbirk naenkrat.' diff --git a/app/script/localization/strings-tr.coffee b/app/script/localization/strings-tr.coffee index cd8a0ddf913..ed9fdd2329b 100644 --- a/app/script/localization/strings-tr.coffee +++ b/app/script/localization/strings-tr.coffee @@ -208,11 +208,6 @@ z.string.tr.modal_session_reset_message_2 = 'biz.' # Too many members in conversation z.string.tr.modal_too_many_members_headline = 'Dolup taşmış' z.string.tr.modal_too_many_members_message = 'Görüşmeye en fazla %max kişi katılabilir. Burada %no kişi için daha boş yer var.' -# Whitelist screensharing -z.string.tr.modal_whitelist_screensharing_headline = 'Wire ekranınızı paylaşmak için gerekli izinleri sağlamalıdır' -z.string.tr.modal_whitelist_screensharing_message_1 = 'Hakkında:ayarlar bölümünü açın ve *.wire.com adresini ekran paylaşımı izinli alan adları listesine ekleyin' -z.string.tr.modal_whitelist_screensharing_message_link = 'SSS' -z.string.tr.modal_whitelist_screensharing_message_2 = 'detaylar için.' # Parallel uploads z.string.tr.modal_uploads_parallel = 'Tek seferde en fazla %no boyutunda dosya gönderebilirsiniz.' diff --git a/app/script/localization/strings-uk.coffee b/app/script/localization/strings-uk.coffee index e0927270303..de353efa4e4 100644 --- a/app/script/localization/strings-uk.coffee +++ b/app/script/localization/strings-uk.coffee @@ -208,11 +208,6 @@ z.string.uk.modal_session_reset_message_2 = 'з нами.' # Too many members in conversation z.string.uk.modal_too_many_members_headline = 'Голосовий канал переповнений' z.string.uk.modal_too_many_members_message = 'В розмові може бути до %max учасників. В даній розмові уже не вистачає місця для %no учасників.' -# Whitelist screensharing -z.string.uk.modal_whitelist_screensharing_headline = 'Для Wire необхідний дозвіл, щоб ви могли поділитись скріншотами робочого столу' -z.string.uk.modal_whitelist_screensharing_message_1 = 'Перейдіть до about:config та додайте *.wire.com до списку дозволених доменів для обміну скріншотами робочого столу. Дивіться' -z.string.uk.modal_whitelist_screensharing_message_link = 'розділ Часті запитання' -z.string.uk.modal_whitelist_screensharing_message_2 = ', щоб дізнатись більше деталей.' # Parallel uploads z.string.uk.modal_uploads_parallel = 'Ви можете надіслати до %no файлів за один раз.' diff --git a/app/script/localization/strings.coffee b/app/script/localization/strings.coffee index bfd497b45ac..f452c8004ed 100644 --- a/app/script/localization/strings.coffee +++ b/app/script/localization/strings.coffee @@ -38,7 +38,7 @@ z.string.auth_account_create_account = 'Create an account' z.string.auth_account_expiration = 'You were signed out because your session expired. Please log in again.' z.string.auth_account_get_wire = 'Simple, private & secure messenger for chat, calls, sharing pics, music, videos, GIFs and more.' z.string.auth_account_password_forgot = 'Forgot password' -z.string.auth_account_remember_me = 'Remember me' +z.string.auth_account_public_computer = 'This is a public computer' z.string.auth_account_sign_in = 'Log in' z.string.auth_account_sign_in_email = 'Email' z.string.auth_account_sign_in_phone = 'Phone' @@ -209,11 +209,6 @@ z.string.modal_session_reset_message_2 = 'us.' # Too many members in conversation z.string.modal_too_many_members_headline = 'Full house' z.string.modal_too_many_members_message = 'Up to %max people can join a conversation. There is room for %no more people in here.' -# Whitelist screensharing -z.string.modal_whitelist_screensharing_headline = 'Wire needs permission to share your screen' -z.string.modal_whitelist_screensharing_message_1 = 'Open about:config and add *.wire.com to the list of allowed domains for screensharing. See' -z.string.modal_whitelist_screensharing_message_link = 'FAQ' -z.string.modal_whitelist_screensharing_message_2 = 'for details.' # Parallel uploads z.string.modal_uploads_parallel = 'You can send up to %no files at once.' diff --git a/app/script/media/MediaDevicesHandler.coffee b/app/script/media/MediaDevicesHandler.coffee index 9f840bbef6f..9e1452f20de 100644 --- a/app/script/media/MediaDevicesHandler.coffee +++ b/app/script/media/MediaDevicesHandler.coffee @@ -97,7 +97,7 @@ class z.media.MediaDevicesHandler @current_device_id.audio_input.subscribe (media_device_id) => z.util.StorageUtil.set_value z.media.MediaDeviceType.AUDIO_INPUT, media_device_id - if media_device_id and @media_repository.stream_handler.local_media_streams.audio() + if media_device_id and @media_repository.stream_handler.local_media_stream() @media_repository.stream_handler.replace_input_source z.media.MediaType.AUDIO @_update_current_index_from_id z.media.MediaDeviceType.AUDIO_INPUT, media_device_id @@ -108,13 +108,13 @@ class z.media.MediaDevicesHandler @_update_current_index_from_id z.media.MediaDeviceType.AUDIO_OUTPUT, media_device_id @current_device_id.screen_input.subscribe (media_device_id) => - if media_device_id and @media_repository.stream_handler.local_media_streams.video() and @media_repository.stream_handler.local_media_type() is z.media.MediaType.SCREEN + if media_device_id and @media_repository.stream_handler.local_media_stream() and @media_repository.stream_handler.local_media_type() is z.media.MediaType.SCREEN @media_repository.stream_handler.replace_input_source z.media.MediaType.SCREEN @_update_current_index_from_id z.media.MediaDeviceType.SCREEN_INPUT, media_device_id @current_device_id.video_input.subscribe (media_device_id) => z.util.StorageUtil.set_value z.media.MediaDeviceType.VIDEO_INPUT, media_device_id - if media_device_id and @media_repository.stream_handler.local_media_streams.video() and @media_repository.stream_handler.local_media_type() is z.media.MediaType.VIDEO + if media_device_id and @media_repository.stream_handler.local_media_stream() and @media_repository.stream_handler.local_media_type() is z.media.MediaType.VIDEO @media_repository.stream_handler.replace_input_source z.media.MediaType.VIDEO @_update_current_index_from_id z.media.MediaDeviceType.VIDEO_INPUT, media_device_id diff --git a/app/script/media/MediaStreamHandler.coffee b/app/script/media/MediaStreamHandler.coffee index 28e73b0e402..a6a4c448be8 100644 --- a/app/script/media/MediaStreamHandler.coffee +++ b/app/script/media/MediaStreamHandler.coffee @@ -65,9 +65,7 @@ class z.media.MediaStreamHandler @calls = -> return [] @joined_call = -> return undefined - @local_media_streams = - audio: ko.observable() - video: ko.observable() + @local_media_stream = ko.observable() @remote_media_streams = audio: ko.observableArray [] @@ -80,9 +78,6 @@ class z.media.MediaStreamHandler @local_media_type = ko.observable z.media.MediaType.AUDIO - @has_media_streams = ko.pureComputed => - return @local_media_streams.audio() or @local_media_streams.video() - @request_hint_timeout = undefined @current_device_id = @media_repository.devices_handler.current_device_id @@ -211,16 +206,12 @@ class z.media.MediaStreamHandler if _.isArray error [error, media_type] = error @_initiate_media_stream_failure error, media_type, conversation_id - @logger.error "Requesting MediaStream failed: #{error.message or error.name}", error amplify.publish z.event.WebApp.ANALYTICS.EVENT, z.tracking.EventName.CALLING.FAILED_REQUESTING_MEDIA, {cause: error.name or error.message, video: video_send} throw error - # Release the MediaStreams. - release_media_streams: => - media_streams_identical = @_compare_local_media_streams() - - @local_media_streams.audio undefined if @_release_media_stream @local_media_streams.audio() - @local_media_streams.video undefined if media_streams_identical or @_release_media_stream @local_media_streams.video() + # Release the MediaStream. + release_media_stream: => + @local_media_stream undefined if @_release_media_stream @local_media_stream() ### Replace the MediaStream after a change of the selected input device. @@ -234,15 +225,12 @@ class z.media.MediaStreamHandler @_set_stream_state media_stream_info update_promise = Promise.all (flow_et.update_media_stream media_stream_info for flow_et in @joined_call().get_flows()) else - update_promise = Promise.resolve [media_stream_info] + update_promise = Promise.resolve media_stream_info.stream update_promise.then (resolve_array) => - media_stream_info = resolve_array[0] - if media_stream_info.type is z.media.MediaType.AUDIO - @_release_media_stream @local_media_streams.audio(), z.media.MediaType.AUDIO - else - @_release_media_stream @local_media_streams.video(), z.media.MediaType.VIDEO - @set_local_media_stream media_stream_info + media_stream = resolve_array[0] + @_release_media_stream @local_media_stream() + @local_media_stream media_stream ### Update the used MediaStream after a new input device was selected. @@ -264,7 +252,9 @@ class z.media.MediaStreamHandler return @replace_media_stream media_stream_info .catch (error) => [error, media_type] = error if _.isArray error - @_replace_input_source_failure error, media_type + if media_type is z.media.MediaType.SCREEN + return @logger.error "Failed to enable screen sharing: #{error.message}", error + @logger.error "Failed to replace '#{media_type}' input source: #{error.message}", error ### Request a MediaStream. @@ -292,6 +282,7 @@ class z.media.MediaStreamHandler @_clear_permission_request_hint media_type resolve new z.media.MediaStreamInfo z.media.MediaStreamSource.LOCAL, 'self', media_stream .catch (error) => + @logger.warn "MediaStream request failed: #{error.name} - #{error.message}" @_clear_permission_request_hint media_type if error.name in z.calling.rtc.MediaStreamErrorTypes.DEVICE error = new z.media.MediaError z.media.MediaError::TYPE.MEDIA_STREAM_DEVICE @@ -301,15 +292,8 @@ class z.media.MediaStreamHandler error = new z.media.MediaError z.media.MediaError::TYPE.MEDIA_STREAM_PERMISSION reject [error, media_type] - ### - Save a reference to a local MediaStream. - @param media_stream_info [z.media.MediaStreamInfo] MediaStream and meta information - ### - set_local_media_stream: (media_stream_info) => - if media_stream_info.type in [z.media.MediaType.AUDIO, z.media.MediaType.AUDIO_VIDEO] - @local_media_streams.audio media_stream_info.stream - if media_stream_info.type in [z.media.MediaType.AUDIO_VIDEO, z.media.MediaType.VIDEO] - @local_media_streams.video media_stream_info.stream + set_local_media_stream: (media_stream) => + @local_media_stream media_stream ### Clear the permission request hint timeout or hide the warning. @@ -321,14 +305,6 @@ class z.media.MediaStreamHandler return window.clearTimeout @request_hint_timeout return @_hide_permission_request_hint media_type - ### - Compare the local MediaStreams for equality. - @private - @return [Boolean] True if both audio and video stream are identical - ### - _compare_local_media_streams: -> - return @local_media_streams.audio()?.id is @local_media_streams.video()?.id - ### Hide the permission denied hint banner. @private @@ -368,7 +344,7 @@ class z.media.MediaStreamHandler @logger.debug "Received initial MediaStream with '#{media_stream_info.stream.getTracks().length}' MediaStreamTrack(s)", {stream: media_stream_info.stream, audio_tracks: media_stream_info.stream.getAudioTracks(), video_tracks: media_stream_info.stream.getVideoTracks()} @_set_stream_state media_stream_info - @set_local_media_stream media_stream_info + @local_media_stream media_stream_info.stream ### Local MediaStream creation failed. @@ -407,24 +383,6 @@ class z.media.MediaStreamHandler @logger.warn 'No MediaStreamTrack found to stop', media_stream return false - ### - Failed to replace an input source. - - @private - @param error [Error] Error thrown when attempting to replace the source - @param media_type [z.media.MediaType] Type of failed request - ### - _replace_input_source_failure: (error, media_type) -> - if media_type is z.media.MediaType.SCREEN - if z.util.Environment.browser.firefox and error.type is z.media.MediaError::TYPE.MEDIA_STREAM_PERMISSION - # @deprecated Remove once we require Firefox 52 ESR - @logger.warn 'We are not on the white list. Manually add the current domain to media.getusermedia.screensharing.allowed_domains on about:config' - amplify.publish z.event.WebApp.WARNING.MODAL, z.ViewModel.ModalType.WHITELIST_SCREENSHARING - else - @logger.error "Failed to enable screen sharing: #{error.message}", error - else - @logger.error "Failed to replace '#{media_type}' input source: #{error.message}", error - ### Show microphone not found hin banner. @@ -502,18 +460,18 @@ class z.media.MediaStreamHandler # Toggle the camera. toggle_video_send: => - if @local_media_streams.video() and @local_media_type() is z.media.MediaType.VIDEO + if @local_media_stream() and @local_media_type() is z.media.MediaType.VIDEO return @_toggle_video_send() return @replace_input_source z.media.MediaType.VIDEO # Toggle the mute state of the microphone. toggle_audio_send: => - return @_toggle_audio_send() if @local_media_streams.audio() + return @_toggle_audio_send() if @local_media_stream() return Promise.reject new z.media.MediaError z.media.MediaError::TYPE.NO_AUDIO_STREAM_FOUND # Toggle the screen. toggle_screen_send: => - if @local_media_streams.video() and @local_media_type() is z.media.MediaType.SCREEN + if @local_media_stream() and @local_media_type() is z.media.MediaType.SCREEN return @_toggle_screen_send() return @replace_input_source z.media.MediaType.SCREEN @@ -524,10 +482,10 @@ class z.media.MediaStreamHandler @self_stream_state.video_send false @local_media_type z.media.MediaType.AUDIO - # Reset the MediaStreams and states. - reset_media_streams: => + # Reset the MediaStream and states. + reset_media_stream: => if not @needs_media_stream() - @release_media_streams() + @release_media_stream() @reset_self_states() @media_repository.close_audio_context() @@ -567,7 +525,7 @@ class z.media.MediaStreamHandler @private ### _toggle_audio_send: -> - @_toggle_stream_enabled z.media.MediaType.AUDIO, @local_media_streams.audio(), @self_stream_state.audio_send + @_toggle_stream_enabled z.media.MediaType.AUDIO, @local_media_stream(), @self_stream_state.audio_send .then (audio_track) => @logger.info "Microphone enabled: #{@self_stream_state.audio_send()}", audio_track return @self_stream_state.audio_send() @@ -577,7 +535,7 @@ class z.media.MediaStreamHandler @private ### _toggle_screen_send: -> - @_toggle_stream_enabled z.media.MediaType.VIDEO, @local_media_streams.video(), @self_stream_state.screen_send + @_toggle_stream_enabled z.media.MediaType.VIDEO, @local_media_stream(), @self_stream_state.screen_send .then (video_track) => @logger.info "Screen enabled: #{@self_stream_state.screen_send()}", video_track return @self_stream_state.screen_send() @@ -587,7 +545,7 @@ class z.media.MediaStreamHandler @private ### _toggle_video_send: -> - @_toggle_stream_enabled z.media.MediaType.VIDEO, @local_media_streams.video(), @self_stream_state.video_send + @_toggle_stream_enabled z.media.MediaType.VIDEO, @local_media_stream(), @self_stream_state.video_send .then (video_track) => @logger.info "Camera enabled: #{@self_stream_state.video_send()}", video_track return @self_stream_state.video_send() diff --git a/app/script/service/Client.coffee b/app/script/service/Client.coffee index 7d72fd6ce11..53cb34d4cc5 100644 --- a/app/script/service/Client.coffee +++ b/app/script/service/Client.coffee @@ -19,18 +19,25 @@ window.z ?= {} z.service ?= {} -IGNORED_BACKEND_ERRORS = [ - z.service.BackendClientError::STATUS_CODE.BAD_GATEWAY - z.service.BackendClientError::STATUS_CODE.BAD_REQUEST - z.service.BackendClientError::STATUS_CODE.CONFLICT - z.service.BackendClientError::STATUS_CODE.CONNECTIVITY_PROBLEM - z.service.BackendClientError::STATUS_CODE.INTERNAL_SERVER_ERROR - z.service.BackendClientError::STATUS_CODE.NOT_FOUND - z.service.BackendClientError::STATUS_CODE.PRECONDITION_FAILED - z.service.BackendClientError::STATUS_CODE.REQUEST_TIMEOUT - z.service.BackendClientError::STATUS_CODE.REQUEST_TOO_LARGE - z.service.BackendClientError::STATUS_CODE.TOO_MANY_REQUESTS -] +CLIENT_CONFIG = + IGNORED_BACKEND_ERRORS: [ + z.service.BackendClientError::STATUS_CODE.BAD_GATEWAY + z.service.BackendClientError::STATUS_CODE.BAD_REQUEST + z.service.BackendClientError::STATUS_CODE.CONFLICT + z.service.BackendClientError::STATUS_CODE.CONNECTIVITY_PROBLEM + z.service.BackendClientError::STATUS_CODE.INTERNAL_SERVER_ERROR + z.service.BackendClientError::STATUS_CODE.NOT_FOUND + z.service.BackendClientError::STATUS_CODE.PRECONDITION_FAILED + z.service.BackendClientError::STATUS_CODE.REQUEST_TIMEOUT + z.service.BackendClientError::STATUS_CODE.REQUEST_TOO_LARGE + z.service.BackendClientError::STATUS_CODE.TOO_MANY_REQUESTS + ] + IGNORED_BACKEND_LABELS: [ + z.service.BackendClientError::LABEL.PASSWORD_EXISTS + z.service.BackendClientError::LABEL.TOO_MANY_CLIENTS + z.service.BackendClientError::LABEL.TOO_MANY_MEMBERS + ] + # Client for all backend REST API calls. class z.service.Client @@ -180,15 +187,12 @@ class z.service.Client amplify.publish z.event.WebApp.CONNECTION.ACCESS_TOKEN.RENEW, 'Unauthorized backend request' return when z.service.BackendClientError::STATUS_CODE.FORBIDDEN - switch jqXHR.responseJSON?.label - when z.service.BackendClientError::LABEL.INVALID_CREDENTIALS - Raygun.send new Error 'Server request failed: Invalid credentials' - when z.service.BackendClientError::LABEL.TOO_MANY_CLIENTS, z.service.BackendClientError::LABEL.TOO_MANY_MEMBERS - @logger.warn "Server request failed: '#{jqXHR.responseJSON.label}'" - else - Raygun.send new Error 'Server request failed' + if jqXHR.responseJSON?.label in CLIENT_CONFIG.IGNORED_BACKEND_LABELS + @logger.warn "Server request failed: #{jqXHR.responseJSON?.label}" + else + Raygun.send new Error "Server request failed: #{jqXHR.responseJSON?.label}" else - if jqXHR.status not in IGNORED_BACKEND_ERRORS + if jqXHR.status not in CLIENT_CONFIG.IGNORED_BACKEND_ERRORS Raygun.send new Error "Server request failed: #{jqXHR.status}" reject jqXHR.responseJSON or new z.service.BackendClientError jqXHR.status diff --git a/app/script/util/ArrayUtil.coffee b/app/script/util/ArrayUtil.coffee index fec3da93e13..4b534fd75d6 100644 --- a/app/script/util/ArrayUtil.coffee +++ b/app/script/util/ArrayUtil.coffee @@ -24,7 +24,7 @@ z.util.ArrayUtil = # returns chunks of the given size chunk: (array, size) -> chunks = [] - temp_array = [].concat array + temp_array = Array.from array while temp_array.length chunks.push temp_array.splice 0, size return chunks diff --git a/app/script/util/DebugUtil.coffee b/app/script/util/DebugUtil.coffee index 4c3cc5bf552..49a6fe93d75 100644 --- a/app/script/util/DebugUtil.coffee +++ b/app/script/util/DebugUtil.coffee @@ -82,6 +82,40 @@ class z.util.DebugUtil @logger.warn "From: #{debug_information.user.name()}", debug_information.user resolve debug_information + get_serialised_session: (session_id) -> + return wire.app.repository.storage.storage_service.load 'sessions', session_id + .then (record) -> + base64_encoded_payload = z.util.array_to_base64 record.serialised + record.serialised = base64_encoded_payload + return record + + get_serialised_identity: -> + return wire.app.repository.storage.storage_service.load 'keys', 'local_identity' + .then (record) -> + base64_encoded_payload = z.util.array_to_base64 record.serialised + record.serialised = base64_encoded_payload + return record + + get_event_from_notification_stream: (event_id) -> + client_id = wire.app.repository.client.current_client().id + return wire.app.service.notification.get_notifications(client_id, undefined, 10000) + .then (response) -> + events = response.notifications.filter (item) -> + return item.id is event_id + return events[0] + + get_objects_for_decryption_errors: (session_id, event_id) -> + return Promise.all([ + @get_event_from_notification_stream event_id + @get_serialised_identity() + @get_serialised_session session_id + ]) + .then (items) -> + return JSON.stringify + event: items[0] + identity: items[1] + session: items[2] + log_connection_status: -> @logger.log 'Online Status' @logger.log "-- Browser online: #{window.navigator.onLine}" diff --git a/app/script/util/Environment.coffee b/app/script/util/Environment.coffee index 539ea1e8698..ffba63aabf4 100644 --- a/app/script/util/Environment.coffee +++ b/app/script/util/Environment.coffee @@ -72,8 +72,7 @@ z.util.Environment = do -> return false supports_screen_sharing: -> return true if window.desktopCapturer - # @deprecated Remove warning modal once we require Firefox 52 ESR - return @is_firefox() and @get_version() >= 48 + return @is_firefox() os = is_mac: -> diff --git a/app/script/view_model/AuthViewModel.coffee b/app/script/view_model/AuthViewModel.coffee index 3939309e7e0..d9e0d948424 100644 --- a/app/script/view_model/AuthViewModel.coffee +++ b/app/script/view_model/AuthViewModel.coffee @@ -73,17 +73,19 @@ class z.ViewModel.AuthViewModel @session_expired = ko.observable false @device_reused = ko.observable false - @client_type = ko.observable z.client.ClientType.TEMPORARY @country_code = ko.observable '' @country = ko.observable '' @name = ko.observable '' @password = ko.observable '' - @persist = ko.observable z.util.Environment.electron + @persist = ko.observable true @phone_number = ko.observable '' @username = ko.observable '' - @persist.subscribe (is_persistent) => - if is_persistent then @client_type z.client.ClientType.PERMANENT else z.client.ClientType.TEMPORARY + @is_public_computer = ko.observable false + @is_public_computer.subscribe (is_public_computer) => @persist not is_public_computer + + @client_type = ko.pureComputed => + if @persist() then z.client.ClientType.PERMANENT else z.client.ClientType.TEMPORARY @self_user = ko.observable() diff --git a/app/script/view_model/ModalsViewModel.coffee b/app/script/view_model/ModalsViewModel.coffee index 6d44315ce96..b91552f37b9 100644 --- a/app/script/view_model/ModalsViewModel.coffee +++ b/app/script/view_model/ModalsViewModel.coffee @@ -43,7 +43,6 @@ z.ViewModel.ModalType = SESSION_RESET: '.modal-session-reset' UPLOAD_PARALLEL: '.modal-asset-upload-parallel' UPLOAD_TOO_LARGE: '.modal-asset-upload-too-large' - WHITELIST_SCREENSHARING: '.modal-whitelist-screensharing' z.ViewModel.MODAL_CONSENT_TYPE = diff --git a/app/script/view_model/VideoCallingViewModel.coffee b/app/script/view_model/VideoCallingViewModel.coffee index 669121a88d0..a4a204a59d5 100644 --- a/app/script/view_model/VideoCallingViewModel.coffee +++ b/app/script/view_model/VideoCallingViewModel.coffee @@ -30,7 +30,7 @@ class z.ViewModel.VideoCallingViewModel @current_device_id = @media_repository.devices_handler.current_device_id @current_device_index = @media_repository.devices_handler.current_device_index - @local_video_stream = @media_repository.stream_handler.local_media_streams.video + @local_video_stream = @media_repository.stream_handler.local_media_stream @remote_video_stream = @media_repository.stream_handler.remote_media_streams.video @self_stream_state = @media_repository.stream_handler.self_stream_state diff --git a/app/script/view_model/bindings/CommonBindings.coffee b/app/script/view_model/bindings/CommonBindings.coffee index b142b918a8e..aaa7383e968 100644 --- a/app/script/view_model/bindings/CommonBindings.coffee +++ b/app/script/view_model/bindings/CommonBindings.coffee @@ -217,9 +217,6 @@ ko.subscribable.fn.subscribe_once = (handler, owner, event_name) -> ko.bindingHandlers.antiscroll = init: (element, valueAccessor) -> - if z.util.Environment.os.mac and not z.util.Environment.browser.firefox - return - $(element).antiscroll autoHide: true autoWrap: true diff --git a/app/script/view_model/content/CollectionDetailsViewModel.coffee b/app/script/view_model/content/CollectionDetailsViewModel.coffee index 78af5e07f74..6f7a7519ce3 100644 --- a/app/script/view_model/content/CollectionDetailsViewModel.coffee +++ b/app/script/view_model/content/CollectionDetailsViewModel.coffee @@ -71,7 +71,7 @@ class z.ViewModel.content.CollectionDetailsViewModel should_show_header: (message_et) => if not @last_message_timestamp? - @last_message_timestamp = message_et.timestamp + @last_message_timestamp = message_et.timestamp() return true # we passed today diff --git a/app/script/view_model/content/PreferencesAVViewModel.coffee b/app/script/view_model/content/PreferencesAVViewModel.coffee index f8aa3184fdd..f326426158d 100644 --- a/app/script/view_model/content/PreferencesAVViewModel.coffee +++ b/app/script/view_model/content/PreferencesAVViewModel.coffee @@ -30,14 +30,13 @@ class z.ViewModel.content.PreferencesAVViewModel @current_device_id = @media_devices_handler.current_device_id @media_stream_handler = @media_repository.stream_handler - @audio_stream = @media_stream_handler.local_media_streams.audio - @video_stream = @media_stream_handler.local_media_streams.video + @media_stream = @media_stream_handler.local_media_stream @is_visible = false - @audio_stream.subscribe (audio_stream) => + @media_stream.subscribe (media_stream) => @_release_audio_meter() if @audio_interval - @_initiate_audio_meter audio_stream if @is_visible and audio_stream + @_initiate_audio_meter media_stream if @is_visible and media_stream @audio_context = undefined @audio_level = ko.observable 0 @@ -49,29 +48,29 @@ class z.ViewModel.content.PreferencesAVViewModel initiate_devices: => @is_visible = true @_get_media_stream() - .then (audio_stream) => - @_initiate_audio_meter audio_stream if audio_stream and not @audio_interval + .then (media_stream) => + @_initiate_audio_meter media_stream if media_stream and not @audio_interval # Release devices. release_devices: => @is_visible = false @_release_audio_meter() - @_release_media_streams() + @_release_media_stream() ### Get current MediaStream or initiate it. @private ### _get_media_stream: => - return Promise.resolve @audio_stream() if @audio_stream() and @video_stream() + return Promise.resolve @media_stream() if @media_stream() and @media_stream_handler.local_media_type() is z.media.MediaType.VIDEO @media_stream_handler.get_media_stream_constraints @available_devices.audio_input().length, @available_devices.video_input().length .then ([media_type, media_stream_constraints]) => return @media_stream_handler.request_media_stream media_type, media_stream_constraints .then (media_stream_info) => @media_stream_handler.local_media_type z.media.MediaType.VIDEO if @available_devices.video_input().length - @media_stream_handler.set_local_media_stream media_stream_info - return @audio_stream() + @media_stream_handler.local_media_stream media_stream_info.stream + return @media_stream_handler.local_media_stream() .catch (error) => error = error[0] if _.isArray error @logger.error "Requesting MediaStream failed: #{error.message}", error @@ -83,10 +82,10 @@ class z.ViewModel.content.PreferencesAVViewModel ### Initiate audio meter. @private - @param audio_stream [] + @param media_stream [MediaStream] MediaStream to measure audio levels on ### - _initiate_audio_meter: (audio_stream) => - @logger.info 'Initiating new audio meter', audio_stream + _initiate_audio_meter: (media_stream) => + @logger.info 'Initiating new audio meter', media_stream @audio_context = @media_repository.get_audio_context() @audio_analyser = @audio_context.createAnalyser() @@ -106,7 +105,7 @@ class z.ViewModel.content.PreferencesAVViewModel @audio_level average_volume - 0.075 , 100 - @audio_source = @audio_context.createMediaStreamSource audio_stream + @audio_source = @audio_context.createMediaStreamSource media_stream @audio_source.connect @audio_analyser _release_audio_meter: => @@ -114,6 +113,6 @@ class z.ViewModel.content.PreferencesAVViewModel @audio_interval = undefined @audio_source.disconnect() if @audio_source - _release_media_streams: => - @media_stream_handler.reset_media_streams() + _release_media_stream: => + @media_stream_handler.reset_media_stream() @permission_denied false diff --git a/app/style/components/asset/link-preview-asset.less b/app/style/components/asset/link-preview-asset.less index b903d4a0805..196035d3355 100644 --- a/app/style/components/asset/link-preview-asset.less +++ b/app/style/components/asset/link-preview-asset.less @@ -61,7 +61,6 @@ link-preview-asset { .link-preview-info-title { font-weight: @font-weight-bold; line-height: @line-height-lg; - margin-bottom: 4px; } .link-preview-info-title-singleline { diff --git a/aws/config.py b/aws/config.py index 90430707675..04eb71e0e51 100644 --- a/aws/config.py +++ b/aws/config.py @@ -29,9 +29,9 @@ PUBLIC_KEY_PINS = os.environ.get('PUBLIC_KEY_PINS', '') SUPPORTED = { - 'chrome': 52, - 'firefox': 45, - 'opera': 41, + 'chrome': 55, + 'firefox': 52, + 'opera': 42, 'msedge': 14, } diff --git a/karma.conf.js b/karma.conf.js index 2eb1b71d732..1f8fc51589c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -102,7 +102,7 @@ module.exports = function (config) { each: { statements: 2, branches: 0, - functions: 1, + functions: 0, lines: 2 } } diff --git a/test/unit_tests/assets/AssetRemoteDataSpec.coffee b/test/unit_tests/assets/AssetRemoteDataSpec.coffee index fe2bbdf6d28..49d21a14b5c 100644 --- a/test/unit_tests/assets/AssetRemoteDataSpec.coffee +++ b/test/unit_tests/assets/AssetRemoteDataSpec.coffee @@ -28,7 +28,7 @@ describe 'z.assets.AssetRemoteData', -> conversation_id = z.util.create_random_uuid() asset_id = z.util.create_random_uuid() remote_data = z.assets.AssetRemoteData.v1 conversation_id, asset_id - spyOn(remote_data, '_load_buffer').and.returnValue Promise.resolve video_bytes.buffer + spyOn(remote_data, '_load_buffer').and.returnValue Promise.resolve([video_bytes.buffer, video_type]) it 'should load and decrypt asset', (done) -> remote_data.load() diff --git a/test/unit_tests/audio/AudioRepositorySpec.coffee b/test/unit_tests/audio/AudioRepositorySpec.coffee index 9bfff551fa3..99fb50f605c 100644 --- a/test/unit_tests/audio/AudioRepositorySpec.coffee +++ b/test/unit_tests/audio/AudioRepositorySpec.coffee @@ -42,7 +42,7 @@ describe 'z.audio.AudioRepository', -> .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.audio.AudioError - expect(error.type).toBe z.audio.AudioError::TYPE.IGNORED_SOUND + expect(error.type).toBe z.audio.AudioError.TYPE.IGNORED_SOUND done() describe '_get_sound_by_id', -> @@ -58,7 +58,7 @@ describe 'z.audio.AudioRepository', -> .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.audio.AudioError - expect(error.type).toBe z.audio.AudioError::TYPE.NOT_FOUND + expect(error.type).toBe z.audio.AudioError.TYPE.NOT_FOUND done() xdescribe '_play', -> @@ -91,7 +91,7 @@ describe 'z.audio.AudioRepository', -> .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.audio.AudioError - expect(error.type).toBe z.audio.AudioError::TYPE.ALREADY_PLAYING + expect(error.type).toBe z.audio.AudioError.TYPE.ALREADY_PLAYING done() .catch done.fail @@ -99,7 +99,7 @@ describe 'z.audio.AudioRepository', -> audio_repository._play undefined, audio_repository.audio_elements[z.audio.AudioType.OUTGOING_CALL] .catch (error) -> expect(error).toEqual jasmine.any z.audio.AudioError - expect(error.type).toBe z.audio.AudioError::TYPE.NOT_FOUND + expect(error.type).toBe z.audio.AudioError.TYPE.NOT_FOUND done() .catch done.fail @@ -107,6 +107,6 @@ describe 'z.audio.AudioRepository', -> audio_repository._play z.audio.AudioType.OUTGOING_CALL, undefined .catch (error) -> expect(error).toEqual jasmine.any z.audio.AudioError - expect(error.type).toBe z.audio.AudioError::TYPE.NOT_FOUND + expect(error.type).toBe z.audio.AudioError.TYPE.NOT_FOUND done() .catch done.fail diff --git a/test/unit_tests/calling/v2/CallCenterSpec.coffee b/test/unit_tests/calling/v2/CallCenterSpec.coffee index 0f028d17b93..c828be4b4a3 100644 --- a/test/unit_tests/calling/v2/CallCenterSpec.coffee +++ b/test/unit_tests/calling/v2/CallCenterSpec.coffee @@ -397,14 +397,14 @@ describe 'z.calling.v2.CallCenter', -> 'member_count': 11 'message': 'too many members for calling' } - spyOn v2_call_center.media_stream_handler, 'release_media_streams' + spyOn v2_call_center.media_stream_handler, 'release_media_stream' v2_call_center.state_handler._put_state conversation_et.id, {state: z.calling.enum.ParticipantState.JOINED, videod: false} .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.calling.v2.CallError expect(error.type).toBe z.calling.v2.CallError::TYPE.CONVERSATION_TOO_BIG - expect(v2_call_center.media_stream_handler.release_media_streams).toHaveBeenCalled() + expect(v2_call_center.media_stream_handler.release_media_stream).toHaveBeenCalled() done() server.requests[0].respond 409, 'Content-Type': 'application/json', JSON.stringify error_payload @@ -415,14 +415,14 @@ describe 'z.calling.v2.CallCenter', -> 'max_joined': 5 'message': 'the voice channel is full' } - spyOn v2_call_center.media_stream_handler, 'release_media_streams' + spyOn v2_call_center.media_stream_handler, 'release_media_stream' v2_call_center.state_handler._put_state conversation_et.id, {state: z.calling.enum.ParticipantState.JOINED, videod: false} .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.calling.v2.CallError expect(error.type).toBe z.calling.v2.CallError::TYPE.VOICE_CHANNEL_FULL - expect(v2_call_center.media_stream_handler.release_media_streams).toHaveBeenCalled() + expect(v2_call_center.media_stream_handler.release_media_stream).toHaveBeenCalled() done() server.requests[0].respond 409, 'Content-Type': 'application/json', JSON.stringify error_payload @@ -432,13 +432,13 @@ describe 'z.calling.v2.CallCenter', -> 'label': 'invalid-op' 'message': 'Nobody left to call' } - spyOn v2_call_center.media_stream_handler, 'release_media_streams' + spyOn v2_call_center.media_stream_handler, 'release_media_stream' v2_call_center.state_handler._put_state conversation_et.id, {state: z.calling.enum.ParticipantState.JOINED, videod: false} .then done.fail .catch (error) -> expect(error).toEqual jasmine.any z.calling.v2.CallError expect(error.type).toBe z.calling.v2.CallError::TYPE.CONVERSATION_EMPTY - expect(v2_call_center.media_stream_handler.release_media_streams).toHaveBeenCalled() + expect(v2_call_center.media_stream_handler.release_media_stream).toHaveBeenCalled() done() server.requests[0].respond 400, 'Content-Type': 'application/json', JSON.stringify error_payload diff --git a/test/unit_tests/media/MediaStreamHandlerSpec.coffee b/test/unit_tests/media/MediaStreamHandlerSpec.coffee index 2e1210977fb..e2db322dc75 100644 --- a/test/unit_tests/media/MediaStreamHandlerSpec.coffee +++ b/test/unit_tests/media/MediaStreamHandlerSpec.coffee @@ -36,7 +36,7 @@ describe 'z.media.MediaStreamHandler', -> spyOn(media_repository.stream_handler, '_toggle_audio_send').and.returnValue Promise.resolve() it 'toggles the audio stream if available', (done) -> - media_repository.stream_handler.local_media_streams.audio true + media_repository.stream_handler.local_media_stream true media_repository.stream_handler.toggle_audio_send() .then -> @@ -45,7 +45,7 @@ describe 'z.media.MediaStreamHandler', -> .catch done.fail it 'throws an error if no audio stream is found', (done) -> - media_repository.stream_handler.local_media_streams.audio undefined + media_repository.stream_handler.local_media_stream undefined media_repository.stream_handler.toggle_audio_send() .then done.fail @@ -61,7 +61,7 @@ describe 'z.media.MediaStreamHandler', -> spyOn(media_repository.stream_handler, 'replace_input_source').and.returnValue Promise.resolve() it 'toggles the video stream if available and in video mode', (done) -> - media_repository.stream_handler.local_media_streams.video true + media_repository.stream_handler.local_media_stream true media_repository.stream_handler.local_media_type z.media.MediaType.VIDEO media_repository.stream_handler.toggle_video_send() @@ -72,7 +72,7 @@ describe 'z.media.MediaStreamHandler', -> .catch done.fail it 'turns on the video stream if it does not exist', (done) -> - media_repository.stream_handler.local_media_streams.video undefined + media_repository.stream_handler.local_media_stream undefined media_repository.stream_handler.local_media_type z.media.MediaType.VIDEO media_repository.stream_handler.toggle_video_send() @@ -83,7 +83,7 @@ describe 'z.media.MediaStreamHandler', -> .catch done.fail it 'turns on the video stream if not in video mode', (done) -> - media_repository.stream_handler.local_media_streams.video true + media_repository.stream_handler.local_media_stream true media_repository.stream_handler.local_media_type z.media.MediaType.SCREEN media_repository.stream_handler.toggle_video_send() @@ -100,7 +100,7 @@ describe 'z.media.MediaStreamHandler', -> spyOn(media_repository.stream_handler, 'replace_input_source').and.returnValue Promise.resolve() it 'toggles screen sharing if available and in screen sharing mode', (done) -> - media_repository.stream_handler.local_media_streams.video true + media_repository.stream_handler.local_media_stream true media_repository.stream_handler.local_media_type z.media.MediaType.SCREEN media_repository.stream_handler.toggle_screen_send() @@ -111,7 +111,7 @@ describe 'z.media.MediaStreamHandler', -> .catch done.fail it 'turns on the screen sharing stream if it does not exist', (done) -> - media_repository.stream_handler.local_media_streams.video undefined + media_repository.stream_handler.local_media_stream undefined media_repository.stream_handler.local_media_type z.media.MediaType.SCREEN media_repository.stream_handler.toggle_screen_send() @@ -122,7 +122,7 @@ describe 'z.media.MediaStreamHandler', -> .catch done.fail it 'turns on the video stream if not in screen sharing mode', (done) -> - media_repository.stream_handler.local_media_streams.video true + media_repository.stream_handler.local_media_stream true media_repository.stream_handler.local_media_type z.media.MediaType.VIDEO media_repository.stream_handler.toggle_screen_send() diff --git a/yarn.lock b/yarn.lock index 71fbc71a42d..8e4de076461 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,10 +13,35 @@ accepts@1.3.3, accepts@~1.3.3: mime-types "~2.1.11" negotiator "0.6.1" +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0, ajv@^4.9.1: + version "4.11.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -29,6 +54,10 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + ansi-regex@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.1.0.tgz#55ca60db6900857c423ae9297980026f941ed903" @@ -216,15 +245,15 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -autoprefixer@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.6.1.tgz#11a4077abb4b313253ec2f6e1adb91ad84253519" +autoprefixer@6.7.7: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" dependencies: - browserslist "~1.5.1" - caniuse-db "^1.0.30000604" + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^5.2.8" + postcss "^5.2.16" postcss-value-parser "^3.2.3" aws-sdk@2.0.x: @@ -246,6 +275,14 @@ aws4@^1.2.1: version "1.5.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" +babel-code-frame@^6.16.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -319,18 +356,18 @@ bluebird@^3.3.0: version "3.4.7" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" -body-parser@^1.12.4: - version "1.16.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b" +body-parser@^1.16.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47" dependencies: bytes "2.4.0" content-type "~1.0.2" - debug "2.6.0" + debug "2.6.1" depd "~1.1.0" - http-errors "~1.5.1" + http-errors "~1.6.1" iconv-lite "0.4.15" on-finished "~2.3.0" - qs "6.2.1" + qs "6.4.0" raw-body "~2.2.0" type-is "~1.6.14" @@ -472,11 +509,12 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserslist@~1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.5.2.tgz#1c82fde0ee8693e6d15c49b7bff209dc06298c56" +browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" dependencies: - caniuse-db "^1.0.30000604" + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" buffer-crc32@^0.2.1: version "0.2.13" @@ -502,10 +540,20 @@ bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -521,9 +569,9 @@ camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" -caniuse-db@^1.0.30000604: - version "1.0.30000613" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000613.tgz#639133b7a5380c1416f9701d23d54d093dd68299" +caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000640" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000640.tgz#7b7fd3cf13c0d9d41f8754b577b202113e2be7ca" cardinal@0.4.0: version "0.4.0" @@ -535,6 +583,10 @@ caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + caseless@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.6.0.tgz#8167c1ab8397fb5bb95f96d28e5a81c50f247ac4" @@ -556,7 +608,7 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@0.5.0: +chalk@0.5.0, chalk@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.0.tgz#375dfccbc21c0a60a8b61bc5b78f3dc2a55c212f" dependencies: @@ -566,7 +618,7 @@ chalk@0.5.0: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^0.5.0, chalk@^0.5.1: +chalk@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" dependencies: @@ -576,7 +628,7 @@ chalk@^0.5.0, chalk@^0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1: +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -615,6 +667,10 @@ chokidar@^1.4.1: optionalDependencies: fsevents "^1.0.0" +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + cli-color@~0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-0.3.3.tgz#12d5bdd158ff8a0b0db401198913c03df069f6f5" @@ -624,6 +680,16 @@ cli-color@~0.3.2: memoizee "~0.3.8" timers-ext "0.1" +cli-cursor@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -632,6 +698,10 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -723,7 +793,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.1: +concat-stream@^1.4.1, concat-stream@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -755,7 +825,7 @@ connect-livereload@^0.5.0: version "0.5.4" resolved "https://registry.yarnpkg.com/connect-livereload/-/connect-livereload-0.5.4.tgz#80157d1371c9f37cc14039ab1895970d119dc3bc" -connect@^3.3.5, connect@^3.4.0: +connect@^3.4.0: version "3.5.0" resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198" dependencies: @@ -764,6 +834,15 @@ connect@^3.3.5, connect@^3.4.0: parseurl "~1.3.1" utils-merge "1.0.0" +connect@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.0.tgz#f09a4f7dcd17324b663b725c815bdb1c4158a46e" + dependencies: + debug "2.6.1" + finalhandler "1.0.0" + parseurl "~1.3.1" + utils-merge "1.0.0" + console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -821,6 +900,12 @@ custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + d@^0.1.1, d@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" @@ -840,7 +925,7 @@ dateformat@^1.0.6, dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -debug@2.2.0, debug@~2.2.0: +debug@2.2.0, debug@^2.1.1, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -852,9 +937,9 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" dependencies: ms "0.7.2" @@ -886,6 +971,25 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + delayed-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" @@ -898,7 +1002,7 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@~1.1.0: +depd@1.1.0, depd@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" @@ -914,6 +1018,13 @@ diff@^2.0.2: version "2.2.3" resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + dom-serialize@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" @@ -933,6 +1044,10 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +electron-to-chromium@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.8.tgz#22c2e6200d350da27d6050db7e3f6f85d18cf4ed" + encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -949,9 +1064,9 @@ end-of-stream@~1.0.0: dependencies: once "~1.3.0" -engine.io-client@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.2.tgz#c38767547f2a7d184f5752f6f0ad501006703766" +engine.io-client@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" dependencies: component-emitter "1.2.1" component-inherit "0.0.3" @@ -962,7 +1077,7 @@ engine.io-client@1.8.2: parsejson "0.0.3" parseqs "0.0.5" parseuri "0.0.5" - ws "1.1.1" + ws "1.1.2" xmlhttprequest-ssl "1.5.3" yeast "0.1.2" @@ -977,16 +1092,16 @@ engine.io-parser@1.3.2: has-binary "0.1.7" wtf-8 "1.0.0" -engine.io@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.2.tgz#6b59be730b348c0125b0a4589de1c355abcf7a7e" +engine.io@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4" dependencies: accepts "1.3.3" base64id "1.0.0" cookie "0.3.1" debug "2.3.3" engine.io-parser "1.3.2" - ws "1.1.1" + ws "1.1.2" ent@~2.2.0: version "2.2.0" @@ -1004,6 +1119,13 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.15" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.15.tgz#c330a5934c1ee21284a7c081a86e5fd937c91ea6" + dependencies: + es6-iterator "2" + es6-symbol "~3.1" + es5-ext@^0.10.7, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.5, es5-ext@~0.10.6, es5-ext@~0.10.7: version "0.10.12" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" @@ -1019,6 +1141,14 @@ es6-iterator@2: es5-ext "^0.10.7" es6-symbol "3" +es6-iterator@^2.0.1, es6-iterator@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-symbol "^3.1" + es6-iterator@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-0.1.3.tgz#d6f58b8c4fc413c249b4baa19768f8e4d7c8944e" @@ -1027,6 +1157,27 @@ es6-iterator@~0.1.3: es5-ext "~0.10.5" es6-symbol "~2.0.1" +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + es6-symbol@3, es6-symbol@~3.1: version "3.1.0" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" @@ -1034,6 +1185,13 @@ es6-symbol@3, es6-symbol@~3.1: d "~0.1.1" es5-ext "~0.10.11" +es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-symbol@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-2.0.1.tgz#761b5c67cfd4f1d18afb234f691d678682cb3bf3" @@ -1041,6 +1199,15 @@ es6-symbol@~2.0.1: d "~0.1.1" es5-ext "~0.10.5" +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + es6-weak-map@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-0.1.4.tgz#706cef9e99aa236ba7766c239c8b9e286ea7d228" @@ -1069,6 +1236,62 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.18.0.tgz#647e985c4ae71502d20ac62c109f66d5104c8a4b" + dependencies: + babel-code-frame "^6.16.0" + chalk "^1.1.3" + concat-stream "^1.5.2" + debug "^2.1.1" + doctrine "^2.0.0" + escope "^3.6.0" + espree "^3.4.0" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.0.3" + globals "^9.14.0" + ignore "^3.2.0" + imurmurhash "^0.1.4" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" + is-resolvable "^1.0.0" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" + levn "^0.3.0" + lodash "^4.0.0" + mkdirp "^0.5.0" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.7.5" + strip-bom "^3.0.0" + strip-json-comments "~2.0.1" + table "^3.7.8" + text-table "~0.2.0" + user-home "^2.0.0" + +espree@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.0.tgz#41656fa5628e042878025ef467e78f125cb86e1d" + dependencies: + acorn "4.0.4" + acorn-jsx "^3.0.0" + esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -1077,10 +1300,31 @@ esprima@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" + dependencies: + estraverse "~4.1.0" + object-assign "^4.0.1" + estraverse@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" +estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +estraverse@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -1096,6 +1340,13 @@ event-emitter@~0.3.4: d "~0.1.1" es5-ext "~0.10.7" +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + eventemitter2@~0.4.13: version "0.4.14" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" @@ -1104,6 +1355,10 @@ eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + exit@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -1159,13 +1414,20 @@ faye-websocket@~0.10.0: dependencies: websocket-driver ">=0.5.1" -figures@^1.0.1, figures@^1.3.2: +figures@^1.0.1, figures@^1.3.2, figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" dependencies: escape-string-regexp "^1.0.5" object-assign "^4.1.0" +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + file-sync-cmp@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz#a5e7a8ffbfa493b43b923bbd4ca89a53b63b612b" @@ -1194,6 +1456,18 @@ finalhandler@0.5.0: statuses "~1.3.0" unpipe "~1.0.0" +finalhandler@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.0.tgz#b5691c2c0912092f18ac23e9416bde5cd7dc6755" + dependencies: + debug "2.6.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.1" + statuses "~1.3.1" + unpipe "~1.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -1207,6 +1481,15 @@ findup-sync@~0.3.0: dependencies: glob "~5.0.0" +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + for-in@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8" @@ -1217,6 +1500,10 @@ for-own@^0.1.4: dependencies: for-in "^0.1.5" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.5.2.tgz#6d0e09c4921f94a27f63d3b49c5feff1ea4c5130" @@ -1293,6 +1580,10 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10, fstream@~1.0.2: mkdirp ">=0.5 0" rimraf "2" +function-bind@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + gauge@~2.7.1: version "2.7.2" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.2.tgz#15cecc31b02d05345a5d6b0e171cdb3ad2307774" @@ -1360,9 +1651,9 @@ glob@^5.0.15, glob@~5.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" +glob@^7.0.0, glob@^7.0.5, glob@^7.0.6, glob@~7.0.0: + version "7.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -1371,9 +1662,9 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.6, glob@~7.0.0: - version "7.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" +glob@^7.0.3, glob@^7.1.1, glob@~7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -1391,6 +1682,21 @@ glob@~4.0.2: minimatch "^1.0.0" once "^1.3.0" +globals@^9.14.0: + version "9.16.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.16.0.tgz#63e903658171ec2d9f51b1d31de5e2b8dc01fb80" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globule@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/globule/-/globule-1.1.0.tgz#c49352e4dc183d85893ee825385eb994bb6df45f" @@ -1511,23 +1817,23 @@ grunt-contrib-copy@1.0.0: chalk "^1.1.1" file-sync-cmp "^0.1.0" -grunt-contrib-less@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-less/-/grunt-contrib-less-1.4.0.tgz#17ee79cad21c9720ee07b3a991fab5103b513514" +grunt-contrib-less@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/grunt-contrib-less/-/grunt-contrib-less-1.4.1.tgz#3bbdec0b75d12ceaa55d62943625c0b0861cdf6f" dependencies: async "^2.0.0" chalk "^1.0.0" less "~2.7.1" lodash "^4.8.2" -grunt-contrib-uglify@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-uglify/-/grunt-contrib-uglify-2.0.0.tgz#8c9970d690936cde6d25aa1193549bd929016930" +grunt-contrib-uglify@wireapp/grunt-contrib-uglify#2f25cd5df1fe8160168843831b9966ec70e64f9e: + version "2.2.0" + resolved "https://codeload.github.com/wireapp/grunt-contrib-uglify/tar.gz/2f25cd5df1fe8160168843831b9966ec70e64f9e" dependencies: chalk "^1.0.0" - lodash.assign "^4.0.9" maxmin "^1.1.0" - uglify-js "~2.7.0" + object.assign "^4.0.4" + uglify-js mishoo/UglifyJS2#harmony-v2.8.12 uri-path "^1.0.0" grunt-contrib-watch@1.0.0: @@ -1671,6 +1977,10 @@ handlebars@~2.0.0: optionalDependencies: uglify-js "~2.3" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + har-validator@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" @@ -1680,6 +1990,13 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + has-ansi@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" @@ -1758,7 +2075,7 @@ http-errors@~1.3.1: inherits "~2.0.1" statuses "1" -http-errors@~1.5.0, http-errors@~1.5.1: +http-errors@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" dependencies: @@ -1766,6 +2083,15 @@ http-errors@~1.5.0, http-errors@~1.5.1: setprototypeof "1.0.2" statuses ">= 1.3.1 < 2" +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" + dependencies: + depd "1.1.0" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + http-proxy@^1.13.0: version "1.16.2" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" @@ -1801,7 +2127,7 @@ iconv-lite@0.4.15, iconv-lite@~0.4.13: version "0.4.15" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" -ignore@^3.0.9: +ignore@^3.0.9, ignore@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435" @@ -1809,6 +2135,10 @@ image-size@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.1.tgz#28eea8548a4b1443480ddddc1e083ae54652439f" +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -1851,6 +2181,24 @@ inquirer@0.7.1: rx "^2.2.27" through "~2.3.4" +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + figures "^1.3.5" + lodash "^4.3.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + inquirer@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.6.0.tgz#614d7bb3e48f9e6a8028e94a0c38f23ef29823d3" @@ -1877,6 +2225,10 @@ insight@0.4.3: request "^2.40.0" tough-cookie "^0.12.1" +interpret@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" + intersect@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/intersect/-/intersect-0.0.3.tgz#c1a4a5e5eac6ede4af7504cc07e0ada7bc9f4920" @@ -1931,13 +2283,17 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" dependencies: is-extglob "^1.0.0" -is-my-json-valid@^2.12.4: +is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: version "2.15.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" dependencies: @@ -1956,6 +2312,22 @@ is-number@^2.0.2, is-number@^2.1.0: dependencies: kind-of "^3.0.2" +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -1968,6 +2340,12 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + is-root@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" @@ -1984,7 +2362,7 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2043,7 +2421,11 @@ js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" -js-yaml@3.x, js-yaml@^3.1.0, js-yaml@~3.5.2: +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + +js-yaml@3.x, js-yaml@^3.1.0, js-yaml@^3.5.1, js-yaml@~3.5.2: version "3.5.5" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.5.5.tgz#0377c38017cabc7322b0d1fbcd25a491641f2fbe" dependencies: @@ -2058,6 +2440,12 @@ json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -2107,16 +2495,16 @@ karma-jasmine@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf" -karma@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/karma/-/karma-1.4.0.tgz#bf5edbccabb8579cb68ae699871f3e79608ec94b" +karma@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/karma/-/karma-1.5.0.tgz#9c4c14f0400bef2c04c8e8e6bff59371025cc009" dependencies: bluebird "^3.3.0" - body-parser "^1.12.4" + body-parser "^1.16.1" chokidar "^1.4.1" colors "^1.1.0" combine-lists "^1.0.0" - connect "^3.3.5" + connect "^3.6.0" core-js "^2.2.0" di "^0.0.1" dom-serialize "^2.2.0" @@ -2132,12 +2520,12 @@ karma@1.4.0: optimist "^0.6.1" qjobs "^1.1.4" range-parser "^1.2.0" - rimraf "^2.3.3" + rimraf "^2.6.0" safe-buffer "^5.0.1" - socket.io "1.7.2" + socket.io "1.7.3" source-map "^0.5.3" - tmp "0.0.28" - useragent "^2.1.10" + tmp "0.0.31" + useragent "^2.1.12" kind-of@^3.0.2: version "3.1.0" @@ -2174,7 +2562,7 @@ less@~2.7.1: request "^2.72.0" source-map "^0.5.3" -levn@~0.3.0: +levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" dependencies: @@ -2216,10 +2604,6 @@ lodash._objecttypes@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11" -lodash.assign@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - lodash.debounce@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-2.4.1.tgz#d8cead246ec4b926e8b85678fc396bfeba8cc6fc" @@ -2252,7 +2636,7 @@ lodash@^3.10.1, lodash@^3.8.0, lodash@~3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.14.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@^4.8.2: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@^4.8.2: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2433,16 +2817,16 @@ mkdirp@0.5.0, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0: dependencies: minimist "0.0.8" -mkdirp@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - -mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +mkdirp@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + mkpath@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-0.1.0.tgz#7554a6f8d871834cc97b5462b122c4c124d6de91" @@ -2482,6 +2866,10 @@ mute-stream@0.0.4, mute-stream@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e" +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" + nan@^2.3.0: version "2.5.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.0.tgz#aa8f1e34531d807e9e27755b234b4a6ec0c152a8" @@ -2490,6 +2878,10 @@ natives@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31" +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -2628,6 +3020,18 @@ object-component@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" +object-keys@^1.0.10, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -2661,6 +3065,10 @@ once@~1.3.0, once@~1.3.3: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + open@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc" @@ -2689,7 +3097,7 @@ optimist@~0.3, optimist@~0.3.5: dependencies: wordwrap "~0.0.2" -optionator@^0.8.1: +optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" dependencies: @@ -2704,6 +3112,10 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + os-name@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/os-name/-/os-name-1.0.3.tgz#1b379f64835af7c5a7f498b357cb95215c159edf" @@ -2793,6 +3205,10 @@ path-is-absolute@^1.0.0, path-is-absolute@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -2805,6 +3221,10 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -2825,6 +3245,10 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" + portscanner@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-1.2.0.tgz#b14bbda257d14c310fa9cc09682af02d40961802" @@ -2835,7 +3259,7 @@ postcss-value-parser@^3.2.3: version "3.3.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" -postcss@^5.0.0, postcss@^5.2.8: +postcss@^5.0.0: version "5.2.10" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.10.tgz#b58b64e04f66f838b7bc7cb41f7dac168568a945" dependencies: @@ -2844,6 +3268,15 @@ postcss@^5.0.0, postcss@^5.2.8: source-map "^0.5.6" supports-color "^3.1.2" +postcss@^5.2.16: + version "5.2.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.16.tgz#732b3100000f9ff8379a48a53839ed097376ad57" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -2869,7 +3302,7 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -progress@1.1.x: +progress@1.1.x, progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -2920,9 +3353,9 @@ qs@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be" -qs@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" +qs@6.4.0, qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" qs@~1.2.0: version "1.2.2" @@ -3048,6 +3481,14 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + readline2@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/readline2/-/readline2-0.1.1.tgz#99443ba6e83b830ef3051bfd7dc241a82728d568" @@ -3055,6 +3496,12 @@ readline2@~0.1.0: mute-stream "0.0.4" strip-ansi "^2.0.1" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -3111,7 +3558,55 @@ request-replay@~0.2.0: dependencies: retry "~0.6.0" -request@2.79.0, request@^2.40.0, request@^2.72.0, request@^2.79.0: +request@2.81.0, request@^2.79.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.40.0, request@~2.51.0: + version "2.51.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.51.0.tgz#35d00bbecc012e55f907b1bd9e0dbd577bfef26e" + dependencies: + aws-sign2 "~0.5.0" + bl "~0.9.0" + caseless "~0.8.0" + combined-stream "~0.0.5" + forever-agent "~0.5.0" + form-data "~0.2.0" + hawk "1.1.1" + http-signature "~0.10.0" + json-stringify-safe "~5.0.0" + mime-types "~1.0.1" + node-uuid "~1.4.0" + oauth-sign "~0.5.0" + qs "~2.3.1" + stringstream "~0.0.4" + tough-cookie ">=0.12.0" + tunnel-agent "~0.4.0" + +request@^2.72.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -3157,31 +3652,21 @@ request@~2.42.0: stringstream "~0.0.4" tough-cookie ">=0.12.0" -request@~2.51.0: - version "2.51.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.51.0.tgz#35d00bbecc012e55f907b1bd9e0dbd577bfef26e" +require-uncached@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" dependencies: - aws-sign2 "~0.5.0" - bl "~0.9.0" - caseless "~0.8.0" - combined-stream "~0.0.5" - forever-agent "~0.5.0" - form-data "~0.2.0" - hawk "1.1.1" - http-signature "~0.10.0" - json-stringify-safe "~5.0.0" - mime-types "~1.0.1" - node-uuid "~1.4.0" - oauth-sign "~0.5.0" - qs "~2.3.1" - stringstream "~0.0.4" - tough-cookie ">=0.12.0" - tunnel-agent "~0.4.0" + caller-path "^0.1.0" + resolve-from "^1.0.0" requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" @@ -3192,7 +3677,7 @@ resolve-pkg@^0.1.0: dependencies: resolve-from "^2.0.0" -resolve@1.1.x, resolve@~1.1.0: +resolve@1.1.x, resolve@^1.1.6, resolve@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" @@ -3200,6 +3685,13 @@ resolve@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.6.3.tgz#dd957982e7e736debdf53b58a4dd91754575dd46" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + retry@0.6.0, retry@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.6.0.tgz#1c010713279a6fd1e8def28af0c3ff1871caa537" @@ -3210,12 +3702,18 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.3.3, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@~2.5.1, rimraf@~2.5.4: +rimraf@2, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: glob "^7.0.5" +rimraf@^2.2.8, rimraf@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + rimraf@~2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.0.3.tgz#f50a2965e7144e9afd998982f15df706730f56a9" @@ -3226,6 +3724,16 @@ rimraf@~2.2.0, rimraf@~2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" + dependencies: + once "^1.3.0" + +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" + rx@^2.2.27: version "2.5.3" resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566" @@ -3248,11 +3756,7 @@ semver-diff@^0.1.0: dependencies: semver "^2.2.1" -"semver@2 || 3 || 4", "semver@2 || 3 || 4 || 5", semver@~4.3.3: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - -semver@^2.2.1, semver@~2.3.0: +"semver@2 || 3 || 4", "semver@2 || 3 || 4 || 5", semver@^2.2.1, semver@~2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" @@ -3260,6 +3764,10 @@ semver@^5.0.1, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" +semver@~4.3.3: + version "4.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" + send@0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" @@ -3311,6 +3819,10 @@ setprototypeof@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + shell-quote@~1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.4.3.tgz#952c44e0b1ed9013ef53958179cc643e8777466b" @@ -3320,6 +3832,14 @@ shell-quote@~1.4.1: array-reduce "~0.0.0" jsonify "~0.0.0" +shelljs@^0.7.5: + version "0.7.7" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.7.tgz#b2f5c77ef97148f4b4f6e22682e10bba8667cff1" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -3337,6 +3857,10 @@ sinon@1.17.7: samsam "1.1.2" util ">=0.10.3 <1" +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + sntp@0.2.x: version "0.2.4" resolved "https://registry.yarnpkg.com/sntp/-/sntp-0.2.4.tgz#fb885f18b0f3aad189f824862536bceeec750900" @@ -3356,15 +3880,15 @@ socket.io-adapter@0.5.0: debug "2.3.3" socket.io-parser "2.3.1" -socket.io-client@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.2.tgz#39fdb0c3dd450e321b7e40cfd83612ec533dd644" +socket.io-client@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377" dependencies: backo2 "1.0.2" component-bind "1.0.0" component-emitter "1.2.1" debug "2.3.3" - engine.io-client "1.8.2" + engine.io-client "1.8.3" has-binary "0.1.7" indexof "0.0.1" object-component "0.0.3" @@ -3381,16 +3905,16 @@ socket.io-parser@2.3.1: isarray "0.0.1" json3 "3.3.2" -socket.io@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.2.tgz#83bbbdf2e79263b378900da403e7843e05dc3b71" +socket.io@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b" dependencies: debug "2.3.3" - engine.io "1.8.2" + engine.io "1.8.3" has-binary "0.1.7" object-assign "4.1.0" socket.io-adapter "0.5.0" - socket.io-client "1.7.2" + socket.io-client "1.7.3" socket.io-parser "2.3.1" source-map@^0.4.4: @@ -3448,7 +3972,7 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -statuses@1, "statuses@>= 1.3.1 < 2", statuses@~1.3.0: +statuses@1, "statuses@>= 1.3.1 < 2", statuses@~1.3.0, statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -3470,6 +3994,13 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +string-width@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^3.0.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -3512,6 +4043,10 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -3522,6 +4057,10 @@ strip-json-comments@^1.0.2, strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -3534,12 +4073,23 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.2: +supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + tar-fs@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-0.5.2.tgz#0f59424be7eeee45232316e302f66d3f6ea6db3e" @@ -3587,7 +4137,7 @@ tar@~2.2.1: fstream "^1.0.2" inherits "2" -text-table@^0.2.0: +text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -3595,7 +4145,7 @@ throttleit@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" -through@~2.3.4: +through@^2.3.6, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -3621,9 +4171,9 @@ tmp@0.0.23: version "0.0.23" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.23.tgz#de874aa5e974a85f0a32cdfdbd74663cb3bd9c74" -tmp@0.0.28: - version "0.0.28" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" +tmp@0.0.31, tmp@0.0.x: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" dependencies: os-tmpdir "~1.0.1" @@ -3637,18 +4187,18 @@ touch@0.0.2: dependencies: nopt "~1.0.10" -tough-cookie@>=0.12.0, tough-cookie@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" - dependencies: - punycode "^1.4.1" - -tough-cookie@^0.12.1: +tough-cookie@>=0.12.0, tough-cookie@^0.12.1: version "0.12.1" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-0.12.1.tgz#8220c7e21abd5b13d96804254bd5a81ebf2c7d62" dependencies: punycode ">=0.2.0" +tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -3657,6 +4207,16 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + tunnel-agent@~0.4.0, tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" @@ -3682,7 +4242,7 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uglify-js@^2.6, uglify-js@~2.7.0: +uglify-js@^2.6: version "2.7.5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" dependencies: @@ -3691,6 +4251,14 @@ uglify-js@^2.6, uglify-js@~2.7.0: uglify-to-browserify "~1.0.0" yargs "~3.10.0" +uglify-js@mishoo/UglifyJS2#harmony-v2.8.12: + version "2.8.12" + resolved "https://codeload.github.com/mishoo/UglifyJS2/tar.gz/2fd86d3cb02c2bcde81633c0096b308e2809ea00" + dependencies: + source-map "~0.5.1" + uglify-to-browserify "~1.0.0" + yargs "~3.10.0" + uglify-js@~2.3: version "2.3.6" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.3.6.tgz#fa0984770b428b7a9b2a8058f46355d14fef211a" @@ -3741,11 +4309,18 @@ user-home@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" -useragent@^2.1.10: - version "2.1.11" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.11.tgz#6a026e6a6c619b46ca7a0b2fdef6c1ac3da8ca29" +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + dependencies: + os-homedir "^1.0.0" + +useragent@^2.1.12: + version "2.1.12" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.12.tgz#aa7da6cdc48bdc37ba86790871a7321d64edbaa2" dependencies: lru-cache "2.2.x" + tmp "0.0.x" util-deprecate@~1.0.1: version "1.0.2" @@ -3846,9 +4421,15 @@ wrench@~1.4.3: version "1.4.4" resolved "https://registry.yarnpkg.com/wrench/-/wrench-1.4.4.tgz#7f523efdb71b0100e77dce834c06523cbe3d54e0" -ws@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018" +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +ws@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" dependencies: options ">=0.0.5" ultron "1.0.x"