Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

remix-utils

Package Overview
Dependencies
Maintainers
1
Versions
59
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

remix-utils - npm Package Compare versions

Comparing version 6.6.0 to 7.0.0

build/react/fetcher-type.d.ts

18

build/common.js

@@ -1,17 +0,1 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./common/promise"), exports);
export * from "./common/promise";
/**
* @see https://twitter.com/buildsghost/status/1507109734519750680
*/
export declare type PromiseHash = Record<string, Promise<unknown>>;
export declare type AwaitedPromiseHash<Hash extends PromiseHash> = {
[Key in keyof Hash]: Awaited<Hash[Key]>;
export type PromiseHash = Record<string, Promise<unknown>>;
export type AwaitedPromiseHash<Hash extends PromiseHash> = {
[Key in keyof Hash]: Awaited<Hash[Key]>;
};

@@ -36,3 +36,5 @@ /**

*/
export declare function promiseHash<Hash extends PromiseHash>(hash: Hash): Promise<AwaitedPromiseHash<Hash>>;
export declare function promiseHash<Hash extends PromiseHash>(
hash: Hash,
): Promise<AwaitedPromiseHash<Hash>>;
/**

@@ -71,6 +73,9 @@ * Attach a timeout to any promise, if the timeout resolves first ignore the

*/
export declare function timeout<Value>(promise: Promise<Value>, options: {
export declare function timeout<Value>(
promise: Promise<Value>,
options: {
controller?: AbortController;
ms: number;
}): Promise<Value>;
},
): Promise<Value>;
/**

@@ -88,3 +93,3 @@ * An error thrown when a timeout occurs

export declare class TimeoutError extends Error {
constructor(message: string);
constructor(message: string);
}

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeoutError = exports.timeout = exports.promiseHash = void 0;
/**

@@ -32,6 +29,9 @@ * Get a hash of promises and await them all.

*/
async function promiseHash(hash) {
return Object.fromEntries(await Promise.all(Object.entries(hash).map(async ([key, promise]) => [key, await promise])));
export async function promiseHash(hash) {
return Object.fromEntries(
await Promise.all(
Object.entries(hash).map(async ([key, promise]) => [key, await promise]),
),
);
}
exports.promiseHash = promiseHash;
/**

@@ -75,29 +75,24 @@ * Used to uniquely identify a timeout

*/
function timeout(promise, options) {
return new Promise(async (resolve, reject) => {
let timer = null;
try {
let result = await Promise.race([
promise,
new Promise((resolve) => {
timer = setTimeout(() => resolve(TIMEOUT), options.ms);
}),
]);
if (timer)
clearTimeout(timer);
if (result === TIMEOUT) {
if (options.controller)
options.controller.abort();
return reject(new TimeoutError(`Timed out after ${options.ms}ms`));
}
return resolve(result);
}
catch (error) {
if (timer)
clearTimeout(timer);
reject(error);
}
});
export function timeout(promise, options) {
return new Promise(async (resolve, reject) => {
let timer = null;
try {
let result = await Promise.race([
promise,
new Promise((resolve) => {
timer = setTimeout(() => resolve(TIMEOUT), options.ms);
}),
]);
if (timer) clearTimeout(timer);
if (result === TIMEOUT) {
if (options.controller) options.controller.abort();
return reject(new TimeoutError(`Timed out after ${options.ms}ms`));
}
return resolve(result);
} catch (error) {
if (timer) clearTimeout(timer);
reject(error);
}
});
}
exports.timeout = timeout;
/**

@@ -114,8 +109,7 @@ * An error thrown when a timeout occurs

*/
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
export class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
}
exports.TimeoutError = TimeoutError;

@@ -1,19 +0,3 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./common"), exports);
__exportStar(require("./react"), exports);
__exportStar(require("./server"), exports);
export * from "./common";
export * from "./react";
export * from "./server";
export * from "./react/cache-assets";
export * from "./react/client-only";
export * from "./react/csrf";
export * from "./react/dynamic-links";
export * from "./react/external-scripts";
export * from "./react/fetcher-type";
export * from "./react/honeypot-inputs";
export * from "./react/matches-type";
export * from "./react/server-only";
export * from "./react/structured-data";
export * from "./react/use-debounced-fetcher";
export * from "./react/use-delegated-anchors";
export * from "./react/use-event-source";

@@ -14,2 +16,1 @@ export * from "./react/use-global-pending-state";

export * from "./react/use-should-hydrate";
export * from "./react/use-delegated-anchors";

@@ -1,30 +0,15 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./react/cache-assets"), exports);
__exportStar(require("./react/client-only"), exports);
__exportStar(require("./react/csrf"), exports);
__exportStar(require("./react/dynamic-links"), exports);
__exportStar(require("./react/external-scripts"), exports);
__exportStar(require("./react/matches-type"), exports);
__exportStar(require("./react/server-only"), exports);
__exportStar(require("./react/structured-data"), exports);
__exportStar(require("./react/use-event-source"), exports);
__exportStar(require("./react/use-global-pending-state"), exports);
__exportStar(require("./react/use-hydrated"), exports);
__exportStar(require("./react/use-locales"), exports);
__exportStar(require("./react/use-should-hydrate"), exports);
__exportStar(require("./react/use-delegated-anchors"), exports);
export * from "./react/cache-assets";
export * from "./react/client-only";
export * from "./react/csrf";
export * from "./react/external-scripts";
export * from "./react/fetcher-type";
export * from "./react/honeypot-inputs";
export * from "./react/matches-type";
export * from "./react/server-only";
export * from "./react/use-debounced-fetcher";
export * from "./react/use-delegated-anchors";
export * from "./react/use-event-source";
export * from "./react/use-global-pending-state";
export * from "./react/use-hydrated";
export * from "./react/use-locales";
export * from "./react/use-should-hydrate";
export interface CacheAssetsOptions {
/**
* The name of the cache to use inside the browser Cache Storage
* @default "assets"
*/
cacheName?: string;
/**
* The path prefix for all build assets, if you used a subdomain ensure this
* is only the pathname part.
* @default "/build/"
*/
buildPath?: string;
/**
* The name of the cache to use inside the browser Cache Storage
* @default "assets"
*/
cacheName?: string;
/**
* The path prefix for all build assets, if you used a subdomain ensure this
* is only the pathname part.
* @default "/build/"
*/
buildPath?: string;
}

@@ -22,2 +22,5 @@ /**

*/
export declare function cacheAssets({ cacheName, buildPath, }?: CacheAssetsOptions): Promise<void>;
export declare function cacheAssets({
cacheName,
buildPath,
}?: CacheAssetsOptions): Promise<void>;

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cacheAssets = void 0;
/**

@@ -12,61 +9,64 @@ * Caches all JS files built by Remix in a browser cache.

*/
async function cacheAssets({ cacheName = "assets", buildPath = "/build/", } = {}) {
let paths = getFilePaths();
let cache = await caches.open(cacheName);
let urls = await getCachedUrls(cache, buildPath);
await removeOldAssets(cache, paths, urls);
await addNewAssets(cache, paths, urls);
export async function cacheAssets({
cacheName = "assets",
buildPath = "/build/",
} = {}) {
let paths = getFilePaths();
let cache = await caches.open(cacheName);
let urls = await getCachedUrls(cache, buildPath);
await removeOldAssets(cache, paths, urls);
await addNewAssets(cache, paths, urls);
}
exports.cacheAssets = cacheAssets;
function getFilePaths() {
try {
return unique([
...Object.values(window.__remixManifest.routes).flatMap((route) => {
var _a;
return [route.module, ...((_a = route.imports) !== null && _a !== void 0 ? _a : [])];
}),
window.__remixManifest.url,
window.__remixManifest.entry.module,
...window.__remixManifest.entry.imports,
]);
}
catch {
throw new Error("Failed to get file paths from Remix manifest");
}
try {
return unique([
...Object.values(window.__remixManifest.routes).flatMap((route) => {
var _a;
return [
route.module,
...((_a = route.imports) !== null && _a !== void 0 ? _a : []),
];
}),
window.__remixManifest.url,
window.__remixManifest.entry.module,
...window.__remixManifest.entry.imports,
]);
} catch {
throw new Error("Failed to get file paths from Remix manifest");
}
}
async function getCachedUrls(cache, buildPath = "/build/") {
try {
let keys = await cache.keys();
return keys
.map((key) => {
return new URL(key.url);
})
.filter((url) => url.hostname === window.location.hostname)
.map((url) => url.pathname)
.filter((pathname) => pathname.startsWith(buildPath));
}
catch {
throw new Error("Failed to retrieve cached URLs");
}
try {
let keys = await cache.keys();
return keys
.map((key) => {
return new URL(key.url);
})
.filter((url) => url.hostname === window.location.hostname)
.map((url) => url.pathname)
.filter((pathname) => pathname.startsWith(buildPath));
} catch {
throw new Error("Failed to retrieve cached URLs");
}
}
async function removeOldAssets(cache, paths, urls) {
try {
await Promise.all(urls
.filter((pathname) => !paths.includes(pathname))
.map((pathname) => cache.delete(pathname)));
}
catch {
throw new Error("Failed to remove old assets from the cache");
}
try {
await Promise.all(
urls
.filter((pathname) => !paths.includes(pathname))
.map((pathname) => cache.delete(pathname)),
);
} catch {
throw new Error("Failed to remove old assets from the cache");
}
}
async function addNewAssets(cache, paths, urls) {
try {
await cache.addAll(paths.filter((path) => !urls.includes(path)));
}
catch {
throw new Error("Failed to add new assets to the cache");
}
try {
await cache.addAll(paths.filter((path) => !urls.includes(path)));
} catch {
throw new Error("Failed to add new assets to the cache");
}
}
function unique(array) {
return [...new Set(array)];
return [...new Set(array)];
}
import { ReactNode } from "react";
declare type Props = {
/**
* You are encouraged to add a fallback that is the same dimensions
* as the client rendered children. This will avoid content layout
* shift which is disgusting
*/
children(): ReactNode;
fallback?: ReactNode;
type Props = {
/**
* You are encouraged to add a fallback that is the same dimensions
* as the client rendered children. This will avoid content layout
* shift which is disgusting
*/
children(): ReactNode;
fallback?: ReactNode;
};

@@ -11,0 +11,0 @@ /**

@@ -1,6 +0,3 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientOnly = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const use_hydrated_1 = require("./use-hydrated");
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useHydrated } from "./use-hydrated";
/**

@@ -21,5 +18,6 @@ * Render the children only after the JS has loaded client-side. Use an optional

*/
function ClientOnly({ children, fallback = null }) {
return (0, use_hydrated_1.useHydrated)() ? (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children() }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback });
export function ClientOnly({ children, fallback = null }) {
return useHydrated()
? _jsx(_Fragment, { children: children() })
: _jsx(_Fragment, { children: fallback });
}
exports.ClientOnly = ClientOnly;
import { ReactNode } from "react";
export interface AuthenticityTokenProviderProps {
children: ReactNode;
token: string;
children: ReactNode;
token: string;
}
export interface AuthenticityTokenInputProps {
name?: string;
name?: string;
}
/**
* Save the Authenticity Token into context
* Example: In the `root` add `<AuthenticityTokenProvider>`
* ```tsx
* let { csrf } = useLoaderData<{ csrf: string }>();
* @example
* // Add `<AuthenticityTokenProvider>` wrapping your Outlet
* let { csrf } = useLoaderData<typeof loader>();
* return (
* <AuthenticityTokenProvider token={csrf}>
* <Document>
* <Outlet />
* </Document>
* <Outlet />
* </AuthenticityTokenProvider>
* )'
* ```
* )
*/
export declare function AuthenticityTokenProvider({ children, token, }: AuthenticityTokenProviderProps): JSX.Element;
export declare function AuthenticityTokenProvider({
children,
token,
}: AuthenticityTokenProviderProps): JSX.Element;
/**

@@ -39,3 +39,4 @@ * Get the authenticity token, this should be used to send it in a submit.

* Render a hidden input with the name csrf and the authenticity token as value.
* ```tsx
* @example
* // Default usage
* return (

@@ -49,4 +50,8 @@ * <Form action="/login" method="post">

* );
* ```
* @example
* // Customizing the name
* <AuthenticityTokenInput name="authenticity_token" />
*/
export declare function AuthenticityTokenInput({ name, }: AuthenticityTokenInputProps): JSX.Element;
export declare function AuthenticityTokenInput({
name,
}: AuthenticityTokenInputProps): JSX.Element;

@@ -1,25 +0,18 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthenticityTokenInput = exports.useAuthenticityToken = exports.AuthenticityTokenProvider = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
let context = (0, react_1.createContext)(null);
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useContext } from "react";
let context = createContext(null);
/**
* Save the Authenticity Token into context
* Example: In the `root` add `<AuthenticityTokenProvider>`
* ```tsx
* let { csrf } = useLoaderData<{ csrf: string }>();
* @example
* // Add `<AuthenticityTokenProvider>` wrapping your Outlet
* let { csrf } = useLoaderData<typeof loader>();
* return (
* <AuthenticityTokenProvider token={csrf}>
* <Document>
* <Outlet />
* </Document>
* <Outlet />
* </AuthenticityTokenProvider>
* )'
* ```
* )
*/
function AuthenticityTokenProvider({ children, token, }) {
return (0, jsx_runtime_1.jsx)(context.Provider, { value: token, children: children });
export function AuthenticityTokenProvider({ children, token }) {
return _jsx(context.Provider, { value: token, children: children });
}
exports.AuthenticityTokenProvider = AuthenticityTokenProvider;
/**

@@ -37,12 +30,11 @@ * Get the authenticity token, this should be used to send it in a submit.

*/
function useAuthenticityToken() {
let token = (0, react_1.useContext)(context);
if (!token)
throw new Error("Missing AuthenticityTokenProvider.");
return token;
export function useAuthenticityToken() {
let token = useContext(context);
if (!token) throw new Error("Missing AuthenticityTokenProvider.");
return token;
}
exports.useAuthenticityToken = useAuthenticityToken;
/**
* Render a hidden input with the name csrf and the authenticity token as value.
* ```tsx
* @example
* // Default usage
* return (

@@ -56,8 +48,9 @@ * <Form action="/login" method="post">

* );
* ```
* @example
* // Customizing the name
* <AuthenticityTokenInput name="authenticity_token" />
*/
function AuthenticityTokenInput({ name = "csrf", }) {
let token = useAuthenticityToken();
return (0, jsx_runtime_1.jsx)("input", { type: "hidden", value: token, name: name });
export function AuthenticityTokenInput({ name = "csrf" }) {
let token = useAuthenticityToken();
return _jsx("input", { type: "hidden", value: token, name: name });
}
exports.AuthenticityTokenInput = AuthenticityTokenInput;
/// <reference types="react" />
import type { AppData } from "@remix-run/server-runtime";
import { HandleConventionArguments } from "./handle-conventions";
declare type ReferrerPolicy = "no-referrer-when-downgrade" | "no-referrer" | "origin-when-cross-origin" | "origin" | "same-origin" | "strict-origin-when-cross-origin" | "strict-origin" | "unsafe-url";
declare type CrossOrigin = "anonymous" | "use-credentials";
declare type ScriptDescriptor = {
async?: boolean;
crossOrigin?: CrossOrigin;
defer?: boolean;
integrity?: string;
noModule?: boolean;
nonce?: string;
referrerPolicy?: ReferrerPolicy;
src: string;
type?: string;
type ReferrerPolicy =
| "no-referrer-when-downgrade"
| "no-referrer"
| "origin-when-cross-origin"
| "origin"
| "same-origin"
| "strict-origin-when-cross-origin"
| "strict-origin"
| "unsafe-url";
type CrossOrigin = "anonymous" | "use-credentials";
type ScriptDescriptor = {
async?: boolean;
crossOrigin?: CrossOrigin;
defer?: boolean;
integrity?: string;
noModule?: boolean;
nonce?: string;
referrerPolicy?: ReferrerPolicy;
src: string;
type?: string;
};
export interface ExternalScriptsFunction<Data extends AppData = AppData> {
(args: HandleConventionArguments<Data>): ScriptDescriptor[];
(args: HandleConventionArguments<Data>): ScriptDescriptor[];
}
export declare function ExternalScripts(): JSX.Element;
export {};

@@ -1,34 +0,49 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExternalScripts = void 0;
const react_1 = require("react");
const jsx_runtime_1 = require("react/jsx-runtime");
const react_2 = require("@remix-run/react");
function ExternalScripts() {
let location = (0, react_2.useLocation)();
let scripts = (0, react_2.useMatches)().flatMap((match, index, matches) => {
var _a;
let scripts = (_a = match.handle) === null || _a === void 0 ? void 0 : _a.scripts;
if (typeof scripts !== "function")
return [];
let result = scripts({
id: match.id,
data: match.data,
params: match.params,
location,
parentsData: matches.slice(0, index).map((match) => match.data),
matches,
});
if (Array.isArray(result))
return result;
return [];
import { createElement as _createElement } from "react";
import {
jsx as _jsx,
Fragment as _Fragment,
jsxs as _jsxs,
} from "react/jsx-runtime";
import { useLocation, useMatches } from "@remix-run/react";
export function ExternalScripts() {
let location = useLocation();
let scripts = useMatches().flatMap((match, index, matches) => {
var _a;
let scripts =
(_a = match.handle) === null || _a === void 0 ? void 0 : _a.scripts;
if (typeof scripts !== "function") return [];
let result = scripts({
id: match.id,
data: match.data,
params: match.params,
location,
parentsData: matches.slice(0, index).map((match) => match.data),
matches,
});
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [scripts.map((props) => {
let rel = props.noModule ? "modulepreload" : "preload";
let as = !props.noModule ? "script" : undefined;
return ((0, jsx_runtime_1.jsx)("link", { rel: rel, href: props.src, as: as, crossOrigin: props.crossOrigin, integrity: props.integrity, referrerPolicy: props.referrerPolicy }, props.src));
}), scripts.map((props) => {
return (0, react_1.createElement)("script", { ...props, key: props.src });
})] }));
if (Array.isArray(result)) return result;
return [];
});
return _jsxs(_Fragment, {
children: [
scripts.map((props) => {
let rel = props.noModule ? "modulepreload" : "preload";
let as = !props.noModule ? "script" : undefined;
return _jsx(
"link",
{
rel: rel,
href: props.src,
as: as,
crossOrigin: props.crossOrigin,
integrity: props.integrity,
referrerPolicy: props.referrerPolicy,
},
props.src,
);
}),
scripts.map((props) => {
return _createElement("script", { ...props, key: props.src });
}),
],
});
}
exports.ExternalScripts = ExternalScripts;

@@ -1,12 +0,12 @@

import { RouteData } from "@remix-run/react/dist/routeData";
import type { RouterState } from "@remix-run/router";
import type { AppData } from "@remix-run/server-runtime";
import type { Location, Params } from "react-router-dom";
import type { Location, Params } from "@remix-run/react";
import { Matches } from "./matches-type";
export declare type HandleConventionArguments<Data extends AppData = AppData> = {
id: string;
data: Data;
params: Params;
location: Location;
parentsData: RouteData;
matches: Matches;
export type HandleConventionArguments<Data extends AppData = AppData> = {
id: string;
data: Data;
params: Params;
location: Location;
parentsData: RouterState["loaderData"];
matches: Matches;
};

@@ -1,2 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
export {};
import type { useMatches } from "@remix-run/react";
export declare type Matches = ReturnType<typeof useMatches>;
export type Matches = ReturnType<typeof useMatches>;

@@ -1,2 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
export {};
import { ReactNode } from "react";
declare type Props = {
/**
* You are encouraged to add a fallback that is the same dimensions
* as the server rendered children. This will avoid content layout
* shift which is disgusting
*/
children(): ReactNode;
fallback?: ReactNode;
type Props = {
/**
* You are encouraged to add a fallback that is the same dimensions
* as the server rendered children. This will avoid content layout
* shift which is disgusting
*/
children(): ReactNode;
fallback?: ReactNode;
};

@@ -11,0 +11,0 @@ /**

@@ -1,6 +0,3 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServerOnly = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const use_hydrated_1 = require("./use-hydrated");
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useHydrated } from "./use-hydrated";
/**

@@ -19,5 +16,6 @@ * Render the children only before the JS has loaded client-side. Use an

*/
function ServerOnly({ children, fallback = null }) {
return (0, use_hydrated_1.useHydrated)() ? (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children() });
export function ServerOnly({ children, fallback = null }) {
return useHydrated()
? _jsx(_Fragment, { children: fallback })
: _jsx(_Fragment, { children: children() });
}
exports.ServerOnly = ServerOnly;
import { ReactNode, RefObject } from "react";
export declare function isLinkEvent(event: MouseEvent): HTMLAnchorElement | undefined;
export declare function useDelegatedAnchors(nodeRef: RefObject<HTMLElement>): void;
export declare function PrefetchPageAnchors({ children }: {
children: ReactNode;
export declare function isLinkEvent(
event: MouseEvent,
): HTMLAnchorElement | undefined;
export declare function useDelegatedAnchors(
nodeRef: RefObject<HTMLElement>,
): void;
export declare function PrefetchPageAnchors({
children,
}: {
children: ReactNode;
}): JSX.Element;

@@ -1,74 +0,74 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrefetchPageAnchors = exports.useDelegatedAnchors = exports.isLinkEvent = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("@remix-run/react");
const react_2 = require("react");
const context = (0, react_2.createContext)(false);
function isLinkEvent(event) {
if (!(event.target instanceof HTMLElement))
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { PrefetchPageLinks, useNavigate } from "@remix-run/react";
import { createContext, useContext, useEffect, useRef, useState } from "react";
const context = createContext(false);
export function isLinkEvent(event) {
if (!(event.target instanceof HTMLElement)) return;
let a = event.target.closest("a");
if (a && a.hasAttribute("href") && a.host === window.location.host) return a;
return;
}
export function useDelegatedAnchors(nodeRef) {
let navigate = useNavigate();
let hasParentPrefetch = useContext(context);
useEffect(() => {
// if you call useDelegatedAnchors as a children of a PrefetchPageAnchors
// then do nothing
if (hasParentPrefetch) return;
let node = nodeRef.current;
node === null || node === void 0
? void 0
: node.addEventListener("click", handleClick);
return () =>
node === null || node === void 0
? void 0
: node.removeEventListener("click", handleClick);
function handleClick(event) {
if (!node) return;
let anchor = isLinkEvent(event);
if (!anchor) return;
if (event.button !== 0) return;
if (anchor.target && anchor.target !== "_self") return;
if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
return;
let a = event.target.closest("a");
if (a && a.hasAttribute("href") && a.host === window.location.host)
return a;
return;
}
if (anchor.hasAttribute("download")) return;
let { pathname, search, hash } = anchor;
navigate({ pathname, search, hash });
event.preventDefault();
}
}, [hasParentPrefetch, navigate, nodeRef]);
}
exports.isLinkEvent = isLinkEvent;
function useDelegatedAnchors(nodeRef) {
let navigate = (0, react_1.useNavigate)();
let hasParentPrefetch = (0, react_2.useContext)(context);
(0, react_2.useEffect)(() => {
// if you call useDelegatedAnchors as a children of a PrefetchPageAnchors
// then do nothing
if (hasParentPrefetch)
return;
let node = nodeRef.current;
node === null || node === void 0 ? void 0 : node.addEventListener("click", handleClick);
return () => node === null || node === void 0 ? void 0 : node.removeEventListener("click", handleClick);
function handleClick(event) {
if (!node)
return;
let anchor = isLinkEvent(event);
if (!anchor)
return;
if (event.button !== 0)
return;
if (anchor.target && anchor.target !== "_self")
return;
if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
return;
}
if (anchor.hasAttribute("download"))
return;
let { pathname, search, hash } = anchor;
navigate({ pathname, search, hash });
event.preventDefault();
}
}, [hasParentPrefetch, navigate, nodeRef]);
export function PrefetchPageAnchors({ children }) {
let nodeRef = useRef(null);
let [page, setPage] = useState(null);
let hasParentPrefetch = useContext(context);
// prefetch is useless without delegated anchors, so we enable it
useDelegatedAnchors(nodeRef);
useEffect(() => {
if (hasParentPrefetch) return;
let node = nodeRef.current;
node === null || node === void 0
? void 0
: node.addEventListener("mouseenter", handleMouseEnter, true);
return () =>
node === null || node === void 0
? void 0
: node.removeEventListener("mouseenter", handleMouseEnter);
function handleMouseEnter(event) {
if (!nodeRef.current) return;
let anchor = isLinkEvent(event);
if (!anchor) return;
let { pathname, search } = anchor;
setPage(pathname + search);
}
}, [hasParentPrefetch]);
return _jsxs("div", {
ref: nodeRef,
style: { display: "contents" },
children: [
_jsx(context.Provider, { value: true, children: children }),
page && !hasParentPrefetch && _jsx(PrefetchPageLinks, { page: page }),
],
});
}
exports.useDelegatedAnchors = useDelegatedAnchors;
function PrefetchPageAnchors({ children }) {
let nodeRef = (0, react_2.useRef)(null);
let [page, setPage] = (0, react_2.useState)(null);
let hasParentPrefetch = (0, react_2.useContext)(context);
// prefetch is useless without delegated anchors, so we enable it
useDelegatedAnchors(nodeRef);
(0, react_2.useEffect)(() => {
if (hasParentPrefetch)
return;
let node = nodeRef.current;
node === null || node === void 0 ? void 0 : node.addEventListener("mouseenter", handleMouseEnter, true);
return () => node === null || node === void 0 ? void 0 : node.removeEventListener("mouseenter", handleMouseEnter);
function handleMouseEnter(event) {
if (!nodeRef.current)
return;
let anchor = isLinkEvent(event);
if (!anchor)
return;
let { pathname, search } = anchor;
setPage(pathname + search);
}
}, [hasParentPrefetch]);
return ((0, jsx_runtime_1.jsxs)("div", { ref: nodeRef, style: { display: "contents" }, children: [(0, jsx_runtime_1.jsx)(context.Provider, { value: true, children: children }), page && !hasParentPrefetch && (0, jsx_runtime_1.jsx)(react_1.PrefetchPageLinks, { page: page })] }));
}
exports.PrefetchPageAnchors = PrefetchPageAnchors;

@@ -1,5 +0,14 @@

declare type EventSourceOptions = {
init?: EventSourceInit;
event?: string;
};
/// <reference types="react" />
export interface EventSourceOptions {
init?: EventSourceInit;
event?: string;
}
export type EventSourceMap = Map<
string,
{
count: number;
source: EventSource;
}
>;
export declare const EventSourceProvider: import("react").Provider<EventSourceMap>;
/**

@@ -11,3 +20,5 @@ * Subscribe to an event source and return the latest event.

*/
export declare function useEventSource(url: string | URL, { event, init }?: EventSourceOptions): string | null;
export {};
export declare function useEventSource(
url: string | URL,
{ event, init }?: EventSourceOptions,
): string | null;

@@ -1,5 +0,4 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useEventSource = void 0;
const react_1 = require("react");
import { useEffect, useState, createContext, useContext } from "react";
const context = createContext(new Map());
export const EventSourceProvider = context.Provider;
/**

@@ -11,19 +10,37 @@ * Subscribe to an event source and return the latest event.

*/
function useEventSource(url, { event = "message", init } = {}) {
const [data, setData] = (0, react_1.useState)(null);
(0, react_1.useEffect)(() => {
const eventSource = new EventSource(url, init);
eventSource.addEventListener(event !== null && event !== void 0 ? event : "message", handler);
// rest data if dependencies change
setData(null);
function handler(event) {
setData(event.data || "UNKNOWN_EVENT_DATA");
}
return () => {
eventSource.removeEventListener(event !== null && event !== void 0 ? event : "message", handler);
eventSource.close();
};
}, [url, event, init]);
return data;
export function useEventSource(url, { event = "message", init } = {}) {
let map = useContext(context);
let [data, setData] = useState(null);
useEffect(() => {
var _a;
let key = [
url.toString(),
event,
init === null || init === void 0 ? void 0 : init.withCredentials,
].join("::");
let value =
(_a = map.get(key)) !== null && _a !== void 0
? _a
: {
count: 0,
source: new EventSource(url, init),
};
++value.count;
map.set(key, value);
value.source.addEventListener(event, handler);
// rest data if dependencies change
setData(null);
function handler(event) {
setData(event.data || "UNKNOWN_EVENT_DATA");
}
return () => {
value.source.removeEventListener(event, handler);
--value.count;
if (value.count <= 0) {
value.source.close();
map.delete(key);
}
};
}, [url, event, init, map]);
return data;
}
exports.useEventSource = useEventSource;
/**
* This is a helper hook that returns the state of every fetcher active on
* the app and combine it with the state of the global transition.
* the app and combine it with the state of the global transition and
* revalidator.
* @example
* let states = useGlobalTransitionStates();
* let states = useGlobalNavigationState();
* if (state.includes("loading")) {
* // The app is loading.
* // The app is loading or revalidating.
* }

@@ -14,3 +15,7 @@ * if (state.includes("submitting")) {

*/
export declare function useGlobalTransitionStates(): ("idle" | "submitting" | "loading")[];
export declare function useGlobalNavigationState(): (
| "idle"
| "loading"
| "submitting"
)[];
/**

@@ -17,0 +22,0 @@ * Let you know if the app is pending some request, either global transition

@@ -1,13 +0,11 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useGlobalLoadingState = exports.useGlobalSubmittingState = exports.useGlobalPendingState = exports.useGlobalTransitionStates = void 0;
const react_1 = require("@remix-run/react");
const react_2 = require("react");
import { useNavigation, useFetchers, useRevalidator } from "@remix-run/react";
import { useMemo } from "react";
/**
* This is a helper hook that returns the state of every fetcher active on
* the app and combine it with the state of the global transition.
* the app and combine it with the state of the global transition and
* revalidator.
* @example
* let states = useGlobalTransitionStates();
* let states = useGlobalNavigationState();
* if (state.includes("loading")) {
* // The app is loading.
* // The app is loading or revalidating.
* }

@@ -19,14 +17,22 @@ * if (state.includes("submitting")) {

*/
function useGlobalTransitionStates() {
let transition = (0, react_1.useTransition)();
let fetchers = (0, react_1.useFetchers)();
/**
* This gets the state of every fetcher active on the app and combine it with
* the state of the global transition (Link and Form).
*/
return (0, react_2.useMemo)(function getGlobalTransitionStates() {
return [transition.state, ...fetchers.map((fetcher) => fetcher.state)];
}, [transition.state, fetchers]);
export function useGlobalNavigationState() {
let { state: navigationState } = useNavigation();
let { state: revalidatorState } = useRevalidator();
let fetchers = useFetchers();
/**
* This gets the state of every fetcher active on the app and combine it with
* the state of the global transition (Link and Form) and revalidator.
*/
return useMemo(
function getGlobalNavigationState() {
return [
navigationState,
// The type cast here is used to remove RevalidatorState from the union
revalidatorState,
...fetchers.map((fetcher) => fetcher.state),
];
},
[navigationState, revalidatorState, fetchers],
);
}
exports.useGlobalTransitionStates = useGlobalTransitionStates;
/**

@@ -37,10 +43,8 @@ * Let you know if the app is pending some request, either global transition

*/
function useGlobalPendingState() {
let isSubmitting = useGlobalSubmittingState() === "submitting";
let isLoading = useGlobalLoadingState() === "loading";
if (isLoading || isSubmitting)
return "pending";
return "idle";
export function useGlobalPendingState() {
let isSubmitting = useGlobalSubmittingState() === "submitting";
let isLoading = useGlobalLoadingState() === "loading";
if (isLoading || isSubmitting) return "pending";
return "idle";
}
exports.useGlobalPendingState = useGlobalPendingState;
/**

@@ -51,9 +55,7 @@ * Let you know if the app is submitting some request, either global transition

*/
function useGlobalSubmittingState() {
let states = useGlobalTransitionStates();
if (states.includes("submitting"))
return "submitting";
return "idle";
export function useGlobalSubmittingState() {
let states = useGlobalNavigationState();
if (states.includes("submitting")) return "submitting";
return "idle";
}
exports.useGlobalSubmittingState = useGlobalSubmittingState;
/**

@@ -64,8 +66,6 @@ * Let you know if the app is loading some request, either global transition

*/
function useGlobalLoadingState() {
let states = useGlobalTransitionStates();
if (states.includes("loading"))
return "loading";
return "idle";
export function useGlobalLoadingState() {
let states = useGlobalNavigationState();
if (states.includes("loading")) return "loading";
return "idle";
}
exports.useGlobalLoadingState = useGlobalLoadingState;

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useHydrated = void 0;
const react_1 = require("react");
import { useEffect, useState } from "react";
let hydrating = true;

@@ -23,10 +20,9 @@ /**

*/
function useHydrated() {
let [hydrated, setHydrated] = (0, react_1.useState)(() => !hydrating);
(0, react_1.useEffect)(function hydrate() {
hydrating = false;
setHydrated(true);
}, []);
return hydrated;
export function useHydrated() {
let [hydrated, setHydrated] = useState(() => !hydrating);
useEffect(function hydrate() {
hydrating = false;
setHydrated(true);
}, []);
return hydrated;
}
exports.useHydrated = useHydrated;

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useLocales = void 0;
const react_1 = require("@remix-run/react");
import { useMatches } from "@remix-run/react";
/**

@@ -27,35 +24,28 @@ * Get the locales returned by the loader of the root route.

*/
function useLocales() {
let matches = (0, react_1.useMatches)();
if (!matches)
return undefined;
if (matches.length === 0)
return undefined;
let [rootMatch] = matches;
// check if rootMatch exists and has data
if (!rootMatch)
return undefined;
if (!rootMatch.data)
return undefined;
let { data } = rootMatch;
// check if data is an object and has locales
if (typeof data !== "object")
return undefined;
if (data === null)
return undefined;
if (Array.isArray(data))
return undefined;
if (!("locales" in data))
return undefined;
let { locales } = data;
// check the type of value of locales
// it could be a string
// or it could be an array of strings
if (Array.isArray(locales) &&
locales.every((value) => typeof value === "string")) {
return locales;
}
// finally, return undefined
return undefined;
export function useLocales() {
let matches = useMatches();
if (!matches) return undefined;
if (matches.length === 0) return undefined;
let [rootMatch] = matches;
// check if rootMatch exists and has data
if (!rootMatch) return undefined;
if (!rootMatch.data) return undefined;
let { data } = rootMatch;
// check if data is an object and has locales
if (typeof data !== "object") return undefined;
if (data === null) return undefined;
if (Array.isArray(data)) return undefined;
if (!("locales" in data)) return undefined;
let { locales } = data;
// check the type of value of locales
// it could be a string
// or it could be an array of strings
if (
Array.isArray(locales) &&
locales.every((value) => typeof value === "string")
) {
return locales;
}
// finally, return undefined
return undefined;
}
exports.useLocales = useLocales;

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useShouldHydrate = void 0;
const react_1 = require("@remix-run/react");
import { useMatches } from "@remix-run/react";
/**

@@ -25,23 +22,16 @@ * Determine if at least one of the routes is asking to load JS and return a

*/
function useShouldHydrate() {
return (0, react_1.useMatches)().some((match) => {
if (!match.handle)
return false;
let { handle, data } = match;
// handle must be an object to continue
if (typeof handle !== "object")
return false;
if (handle === null)
return false;
if (Array.isArray(handle))
return false;
// get hydrate from handle (it may not exists)
let hydrate = handle.hydrate;
if (!hydrate)
return false;
if (typeof hydrate === "function")
return hydrate(data);
return hydrate;
});
export function useShouldHydrate() {
return useMatches().some((match) => {
if (!match.handle) return false;
let { handle, data } = match;
// handle must be an object to continue
if (typeof handle !== "object") return false;
if (handle === null) return false;
if (Array.isArray(handle)) return false;
// get hydrate from handle (it may not exists)
let hydrate = handle.hydrate;
if (!hydrate) return false;
if (typeof hydrate === "function") return hydrate(data);
return hydrate;
});
}
exports.useShouldHydrate = useShouldHydrate;

@@ -6,5 +6,8 @@ export * from "./server/cors";

export * from "./server/get-client-locales";
export * from "./server/honeypot";
export * from "./server/is-prefetch";
export * from "./server/named-action";
export * from "./server/parse-accept-header";
export * from "./server/preload-route-assets";
export * from "./server/respond-to";
export * from "./server/json-hash";

@@ -11,0 +14,0 @@ export * from "./server/responses";

@@ -1,30 +0,17 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./server/cors"), exports);
__exportStar(require("./server/csrf"), exports);
__exportStar(require("./server/event-stream"), exports);
__exportStar(require("./server/get-client-ip-address"), exports);
__exportStar(require("./server/get-client-locales"), exports);
__exportStar(require("./server/is-prefetch"), exports);
__exportStar(require("./server/named-action"), exports);
__exportStar(require("./server/preload-route-assets"), exports);
__exportStar(require("./server/json-hash"), exports);
__exportStar(require("./server/responses"), exports);
__exportStar(require("./server/rolling-cookie"), exports);
__exportStar(require("./server/safe-redirect"), exports);
__exportStar(require("./server/typed-cookie"), exports);
__exportStar(require("./server/typed-session"), exports);
export * from "./server/cors";
export * from "./server/csrf";
export * from "./server/event-stream";
export * from "./server/get-client-ip-address";
export * from "./server/get-client-locales";
export * from "./server/honeypot";
export * from "./server/is-prefetch";
export * from "./server/named-action";
export * from "./server/parse-accept-header";
export * from "./server/preload-route-assets";
export * from "./server/respond-to";
export * from "./server/json-hash";
export * from "./server/responses";
export * from "./server/rolling-cookie";
export * from "./server/safe-redirect";
export * from "./server/typed-cookie";
export * from "./server/typed-session";
import { Promisable } from "type-fest";
declare type Origin = boolean | string | RegExp | Array<string | RegExp>;
type Origin = boolean | string | RegExp | Array<string | RegExp>;
interface CORSOptions {
/**
* Configures the **Access-Control-Allow-Origin** CORS header.
*
* Possible values:
* - true: Enable CORS for any origin (same as "*")
* - false: Don't setup CORS
* - string: Set to a specific origin, if set to "*" it will allow any origin
* - RegExp: Set to a RegExp to match against the origin
* - Array<string | RegExp>: Set to an array of origins to match against the
* string or RegExp
* - Function: Set to a function that will be called with the request origin
* and should return a boolean indicating if the origin is allowed or not.
* @default true
*/
origin?: Origin | ((origin: string) => Promisable<Origin>);
/**
* Configures the **Access-Control-Allow-Methods** CORS header.
* @default ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"]
*/
methods?: Array<string>;
/**
* Configures the **Access-Control-Allow-Headers** CORS header.
* @default []
*/
allowedHeaders?: string[];
/**
* Configures the **Access-Control-Expose-Headers** CORS header.
* @default []
*/
exposedHeaders?: string[];
/**
* Configures the **Access-Control-Allow-Credentials** CORS header.
* @default false
*/
credentials?: boolean;
/**
* Configures the **Access-Control-Max-Age** CORS header.
* @default 0
*/
maxAge?: number;
/**
* Configures the **Access-Control-Allow-Origin** CORS header.
*
* Possible values:
* - true: Enable CORS for any origin (same as "*")
* - false: Don't setup CORS
* - string: Set to a specific origin, if set to "*" it will allow any origin
* - RegExp: Set to a RegExp to match against the origin
* - Array<string | RegExp>: Set to an array of origins to match against the
* string or RegExp
* - Function: Set to a function that will be called with the request origin
* and should return a boolean indicating if the origin is allowed or not.
* @default true
*/
origin?: Origin | ((origin: string) => Promisable<Origin>);
/**
* Configures the **Access-Control-Allow-Methods** CORS header.
* @default ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"]
*/
methods?: Array<string>;
/**
* Configures the **Access-Control-Allow-Headers** CORS header.
* @default []
*/
allowedHeaders?: string[];
/**
* Configures the **Access-Control-Expose-Headers** CORS header.
* @default []
*/
exposedHeaders?: string[];
/**
* Configures the **Access-Control-Allow-Credentials** CORS header.
* @default false
*/
credentials?: boolean;
/**
* Configures the **Access-Control-Max-Age** CORS header.
* @default 0
*/
maxAge?: number;
}

@@ -100,3 +100,7 @@ /**

*/
export declare function cors(request: Request, response: Response, options?: CORSOptions): Promise<Response>;
export declare function cors(
request: Request,
response: Response,
options?: CORSOptions,
): Promise<Response>;
export {};

@@ -1,127 +0,131 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cors = void 0;
const DEFAULT_OPTIONS = {
origin: true,
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
allowedHeaders: [],
exposedHeaders: [],
origin: true,
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
allowedHeaders: [],
exposedHeaders: [],
};
class CORS {
constructor(options) {
// Merge user options with default options
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
constructor(options) {
// Merge user options with default options
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
}
async exec(request, response) {
let isPreflight = request.method.toLowerCase() === "options";
await this.configureOrigin(response.headers, request);
this.configureCredentials(response.headers);
this.configureExposedHeaders(response.headers);
if (isPreflight) {
this.configureMethods(response.headers);
this.configureAllowedHeaders(response.headers, request);
this.configureMaxAge(response.headers);
// waiting for the body
if (response.status === 204) response.headers.set("Content-Length", "0");
}
async exec(request, response) {
let isPreflight = request.method.toLowerCase() === "options";
await this.configureOrigin(response.headers, request);
this.configureCredentials(response.headers);
this.configureExposedHeaders(response.headers);
if (isPreflight) {
this.configureMethods(response.headers);
this.configureAllowedHeaders(response.headers, request);
this.configureMaxAge(response.headers);
// waiting for the body
if (response.status === 204)
response.headers.set("Content-Length", "0");
}
return response;
return response;
}
async resolveOrigin(request) {
var _a;
let { origin } = this.options;
if (typeof origin === "function") {
return await origin(
(_a = request.headers.get("origin")) !== null && _a !== void 0
? _a
: "",
);
}
async resolveOrigin(request) {
var _a;
let { origin } = this.options;
if (typeof origin === "function") {
return await origin((_a = request.headers.get("origin")) !== null && _a !== void 0 ? _a : "");
}
return origin;
return origin;
}
configureMaxAge(headers) {
var { maxAge } = this.options;
if (!this.isNumber(maxAge)) return headers;
headers.append("Access-Control-Max-Age", maxAge.toString());
return headers;
}
configureExposedHeaders(headers) {
var _a;
let exposedHeaders =
(_a = this.options.exposedHeaders) === null || _a === void 0
? void 0
: _a.join(",");
if (!this.isString(exposedHeaders) || exposedHeaders === "") return headers;
headers.append("Access-Control-Expose-Headers", exposedHeaders);
return null;
}
configureAllowedHeaders(headers, request) {
var _a;
let allowedHeaders =
(_a = this.options.allowedHeaders) === null || _a === void 0
? void 0
: _a.join(",");
if (!allowedHeaders) {
// headers wasn't specified, so reflect the request headers
let requestHeaders = request.headers.get(
"Access-Control-Request-Headers",
);
if (this.isString(requestHeaders)) allowedHeaders = requestHeaders;
headers.append("Vary", "Access-Control-Request-Headers");
}
configureMaxAge(headers) {
var { maxAge } = this.options;
if (!this.isNumber(maxAge))
return headers;
headers.append("Access-Control-Max-Age", maxAge.toString());
return headers;
if (allowedHeaders && allowedHeaders !== "") {
headers.append("Access-Control-Allow-Headers", allowedHeaders);
}
configureExposedHeaders(headers) {
var _a;
let exposedHeaders = (_a = this.options.exposedHeaders) === null || _a === void 0 ? void 0 : _a.join(",");
if (!this.isString(exposedHeaders) || exposedHeaders === "")
return headers;
headers.append("Access-Control-Expose-Headers", exposedHeaders);
return null;
return headers;
}
configureCredentials(headers) {
if (this.options.credentials === true) {
headers.append("Access-Control-Allow-Credentials", "true");
}
configureAllowedHeaders(headers, request) {
var _a;
let allowedHeaders = (_a = this.options.allowedHeaders) === null || _a === void 0 ? void 0 : _a.join(",");
if (!allowedHeaders) {
// headers wasn't specified, so reflect the request headers
let requestHeaders = request.headers.get("Access-Control-Request-Headers");
if (this.isString(requestHeaders))
allowedHeaders = requestHeaders;
headers.append("Vary", "Access-Control-Request-Headers");
}
if (allowedHeaders && allowedHeaders !== "") {
headers.append("Access-Control-Allow-Headers", allowedHeaders);
}
return headers;
return headers;
}
configureMethods(headers) {
var _a;
let methods =
(_a = this.options.methods) === null || _a === void 0
? void 0
: _a.join(",");
if (!this.isString(methods)) return headers;
headers.append("Access-Control-Allow-Methods", methods);
return headers;
}
async configureOrigin(headers, request) {
let origin = await this.resolveOrigin(request);
let requestOrigin = request.headers.get("origin");
if (!requestOrigin || origin === false) return headers;
if (origin === undefined || origin === "*") {
// allow any origin
headers.append("Access-Control-Allow-Origin", "*");
return headers;
}
configureCredentials(headers) {
if (this.options.credentials === true) {
headers.append("Access-Control-Allow-Credentials", "true");
}
return headers;
if (this.isString(origin)) {
// fixed origin
headers.append("Access-Control-Allow-Origin", origin);
headers.append("Vary", "Origin");
return headers;
}
configureMethods(headers) {
var _a;
let methods = (_a = this.options.methods) === null || _a === void 0 ? void 0 : _a.join(",");
if (!this.isString(methods))
return headers;
headers.append("Access-Control-Allow-Methods", methods);
return headers;
if (!this.isOriginAllowed(requestOrigin, origin)) return headers;
// reflect origin
headers.append("Access-Control-Allow-Origin", requestOrigin);
headers.append("Vary", "Origin");
return headers;
}
isOriginAllowed(origin, allowedOrigin) {
if (Array.isArray(allowedOrigin)) {
for (let element of allowedOrigin) {
if (this.isOriginAllowed(origin, element)) return true;
}
return false;
}
async configureOrigin(headers, request) {
let origin = await this.resolveOrigin(request);
let requestOrigin = request.headers.get("origin");
if (!requestOrigin || origin === false)
return headers;
if (origin === undefined || origin === "*") {
// allow any origin
headers.append("Access-Control-Allow-Origin", "*");
return headers;
}
if (this.isString(origin)) {
// fixed origin
headers.append("Access-Control-Allow-Origin", origin);
headers.append("Vary", "Origin");
return headers;
}
if (!this.isOriginAllowed(requestOrigin, origin))
return headers;
// reflect origin
headers.append("Access-Control-Allow-Origin", requestOrigin);
headers.append("Vary", "Origin");
return headers;
if (this.isString(allowedOrigin)) {
return origin === allowedOrigin;
}
isOriginAllowed(origin, allowedOrigin) {
if (Array.isArray(allowedOrigin)) {
for (let element of allowedOrigin) {
if (this.isOriginAllowed(origin, element))
return true;
}
return false;
}
if (this.isString(allowedOrigin)) {
return origin === allowedOrigin;
}
if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
}
return !!allowedOrigin;
if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
}
isString(value) {
return typeof value === "string" || value instanceof String;
}
isNumber(value) {
return typeof value === "number" || value instanceof Number;
}
return !!allowedOrigin;
}
isString(value) {
return typeof value === "string" || value instanceof String;
}
isNumber(value) {
return typeof value === "number" || value instanceof Number;
}
}

@@ -183,5 +187,4 @@ /**

*/
async function cors(request, response, options = DEFAULT_OPTIONS) {
return new CORS(options).exec(request, response);
export async function cors(request, response, options = DEFAULT_OPTIONS) {
return new CORS(options).exec(request, response);
}
exports.cors = cors;

@@ -1,36 +0,76 @@

import { Session } from "@remix-run/server-runtime";
/**
* Create a random string in Base64 to be used as an authenticity token for
* CSRF protection. You should run this on the `root.tsx` loader only.
* @example
* let token = createAuthenticityToken(session); // create and set in session
* return json({ ...otherData, csrf: token }); // return the token in the data
* @example
* // create and set in session with the key `csrf-token`
* let token = createAuthenticityToken(session, "csrfToken");
* return json({ ...otherData, csrf: token }); // return the token in the data
*/
export declare function createAuthenticityToken(session: Session, sessionKey?: string): string;
/**
* Verify if a request and session has a valid CSRF token.
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session);
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session, "csrfToken");
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* let formData = await unstable_parseMultipartFormData(request, uploadHandler);
* await verifyAuthenticityToken(formData, session);
* // the request is authenticated and you can do anything here
* }
*/
export declare function verifyAuthenticityToken(data: Request | FormData, session: Session, sessionKey?: string): Promise<void>;
import type { Cookie } from "@remix-run/server-runtime";
export type CSRFErrorCode =
| "missing_token_in_cookie"
| "invalid_token_in_cookie"
| "tampered_token_in_cookie"
| "missing_token_in_body"
| "mismatched_token";
export declare class CSRFError extends Error {
code: CSRFErrorCode;
constructor(code: CSRFErrorCode, message: string);
}
interface CSRFOptions {
/**
* The cookie object to use for serializing and parsing the CSRF token.
*/
cookie: Cookie;
/**
* The name of the form data key to use for the CSRF token.
*/
formDataKey?: string;
/**
* A secret to use for signing the CSRF token.
*/
secret?: string;
}
export declare class CSRF {
private cookie;
private formDataKey;
private secret?;
constructor(options: CSRFOptions);
/**
* Generates a random string in Base64URL to be used as an authenticity token
* for CSRF protection.
* @param bytes The number of bytes used to generate the token
* @returns A random string in Base64URL
*/
generate(bytes?: number): string;
/**
* Generates a token and serialize it into the cookie.
* @param bytes The number of bytes used to generate the token
* @returns A tuple with the token and the string to send in Set-Cookie
* @example
* let [token, cookie] = await csrf.commitToken();
* return json({ token }, {
* headers: { "set-cookie": cookie }
* })
*/
commitToken(bytes?: number): Promise<readonly [string, string]>;
/**
* Verify if a request and cookie has a valid CSRF token.
* @example
* export async function action({ request }: ActionArgs) {
* await csrf.validate(request);
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let formData = await request.formData()
* await csrf.validate(formData, request.headers);
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let formData = await parseMultipartFormData(request);
* await csrf.validate(formData, request.headers);
* // the request is authenticated and you can do anything here
* }
*/
validate(data: Request): Promise<void>;
validate(data: FormData, headers: Headers): Promise<void>;
private readBody;
private parseCookie;
private sign;
private verifySignature;
}
export {};

@@ -1,76 +0,111 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyAuthenticityToken = exports.createAuthenticityToken = void 0;
const uuid_1 = require("uuid");
const responses_1 = require("./responses");
/**
* Create a random string in Base64 to be used as an authenticity token for
* CSRF protection. You should run this on the `root.tsx` loader only.
* @example
* let token = createAuthenticityToken(session); // create and set in session
* return json({ ...otherData, csrf: token }); // return the token in the data
* @example
* // create and set in session with the key `csrf-token`
* let token = createAuthenticityToken(session, "csrfToken");
* return json({ ...otherData, csrf: token }); // return the token in the data
*/
function createAuthenticityToken(session, sessionKey = "csrf") {
let token = session.get(sessionKey);
if (typeof token === "string")
return token;
let newToken = (0, uuid_1.v4)();
session.set(sessionKey, newToken);
return newToken;
import cryptoJS from "crypto-js";
export class CSRFError extends Error {
constructor(code, message) {
super(message);
this.code = code;
this.name = "CSRFError";
}
}
exports.createAuthenticityToken = createAuthenticityToken;
/**
* Verify if a request and session has a valid CSRF token.
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session);
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session, "csrfToken");
* // the request is authenticated and you can do anything here
* }
* @example
* export async function action({ request }: ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* let formData = await unstable_parseMultipartFormData(request, uploadHandler);
* await verifyAuthenticityToken(formData, session);
* // the request is authenticated and you can do anything here
* }
*/
async function verifyAuthenticityToken(data, session, sessionKey = "csrf") {
export class CSRF {
constructor(options) {
var _a;
this.formDataKey = "csrf";
this.cookie = options.cookie;
this.formDataKey =
(_a = options.formDataKey) !== null && _a !== void 0 ? _a : "csrf";
this.secret = options.secret;
}
/**
* Generates a random string in Base64URL to be used as an authenticity token
* for CSRF protection.
* @param bytes The number of bytes used to generate the token
* @returns A random string in Base64URL
*/
generate(bytes = 32) {
let token = cryptoJS.lib.WordArray.random(bytes).toString(
cryptoJS.enc.Base64url,
);
if (!this.secret) return token;
let signature = this.sign(token);
return [token, signature].join(".");
}
/**
* Generates a token and serialize it into the cookie.
* @param bytes The number of bytes used to generate the token
* @returns A tuple with the token and the string to send in Set-Cookie
* @example
* let [token, cookie] = await csrf.commitToken();
* return json({ token }, {
* headers: { "set-cookie": cookie }
* })
*/
async commitToken(bytes = 32) {
let token = this.generate(bytes);
let cookie = await this.cookie.serialize(token);
return [token, cookie];
}
async validate(data, headers) {
if (data instanceof Request && data.bodyUsed) {
throw new Error("The body of the request was read before calling verifyAuthenticityToken. Ensure you clone it before reading it.");
throw new Error(
"The body of the request was read before calling CSRF#verify. Ensure you clone it before reading it.",
);
}
// We clone the request to ensure we don't modify the original request.
// This allow us to parse the body of the request and let the original request
// still be used and parsed without errors.
let formData = data instanceof FormData ? data : await data.clone().formData();
let formData = await this.readBody(data);
let cookie = await this.parseCookie(data, headers);
// if the session doesn't have a csrf token, throw an error
if (!session.has(sessionKey)) {
throw (0, responses_1.unprocessableEntity)({
message: "Can't find CSRF token in session.",
});
if (cookie === null) {
throw new CSRFError(
"missing_token_in_cookie",
"Can't find CSRF token in cookie.",
);
}
if (typeof cookie !== "string") {
throw new CSRFError(
"invalid_token_in_cookie",
"Invalid CSRF token in cookie.",
);
}
if (this.verifySignature(cookie) === false) {
throw new CSRFError(
"tampered_token_in_cookie",
"Tampered CSRF token in cookie.",
);
}
// if the body doesn't have a csrf token, throw an error
if (!formData.get(sessionKey)) {
throw (0, responses_1.unprocessableEntity)({
message: "Can't find CSRF token in body.",
});
if (!formData.get(this.formDataKey)) {
throw new CSRFError(
"missing_token_in_body",
"Can't find CSRF token in body.",
);
}
// if the body csrf token doesn't match the session csrf token, throw an
// error
if (formData.get(sessionKey) !== session.get(sessionKey)) {
throw (0, responses_1.unprocessableEntity)({
message: "Can't verify CSRF token authenticity.",
});
if (formData.get(this.formDataKey) !== cookie) {
throw new CSRFError(
"mismatched_token",
"Can't verify CSRF token authenticity.",
);
}
}
async readBody(data) {
if (data instanceof FormData) return data;
return await data.clone().formData();
}
parseCookie(data, headers) {
if (data instanceof Request) headers = data.headers;
if (!headers) return null;
return this.cookie.parse(headers.get("cookie"));
}
sign(token) {
if (!this.secret) return token;
return cryptoJS
.HmacSHA256(token, this.secret)
.toString(cryptoJS.enc.Base64url);
}
verifySignature(token) {
if (!this.secret) return true;
let [value, signature] = token.split(".");
let expectedSignature = this.sign(value);
return signature === expectedSignature;
}
}
exports.verifyAuthenticityToken = verifyAuthenticityToken;
interface SendFunctionArgs {
/**
* @default "message"
*/
event?: string;
data: string;
/**
* @default "message"
*/
event?: string;
data: string;
}
interface SendFunction {
(args: SendFunctionArgs): void;
(args: SendFunctionArgs): void;
}
interface CleanupFunction {
(): void;
(): void;
}
interface AbortFunction {
(): void;
(): void;
}
interface InitFunction {
(send: SendFunction, abort: AbortFunction): CleanupFunction;
(send: SendFunction, abort: AbortFunction): CleanupFunction;
}
/**
* A response holper to use Server Sent Events server-side
* A response helper to use Server Sent Events server-side
* @param signal The AbortSignal used to close the stream

@@ -26,3 +26,7 @@ * @param init The function that will be called to initialize the stream, here you can subscribe to your events

*/
export declare function eventStream(signal: AbortSignal, init: InitFunction, options?: ResponseInit): Response;
export declare function eventStream(
signal: AbortSignal,
init: InitFunction,
options?: ResponseInit,
): Response;
export {};

@@ -1,6 +0,3 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.eventStream = void 0;
/**
* A response holper to use Server Sent Events server-side
* A response helper to use Server Sent Events server-side
* @param signal The AbortSignal used to close the stream

@@ -10,40 +7,37 @@ * @param init The function that will be called to initialize the stream, here you can subscribe to your events

*/
function eventStream(signal, init, options = {}) {
let stream = new ReadableStream({
start(controller) {
let encoder = new TextEncoder();
function send({ event = "message", data }) {
controller.enqueue(encoder.encode(`event: ${event}\n`));
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}
let cleanup = init(send, close);
let closed = false;
function close() {
if (closed)
return;
cleanup();
closed = true;
signal.removeEventListener("abort", close);
controller.close();
}
signal.addEventListener("abort", close);
if (signal.aborted)
return close();
},
});
let headers = new Headers(options.headers);
if (headers.has("Content-Type")) {
console.warn("Overriding Content-Type header to `text/event-stream`");
}
if (headers.has("Cache-Control")) {
console.warn("Overriding Cache-Control header to `no-cache`");
}
if (headers.has("Connection")) {
console.warn("Overriding Connection header to `keep-alive`");
}
headers.set("Content-Type", "text/event-stream");
headers.set("Cache-Control", "no-cache");
headers.set("Connection", "keep-alive");
return new Response(stream, { headers });
export function eventStream(signal, init, options = {}) {
let stream = new ReadableStream({
start(controller) {
let encoder = new TextEncoder();
function send({ event = "message", data }) {
controller.enqueue(encoder.encode(`event: ${event}\n`));
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}
let cleanup = init(send, close);
let closed = false;
function close() {
if (closed) return;
cleanup();
closed = true;
signal.removeEventListener("abort", close);
controller.close();
}
signal.addEventListener("abort", close);
if (signal.aborted) return close();
},
});
let headers = new Headers(options.headers);
if (headers.has("Content-Type")) {
console.warn("Overriding Content-Type header to `text/event-stream`");
}
if (headers.has("Cache-Control")) {
console.warn("Overriding Cache-Control header to `no-cache`");
}
if (headers.has("Connection")) {
console.warn("Overriding Connection header to `keep-alive`");
}
headers.set("Content-Type", "text/event-stream");
headers.set("Cache-Control", "no-cache");
headers.set("Connection", "keep-alive");
return new Response(stream, { headers });
}
exports.eventStream = eventStream;

@@ -1,9 +0,3 @@

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getClientIPAddress = void 0;
const is_ip_1 = __importDefault(require("is-ip"));
const get_headers_1 = require("./get-headers");
import isIP from "is-ip";
import { getHeaders } from "./get-headers";
/**

@@ -14,46 +8,42 @@ * This is the list of headers, in order of preference, that will be used to

const headerNames = Object.freeze([
"X-Client-IP",
"X-Forwarded-For",
"HTTP-X-Forwarded-For",
"Fly-Client-IP",
"CF-Connecting-IP",
"Fastly-Client-Ip",
"True-Client-Ip",
"X-Real-IP",
"X-Cluster-Client-IP",
"X-Forwarded",
"Forwarded-For",
"Forwarded",
"DO-Connecting-IP" /** Digital ocean app platform */,
"oxygen-buyer-ip" /** Shopify oxygen platform */,
"X-Client-IP",
"X-Forwarded-For",
"HTTP-X-Forwarded-For",
"Fly-Client-IP",
"CF-Connecting-IP",
"Fastly-Client-Ip",
"True-Client-Ip",
"X-Real-IP",
"X-Cluster-Client-IP",
"X-Forwarded",
"Forwarded-For",
"Forwarded",
"DO-Connecting-IP" /** Digital ocean app platform */,
"oxygen-buyer-ip" /** Shopify oxygen platform */,
]);
function getClientIPAddress(requestOrHeaders) {
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders);
let ipAddress = headerNames
.flatMap((headerName) => {
let value = headers.get(headerName);
if (headerName === "Forwarded") {
return parseForwardedHeader(value);
}
if (!(value === null || value === void 0 ? void 0 : value.includes(",")))
return value;
return value.split(",").map((ip) => ip.trim());
export function getClientIPAddress(requestOrHeaders) {
let headers = getHeaders(requestOrHeaders);
let ipAddress = headerNames
.flatMap((headerName) => {
let value = headers.get(headerName);
if (headerName === "Forwarded") {
return parseForwardedHeader(value);
}
if (!(value === null || value === void 0 ? void 0 : value.includes(",")))
return value;
return value.split(",").map((ip) => ip.trim());
})
.find((ip) => {
if (ip === null)
return false;
return (0, is_ip_1.default)(ip);
.find((ip) => {
if (ip === null) return false;
return isIP(ip);
});
return ipAddress !== null && ipAddress !== void 0 ? ipAddress : null;
return ipAddress !== null && ipAddress !== void 0 ? ipAddress : null;
}
exports.getClientIPAddress = getClientIPAddress;
function parseForwardedHeader(value) {
if (!value)
return null;
for (let part of value.split(";")) {
if (part.startsWith("for="))
return part.slice(4);
continue;
}
return null;
if (!value) return null;
for (let part of value.split(";")) {
if (part.startsWith("for=")) return part.slice(4);
continue;
}
return null;
}

@@ -1,2 +0,2 @@

export declare type Locales = string[] | undefined;
export type Locales = string[] | undefined;
/**

@@ -3,0 +3,0 @@ * Get the client's locales from the Accept-Language header.

@@ -1,22 +0,16 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getClientLocales = void 0;
const intl_parse_accept_language_1 = require("intl-parse-accept-language");
const get_headers_1 = require("./get-headers");
function getClientLocales(requestOrHeaders) {
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders);
let acceptLanguage = headers.get("Accept-Language");
// if the header is not defined, return undefined
if (!acceptLanguage)
return undefined;
let locales = (0, intl_parse_accept_language_1.parseAcceptLanguage)(acceptLanguage, {
validate: Intl.DateTimeFormat.supportedLocalesOf,
ignoreWildcard: true,
});
// if there are no locales found, return undefined
if (locales.length === 0)
return undefined;
// if there are multiple locales, return the array
return locales;
import { parseAcceptLanguage } from "intl-parse-accept-language";
import { getHeaders } from "./get-headers";
export function getClientLocales(requestOrHeaders) {
let headers = getHeaders(requestOrHeaders);
let acceptLanguage = headers.get("Accept-Language");
// if the header is not defined, return undefined
if (!acceptLanguage) return undefined;
let locales = parseAcceptLanguage(acceptLanguage, {
validate: Intl.DateTimeFormat.supportedLocalesOf,
ignoreWildcard: true,
});
// if there are no locales found, return undefined
if (locales.length === 0) return undefined;
// if there are multiple locales, return the array
return locales;
}
exports.getClientLocales = getClientLocales;

@@ -6,2 +6,4 @@ /**

*/
export declare function getHeaders(requestOrHeaders: Request | Headers): Headers;
export declare function getHeaders(
requestOrHeaders: Request | Headers,
): Headers;

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getHeaders = void 0;
/**

@@ -9,8 +6,7 @@ * Receives a Request or Headers objects.

*/
function getHeaders(requestOrHeaders) {
if (requestOrHeaders instanceof Request) {
return requestOrHeaders.headers;
}
return requestOrHeaders;
export function getHeaders(requestOrHeaders) {
if (requestOrHeaders instanceof Request) {
return requestOrHeaders.headers;
}
return requestOrHeaders;
}
exports.getHeaders = getHeaders;

@@ -1,15 +0,16 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isPrefetch = void 0;
const get_headers_1 = require("./get-headers");
function isPrefetch(requestOrHeaders) {
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders);
let purpose = headers.get("Purpose") ||
headers.get("X-Purpose") ||
headers.get("Sec-Purpose") ||
headers.get("Sec-Fetch-Purpose") ||
headers.get("Moz-Purpose") ||
headers.get("X-Moz");
return (purpose === null || purpose === void 0 ? void 0 : purpose.toLowerCase()) === "prefetch";
import { getHeaders } from "./get-headers";
export function isPrefetch(requestOrHeaders) {
let headers = getHeaders(requestOrHeaders);
let purpose =
headers.get("Purpose") ||
headers.get("X-Purpose") ||
headers.get("Sec-Purpose") ||
headers.get("Sec-Fetch-Purpose") ||
headers.get("Moz-Purpose") ||
headers.get("X-Moz");
return (
(purpose === null || purpose === void 0
? void 0
: purpose.toLowerCase()) === "prefetch"
);
}
exports.isPrefetch = isPrefetch;
import type { TypedResponse } from "@remix-run/server-runtime";
declare type ResponseResult<LoaderData> = {
[Key in keyof LoaderData]: LoaderData[Key] extends () => infer ReturnValue ? ReturnValue extends PromiseLike<infer Value> ? Value : ReturnValue : LoaderData[Key] extends PromiseLike<infer Value> ? Value : LoaderData[Key];
type ResponseResult<LoaderData> = {
[Key in keyof LoaderData]: LoaderData[Key] extends () => infer ReturnValue
? ReturnValue extends PromiseLike<infer Value>
? Value
: ReturnValue
: LoaderData[Key] extends PromiseLike<infer Value>
? Value
: LoaderData[Key];
};
export declare function jsonHash<LoaderData extends Record<string, unknown>>(input: LoaderData, init?: ResponseInit | number): Promise<TypedResponse<ResponseResult<LoaderData>>>;
export declare function jsonHash<LoaderData extends Record<string, unknown>>(
input: LoaderData,
init?: ResponseInit | number,
): Promise<TypedResponse<ResponseResult<LoaderData>>>;
export {};

@@ -1,20 +0,15 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.jsonHash = void 0;
const server_runtime_1 = require("@remix-run/server-runtime");
async function jsonHash(input, init) {
let result = {};
let resolvedResults = await Promise.all(Object.entries(input).map(async ([key, value]) => {
if (value instanceof Function)
value = value();
if (value instanceof Promise)
value = await value;
return [key, value];
}));
for (let [key, value] of resolvedResults) {
result[key] =
value;
}
return (0, server_runtime_1.json)(result, init);
import { json as remixJson } from "@remix-run/server-runtime";
export async function jsonHash(input, init) {
let result = {};
let resolvedResults = await Promise.all(
Object.entries(input).map(async ([key, value]) => {
if (value instanceof Function) value = value();
if (value instanceof Promise) value = await value;
return [key, value];
}),
);
for (let [key, value] of resolvedResults) {
result[key] = value;
}
return remixJson(result, init);
}
exports.jsonHash = jsonHash;
import type { TypedResponse } from "@remix-run/server-runtime";
declare type ActionsRecord = Record<string, () => Promise<TypedResponse<unknown>>>;
declare type ResponsesRecord<Actions extends ActionsRecord> = {
[Action in keyof Actions]: Actions[Action] extends () => Promise<TypedResponse<infer Result>> ? Result : never;
type ActionsRecord = Record<string, () => Promise<TypedResponse<unknown>>>;
type ResponsesRecord<Actions extends ActionsRecord> = {
[Action in keyof Actions]: Actions[Action] extends () => Promise<
TypedResponse<infer Result>
>
? Result
: never;
};
declare type ResponsesUnion<Actions extends ActionsRecord> = ResponsesRecord<Actions>[keyof Actions];
type ResponsesUnion<Actions extends ActionsRecord> =
ResponsesRecord<Actions>[keyof Actions];
/**

@@ -15,6 +20,18 @@ * Runs an action based on the request's action name

*/
export declare function namedAction<Actions extends ActionsRecord>(request: Request, actions: Actions): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(url: URL, actions: Actions): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(searchParams: URLSearchParams, actions: Actions): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(formData: FormData, actions: Actions): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(
request: Request,
actions: Actions,
): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(
url: URL,
actions: Actions,
): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(
searchParams: URLSearchParams,
actions: Actions,
): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export declare function namedAction<Actions extends ActionsRecord>(
formData: FormData,
actions: Actions,
): Promise<TypedResponse<ResponsesUnion<Actions>>>;
export {};

@@ -1,66 +0,52 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.namedAction = void 0;
async function namedAction(input, actions) {
let name = await getActionName(input);
if (name && name in actions) {
return actions[name]();
}
if (name === null && "default" in actions) {
return actions["default"]();
}
if (name === null)
throw new ReferenceError("Action name not found");
throw new ReferenceError(`Action "${name}" not found`);
export async function namedAction(input, actions) {
let name = await getActionName(input);
if (name && name in actions) {
return actions[name]();
}
if (name === null && "default" in actions) {
return actions["default"]();
}
if (name === null) throw new ReferenceError("Action name not found");
throw new ReferenceError(`Action "${name}" not found`);
}
exports.namedAction = namedAction;
async function getActionName(input) {
if (input instanceof Request) {
let actionName = findNameInURL(new URL(input.url).searchParams);
if (actionName)
return actionName;
return findNameInFormData(await input.clone().formData());
}
if (input instanceof URL) {
return findNameInURL(input.searchParams);
}
if (input instanceof URLSearchParams) {
return findNameInURL(input);
}
if (input instanceof FormData) {
return findNameInFormData(input);
}
return null;
if (input instanceof Request) {
let actionName = findNameInURL(new URL(input.url).searchParams);
if (actionName) return actionName;
return findNameInFormData(await input.clone().formData());
}
if (input instanceof URL) {
return findNameInURL(input.searchParams);
}
if (input instanceof URLSearchParams) {
return findNameInURL(input);
}
if (input instanceof FormData) {
return findNameInFormData(input);
}
return null;
}
function findNameInURL(searchParams) {
for (let key of searchParams.keys()) {
if (key.startsWith("/"))
return key.slice(1);
}
let actionName = searchParams.get("intent");
if (typeof actionName === "string")
return actionName;
actionName = searchParams.get("action");
if (typeof actionName === "string")
return actionName;
actionName = searchParams.get("_action");
if (typeof actionName === "string")
return actionName;
return null;
for (let key of searchParams.keys()) {
if (key.startsWith("/")) return key.slice(1);
}
let actionName = searchParams.get("intent");
if (typeof actionName === "string") return actionName;
actionName = searchParams.get("action");
if (typeof actionName === "string") return actionName;
actionName = searchParams.get("_action");
if (typeof actionName === "string") return actionName;
return null;
}
function findNameInFormData(formData) {
for (let key of formData.keys()) {
if (key.startsWith("/"))
return key.slice(1);
}
let actionName = formData.get("intent");
if (typeof actionName === "string")
return actionName;
actionName = formData.get("action");
if (typeof actionName === "string")
return actionName;
actionName = formData.get("_action");
if (typeof actionName === "string")
return actionName;
return null;
for (let key of formData.keys()) {
if (key.startsWith("/")) return key.slice(1);
}
let actionName = formData.get("intent");
if (typeof actionName === "string") return actionName;
actionName = formData.get("action");
if (typeof actionName === "string") return actionName;
actionName = formData.get("_action");
if (typeof actionName === "string") return actionName;
return null;
}

@@ -27,3 +27,6 @@ import { EntryContext } from "@remix-run/server-runtime";

*/
export declare function preloadRouteAssets(context: EntryContext, headers: Headers): void;
export declare function preloadRouteAssets(
context: EntryContext,
headers: Headers,
): void;
/**

@@ -55,3 +58,6 @@ * Preload the assets linked in the routes matching the current request.

*/
export declare function preloadLinkedAssets(context: EntryContext, headers: Headers): void;
export declare function preloadLinkedAssets(
context: EntryContext,
headers: Headers,
): void;
/**

@@ -82,2 +88,5 @@ * Add Link headers to preload the JS modules in the route matching the current

*/
export declare function preloadModuleAssets(context: EntryContext, headers: Headers): void;
export declare function preloadModuleAssets(
context: EntryContext,
headers: Headers,
): void;

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.preloadModuleAssets = exports.preloadLinkedAssets = exports.preloadRouteAssets = void 0;
/**

@@ -29,7 +26,6 @@ * Combine `preloadLinkedAssets` and `preloadModuleAssets` into a single

*/
function preloadRouteAssets(context, headers) {
preloadLinkedAssets(context, headers); // preload links
preloadModuleAssets(context, headers); // preload JS modules
export function preloadRouteAssets(context, headers) {
preloadLinkedAssets(context, headers); // preload links
preloadModuleAssets(context, headers); // preload JS modules
}
exports.preloadRouteAssets = preloadRouteAssets;
/**

@@ -61,29 +57,27 @@ * Preload the assets linked in the routes matching the current request.

*/
function preloadLinkedAssets(context, headers) {
let links = context.staticHandlerContext.matches
.flatMap((match) => {
let route = context.routeModules[match.route.id];
if (route.links instanceof Function)
return route.links();
return [];
export function preloadLinkedAssets(context, headers) {
let links = context.staticHandlerContext.matches
.flatMap((match) => {
let route = context.routeModules[match.route.id];
if (route.links instanceof Function) return route.links();
return [];
})
.map((link) => {
if ("as" in link && "href" in link) {
return { href: link.href, as: link.as };
}
if ("rel" in link && "href" in link && link.rel === "stylesheet")
return { href: link.href, as: "style" };
return null;
.map((link) => {
if ("as" in link && "href" in link) {
return { href: link.href, as: link.as };
}
if ("rel" in link && "href" in link && link.rel === "stylesheet")
return { href: link.href, as: "style" };
return null;
})
.filter((link) => {
return link !== null && "href" in link;
.filter((link) => {
return link !== null && "href" in link;
})
.filter((item, index, list) => {
return index === list.findIndex((link) => link.href === item.href);
.filter((item, index, list) => {
return index === list.findIndex((link) => link.href === item.href);
});
for (let link of links) {
headers.append("Link", `<${link.href}>; rel=preload; as=${link.as}`);
}
for (let link of links) {
headers.append("Link", `<${link.href}>; rel=preload; as=${link.as}`);
}
}
exports.preloadLinkedAssets = preloadLinkedAssets;
/**

@@ -114,17 +108,22 @@ * Add Link headers to preload the JS modules in the route matching the current

*/
function preloadModuleAssets(context, headers) {
var _a;
let urls = [
context.manifest.url,
context.manifest.entry.module,
...context.manifest.entry.imports,
];
for (let match of context.staticHandlerContext.matches) {
let route = context.manifest.routes[match.route.id];
urls.push(route.module, ...((_a = route.imports) !== null && _a !== void 0 ? _a : []));
}
for (let url of urls) {
headers.append("Link", `<${url}>; rel=preload; as=script; crossorigin=anonymous`);
}
export function preloadModuleAssets(context, headers) {
var _a;
let urls = [
context.manifest.url,
context.manifest.entry.module,
...context.manifest.entry.imports,
];
for (let match of context.staticHandlerContext.matches) {
let route = context.manifest.routes[match.route.id];
urls.push(
route.module,
...((_a = route.imports) !== null && _a !== void 0 ? _a : []),
);
}
for (let url of urls) {
headers.append(
"Link",
`<${url}>; rel=preload; as=script; crossorigin=anonymous`,
);
}
}
exports.preloadModuleAssets = preloadModuleAssets;

@@ -10,3 +10,6 @@ /// <reference types="node" />

*/
export declare function created<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function created<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -26,5 +29,11 @@ * Create a new Response with a redirect set to the URL the user was before.

*/
export declare function redirectBack(request: Request, { fallback, ...init }: ResponseInit & {
export declare function redirectBack(
request: Request,
{
fallback,
...init
}: ResponseInit & {
fallback: string;
}): Response;
},
): Response;
/**

@@ -38,3 +47,6 @@ * Create a response receiving a JSON object with the status code 400.

*/
export declare function badRequest<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function badRequest<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -48,3 +60,6 @@ * Create a response receiving a JSON object with the status code 401.

*/
export declare function unauthorized<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function unauthorized<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -58,3 +73,6 @@ * Create a response receiving a JSON object with the status code 403.

*/
export declare function forbidden<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function forbidden<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -68,3 +86,6 @@ * Create a response receiving a JSON object with the status code 404.

*/
export declare function notFound<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function notFound<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -78,3 +99,6 @@ * Create a response receiving a JSON object with the status code 422.

*/
export declare function unprocessableEntity<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function unprocessableEntity<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -88,3 +112,6 @@ * Create a response receiving a JSON object with the status code 500.

*/
export declare function serverError<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): import("@remix-run/server-runtime").TypedResponse<Data>;
export declare function serverError<Data = unknown>(
data: Data,
init?: Omit<ResponseInit, "status">,
): import("@remix-run/server-runtime").TypedResponse<Data>;
/**

@@ -98,3 +125,5 @@ * Create a response with only the status 304 and optional headers.

*/
export declare function notModified(init?: Omit<ResponseInit, "status">): Response;
export declare function notModified(
init?: Omit<ResponseInit, "status">,
): Response;
/**

@@ -111,3 +140,6 @@ * Create a response with a JavaScript file response.

*/
export declare function javascript(content: string, init?: number | ResponseInit): Response;
export declare function javascript(
content: string,
init?: number | ResponseInit,
): Response;
/**

@@ -124,3 +156,6 @@ * Create a response with a CSS file response.

*/
export declare function stylesheet(content: string, init?: number | ResponseInit): Response;
export declare function stylesheet(
content: string,
init?: number | ResponseInit,
): Response;
/**

@@ -137,3 +172,6 @@ * Create a response with a PDF file response.

*/
export declare function pdf(content: Blob | Buffer | ArrayBuffer, init?: number | ResponseInit): Response;
export declare function pdf(
content: Blob | Buffer | ArrayBuffer,
init?: number | ResponseInit,
): Response;
/**

@@ -150,3 +188,6 @@ * Create a response with a HTML file response.

*/
export declare function html(content: string, init?: number | ResponseInit): Response;
export declare function html(
content: string,
init?: number | ResponseInit,
): Response;
/**

@@ -163,3 +204,6 @@ * Create a response with a XML file response.

*/
export declare function xml(content: string, init?: number | ResponseInit): Response;
export declare function xml(
content: string,
init?: number | ResponseInit,
): Response;
/**

@@ -179,4 +223,14 @@ * Create a response with a TXT file response.

*/
export declare function txt(content: string, init?: number | ResponseInit): Response;
export declare type ImageType = "image/jpeg" | "image/png" | "image/gif" | "image/svg+xml" | "image/webp" | "image/bmp" | "image/avif";
export declare function txt(
content: string,
init?: number | ResponseInit,
): Response;
export type ImageType =
| "image/jpeg"
| "image/png"
| "image/gif"
| "image/svg+xml"
| "image/webp"
| "image/bmp"
| "image/avif";
/**

@@ -193,4 +247,10 @@ * Create a response with a image file response.

*/
export declare function image(content: Buffer | ArrayBuffer | ReadableStream, { type, ...init }: ResponseInit & {
export declare function image(
content: Buffer | ArrayBuffer | ReadableStream,
{
type,
...init
}: ResponseInit & {
type: ImageType;
}): Response;
},
): Response;

@@ -1,5 +0,2 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.image = exports.txt = exports.xml = exports.html = exports.pdf = exports.stylesheet = exports.javascript = exports.notModified = exports.serverError = exports.unprocessableEntity = exports.notFound = exports.forbidden = exports.unauthorized = exports.badRequest = exports.redirectBack = exports.created = void 0;
const server_runtime_1 = require("@remix-run/server-runtime");
import { json, redirect } from "@remix-run/server-runtime";
/**

@@ -13,6 +10,5 @@ * Create a response receiving a JSON object with the status code 201.

*/
function created(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 201 });
export function created(data, init) {
return json(data, { ...init, status: 201 });
}
exports.created = created;
/**

@@ -32,7 +28,11 @@ * Create a new Response with a redirect set to the URL the user was before.

*/
function redirectBack(request, { fallback, ...init }) {
var _a;
return (0, server_runtime_1.redirect)((_a = request.headers.get("Referer")) !== null && _a !== void 0 ? _a : fallback, init);
export function redirectBack(request, { fallback, ...init }) {
var _a;
return redirect(
(_a = request.headers.get("Referer")) !== null && _a !== void 0
? _a
: fallback,
init,
);
}
exports.redirectBack = redirectBack;
/**

@@ -46,6 +46,5 @@ * Create a response receiving a JSON object with the status code 400.

*/
function badRequest(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 400 });
export function badRequest(data, init) {
return json(data, { ...init, status: 400 });
}
exports.badRequest = badRequest;
/**

@@ -59,6 +58,5 @@ * Create a response receiving a JSON object with the status code 401.

*/
function unauthorized(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 401 });
export function unauthorized(data, init) {
return json(data, { ...init, status: 401 });
}
exports.unauthorized = unauthorized;
/**

@@ -72,6 +70,5 @@ * Create a response receiving a JSON object with the status code 403.

*/
function forbidden(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 403 });
export function forbidden(data, init) {
return json(data, { ...init, status: 403 });
}
exports.forbidden = forbidden;
/**

@@ -85,6 +82,5 @@ * Create a response receiving a JSON object with the status code 404.

*/
function notFound(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 404 });
export function notFound(data, init) {
return json(data, { ...init, status: 404 });
}
exports.notFound = notFound;
/**

@@ -98,6 +94,5 @@ * Create a response receiving a JSON object with the status code 422.

*/
function unprocessableEntity(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 422 });
export function unprocessableEntity(data, init) {
return json(data, { ...init, status: 422 });
}
exports.unprocessableEntity = unprocessableEntity;
/**

@@ -111,6 +106,5 @@ * Create a response receiving a JSON object with the status code 500.

*/
function serverError(data, init) {
return (0, server_runtime_1.json)(data, { ...init, status: 500 });
export function serverError(data, init) {
return json(data, { ...init, status: 500 });
}
exports.serverError = serverError;
/**

@@ -124,6 +118,5 @@ * Create a response with only the status 304 and optional headers.

*/
function notModified(init) {
return new Response("", { ...init, status: 304 });
export function notModified(init) {
return new Response("", { ...init, status: 304 });
}
exports.notModified = notModified;
/**

@@ -140,14 +133,13 @@ * Create a response with a JavaScript file response.

*/
function javascript(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/javascript; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
export function javascript(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/javascript; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.javascript = javascript;
/**

@@ -164,14 +156,13 @@ * Create a response with a CSS file response.

*/
function stylesheet(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/css; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
export function stylesheet(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/css; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.stylesheet = stylesheet;
/**

@@ -188,14 +179,13 @@ * Create a response with a PDF file response.

*/
function pdf(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/pdf");
}
return new Response(content, {
...responseInit,
headers,
});
export function pdf(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/pdf");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.pdf = pdf;
/**

@@ -212,14 +202,13 @@ * Create a response with a HTML file response.

*/
function html(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/html; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
export function html(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/html; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.html = html;
/**

@@ -236,14 +225,13 @@ * Create a response with a XML file response.

*/
function xml(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/xml; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
export function xml(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/xml; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.xml = xml;
/**

@@ -263,14 +251,13 @@ * Create a response with a TXT file response.

*/
function txt(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/plain; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
export function txt(content, init = {}) {
let responseInit = typeof init === "number" ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/plain; charset=utf-8");
}
return new Response(content, {
...responseInit,
headers,
});
}
exports.txt = txt;
/**

@@ -287,12 +274,11 @@ * Create a response with a image file response.

*/
function image(content, { type, ...init }) {
let headers = new Headers(init.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", type);
}
return new Response(content, {
...init,
headers,
});
export function image(content, { type, ...init }) {
let headers = new Headers(init.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", type);
}
return new Response(content, {
...init,
headers,
});
}
exports.image = image;
import type { Cookie } from "@remix-run/server-runtime";
import { z } from "zod";
import { TypedCookie } from "./typed-cookie";
export declare function rollingCookie<Schema extends z.ZodTypeAny>(cookie: Cookie | TypedCookie<Schema>, request: Request, responseHeaders: Headers): Promise<void>;
export declare function rollingCookie<Schema extends z.ZodTypeAny>(
cookie: Cookie | TypedCookie<Schema>,
request: Request,
responseHeaders: Headers,
): Promise<void>;

@@ -1,13 +0,7 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rollingCookie = void 0;
async function rollingCookie(cookie, request, responseHeaders) {
let value = await cookie.parse(responseHeaders.get("Set-Cookie"));
if (value !== null)
return;
value = await cookie.parse(request.headers.get("Cookie"));
if (!value)
return;
responseHeaders.append("Set-Cookie", await cookie.serialize(value));
export async function rollingCookie(cookie, request, responseHeaders) {
let value = await cookie.parse(responseHeaders.get("Set-Cookie"));
if (value !== null) return;
value = await cookie.parse(request.headers.get("Cookie"));
if (!value) return;
responseHeaders.append("Set-Cookie", await cookie.serialize(value));
}
exports.rollingCookie = rollingCookie;

@@ -10,2 +10,5 @@ /**

*/
export declare function safeRedirect(to: FormDataEntryValue | string | null | undefined, defaultRedirect?: string): string;
export declare function safeRedirect(
to: FormDataEntryValue | string | null | undefined,
defaultRedirect?: string,
): string;

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeRedirect = void 0;
const DEFAULT_REDIRECT = "/";

@@ -14,12 +11,14 @@ /**

*/
function safeRedirect(to, defaultRedirect = DEFAULT_REDIRECT) {
if (!to || typeof to !== "string") {
return defaultRedirect;
}
to = to.trim();
if (!to.startsWith("/") || to.startsWith("//")) {
return defaultRedirect;
}
return to;
export function safeRedirect(to, defaultRedirect = DEFAULT_REDIRECT) {
if (!to || typeof to !== "string") return defaultRedirect;
to = to.trim();
if (
!to.startsWith("/") ||
to.startsWith("//") ||
to.startsWith("/\\") ||
to.includes("..")
) {
return defaultRedirect;
}
return to;
}
exports.safeRedirect = safeRedirect;

@@ -1,11 +0,24 @@

import { Cookie, CookieParseOptions, CookieSerializeOptions } from "@remix-run/server-runtime";
import {
Cookie,
CookieParseOptions,
CookieSerializeOptions,
} from "@remix-run/server-runtime";
import type { z } from "zod";
export interface TypedCookie<Schema extends z.ZodTypeAny> extends Cookie {
isTyped: true;
parse(cookieHeader: string | null, options?: CookieParseOptions): Promise<z.infer<Schema> | null>;
serialize(value: z.infer<Schema>, options?: CookieSerializeOptions): Promise<string>;
isTyped: true;
parse(
cookieHeader: string | null,
options?: CookieParseOptions,
): Promise<z.infer<Schema> | null>;
serialize(
value: z.infer<Schema>,
options?: CookieSerializeOptions,
): Promise<string>;
}
export declare function createTypedCookie<Schema extends z.ZodTypeAny>({ cookie, schema, }: {
cookie: Cookie;
schema: Schema;
export declare function createTypedCookie<Schema extends z.ZodTypeAny>({
cookie,
schema,
}: {
cookie: Cookie;
schema: Schema;
}): TypedCookie<Schema>;

@@ -17,2 +30,4 @@ /**

*/
export declare function isTypedCookie<Schema extends z.ZodTypeAny>(value: unknown): value is TypedCookie<Schema>;
export declare function isTypedCookie<Schema extends z.ZodTypeAny>(
value: unknown,
): value is TypedCookie<Schema>;

@@ -1,36 +0,31 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTypedCookie = exports.createTypedCookie = void 0;
const server_runtime_1 = require("@remix-run/server-runtime");
function createTypedCookie({ cookie, schema, }) {
if (schema._def.typeName === "ZodObject") {
let flashSchema = {};
for (let key in schema.shape) {
flashSchema[flash(key)] = schema.shape[key].optional();
}
import { isCookie } from "@remix-run/server-runtime";
export function createTypedCookie({ cookie, schema }) {
if (schema._def.typeName === "ZodObject") {
let flashSchema = {};
for (let key in schema.shape) {
flashSchema[flash(key)] = schema.shape[key].optional();
}
return {
isTyped: true,
get name() {
return cookie.name;
},
get isSigned() {
return cookie.isSigned;
},
get expires() {
return cookie.expires;
},
async parse(cookieHeader, options) {
if (!cookieHeader)
return null;
let value = await cookie.parse(cookieHeader, options);
return await parseSchemaWithFlashKeys(schema, value);
},
async serialize(value, options) {
let parsedValue = await parseSchemaWithFlashKeys(schema, value);
return cookie.serialize(parsedValue, options);
},
};
}
return {
isTyped: true,
get name() {
return cookie.name;
},
get isSigned() {
return cookie.isSigned;
},
get expires() {
return cookie.expires;
},
async parse(cookieHeader, options) {
if (!cookieHeader) return null;
let value = await cookie.parse(cookieHeader, options);
return await parseSchemaWithFlashKeys(schema, value);
},
async serialize(value, options) {
let parsedValue = await parseSchemaWithFlashKeys(schema, value);
return cookie.serialize(parsedValue, options);
},
};
}
exports.createTypedCookie = createTypedCookie;
/**

@@ -41,24 +36,22 @@ * Returns true if an object is a Remix Utils Typed Cookie container.

*/
function isTypedCookie(value) {
return ((0, server_runtime_1.isCookie)(value) &&
value.isTyped === true);
export function isTypedCookie(value) {
return isCookie(value) && value.isTyped === true;
}
exports.isTypedCookie = isTypedCookie;
function flash(name) {
return `__flash_${name}__`;
return `__flash_${name}__`;
}
function parseSchemaWithFlashKeys(schema, value) {
// if the Schema is not a ZodObject, we use it directly
if (schema._def.typeName !== "ZodObject") {
return schema.nullable().parseAsync(value);
}
// but if it's a ZodObject, we need to add support for flash keys, so we
// get the shape of the schema, create a flash key for each key, and then we
// extend the original schema with the flash schema and parse the value
let objectSchema = schema;
let flashSchema = {};
for (let key in objectSchema.shape) {
flashSchema[flash(key)] = objectSchema.shape[key].optional();
}
return objectSchema.extend(flashSchema).parseAsync(value);
// if the Schema is not a ZodObject, we use it directly
if (schema._def.typeName !== "ZodObject") {
return schema.nullable().parseAsync(value);
}
// but if it's a ZodObject, we need to add support for flash keys, so we
// get the shape of the schema, create a flash key for each key, and then we
// extend the original schema with the flash schema and parse the value
let objectSchema = schema;
let flashSchema = {};
for (let key in objectSchema.shape) {
flashSchema[flash(key)] = objectSchema.shape[key].optional();
}
return objectSchema.extend(flashSchema).parseAsync(value);
}

@@ -1,53 +0,77 @@

import { CookieParseOptions, CookieSerializeOptions, SessionStorage } from "@remix-run/server-runtime";
import {
CookieParseOptions,
CookieSerializeOptions,
SessionStorage,
} from "@remix-run/server-runtime";
import { z } from "zod";
export interface TypedSession<Schema extends z.ZodTypeAny> {
/**
* Marks a session as a typed session.
*/
readonly isTyped: boolean;
/**
* A unique identifier for this session.
*
* Note: This will be the empty string for newly created sessions and
* sessions that are not backed by a database (i.e. cookie-based sessions).
*/
readonly id: string;
/**
* The raw data contained in this session.
*
* This is useful mostly for SessionStorage internally to access the raw
* session data to persist.
*/
readonly data: z.infer<Schema>;
/**
* Returns `true` if the session has a value for the given `name`, `false`
* otherwise.
*/
has<Key extends keyof z.infer<Schema>>(name: Key): boolean;
/**
* Returns the value for the given `name` in this session.
*/
get<Key extends keyof z.infer<Schema>>(key: Key): z.infer<Schema>[Key] | null;
/**
* Sets a value in the session for the given `name`.
*/
set<Key extends keyof z.infer<Schema>>(name: Key, value: z.infer<Schema>[Key]): void;
/**
* Sets a value in the session that is only valid until the next `get()`.
* This can be useful for temporary values, like error messages.
*/
flash<Key extends keyof z.infer<Schema>>(name: Key, value: z.infer<Schema>[Key]): void;
/**
* Removes a value from the session.
*/
unset<Key extends keyof z.infer<Schema>>(name: Key): void;
/**
* Marks a session as a typed session.
*/
readonly isTyped: boolean;
/**
* A unique identifier for this session.
*
* Note: This will be the empty string for newly created sessions and
* sessions that are not backed by a database (i.e. cookie-based sessions).
*/
readonly id: string;
/**
* The raw data contained in this session.
*
* This is useful mostly for SessionStorage internally to access the raw
* session data to persist.
*/
readonly data: z.infer<Schema>;
/**
* Returns `true` if the session has a value for the given `name`, `false`
* otherwise.
*/
has<Key extends keyof z.infer<Schema>>(name: Key): boolean;
/**
* Returns the value for the given `name` in this session.
*/
get<Key extends keyof z.infer<Schema>>(key: Key): z.infer<Schema>[Key] | null;
/**
* Sets a value in the session for the given `name`.
*/
set<Key extends keyof z.infer<Schema>>(
name: Key,
value: z.infer<Schema>[Key],
): void;
/**
* Sets a value in the session that is only valid until the next `get()`.
* This can be useful for temporary values, like error messages.
*/
flash<Key extends keyof z.infer<Schema>>(
name: Key,
value: z.infer<Schema>[Key],
): void;
/**
* Removes a value from the session.
*/
unset<Key extends keyof z.infer<Schema>>(name: Key): void;
}
export interface TypedSessionStorage<Schema extends z.ZodTypeAny> {
getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<TypedSession<Schema>>;
commitSession(session: TypedSession<Schema>, options?: CookieSerializeOptions | undefined): Promise<string>;
destroySession(session: TypedSession<Schema>, options?: CookieSerializeOptions | undefined): Promise<string>;
getSession(
cookieHeader?: string | null | undefined,
options?: CookieParseOptions | undefined,
): Promise<TypedSession<Schema>>;
commitSession(
session: TypedSession<Schema>,
options?: CookieSerializeOptions | undefined,
): Promise<string>;
destroySession(
session: TypedSession<Schema>,
options?: CookieSerializeOptions | undefined,
): Promise<string>;
}
export declare function createTypedSessionStorage<Schema extends z.AnyZodObject>({ sessionStorage, schema, }: {
sessionStorage: SessionStorage;
schema: Schema;
export declare function createTypedSessionStorage<
Schema extends z.AnyZodObject,
>({
sessionStorage,
schema,
}: {
sessionStorage: SessionStorage;
schema: Schema;
}): TypedSessionStorage<Schema>;

@@ -59,2 +83,4 @@ /**

*/
export declare function isTypedSession<Schema extends z.AnyZodObject>(value: unknown): value is TypedSession<Schema>;
export declare function isTypedSession<Schema extends z.AnyZodObject>(
value: unknown,
): value is TypedSession<Schema>;

@@ -1,76 +0,71 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTypedSession = exports.createTypedSessionStorage = void 0;
const server_runtime_1 = require("@remix-run/server-runtime");
function createTypedSessionStorage({ sessionStorage, schema, }) {
return {
async getSession(cookieHeader, options) {
let session = await sessionStorage.getSession(cookieHeader, options);
return await createTypedSession({ session, schema });
},
async commitSession(session, options) {
// check if session.data is valid
await schema.parseAsync(session.data);
return await sessionStorage.commitSession(session, options);
},
async destroySession(session) {
// check if session.data is valid
await schema.parseAsync(session.data);
return await sessionStorage.destroySession(session);
},
};
import { isSession } from "@remix-run/server-runtime";
export function createTypedSessionStorage({ sessionStorage, schema }) {
return {
async getSession(cookieHeader, options) {
let session = await sessionStorage.getSession(cookieHeader, options);
return await createTypedSession({ session, schema });
},
async commitSession(session, options) {
// check if session.data is valid
await schema.parseAsync(session.data);
return await sessionStorage.commitSession(session, options);
},
async destroySession(session) {
// check if session.data is valid
await schema.parseAsync(session.data);
return await sessionStorage.destroySession(session);
},
};
}
exports.createTypedSessionStorage = createTypedSessionStorage;
async function createTypedSession({ session, schema, }) {
// get a raw shape version of the schema but converting all the keys to their
// flash versions.
let flashSchema = {};
for (let key in schema.shape) {
flashSchema[flash(key)] = schema.shape[key].optional();
}
// parse session.data to add default values and remove invalid ones
// we use strict mode here so we can throw an error if the session data
// contains any invalid key, which is a sign that the session data is
// corrupted.
let data = await schema.extend(flashSchema).strict().parseAsync(session.data);
return {
get isTyped() {
return true;
},
get id() {
return session.id;
},
get data() {
return data;
},
has(name) {
let key = String(safeKey(schema, name));
return key in data || flash(key) in data;
},
get(name) {
let key = String(safeKey(schema, name));
if (key in data)
return data[key];
let flashKey = flash(key);
if (flashKey in data) {
let value = data[flashKey];
delete data[flashKey];
return value;
}
return;
},
set(name, value) {
let key = String(safeKey(schema, name));
data[key] = value;
},
flash(name, value) {
let key = String(safeKey(schema, name));
let flashKey = flash(key);
data[flashKey] = value;
},
unset(name) {
let key = String(safeKey(schema, name));
delete data[key];
},
};
async function createTypedSession({ session, schema }) {
// get a raw shape version of the schema but converting all the keys to their
// flash versions.
let flashSchema = {};
for (let key in schema.shape) {
flashSchema[flash(key)] = schema.shape[key].optional();
}
// parse session.data to add default values and remove invalid ones
// we use strict mode here so we can throw an error if the session data
// contains any invalid key, which is a sign that the session data is
// corrupted.
let data = await schema.extend(flashSchema).strict().parseAsync(session.data);
return {
get isTyped() {
return true;
},
get id() {
return session.id;
},
get data() {
return data;
},
has(name) {
let key = String(safeKey(schema, name));
return key in data || flash(key) in data;
},
get(name) {
let key = String(safeKey(schema, name));
if (key in data) return data[key];
let flashKey = flash(key);
if (flashKey in data) {
let value = data[flashKey];
delete data[flashKey];
return value;
}
return;
},
set(name, value) {
let key = String(safeKey(schema, name));
data[key] = value;
},
flash(name, value) {
let key = String(safeKey(schema, name));
let flashKey = flash(key);
data[flashKey] = value;
},
unset(name) {
let key = String(safeKey(schema, name));
delete data[key];
},
};
}

@@ -82,13 +77,11 @@ /**

*/
function isTypedSession(value) {
return ((0, server_runtime_1.isSession)(value) &&
value.isTyped === true);
export function isTypedSession(value) {
return isSession(value) && value.isTyped === true;
}
exports.isTypedSession = isTypedSession;
function flash(name) {
return `__flash_${name}__`;
return `__flash_${name}__`;
}
// checks that the key is a valid key of the schema
function safeKey(schema, key) {
return schema.keyof().parse(key);
return schema.keyof().parse(key);
}
{
"name": "remix-utils",
"version": "6.6.0",
"version": "7.0.0",
"license": "MIT",
"engines": {
"node": ">=14"
"node": ">=18.0.0"
},
"browser": "./browser/index.js",
"main": "./build/index.js",

@@ -13,8 +12,10 @@ "sideEffects": false,

"prepare": "npm run build",
"build": "npm run build:browser && npm run build:main",
"build:browser": "tsc --project tsconfig.json --module ESNext --outDir ./browser",
"build:main": "tsc --project tsconfig.json --module CommonJS --outDir ./build",
"build": "tsc --project tsconfig.json --module ESNext --outDir ./build",
"postbuild": "prettier --write \"build/**/*.js\" \"build/**/*.d.ts\"",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.tsx\" \"test/**/*.ts\" \"test/**/*.tsx\"",
"typecheck": "tsc --project tsconfig.json --noEmit",
"lint": "eslint --ext .ts,.tsx src/",
"test": "jest --config=config/jest.config.ts --passWithNoTests"
"test": "vitest --run",
"test:watch": "vitest",
"test:coverage": "vitest --coverage"
},

@@ -45,18 +46,17 @@ "author": {

"peerDependencies": {
"@remix-run/react": "^1.10.0",
"@remix-run/server-runtime": "^1.10.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"@remix-run/react": "^1.19.1",
"@remix-run/router": "^1.7.2",
"@remix-run/server-runtime": "^1.19.1",
"react": "^18.0.0",
"zod": "^3.19.1"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@jest/types": "^27.2.5",
"@remix-run/node": "^1.10.0",
"@remix-run/react": "^1.10.0",
"@remix-run/server-runtime": "^1.10.0",
"@remix-run/node": "^1.19.2",
"@remix-run/react": "^1.19.2",
"@remix-run/router": "^1.7.2",
"@remix-run/server-runtime": "^1.19.2",
"@remix-run/testing": "^1.19.3",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^12.1.2",
"@types/crypto-js": "^4.1.1",
"@types/react": "^17.0.14",

@@ -66,3 +66,4 @@ "@types/uuid": "^8.3.3",

"@typescript-eslint/parser": "^5.3.0",
"babel-jest": "^27.3.1",
"@vitejs/plugin-react": "^4.0.4",
"@vitest/coverage-v8": "^0.34.1",
"eslint": "^8.12.0",

@@ -73,3 +74,2 @@ "eslint-config-prettier": "^8.3.0",

"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^26.1.3",
"eslint-plugin-jest-dom": "^4.0.1",

@@ -83,13 +83,15 @@ "eslint-plugin-jsx-a11y": "^6.4.1",

"eslint-plugin-unicorn": "^41.0.1",
"jest": "^27.3.1",
"jest-fetch-mock": "^3.0.3",
"happy-dom": "^10.9.0",
"msw": "^1.2.3",
"prettier": "^2.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "6.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-node": "^10.4.0",
"typescript": "^4.2.4",
"typescript": "^5.1.6",
"vite": "^4.4.9",
"vitest": "^0.34.1",
"zod": "^3.19.1"
},
"dependencies": {
"crypto-js": "^4.1.1",
"intl-parse-accept-language": "^1.0.0",

@@ -96,0 +98,0 @@ "is-ip": "^3.1.0",

@@ -318,50 +318,68 @@ # Remix Utils

#### Generate the authenticity token
First create a new CSRF instance.
In the server, we need to add to our `root` component the following.
```ts
// app/utils/csrf.server.ts
import { CSRF } from "remix-utils";
import { createCookie } from "@remix-run/node"; // or /cloudflare
export const cookie = createCookie("csrf", {
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
secrets: ["s3cr3t"],
});
export const csrf = new CSRF({
cookie,
// what key in FormData objects will be used for the token, defaults to `csrf`
formDataKey: "csrf",
// an optional secret used to sign the token, recommended for extra safety
secret: "s3cr3t",
});
```
Then you can use `csrf` to generate a new token.
```ts
import { createAuthenticityToken, json } from "remix-utils";
import { getSession, commitSession } from "~/services/session.server";
import { csrf } from "~/utils/csrf.server";
interface LoaderData {
csrf: string;
}
export async function loader({ request }: LoaderArgs) {
let session = await getSession(request.headers.get("cookie"));
let token = createAuthenticityToken(session);
return json<LoaderData>(
{ csrf: token },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
let token = csrf.generate();
}
```
The `createAuthenticityToken` function receives a session object and stores the authenticity token there using the `csrf` key (you can pass the key name as a second argument). Finally, you need to return the token in a `json` response and commit the session.
You can customize the token size by passing the byte size, the default one is 32 bytes which will give you a string with a length of 43 after encoding.
#### Render the AuthenticityTokenProvider
```ts
let token = csrf.generate(64); // customize token length
```
You need to read the authenticity token and render the `AuthenticityTokenProvider` component wrapping your code in your root.
You will need to save this token in a cookie and also return it from the loader. For convenience, you can use the `CSRF#commitToken` helper.
```tsx
import { Outlet, useLoaderData } from "@remix-run/react";
import { Document } from "~/components/document";
```ts
import { csrf } from "~/utils/csrf.server";
export default function Component() {
let { csrf } = useLoaderData<LoaderData>();
return (
<AuthenticityTokenProvider token={csrf}>
<Document>
<Outlet />
</Document>
</AuthenticityTokenProvider>
);
export async function loader({ request }: LoaderArgs) {
let [token, cookieHeader] = await csrf.commitToken();
return json({ token }, { headers: { "set-cookie": cookieHeader } });
}
```
With this, your whole app can access the authenticity token generated in the root.
> **Note**: You could do this on any route, but I recommend you to do it on the `root` loader.
#### Rendering a Form
Now that you returned the token and set it in a cookie, you can use the `AuthenticityTokenProvider` component to provide the token to your React components.
```tsx
let { csrf } = useLoaderData<LoaderData>();
return (
<AuthenticityTokenProvider token={csrf}>
<Outlet />
</AuthenticityTokenProvider>
);
```
Render it in your `root` component and wrap the `Outlet` with it.
When you create a form in some route, you can use the `AuthenticityTokenInput` component to add the authenticity token to the form.

@@ -393,8 +411,6 @@

##### Alternative: Using `useAuthenticityToken` and `useFetcher`.
If you need to use `useFetcher` (or `useSubmit`) instead of `Form` you can also get the authenticity token with the `useAuthenticityToken` hook.
```tsx
import { useFetcher } from "remix";
import { useFetcher } from "@remix-run/react";
import { useAuthenticityToken } from "remix-utils";

@@ -406,3 +422,6 @@

return function submit(data) {
fetcher.submit({ csrf, ...data }, { action: "/action", method: "post" });
fetcher.submit(
{ csrf, ...data },
{ action: "/api/mark-as-read", method: "post" }
);
};

@@ -412,14 +431,19 @@ }

#### Verify in the Action
Finally, you need to validate the authenticity token in the action that received the request.
Finally, you need to verify the authenticity token in the action that received the request.
```ts
import { verifyAuthenticityToken, redirectBack } from "remix-utils";
import { getSession, commitSession } from "~/services/session.server";
import { CSRFError, redirectBack } from "remix-utils";
import { csrf } from "~/utils/csrf.server";
export async function action({ request }: ActionArgs) {
let session = await getSession(request.headers.get("Cookie"));
await verifyAuthenticityToken(request, session);
// do something here
try {
await csrf.validate(request);
} catch (error) {
if (error instanceof CSRFError) {
// handle CSRF errors
}
// handle other possible errors
}
// here you know the request is valid
return redirectBack(request, { fallback: "/fallback" });

@@ -429,86 +453,28 @@ }

Suppose the authenticity token is missing on the session, the request body, or doesn't match. In that case, the function will throw an Unprocessable Entity response that you can either catch and handle manually or let pass and render your CatchBoundary.
If you need to parse the body as FormData yourself (e.g. to support file uploads) you can also call `CSRF#validate` with the FormData and Headers objects.
### DynamicLinks
> **Warning**: Deprecated in favor of the `V2_MetaFunction`. This will be removed in the next major version. Check below for the new way to do this.
If you need to create `<link />` tags based on the loader data instead of being static, you can use the `DynamicLinks` component together with the `DynamicLinksFunction` type.
In the route you want to define dynamic links add `handle` export with a `dynamicLinks` method, this method should implement the `DynamicLinksFunction` type.
```ts
// create the dynamicLinks function with the correct type
// note: loader type is optional
let dynamicLinks: DynamicLinksFunction<SerializeFrom<typeof loader>> = ({
id,
data,
params,
matches,
location,
parentsData,
}) => {
if (!data.user) return [];
return [{ rel: "preload", href: data.user.avatar, as: "image" }];
};
// and export it through the handle, you could also create it inline here
// if you don't care about the type
export let handle = { dynamicLinks };
```
Then, in the root route, add the `DynamicLinks` component before the Remix's Links component, usually inside a Document component.
```tsx
import { Links, LiveReload, Meta, Scripts, ScrollRestoration } from "remix";
import { DynamicLinks } from "remix-utils";
type Props = { children: React.ReactNode; title?: string };
export function Document({ children, title }: Props) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Meta />
<DynamicLinks />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
let formData = await parseMultiPartFormData(request);
try {
await csrf.validate(formData, request.headers);
} catch (error) {
// handle errors
}
```
Now, any link you defined in the `DynamicLinksFunction` will be added to the HTML as any static link in your `LinksFunction`s.
> **Warning**: If you call `CSRF#validate` with the request instance, but you already read its body, it will throw an error.
> **Note**
> You can also put the `DynamicLinks` after the `Links` component, it's up to you what to prioritize, since static links are probably prefetched when you do `<Link prefetch>` you may want to put the `DynamicLinks` first to prioritize them.
In case the CSRF validation fails, it will throw a `CSRFError` which can be used to correctly identify it against other possible errors that may get thrown.
If you want to upgrade to use the `V2_MetaFunction`, first enable it in your Remix app:
The list of possible error messages are:
```js
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
future: { v2_meta: true },
};
```
- `missing_token_in_cookie`: The request is missing the CSRF token in the cookie.
- `invalid_token_in_cookie`: The CSRF token is not valid (is not a string).
- `tampered_token_in_cookie`: The CSRF token doesn't match the signature.
- `missing_token_in_body`: The request is missing the CSRF token in the body (FormData).
- `mismatched_token`: The CSRF token in the cookie and the body don't match.
Then you can use it like this:
You can use `error.code` to check one of the error codes above, and `error.message` to get a human friendly description.
```ts
export let meta: V2_MetaFunction<typeof loader> = ({ data }) => {
if (!data.user) return [];
return [
{ tagName: "link", rel: "preload", href: data.user.avatar, as: "image" },
];
};
```
> **Warning**: Don't send those error messages to the end-user, they are meant to be used for debugging purposes only.

@@ -580,112 +546,11 @@ ### ExternalScripts

### StructuredData
### useGlobalNavigationState
> **Warning**: Deprecated in favor of the V2_MetaFunction. This will be removed in the next major version. Check below for the new way to do this.
This hook allows you to read the value of `transition.state`, every `fetcher.state` in the app, and `revalidator.state`.
If you need to include structured data (JSON-LD) scripts on certain routes, you can use the `StructuredData` component together with the `HandleStructuredData` type or `StructuredDataFunction` type.
In the route you want to include the structured data, add a `handle` export with a `structuredData` method, this method should implement the `StructuredDataFunction` type.
```ts
import type { WithContext, BlogPosting } from "schema-dts";
import { useGlobalNavigationState } from "remix-utils";
// create the structuredData function with the correct type
// note: loader type is optional
let structuredData: StructuredDataFunction<
SerializeFrom<typeof loader>,
BlogPosting
> = ({ id, data, params, location, parentsData }) => {
let { post } = data;
return {
"@context": "https://schema.org",
"@type": "BlogPosting",
datePublished: post.published,
mainEntityOfPage: {
"@type": "WebPage",
"@id": post.postUrl,
},
image: post.featuredImage,
author: {
"@type": "Person",
name: post.authorName,
},
};
};
// and export it through the handle, you could also create it inline here
// if you don't care about the type or using the `HandleStructuredData` type
export let handle = { structuredData };
```
Then, in the root route, add the `StructuredData` component together with the Remix's Scripts component, usually inside a Document component.
```tsx
import { Links, LiveReload, Meta, Scripts, ScrollRestoration } from "remix";
import { StructuredData } from "remix-utils";
type Props = { children: React.ReactNode; title?: string };
export function Document({ children, title }: Props) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Meta />
<Links />
<StructuredData />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
```
Now, any structured data you defined in the `StructuredDataFunction` will be added to the HTML, in the head. You may choose to include the `<StructuredData />` in either the head or the body, both are valid.
If you want to upgrade to use the `V2_MetaFunction`, first enable it in your Remix app:
```js
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
future: { v2_meta: true },
};
```
Then you can use it like this:
```ts
export let meta: V2_MetaFunction<typeof loader> = ({ data }) => {
let { post } = data;
return [
{
"script:ld+json": {
"@context": "https://schema.org",
"@type": "BlogPosting",
datePublished: post.published,
mainEntityOfPage: { "@type": "WebPage", "@id": post.postUrl },
image: post.featuredImage,
author: { "@type": "Person", name: post.authorName },
},
},
];
};
```
### useGlobalTransitionStates
This hook lets you know if the value of `transition.state` and every `fetcher.state` in the app.
```ts
import { useGlobalTransitionStates } from "remix-utils";
export function GlobalPendingUI() {
let states = useGlobalTransitionStates();
let states = useGlobalNavigationState();

@@ -704,3 +569,3 @@ if (state.includes("loading")) {

The return value of `useGlobalTransitionStates` can be `"idle"`, `"loading"` or `"submitting"`
The return value of `useGlobalNavigationState` can be `"idle"`, `"loading"` or `"submitting"`

@@ -711,3 +576,3 @@ > **Note** This is used by the hooks below to determine if the app is loading, submitting or both (pending).

This hook lets you know if the global transition or if one of any active fetchers is either loading or submitting.
This hook lets you know if the global navigation, if one of any active fetchers is either loading or submitting, or if the revalidator is running.

@@ -750,3 +615,3 @@ ```ts

This hook lets you know if the global transition or if one of any active fetchers is loading.
This hook lets you know if the global transition, if one of any active fetchers is loading, or if the revalidator is running

@@ -1158,3 +1023,6 @@ ```ts

let cookie = createCookie("returnTo", cookieOptions);
let schema = z.string().url();
// I recommend you to always add `nullable` to your schema, if a cookie didn't
// come with the request Cookie header Remix will return null, and it can be
// useful to remove it later when clearing the cookie
let schema = z.string().url().nullable();

@@ -1168,6 +1036,6 @@ // pass the cookie and the schema

// this will not pass the schema validation and throw a ZodError
await cookie.serialize("a random string that's not a URL");
await typedCookie.serialize("a random string that's not a URL");
// this will make TS yell because it's not a string, if you ignore it it will
// throw a ZodError
await cookie.serialize(123);
await typedCookie.serialize(123);
```

@@ -1179,3 +1047,3 @@

let cookie = createCookie("session", cookieOptions);
let schema = z.object({ token: z.string() });
let schema = z.object({ token: z.string() }).nullable();

@@ -1205,8 +1073,10 @@ let sessionStorage = createCookieSessionStorage({

let schema = z.object({
token: z.string().refine(async (token) => {
let user = await getUserByToken(token);
return user !== null;
}, "INVALID_TOKEN"),
});
let schema = z
.object({
token: z.string().refine(async (token) => {
let user = await getUserByToken(token);
return user !== null;
}, "INVALID_TOKEN"),
})
.nullable();

@@ -1219,2 +1089,22 @@ let sessionTypedCookie = createTypedCookie({ cookie, schema });

Finally, to be able to delete a cookie, you can add `.nullable()` to your schema and serialize it with `null` as value.
```ts
// Set the value as null and expires as current date - 1 second so the browser expires the cookie
await typedCookie.serialize(null, { expires: new Date(Date.now() - 1) });
```
If you didn't add `.nullable()` to your schema, you will need to provide a mock value and set the expires date to the past.
```ts
let cookie = createCookie("returnTo", cookieOptions);
let schema = z.string().url().nullable();
let typedCookie = createTypedCookie({ cookie, schema });
await typedCookie.serialize("some fake url to pass schema validation", {
expires: new Date(Date.now() - 1),
});
```
### Typed Sessions

@@ -1234,3 +1124,3 @@

// you can use a Remix's Cookie container or a Remix Utils's Typed Cookie container
// you can use a Remix's Cookie container or a Remix Utils' Typed Cookie container
let sessionStorage = createCookieSessionStorage({ cookie });

@@ -1340,2 +1230,19 @@

Because SSE count towards the limit of HTTP connections per domain, the `useEventSource` hook keeps a global map of connections based on the provided URL and options. As long as they are the same, the hook will open a single SSE connection and share it between instances of the hook.
Once there are no more instances of the hook re-using a connection, it will be closed and removed from the map.
You can use the `<EventSourceProvider />` component to control the map.
```tsx
let map: EventSourceMap = new Map();
return (
<EventSourceProvider value={map}>
<YourAppOrPartOfIt />
</EventSourceProvider>
);
```
This way, you can overwrite the map with a new one for a specific part of your app. Note that this provider is optional and a default map will be used if you don't provide one.
### Rolling Cookies

@@ -1656,2 +1563,138 @@

### Debounced Fetcher
The `useDebounceFetcher` is a wrapper of `useFetcher` that adds debounce support to `fetcher.submit`.
The hook is based on @JacobParis [article](https://www.jacobparis.com/content/use-debounce-fetcher).
The main difference with Jacob's version is that Remix Utils' version overwrites `fetcher.submit` instead of appending a `fetcher.debounceSubmit` method.
```tsx
import { useDebounceFetcher } from "remix-utils";
export function Component({ data }) {
let fetcher = useDebounceFetcher<Type>();
function handleClick() {
fetcher.submit(data, { debounceTimeout: 1000 });
}
return (
<button type="button" onClick={handleClick}>
Do Something
</button>
);
}
```
### Derive Fetcher init
Derive the value of the deprecated `fetcher.type` from the fetcher and navigation data.
```ts
import { getFetcherType } from "remix-utils";
function Component() {
let fetcher = useFetcher();
let navigation = useNavigation();
let fetcherType = getFetcherType(fetcher, navigation);
useEffect(() => {
if (fetcherType === "done") {
// do something once the fetcher is done submitting the data
}
}, [fetcherType]);
}
```
You can also use the React Hook API which let's you avoid calling `useNavigation`.
```ts
import { useFetcherType } from "remix-utils";
function Component() {
let fetcher = useFetcher();
let fetcherType = useFetcherType(fetcher);
useEffect(() => {
if (fetcherType === "done") {
// do something once the fetcher is done submitting the data
}
}, [fetcherType]);
}
```
If you need to pass the fetcher type around, you can also import `FetcherType` type.
```ts
import { type FetcherType } from "remix-utils";
function useCallbackOnDone(type: FetcherType, cb) {
useEffect(() => {
if (type === "done") cb();
}, [type, cb]);
}
```
### respondTo for Content Negotiation
If you're building a resource route and wants to send a different response based on what content type the client requested (e.g. send the same data as PDF or XML or JSON), you will need to implement content negotiation, this can be done with the `respondTo` header.
```ts
import { respondTo } from "remix-utils";
export async function loader({ request }: LoaderArgs) {
// do any work independent of the response type before respondTo
let data = await getData(request);
let headers = new Headers({ vary: "accept" });
// Here we will decide how to respond to different content types
return respondTo(request, {
// The handler can be a subtype handler, in `text/html` html is the subtype
html() {
// We can call any function only really need to respond to this
// content-type
let body = ReactDOMServer.renderToString(<UI {...data} />);
headers.append("content-type", "text/html");
return new Response(body, { headers });
},
// It can also be a highly specific type
async "application/rss+xml"() {
// we can do more async work inside this code if needed
let body = await generateRSSFeed(data);
headers.append("content-type", "application/rss+xml");
return new Response(body, { headers });
},
// Or a generic type
async text() {
// To respond to any text type, e.g. text/plain, text/csv, etc.
let body = generatePlain(data);
headers.append("content-type", "text/plain");
return new Response(body, { headers });
},
// The default will be used if the accept header doesn't match any of the
// other handlers
default() {
// Here we could have a default type of response, e.g. use json by
// default, or we can return a 406 which means the server can't respond
// with any of the requested content types
return new Response("Not Acceptable", { status: 406 });
},
});
}
```
Now, the `respondTo` function will check the `Accept` header and call the correct handler, to know which one to call it will use the `parseAcceptHeader` function also exported from Remix Utils
```ts
import { parseAcceptHeader } from "remix-utils";
let parsed = parseAcceptHeader(
"text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, image/*, */*;q=0.8"
);
```
The result is an array with the type, subtype and extra params (e.g. the `q` value). The order will be the same encountered in the header, in the example aabove `text/html` will be the first, followed by `application/xhtml+xml`.
This means that the `respondTo` helper will prioritize any handler that match `text/html`, in our example above, that will be the `html` handler, but if we remove it then the `text` handler will be called instead.67
## Author

@@ -1658,0 +1701,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc