diff --git a/e2e/custom.test.js b/e2e/custom.test.js
index e5b9c3b..da1c781 100644
--- a/e2e/custom.test.js
+++ b/e2e/custom.test.js
@@ -45,3 +45,37 @@ test.describe('Check customizations', () => {
expect(logoId).toBe('example-logo') // it is included in the svg file
})
})
+
+test.describe('Check redirection and url handling of static assets', () => {
+ test('Check static/index.html redirects', async ({ page }) => {
+ const jsonResponsePromise = page.waitForResponse(/json/)
+ await page.goto(`${URL_DOCUMENTATION}/static/index.html`)
+
+ // Check if the page is redirected to /documentation
+ const url = await page.url()
+ expect(url).toContain(`${URL_DOCUMENTATION}`)
+ expect(url).not.toContain('static/index.html')
+
+ // Check if the page has requested the json spec, and if so has it succeeded
+ const jsonResponse = await jsonResponsePromise
+ expect(jsonResponse.ok()).toBe(true)
+ })
+
+ test('Check root UI without slash loads json spec', async ({ page }) => {
+ const jsonResponsePromise = page.waitForResponse(/json/)
+ await page.goto(`${URL_DOCUMENTATION}`)
+
+ // Check if the page has requested the json spec, and if so has it succeeded
+ const jsonResponse = await jsonResponsePromise
+ expect(jsonResponse.ok()).toBe(true)
+ })
+
+ test('Check root UI with trailing slash loads json spec', async ({ page }) => {
+ const jsonResponsePromise = page.waitForResponse(/json/)
+ await page.goto(`${URL_DOCUMENTATION}/`)
+
+ // Check if the page has requested the json spec, and if so has it succeeded
+ const jsonResponse = await jsonResponsePromise
+ expect(jsonResponse.ok()).toBe(true)
+ })
+})
diff --git a/lib/index-html.js b/lib/index-html.js
index 518c4a6..2e83015 100644
--- a/lib/index-html.js
+++ b/lib/index-html.js
@@ -1,29 +1,29 @@
'use strict'
function indexHtml (opts) {
- return `
+ return (url) => `
${opts.theme?.title || 'Swagger UI'}
-
-
- ${opts.theme && opts.theme.css ? opts.theme.css.map(css => `\n`).join('') : ''}
+
+
+ ${opts.theme && opts.theme.css ? opts.theme.css.map(css => `\n`).join('') : ''}
${opts.theme && opts.theme.favicon
-? opts.theme.favicon.map(favicon => `\n`).join('')
+? opts.theme.favicon.map(favicon => `\n`).join('')
: `
-
-
+
+
`}
-
-
-
- ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''}
+
+
+
+ ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''}
`
diff --git a/lib/routes.js b/lib/routes.js
index a179ff8..bb6e4aa 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -10,19 +10,6 @@ const indexHtml = require('./index-html')
// URI prefix to separate static assets for swagger UI
const staticPrefix = '/static'
-function getRedirectPathForTheRootRoute (url) {
- let redirectPath
-
- if (url.length !== 0 && url[url.length - 1] === '/') {
- redirectPath = `.${staticPrefix}/index.html`
- } else {
- const urlPathParts = url.split('/')
- redirectPath = `./${urlPathParts[urlPathParts.length - 1]}${staticPrefix}/index.html`
- }
-
- return redirectPath
-}
-
function fastifySwagger (fastify, opts, done) {
let staticCSP = false
if (opts.staticCSP === true) {
@@ -66,16 +53,6 @@ function fastifySwagger (fastify, opts, done) {
}
}
- fastify.route({
- url: '/',
- method: 'GET',
- schema: { hide: true },
- ...hooks,
- handler: (req, reply) => {
- reply.redirect(getRedirectPathForTheRootRoute(req.raw.url))
- }
- })
-
if (opts.theme) {
const themePrefix = `${staticPrefix}/theme`
if (opts.theme.css) {
@@ -127,17 +104,27 @@ function fastifySwagger (fastify, opts, done) {
}
}
- const indexHtmlContent = indexHtml(opts)
+ const indexHtmlContent = indexHtml({ ...opts, staticPrefix })
fastify.route({
- url: `${staticPrefix}/index.html`,
+ url: '/',
method: 'GET',
schema: { hide: true },
...hooks,
handler: (req, reply) => {
reply
.header('content-type', 'text/html; charset=utf-8')
- .send(indexHtmlContent)
+ .send(indexHtmlContent(req.url.replace(/\/$/, ''))) // remove trailing slash, as staticPrefix has a leading slash
+ }
+ })
+
+ fastify.route({
+ url: `${staticPrefix}/index.html`,
+ method: 'GET',
+ schema: { hide: true },
+ ...hooks,
+ handler: (req, reply) => {
+ reply.redirect(req.url.replace(/\/static\/index\.html$/, '/'))
}
})
diff --git a/lib/swagger-initializer.js b/lib/swagger-initializer.js
index e8e9372..40e8165 100644
--- a/lib/swagger-initializer.js
+++ b/lib/swagger-initializer.js
@@ -28,8 +28,10 @@ function swaggerInitializer (opts) {
});
}
function resolveUrl(url) {
- const anchor = document.createElement('a')
- anchor.href = url
+ var currentHref = window.location.href;
+ currentHref = currentHref.endsWith('/') ? currentHref : currentHref + '/';
+ var anchor = document.createElement('a');
+ anchor.href = currentHref + url;
return anchor.href
}
@@ -47,8 +49,8 @@ function swaggerInitializer (opts) {
layout: "StandaloneLayout",
validatorUrl: ${serialize(opts.validatorUrl || null)},
}, config, {
- url: resolveUrl('./json').replace('static/json', 'json'),
- oauth2RedirectUrl: resolveUrl('./oauth2-redirect.html')
+ url: resolveUrl('./json'),
+ oauth2RedirectUrl: resolveUrl('./static/oauth2-redirect.html')
});
const ui = SwaggerUIBundle(resConfig)
diff --git a/test/csp.test.js b/test/csp.test.js
index 6d59260..af3d650 100644
--- a/test/csp.test.js
+++ b/test/csp.test.js
@@ -31,7 +31,7 @@ test('staticCSP = undefined', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(typeof res.headers['content-security-policy'], 'undefined')
@@ -57,7 +57,7 @@ test('staticCSP = true', async (t) => {
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(res.headers['content-security-policy'], `default-src 'self'; base-uri 'self'; font-src 'self' https: data:; frame-ancestors 'self'; img-src 'self' data: validator.swagger.io; object-src 'none'; script-src 'self' ${csp.script.join(' ')}; script-src-attr 'none'; style-src 'self' https: ${csp.style.join(' ')}; upgrade-insecure-requests;`)
@@ -93,7 +93,7 @@ test('staticCSP = "default-src \'self\';"', async (t) => {
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(res.headers['content-security-policy'], "default-src 'self';")
@@ -132,7 +132,7 @@ test('staticCSP = object', async (t) => {
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';")
@@ -172,7 +172,7 @@ test('transformStaticCSP = function', async (t) => {
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';")
@@ -212,7 +212,7 @@ test('transformStaticCSP = function, with @fastify/helmet', async (t) => {
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(res.statusCode, 200)
t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';")
diff --git a/test/route.test.js b/test/route.test.js
index 56e41aa..03fd690 100644
--- a/test/route.test.js
+++ b/test/route.test.js
@@ -100,8 +100,8 @@ test('fastify.swagger should return a valid swagger yaml', async (t) => {
t.pass('valid swagger yaml')
})
-test('/documentation should redirect to ./documentation/static/index.html', async (t) => {
- t.plan(3)
+test('/documentation should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi)
@@ -117,13 +117,14 @@ test('/documentation should redirect to ./documentation/static/index.html', asyn
method: 'GET',
url: '/documentation'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './documentation/static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('/documentation/ should redirect to ./static/index.html', async (t) => {
- t.plan(3)
+test('/documentation/ should display index html ', async (t) => {
+ t.plan(4)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi)
@@ -139,13 +140,14 @@ test('/documentation/ should redirect to ./static/index.html', async (t) => {
method: 'GET',
url: '/documentation/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('/v1/documentation should redirect to ./documentation/static/index.html', async (t) => {
- t.plan(3)
+test('/v1/documentation should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation' })
@@ -161,13 +163,14 @@ test('/v1/documentation should redirect to ./documentation/static/index.html', a
method: 'GET',
url: '/v1/documentation'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './documentation/static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('/v1/documentation/ should redirect to ./static/index.html', async (t) => {
- t.plan(3)
+test('/v1/documentation/ should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation' })
@@ -183,18 +186,19 @@ test('/v1/documentation/ should redirect to ./static/index.html', async (t) => {
method: 'GET',
url: '/v1/documentation/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', async (t) => {
- t.plan(3)
+test('/v1/foobar should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
fastify.register(async function (fastify, options) {
await fastify.register(fastifySwagger, swaggerOption)
- await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar' })
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar', noRedirect: true })
fastify.get('/', () => {})
fastify.post('/', () => {})
@@ -208,13 +212,14 @@ test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', asy
method: 'GET',
url: '/v1/foobar'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './foobar/static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t) => {
- t.plan(3)
+test('/v1/foobar/ should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
fastify.register(async function (fastify, options) {
@@ -233,13 +238,14 @@ test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t)
method: 'GET',
url: '/v1/foobar/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
-test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t) => {
- t.plan(3)
+test('with routePrefix: \'/\' should display index html', async (t) => {
+ t.plan(4)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
@@ -251,9 +257,10 @@ test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t)
method: 'GET',
url: '/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
})
test('/documentation/static/:file should send back the correct file', async (t) => {
@@ -275,10 +282,10 @@ test('/documentation/static/:file should send back the correct file', async (t)
{
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/'
+ url: '/documentation/static/index.html'
})
t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.headers.location, '/documentation/')
}
{
@@ -481,20 +488,6 @@ test('/documentation/:myfile should run custom NotFoundHandler in dynamic mode',
t.equal(res.statusCode, 410)
})
-test('/documentation/ should redirect to ./static/index.html', async (t) => {
- t.plan(2)
- const fastify = Fastify()
- await fastify.register(fastifySwagger, swaggerOption)
- await fastify.register(fastifySwaggerUi)
-
- const res = await fastify.inject({
- method: 'GET',
- url: '/documentation/'
- })
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
-})
-
test('/documentation/* should not return module files when baseDir not set', async (t) => {
t.plan(1)
const fastify = Fastify()
@@ -522,8 +515,8 @@ test('should return silent log level of route /documentation', async (t) => {
method: 'GET',
url: '/documentation/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers['content-type'], 'text/html; charset=utf-8')
})
test('should return empty log level of route /documentation', async (t) => {
@@ -540,6 +533,23 @@ test('should return empty log level of route /documentation', async (t) => {
method: 'GET',
url: '/documentation/'
})
- t.equal(res.statusCode, 302)
- t.equal(res.headers.location, './static/index.html')
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers['content-type'], 'text/html; charset=utf-8')
+})
+
+test('/documentation should display index html with correct asset urls', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/documentation'
+ })
+
+ t.equal(res.payload.includes('href="/documentation/static/index.css"'), true)
+ t.equal(res.payload.includes('src="/documentation/static/theme/theme-js.js"'), true)
+ t.equal(res.payload.includes('href="/documentation/index.css"'), false)
+ t.equal(res.payload.includes('src="/documentation/theme/theme-js.js"'), false)
})
diff --git a/test/theme.test.js b/test/theme.test.js
index 894004b..755e2c7 100644
--- a/test/theme.test.js
+++ b/test/theme.test.js
@@ -20,7 +20,7 @@ test('swagger route does not return additional theme', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')
@@ -63,7 +63,7 @@ test('swagger route returns additional theme', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')
@@ -119,7 +119,7 @@ test('swagger route returns additional theme - only js', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')
@@ -156,7 +156,7 @@ test('swagger route returns additional theme - only css', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')
@@ -199,7 +199,7 @@ test('swagger route returns additional theme - only favicon', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')
@@ -235,7 +235,7 @@ test('swagger route returns additional theme - only title', async (t) => {
const res = await fastify.inject({
method: 'GET',
- url: '/documentation/static/index.html'
+ url: '/documentation'
})
t.equal(typeof res.payload, 'string')