Skip to content

Commit bf573f5

Browse files
committed
Prevent navigation on HTTP exception
1 parent 6bbde9c commit bf573f5

File tree

9 files changed

+258
-0
lines changed

9 files changed

+258
-0
lines changed

packages/core/src/response.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ export class Response {
6565
return this.handleNonInertiaResponse()
6666
}
6767

68+
if (this.isHttpException()) {
69+
const response = {
70+
...this.response,
71+
data: this.getDataFromResponse(this.response.data),
72+
}
73+
74+
if (this.requestParams.all().onHttpException(response) === false) {
75+
return
76+
}
77+
78+
if (!fireHttpExceptionEvent(response)) {
79+
return
80+
}
81+
}
82+
6883
await history.processQueue()
6984

7085
history.preserveUrl = this.requestParams.all().preserveUrl
@@ -157,6 +172,10 @@ export class Response {
157172
return this.hasHeader('x-inertia')
158173
}
159174

175+
protected isHttpException(): boolean {
176+
return this.response.status >= 400
177+
}
178+
160179
protected hasStatus(status: number): boolean {
161180
return this.response.status === status
162181
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default ({ status }: { status: number }) => {
2+
return (
3+
<div>
4+
<h1>Error Page</h1>
5+
<p id="status">{status}</p>
6+
</div>
7+
)
8+
}

packages/react/test-app/Pages/Events.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,52 @@ export default () => {
378378
)
379379
}
380380

