const { resolve } = require('path')
|
const { readFileSync } = require('fs')
|
const connect = require('connect')
|
const serveStatic = require('serve-static')
|
const getPort = require('get-port-please')
|
const { json, end, header } = require('node-res')
|
|
const { parseStack } = require('./utils')
|
const SSE = require('./sse')
|
|
class LoadingUI {
|
constructor (options) {
|
this.options = options
|
|
this._lastBroadcast = 0
|
|
this.states = []
|
this.allDone = true
|
this.hasErrors = false
|
|
this.serveIndex = this.serveIndex.bind(this)
|
|
this._init()
|
}
|
|
_init () {
|
// Create a connect middleware stack
|
this.app = connect()
|
|
// Create an SSE handler instance
|
this.sse = new SSE()
|
|
// Fix CORS
|
this.app.use((req, res, next) => {
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
next()
|
})
|
|
// Subscribe to SSR channel
|
this.app.use('/sse', (req, res) => this.sse.subscribe(req, res))
|
|
// Serve state with JSON
|
this.app.use('/json', (req, res) => json(req, res, this.state))
|
|
// Load indexTemplate
|
const distPath = resolve(__dirname, '../app-dist')
|
this.indexTemplate = readFileSync(resolve(distPath, 'index.html'), 'utf-8')
|
|
// Serve assets
|
this.app.use('/assets', serveStatic(resolve(distPath, 'assets')))
|
}
|
|
async initAlt ({ url }) {
|
if (this._server || this.options.baseURLAlt) {
|
return
|
}
|
|
// Redirect users directly open this port
|
this.app.use('/', (req, res) => {
|
res.setHeader('Location', url)
|
res.statusCode = 307
|
res.end(url)
|
})
|
|
// Start listening on alternative port
|
const port = await getPort({ random: true, name: 'nuxt_loading' })
|
|
return new Promise((resolve, reject) => {
|
this._server = this.app.listen(port, (err) => {
|
if (err) { return reject(err) }
|
this.options.baseURLAlt = `http://localhost:${port}`
|
resolve()
|
})
|
})
|
}
|
|
close () {
|
if (this._server) {
|
return new Promise((resolve, reject) => {
|
this._server.close((err) => {
|
if (err) {
|
return reject(err)
|
}
|
resolve()
|
})
|
})
|
}
|
}
|
|
get state () {
|
return {
|
error: this.error,
|
states: this.states,
|
allDone: this.allDone,
|
hasErrors: this.hasErrors
|
}
|
}
|
|
setStates (states) {
|
this.clearError()
|
this.states = states
|
this.allDone = this.states.every(state => state.progress === 0 || state.progress === 100)
|
this.hasErrors = this.states.some(state => state.hasErrors === true)
|
this.broadcastState()
|
}
|
|
setError (error) {
|
this.clearStates(true)
|
this.error = {
|
description: error.toString(),
|
stack: parseStack(error.stack).join('\n')
|
}
|
this.broadcastState()
|
}
|
|
clearError () {
|
this.error = undefined
|
}
|
|
clearStates (hasErrors) {
|
this.states = []
|
this.allDone = false
|
this.hasErrors = !!hasErrors
|
}
|
|
broadcastState () {
|
const now = new Date()
|
|
if ((now - this._lastBroadcast > 500) || this.allDone || this.hasErrors) {
|
this.sse.broadcast('state', this.state)
|
this._lastBroadcast = now
|
}
|
}
|
|
serveIndex (req, res) {
|
const html = this.indexTemplate
|
.replace('__STATE__', JSON.stringify(this.state))
|
.replace('__OPTIONS__', JSON.stringify(this.options))
|
.replace(/__BASE_URL__/g, this.options.baseURL)
|
|
header(res, 'Content-Type', 'text/html')
|
end(res, html)
|
}
|
}
|
|
module.exports = LoadingUI
|