Compare commits

...

2 Commits

Author SHA1 Message Date
Florent Beauchamp
5299c101c2 fix: add missing service worker file 2021-10-07 17:55:20 +02:00
Florent Beauchamp
83ca34807d feat(xo-web): send notification 2021-10-07 10:27:52 +02:00
7 changed files with 214 additions and 63 deletions

View File

@@ -9,8 +9,8 @@ export default class Logs {
{ {
filter: [process.env.DEBUG, filter], filter: [process.env.DEBUG, filter],
level, level,
transport, transport
}, }
]) ])
}) })
} }

View File

@@ -123,6 +123,7 @@
"uuid": "^8.3.1", "uuid": "^8.3.1",
"value-matcher": "^0.2.0", "value-matcher": "^0.2.0",
"vhd-lib": "^1.2.0", "vhd-lib": "^1.2.0",
"web-push": "^3.4.5",
"ws": "^7.1.2", "ws": "^7.1.2",
"xdg-basedir": "^4.0.0", "xdg-basedir": "^4.0.0",
"xen-api": "^0.34.3", "xen-api": "^0.34.3",

View File

@@ -20,6 +20,7 @@ import proxyConsole from './proxy-console.mjs'
import pw from 'pw' import pw from 'pw'
import serveStatic from 'serve-static' import serveStatic from 'serve-static'
import stoppable from 'stoppable' import stoppable from 'stoppable'
import webpush from 'web-push'
import WebServer from 'http-server-plus' import WebServer from 'http-server-plus'
import WebSocket from 'ws' import WebSocket from 'ws'
import xdg from 'xdg-basedir' import xdg from 'xdg-basedir'
@@ -71,8 +72,8 @@ configure([
{ {
filter: process.env.DEBUG, filter: process.env.DEBUG,
level: 'info', level: 'info',
transport: transportConsole(), transport: transportConsole()
}, }
]) ])
const log = createLogger('xo:main') const log = createLogger('xo:main')
@@ -84,7 +85,7 @@ const DEPRECATED_ENTRIES = ['users', 'servers']
async function loadConfiguration() { async function loadConfiguration() {
const config = await appConf.load(APP_NAME, { const config = await appConf.load(APP_NAME, {
appDir: APP_DIR, appDir: APP_DIR,
ignoreUnknownFormats: true, ignoreUnknownFormats: true
}) })
log.info('Configuration loaded.') log.info('Configuration loaded.')
@@ -105,7 +106,7 @@ async function updateLocalConfig(diff) {
const localConfig = await fse.readFile(LOCAL_CONFIG_FILE).then(JSON.parse, () => ({})) const localConfig = await fse.readFile(LOCAL_CONFIG_FILE).then(JSON.parse, () => ({}))
merge(localConfig, diff) merge(localConfig, diff)
await fse.outputFile(LOCAL_CONFIG_FILE, JSON.stringify(localConfig), { await fse.outputFile(LOCAL_CONFIG_FILE, JSON.stringify(localConfig), {
mode: 0o600, mode: 0o600
}) })
} }
@@ -135,8 +136,8 @@ async function createExpressApp(config) {
saveUninitialized: false, saveUninitialized: false,
secret: sessionSecret, secret: sessionSecret,
store: new MemoryStore({ store: new MemoryStore({
checkPeriod: 24 * 3600 * 1e3, checkPeriod: 24 * 3600 * 1e3
}), })
}) })
) )
@@ -174,7 +175,7 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
res.send( res.send(
signInPage({ signInPage({
error: req.flash('error')[0], error: req.flash('error')[0],
strategies, strategies
}) })
) )
}) })
@@ -193,7 +194,7 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
signInPage({ signInPage({
error: req.flash('error')[0], error: req.flash('error')[0],
otp: true, otp: true,
strategies, strategies
}) })
) )
}) })
@@ -219,7 +220,7 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
const { user, isPersistent } = req.session const { user, isPersistent } = req.session
const token = await xo.createAuthenticationToken({ const token = await xo.createAuthenticationToken({
expiresIn: isPersistent ? PERMANENT_VALIDITY : SESSION_VALIDITY, expiresIn: isPersistent ? PERMANENT_VALIDITY : SESSION_VALIDITY,
userId: user.id, userId: user.id
}) })
res.cookie('token', token.id, { res.cookie('token', token.id, {
@@ -227,7 +228,7 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
// a session (non-permanent) cookie must not have an expiration date // a session (non-permanent) cookie must not have an expiration date
// because it must not survive browser restart // because it must not survive browser restart
...(isPersistent ? { expires: new Date(token.expiration) } : undefined), ...(isPersistent ? { expires: new Date(token.expiration) } : undefined)
}) })
delete req.session.isPersistent delete req.session.isPersistent
@@ -286,6 +287,30 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
} }
}) })
) )
// ==============================================================
const publicVapidKey = 'BDAqBcWLLjbzGSMjVqlhZmU88uiAVascwXn5mbiuMVFpsXiJixtIxVpu06pIX1b8cjXKYawsv-FuGhp9oH_1dwc'
const privateVapidKey = 'b1QTbeDFOeu0th23w9bDEpLHfkSKGvXJ3VQq50gHEcQ'
webpush.setVapidDetails('mailto:example@yourdomain.org', publicVapidKey, privateVapidKey)
// subscribe route
express.use(createExpress.json())
express.post('/service-worker-subscribe', (req, res) => {
// get push subscription object from the request
const subscription = req.body
// send status 201 for the request
res.status(201).json({})
// create paylod: specified the detals of the push notification
const payload = JSON.stringify({
title: 'Titre de ma notification from server',
body: 'Contenu de ma notification',
url: 'https://www.vates.fr'
})
// pass the object into sendNotification fucntion and catch any error
webpush.sendNotification(subscription, payload).catch(err => console.error(err))
})
} }
// =================================================================== // ===================================================================
@@ -319,14 +344,14 @@ async function registerPlugin(pluginPath, pluginName) {
getDataDir: () => { getDataDir: () => {
const dir = `${datadir}/${pluginName}` const dir = `${datadir}/${pluginName}`
return fse.ensureDir(dir).then(() => dir) return fse.ensureDir(dir).then(() => dir)
}, }
}) })
: factory : factory
;[instance, configurationSchema, configurationPresets, testSchema] = await Promise.all([ ;[instance, configurationSchema, configurationPresets, testSchema] = await Promise.all([
handleFactory(factory), handleFactory(factory),
handleFactory(configurationSchema), handleFactory(configurationSchema),
handleFactory(configurationPresets), handleFactory(configurationPresets),
handleFactory(testSchema), handleFactory(testSchema)
]) ])
await this.registerPlugin( await this.registerPlugin(
@@ -363,11 +388,11 @@ async function registerPluginsInPath(path, prefix) {
}) })
await Promise.all( await Promise.all(
files.map(name => { files
if (name.startsWith(prefix)) { .filter(name => name.startsWith(prefix))
.map(name => {
return registerPluginWrapper.call(this, `${path}/${name}`, name.slice(prefix.length)) return registerPluginWrapper.call(this, `${path}/${name}`, name.slice(prefix.length))
} })
})
) )
} }
@@ -376,7 +401,7 @@ async function registerPlugins(xo) {
[new URL('../node_modules', import.meta.url).pathname, '/usr/local/lib/node_modules'].map(path => [new URL('../node_modules', import.meta.url).pathname, '/usr/local/lib/node_modules'].map(path =>
Promise.all([ Promise.all([
registerPluginsInPath.call(xo, path, 'xo-server-'), registerPluginsInPath.call(xo, path, 'xo-server-'),
registerPluginsInPath.call(xo, `${path}/@xen-orchestra`, 'server-'), registerPluginsInPath.call(xo, `${path}/@xen-orchestra`, 'server-')
]) ])
) )
) )
@@ -418,7 +443,7 @@ async function makeWebServerListen(
const pems = await genSelfSignedCert() const pems = await genSelfSignedCert()
await Promise.all([ await Promise.all([
fse.outputFile(cert, pems.cert, { flag: 'wx', mode: 0o400 }), fse.outputFile(cert, pems.cert, { flag: 'wx', mode: 0o400 }),
fse.outputFile(key, pems.key, { flag: 'wx', mode: 0o400 }), fse.outputFile(key, pems.key, { flag: 'wx', mode: 0o400 })
]) ])
log.info('new certificate generated', { cert, key }) log.info('new certificate generated', { cert, key })
opts.cert = pems.cert opts.cert = pems.cert
@@ -464,7 +489,7 @@ const setUpProxies = (express, opts, xo) => {
.createServer({ .createServer({
changeOrigin: true, changeOrigin: true,
ignorePath: true, ignorePath: true,
xfwd: true, xfwd: true
}) })
.on('error', (error, req, res) => { .on('error', (error, req, res) => {
// `res` can be either a `ServerResponse` or a `Socket` (which does not have // `res` can be either a `ServerResponse` or a `Socket` (which does not have
@@ -478,7 +503,7 @@ const setUpProxies = (express, opts, xo) => {
const { method, url } = req const { method, url } = req
log.error('failed to proxy request', { log.error('failed to proxy request', {
error, error,
req: { method, url }, req: { method, url }
}) })
}) })
@@ -494,7 +519,7 @@ const setUpProxies = (express, opts, xo) => {
proxy.web(req, res, { proxy.web(req, res, {
agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent, agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent,
target: target + url.slice(prefix.length), target: target + url.slice(prefix.length)
}) })
return return
@@ -506,7 +531,7 @@ const setUpProxies = (express, opts, xo) => {
// WebSocket proxy. // WebSocket proxy.
const webSocketServer = new WebSocket.Server({ const webSocketServer = new WebSocket.Server({
noServer: true, noServer: true
}) })
xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close')) xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close'))
@@ -519,7 +544,7 @@ const setUpProxies = (express, opts, xo) => {
proxy.ws(req, socket, head, { proxy.ws(req, socket, head, {
agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent, agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent,
target: target + url.slice(prefix.length), target: target + url.slice(prefix.length)
}) })
return return
@@ -546,7 +571,7 @@ const setUpApi = (webServer, xo, config) => {
const webSocketServer = new WebSocket.Server({ const webSocketServer = new WebSocket.Server({
...config.apiWebSocketOptions, ...config.apiWebSocketOptions,
noServer: true, noServer: true
}) })
xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close')) xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close'))
@@ -614,7 +639,7 @@ const CONSOLE_PROXY_PATH_RE = /^\/api\/consoles\/(.*)$/
const setUpConsoleProxy = (webServer, xo) => { const setUpConsoleProxy = (webServer, xo) => {
const webSocketServer = new WebSocket.Server({ const webSocketServer = new WebSocket.Server({
noServer: true, noServer: true
}) })
xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close')) xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close'))
@@ -644,7 +669,7 @@ const setUpConsoleProxy = (webServer, xo) => {
timestamp: Date.now(), timestamp: Date.now(),
userId: user.id, userId: user.id,
userIp: remoteAddress, userIp: remoteAddress,
userName: user.name, userName: user.name
} }
if (vm.is_control_domain) { if (vm.is_control_domain) {
@@ -663,7 +688,7 @@ const setUpConsoleProxy = (webServer, xo) => {
socket.on('close', () => { socket.on('close', () => {
xo.emit('xo:audit', 'consoleClosed', { xo.emit('xo:audit', 'consoleClosed', {
...data, ...data,
timestamp: Date.now(), timestamp: Date.now()
}) })
log.info(`- Console proxy (${user.name} - ${remoteAddress})`) log.info(`- Console proxy (${user.name} - ${remoteAddress})`)
}) })
@@ -710,7 +735,7 @@ export default async function main(args) {
blocked((time, stack) => { blocked((time, stack) => {
logPerf.info(`blocked for ${ms(time)}`, { logPerf.info(`blocked for ${ms(time)}`, {
time, time,
stack, stack
}) })
}, options) }, options)
} }
@@ -742,7 +767,7 @@ export default async function main(args) {
appVersion: APP_VERSION, appVersion: APP_VERSION,
config, config,
httpServer: webServer, httpServer: webServer,
safeMode, safeMode
}) })
// Register web server close on XO stop. // Register web server close on XO stop.

View File

@@ -25,28 +25,28 @@ const gulp = require('gulp')
// =================================================================== // ===================================================================
function lazyFn(factory) { function lazyFn(factory) {
let fn = function () { let fn = function() {
fn = factory() fn = factory()
return fn.apply(this, arguments) return fn.apply(this, arguments)
} }
return function () { return function() {
return fn.apply(this, arguments) return fn.apply(this, arguments)
} }
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------
const livereload = lazyFn(function () { const livereload = lazyFn(function() {
const livereload = require('gulp-refresh') const livereload = require('gulp-refresh')
livereload.listen({ livereload.listen({
port: LIVERELOAD_PORT, port: LIVERELOAD_PORT
}) })
return livereload return livereload
}) })
const pipe = lazyFn(function () { const pipe = lazyFn(function() {
let current let current
function pipeCore(streams) { function pipeCore(streams) {
let i, n, stream let i, n, stream
@@ -63,7 +63,7 @@ const pipe = lazyFn(function () {
} }
const push = Array.prototype.push const push = Array.prototype.push
return function (streams) { return function(streams) {
try { try {
if (!(streams instanceof Array)) { if (!(streams instanceof Array)) {
streams = [] streams = []
@@ -79,7 +79,7 @@ const pipe = lazyFn(function () {
} }
}) })
const resolvePath = lazyFn(function () { const resolvePath = lazyFn(function() {
return require('path').resolve return require('path').resolve
}) })
@@ -87,7 +87,7 @@ const resolvePath = lazyFn(function () {
// Similar to `gulp.src()` but the pattern is relative to `SRC_DIR` // Similar to `gulp.src()` but the pattern is relative to `SRC_DIR`
// and files are automatically watched when not in production mode. // and files are automatically watched when not in production mode.
const src = lazyFn(function () { const src = lazyFn(function() {
function resolve(path) { function resolve(path) {
return path ? resolvePath(SRC_DIR, path) : SRC_DIR return path ? resolvePath(SRC_DIR, path) : SRC_DIR
} }
@@ -100,7 +100,7 @@ const src = lazyFn(function () {
base: base, base: base,
cwd: base, cwd: base,
passthrough: opts && opts.passthrough, passthrough: opts && opts.passthrough,
sourcemaps: opts && opts.sourcemaps, sourcemaps: opts && opts.sourcemaps
}) })
} }
: function src(pattern, opts) { : function src(pattern, opts) {
@@ -111,11 +111,11 @@ const src = lazyFn(function () {
base: base, base: base,
cwd: base, cwd: base,
passthrough: opts && opts.passthrough, passthrough: opts && opts.passthrough,
sourcemaps: opts && opts.sourcemaps, sourcemaps: opts && opts.sourcemaps
}), }),
require('gulp-watch')(pattern, { require('gulp-watch')(pattern, {
base: base, base: base,
cwd: base, cwd: base
}), }),
require('gulp-plumber')() require('gulp-plumber')()
) )
@@ -125,13 +125,13 @@ const src = lazyFn(function () {
// Similar to `gulp.dest()` but the output directory is relative to // Similar to `gulp.dest()` but the output directory is relative to
// `DIST_DIR` and default to `./`, and files are automatically live- // `DIST_DIR` and default to `./`, and files are automatically live-
// reloaded when not in production mode. // reloaded when not in production mode.
const dest = lazyFn(function () { const dest = lazyFn(function() {
function resolve(path) { function resolve(path) {
return path ? resolvePath(DIST_DIR, path) : DIST_DIR return path ? resolvePath(DIST_DIR, path) : DIST_DIR
} }
const opts = { const opts = {
sourcemaps: '.', sourcemaps: '.'
} }
return PRODUCTION return PRODUCTION
@@ -162,7 +162,7 @@ function browserify(path, opts) {
// Required by Watchify. // Required by Watchify.
cache: {}, cache: {},
packageCache: {}, packageCache: {}
}) })
const plugins = opts.plugins const plugins = opts.plugins
@@ -178,7 +178,7 @@ function browserify(path, opts) {
bundler = require('watchify')(bundler, { bundler = require('watchify')(bundler, {
// do not watch in `node_modules` // do not watch in `node_modules`
// https://github.com/browserify/watchify#options // https://github.com/browserify/watchify#options
ignoreWatch: true, ignoreWatch: true
}) })
} }
@@ -189,7 +189,7 @@ function browserify(path, opts) {
path = resolvePath(SRC_DIR, path) path = resolvePath(SRC_DIR, path)
let stream = new (require('readable-stream'))({ let stream = new (require('readable-stream'))({
objectMode: true, objectMode: true
}) })
let write let write
@@ -204,28 +204,28 @@ function browserify(path, opts) {
new (require('vinyl'))({ new (require('vinyl'))({
base: SRC_DIR, base: SRC_DIR,
contents: buffer, contents: buffer,
path: path, path: path
}) })
) )
}) })
} }
if (PRODUCTION) { if (PRODUCTION) {
write = function (data) { write = function(data) {
stream.push(data) stream.push(data)
stream.push(null) stream.push(null)
} }
} else { } else {
stream = require('gulp-plumber')().pipe(stream) stream = require('gulp-plumber')().pipe(stream)
write = function (data) { write = function(data) {
stream.push(data) stream.push(data)
} }
bundler.on('update', bundle) bundler.on('update', bundle)
} }
stream._read = function () { stream._read = function() {
this._read = function () {} this._read = function() {}
bundle() bundle()
} }
@@ -240,7 +240,7 @@ gulp.task(function buildPages() {
require('gulp-pug')(), require('gulp-pug')(),
DEVELOPMENT && DEVELOPMENT &&
require('gulp-embedlr')({ require('gulp-embedlr')({
port: LIVERELOAD_PORT, port: LIVERELOAD_PORT
}), }),
dest() dest()
) )
@@ -255,10 +255,10 @@ gulp.task(function buildScripts() {
'modular-cssify', 'modular-cssify',
{ {
css: DIST_DIR + '/modules.css', css: DIST_DIR + '/modules.css',
from: undefined, from: undefined
}, }
], ]
], ]
}), }),
require('gulp-sourcemaps').init({ loadMaps: true }), require('gulp-sourcemaps').init({ loadMaps: true }),
PRODUCTION && require('gulp-terser')(), PRODUCTION && require('gulp-terser')(),
@@ -275,18 +275,18 @@ gulp.task(function buildStyles() {
dest() dest()
) )
}) })
gulp.task(function copyAssets() { gulp.task(function copyAssets() {
return pipe( return pipe(
src(['assets/**/*', 'favicon.*']), src(['assets/**/*', 'favicon.*']),
src('fontawesome-webfont.*', { src('fontawesome-webfont.*', {
base: __dirname + '/../../node_modules/font-awesome/fonts', // eslint-disable-line no-path-concat base: path.join(__dirname, '/../../node_modules/font-awesome/fonts'), // eslint-disable-line no-path-concat
passthrough: true, passthrough: true
}), }),
src(['!*.css', 'font-mfizz.*'], { src(['!*.css', 'font-mfizz.*'], {
base: __dirname + '/../../node_modules/font-mfizz/dist', // eslint-disable-line no-path-concat base: __dirname + '/../../node_modules/font-mfizz/dist', // eslint-disable-line no-path-concat
passthrough: true, passthrough: true
}), }),
src(['serviceworker.js']),
dest() dest()
) )
}) })

