finalized project
This commit is contained in:
1 parent
b8b3e348e8
commit
4b87e9d20c
15 files changed
+1027
-4
No files matched your search
@@ -0,0 +1,49 @@
|
|||||||
|
# 3Dverse test task
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Technology choosen
|
||||||
|
|
||||||
|
1. [deno](https://deno.com/) instead of nodejs as platform.
|
||||||
|
2. [deno kv](https://deno.com/kv) for database.
|
||||||
|
3. [nix](https://nixos.org/) flake as build system
|
||||||
|
|
||||||
|
|
||||||
|
### Development environment
|
||||||
|
If the developper has nix flake installed. `nix develop` will install
|
||||||
|
all the system dependencies. Otherwise, `deno` is required; hey,
|
||||||
|
and psrecord are two useful tool to benchmark the server.
|
||||||
|
|
||||||
|
### Running the tests.
|
||||||
|
|
||||||
|
The best way to build the test is to run `nix build .#test` which
|
||||||
|
will output a report in `./result`. Otherwise,
|
||||||
|
`deno test --unstable --allow-read=tests tests` should run all the tests.
|
||||||
|
|
||||||
|
|
||||||
|
### Running the project.
|
||||||
|
|
||||||
|
|
||||||
|
The main file to run the server is `src/server.ts`. To command line
|
||||||
|
`deno run --unstable --allow-net src/server.ts` will run the server locally.
|
||||||
|
One can add the flag `--v8-flags=--max-heap-size=50` to limit the
|
||||||
|
allowed memory to, in this example, 50mb.
|
||||||
|
|
||||||
|
|
||||||
|
In a similar fashion, it is possible to run the application without
|
||||||
|
checking out the code using the command line
|
||||||
|
|
||||||
|
```shell
|
||||||
|
deno run --v8-flags=--max-heap-size=50 --unstable --allow-net https://gitlab.com/didiercrunch/3dverse-testtask/-/raw/master/src/server.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
title: Persist file
|
||||||
|
client->>RestAPI: HTTP request
|
||||||
|
RestAPI->>FileService: Persist file metadata
|
||||||
|
FileService->>BlobService: Persist file
|
||||||
|
```
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
@@ -22,12 +22,28 @@
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
mv test_results.txt $out
|
mv test_results.txt $out
|
||||||
'';
|
'';
|
||||||
|
};
|
||||||
|
swagger-ui-src = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "swagger-ui-src";
|
||||||
|
src = pkgs.fetchzip {
|
||||||
|
url = "https://github.com/swagger-api/swagger-ui/archive/refs/tags/v5.0.0.zip";
|
||||||
|
hash = "sha256-M/P/teEMqQ0gW0k/mvsr/VnkzuTI82+OMc8PAxCQAKM=";
|
||||||
|
};
|
||||||
|
installPhase = ''
|
||||||
|
cp -r dist $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
swagger-ui = pkgs.writeShellApplication {
|
||||||
|
name = "swagger-ui";
|
||||||
|
runtimeInputs = [swagger-ui-src pkgs.python311];
|
||||||
|
text = ''
|
||||||
|
exec ${pkgs.python311}/bin/python -m http.server -d ${swagger-ui-src} 7766
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
packages = {
|
packages = {
|
||||||
test = test;
|
test = test;
|
||||||
|
swagger-ui = swagger-ui;
|
||||||
};
|
};
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [deno
|
buildInputs = with pkgs; [deno
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export {
|
|||||||
TextLineStream,
|
TextLineStream,
|
||||||
} from 'https://deno.land/std@0.191.0/streams/mod.ts';
|
} from 'https://deno.land/std@0.191.0/streams/mod.ts';
|
||||||
export {Status} from "https://deno.land/std@0.191.0/http/http_status.ts";
|
export {Status} from "https://deno.land/std@0.191.0/http/http_status.ts";
|
||||||
|
export { oakCors } from 'https://deno.land/x/cors/mod.ts';
|
||||||
|
|
||||||
|
|
||||||
// oak
|
// oak
|
||||||
export { Application, Context, Router } from 'https://deno.land/x/oak/mod.ts';
|
export { Application, Context, Router } from 'https://deno.land/x/oak/mod.ts';
|
||||||
@@ -3,6 +3,7 @@ import { FileMetaData, FileService } from './services/mod.ts';
|
|||||||
import { readerFromStreamReader } from './deps.ts';
|
import { readerFromStreamReader } from './deps.ts';
|
||||||
import { asyncIterableToArray } from './utils.ts';
|
import { asyncIterableToArray } from './utils.ts';
|
||||||
import { Vector3D, WaveFrontService } from './services/interfaces.ts';
|
import { Vector3D, WaveFrontService } from './services/interfaces.ts';
|
||||||
|
import { bigAndUglySwagger } from "./stagger.ts";
|
||||||
|
|
||||||
type Handler = (ctx: Context) => void;
|
type Handler = (ctx: Context) => void;
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ function deleteFileHandlerFactory(fileService: FileService): Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildAPI(router: Router, fileService: FileService, waveFrontService: WaveFrontService): Router {
|
export function buildAPI(router: Router, fileService: FileService, waveFrontService: WaveFrontService): Router {
|
||||||
|
router.get('/swagger', (ctx) => ctx.response.body = bigAndUglySwagger);
|
||||||
router.get('/files', listFileHandlerFactory(fileService));
|
router.get('/files', listFileHandlerFactory(fileService));
|
||||||
router.get('/files/:fileId', getFileHandlerFactory(fileService));
|
router.get('/files/:fileId', getFileHandlerFactory(fileService));
|
||||||
router.delete('/files/:fileId', deleteFileHandlerFactory(fileService));
|
router.delete('/files/:fileId', deleteFileHandlerFactory(fileService));
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
import { Application, Router } from './deps.ts';
|
import { Application, Router, oakCors } from './deps.ts';
|
||||||
import { buildAPI } from './restapi.ts';
|
import { buildAPI } from './restapi.ts';
|
||||||
import { DenoKVFileService } from './services/mod.ts';
|
import { DenoKVFileService } from './services/mod.ts';
|
||||||
import { WaveFrontServiceImpl } from './services/wavefrontService.ts';
|
import { WaveFrontServiceImpl } from './services/wavefrontService.ts';
|
||||||
@@ -10,6 +10,7 @@ async function main() {
|
|||||||
const waveFrontService = new WaveFrontServiceImpl();
|
const waveFrontService = new WaveFrontServiceImpl();
|
||||||
const router = new Router({ prefix: '/v1' });
|
const router = new Router({ prefix: '/v1' });
|
||||||
|
|
||||||
|
app.use(oakCors({ origin: /^.+localhost:7766$/ }));
|
||||||
app.use(
|
app.use(
|
||||||
buildAPI(router, fileService, waveFrontService).routes(),
|
buildAPI(router, fileService, waveFrontService).routes(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const bigAndUglySwagger = `
|
||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Test task
|
||||||
|
description: |-
|
||||||
|
This should be swagger documentation.
|
||||||
|
version: 1.0.11
|
||||||
|
|
||||||
|
paths: {}
|
||||||
|
`
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||||
|
import { Cors } from "./cors.ts";
|
||||||
|
|
||||||
|
interface Req {
|
||||||
|
method: string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
status?: number | string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
set(headerKey: string, headerValue: string): any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* abcCors middleware wrapper
|
||||||
|
* @param o CorsOptions | CorsOptionsDelegate
|
||||||
|
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||||
|
*/
|
||||||
|
export const abcCors = <
|
||||||
|
RequestT extends Req = any,
|
||||||
|
ResponseT extends Res = any,
|
||||||
|
MiddlewareT extends (
|
||||||
|
next: (...args: any) => any,
|
||||||
|
) => (context: { request: RequestT; response: ResponseT }) => any = any,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||||
|
) => {
|
||||||
|
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||||
|
CorsOptionsDelegate<RequestT>
|
||||||
|
>(o);
|
||||||
|
|
||||||
|
return ((abcNext) =>
|
||||||
|
async (context) => {
|
||||||
|
const next = () => abcNext(context);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { request, response } = context;
|
||||||
|
|
||||||
|
const options = await corsOptionsDelegate(request);
|
||||||
|
|
||||||
|
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||||
|
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||||
|
|
||||||
|
if (originDelegate) {
|
||||||
|
const requestMethod = request.method;
|
||||||
|
const getRequestHeader = (headerKey: string) =>
|
||||||
|
request.headers.get(headerKey);
|
||||||
|
const getResponseHeader = (headerKey: string) =>
|
||||||
|
response.headers.get(headerKey);
|
||||||
|
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||||
|
response.headers.set(headerKey, headerValue);
|
||||||
|
const setStatus = (
|
||||||
|
statusCode: number,
|
||||||
|
) => (response.status = statusCode);
|
||||||
|
const end = () => {};
|
||||||
|
|
||||||
|
const origin = await originDelegate(getRequestHeader("origin"));
|
||||||
|
|
||||||
|
if (!origin) return next();
|
||||||
|
else {
|
||||||
|
corsOptions.origin = origin;
|
||||||
|
|
||||||
|
return new Cors({
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
getRequestHeader,
|
||||||
|
getResponseHeader,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next,
|
||||||
|
end,
|
||||||
|
}).configureHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}) as MiddlewareT;
|
||||||
|
};
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||||
|
import { Cors } from "./cors.ts";
|
||||||
|
|
||||||
|
interface Req {
|
||||||
|
method: string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
status: (status: number) => any;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
set(headerKey: string, headerValue: string): any;
|
||||||
|
};
|
||||||
|
send: (body: any) => any;
|
||||||
|
end(...args: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* attainCors middleware wrapper
|
||||||
|
* @param o CorsOptions | CorsOptionsDelegate
|
||||||
|
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||||
|
*/
|
||||||
|
export const attainCors = <
|
||||||
|
RequestT extends Req = any,
|
||||||
|
ResponseT extends Res = any,
|
||||||
|
MiddlewareT extends (request: RequestT, response: ResponseT) => any = any,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||||
|
) => {
|
||||||
|
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||||
|
CorsOptionsDelegate<RequestT>
|
||||||
|
>(o);
|
||||||
|
|
||||||
|
return async function cors(request, response) {
|
||||||
|
try {
|
||||||
|
const fakeNext = () => undefined;
|
||||||
|
const options = await corsOptionsDelegate(request);
|
||||||
|
|
||||||
|
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||||
|
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||||
|
|
||||||
|
if (originDelegate) {
|
||||||
|
const requestMethod = request.method;
|
||||||
|
const getRequestHeader = (headerKey: string) =>
|
||||||
|
request.headers.get(headerKey);
|
||||||
|
const getResponseHeader = (headerKey: string) =>
|
||||||
|
response.headers.get(headerKey);
|
||||||
|
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||||
|
response.headers.set(headerKey, headerValue);
|
||||||
|
const setStatus = (statusCode: number) => response.status(statusCode);
|
||||||
|
const end = response.end();
|
||||||
|
|
||||||
|
const origin = await originDelegate(getRequestHeader("origin"));
|
||||||
|
|
||||||
|
if (origin) {
|
||||||
|
corsOptions.origin = origin;
|
||||||
|
|
||||||
|
new Cors({
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
getRequestHeader,
|
||||||
|
getResponseHeader,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next: fakeNext,
|
||||||
|
end,
|
||||||
|
}).configureHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === "OPTIONS") {
|
||||||
|
response.send("");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
} as MiddlewareT;
|
||||||
|
};
|
||||||
+291
@@ -0,0 +1,291 @@
|
|||||||
|
import type {
|
||||||
|
CorsOptions,
|
||||||
|
CorsOptionsDelegate,
|
||||||
|
OriginDelegate,
|
||||||
|
} from "./types.ts";
|
||||||
|
|
||||||
|
interface DefaultCorsOptions {
|
||||||
|
origin: string;
|
||||||
|
methods: string;
|
||||||
|
preflightContinue: boolean;
|
||||||
|
optionsSuccessStatus: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CorsProps {
|
||||||
|
corsOptions: ReturnType<typeof Cors.produceCorsOptions>;
|
||||||
|
requestMethod: string;
|
||||||
|
getRequestHeader: (headerKey: string) => string | null | undefined;
|
||||||
|
getResponseHeader: (headerKey: string) => string | null | undefined;
|
||||||
|
setResponseHeader: (headerKey: string, headerValue: string) => any;
|
||||||
|
setStatus: (statusCode: number) => any;
|
||||||
|
next: (...args: any) => any;
|
||||||
|
end: (...args: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Cors {
|
||||||
|
constructor(private props: CorsProps) {}
|
||||||
|
|
||||||
|
public static produceCorsOptions = (
|
||||||
|
corsOptions: CorsOptions = {},
|
||||||
|
defaultCorsOptions: DefaultCorsOptions = {
|
||||||
|
origin: "*",
|
||||||
|
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
preflightContinue: false,
|
||||||
|
optionsSuccessStatus: 204,
|
||||||
|
},
|
||||||
|
) => ({
|
||||||
|
...defaultCorsOptions,
|
||||||
|
...corsOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
public static produceCorsOptionsDelegate = <
|
||||||
|
OptionsCallbackT = CorsOptionsDelegate<any>,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | OptionsCallbackT,
|
||||||
|
) =>
|
||||||
|
typeof o === "function"
|
||||||
|
? (o as OptionsCallbackT)
|
||||||
|
: ((((_request: any) => o) as unknown) as OptionsCallbackT);
|
||||||
|
|
||||||
|
public static produceOriginDelegate = (
|
||||||
|
corsOptions: CorsProps["corsOptions"],
|
||||||
|
) => {
|
||||||
|
if (corsOptions.origin) {
|
||||||
|
if (typeof corsOptions.origin === "function") {
|
||||||
|
return corsOptions.origin as OriginDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((_requestOrigin) => corsOptions.origin) as OriginDelegate;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static isOriginAllowed = (
|
||||||
|
requestOrigin: string | null | undefined,
|
||||||
|
allowedOrigin: CorsOptions["origin"],
|
||||||
|
): boolean => {
|
||||||
|
if (Array.isArray(allowedOrigin)) {
|
||||||
|
return allowedOrigin.some((ao) =>
|
||||||
|
Cors.isOriginAllowed(requestOrigin, ao)
|
||||||
|
);
|
||||||
|
} else if (typeof allowedOrigin === "string") {
|
||||||
|
return requestOrigin === allowedOrigin;
|
||||||
|
} else if (
|
||||||
|
allowedOrigin instanceof RegExp &&
|
||||||
|
typeof requestOrigin === "string"
|
||||||
|
) {
|
||||||
|
return allowedOrigin.test(requestOrigin);
|
||||||
|
} else return !!allowedOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
|
public configureHeaders = () => {
|
||||||
|
const {
|
||||||
|
props: {
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next,
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
configureOrigin,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof requestMethod === "string" &&
|
||||||
|
requestMethod.toUpperCase() === "OPTIONS"
|
||||||
|
) {
|
||||||
|
configureOrigin()
|
||||||
|
.configureCredentials()
|
||||||
|
.configureMethods()
|
||||||
|
.configureAllowedHeaders()
|
||||||
|
.configureMaxAge()
|
||||||
|
.configureExposedHeaders();
|
||||||
|
|
||||||
|
if (corsOptions.preflightContinue) return next();
|
||||||
|
else {
|
||||||
|
setStatus(corsOptions.optionsSuccessStatus);
|
||||||
|
setResponseHeader("Content-Length", "0");
|
||||||
|
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configureOrigin().configureCredentials().configureExposedHeaders();
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureOrigin = () => {
|
||||||
|
const {
|
||||||
|
props: { corsOptions, getRequestHeader, setResponseHeader },
|
||||||
|
setVaryHeader,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (!corsOptions.origin || corsOptions.origin === "*") {
|
||||||
|
setResponseHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
} else if (typeof corsOptions.origin === "string") {
|
||||||
|
setResponseHeader("Access-Control-Allow-Origin", corsOptions.origin);
|
||||||
|
setVaryHeader("Origin");
|
||||||
|
} else {
|
||||||
|
const requestOrigin = getRequestHeader("origin") ??
|
||||||
|
getRequestHeader("Origin");
|
||||||
|
|
||||||
|
setResponseHeader(
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
Cors.isOriginAllowed(requestOrigin, corsOptions.origin)
|
||||||
|
? (requestOrigin as string)
|
||||||
|
: "false",
|
||||||
|
);
|
||||||
|
setVaryHeader("Origin");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureCredentials = () => {
|
||||||
|
const { corsOptions, setResponseHeader } = this.props;
|
||||||
|
|
||||||
|
if (corsOptions.credentials === true) {
|
||||||
|
setResponseHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureMethods = () => {
|
||||||
|
const { corsOptions, setResponseHeader } = this.props;
|
||||||
|
|
||||||
|
let methods = corsOptions.methods;
|
||||||
|
|
||||||
|
setResponseHeader(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
Array.isArray(methods) ? methods.join(",") : methods,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureAllowedHeaders = () => {
|
||||||
|
const {
|
||||||
|
props: { corsOptions, getRequestHeader, setResponseHeader },
|
||||||
|
setVaryHeader,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let allowedHeaders = corsOptions.allowedHeaders;
|
||||||
|
|
||||||
|
if (!allowedHeaders) {
|
||||||
|
allowedHeaders = getRequestHeader("access-control-request-headers") ??
|
||||||
|
getRequestHeader("Access-Control-Request-Headers") ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
setVaryHeader("Access-Control-request-Headers");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedHeaders?.length) {
|
||||||
|
setResponseHeader(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
Array.isArray(allowedHeaders)
|
||||||
|
? allowedHeaders.join(",")
|
||||||
|
: allowedHeaders,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureMaxAge = () => {
|
||||||
|
const { corsOptions, setResponseHeader } = this.props;
|
||||||
|
|
||||||
|
const maxAge = (typeof corsOptions.maxAge === "number" ||
|
||||||
|
typeof corsOptions.maxAge === "string") &&
|
||||||
|
corsOptions.maxAge.toString();
|
||||||
|
|
||||||
|
if (maxAge && maxAge.length) {
|
||||||
|
setResponseHeader("Access-Control-Max-Age", maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private configureExposedHeaders = () => {
|
||||||
|
const { corsOptions, setResponseHeader } = this.props;
|
||||||
|
|
||||||
|
let exposedHeaders = corsOptions.exposedHeaders;
|
||||||
|
|
||||||
|
if (exposedHeaders?.length) {
|
||||||
|
setResponseHeader(
|
||||||
|
"Access-Control-Expose-Headers",
|
||||||
|
Array.isArray(exposedHeaders)
|
||||||
|
? exposedHeaders.join(",")
|
||||||
|
: exposedHeaders,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
private setVaryHeader = (field: string) => {
|
||||||
|
const {
|
||||||
|
props: { getResponseHeader, setResponseHeader },
|
||||||
|
appendVaryHeader,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let existingHeader = getResponseHeader("Vary") ?? "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
existingHeader &&
|
||||||
|
typeof existingHeader === "string" &&
|
||||||
|
(existingHeader = appendVaryHeader(existingHeader, field))
|
||||||
|
) {
|
||||||
|
setResponseHeader("Vary", existingHeader);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private appendVaryHeader = (header: string, field: string) => {
|
||||||
|
const { parseVaryHeader } = this;
|
||||||
|
|
||||||
|
if (header === "*") return header;
|
||||||
|
|
||||||
|
let varyHeader = header;
|
||||||
|
const fields = parseVaryHeader(field);
|
||||||
|
const headers = parseVaryHeader(header.toLocaleLowerCase());
|
||||||
|
|
||||||
|
if (fields.includes("*") || headers.includes("*")) return "*";
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const fld = field.toLowerCase();
|
||||||
|
|
||||||
|
if (headers.includes(fld)) {
|
||||||
|
headers.push(fld);
|
||||||
|
varyHeader = varyHeader ? `${varyHeader}, ${field}` : field;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return varyHeader;
|
||||||
|
};
|
||||||
|
|
||||||
|
private parseVaryHeader = (header: string) => {
|
||||||
|
let end = 0;
|
||||||
|
const list = [];
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
for (let i = 0, len = header.length; i < len; i++) {
|
||||||
|
switch (header.charCodeAt(i)) {
|
||||||
|
case 0x20 /* */:
|
||||||
|
if (start === end) start = end = i + 1;
|
||||||
|
break;
|
||||||
|
case 0x2c /* , */:
|
||||||
|
list.push(header.substring(start, end));
|
||||||
|
start = end = i + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
end = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(header.substring(start, end));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||||
|
import { Cors } from "./cors.ts";
|
||||||
|
|
||||||
|
interface Req {
|
||||||
|
serverRequest: {
|
||||||
|
method: string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
status?: number | string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
set(headerKey: string, headerValue: string): any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mithCors middleware wrapper
|
||||||
|
* @param o CorsOptions | CorsOptionsDelegate
|
||||||
|
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||||
|
*/
|
||||||
|
export const mithCors = <
|
||||||
|
RequestT extends Req = any,
|
||||||
|
ResponseT extends Res = any,
|
||||||
|
MiddlewareT extends (
|
||||||
|
request: RequestT,
|
||||||
|
response: ResponseT,
|
||||||
|
next: (...args: any) => any,
|
||||||
|
) => any = any,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||||
|
) => {
|
||||||
|
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||||
|
CorsOptionsDelegate<RequestT>
|
||||||
|
>(o);
|
||||||
|
|
||||||
|
return (async (request, response, next) => {
|
||||||
|
const serverRequest = request.serverRequest;
|
||||||
|
try {
|
||||||
|
const options = await corsOptionsDelegate(request);
|
||||||
|
|
||||||
|
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||||
|
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||||
|
|
||||||
|
if (originDelegate) {
|
||||||
|
const requestMethod = serverRequest.method;
|
||||||
|
const getRequestHeader = (headerKey: string) =>
|
||||||
|
serverRequest.headers.get(headerKey);
|
||||||
|
const getResponseHeader = (headerKey: string) =>
|
||||||
|
response.headers.get(headerKey);
|
||||||
|
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||||
|
response.headers.set(headerKey, headerValue);
|
||||||
|
const setStatus = (
|
||||||
|
statusCode: number,
|
||||||
|
) => (response.status = statusCode);
|
||||||
|
const end = () => {};
|
||||||
|
|
||||||
|
const origin = await originDelegate(getRequestHeader("origin"));
|
||||||
|
|
||||||
|
if (!origin) return next();
|
||||||
|
else {
|
||||||
|
corsOptions.origin = origin;
|
||||||
|
|
||||||
|
return new Cors({
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
getRequestHeader,
|
||||||
|
getResponseHeader,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next,
|
||||||
|
end,
|
||||||
|
}).configureHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}) as MiddlewareT;
|
||||||
|
};
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export * from "./types.ts";
|
||||||
|
|
||||||
|
export { oakCors } from "./oakCors.ts";
|
||||||
|
export { abcCors } from "./abcCors.ts";
|
||||||
|
export { opineCors } from "./opineCors.ts";
|
||||||
|
export { mithCors } from "./mithCors.ts";
|
||||||
|
export { attainCors } from "./attainCors.ts";
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||||
|
import { Cors } from "./cors.ts";
|
||||||
|
|
||||||
|
interface Req {
|
||||||
|
method: string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
status?: number | string;
|
||||||
|
headers: {
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
set(headerKey: string, headerValue: string): any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oakCors middleware wrapper
|
||||||
|
* @param o CorsOptions | CorsOptionsDelegate
|
||||||
|
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||||
|
*/
|
||||||
|
export const oakCors = <
|
||||||
|
RequestT extends Req = any,
|
||||||
|
ResponseT extends Res = any,
|
||||||
|
MiddlewareT extends (
|
||||||
|
context: { request: RequestT; response: ResponseT },
|
||||||
|
next: (...args: any) => any,
|
||||||
|
) => any = any,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||||
|
) => {
|
||||||
|
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||||
|
CorsOptionsDelegate<RequestT>
|
||||||
|
>(o);
|
||||||
|
|
||||||
|
return (async ({ request, response }, next) => {
|
||||||
|
try {
|
||||||
|
const options = await corsOptionsDelegate(request);
|
||||||
|
|
||||||
|
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||||
|
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||||
|
|
||||||
|
if (originDelegate) {
|
||||||
|
const requestMethod = request.method;
|
||||||
|
const getRequestHeader = (headerKey: string) =>
|
||||||
|
request.headers.get(headerKey);
|
||||||
|
const getResponseHeader = (headerKey: string) =>
|
||||||
|
response.headers.get(headerKey);
|
||||||
|
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||||
|
response.headers.set(headerKey, headerValue);
|
||||||
|
const setStatus = (
|
||||||
|
statusCode: number,
|
||||||
|
) => (response.status = statusCode);
|
||||||
|
const end = () => {};
|
||||||
|
|
||||||
|
const origin = await originDelegate(getRequestHeader("origin"));
|
||||||
|
|
||||||
|
if (!origin) next();
|
||||||
|
else {
|
||||||
|
corsOptions.origin = origin;
|
||||||
|
|
||||||
|
return new Cors({
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
getRequestHeader,
|
||||||
|
getResponseHeader,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next,
|
||||||
|
end,
|
||||||
|
}).configureHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}) as MiddlewareT;
|
||||||
|
};
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||||
|
import { Cors } from "./cors.ts";
|
||||||
|
|
||||||
|
interface Req {
|
||||||
|
method: string;
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
setStatus(code: any): this;
|
||||||
|
set(headerKey: string, headerValue: string): this;
|
||||||
|
get(headerKey: string): string | null | undefined;
|
||||||
|
end(...args: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* opineCors middleware wrapper
|
||||||
|
* @param o CorsOptions | CorsOptionsDelegate
|
||||||
|
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||||
|
*/
|
||||||
|
export const opineCors = <
|
||||||
|
RequestT extends Req = Req,
|
||||||
|
ResponseT extends Res = any,
|
||||||
|
MiddlewareT extends (
|
||||||
|
request: RequestT,
|
||||||
|
response: ResponseT,
|
||||||
|
next: (...args: any) => any,
|
||||||
|
) => any = any,
|
||||||
|
>(
|
||||||
|
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||||
|
) => {
|
||||||
|
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||||
|
CorsOptionsDelegate<RequestT>
|
||||||
|
>(o);
|
||||||
|
|
||||||
|
return (async (request, response, next) => {
|
||||||
|
try {
|
||||||
|
const options = await corsOptionsDelegate(request);
|
||||||
|
|
||||||
|
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||||
|
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||||
|
|
||||||
|
if (originDelegate) {
|
||||||
|
const requestMethod = request.method;
|
||||||
|
const getRequestHeader = (headerKey: string) =>
|
||||||
|
request.headers.get(headerKey);
|
||||||
|
const getResponseHeader = (headerKey: string) =>
|
||||||
|
response.get(headerKey);
|
||||||
|
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||||
|
response.set(headerKey, headerValue);
|
||||||
|
const setStatus = (statusCode: number) =>
|
||||||
|
response.setStatus(statusCode);
|
||||||
|
const end = () => response.end();
|
||||||
|
|
||||||
|
const origin = await originDelegate(getRequestHeader("origin"));
|
||||||
|
|
||||||
|
if (!origin) return next();
|
||||||
|
else {
|
||||||
|
corsOptions.origin = origin;
|
||||||
|
|
||||||
|
return new Cors({
|
||||||
|
corsOptions,
|
||||||
|
requestMethod,
|
||||||
|
getRequestHeader,
|
||||||
|
getResponseHeader,
|
||||||
|
setResponseHeader,
|
||||||
|
setStatus,
|
||||||
|
next,
|
||||||
|
end,
|
||||||
|
}).configureHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}) as MiddlewareT;
|
||||||
|
};
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
/**
|
||||||
|
* CorsOptions
|
||||||
|
*
|
||||||
|
* An Object that describes how CORS middleware should behave. The default configuration is the equivalent of:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* {
|
||||||
|
* "origin": "*",
|
||||||
|
* "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
* "preflightContinue": false,
|
||||||
|
* "optionsSuccessStatus": 204,
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @link https://github.com/tajpouria/cors#configuration-options
|
||||||
|
*/
|
||||||
|
export type CorsOptions = {
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Allow-Origin CORS header.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - Boolean
|
||||||
|
*
|
||||||
|
* set origin to true to reflect the request origin, as defined by req.header('Origin'):
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ origin: true }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* or set it to false to disable CORS:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ origin: false }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - String
|
||||||
|
*
|
||||||
|
* set origin to a specific origin. For example if you set it to "http://example.com" only requests from "http://example.com" will be allowed:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ origin: 'http://example.com' }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - RegExp
|
||||||
|
*
|
||||||
|
* set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern /example\.com$/ will reflect any request that is coming from an origin ending with "example.com":
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ origin: /example\.com$/ }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - Array
|
||||||
|
*
|
||||||
|
* set origin to an array of valid origins. Each origin can be a String or a RegExp. For example ["http://example1.com", /\.example2\.com$/] will accept any request from "http://example1.com" or from a subdomain of "example2.com":
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ origin: ["http://example1.com", /\.example2\.com$/] }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - Function
|
||||||
|
*
|
||||||
|
* set origin to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (called as callback(err, origin), where origin is a non-function value of the origin option) as the second:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({
|
||||||
|
* origin: async (requestOrigin) => {
|
||||||
|
* await loadOriginsFromDataBase(); // Simulate asynchronous task
|
||||||
|
* return ["http://example1.com", /\.example2\.com$/];
|
||||||
|
* },
|
||||||
|
* }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
origin?: boolean | string | RegExp | (string | RegExp)[] | OriginDelegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Allow-Methods CORS header.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - String
|
||||||
|
*
|
||||||
|
* Expects a comma-delimited string (ex: 'GET,PUT,POST'):
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ methods: 'GET,PUT,POST' }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - Array
|
||||||
|
*
|
||||||
|
* or an array (ex: ['GET', 'PUT', 'POST'])
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ methods: ['GET', 'PUT', 'POST'] }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
methods?: string | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Allow-Headers CORS header. If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - String
|
||||||
|
*
|
||||||
|
* Expects a comma-delimited string (ex: 'Content-Type,Authorization'):
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ allowedHeaders: 'Content-Type,Authorization' }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - Array
|
||||||
|
*
|
||||||
|
* or an array (ex: ['Content-Type', 'Authorization']):
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ allowedHeaders: ['Content-Type', 'Authorization'] }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
allowedHeaders?: string | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Expose-Headers CORS header. If not specified, no custom headers are exposed.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - String
|
||||||
|
*
|
||||||
|
* Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range'):
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ exposedHeaders: 'Content-Range,X-Content-Range' }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
exposedHeaders?: string | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Allow-Credentials CORS header. It is omitted by default.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - Boolean
|
||||||
|
*
|
||||||
|
* Set to true to pass the header:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ credentials: true }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
credentials?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Access-Control-Max-Age CORS header. It is omitted by default.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - Number
|
||||||
|
*
|
||||||
|
* Set to an integer to pass the header:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ maxAge: 1 }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
maxAge?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the CORS preflight response to the next handler:
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - Boolean
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ preflightContinue: true }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
preflightContinue?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - Number
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.use(cors({ optionsSuccessStatus: 200 }));
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
optionsSuccessStatus?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link https://github.com/tajpouria/cors#configuring-cors-w-dynamic-origin
|
||||||
|
*/
|
||||||
|
export type OriginDelegate = (
|
||||||
|
requestOrigin: string | undefined | null,
|
||||||
|
) => CorsOptions["origin"] | void | Promise<CorsOptions["origin"] | void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link https://github.com/tajpouria/cors#configuring-cors-asynchronously
|
||||||
|
*/
|
||||||
|
export type CorsOptionsDelegate<RequestT = any> = (
|
||||||
|
request: RequestT,
|
||||||
|
) => CorsOptions | void | Promise<CorsOptions | void>;
|
||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
|
"https://deno.land/x/cors/mod.ts": "./deno.land/x/cors@v1.2.2/mod.ts",
|
||||||
"https://deno.land/x/oak/mod.ts": "./deno.land/x/oak@v12.1.0/mod.ts",
|
"https://deno.land/x/oak/mod.ts": "./deno.land/x/oak@v12.1.0/mod.ts",
|
||||||
"https://deno.land/std/testing/asserts.ts": "./deno.land/std@0.191.0/testing/asserts.ts",
|
"https://deno.land/std/testing/asserts.ts": "./deno.land/std@0.191.0/testing/asserts.ts",
|
||||||
"https://deno.land/": "./deno.land/"
|
"https://deno.land/": "./deno.land/"
|
||||||
|
|||||||
Reference in new issue
Block a user