@draftlab/auth 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +3 -3
- package/dist/provider/code.mjs +88 -0
- package/dist/provider/magiclink.mjs +58 -0
- package/dist/provider/oauth2.mjs +57 -0
- package/dist/provider/passkey.mjs +1 -1
- package/dist/provider/password.mjs +9 -0
- package/dist/provider/provider.d.mts +2 -2
- package/dist/router/context.d.mts +21 -0
- package/dist/router/context.mjs +193 -0
- package/dist/router/cookies.d.mts +8 -0
- package/dist/router/cookies.mjs +13 -0
- package/dist/router/index.d.mts +21 -0
- package/dist/router/index.mjs +107 -0
- package/dist/router/matcher.d.mts +15 -0
- package/dist/router/matcher.mjs +76 -0
- package/dist/router/middleware/cors.d.mts +15 -0
- package/dist/router/middleware/cors.mjs +114 -0
- package/dist/router/safe-request.d.mts +52 -0
- package/dist/router/safe-request.mjs +160 -0
- package/dist/router/types.d.mts +67 -0
- package/dist/router/types.mjs +1 -0
- package/dist/router/variables.d.mts +12 -0
- package/dist/router/variables.mjs +20 -0
- package/dist/ui/code.mjs +15 -8
- package/dist/ui/magiclink.mjs +15 -8
- package/dist/ui/passkey.d.mts +5 -3
- package/dist/ui/passkey.mjs +34 -8
- package/dist/ui/password.d.mts +0 -4
- package/dist/ui/password.mjs +259 -243
- package/dist/util.d.mts +25 -2
- package/dist/util.mjs +24 -1
- package/package.json +5 -6
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CompiledRoute, MatchResult, RouterOptions } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/matcher.d.ts
|
|
4
|
+
declare class RouteMatcher {
|
|
5
|
+
private readonly compiledRoutes;
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options?: RouterOptions);
|
|
8
|
+
compile(pattern: string): CompiledRoute;
|
|
9
|
+
match(pattern: string, pathname: string): MatchResult | null;
|
|
10
|
+
sortRoutesBySpecificity(patterns: string[]): string[];
|
|
11
|
+
private calculateSpecificity;
|
|
12
|
+
normalizePath(pathname: string): string;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { RouteMatcher };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/router/matcher.ts
|
|
2
|
+
var RouteMatcher = class {
|
|
3
|
+
compiledRoutes = /* @__PURE__ */ new Map();
|
|
4
|
+
options;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.options = {
|
|
7
|
+
caseSensitive: false,
|
|
8
|
+
strict: false,
|
|
9
|
+
basePath: "",
|
|
10
|
+
...options
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
compile(pattern) {
|
|
14
|
+
const cacheKey = `${pattern}:${this.options.caseSensitive}:${this.options.strict}`;
|
|
15
|
+
const cached = this.compiledRoutes.get(cacheKey);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
const paramNames = [];
|
|
18
|
+
let regexPattern = pattern.replace(/:([^/]+)/g, (_, name) => {
|
|
19
|
+
paramNames.push(name);
|
|
20
|
+
return "([^/]+)";
|
|
21
|
+
});
|
|
22
|
+
regexPattern = regexPattern.replace(/\*/g, "(.*)");
|
|
23
|
+
const finalPattern = this.options.strict || pattern === "/" ? regexPattern : `${regexPattern.replace(/\/$/, "")}/?`;
|
|
24
|
+
const compiled = {
|
|
25
|
+
regex: new RegExp(`^${finalPattern}$`, this.options.caseSensitive ? "" : "i"),
|
|
26
|
+
paramNames,
|
|
27
|
+
pattern
|
|
28
|
+
};
|
|
29
|
+
this.compiledRoutes.set(cacheKey, compiled);
|
|
30
|
+
return compiled;
|
|
31
|
+
}
|
|
32
|
+
match(pattern, pathname) {
|
|
33
|
+
const compiled = this.compile(pattern);
|
|
34
|
+
const match = pathname.match(compiled.regex);
|
|
35
|
+
if (!match) return null;
|
|
36
|
+
const params = {};
|
|
37
|
+
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
38
|
+
const name = compiled.paramNames[i];
|
|
39
|
+
const value = match[i + 1];
|
|
40
|
+
if (name && value !== void 0) try {
|
|
41
|
+
params[name] = decodeURIComponent(value);
|
|
42
|
+
} catch {
|
|
43
|
+
params[name] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
params: Object.freeze(params),
|
|
48
|
+
pattern
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
sortRoutesBySpecificity(patterns) {
|
|
52
|
+
return [...patterns].sort((a, b) => {
|
|
53
|
+
const aScore = this.calculateSpecificity(a);
|
|
54
|
+
return this.calculateSpecificity(b) - aScore;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
calculateSpecificity(pattern) {
|
|
58
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
59
|
+
let score = 0;
|
|
60
|
+
for (const segment of segments) if (segment.startsWith(":")) score += 2;
|
|
61
|
+
else if (segment === "*") score += 1;
|
|
62
|
+
else score += 4;
|
|
63
|
+
return score;
|
|
64
|
+
}
|
|
65
|
+
normalizePath(pathname) {
|
|
66
|
+
let normalized = pathname;
|
|
67
|
+
const { basePath, strict } = this.options;
|
|
68
|
+
if (basePath && normalized.startsWith(basePath)) normalized = normalized.slice(basePath.length) || "/";
|
|
69
|
+
if (!normalized.startsWith("/")) normalized = `/${normalized}`;
|
|
70
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { RouteMatcher };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MiddlewareHandler, RouterContext, VariableMap } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/middleware/cors.d.ts
|
|
4
|
+
interface CORSOptions {
|
|
5
|
+
origin?: "*" | string | readonly string[] | ((origin: string, ctx: RouterContext<Record<string, string>, VariableMap>) => string | null);
|
|
6
|
+
allowMethods?: readonly string[] | ((origin: string, ctx: RouterContext<Record<string, string>, VariableMap>) => readonly string[]);
|
|
7
|
+
allowHeaders?: readonly string[];
|
|
8
|
+
maxAge?: number;
|
|
9
|
+
credentials?: boolean;
|
|
10
|
+
exposeHeaders?: readonly string[];
|
|
11
|
+
preflightHandler?: <TVariables extends VariableMap>(ctx: RouterContext<Record<string, string>, TVariables>) => Promise<Response> | Response;
|
|
12
|
+
}
|
|
13
|
+
declare const cors: <TVariables extends VariableMap = VariableMap>(options?: CORSOptions) => MiddlewareHandler<Record<string, string>, TVariables>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { CORSOptions, cors };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/router/middleware/cors.ts
|
|
2
|
+
const DEFAULT_CORS_OPTIONS = {
|
|
3
|
+
origin: "*",
|
|
4
|
+
allowMethods: [
|
|
5
|
+
"GET",
|
|
6
|
+
"HEAD",
|
|
7
|
+
"PUT",
|
|
8
|
+
"POST",
|
|
9
|
+
"DELETE",
|
|
10
|
+
"PATCH",
|
|
11
|
+
"OPTIONS"
|
|
12
|
+
],
|
|
13
|
+
allowHeaders: [],
|
|
14
|
+
maxAge: 86400,
|
|
15
|
+
credentials: false,
|
|
16
|
+
exposeHeaders: []
|
|
17
|
+
};
|
|
18
|
+
const createOriginResolver = (origin) => {
|
|
19
|
+
if (!origin || origin === "*") return () => "*";
|
|
20
|
+
if (typeof origin === "string") return (requestOrigin) => origin === requestOrigin ? origin : null;
|
|
21
|
+
if (typeof origin === "function") return origin;
|
|
22
|
+
if (Array.isArray(origin)) {
|
|
23
|
+
const allowedOrigins = new Set(origin);
|
|
24
|
+
return (requestOrigin) => allowedOrigins.has(requestOrigin) ? requestOrigin : null;
|
|
25
|
+
}
|
|
26
|
+
return () => null;
|
|
27
|
+
};
|
|
28
|
+
const createMethodsResolver = (methods) => {
|
|
29
|
+
if (typeof methods === "function") return methods;
|
|
30
|
+
if (Array.isArray(methods)) return () => methods;
|
|
31
|
+
return () => DEFAULT_CORS_OPTIONS.allowMethods;
|
|
32
|
+
};
|
|
33
|
+
const normalizeCORSOptions = (options = {}) => {
|
|
34
|
+
const opts = {
|
|
35
|
+
...DEFAULT_CORS_OPTIONS,
|
|
36
|
+
...options
|
|
37
|
+
};
|
|
38
|
+
if (opts.maxAge < 0) throw new Error("CORS maxAge must be non-negative");
|
|
39
|
+
if (Array.isArray(opts.allowMethods)) {
|
|
40
|
+
const validMethods = new Set([
|
|
41
|
+
"GET",
|
|
42
|
+
"HEAD",
|
|
43
|
+
"PUT",
|
|
44
|
+
"POST",
|
|
45
|
+
"DELETE",
|
|
46
|
+
"PATCH",
|
|
47
|
+
"OPTIONS"
|
|
48
|
+
]);
|
|
49
|
+
const invalidMethods = opts.allowMethods.filter((method) => !validMethods.has(method.toUpperCase()));
|
|
50
|
+
if (invalidMethods.length > 0) console.warn(`CORS: Invalid HTTP methods detected: ${invalidMethods.join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
...opts,
|
|
54
|
+
_originResolver: createOriginResolver(options.origin),
|
|
55
|
+
_methodsResolver: createMethodsResolver(options.allowMethods)
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const cors = (options = {}) => {
|
|
59
|
+
const opts = normalizeCORSOptions(options);
|
|
60
|
+
return async (ctx, next) => {
|
|
61
|
+
const requestOrigin = ctx.header("origin") || "";
|
|
62
|
+
const requestMethod = ctx.request.method.toUpperCase();
|
|
63
|
+
const allowedOrigin = opts._originResolver(requestOrigin, ctx);
|
|
64
|
+
const corsHeaders = {};
|
|
65
|
+
if (allowedOrigin) corsHeaders["Access-Control-Allow-Origin"] = allowedOrigin;
|
|
66
|
+
if (opts.origin !== "*" && allowedOrigin) {
|
|
67
|
+
const existingVary = ctx.header("vary");
|
|
68
|
+
corsHeaders.Vary = existingVary ? `${existingVary}, Origin` : "Origin";
|
|
69
|
+
}
|
|
70
|
+
if (opts.credentials) corsHeaders["Access-Control-Allow-Credentials"] = "true";
|
|
71
|
+
if (opts.exposeHeaders && opts.exposeHeaders.length > 0) corsHeaders["Access-Control-Expose-Headers"] = opts.exposeHeaders.join(",");
|
|
72
|
+
if (requestMethod === "OPTIONS") {
|
|
73
|
+
if (opts.preflightHandler) return await opts.preflightHandler(ctx);
|
|
74
|
+
if (opts.maxAge != null) corsHeaders["Access-Control-Max-Age"] = opts.maxAge.toString();
|
|
75
|
+
const allowedMethods = opts._methodsResolver(requestOrigin, ctx);
|
|
76
|
+
if (allowedMethods.length > 0) corsHeaders["Access-Control-Allow-Methods"] = allowedMethods.join(",");
|
|
77
|
+
let allowedHeaders = opts.allowHeaders;
|
|
78
|
+
if (!allowedHeaders || allowedHeaders.length === 0) {
|
|
79
|
+
const requestHeaders = ctx.header("access-control-request-headers");
|
|
80
|
+
if (requestHeaders) allowedHeaders = requestHeaders.split(/\s*,\s*/);
|
|
81
|
+
}
|
|
82
|
+
if (allowedHeaders && allowedHeaders.length > 0) {
|
|
83
|
+
corsHeaders["Access-Control-Allow-Headers"] = allowedHeaders.join(",");
|
|
84
|
+
const existingVary = corsHeaders.Vary || ctx.header("vary");
|
|
85
|
+
corsHeaders.Vary = existingVary ? `${existingVary}, Access-Control-Request-Headers` : "Access-Control-Request-Headers";
|
|
86
|
+
}
|
|
87
|
+
const headers = new Headers();
|
|
88
|
+
Object.entries(corsHeaders).forEach(([key, value]) => {
|
|
89
|
+
headers.set(key, value);
|
|
90
|
+
});
|
|
91
|
+
return new Response(null, {
|
|
92
|
+
status: 204,
|
|
93
|
+
statusText: "No Content",
|
|
94
|
+
headers
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return applyCORSHeaders(await next(), corsHeaders);
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const applyCORSHeaders = (response, corsHeaders) => {
|
|
101
|
+
if (Object.keys(corsHeaders).length === 0) return response;
|
|
102
|
+
const newHeaders = new Headers(response.headers);
|
|
103
|
+
Object.entries(corsHeaders).forEach(([key, value]) => {
|
|
104
|
+
newHeaders.set(key, value);
|
|
105
|
+
});
|
|
106
|
+
return new Response(response.body, {
|
|
107
|
+
status: response.status,
|
|
108
|
+
statusText: response.statusText,
|
|
109
|
+
headers: newHeaders
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { cors };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/router/safe-request.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* SafeRequest wraps a Request to cache the body and allow multiple reads.
|
|
4
|
+
* This solves issues with Request implementations that don't support multiple body reads.
|
|
5
|
+
*/
|
|
6
|
+
interface SafeRequestOptions {
|
|
7
|
+
cache?: RequestCache;
|
|
8
|
+
credentials?: RequestCredentials;
|
|
9
|
+
destination?: RequestDestination;
|
|
10
|
+
integrity?: string;
|
|
11
|
+
keepalive?: boolean;
|
|
12
|
+
mode?: RequestMode;
|
|
13
|
+
redirect?: RequestRedirect;
|
|
14
|
+
referrer?: string;
|
|
15
|
+
referrerPolicy?: ReferrerPolicy;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
declare class SafeRequest implements Request {
|
|
19
|
+
private cachedBody;
|
|
20
|
+
private bodyBuffer;
|
|
21
|
+
private readonly textDecoder;
|
|
22
|
+
readonly cache: RequestCache;
|
|
23
|
+
readonly credentials: RequestCredentials;
|
|
24
|
+
readonly destination: RequestDestination;
|
|
25
|
+
readonly headers: Headers;
|
|
26
|
+
readonly integrity: string;
|
|
27
|
+
readonly keepalive: boolean;
|
|
28
|
+
readonly method: string;
|
|
29
|
+
readonly mode: RequestMode;
|
|
30
|
+
readonly redirect: RequestRedirect;
|
|
31
|
+
readonly referrer: string;
|
|
32
|
+
readonly referrerPolicy: ReferrerPolicy;
|
|
33
|
+
readonly signal: AbortSignal;
|
|
34
|
+
readonly url: string;
|
|
35
|
+
constructor(url: string, method: string, headers: Headers, body: ArrayBuffer | null, options?: SafeRequestOptions);
|
|
36
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
37
|
+
blob(): Promise<Blob>;
|
|
38
|
+
formData(): Promise<FormData>;
|
|
39
|
+
json<T = unknown>(): Promise<T>;
|
|
40
|
+
text(): Promise<string>;
|
|
41
|
+
bytes(): Promise<Uint8Array<ArrayBuffer>>;
|
|
42
|
+
get body(): ReadableStream<Uint8Array<ArrayBuffer>> | null;
|
|
43
|
+
get bodyUsed(): boolean;
|
|
44
|
+
clone(): Request;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extracts data from a Request and creates a SafeRequest.
|
|
48
|
+
* Uses ReadableStream to safely read the body and avoid issues with certain Request implementations.
|
|
49
|
+
*/
|
|
50
|
+
declare function makeSafeRequest(request: Request): Promise<Request>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { SafeRequest, makeSafeRequest };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//#region src/router/safe-request.ts
|
|
2
|
+
var SafeRequest = class SafeRequest {
|
|
3
|
+
cachedBody = null;
|
|
4
|
+
bodyBuffer = null;
|
|
5
|
+
textDecoder = new TextDecoder();
|
|
6
|
+
cache;
|
|
7
|
+
credentials;
|
|
8
|
+
destination;
|
|
9
|
+
headers;
|
|
10
|
+
integrity;
|
|
11
|
+
keepalive;
|
|
12
|
+
method;
|
|
13
|
+
mode;
|
|
14
|
+
redirect;
|
|
15
|
+
referrer;
|
|
16
|
+
referrerPolicy;
|
|
17
|
+
signal;
|
|
18
|
+
url;
|
|
19
|
+
constructor(url, method, headers, body, options) {
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.method = method;
|
|
22
|
+
this.headers = headers;
|
|
23
|
+
this.bodyBuffer = body;
|
|
24
|
+
this.cache = options?.cache ?? "default";
|
|
25
|
+
this.credentials = options?.credentials ?? "same-origin";
|
|
26
|
+
this.destination = options?.destination ?? "";
|
|
27
|
+
this.integrity = options?.integrity ?? "";
|
|
28
|
+
this.keepalive = options?.keepalive ?? false;
|
|
29
|
+
this.mode = options?.mode ?? "cors";
|
|
30
|
+
this.redirect = options?.redirect ?? "follow";
|
|
31
|
+
this.referrer = options?.referrer ?? "";
|
|
32
|
+
this.referrerPolicy = options?.referrerPolicy ?? "";
|
|
33
|
+
this.signal = options?.signal ?? new AbortController().signal;
|
|
34
|
+
}
|
|
35
|
+
async arrayBuffer() {
|
|
36
|
+
return this.bodyBuffer ?? /* @__PURE__ */ new ArrayBuffer(0);
|
|
37
|
+
}
|
|
38
|
+
async blob() {
|
|
39
|
+
const buffer = await this.arrayBuffer();
|
|
40
|
+
return new Blob([buffer]);
|
|
41
|
+
}
|
|
42
|
+
async formData() {
|
|
43
|
+
const buffer = await this.arrayBuffer();
|
|
44
|
+
const blob = new Blob([buffer], { type: this.headers.get("content-type") || "application/x-www-form-urlencoded" });
|
|
45
|
+
return new Request(this.url, {
|
|
46
|
+
method: this.method,
|
|
47
|
+
headers: this.headers,
|
|
48
|
+
body: blob
|
|
49
|
+
}).formData();
|
|
50
|
+
}
|
|
51
|
+
async json() {
|
|
52
|
+
const text = await this.text();
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
}
|
|
55
|
+
async text() {
|
|
56
|
+
const buffer = await this.arrayBuffer();
|
|
57
|
+
return this.textDecoder.decode(buffer);
|
|
58
|
+
}
|
|
59
|
+
async bytes() {
|
|
60
|
+
return this.arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
|
61
|
+
}
|
|
62
|
+
get body() {
|
|
63
|
+
if (this.cachedBody) return this.cachedBody;
|
|
64
|
+
if (this.bodyBuffer) {
|
|
65
|
+
const buffer = this.bodyBuffer;
|
|
66
|
+
this.cachedBody = new ReadableStream({ start(controller) {
|
|
67
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
68
|
+
controller.close();
|
|
69
|
+
} });
|
|
70
|
+
return this.cachedBody;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
get bodyUsed() {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
clone() {
|
|
78
|
+
return new SafeRequest(this.url, this.method, this.headers, this.bodyBuffer, {
|
|
79
|
+
cache: this.cache,
|
|
80
|
+
credentials: this.credentials,
|
|
81
|
+
destination: this.destination,
|
|
82
|
+
integrity: this.integrity,
|
|
83
|
+
keepalive: this.keepalive,
|
|
84
|
+
mode: this.mode,
|
|
85
|
+
redirect: this.redirect,
|
|
86
|
+
referrer: this.referrer,
|
|
87
|
+
referrerPolicy: this.referrerPolicy,
|
|
88
|
+
signal: this.signal
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Extracts data from a Request and creates a SafeRequest.
|
|
94
|
+
* Uses ReadableStream to safely read the body and avoid issues with certain Request implementations.
|
|
95
|
+
*/
|
|
96
|
+
async function makeSafeRequest(request) {
|
|
97
|
+
if (request instanceof SafeRequest) return request;
|
|
98
|
+
const url = request.url;
|
|
99
|
+
const method = request.method;
|
|
100
|
+
const headers = new Headers(request.headers);
|
|
101
|
+
let bodyBuffer = null;
|
|
102
|
+
const requestMethod = method.toUpperCase();
|
|
103
|
+
if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") try {
|
|
104
|
+
const body = request.body;
|
|
105
|
+
if (body) {
|
|
106
|
+
const reader = body.getReader();
|
|
107
|
+
const chunks = [];
|
|
108
|
+
while (true) {
|
|
109
|
+
const { done, value } = await reader.read();
|
|
110
|
+
if (done) break;
|
|
111
|
+
if (value) chunks.push(value);
|
|
112
|
+
}
|
|
113
|
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
114
|
+
const combined = new Uint8Array(totalLength);
|
|
115
|
+
let offset = 0;
|
|
116
|
+
for (const chunk of chunks) {
|
|
117
|
+
combined.set(chunk, offset);
|
|
118
|
+
offset += chunk.length;
|
|
119
|
+
}
|
|
120
|
+
bodyBuffer = combined.buffer;
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn("Failed to read request body via stream:", error);
|
|
124
|
+
}
|
|
125
|
+
const options = {};
|
|
126
|
+
try {
|
|
127
|
+
options.cache = request.cache;
|
|
128
|
+
} catch {}
|
|
129
|
+
try {
|
|
130
|
+
options.credentials = request.credentials;
|
|
131
|
+
} catch {}
|
|
132
|
+
try {
|
|
133
|
+
options.destination = request.destination;
|
|
134
|
+
} catch {}
|
|
135
|
+
try {
|
|
136
|
+
options.integrity = request.integrity;
|
|
137
|
+
} catch {}
|
|
138
|
+
try {
|
|
139
|
+
options.keepalive = request.keepalive;
|
|
140
|
+
} catch {}
|
|
141
|
+
try {
|
|
142
|
+
options.mode = request.mode;
|
|
143
|
+
} catch {}
|
|
144
|
+
try {
|
|
145
|
+
options.redirect = request.redirect;
|
|
146
|
+
} catch {}
|
|
147
|
+
try {
|
|
148
|
+
options.referrer = request.referrer;
|
|
149
|
+
} catch {}
|
|
150
|
+
try {
|
|
151
|
+
options.referrerPolicy = request.referrerPolicy;
|
|
152
|
+
} catch {}
|
|
153
|
+
try {
|
|
154
|
+
options.signal = request.signal;
|
|
155
|
+
} catch {}
|
|
156
|
+
return new SafeRequest(url, method, headers, bodyBuffer, options);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { SafeRequest, makeSafeRequest };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/router/types.d.ts
|
|
2
|
+
type ExtractParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? { readonly [K in Param]: string } & ExtractParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? { readonly [K in Param]: string } : Record<string, never>;
|
|
3
|
+
type VariableMap = Record<string, unknown>;
|
|
4
|
+
interface RouterEnvironment<TVariables extends VariableMap = VariableMap> {
|
|
5
|
+
Variables: TVariables;
|
|
6
|
+
}
|
|
7
|
+
interface CookieOptions {
|
|
8
|
+
domain?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
expires?: Date;
|
|
11
|
+
maxAge?: number;
|
|
12
|
+
httpOnly?: boolean;
|
|
13
|
+
secure?: boolean;
|
|
14
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
15
|
+
}
|
|
16
|
+
interface RouterContext<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> {
|
|
17
|
+
readonly request: Request;
|
|
18
|
+
readonly params: Readonly<TParams>;
|
|
19
|
+
readonly searchParams: Readonly<URLSearchParams>;
|
|
20
|
+
query<K extends string>(key: K): string | undefined;
|
|
21
|
+
header<K extends string>(key: K): string | undefined;
|
|
22
|
+
cookie<K extends string>(key: K): string | undefined;
|
|
23
|
+
formData(): Promise<FormData>;
|
|
24
|
+
parseJson<T = unknown>(): Promise<T>;
|
|
25
|
+
json<T>(data: T, init?: ResponseInit): Response;
|
|
26
|
+
redirect(url: string | URL, status?: 300 | 301 | 302 | 303 | 307 | 308): Response;
|
|
27
|
+
text(data: string, init?: ResponseInit): Response;
|
|
28
|
+
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
29
|
+
deleteCookie(name: string, options?: Pick<CookieOptions, "domain" | "path">): void;
|
|
30
|
+
newResponse(body?: BodyInit, init?: ResponseInit): Response;
|
|
31
|
+
set<K extends keyof TVariables>(key: K, value: TVariables[K]): void;
|
|
32
|
+
get<K extends keyof TVariables>(key: K): TVariables[K];
|
|
33
|
+
has<K extends keyof TVariables>(key: K): key is K;
|
|
34
|
+
}
|
|
35
|
+
type RouteHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<TParams, TVariables>) => Promise<Response> | Response;
|
|
36
|
+
type MiddlewareHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<TParams, TVariables>, next: () => Promise<Response> | Response) => Promise<Response> | Response;
|
|
37
|
+
type EnhancedRouteHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = {
|
|
38
|
+
handler: RouteHandler<TParams, TVariables>;
|
|
39
|
+
middleware?: MiddlewareHandler<TParams, TVariables>[];
|
|
40
|
+
};
|
|
41
|
+
type AnyHandler<TParams extends Record<string, string>, TVariables extends VariableMap = VariableMap> = RouteHandler<TParams, TVariables> | EnhancedRouteHandler<TParams, TVariables>;
|
|
42
|
+
type HttpMethod = "GET" | "POST";
|
|
43
|
+
interface CompiledRoute {
|
|
44
|
+
regex: RegExp;
|
|
45
|
+
paramNames: string[];
|
|
46
|
+
pattern: string;
|
|
47
|
+
}
|
|
48
|
+
interface MatchResult {
|
|
49
|
+
params: Record<string, string>;
|
|
50
|
+
pattern: string;
|
|
51
|
+
}
|
|
52
|
+
interface RouteDefinition<TVariables extends VariableMap = VariableMap> {
|
|
53
|
+
method: HttpMethod;
|
|
54
|
+
pattern: string;
|
|
55
|
+
handler: RouteHandler<Record<string, string>, TVariables>;
|
|
56
|
+
middleware: MiddlewareHandler<Record<string, string>, TVariables>[];
|
|
57
|
+
compiled: CompiledRoute;
|
|
58
|
+
}
|
|
59
|
+
interface RouterOptions {
|
|
60
|
+
caseSensitive?: boolean;
|
|
61
|
+
strict?: boolean;
|
|
62
|
+
basePath?: string;
|
|
63
|
+
}
|
|
64
|
+
type ErrorHandler<TVariables extends VariableMap = VariableMap> = (error: Error, ctx: RouterContext<Record<string, string>, TVariables>) => Promise<Response> | Response;
|
|
65
|
+
type GlobalMiddleware<TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<Record<string, string>, TVariables>, next: () => Promise<Response> | Response) => Promise<Response> | Response;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { AnyHandler, CompiledRoute, CookieOptions, EnhancedRouteHandler, ErrorHandler, ExtractParams, GlobalMiddleware, HttpMethod, MatchResult, MiddlewareHandler, RouteDefinition, RouteHandler, RouterContext, RouterEnvironment, RouterOptions, VariableMap };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VariableMap } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/variables.d.ts
|
|
4
|
+
declare class ContextVariableManager<TVariables extends VariableMap = VariableMap> {
|
|
5
|
+
private readonly state;
|
|
6
|
+
constructor(initialVariables?: Partial<TVariables>);
|
|
7
|
+
set<K extends keyof TVariables>(key: K, value: TVariables[K]): void;
|
|
8
|
+
get<K extends keyof TVariables>(key: K): TVariables[K];
|
|
9
|
+
has<K extends keyof TVariables>(key: K): key is K;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ContextVariableManager };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/router/variables.ts
|
|
2
|
+
var ContextVariableManager = class {
|
|
3
|
+
state = /* @__PURE__ */ new Map();
|
|
4
|
+
constructor(initialVariables) {
|
|
5
|
+
if (initialVariables) for (const [key, value] of Object.entries(initialVariables)) this.state.set(key, value);
|
|
6
|
+
}
|
|
7
|
+
set(key, value) {
|
|
8
|
+
if (typeof key !== "string" || key.length === 0) throw new Error("Variable key must be a non-empty string");
|
|
9
|
+
this.state.set(key, value);
|
|
10
|
+
}
|
|
11
|
+
get(key) {
|
|
12
|
+
return this.state.get(key);
|
|
13
|
+
}
|
|
14
|
+
has(key) {
|
|
15
|
+
return this.state.has(key);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ContextVariableManager };
|
package/dist/ui/code.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { run } from "../util.mjs";
|
|
1
2
|
import { Layout, renderToHTML } from "./base.mjs";
|
|
2
3
|
import { FormAlert } from "./form.mjs";
|
|
3
4
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
@@ -57,10 +58,13 @@ const CodeUI = (options) => {
|
|
|
57
58
|
"data-component": "form",
|
|
58
59
|
method: "post",
|
|
59
60
|
children: [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
run(() => {
|
|
62
|
+
if (success) return /* @__PURE__ */ jsx(FormAlert, {
|
|
63
|
+
message: success.message,
|
|
64
|
+
color: "success"
|
|
65
|
+
});
|
|
66
|
+
return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
|
|
67
|
+
}),
|
|
64
68
|
/* @__PURE__ */ jsx("input", {
|
|
65
69
|
"data-component": "input",
|
|
66
70
|
type: mode === "email" ? "email" : "tel",
|
|
@@ -97,10 +101,13 @@ const CodeUI = (options) => {
|
|
|
97
101
|
"data-component": "form",
|
|
98
102
|
method: "post",
|
|
99
103
|
children: [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
run(() => {
|
|
105
|
+
if (success) return /* @__PURE__ */ jsx(FormAlert, {
|
|
106
|
+
message: success.message,
|
|
107
|
+
color: "success"
|
|
108
|
+
});
|
|
109
|
+
return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
|
|
110
|
+
}),
|
|
104
111
|
/* @__PURE__ */ jsx("input", {
|
|
105
112
|
name: "action",
|
|
106
113
|
type: "hidden",
|
package/dist/ui/magiclink.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { run } from "../util.mjs";
|
|
1
2
|
import { Layout, renderToHTML } from "./base.mjs";
|
|
2
3
|
import { FormAlert } from "./form.mjs";
|
|
3
4
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
@@ -55,10 +56,13 @@ const MagicLinkUI = (options) => {
|
|
|
55
56
|
"data-component": "form",
|
|
56
57
|
method: "post",
|
|
57
58
|
children: [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
run(() => {
|
|
60
|
+
if (success) return /* @__PURE__ */ jsx(FormAlert, {
|
|
61
|
+
message: success.message,
|
|
62
|
+
color: "success"
|
|
63
|
+
});
|
|
64
|
+
return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
|
|
65
|
+
}),
|
|
62
66
|
/* @__PURE__ */ jsx("input", {
|
|
63
67
|
"data-component": "input",
|
|
64
68
|
type: mode === "email" ? "email" : "tel",
|
|
@@ -99,10 +103,13 @@ const MagicLinkUI = (options) => {
|
|
|
99
103
|
"data-component": "title",
|
|
100
104
|
children: "Check your email"
|
|
101
105
|
}),
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
run(() => {
|
|
107
|
+
if (success) return /* @__PURE__ */ jsx(FormAlert, {
|
|
108
|
+
message: success.message,
|
|
109
|
+
color: "success"
|
|
110
|
+
});
|
|
111
|
+
return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
|
|
112
|
+
}),
|
|
106
113
|
/* @__PURE__ */ jsx("p", {
|
|
107
114
|
"data-component": "description",
|
|
108
115
|
children: "Click the link in your email to sign in."
|
package/dist/ui/passkey.d.mts
CHANGED
|
@@ -12,10 +12,12 @@ interface PasskeyUICopy {
|
|
|
12
12
|
readonly register_prompt: string;
|
|
13
13
|
readonly login_prompt: string;
|
|
14
14
|
readonly login: string;
|
|
15
|
-
readonly change_prompt: string;
|
|
16
|
-
readonly code_resend: string;
|
|
17
|
-
readonly code_return: string;
|
|
18
15
|
readonly input_email: string;
|
|
16
|
+
readonly error_register_already_registered: string;
|
|
17
|
+
readonly error_register_cancelled: string;
|
|
18
|
+
readonly error_register_failed: string;
|
|
19
|
+
readonly error_auth_cancelled: string;
|
|
20
|
+
readonly error_auth_failed: string;
|
|
19
21
|
}
|
|
20
22
|
interface PasskeyUIOptions extends Omit<PasskeyProviderConfig, "authorize" | "register" | "copy"> {
|
|
21
23
|
readonly copy?: Partial<PasskeyUICopy>;
|