remix-utils
Advanced tools
Comparing version 7.6.0 to 7.7.0
@@ -70,27 +70,25 @@ /** | ||
*/ | ||
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); | ||
export async function timeout(promise, options) { | ||
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(); | ||
throw new TimeoutError(`Timed out after ${options.ms}ms`); | ||
} | ||
catch (error) { | ||
if (timer) | ||
clearTimeout(timer); | ||
reject(error); | ||
} | ||
}); | ||
return result; | ||
} | ||
catch (error) { | ||
if (timer) | ||
clearTimeout(timer); | ||
throw error; | ||
} | ||
} | ||
@@ -97,0 +95,0 @@ /** |
@@ -0,3 +1,3 @@ | ||
import { useSearchParams } from "@remix-run/react"; | ||
import * as React from "react"; | ||
import { useSearchParams } from "@remix-run/react"; | ||
/** | ||
@@ -4,0 +4,0 @@ * Include existing query params as hidden inputs in a form. |
@@ -45,6 +45,8 @@ import * as React from "react"; | ||
type?: ScriptType; | ||
/** | ||
* Optional element ID. Use only if the script element needs to be explicitly referenced later. | ||
*/ | ||
id?: string; | ||
}; | ||
export interface ExternalScriptsFunction<Loader = unknown> { | ||
(args: HandleConventionArguments<Loader>): ScriptDescriptor[]; | ||
} | ||
export type ExternalScriptsFunction<Loader = unknown> = (args: HandleConventionArguments<Loader>) => ScriptDescriptor[]; | ||
/** | ||
@@ -92,2 +94,2 @@ * Define the shape of the `handle` export if you want to use `scripts`. Combine | ||
export declare function useExternalScripts(): any[]; | ||
export declare function ExternalScript({ src, preload, async, defer, crossOrigin, integrity, type, referrerPolicy, noModule, nonce, }: ScriptDescriptor): React.JSX.Element | null; | ||
export declare function ExternalScript({ src, preload, async, defer, crossOrigin, integrity, type, referrerPolicy, noModule, nonce, id, }: ScriptDescriptor): React.JSX.Element | null; |
@@ -0,3 +1,3 @@ | ||
import { useLocation, useMatches } from "@remix-run/react"; | ||
import * as React from "react"; | ||
import { useLocation, useMatches } from "@remix-run/react"; | ||
import { useHydrated } from "./use-hydrated.js"; | ||
@@ -63,3 +63,3 @@ /** | ||
} | ||
export function ExternalScript({ src, preload = false, async = true, defer = true, crossOrigin, integrity, type, referrerPolicy, noModule, nonce, }) { | ||
export function ExternalScript({ src, preload = false, async = true, defer = true, crossOrigin, integrity, type, referrerPolicy, noModule, nonce, id, }) { | ||
let isHydrated = useHydrated(); | ||
@@ -81,2 +81,3 @@ let startsHydrated = React.useRef(isHydrated); | ||
nonce, | ||
id, | ||
}; | ||
@@ -87,3 +88,3 @@ for (let [key, value] of Object.entries(attributes)) { | ||
} | ||
document.body.append($script); | ||
document.body.appendChild($script); | ||
return () => $script.remove(); | ||
@@ -101,2 +102,3 @@ }, [ | ||
type, | ||
id, | ||
]); | ||
@@ -109,3 +111,3 @@ if (startsHydrated.current && isHydrated) | ||
preload && (React.createElement("link", { rel: rel, href: src, as: as, crossOrigin: crossOrigin, integrity: integrity, referrerPolicy: referrerPolicy })), | ||
React.createElement("script", { src: src, defer: defer, async: async, type: type, noModule: noModule, nonce: nonce, crossOrigin: crossOrigin, integrity: integrity, referrerPolicy: referrerPolicy }))); | ||
React.createElement("script", { id: id, src: src, defer: defer, async: async, type: type, noModule: noModule, nonce: nonce, crossOrigin: crossOrigin, integrity: integrity, referrerPolicy: referrerPolicy }))); | ||
} |
@@ -0,3 +1,3 @@ | ||
import type { Location, Params, useMatches } from "@remix-run/react"; | ||
import type { RouterState } from "@remix-run/router"; | ||
import type { Location, Params, useMatches } from "@remix-run/react"; | ||
export type HandleConventionArguments<Data = unknown> = { | ||
@@ -4,0 +4,0 @@ id: string; |
@@ -1,2 +0,2 @@ | ||
import type { SubmitOptions, FetcherWithComponents, SubmitFunction } from "@remix-run/react"; | ||
import type { FetcherWithComponents, SubmitFunction, SubmitOptions } from "@remix-run/react"; | ||
type SubmitTarget = Parameters<SubmitFunction>["0"]; | ||
@@ -3,0 +3,0 @@ /** |
@@ -12,3 +12,3 @@ import { useFetcher } from "@remix-run/react"; | ||
}; | ||
}, [timeoutRef]); | ||
}, []); | ||
let fetcher = useFetcher(); | ||
@@ -15,0 +15,0 @@ // Clone the original submit to avoid a recursive loop |
@@ -1,2 +0,2 @@ | ||
import type { SubmitOptions, SubmitFunction } from "@remix-run/react"; | ||
import type { SubmitFunction, SubmitOptions } from "@remix-run/react"; | ||
type SubmitTarget = Parameters<SubmitFunction>["0"]; | ||
@@ -3,0 +3,0 @@ export declare function useDebounceSubmit(): (target: SubmitTarget, options?: SubmitOptions & { |
@@ -12,3 +12,3 @@ import { useSubmit } from "@remix-run/react"; | ||
}; | ||
}, [timeoutRef]); | ||
}, []); | ||
// Clone the original submit to avoid a recursive loop | ||
@@ -15,0 +15,0 @@ const originalSubmit = useSubmit(); |
@@ -8,3 +8,3 @@ import { PrefetchPageLinks, useNavigate } from "@remix-run/react"; | ||
let a = event.target.closest("a"); | ||
if (a && a.hasAttribute("href") && a.host === window.location.host) | ||
if (a?.hasAttribute("href") && a.host === window.location.host) | ||
return a; | ||
@@ -11,0 +11,0 @@ return; |
@@ -5,2 +5,3 @@ /// <reference types="react" /> | ||
event?: string; | ||
enabled?: boolean; | ||
} | ||
@@ -18,2 +19,2 @@ export type EventSourceMap = Map<string, { | ||
*/ | ||
export declare function useEventSource(url: string | URL, { event, init }?: EventSourceOptions): string | null; | ||
export declare function useEventSource(url: string | URL, { event, init, enabled }?: EventSourceOptions): string | null; |
@@ -1,2 +0,2 @@ | ||
import { useEffect, useState, createContext, useContext } from "react"; | ||
import { createContext, useContext, useEffect, useState } from "react"; | ||
const context = createContext(new Map()); | ||
@@ -10,6 +10,9 @@ export const EventSourceProvider = context.Provider; | ||
*/ | ||
export function useEventSource(url, { event = "message", init } = {}) { | ||
export function useEventSource(url, { event = "message", init, enabled = true } = {}) { | ||
let map = useContext(context); | ||
let [data, setData] = useState(null); | ||
useEffect(() => { | ||
if (!enabled) { | ||
return undefined; | ||
} | ||
let key = [url.toString(), init?.withCredentials].join("::"); | ||
@@ -36,4 +39,4 @@ let value = map.get(key) ?? { | ||
}; | ||
}, [url, event, init, map]); | ||
}, [url, event, init, map, enabled]); | ||
return data; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { useNavigation, useFetchers, useRevalidator } from "@remix-run/react"; | ||
import { useFetchers, useNavigation, useRevalidator } from "@remix-run/react"; | ||
import { useMemo } from "react"; | ||
@@ -3,0 +3,0 @@ /** |
import { useSyncExternalStore } from "react"; | ||
function subscribe() { | ||
// biome-ignore lint/suspicious/noEmptyBlockStatements: Mock function | ||
return () => { }; | ||
@@ -4,0 +5,0 @@ } |
@@ -36,3 +36,3 @@ const DEFAULT_OPTIONS = { | ||
configureMaxAge(headers) { | ||
var { maxAge } = this.options; | ||
const { maxAge } = this.options; | ||
if (!this.isNumber(maxAge)) | ||
@@ -39,0 +39,0 @@ return headers; |
@@ -101,7 +101,6 @@ import cryptoJS from "crypto-js"; | ||
parseCookie(data, headers) { | ||
if (data instanceof Request) | ||
headers = data.headers; | ||
if (!headers) | ||
let _headers = data instanceof Request ? data.headers : headers; | ||
if (!_headers) | ||
return null; | ||
return this.cookie.parse(headers.get("cookie")); | ||
return this.cookie.parse(_headers.get("cookie")); | ||
} | ||
@@ -108,0 +107,0 @@ sign(token) { |
@@ -8,14 +8,6 @@ interface SendFunctionArgs { | ||
} | ||
interface SendFunction { | ||
(args: SendFunctionArgs): void; | ||
} | ||
interface CleanupFunction { | ||
(): void; | ||
} | ||
interface AbortFunction { | ||
(): void; | ||
} | ||
interface InitFunction { | ||
(send: SendFunction, abort: AbortFunction): CleanupFunction; | ||
} | ||
type SendFunction = (args: SendFunctionArgs) => void; | ||
type CleanupFunction = () => void; | ||
type AbortFunction = () => void; | ||
type InitFunction = (send: SendFunction, abort: AbortFunction) => CleanupFunction; | ||
/** | ||
@@ -22,0 +14,0 @@ * A response helper to use Server Sent Events server-side |
@@ -48,5 +48,4 @@ import { isIP } from "is-ip"; | ||
return part.slice(4); | ||
continue; | ||
} | ||
return null; | ||
} |
export interface HoneypotInputProps { | ||
/** | ||
* The name expected to be used by the honeypot input field. | ||
*/ | ||
nameFieldName: string; | ||
/** | ||
* The name expected to be used by the honeypot valid from input field. | ||
*/ | ||
validFromFieldName: string | null; | ||
/** | ||
* The encrypted value of the current timestamp. | ||
*/ | ||
encryptedValidFrom: string; | ||
} | ||
export interface HoneypotConfig { | ||
/** | ||
* Enable randomization of the name field name, this way the honeypot field | ||
* name will be different for each request. | ||
*/ | ||
randomizeNameFieldName?: boolean; | ||
/** | ||
* The name of the field that will be used for the honeypot input. | ||
*/ | ||
nameFieldName?: string; | ||
/** | ||
* The name of the field that will be used for the honeypot valid from input. | ||
*/ | ||
validFromFieldName?: string | null; | ||
/** | ||
* The seed used for the encryption of the valid from timestamp. | ||
*/ | ||
encryptionSeed?: string; | ||
} | ||
/** | ||
* The error thrown when the Honeypot fails, meaning some automated bot filled | ||
* the form and the request is probably spam. | ||
*/ | ||
export declare class SpamError extends Error { | ||
readonly name = "SpamError"; | ||
} | ||
/** | ||
* Module used to implement a Honeypot. | ||
* A Honeypot is a visually hidden input that is used to detect spam bots. This | ||
* field is expected to be left empty by users because they don't see it, but | ||
* bots will fill it falling in the honeypot trap. | ||
*/ | ||
export declare class Honeypot { | ||
private generatedEncryptionSeed; | ||
protected config: HoneypotConfig; | ||
private generatedEncryptionSeed; | ||
constructor(config?: HoneypotConfig); | ||
/** | ||
* Get the HoneypotInputProps to be used in your forms. | ||
* @param {Object} options The options for the input props. | ||
* @param {number} options.validFromTimestamp Since when the timestamp is valid. | ||
* @returns {HoneypotInputProps} The props to be used in the form. | ||
*/ | ||
getInputProps({ validFromTimestamp, }?: { | ||
@@ -19,0 +58,0 @@ validFromTimestamp?: number | undefined; |
import CryptoJS from "crypto-js"; | ||
/** | ||
* The error thrown when the Honeypot fails, meaning some automated bot filled | ||
* the form and the request is probably spam. | ||
*/ | ||
export class SpamError extends Error { | ||
name = "SpamError"; | ||
} | ||
const DEFAULT_NAME_FIELD_NAME = "name__confirm"; | ||
const DEFAULT_VALID_FROM_FIELD_NAME = "from__confirm"; | ||
/** | ||
* Module used to implement a Honeypot. | ||
* A Honeypot is a visually hidden input that is used to detect spam bots. This | ||
* field is expected to be left empty by users because they don't see it, but | ||
* bots will fill it falling in the honeypot trap. | ||
*/ | ||
export class Honeypot { | ||
generatedEncryptionSeed = this.randomValue(); | ||
config; | ||
generatedEncryptionSeed = this.randomValue(); | ||
constructor(config = {}) { | ||
this.config = config; | ||
} | ||
/** | ||
* Get the HoneypotInputProps to be used in your forms. | ||
* @param {Object} options The options for the input props. | ||
* @param {number} options.validFromTimestamp Since when the timestamp is valid. | ||
* @returns {HoneypotInputProps} The props to be used in the form. | ||
*/ | ||
getInputProps({ validFromTimestamp = Date.now(), } = {}) { | ||
@@ -13,0 +30,0 @@ return { |
@@ -7,3 +7,3 @@ export async function namedAction(input, actions) { | ||
if (name === null && "default" in actions) { | ||
return actions["default"](); | ||
return actions.default(); | ||
} | ||
@@ -10,0 +10,0 @@ if (name === null) |
@@ -60,2 +60,4 @@ /** | ||
let route = context.routeModules[match.route.id]; | ||
if (!route) | ||
return []; | ||
if (route.links instanceof Function) | ||
@@ -62,0 +64,0 @@ return route.links(); |
@@ -19,5 +19,4 @@ import { getHeaders } from "./get-headers.js"; | ||
return handler(); | ||
continue; | ||
} | ||
return handlers.default(); | ||
} |
@@ -14,10 +14,10 @@ const DEFAULT_REDIRECT = "/"; | ||
return defaultRedirect; | ||
to = to.trim(); | ||
if (!to.startsWith("/") || | ||
to.startsWith("//") || | ||
to.startsWith("/\\") || | ||
to.includes("..")) { | ||
let trimmedTo = to.trim(); | ||
if (!trimmedTo.startsWith("/") || | ||
trimmedTo.startsWith("//") || | ||
trimmedTo.startsWith("/\\") || | ||
trimmedTo.includes("..")) { | ||
return defaultRedirect; | ||
} | ||
return to; | ||
return trimmedTo; | ||
} |
@@ -6,3 +6,3 @@ import { Cookie, CookieParseOptions, CookieSerializeOptions } from "@remix-run/server-runtime"; | ||
parse(cookieHeader: string | null, options?: CookieParseOptions): Promise<z.infer<Schema> | null>; | ||
serialize(value: z.infer<Schema>, options?: CookieSerializeOptions): Promise<string>; | ||
serialize(value: z.input<Schema>, options?: CookieSerializeOptions): Promise<string>; | ||
} | ||
@@ -9,0 +9,0 @@ export declare function createTypedCookie<Schema extends z.ZodTypeAny>({ cookie, schema, }: { |
{ | ||
"name": "remix-utils", | ||
"version": "7.6.0", | ||
"version": "7.7.0", | ||
"license": "MIT", | ||
@@ -9,2 +9,5 @@ "engines": { | ||
"type": "module", | ||
"funding": [ | ||
"https://github.com/sponsors/sergiodxa" | ||
], | ||
"exports": { | ||
@@ -54,6 +57,6 @@ "./package.json": "./package.json", | ||
"build": "tsc --project tsconfig.json --outDir ./build", | ||
"postbuild": "prettier --write \"build/**/*.js\" \"build/**/*.d.ts\"", | ||
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.tsx\" \"test/**/*.ts\" \"test/**/*.tsx\" \"*.md\" \"package.json\"", | ||
"format": "biome format --write .", | ||
"typecheck": "tsc --project tsconfig.json --noEmit", | ||
"lint": "eslint --ext .ts,.tsx src/", | ||
"check": "biome check .", | ||
"lint": "biome lint .", | ||
"test": "vitest --run", | ||
@@ -98,3 +101,2 @@ "test:watch": "vitest", | ||
"@remix-run/cloudflare": "^2.0.0", | ||
"@remix-run/deno": "^2.0.0", | ||
"@remix-run/node": "^2.0.0", | ||
@@ -113,5 +115,2 @@ "@remix-run/react": "^2.0.0", | ||
}, | ||
"@remix-run/deno": { | ||
"optional": true | ||
}, | ||
"@remix-run/node": { | ||
@@ -144,2 +143,3 @@ "optional": true | ||
"@arethetypeswrong/cli": "^0.15.0", | ||
"@biomejs/biome": "^1.7.2", | ||
"@remix-run/node": "^2.0.0", | ||
@@ -150,39 +150,26 @@ "@remix-run/react": "^2.0.0", | ||
"@testing-library/jest-dom": "^6.1.3", | ||
"@testing-library/react": "^14.0.0", | ||
"@testing-library/react": "^15.0.2", | ||
"@types/bun": "^1.1.1", | ||
"@types/crypto-js": "^4.1.2", | ||
"@types/react": "^18.2.25", | ||
"@typescript-eslint/eslint-plugin": "^7.0.2", | ||
"@typescript-eslint/parser": "^7.0.1", | ||
"@vitejs/plugin-react": "^4.1.0", | ||
"@vitest/coverage-v8": "^1.3.1", | ||
"@types/react": "^18.2.78", | ||
"@vitejs/plugin-react": "^4.2.1", | ||
"@vitest/coverage-v8": "^1.5.0", | ||
"crypto-js": "^4.1.1", | ||
"eslint": "^8.12.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-import-resolver-typescript": "^3.6.1", | ||
"eslint-plugin-cypress": "^2.15.1", | ||
"eslint-plugin-import": "^2.28.1", | ||
"eslint-plugin-jest-dom": "^5.1.0", | ||
"eslint-plugin-jsx-a11y": "^6.7.1", | ||
"eslint-plugin-prettier": "^5.0.0", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eslint-plugin-react": "^7.33.2", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"eslint-plugin-testing-library": "^6.0.2", | ||
"eslint-plugin-unicorn": "^52.0.0", | ||
"happy-dom": "^14.3.6", | ||
"happy-dom": "^14.7.1", | ||
"intl-parse-accept-language": "^1.0.0", | ||
"is-ip": "5.0.1", | ||
"msw": "^2.0.0", | ||
"prettier": "^3.0.3", | ||
"msw": "^2.2.13", | ||
"react": "^18.0.0", | ||
"react-dom": "^18.0.0", | ||
"ts-node": "^10.9.1", | ||
"typedoc": "^0.25.13", | ||
"typedoc-plugin-mdn-links": "^3.1.23", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.0.0", | ||
"vitest": "^0.34.6", | ||
"vite": "^5.2.8", | ||
"vitest": "^1.4.0", | ||
"zod": "^3.22.4" | ||
}, | ||
"dependencies": { | ||
"type-fest": "^4.3.3" | ||
"type-fest": "^4.18.1" | ||
} | ||
} |
@@ -1,5 +0,5 @@ | ||
/* eslint-disable unicorn/no-process-exit */ | ||
import { file, spawn } from "bun"; | ||
async function main() { | ||
const proc = Bun.spawn([ | ||
let proc = spawn([ | ||
"bunx", | ||
@@ -14,5 +14,5 @@ "attw", | ||
const text = await new Response(proc.stdout).text(); | ||
let text = await new Response(proc.stdout).text(); | ||
const entrypointLines = text | ||
let entrypointLines = text | ||
.slice(text.indexOf('"remix-utils/')) | ||
@@ -30,5 +30,5 @@ .split("\n") | ||
const pkg = await Bun.file("package.json").json(); | ||
const entrypoints = entrypointLines.map((entrypointLine) => { | ||
const [entrypoint, ...resolutionColumns] = entrypointLine.split("│"); | ||
let pkg = await file("package.json").json(); | ||
let entrypoints = entrypointLines.map((entrypointLine) => { | ||
let [entrypoint, ...resolutionColumns] = entrypointLine.split("│"); | ||
return { | ||
@@ -41,18 +41,9 @@ entrypoint: entrypoint.replace(pkg.name, ".").trim(), | ||
const entrypointsWithProblems = entrypoints.filter( | ||
let entrypointsWithProblems = entrypoints.filter( | ||
(item) => item.esm.includes("fail") || item.bundler.includes("fail"), | ||
); | ||
if (entrypointsWithProblems.length > 0) { | ||
console.error("Entrypoints with problems:"); | ||
console.log( | ||
`---\n${entrypointsWithProblems | ||
.map( | ||
({ entrypoint, esm, bundler }) => | ||
`entrypoint: ${entrypoint}\nesm: ${esm}\nbundler: ${bundler}`, | ||
) | ||
.join("\n---\n")}\n---`, | ||
); | ||
process.exit(1); | ||
} else { | ||
console.log("No problems found."); | ||
} | ||
@@ -59,0 +50,0 @@ } |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
204096
10
27
85
3625
2155
+ Added@remix-run/cloudflare@2.14.0(transitive)
+ Added@remix-run/node@2.14.0(transitive)
+ Added@remix-run/react@2.14.0(transitive)
+ Added@remix-run/server-runtime@2.14.0(transitive)
+ Addedtype-fest@4.27.0(transitive)
- Removed@remix-run/cloudflare@2.15.0(transitive)
- Removed@remix-run/deno@2.15.0(transitive)
- Removed@remix-run/node@2.15.0(transitive)
- Removed@remix-run/react@2.15.0(transitive)
- Removed@remix-run/server-runtime@2.15.0(transitive)
- Removedmime@3.0.0(transitive)
- Removedtype-fest@4.27.1(transitive)
Updatedtype-fest@^4.18.1