next
Version:
The React Framework
293 lines (292 loc) • 11.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
HeadersAdapter: null,
ReadonlyHeadersError: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
HeadersAdapter: function() {
return HeadersAdapter;
},
ReadonlyHeadersError: function() {
return ReadonlyHeadersError;
}
});
const _reflect = require("./reflect");
class ReadonlyHeadersError extends Error {
constructor(){
super('Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers');
Object.defineProperty(this, "__NEXT_ERROR_CODE", {
value: "E1176",
enumerable: false,
configurable: true
});
}
static callable() {
throw new ReadonlyHeadersError();
}
}
/**
* Builds the read methods for a sealed view that exposes all of `target`.
*/ function createPassThroughMethods(target, sealed) {
return {
get: target.get.bind(target),
has: target.has.bind(target),
getSetCookie: target.getSetCookie.bind(target),
keys: target.keys.bind(target),
values: target.values.bind(target),
entries: target.entries.bind(target),
[Symbol.iterator]: target[Symbol.iterator].bind(target),
// The native method passes the unsealed target as the callback's `parent`
// argument. That is a mutable handle on the underlying headers. Pass the
// sealed view instead.
forEach (callbackfn, thisArg) {
for (const [name, value] of target.entries()){
callbackfn.call(thisArg, value, name, sealed);
}
}
};
}
/**
* Builds the read methods for a sealed view that omits the header names matched
* by `isHidden`.
*/ function createHidingMethods(target, sealed, isHidden) {
function* entries() {
for (const entry of target.entries()){
if (!isHidden(entry[0])) {
yield entry;
}
}
}
return {
entries,
[Symbol.iterator]: entries,
get: (name)=>isHidden(name) ? null : target.get(name),
has: (name)=>isHidden(name) ? false : target.has(name),
getSetCookie: ()=>isHidden('set-cookie') ? [] : target.getSetCookie(),
*keys () {
for (const name of target.keys()){
if (!isHidden(name)) {
yield name;
}
}
},
*values () {
for (const [, value] of entries()){
yield value;
}
},
// The native method passes the unsealed target as the callback's `parent`
// argument. That is a mutable handle on the underlying headers. Pass the
// sealed view instead.
forEach (callbackfn, thisArg) {
for (const [name, value] of entries()){
callbackfn.call(thisArg, value, name, sealed);
}
}
};
}
class HeadersAdapter extends Headers {
constructor(headers){
// We've already overridden the methods that would be called, so we're just
// calling the super constructor to ensure that the instanceof check works.
super();
this.headers = new Proxy(headers, {
get (target, prop, receiver) {
// Because this is just an object, we expect that all "get" operations
// are for properties. If it's a "get" for a symbol, we'll just return
// the symbol.
if (typeof prop === 'symbol') {
return _reflect.ReflectAdapter.get(target, prop, receiver);
}
const lowercased = prop.toLowerCase();
// Let's find the original casing of the key. This assumes that there is
// no mixed case keys (e.g. "Content-Type" and "content-type") in the
// headers object.
const original = Object.keys(headers).find((o)=>o.toLowerCase() === lowercased);
// If the original casing doesn't exist, return undefined.
if (typeof original === 'undefined') return;
// If the original casing exists, return the value.
return _reflect.ReflectAdapter.get(target, original, receiver);
},
set (target, prop, value, receiver) {
if (typeof prop === 'symbol') {
return _reflect.ReflectAdapter.set(target, prop, value, receiver);
}
const lowercased = prop.toLowerCase();
// Let's find the original casing of the key. This assumes that there is
// no mixed case keys (e.g. "Content-Type" and "content-type") in the
// headers object.
const original = Object.keys(headers).find((o)=>o.toLowerCase() === lowercased);
// If the original casing doesn't exist, use the prop as the key.
return _reflect.ReflectAdapter.set(target, original ?? prop, value, receiver);
},
has (target, prop) {
if (typeof prop === 'symbol') return _reflect.ReflectAdapter.has(target, prop);
const lowercased = prop.toLowerCase();
// Let's find the original casing of the key. This assumes that there is
// no mixed case keys (e.g. "Content-Type" and "content-type") in the
// headers object.
const original = Object.keys(headers).find((o)=>o.toLowerCase() === lowercased);
// If the original casing doesn't exist, return false.
if (typeof original === 'undefined') return false;
// If the original casing exists, return true.
return _reflect.ReflectAdapter.has(target, original);
},
deleteProperty (target, prop) {
if (typeof prop === 'symbol') return _reflect.ReflectAdapter.deleteProperty(target, prop);
const lowercased = prop.toLowerCase();
// Let's find the original casing of the key. This assumes that there is
// no mixed case keys (e.g. "Content-Type" and "content-type") in the
// headers object.
const original = Object.keys(headers).find((o)=>o.toLowerCase() === lowercased);
// If the original casing doesn't exist, return true.
if (typeof original === 'undefined') return true;
// If the original casing exists, delete the property.
return _reflect.ReflectAdapter.deleteProperty(target, original);
}
});
}
/**
* Seals a Headers instance to prevent modification by throwing an error when
* any mutating method is called.
*
* The sealed view stays live. Later writes to `headers` remain visible
* through it.
*
* `hidden` omits the given header names from every read operation (`get`,
* `has`, `getSetCookie`, `forEach`, and iteration). The names must be
* lowercase. The underlying headers are neither copied nor mutated, so hidden
* headers remain available to the framework.
*/ static seal(headers, hidden) {
const isHidden = hidden && hidden.size > 0 ? (name)=>hidden.has(name.toLowerCase()) : null;
// The methods are built once per sealed view and reused, so repeated access
// returns the same function instead of a fresh closure. They are assigned
// after the proxy exists because `forEach` hands the proxy to its callback.
// Creating the proxy runs no trap, so nothing can read them before then.
let methods;
const sealed = new Proxy(headers, {
get (target, prop, receiver) {
switch(prop){
case 'append':
case 'delete':
case 'set':
return ReadonlyHeadersError.callable;
case Symbol.iterator:
return methods[Symbol.iterator];
case 'get':
case 'has':
case 'getSetCookie':
case 'keys':
case 'values':
case 'entries':
case 'forEach':
return methods[prop];
default:
return _reflect.ReflectAdapter.get(target, prop, receiver);
}
}
});
methods = isHidden ? createHidingMethods(headers, sealed, isHidden) : createPassThroughMethods(headers, sealed);
return sealed;
}
/**
* @param headers
* @returns A fresh object identity backed by the original value
*/ static fresh(headers) {
return new Proxy(headers, {
get (target, prop, receiver) {
return _reflect.ReflectAdapter.get(target, prop, receiver);
}
});
}
/**
* Merges a header value into a string. This stores multiple values as an
* array, so we need to merge them into a string.
*
* @param value a header value
* @returns a merged header value (a string)
*/ merge(value) {
if (Array.isArray(value)) return value.join(', ');
return value;
}
/**
* Creates a Headers instance from a plain object or a Headers instance.
*
* @param headers a plain object or a Headers instance
* @returns a headers instance
*/ static from(headers) {
if (headers instanceof Headers) return headers;
return new HeadersAdapter(headers);
}
append(name, value) {
const existing = this.headers[name];
if (typeof existing === 'string') {
this.headers[name] = [
existing,
value
];
} else if (Array.isArray(existing)) {
existing.push(value);
} else {
this.headers[name] = value;
}
}
delete(name) {
delete this.headers[name];
}
get(name) {
const value = this.headers[name];
if (typeof value !== 'undefined') return this.merge(value);
return null;
}
has(name) {
return typeof this.headers[name] !== 'undefined';
}
set(name, value) {
this.headers[name] = value;
}
forEach(callbackfn, thisArg) {
for (const [name, value] of this.entries()){
callbackfn.call(thisArg, value, name, this);
}
}
*entries() {
for (const key of Object.keys(this.headers)){
const name = key.toLowerCase();
// We assert here that this is a string because we got it from the
// Object.keys() call above.
const value = this.get(name);
yield [
name,
value
];
}
}
*keys() {
for (const key of Object.keys(this.headers)){
const name = key.toLowerCase();
yield name;
}
}
*values() {
for (const key of Object.keys(this.headers)){
// We assert here that this is a string because we got it from the
// Object.keys() call above.
const value = this.get(key);
yield value;
}
}
[Symbol.iterator]() {
return this.entries();
}
}
//# sourceMappingURL=headers.js.map