1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00
next.js/packages/next/client/on-demand-entries-client.js
JJ Kasper 5d779a0289 Add falling back to fetch based pinging for onDemandEntries (#6310)
After discussion, I added falling back to fetch based pinging when the WebSocket fails to connect. I also added an example of how to proxy the onDemandEntries WebSocket when using a custom server. Fixes: #6296
2019-02-15 22:22:21 +01:00

128 lines
3.6 KiB
JavaScript

/* global location, WebSocket */
import Router from 'next/router'
import fetch from 'unfetch'
const { hostname, protocol } = location
const wsProtocol = protocol.includes('https') ? 'wss' : 'ws'
const retryTime = 5000
let ws = null
let lastHref = null
let wsConnectTries = 0
let showedWarning = false
export default async ({ assetPrefix }) => {
Router.ready(() => {
Router.events.on('routeChangeComplete', ping)
})
const setup = async () => {
if (ws && ws.readyState === ws.OPEN) {
return Promise.resolve()
} else if (wsConnectTries > 1) {
return
}
wsConnectTries++
return new Promise(resolve => {
ws = new WebSocket(`${wsProtocol}://${hostname}:${process.env.__NEXT_WS_PORT}${process.env.__NEXT_WS_PROXY_PATH}`)
ws.onopen = () => {
wsConnectTries = 0
resolve()
}
ws.onclose = () => {
setTimeout(async () => {
await fetch(`${assetPrefix}/_next/on-demand-entries-ping`)
.then(res => {
// Only reload if next was restarted and we have a new WebSocket port
if (res.status === 200 && res.headers.get('port') !== process.env.__NEXT_WS_PORT + '') {
location.reload()
}
})
.catch(() => {})
await setup(true)
resolve()
}, retryTime)
}
ws.onmessage = async ({ data }) => {
const payload = JSON.parse(data)
if (payload.invalid && lastHref !== location.href) {
// Payload can be invalid even if the page does not exist.
// So, we need to make sure it exists before reloading.
const pageRes = await fetch(location.href, {
credentials: 'omit'
})
if (pageRes.status === 200) {
location.reload()
} else {
lastHref = location.href
}
}
}
})
}
setup()
async function ping () {
// Use WebSocket if available
if (ws && ws.readyState === ws.OPEN) {
return ws.send(Router.pathname)
}
if (!showedWarning) {
console.warn('onDemandEntries WebSocket failed to connect, falling back to fetch based pinging. https://err.sh/zeit/next.js/on-demand-entries-websocket-unavailable')
showedWarning = true
}
// If not, fallback to fetch based pinging
try {
const url = `${assetPrefix || ''}/_next/on-demand-entries-ping?page=${Router.pathname}`
const res = await fetch(url, {
credentials: 'same-origin'
})
const payload = await res.json()
if (payload.invalid) {
// Payload can be invalid even if the page does not exist.
// So, we need to make sure it exists before reloading.
const pageRes = await fetch(location.href, {
credentials: 'same-origin'
})
if (pageRes.status === 200) {
location.reload()
}
}
} catch (err) {
console.error(`Error with on-demand-entries-ping: ${err.message}`)
}
}
let pingerTimeout
async function runPinger () {
// Will restart on the visibilitychange API below. For older browsers, this
// will always be true and will always run, but support is fairly prevalent
// at this point.
while (!document.hidden) {
await ping()
await new Promise(resolve => {
pingerTimeout = setTimeout(resolve, 5000)
})
}
}
document.addEventListener(
'visibilitychange',
() => {
if (!document.hidden) {
runPinger()
} else {
clearTimeout(pingerTimeout)
}
},
false
)
setTimeout(() => {
runPinger().catch(err => {
console.error(err)
})
}, 10000)
}