381+
const httpExceptionInertiaResponseVisit = (e: React.MouseEvent) => {
382+
e.preventDefault()
383+
router.on('httpException', (event) => {
384+
internalAlert('Inertia.on(httpException)')
385+
internalAlert(event)
386+
})
387+
388+
document.addEventListener('inertia:httpException', (event) => {
389+
internalAlert('addEventListener(inertia:httpException)')
390+
internalAlert(event)
391+
})
392+
393+
router.get(
394+
'/inertia-error-page',
395+
{},
396+
{
397+
onHttpException: () => internalAlert('onHttpException'),
398+
},
399+
)
400+
}
401+
402+
const httpExceptionInertiaResponsePreventVisit = (e: React.MouseEvent) => {
403+
e.preventDefault()
404+
router.on('httpException', (event) => {
405+
internalAlert('Inertia.on(httpException)')
406+
internalAlert(event)
407+
})
408+
409+
document.addEventListener('inertia:httpException', (event) => {
410+
internalAlert('addEventListener(inertia:httpException)')
411+
internalAlert(event)
412+
})
413+
414+
router.get(
415+
'/inertia-error-page',
416+
{},
417+
{
418+
onHttpException: (response) => {
419+
internalAlert('onHttpException')
420+
internalAlert(response)
421+
return false
422+
},
423+
},
424+
)
425+
}
426+
381427
const networkErrorVisit = (e: React.MouseEvent) => {
382428
e.preventDefault()
383429
router.on('networkError', (event) => {
@@ -712,6 +758,12 @@ export default () => {
712758
<a href="#" onClick={httpExceptionPreventVisit} className="http-exception-prevent">
713759
HTTP Exception Event (Prevent)
714760
</a>
761+
<a href="#" onClick={httpExceptionInertiaResponseVisit} className="http-exception-inertia-response">
762+
HTTP Exception Event (Inertia Response)
763+
</a>
764+
<a href="#" onClick={httpExceptionInertiaResponsePreventVisit} className="http-exception-inertia-response-prevent">
765+
HTTP Exception Event (Inertia Response Prevent)
766+
</a>
715767

716768
{/* Events: Network Error */}
717769
<a href="#" onClick={networkErrorVisit} className="network-error">
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
let { status } = $props()
3+
</script>
4+
5+
<div>
6+
<h1>Error Page</h1>
7+
<p id="status">{status}</p>
8+
</div>

packages/svelte/test-app/Pages/Events.svelte

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,54 @@
397397
)
398398
}
399399
400+
const httpExceptionInertiaResponseVisit = (e: Event) => {
401+
e.preventDefault()
402+
403+
router.on('httpException', (event) => {
404+
internalAlert('Inertia.on(httpException)')
405+
internalAlert(event)
406+
})
407+
408+
document.addEventListener('inertia:httpException', (event) => {
409+
internalAlert('addEventListener(inertia:httpException)')
410+
internalAlert(event)
411+
})
412+
413+
router.get(
414+
'/inertia-error-page',
415+
{},
416+
{
417+
onHttpException: () => internalAlert('onHttpException'),
418+
},
419+
)
420+
}
421+
422+
const httpExceptionInertiaResponsePreventVisit = (e: Event) => {
423+
e.preventDefault()
424+
425+
router.on('httpException', (event) => {
426+
internalAlert('Inertia.on(httpException)')
427+
internalAlert(event)
428+
})
429+
430+
document.addEventListener('inertia:httpException', (event) => {
431+
internalAlert('addEventListener(inertia:httpException)')
432+
internalAlert(event)
433+
})
434+
435+
router.get(
436+
'/inertia-error-page',
437+
{},
438+
{
439+
onHttpException: (response) => {
440+
internalAlert('onHttpException')
441+
internalAlert(response)
442+
return false
443+
},
444+
},
445+
)
446+
}
447+
400448
const networkErrorVisit = (e: Event) => {
401449
e.preventDefault()
402450
@@ -690,6 +738,12 @@
690738
<!-- Events: HTTP Exception -->
691739
<a href={'#'} onclick={httpExceptionVisit} class="http-exception">HTTP Exception Event</a>
692740
<a href={'#'} onclick={httpExceptionPreventVisit} class="http-exception-prevent">HTTP Exception Event (Prevent)</a>
741+
<a href={'#'} onclick={httpExceptionInertiaResponseVisit} class="http-exception-inertia-response"
742+
>HTTP Exception Event (Inertia Response)</a
743+
>
744+
<a href={'#'} onclick={httpExceptionInertiaResponsePreventVisit} class="http-exception-inertia-response-prevent"
745+
>HTTP Exception Event (Inertia Response Prevent)</a
746+
>
693747

694748
<!-- Events: Network Error -->
695749
<a href={'#'} onclick={networkErrorVisit} class="network-error">Network Error Event</a>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
defineProps({
3+
status: Number,
4+
})
5+
</script>
6+
7+
<template>
8+
<div>
9+
<h1>Error Page</h1>
10+
<p id="status">{{ status }}</p>
11+
</div>
12+
</template>

packages/vue3/test-app/Pages/Events.vue

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,50 @@ const httpExceptionPreventVisit = () => {
360360
)
361361
}
362362
363+
const httpExceptionInertiaResponseVisit = () => {
364+
router.on('httpException', (event) => {
365+
internalAlert('Inertia.on(httpException)')
366+
internalAlert(event)
367+
})
368+
369+
document.addEventListener('inertia:httpException', (event) => {
370+
internalAlert('addEventListener(inertia:httpException)')
371+
internalAlert(event)
372+
})
373+
374+
router.get(
375+
'/inertia-error-page',
376+
{},
377+
{
378+
onHttpException: () => internalAlert('onHttpException'),
379+
},
380+
)
381+
}
382+
383+
const httpExceptionInertiaResponsePreventVisit = () => {
384+
router.on('httpException', (event) => {
385+
internalAlert('Inertia.on(httpException)')
386+
internalAlert(event)
387+
})
388+
389+
document.addEventListener('inertia:httpException', (event) => {
390+
internalAlert('addEventListener(inertia:httpException)')
391+
internalAlert(event)
392+
})
393+
394+
router.get(
395+
'/inertia-error-page',
396+
{},
397+
{
398+
onHttpException: (response) => {
399+
internalAlert('onHttpException')
400+
internalAlert(response)
401+
return false
402+
},
403+
},
404+
)
405+
}
406+
363407
const networkErrorVisit = () => {
364408
router.on('networkError', (event) => {
365409
internalAlert('Inertia.on(networkError)')
@@ -650,6 +694,12 @@ const callbackSuccessErrorPromise = (eventName: string) => {
650694
<a href="#" @click.prevent="httpExceptionPreventVisit" class="http-exception-prevent"
651695
>HTTP Exception Event (Prevent)</a
652696
>
697+
<a href="#" @click.prevent="httpExceptionInertiaResponseVisit" class="http-exception-inertia-response"
698+
>HTTP Exception Event (Inertia Response)</a
699+
>
700+
<a href="#" @click.prevent="httpExceptionInertiaResponsePreventVisit" class="http-exception-inertia-response-prevent"
701+
>HTTP Exception Event (Inertia Response Prevent)</a
702+
>
653703

654704
<!-- Events: Network Error -->
655705
<a href="#" @click.prevent="networkErrorVisit" class="network-error">Network Error Event</a>

tests/app/server.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,28 @@ app.get('/location', ({ res }) => inertia.location(res, '/dump/get'))
18201820
app.post('/redirect-external', (req, res) => inertia.location(res, '/non-inertia'))
18211821
app.post('/disconnect', (req, res) => res.socket.destroy())
18221822
app.post('/json', (req, res) => res.status(200).json({ foo: 'bar' }))
1823+
app.get('/inertia-error-page', (req, res) => {
1824+
const data = {
1825+
component: 'ErrorPage',
1826+
props: { status: 500 },
1827+
url: req.originalUrl,
1828+
version: null,
1829+
}
1830+
1831+
if (req.get('X-Inertia')) {
1832+
res.header('Vary', 'Accept')
1833+
res.header('X-Inertia', true)
1834+
return res.status(500).json(data)
1835+
}
1836+
1837+
return res.status(200).send(
1838+
require('fs')
1839+
.readFileSync(require('path').resolve(__dirname, '../../packages/', inertia.package, 'test-app/dist/index.html'))
1840+
.toString()
1841+
.replace('{{ headAttribute }}', 'data-inertia')
1842+
.replace("'{{ placeholder }}'", JSON.stringify(data)),
1843+
)
1844+
})
18231845

18241846
app.get('/form-component/child-component', (req, res) =>
18251847
inertia.render(req, res, { component: 'FormComponent/ChildComponent' }),

tests/events.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,39 @@ test.describe('Events', () => {
485485
})
486486
})
487487

488+
test.describe('httpException with Inertia error page response', () => {
489+
test('it fires when the server returns a valid Inertia response with an error status code', async ({ page }) => {
490+
await listenForGlobalMessages(page, 'inertia:httpException')
491+
await clickAndWaitForResponse(page, 'HTTP Exception Event (Inertia Response)', 'inertia-error-page')
492+
493+
const messages = await waitForMessages(page, 5)
494+
const globalMessages = await waitForGlobalMessages(page, 'inertia:httpException', 1)
495+
496+
await assertIsGlobalEvent(globalMessages[0], 'inertia:httpException', true)
497+
498+
await expect(messages[0]).toBe('onHttpException')
499+
await expect(messages[1]).toBe('Inertia.on(httpException)')
500+
await expect(messages[3]).toBe('addEventListener(inertia:httpException)')
501+
502+
await expect(page.locator('#status')).toContainText('500')
503+
})
504+
505+
test('it suppresses the error page navigation when the visit callback returns false', async ({ page }) => {
506+
await listenForGlobalMessages(page, 'inertia:httpException')
507+
await clickAndWaitForResponse(page, 'HTTP Exception Event (Inertia Response Prevent)', 'inertia-error-page')
508+
509+
const messages = await waitForMessages(page, 2)
510+
const globalMessages = await waitForGlobalMessages(page, 'inertia:httpException')
511+
512+
await expect(messages[0]).toBe('onHttpException')
513+
await assertResponseObject(messages[1])
514+
515+
await expect(globalMessages).toHaveLength(0)
516+
517+
await expect(page.locator('#status')).not.toBeVisible()
518+
})
519+
})
520+
488521
test.describe('networkError', () => {
489522
test('gets fired when an unexpected situation occurs (e.g. network disconnect)', async ({ page }) => {
490523
await listenForGlobalMessages(page, 'inertia:networkError', true)

0 commit comments

Comments
 (0)