rsnext/packages/next/server/web/next-url.ts
Javi Velasco a815ba9f79
Implement Middleware RFC (#30081)
This PR adds support for [Middleware as per RFC ](https://github.com/vercel/next.js/discussions/29750). 

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes
2021-10-20 17:52:11 +00:00

234 lines
4.7 KiB
TypeScript

import type { PathLocale } from '../../shared/lib/i18n/normalize-locale-path'
import type { DomainLocale, I18NConfig } from '../config-shared'
import { getLocaleMetadata } from '../../shared/lib/i18n/get-locale-metadata'
import cookie from 'next/dist/compiled/cookie'
/**
* TODO
*
* - Add comments to the URLNext API.
* - Move internals to be using symbols for its shape.
* - Make sure logging does not show any implementation details.
* - Include in the event payload the nextJS configuration
*/
interface Options {
basePath?: string
headers?: { [key: string]: string | string[] | undefined }
i18n?: I18NConfig | null
trailingSlash?: boolean
}
export class NextURL extends URL {
private _basePath: string
private _locale?: {
defaultLocale: string
domain?: DomainLocale
locale: string
path: PathLocale
redirect?: string
trailingSlash?: boolean
}
private _options: Options
private _url: URL
constructor(url: string, options: Options = {}) {
super(formatRelative(url))
this._options = options
this._basePath = ''
this._url = formatRelative(url)
this.analyzeUrl()
}
get absolute() {
return this._url.hostname !== 'localhost'
}
analyzeUrl() {
const { headers = {}, basePath, i18n } = this._options
if (basePath && this._url.pathname.startsWith(basePath)) {
this._url.pathname = this._url.pathname.replace(basePath, '') || '/'
this._basePath = basePath
} else {
this._basePath = ''
}
if (i18n) {
this._locale = getLocaleMetadata({
cookies: () => {
const value = headers['cookie']
return value
? cookie.parse(Array.isArray(value) ? value.join(';') : value)
: {}
},
headers: headers,
nextConfig: {
basePath: basePath,
i18n: i18n,
},
url: {
hostname: this._url.hostname || null,
pathname: this._url.pathname,
},
})
if (this._locale?.path.detectedLocale) {
this._url.pathname = this._locale.path.pathname
}
}
}
formatPathname() {
const { i18n } = this._options
let pathname = this._url.pathname
if (this._locale?.locale && i18n?.defaultLocale !== this._locale?.locale) {
pathname = `/${this._locale?.locale}${pathname}`
}
if (this._basePath) {
pathname = `${this._basePath}${pathname}`
}
return pathname
}
get locale() {
if (!this._locale) {
throw new TypeError(`The URL is not configured with i18n`)
}
return this._locale.locale
}
set locale(locale: string) {
if (!this._locale) {
throw new TypeError(`The URL is not configured with i18n`)
}
this._locale.locale = locale
}
get defaultLocale() {
return this._locale?.defaultLocale
}
get domainLocale() {
return this._locale?.domain
}
get searchParams() {
return this._url.searchParams
}
get host() {
return this.absolute ? this._url.host : ''
}
set host(value: string) {
this._url.host = value
}
get hostname() {
return this.absolute ? this._url.hostname : ''
}
set hostname(value: string) {
this._url.hostname = value || 'localhost'
}
get port() {
return this.absolute ? this._url.port : ''
}
set port(value: string) {
this._url.port = value
}
get protocol() {
return this.absolute ? this._url.protocol : ''
}
set protocol(value: string) {
this._url.protocol = value
}
get href() {
const pathname = this.formatPathname()
return this.absolute
? `${this.protocol}//${this.host}${pathname}${this._url.search}`
: `${pathname}${this._url.search}`
}
set href(url: string) {
this._url = formatRelative(url)
this.analyzeUrl()
}
get origin() {
return this.absolute ? this._url.origin : ''
}
get pathname() {
return this._url.pathname
}
set pathname(value: string) {
this._url.pathname = value
}
get hash() {
return this._url.hash
}
set hash(value: string) {
this._url.hash = value
}
get search() {
return this._url.search
}
set search(value: string) {
this._url.search = value
}
get password() {
return this._url.password
}
set password(value: string) {
this._url.password = value
}
get username() {
return this._url.username
}
set username(value: string) {
this._url.username = value
}
get basePath() {
return this._basePath
}
set basePath(value: string) {
this._basePath = value.startsWith('/') ? value : `/${value}`
}
toString() {
return this.href
}
toJSON() {
return this.href
}
}
function formatRelative(url: string) {
return url.startsWith('/')
? new URL(url.replace(/^\/+/, '/'), new URL('https://localhost'))
: new URL(url)
}