Unverified Commit db4a1f8d authored by Tasso Evangelista's avatar Tasso Evangelista Committed by GitHub

[FIX] Use Electron notifications (#1101)

* Relax autoplay policy to allow sound notifications

* Resolve notification icon URL

* Use Electron Notification API

* Add tests for notifications module
parent 08eb7b4c
......@@ -4,19 +4,17 @@ import url from 'url';
import './background/aboutDialog';
import appData from './background/appData';
import certificate from './background/certificate';
import dock from './background/dock';
export { default as dock } from './background/dock';
import { addServer, getMainWindow } from './background/mainWindow';
import menus from './background/menus';
import './background/notifications';
export { default as menus } from './background/menus';
export { default as notifications } from './background/notifications';
import './background/screenshareDialog';
import tray from './background/tray';
export { default as remoteServers } from './background/servers';
export { default as tray } from './background/tray';
import './background/updateDialog';
import './background/updates';
import i18n from './i18n';
export { default as remoteServers } from './background/servers';
export { certificate, dock, menus, tray };
export { certificate };
process.env.GOOGLE_API_KEY = 'AIzaSyADqUh_c1Qhji3Cp1NE43YrcpuPkmhXD-c';
......@@ -49,6 +47,7 @@ app.setAppUserModelId('chat.rocket');
if (process.platform === 'linux') {
app.disableHardwareAcceleration();
}
app.commandLine.appendSwitch('--autoplay-policy', 'no-user-gesture-required');
process.on('unhandledRejection', console.error.bind(console));
......
......@@ -121,4 +121,5 @@ ipcMain.on('focus', async() => {
}
mainWindow.show();
mainWindow.focus();
});
import { app, ipcMain, Notification } from 'electron';
import freedesktopNotifications from 'freedesktop-notifications';
import os from 'os';
import path from 'path';
import { nativeImage, Notification } from 'electron';
class BaseNotification {
constructor(options = {}) {
this.handleShow = this.handleShow.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleClose = this.handleClose.bind(this);
this.initialize(options);
}
handleShow() {
const { id, eventTarget } = this;
eventTarget && !eventTarget.isDestroyed() && eventTarget.send('notification-shown', id);
}
handleClick() {
const { id, eventTarget } = this;
eventTarget && !eventTarget.isDestroyed() && eventTarget.send('notification-clicked', id);
}
handleClose() {
const { id, eventTarget } = this;
eventTarget && !eventTarget.isDestroyed() && eventTarget.send('notification-closed', id);
}
initialize(/* options = {} */) {}
reset(/* options = {} */) {}
show() {}
close() {}
}
class ElectronNotification extends BaseNotification {
initialize({ title, body, icon, silent } = {}) {
this.notification = new Notification({
title,
body,
icon: icon && path.resolve(icon),
silent,
});
this.notification.on('show', this.handleShow);
this.notification.on('click', this.handleClick);
this.notification.on('close', this.handleClose);
}
reset(options = {}) {
this.notification.removeAllListeners();
this.notification.close();
this.createNotification(options);
}
show() {
this.notification.show();
}
close() {
this.notification.close();
}
}
class FreeDesktopNotification extends BaseNotification {
escapeBody(body) {
const escapeMap = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#x27;',
'`': '&#x60;',
};
const escapeRegex = new RegExp(`(?:${ Object.keys(escapeMap).join('|') })`, 'g');
return body.replace(escapeRegex, (match) => escapeMap[match]);
}
initialize({ title, body, icon, silent } = {}) {
this.notification = freedesktopNotifications.createNotification({
summary: title,
body: body && this.escapeBody(body),
icon: icon ? path.resolve(icon) : 'info',
appName: app.getName(),
timeout: 24 * 60 * 60 * 1000,
sound: silent ? undefined : 'message-new-instant',
actions: process.env.XDG_CURRENT_DESKTOP !== 'Unity' ? {
default: '',
} : null,
});
this.notification.on('action', (action) => action === 'default' && this.handleClick());
this.notification.on('close', this.handleClose);
}
reset({ title, body, icon } = {}) {
this.notification.set({
summary: title,
body,
icon: icon ? path.resolve(icon) : 'info',
});
}
show() {
this.notification.push(this.handleShow);
}
function create({ icon, ...options }) {
const notification = new Notification({
icon: icon && nativeImage.createFromDataURL(icon),
...options,
});
close() {
this.notification.close();
}
return notification;
}
const ImplementatedNotification = (() => {
if (os.platform() === 'linux') {
return FreeDesktopNotification;
}
return ElectronNotification;
})();
const instances = new Map();
let creationCount = 1;
const createOrGetNotification = (options = {}) => {
const tag = options.tag ? JSON.stringify(options.tag) : null;
if (!tag || !instances.get(tag)) {
const notification = new ImplementatedNotification(options);
notification.id = tag || creationCount++;
instances.set(notification.id, notification);
return notification;
}
const notification = instances.get(tag);
notification.reset(options);
return notification;
export default {
create,
};
ipcMain.on('request-notification', (event, options) => {
try {
const notification = createOrGetNotification(options);
notification.eventTarget = event.sender;
event.returnValue = notification.id;
setImmediate(() => notification.show());
} catch (e) {
console.error(e);
event.returnValue = -1;
}
});
ipcMain.on('close-notification', (event, id) => {
try {
const notification = instances.get(id);
if (notification) {
notification.close();
instances.delete(id);
}
} catch (e) {
console.error(e);
}
});
app.on('before-quit', () => {
instances.forEach((notification) => {
notification.close();
});
});
import { expect } from 'chai';
import { Notification } from 'electron';
import notifications from './notifications';
const { describe, it } = global;
describe('notifications', () => {
it('create', () => {
expect(notifications.create({})).to.be.instanceOf(Notification);
});
it('create with icon', () => {
const icon = 'data:image/png;base64,' +
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
expect(notifications.create({ icon })).to.be.instanceOf(Notification);
});
});
import { ipcRenderer, nativeImage } from 'electron';
import { ipcRenderer, remote } from 'electron';
import { EventEmitter } from 'events';
import jetpack from 'fs-jetpack';
import tmp from 'tmp';
import mem from 'mem';
const { notifications } = remote.require('./background');
const instances = new Map();
class Notification extends EventEmitter {
static requestPermission() {
return;
......@@ -17,69 +15,90 @@ class Notification extends EventEmitter {
constructor(title, options) {
super();
this.createIcon = mem(this.createIcon.bind(this));
this.create({ title, ...options });
}
async createIcon(icon) {
const img = new Image();
img.src = icon;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
return canvas.toDataURL();
}
async create({ icon, ...options }) {
if (icon) {
Notification.cachedIcons = Notification.cachedIcons || {};
if (!Notification.cachedIcons[icon]) {
Notification.cachedIcons[icon] = await new Promise((resolve, reject) =>
tmp.file((err, path) => (err ? reject(err) : resolve(path))));
const buffer = nativeImage.createFromDataURL(icon).toPNG();
await jetpack.writeAsync(Notification.cachedIcons[icon], buffer);
}
icon = Notification.cachedIcons[icon];
icon = await this.createIcon(icon);
}
this.id = ipcRenderer.sendSync('request-notification', { icon, ...options });
instances.set(this.id, this);
}
const notification = notifications.create({ icon, ...options });
close() {
ipcRenderer.send('close-notification', this.id);
}
}
notification.on('show', this.handleShow.bind(this));
notification.on('close', this.handleClose.bind(this));
notification.on('click', this.handleClick.bind(this));
notification.on('reply', this.handleReply.bind(this));
notification.on('action', this.handleAction.bind(this));
const handleNotificationShown = (event, id) => {
const notification = instances.get(id);
if (!notification) {
return;
notification.show();
this.notification = notification;
}
typeof notification.onshow === 'function' && notification.onshow.call(notification);
notification.emit('show');
};
handleShow(event) {
event.currentTarget = this;
this.onshow && this.onshow.call(this, event);
this.emit('show', event);
}
const handleNotificationClicked = (event, id) => {
const notification = instances.get(id);
if (!notification) {
return;
handleClose(event) {
event.currentTarget = this;
this.onclose && this.onclose.call(this, event);
this.emit('close', event);
}
ipcRenderer.send('focus');
ipcRenderer.sendToHost('focus');
handleClick(event) {
ipcRenderer.send('focus');
ipcRenderer.sendToHost('focus');
event.currentTarget = this;
this.onclick && this.onclick.call(this, event);
this.emit('close', event);
}
typeof notification.onclick === 'function' && notification.onclick.call(notification);
notification.emit('click');
};
handleReply(event, reply) {
event.currentTarget = this;
event.response = reply;
this.onreply && this.onreply.call(this, event);
this.emit('reply', event);
}
const handleNotificationClosed = (event, id) => {
const notification = instances.get(id);
if (!notification) {
return;
handleAction(event, index) {
event.currentTarget = this;
event.index = index;
this.onaction && this.onaction.call(this, event);
this.emit('action', event);
}
typeof notification.onclose === 'function' && notification.onclose.call(notification);
notification.emit('close');
};
close() {
if (!this.notification) {
return;
}
this.notification.close();
this.notification = null;
}
}
export default () => {
window.Notification = Notification;
ipcRenderer.on('notification-shown', handleNotificationShown);
ipcRenderer.on('notification-clicked', handleNotificationClicked);
ipcRenderer.on('notification-closed', handleNotificationClosed);
};
......@@ -441,14 +441,6 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abstract-socket@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abstract-socket/-/abstract-socket-2.0.0.tgz#d83c93e7df30d27e23f3e82a763e7f5e78d916f9"
integrity sha1-2DyT598w0n4j8+gqdj5/XnjZFvk=
dependencies:
bindings "^1.2.1"
nan "^2.0.9"
accord@^0.29.0:
version "0.29.0"
resolved "https://registry.yarnpkg.com/accord/-/accord-0.29.0.tgz#b741c176d00435c5929d466dfe8cf6bee933b1e4"
......@@ -1098,11 +1090,6 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
bindings@^1.2.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
......@@ -2003,21 +1990,6 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dbus-native@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/dbus-native/-/dbus-native-0.2.5.tgz#914056f9689e2779e621c2ab8f7d2347ea607dc3"
integrity sha512-ocxMKCV7QdiNhzhFSeEMhj258OGtvpANSb3oWGiotmI5h1ZIse0TMPcSLiXSpqvbYvQz2Y5RsYPMNYLWhg9eBw==
dependencies:
event-stream "^3.1.7"
hexy "^0.2.10"
long "^3.0.1"
optimist "^0.6.1"
put "0.0.6"
safe-buffer "^5.1.1"
xml2js "0.1.14"
optionalDependencies:
abstract-socket "^2.0.0"
debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
......@@ -2241,11 +2213,6 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
duplexer@^0.1.1, duplexer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
......@@ -2646,19 +2613,6 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
event-stream@^3.1.7:
version "3.3.5"
resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.5.tgz#e5dd8989543630d94c6cf4d657120341fa31636b"
integrity sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==
dependencies:
duplexer "^0.1.1"
from "^0.1.7"
map-stream "0.0.7"
pause-stream "^0.0.11"
split "^1.0.1"
stream-combiner "^0.2.2"
through "^2.3.8"
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
......@@ -3054,18 +3008,6 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
freedesktop-notifications@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/freedesktop-notifications/-/freedesktop-notifications-1.4.0.tgz#ec88202098974594d88168f97f604b4478931745"
integrity sha512-D4vlpBTmc4PGPdaPTbcJLEJyHl1FoxwFWsWkQ0oqfHPVMN7eWWZoYRKSWj2NKVhraq42BX9IkIX6bIv45QbYKw==
dependencies:
dbus-native "^0.2.5"
from@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
......@@ -3670,11 +3612,6 @@ he@1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hexy@^0.2.10:
version "0.2.11"
resolved "https://registry.yarnpkg.com/hexy/-/hexy-0.2.11.tgz#9939c25cb6f86a91302f22b8a8a72573518e25b4"
integrity sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A==
homedir-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
......@@ -4879,11 +4816,6 @@ lolex@^3.1.0:
resolved "https://registry.yarnpkg.com/lolex/-/lolex-3.1.0.tgz#1a7feb2fefd75b3e3a7f79f0e110d9476e294434"
integrity sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==
long@^3.0.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
......@@ -4968,11 +4900,6 @@ map-obj@^2.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
map-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8"
integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
......@@ -4995,7 +4922,7 @@ math-random@^1.0.1:
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w=
mem@^4.0.0:
mem@^4.0.0, mem@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a"
integrity sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==
......@@ -5257,7 +5184,7 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
nan@^2.0.9, nan@^2.10.0, nan@^2.9.2:
nan@^2.10.0, nan@^2.9.2:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
......@@ -5920,13 +5847,6 @@ pathval@^1.1.0:
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
pause-stream@^0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=
dependencies:
through "~2.3"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
......@@ -6177,11 +6097,6 @@ puppeteer@^1.10.0:
rimraf "^2.6.1"
ws "^6.1.0"
put@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/put/-/put-0.0.6.tgz#30f5f60bd6e4389bd329e16a25386cbb2e4a00a3"
integrity sha1-MPX2C9bkOJvTKeFqJThsuy5KAKM=
q@^1.5.1, q@~1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
......@@ -6716,7 +6631,7 @@ sanitize-filename@^1.6.1:
dependencies:
truncate-utf8-bytes "^1.0.0"
sax@>=0.1.1, sax@>=0.6.0, sax@^1.2.4:
sax@>=0.6.0, sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
......@@ -6976,7 +6891,7 @@ split2@^2.0.0:
dependencies:
through2 "^2.0.2"
split@^1.0.0, split@^1.0.1:
split@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==
......@@ -7028,14 +6943,6 @@ stream-array@^1.0.1:
dependencies:
readable-stream "~2.1.0"
stream-combiner@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858"
integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=
dependencies:
duplexer "~0.1.1"
through "~2.3.4"
stream-exhaust@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d"
......@@ -7332,7 +7239,7 @@ through2@~0.2.3:
readable-stream "~1.1.9"
xtend "~2.1.1"
through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4:
through@2, "through@>=2.2.7 <3", through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
......@@ -7946,13 +7853,6 @@ xml-parse-from-string@^1.0.0:
resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28"
integrity sha1-qQKekp09vN7RafPG4oI42VpdWig=
xml2js@0.1.14:
version "0.1.14"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=
dependencies:
sax ">=0.1.1"
xml2js@^0.4.5:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment