Compare commits
2 Commits
improveFor
...
florent-we
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5299c101c2 | ||
|
|
83ca34807d |
@@ -9,8 +9,8 @@ export default class Logs {
|
|||||||
{
|
{
|
||||||
filter: [process.env.DEBUG, filter],
|
filter: [process.env.DEBUG, filter],
|
||||||
level,
|
level,
|
||||||
transport,
|
transport
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
27
packages/xo-web/src/serviceworker.js
Normal file
27
packages/xo-web/src/serviceworker.js
Normal 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));
|
||||||
|
});
|
||||||
55
yarn.lock
55
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user