View File

@@ -5,6 +5,7 @@ import React, { Component } from 'react'
import ReactNotify from 'react-notify' import ReactNotify from 'react-notify'
import { connectStore } from 'utils' import { connectStore } from 'utils'
import { isAdmin } from 'selectors' import { isAdmin } from 'selectors'
import fetch from './fetch'
let instance let instance
@@ -12,8 +13,47 @@ export let error
export let info export let info
export let success export let success
const publicVapidKey = 'BDAqBcWLLjbzGSMjVqlhZmU88uiAVascwXn5mbiuMVFpsXiJixtIxVpu06pIX1b8cjXKYawsv-FuGhp9oH_1dwc'
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
// register the service worker, register our push api, send the notification
async function registerNotificationServiceWorker() {
// register service worker
const register = await navigator.serviceWorker.register('/serviceworker.js', {
scope: '/'
})
// register push
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
// public vapid key
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
})
// Send push notification
await fetch('/service-worker-subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'content-type': 'application/json'
}
})
}
@connectStore({ @connectStore({
isAdmin, isAdmin
}) })
export class Notification extends Component { export class Notification extends Component {
componentDidMount() { componentDidMount() {
@@ -21,6 +61,11 @@ export class Notification extends Component {
throw new Error('Notification is a singleton!') throw new Error('Notification is a singleton!')
} }
instance = this instance = this
// check if the serveice worker can work in the current browser
if ('serviceWorker' in navigator) {
registerNotificationServiceWorker()
}
} }
componentWillUnmount() { componentWillUnmount() {

View File

@@ -0,0 +1,27 @@
const version = 7;
self.addEventListener('install', () => {
console.log(`Installation du service worker v${version}`);
return self.skipWaiting();
});
self.addEventListener('activate', () => console.log(`Activation du service worker v${version}`));
self.addEventListener('push', event => {
const dataJSON = event.data.json();
console.log(dataJSON)
const notificationOptions = {
body: dataJSON.body,
data: {
url: dataJSON.url,
}
};
return self.registration.showNotification(dataJSON.title, notificationOptions);
});
self.addEventListener('notificationclick', event => {
const url = event.notification.data.url;
event.notification.close();
event.waitUntil(clients.openWindow(url));
});

View File

@@ -2680,7 +2680,7 @@ asap@^2.0.6, asap@~2.0.3:
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
asn1.js@^5.0.0, asn1.js@^5.2.0: asn1.js@^5.0.0, asn1.js@^5.2.0, asn1.js@^5.3.0:
version "5.4.1" version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
@@ -3474,6 +3474,11 @@ buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-equal@^1.0.0: buffer-equal@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
@@ -5808,6 +5813,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0" jsbn "~0.1.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -8310,6 +8322,13 @@ http-signature@~1.2.0:
jsprim "^1.2.2" jsprim "^1.2.2"
sshpk "^1.7.0" sshpk "^1.7.0"
http_ece@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.1.0.tgz#74780c6eb32d8ddfe9e36a83abcd81fe0cd4fb75"
integrity sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==
dependencies:
urlsafe-base64 "~1.0.0"
https-browserify@^1.0.0: https-browserify@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@@ -9900,6 +9919,23 @@ just-reduce-object@^1.0.3:
resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6"
integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw== integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw==
jwa@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
keycode@^2.1.0: keycode@^2.1.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
@@ -16513,6 +16549,11 @@ url@^0.11.0, url@~0.11.0:
punycode "1.3.2" punycode "1.3.2"
querystring "0.2.0" querystring "0.2.0"
urlsafe-base64@^1.0.0, urlsafe-base64@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz#23f89069a6c62f46cf3a1d3b00169cefb90be0c6"
integrity sha1-I/iQaabGL0bPOh07ABac77kL4MY=
use@^3.1.0: use@^3.1.0:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -16938,6 +16979,18 @@ wcwidth@^1.0.1:
dependencies: dependencies:
defaults "^1.0.3" defaults "^1.0.3"
web-push@^3.4.5:
version "3.4.5"
resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.4.5.tgz#f94074ff150538872c7183e4d8881c8305920cf1"
integrity sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g==
dependencies:
asn1.js "^5.3.0"
http_ece "1.1.0"
https-proxy-agent "^5.0.0"
jws "^4.0.0"
minimist "^1.2.5"
urlsafe-base64 "^1.0.0"
webidl-conversions@^5.0.0: webidl-conversions@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"