next-sanctum 0.1.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/LICENSE +21 -0
- package/README.md +647 -0
- package/dist/actions.cjs +236 -0
- package/dist/actions.d.cts +81 -0
- package/dist/actions.d.ts +81 -0
- package/dist/actions.js +228 -0
- package/dist/index.cjs +1395 -0
- package/dist/index.d.cts +508 -0
- package/dist/index.d.ts +508 -0
- package/dist/index.js +1379 -0
- package/dist/proxy.cjs +49 -0
- package/dist/proxy.d.cts +29 -0
- package/dist/proxy.d.ts +29 -0
- package/dist/proxy.js +47 -0
- package/dist/server.cjs +358 -0
- package/dist/server.d.cts +78 -0
- package/dist/server.d.ts +78 -0
- package/dist/server.js +353 -0
- package/package.json +140 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Error types for next-sanctum. All failures are normalized to `SanctumError`
|
|
6
|
+
* so consumers can handle them consistently (see plan §10: errors must not leak).
|
|
7
|
+
*/ /** Base error for all module failures. */ class SanctumError extends Error {
|
|
8
|
+
constructor(message, options){
|
|
9
|
+
super(message, {
|
|
10
|
+
cause: options.cause
|
|
11
|
+
});
|
|
12
|
+
this.name = "SanctumError";
|
|
13
|
+
this.kind = options.kind;
|
|
14
|
+
this.status = options.status;
|
|
15
|
+
this.data = options.data;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Invalid configuration — fail-fast on init (see resolveConfig). */ class ConfigError extends SanctumError {
|
|
19
|
+
constructor(message, cause){
|
|
20
|
+
super(message, {
|
|
21
|
+
kind: "config",
|
|
22
|
+
cause
|
|
23
|
+
});
|
|
24
|
+
this.name = "ConfigError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the server-side base URL (`SANCTUM_BASE_URL`, falling back to the public var
|
|
30
|
+
* for dev). Shared by `server.ts`/`actions.ts`. Fails fast with a clear ConfigError.
|
|
31
|
+
*/ function resolveServerBaseUrl(explicit, label = "server helpers") {
|
|
32
|
+
const baseUrl = explicit ?? process.env.SANCTUM_BASE_URL ?? process.env.NEXT_PUBLIC_SANCTUM_BASE_URL;
|
|
33
|
+
if (!baseUrl) {
|
|
34
|
+
throw new ConfigError(`SANCTUM_BASE_URL (server) is not set for next-sanctum ${label}.`);
|
|
35
|
+
}
|
|
36
|
+
return baseUrl.replace(/\/+$/, "");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Open-redirect protection. Only allows SAME-ORIGIN destinations (a relative path
|
|
41
|
+
* or an absolute URL with the same origin), with an optional path allowlist.
|
|
42
|
+
* See PRD §12.1 (Open redirect) & §18 (a unit test is required).
|
|
43
|
+
*/ /** Reject backslashes and any control character (Tab/LF/CR are stripped by the URL
|
|
44
|
+
* parser, which could turn `/\t/evil.com` into `//evil.com` and bypass the `//` guard). */ function hasUnsafeChars(value) {
|
|
45
|
+
for(let i = 0; i < value.length; i++){
|
|
46
|
+
const code = value.charCodeAt(i);
|
|
47
|
+
if (code <= 0x1f || code === 0x7f) return true;
|
|
48
|
+
if (value[i] === "\\") return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Return `target` when it is safe (same-origin), otherwise `fallback`.
|
|
54
|
+
* Rejects: `//evil.com`, `https://evil.com`, the `javascript:` scheme, control-char
|
|
55
|
+
* injection (`/\t//evil.com`), and backslash tricks.
|
|
56
|
+
*/ function safeRedirect(target, fallback, options = {}) {
|
|
57
|
+
if (!target) return fallback;
|
|
58
|
+
const trimmed = target.trim();
|
|
59
|
+
if (trimmed === "") return fallback;
|
|
60
|
+
if (hasUnsafeChars(trimmed)) return fallback;
|
|
61
|
+
let path = null;
|
|
62
|
+
if (trimmed.startsWith("/")) {
|
|
63
|
+
// Reject protocol-relative (//evil.com).
|
|
64
|
+
if (trimmed.startsWith("//")) return fallback;
|
|
65
|
+
path = trimmed;
|
|
66
|
+
} else if (options.origin) {
|
|
67
|
+
try {
|
|
68
|
+
const url = new URL(trimmed);
|
|
69
|
+
const base = new URL(options.origin);
|
|
70
|
+
if (url.origin === base.origin) {
|
|
71
|
+
path = url.pathname + url.search + url.hash;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// not a valid URL → reject
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (path === null) return fallback;
|
|
78
|
+
// Defense-in-depth: resolve the path and confirm it stays same-origin.
|
|
79
|
+
try {
|
|
80
|
+
const base = options.origin ?? "https://sanctum.invalid";
|
|
81
|
+
if (new URL(path, base).origin !== new URL(base).origin) return fallback;
|
|
82
|
+
} catch {
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
if (options.allowList && options.allowList.length > 0) {
|
|
86
|
+
const safe = path;
|
|
87
|
+
const allowed = options.allowList.some((prefix)=>safe === prefix || safe.startsWith(prefix));
|
|
88
|
+
if (!allowed) return fallback;
|
|
89
|
+
}
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** HTTP methods that require CSRF protection in cookie mode. */ const STATEFUL_METHODS = new Set([
|
|
94
|
+
"POST",
|
|
95
|
+
"PUT",
|
|
96
|
+
"PATCH",
|
|
97
|
+
"DELETE"
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Shared Set-Cookie parsing helpers (pure — no `next`/`server-only` imports, so this
|
|
102
|
+
* stays isomorphic and tree-shakes out of the client graph). Used by `server.ts` and
|
|
103
|
+
* `actions.ts` to mirror Laravel's Set-Cookie into Next's writable cookie store.
|
|
104
|
+
*/ /**
|
|
105
|
+
* Read all Set-Cookie headers as an array. Prefers `Headers.getSetCookie()` (Node ≥18.14,
|
|
106
|
+
* all supported Next runtimes). The single-header fallback only handles one cookie — we do
|
|
107
|
+
* NOT attempt to comma-split, which is unreliable (Expires dates contain commas).
|
|
108
|
+
*/ function getSetCookies(headers) {
|
|
109
|
+
const anyHeaders = headers;
|
|
110
|
+
if (typeof anyHeaders.getSetCookie === "function") return anyHeaders.getSetCookie();
|
|
111
|
+
const value = headers.get("set-cookie");
|
|
112
|
+
return value ? [
|
|
113
|
+
value
|
|
114
|
+
] : [];
|
|
115
|
+
}
|
|
116
|
+
/** Parse a single Set-Cookie header. Validates attributes; returns null when unusable. */ function parseSetCookie(header) {
|
|
117
|
+
const segments = header.split(";");
|
|
118
|
+
const first = segments.shift();
|
|
119
|
+
if (!first) return null;
|
|
120
|
+
const eq = first.indexOf("=");
|
|
121
|
+
if (eq === -1) return null;
|
|
122
|
+
const name = first.slice(0, eq).trim();
|
|
123
|
+
if (name === "") return null;
|
|
124
|
+
let value = first.slice(eq + 1).trim();
|
|
125
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
126
|
+
value = value.slice(1, -1);
|
|
127
|
+
}
|
|
128
|
+
const options = {};
|
|
129
|
+
for (const segment of segments){
|
|
130
|
+
const idx = segment.indexOf("=");
|
|
131
|
+
const key = (idx === -1 ? segment : segment.slice(0, idx)).trim().toLowerCase();
|
|
132
|
+
const val = idx === -1 ? "" : segment.slice(idx + 1).trim();
|
|
133
|
+
switch(key){
|
|
134
|
+
case "path":
|
|
135
|
+
options.path = val;
|
|
136
|
+
break;
|
|
137
|
+
case "domain":
|
|
138
|
+
options.domain = val;
|
|
139
|
+
break;
|
|
140
|
+
case "max-age":
|
|
141
|
+
{
|
|
142
|
+
const n = Number(val);
|
|
143
|
+
if (val !== "" && Number.isFinite(n)) options.maxAge = n;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "expires":
|
|
147
|
+
{
|
|
148
|
+
const d = new Date(val);
|
|
149
|
+
if (!Number.isNaN(d.getTime())) options.expires = d;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "samesite":
|
|
153
|
+
{
|
|
154
|
+
const lower = val.toLowerCase();
|
|
155
|
+
if (lower === "lax" || lower === "strict" || lower === "none") {
|
|
156
|
+
options.sameSite = lower;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "secure":
|
|
161
|
+
options.secure = true;
|
|
162
|
+
break;
|
|
163
|
+
case "httponly":
|
|
164
|
+
options.httpOnly = true;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
value,
|
|
171
|
+
options
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Mirror a response's Set-Cookie headers into a writable cookie store. */ function applySetCookies(store, response) {
|
|
175
|
+
for (const raw of getSetCookies(response.headers)){
|
|
176
|
+
const parsed = parseSetCookie(raw);
|
|
177
|
+
if (!parsed) continue;
|
|
178
|
+
try {
|
|
179
|
+
store.set(parsed.name, parsed.value, parsed.options);
|
|
180
|
+
} catch {
|
|
181
|
+
// store is read-only (e.g. called from a Server Component) — ignore.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const CSRF_COOKIE_ENDPOINT = "/sanctum/csrf-cookie";
|
|
187
|
+
/**
|
|
188
|
+
* Anti-SSRF: an absolute URL is only allowed when it matches the configured base
|
|
189
|
+
* origin, otherwise the (cookie-bearing) request could be aimed at an arbitrary host.
|
|
190
|
+
*/ function buildServerUrl(baseUrl, path) {
|
|
191
|
+
if (/^https?:\/\//i.test(path)) {
|
|
192
|
+
if (new URL(path).origin !== new URL(baseUrl).origin) {
|
|
193
|
+
throw new ConfigError("serverFetch: an absolute URL must match the configured base URL origin (anti-SSRF).");
|
|
194
|
+
}
|
|
195
|
+
return path;
|
|
196
|
+
}
|
|
197
|
+
return `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Authenticated fetch from a SERVER context (Server Component / Route Handler /
|
|
201
|
+
* Server Action). Forwards cookies from `await cookies()` to Laravel. For stateful
|
|
202
|
+
* requests it includes the CSRF header, bootstrapping the CSRF cookie when missing
|
|
203
|
+
* (the bootstrap can only persist cookies from a Server Action / Route Handler).
|
|
204
|
+
*/ async function serverFetch(path, init = {}) {
|
|
205
|
+
const baseUrl = resolveServerBaseUrl(init.baseUrl);
|
|
206
|
+
const cookieStore = await cookies();
|
|
207
|
+
const { json, body: rawBody, baseUrl: _baseUrl, csrf, ...rest } = init;
|
|
208
|
+
const headers = new Headers(rest.headers);
|
|
209
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
210
|
+
// Sanctum's statefulApi() only treats a request as first-party (session/cookie
|
|
211
|
+
// auth) when its Origin/Referer matches a stateful domain. A server-side fetch
|
|
212
|
+
// carries no browser Origin, so present the API's own origin — which Sanctum
|
|
213
|
+
// always includes in its stateful domains by default — so SSR cookie auth
|
|
214
|
+
// (e.g. getUser() in a Server Component) is recognised instead of 401-ing.
|
|
215
|
+
if (!headers.has("origin")) headers.set("origin", new URL(baseUrl).origin);
|
|
216
|
+
const method = (rest.method ?? "GET").toUpperCase();
|
|
217
|
+
const csrfCookieName = csrf?.cookie ?? "XSRF-TOKEN";
|
|
218
|
+
const csrfHeaderName = csrf?.header ?? "X-XSRF-TOKEN";
|
|
219
|
+
if (STATEFUL_METHODS.has(method)) {
|
|
220
|
+
let token = cookieStore.get(csrfCookieName)?.value;
|
|
221
|
+
if (!token) {
|
|
222
|
+
const csrfResponse = await fetch(`${baseUrl}${CSRF_COOKIE_ENDPOINT}`, {
|
|
223
|
+
headers: {
|
|
224
|
+
cookie: cookieStore.toString(),
|
|
225
|
+
accept: "application/json"
|
|
226
|
+
},
|
|
227
|
+
cache: "no-store"
|
|
228
|
+
});
|
|
229
|
+
applySetCookies(cookieStore, csrfResponse);
|
|
230
|
+
token = cookieStore.get(csrfCookieName)?.value;
|
|
231
|
+
}
|
|
232
|
+
if (token && !headers.has(csrfHeaderName)) {
|
|
233
|
+
headers.set(csrfHeaderName, decodeURIComponent(token));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const cookieHeader = cookieStore.toString();
|
|
237
|
+
if (cookieHeader) headers.set("cookie", cookieHeader);
|
|
238
|
+
let body = rawBody ?? null;
|
|
239
|
+
if (json !== undefined) {
|
|
240
|
+
body = JSON.stringify(json);
|
|
241
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
242
|
+
}
|
|
243
|
+
return fetch(buildServerUrl(baseUrl, path), {
|
|
244
|
+
...rest,
|
|
245
|
+
method,
|
|
246
|
+
headers,
|
|
247
|
+
body,
|
|
248
|
+
cache: rest.cache ?? "no-store"
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Fetch the authenticated user on the server (forwards cookies). The result is passed
|
|
253
|
+
* as `initialUser` to SanctumProvider to prevent a hydration mismatch. Network/parse
|
|
254
|
+
* errors resolve to `null` (treated as logged-out) so SSR doesn't crash; a missing
|
|
255
|
+
* `SANCTUM_BASE_URL` still throws (fail-fast).
|
|
256
|
+
*/ async function getUser(options = {}) {
|
|
257
|
+
try {
|
|
258
|
+
const response = await serverFetch(options.endpoint ?? "/api/user", {
|
|
259
|
+
method: "GET",
|
|
260
|
+
baseUrl: options.baseUrl
|
|
261
|
+
});
|
|
262
|
+
if (!response.ok) return null;
|
|
263
|
+
return await response.json();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof ConfigError) throw error;
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const FORWARD_REQUEST_HEADERS = [
|
|
270
|
+
"accept",
|
|
271
|
+
"accept-language",
|
|
272
|
+
"content-type",
|
|
273
|
+
"authorization",
|
|
274
|
+
"x-xsrf-token",
|
|
275
|
+
"x-requested-with",
|
|
276
|
+
// Sanctum's SPA (cookie) auth only treats a request as "stateful" when it
|
|
277
|
+
// carries an Origin or Referer matching SANCTUM_STATEFUL_DOMAINS. Forwarding
|
|
278
|
+
// them lets the canonical `routes/api.php` + `auth:sanctum` pattern work
|
|
279
|
+
// through this proxy. Safe: `upstream` is pinned (anti-SSRF preserved).
|
|
280
|
+
"origin",
|
|
281
|
+
"referer"
|
|
282
|
+
];
|
|
283
|
+
// Allowlist of response headers forwarded to the client — internal/debug headers
|
|
284
|
+
// (Server, X-Powered-By, X-Debug-*, rate-limit internals, …) are stripped by default.
|
|
285
|
+
const FORWARD_RESPONSE_HEADERS = [
|
|
286
|
+
"content-type",
|
|
287
|
+
"content-language",
|
|
288
|
+
"content-disposition",
|
|
289
|
+
"cache-control",
|
|
290
|
+
"etag",
|
|
291
|
+
"expires",
|
|
292
|
+
"last-modified",
|
|
293
|
+
"location",
|
|
294
|
+
"vary",
|
|
295
|
+
"www-authenticate"
|
|
296
|
+
];
|
|
297
|
+
/**
|
|
298
|
+
* Create a catch-all Route Handler (`app/api/sanctum/[...path]/route.ts`) that
|
|
299
|
+
* forwards requests to Laravel via the Next domain. **Anti-SSRF**: `upstream` is pinned,
|
|
300
|
+
* path traversal (`..`, `://`, backslash) is rejected, and only an allowlist of
|
|
301
|
+
* response headers (plus Set-Cookie) is forwarded — internal headers are not leaked.
|
|
302
|
+
*/ function createSanctumRouteProxy(options) {
|
|
303
|
+
const upstream = options.upstream.replace(/\/+$/, "");
|
|
304
|
+
if (!/^https?:\/\//i.test(upstream)) {
|
|
305
|
+
throw new ConfigError("createSanctumRouteProxy: `upstream` must be an absolute http(s) URL (anti-SSRF).");
|
|
306
|
+
}
|
|
307
|
+
const forwardCookies = options.forwardCookies ?? true;
|
|
308
|
+
return async function handler(request, context) {
|
|
309
|
+
const { path = [] } = await context.params;
|
|
310
|
+
for (const segment of path){
|
|
311
|
+
if (segment === ".." || segment === "." || segment.includes("\\") || segment.includes("://")) {
|
|
312
|
+
return new Response("Bad request", {
|
|
313
|
+
status: 400
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const suffix = path.map(encodeURIComponent).join("/");
|
|
318
|
+
const search = new URL(request.url).search;
|
|
319
|
+
const target = `${upstream}/${suffix}${search}`;
|
|
320
|
+
const headers = new Headers();
|
|
321
|
+
for (const name of FORWARD_REQUEST_HEADERS){
|
|
322
|
+
const value = request.headers.get(name);
|
|
323
|
+
if (value) headers.set(name, value);
|
|
324
|
+
}
|
|
325
|
+
if (forwardCookies) {
|
|
326
|
+
const cookie = request.headers.get("cookie");
|
|
327
|
+
if (cookie) headers.set("cookie", cookie);
|
|
328
|
+
}
|
|
329
|
+
const method = request.method.toUpperCase();
|
|
330
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
331
|
+
const body = hasBody ? await request.arrayBuffer() : undefined;
|
|
332
|
+
const upstreamResponse = await fetch(target, {
|
|
333
|
+
method,
|
|
334
|
+
headers,
|
|
335
|
+
body,
|
|
336
|
+
redirect: "manual"
|
|
337
|
+
});
|
|
338
|
+
const responseHeaders = new Headers();
|
|
339
|
+
for (const name of FORWARD_RESPONSE_HEADERS){
|
|
340
|
+
const value = upstreamResponse.headers.get(name);
|
|
341
|
+
if (value) responseHeaders.set(name, value);
|
|
342
|
+
}
|
|
343
|
+
for (const cookie of getSetCookies(upstreamResponse.headers)){
|
|
344
|
+
responseHeaders.append("set-cookie", cookie);
|
|
345
|
+
}
|
|
346
|
+
return new Response(upstreamResponse.body, {
|
|
347
|
+
status: upstreamResponse.status,
|
|
348
|
+
headers: responseHeaders
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export { createSanctumRouteProxy, getUser, safeRedirect, serverFetch };
|
package/package.json
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-sanctum",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "aliziodev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/aliziodev/next-sanctum.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/aliziodev/next-sanctum#readme",
|
|
12
|
+
"bugs": "https://github.com/aliziodev/next-sanctum/issues",
|
|
13
|
+
"description": "Complete Laravel Fortify + Sanctum authentication for Next.js — cookie/CSRF SPA + token, SSR + Client, 2FA, passkeys, Proxy route protection.",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"next",
|
|
16
|
+
"nextjs",
|
|
17
|
+
"laravel",
|
|
18
|
+
"sanctum",
|
|
19
|
+
"fortify",
|
|
20
|
+
"authentication",
|
|
21
|
+
"csrf",
|
|
22
|
+
"2fa",
|
|
23
|
+
"passkeys",
|
|
24
|
+
"app-router"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.18.0"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/index.d.cts",
|
|
43
|
+
"default": "./dist/index.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"./server": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/server.d.ts",
|
|
49
|
+
"default": "./dist/server.js"
|
|
50
|
+
},
|
|
51
|
+
"require": {
|
|
52
|
+
"types": "./dist/server.d.cts",
|
|
53
|
+
"default": "./dist/server.cjs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"./proxy": {
|
|
57
|
+
"import": {
|
|
58
|
+
"types": "./dist/proxy.d.ts",
|
|
59
|
+
"default": "./dist/proxy.js"
|
|
60
|
+
},
|
|
61
|
+
"require": {
|
|
62
|
+
"types": "./dist/proxy.d.cts",
|
|
63
|
+
"default": "./dist/proxy.cjs"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"./actions": {
|
|
67
|
+
"import": {
|
|
68
|
+
"types": "./dist/actions.d.ts",
|
|
69
|
+
"default": "./dist/actions.js"
|
|
70
|
+
},
|
|
71
|
+
"require": {
|
|
72
|
+
"types": "./dist/actions.d.cts",
|
|
73
|
+
"default": "./dist/actions.cjs"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"./package.json": "./package.json"
|
|
77
|
+
},
|
|
78
|
+
"main": "./dist/index.cjs",
|
|
79
|
+
"module": "./dist/index.js",
|
|
80
|
+
"types": "./dist/index.d.ts",
|
|
81
|
+
"typesVersions": {
|
|
82
|
+
"*": {
|
|
83
|
+
"server": [
|
|
84
|
+
"./dist/server.d.ts"
|
|
85
|
+
],
|
|
86
|
+
"proxy": [
|
|
87
|
+
"./dist/proxy.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"actions": [
|
|
90
|
+
"./dist/actions.d.ts"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "bunchee",
|
|
96
|
+
"dev": "bunchee --watch",
|
|
97
|
+
"clean": "rimraf dist",
|
|
98
|
+
"typecheck": "tsc --noEmit",
|
|
99
|
+
"lint": "eslint .",
|
|
100
|
+
"test": "vitest run",
|
|
101
|
+
"test:watch": "vitest",
|
|
102
|
+
"test:coverage": "vitest run --coverage",
|
|
103
|
+
"attw": "attw --pack .",
|
|
104
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build && pnpm run attw"
|
|
105
|
+
},
|
|
106
|
+
"peerDependencies": {
|
|
107
|
+
"@laravel/passkeys": "*",
|
|
108
|
+
"next": ">=15.0.0 <17.0.0",
|
|
109
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
110
|
+
"react-dom": "^18.2.0 || ^19.0.0"
|
|
111
|
+
},
|
|
112
|
+
"peerDependenciesMeta": {
|
|
113
|
+
"@laravel/passkeys": {
|
|
114
|
+
"optional": true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"server-only": "^0.0.1"
|
|
119
|
+
},
|
|
120
|
+
"devDependencies": {
|
|
121
|
+
"@arethetypeswrong/cli": "^0.18.0",
|
|
122
|
+
"@eslint/js": "^9.0.0",
|
|
123
|
+
"@testing-library/react": "^16.0.0",
|
|
124
|
+
"@types/node": "^20.0.0",
|
|
125
|
+
"@types/react": "^19.0.0",
|
|
126
|
+
"@types/react-dom": "^19.0.0",
|
|
127
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
128
|
+
"bunchee": "^6.4.0",
|
|
129
|
+
"eslint": "^9.0.0",
|
|
130
|
+
"happy-dom": "^15.0.0",
|
|
131
|
+
"msw": "^2.7.0",
|
|
132
|
+
"next": "16.2.6",
|
|
133
|
+
"react": "19.2.4",
|
|
134
|
+
"react-dom": "19.2.4",
|
|
135
|
+
"rimraf": "^6.0.0",
|
|
136
|
+
"typescript": "^5.6.0",
|
|
137
|
+
"typescript-eslint": "^8.0.0",
|
|
138
|
+
"vitest": "^3.0.0"
|
|
139
|
+
}
|
|
140
|
+
}
|