From e81d87e07bf352f88f6cda5fec7a91febe7c2d7b Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 13:48:02 +0100 Subject: [PATCH 01/49] extracting generators out into their own folder so they're tree-shakeable --- .gitignore | 1 + README.md | 91 ++++++++------ TODO.md | 1 + .../drizzle/pages/api/lnauth/[...lnauth].ts | 7 ++ examples/kv/pages/api/lnauth/[...lnauth].ts | 7 ++ .../pages/api/lnauth/[...lnauth].ts | 6 + src/generators/avatar.ts | 10 ++ src/generators/index.ts | 3 + src/generators/name.ts | 18 +++ src/generators/qr.ts | 44 +++++++ src/main/config/default.ts | 98 +++------------- src/main/config/types.ts | 12 +- src/main/handlers/login.tsx | 111 +++++++++--------- src/main/handlers/qr.ts | 4 +- 14 files changed, 233 insertions(+), 180 deletions(-) create mode 100644 src/generators/avatar.ts create mode 100644 src/generators/index.ts create mode 100644 src/generators/name.ts create mode 100644 src/generators/qr.ts diff --git a/.gitignore b/.gitignore index e417fc8..fb98787 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,6 @@ yarn-error.log* # build /main /react +/generators /index.d.ts* /index.js diff --git a/README.md b/README.md index e53254e..2c41bd4 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ import NextAuthLightning, { LnAuthData, NextAuthLightningConfig, } from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; const config: NextAuthLightningConfig = { // required @@ -91,8 +94,11 @@ const config: NextAuthLightningConfig = { // delete lnurl auth session data based on k1 id }, }, + generateQr, // optional + generateName, + generateAvatar, theme: { colorScheme: "dark", }, @@ -125,6 +131,18 @@ export const authOptions: AuthOptions = { export default NextAuth(authOptions); ``` +# Generators + +This package provides several generator functions that can be used to deterministically generate avatars and usernames as well as a generic QR code generator. The generators are tree-shakeable. If you don't need them, simply don't import them and they'll not be included in your app's bundle. + +```typescript +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; +``` + +> Note: you can also write your own generator functions if those provided don't suit your needs! + # Configuration There are various configurations available to you. Some are required, some are optional. @@ -202,6 +220,23 @@ const config: NextAuthLightningConfig = { // delete lnurl auth session data based on k1 id }, }, + /** + * @param {function} qr.generateQr + * + * Set the QR code generator function. + * It must return a correctly formatted string containing svg XML markup. + * + * A default QR code generator is provided. It can be imported from: + * `import { generateQr } from "next-auth-lightning-provider/generators/qr";` + * + * the default library used is: + * @see https://www.npmjs.com/package/qrcode + */ + async generateQr(data, config) { + return { + qr: '...........' + }; + }, // optional pages: { @@ -268,40 +303,7 @@ const config: NextAuthLightningConfig = { }, - /** - * Control the QR code generation and styling. - */ - qr: { - /** - * @param {function} qr.generateQr - * - * Override the default QR code generator. - * It must return a correctly formatted string containing svg XML markup. - * - * the default library used is: - * https://www.npmjs.com/package/qrcode - */ - async generateQr(data, config) { - return { - qr: '...........' - }; - }, - /** - * @param {object} color - * - * Override the default QR code colors. If left undefined, the - * QR will be styled based on the theme styles. - */ - color: { dark: "#000000", light: "#ffffff" }, - - /** - * @param {number} margin - * - * Override the default QR code margin. - */ - margin: 2, - }, /** * Control the color scheme of the "Login with Lightning" page and button. @@ -335,6 +337,29 @@ const config: NextAuthLightningConfig = { */ text: "#000000", + /** + * @param {object} color + * + * Override the default QR code background color. If left undefined, + * the QR will be styled based on the theme styles. + */ + qrBackground: "#ffffff", + + /** + * @param {object} color + * + * Override the default QR code foreground color. If left undefined, + * the QR will be styled based on the theme styles. + */ + qrForeground: "#000000", + + /** + * @param {number} margin + * + * Override the default QR code margin. + */ + qrMargin: 2, + /** * @param {string} loginButtonBackground * diff --git a/TODO.md b/TODO.md index 258ca6b..d01401d 100644 --- a/TODO.md +++ b/TODO.md @@ -33,3 +33,4 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - add info about deterministic generation of image and name - carefully scan for errors and typos - see if config docs can be moved into TS file and autogenerated +- add examples/images showing generator outputs. e.g. bottts images diff --git a/examples/drizzle/pages/api/lnauth/[...lnauth].ts b/examples/drizzle/pages/api/lnauth/[...lnauth].ts index a632045..8545c69 100644 --- a/examples/drizzle/pages/api/lnauth/[...lnauth].ts +++ b/examples/drizzle/pages/api/lnauth/[...lnauth].ts @@ -1,6 +1,10 @@ import NextAuthLightning, { NextAuthLightningConfig, } from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; + import { eq } from "drizzle-orm"; import { lnAuthTable, LnAuth } from "@/schema/db"; @@ -32,8 +36,11 @@ const config: NextAuthLightningConfig = { await db.delete(lnAuthTable).where(eq(lnAuthTable.k1, k1)); }, }, + generateQr, // optional + generateName, + generateAvatar, theme: { colorScheme: "dark", }, diff --git a/examples/kv/pages/api/lnauth/[...lnauth].ts b/examples/kv/pages/api/lnauth/[...lnauth].ts index c8a99e9..2d3890f 100644 --- a/examples/kv/pages/api/lnauth/[...lnauth].ts +++ b/examples/kv/pages/api/lnauth/[...lnauth].ts @@ -2,6 +2,10 @@ import NextAuthLightning, { LnAuthData, NextAuthLightningConfig, } from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; + import { kv } from "@vercel/kv"; import { env } from "@/env.mjs"; @@ -28,8 +32,11 @@ const config: NextAuthLightningConfig = { await kv.del(`k1:${k1}`); }, }, + generateQr, // optional + generateName, + generateAvatar, theme: { colorScheme: "dark", }, diff --git a/examples/login-page/pages/api/lnauth/[...lnauth].ts b/examples/login-page/pages/api/lnauth/[...lnauth].ts index 27e3e72..d58b026 100644 --- a/examples/login-page/pages/api/lnauth/[...lnauth].ts +++ b/examples/login-page/pages/api/lnauth/[...lnauth].ts @@ -2,6 +2,9 @@ import NextAuthLightning, { LnAuthData, NextAuthLightningConfig, } from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; import { env } from "@/env.mjs"; @@ -31,8 +34,11 @@ const config: NextAuthLightningConfig = { await storage.removeItem(`k1:${k1}`); }, }, + generateQr, // optional + generateName, + generateAvatar, pages: { signIn: "/login", }, diff --git a/src/generators/avatar.ts b/src/generators/avatar.ts new file mode 100644 index 0000000..c7982a4 --- /dev/null +++ b/src/generators/avatar.ts @@ -0,0 +1,10 @@ +import { createAvatar } from "@dicebear/core"; +import { bottts } from "@dicebear/collection"; + +import { Config } from "../main/config/types.js"; + +export const generateAvatar = async (seed: string, config: Config) => { + return { + image: createAvatar(bottts, { seed }).toString(), + }; +}; diff --git a/src/generators/index.ts b/src/generators/index.ts new file mode 100644 index 0000000..969da35 --- /dev/null +++ b/src/generators/index.ts @@ -0,0 +1,3 @@ +export * from "./avatar.js"; +export * from "./name.js"; +export * from "./qr.js"; diff --git a/src/generators/name.ts b/src/generators/name.ts new file mode 100644 index 0000000..cbb4be4 --- /dev/null +++ b/src/generators/name.ts @@ -0,0 +1,18 @@ +import { + uniqueNamesGenerator, + adjectives, + colors, + animals, +} from "unique-names-generator"; + +import { Config } from "../main/config/types.js"; + +export const generateName = async (seed: string, config: Config) => { + return { + name: uniqueNamesGenerator({ + dictionaries: [adjectives, colors, animals], + separator: "-", + seed, + }), + }; +}; diff --git a/src/generators/qr.ts b/src/generators/qr.ts new file mode 100644 index 0000000..beda63a --- /dev/null +++ b/src/generators/qr.ts @@ -0,0 +1,44 @@ +import merge from "lodash.merge"; + +import QRCode from "qrcode"; + +import { Config } from "../main/config/types.js"; + +export const generateQr = async (data: string, config: Config) => { + // generic preset theme options + const themeOptions = + config.theme.colorScheme === "dark" + ? { + margin: 0.5, + color: { + dark: config.theme.background, + light: config.theme.text, + }, + } + : { + margin: 0, + color: { + dark: config.theme.text, + light: config.theme.background, + }, + }; + + // qr specific option overrides + const qrOptions: any = { + color: { + dark: config.theme.qrForeground, + light: config.theme.qrBackground, + }, + margin: config.theme.qrMargin, + }; + + // merge options, prioritize explicit qrOptions + const options = merge(themeOptions, qrOptions); + + return { + qr: (await QRCode.toString(data, { + ...options, + type: "svg", + })) as unknown as string, + }; +}; diff --git a/src/main/config/default.ts b/src/main/config/default.ts index ac9ba8c..cbd6b9a 100644 --- a/src/main/config/default.ts +++ b/src/main/config/default.ts @@ -1,92 +1,32 @@ import merge from "lodash.merge"; import { z } from "zod"; -import { createAvatar } from "@dicebear/core"; -import { bottts } from "@dicebear/collection"; - -import QRCode from "qrcode"; -import { - uniqueNamesGenerator, - adjectives, - colors, - animals, -} from "unique-names-generator"; - import { Config, UserConfig, OptionalConfig, ThemeStyles } from "./types.js"; import { hardConfig } from "./hard.js"; const colorSchemeLight: ThemeStyles = { background: "#ececec", - backgroundCard: "#fff", - text: "#000", + backgroundCard: "#ffffff", + text: "#000000", + qrBackground: "#0d1117", + qrForeground: "#ffffff", + qrMargin: 1, loginButtonBackground: "#24292f", - loginButtonText: "#fff", + loginButtonText: "#ffffff", }; const colorSchemeDark: ThemeStyles = { background: "#161b22", backgroundCard: "#0d1117", - text: "#fff", + text: "#ffffff", + qrBackground: "#ffffff", + qrForeground: "#0d1117", + qrMargin: 1, loginButtonBackground: "#24292f", - loginButtonText: "#fff", + loginButtonText: "#ffffff", }; const defaultConfig: Partial = { - async generateAvatar(seed) { - return { - image: createAvatar(bottts, { seed }).toString(), - }; - }, - async generateName(seed) { - return { - name: uniqueNamesGenerator({ - dictionaries: [adjectives, colors, animals], - separator: "-", - seed, - }), - }; - }, - qr: { - async generateQr(data, config) { - // generic preset theme options - const themeOptions = - config.theme.colorScheme === "dark" - ? { - margin: 0.5, - color: { - dark: config.theme.background, - light: config.theme.text, - }, - } - : { - margin: 0, - color: { - dark: config.theme.text, - light: config.theme.background, - }, - }; - - // qr specific option overrides - const qrOptions: any = {}; - if (config.qr?.color) { - qrOptions.color = config.qr.color; - qrOptions.margin = 0.5; - } - if (typeof config.qr?.margin === "number") { - qrOptions.margin = config.qr.margin; - } - - // merge options, prioritize explicit qrOptions - const options = merge(themeOptions, qrOptions); - - return { - qr: (await QRCode.toString(data, { - ...options, - type: "svg", - })) as unknown as string, - }; - }, - }, pages: { signIn: "/api/lnauth/login", // pre-configured qr lightning login error: "/api/auth/signin", // default next-auth error page @@ -108,22 +48,11 @@ const configValidation = z update: z.function(), delete: z.function(), }), + generateQr: z.function(), // optional generateAvatar: z.function().nullable().optional(), generateName: z.function().nullable().optional(), - qr: z - .object({ - generateQr: z.function().optional(), - color: z - .object({ - dark: z.string().optional(), - light: z.string().optional(), - }) - .nullish(), - margin: z.number().optional(), - }) - .nullish(), pages: z .object({ signIn: z.string().optional(), @@ -140,6 +69,9 @@ const configValidation = z error: z.string().optional(), loginButtonBackground: z.string().optional(), loginButtonText: z.string().optional(), + qrBackground: z.string().optional(), + qrForeground: z.string().optional(), + qrMargin: z.number().optional(), }) .nullish(), }) diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 5a2e655..75c9b37 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -71,12 +71,16 @@ export type RequiredConfig = { ) => Promise; delete: (args: { k1: string }, req: NextApiRequest) => Promise; }; + generateQr: (data: string, config: Config) => Promise<{ qr: string }>; }; export type ThemeStyles = { background: string; backgroundCard: string; text: string; + qrBackground: string; + qrForeground: string; + qrMargin: number; loginButtonBackground: string; loginButtonText: string; }; @@ -94,14 +98,6 @@ export type OptionalConfig = { | ((seed: string, config: Config) => Promise<{ name: string }>) | null; - qr: { - generateQr?: - | ((data: string, config: Config) => Promise<{ qr: string }>) - | null; - color?: { dark?: string; light: string } | null; - margin?: number | null; - }; - theme: { colorScheme?: "auto" | "dark" | "light"; } & Partial; diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 571fa10..84b615d 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -13,8 +13,9 @@ function AuthPage({ config }: { config: Config }) { alignItems: "center", justifyContent: "center", height: "100vh", + boxSizing: "border-box", margin: 0, - padding: 0, + padding: 20, fontFamily: `ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji`, @@ -22,60 +23,62 @@ function AuthPage({ config }: { config: Config }) { color: config.theme.text, }} > - {/* loading component is rendered and shown initially, before window.onload is triggered */} - +
+ {/* loading component is rendered and shown initially, before window.onload is triggered */} + - {/* login component is rendered with display: none, after window.onload is triggered */} -
); } diff --git a/src/main/handlers/qr.ts b/src/main/handlers/qr.ts index 8b98698..ff17f1a 100644 --- a/src/main/handlers/qr.ts +++ b/src/main/handlers/qr.ts @@ -10,7 +10,7 @@ export default async function handler( res: NextApiResponse, config: Config ) { - if (!config.qr.generateQr) throw new Error("Avatars are not enabled"); + if (!config.generateQr) throw new Error("QRs are not enabled"); const url = req.url?.toString(); if (!url) throw new Error("Invalid url"); @@ -19,7 +19,7 @@ export default async function handler( if (!name) throw new Error("Invalid file name"); if (ext !== ".svg") throw new Error("Invalid file type"); - const { qr } = await config.qr.generateQr(`lightning:${name}`, config); + const { qr } = await config.generateQr(`lightning:${name}`, config); res.setHeader("content-type", "image/svg+xml"); res.setHeader("cache-control", `public, max-age=${cacheDuration}`); From 30110962799360dea7048bb8c3b18120c926147d Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 19:15:35 +0100 Subject: [PATCH 02/49] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2517f39..94986b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.11", + "version": "1.0.0-alpha.12", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", From 5f4380a021350f32717c1b7f0600e9a593d4d9d0 Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 19:17:02 +0100 Subject: [PATCH 03/49] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94986b6..6090b9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.12", + "version": "1.0.0-alpha.13", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", From 54e0178c2a31c53d0dca668916d0467c30e43303 Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 19:23:15 +0100 Subject: [PATCH 04/49] tweaking generator readme description --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c41bd4..d2a0dd0 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,11 @@ export default NextAuth(authOptions); # Generators -This package provides several generator functions that can be used to deterministically generate avatars and usernames as well as a generic QR code generator. The generators are tree-shakeable. If you don't need them, simply don't import them and they'll not be included in your app's bundle. +Normally if you authenticate a user with lnurl-auth, all you'd know about the user is their unique ID (a pubkey). This package goes a step further and provides several generator functions that can be used to deterministically (the pubkey is used as a seed) generate avatars and usernames. That means you can show users a unique name and image that'll be associated with their account! + +As well as the avatar and image generators, there's also a QR code generator. + +The generators are tree-shakeable. If you don't need them, simply don't import them and they'll not be included in your app's bundle. ```typescript import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -141,7 +145,7 @@ import { generateName } from "next-auth-lightning-provider/generators/name"; import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; ``` -> Note: you can also write your own generator functions if those provided don't suit your needs! +> Note: you can write your own generator functions if those provided don't suit your needs! # Configuration From 8fb2db597b00a0bfd4d4158118613fe0056c0be9 Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 19:40:09 +0100 Subject: [PATCH 05/49] removing auto colorScheme for now. it'll be added back in later when it's supported --- README.md | 2 +- TODO.md | 4 ++-- src/main/config/default.ts | 2 +- src/main/config/types.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d2a0dd0..bd81ad2 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ const config: NextAuthLightningConfig = { * * Sets a color scheme for the "Login with Lightning" UI. */ - colorScheme: "auto" | "dark" | "light"; + colorScheme: "dark" | "light"; /** * @param {string} background diff --git a/TODO.md b/TODO.md index d01401d..15ee2f4 100644 --- a/TODO.md +++ b/TODO.md @@ -15,17 +15,17 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Tertiary -- consider future proofing qr/name/avatar user config. move all generators into nested object? - add JSDocs comments to functions / hooks etc - decide on terminology (avatar or image or picture) - add more example repos - add spinner to Loading component - open PR on `next-auth` -- support multiple file types for avatar and qr - consider how to clean up old and unused lnauth session data that was created but never reached success state - add `auto` color scheme that uses browsers dark/light settings - add jest tests for all utils - cancel inflight api requests if hook unmounts +- consider adding various styles of avatar and name generators +- support multiple file types for avatar and qr ### Readme diff --git a/src/main/config/default.ts b/src/main/config/default.ts index cbd6b9a..8779f22 100644 --- a/src/main/config/default.ts +++ b/src/main/config/default.ts @@ -33,7 +33,7 @@ const defaultConfig: Partial = { }, title: "Login with Lightning", theme: { - colorScheme: "auto", + colorScheme: "light", }, }; diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 75c9b37..6b90964 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -99,7 +99,7 @@ export type OptionalConfig = { | null; theme: { - colorScheme?: "auto" | "dark" | "light"; + colorScheme?: "dark" | "light"; } & Partial; }; From cff5d1177c7f5beab984865c0235e43e7c37133a Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 22:18:50 +0100 Subject: [PATCH 06/49] readme tweaks and fixes --- README.md | 54 ++++++++++++++++++++++++---------------------- TODO.md | 5 +++-- examples/README.md | 8 +++++++ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index bd81ad2..3073160 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Install the package and add two code snippets to your app (as shown below). It's Your users will then be shown an additional login option in the `next-auth` login page. When they click the new option they'll be presented with a QR code. The QR code can be scanned with any Bitcoin Lightning wallet that supports `lnurl-auth`. After scanning, they'll be securely logged in! No username or password required. -Behind the scenes `next-auth-lightning-provider` sets up several API endpoint which act as a basic OAuth server. These API will authorize users using [lnurl-auth](https://fiatjaf.com/e0a35204.html) as well as issue JWT tokens and more. +Behind the scenes `next-auth-lightning-provider` sets up several API endpoint which act as a basic OAuth server. The API will authorize users with [lnurl-auth](https://fiatjaf.com/e0a35204.html) and then issue a JWT token to them. -As well as providing the basic authentication functionality that you'd expect, `next-auth-lightning-provider` also offers some extra cool functionality such as generating deterministic avatar images and usernames for authenticated users! +As well as providing the basic authentication functionality that you'd expect, `next-auth-lightning-provider` also offers some extra functionality such as deterministicly generating avatars and usernames for authenticated users! # Compatibility @@ -169,17 +169,17 @@ const config: NextAuthLightningConfig = { * @param {string} siteUrl * * Must be defined as a securely generated random string. Used to sign the - * JWT that authenticates users who have logged in with Lightning. + * JWT token that authenticates users who have logged in with Lightning. */ secret: process.env.NEXTAUTH_SECRET, /** * @param {object} storage * - * `lnurl-auth` requires that a user's Lightning wallet triggers a - * callback to authenticate them. So, we require session storage to + * The lnurl-auth spec requires that a user's Lightning wallet triggers a + * callback as part of the authentication flow. So, we require session storage to * persist some data and ensure it's available when the callback is triggered. - * Data can be stored in a medium of your choice. + * Data can be stored in any medium of your choice. * * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/ */ @@ -224,14 +224,15 @@ const config: NextAuthLightningConfig = { // delete lnurl auth session data based on k1 id }, }, + /** * @param {function} qr.generateQr * - * Set the QR code generator function. + * Define the QR code generator function. * It must return a correctly formatted string containing svg XML markup. * * A default QR code generator is provided. It can be imported from: - * `import { generateQr } from "next-auth-lightning-provider/generators/qr";` + * import { generateQr } from "next-auth-lightning-provider/generators/qr"; * * the default library used is: * @see https://www.npmjs.com/package/qrcode @@ -251,7 +252,7 @@ const config: NextAuthLightningConfig = { * `signIn` path is specified. It lets you define your own page where * you can configure a custom Next.js page and customize the UI. * - * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page/pages/login.tsx + * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page/ */ signIn: "/login" @@ -276,12 +277,14 @@ const config: NextAuthLightningConfig = { /** * @param {function | null} generateAvatar * - * Override the default deterministic avatar generator. + * Define the default deterministic avatar generator. * It must return a correctly formatted string containing svg XML markup. * Or, it can be set to null to disable avatars. * - * The default avatar generation library that's used is dicebear's bottts style. + * A default avatar generator is provided. It can be imported from: + * import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; * + * The default avatar generation library that's used is dicebear's bottts style. * @see https://www.dicebear.com/styles/bottts/ */ async generateAvatar(seed) { @@ -293,11 +296,13 @@ const config: NextAuthLightningConfig = { /** * @param {function | null} generateName * - * Override the default deterministic name generator. - * Or, it can be set to null to disable names. + * Define the default deterministic name generator. + * Or, it can be set to null to disable names * - * The default name generation library used is `unique-names-generator` + * A default name generator is provided. It can be imported from: + * import { generateName } from "next-auth-lightning-provider/generators/name"; * + * The default name generation library used is `unique-names-generator` * @see https://www.npmjs.com/package/unique-names-generator */ async generateName(seed) { @@ -306,17 +311,14 @@ const config: NextAuthLightningConfig = { }; }, - - - /** * Control the color scheme of the "Login with Lightning" page and button. */ theme: { /** - * @param {string} colorScheme - e.g. "#000000" + * @param {string} colorScheme * - * Sets a color scheme for the "Login with Lightning" UI. + * Define a color scheme for the "Login with Lightning" UI. */ colorScheme: "dark" | "light"; @@ -344,37 +346,37 @@ const config: NextAuthLightningConfig = { /** * @param {object} color * - * Override the default QR code background color. If left undefined, - * the QR will be styled based on the theme styles. + * Override the theme's background color. */ qrBackground: "#ffffff", /** * @param {object} color * - * Override the default QR code foreground color. If left undefined, - * the QR will be styled based on the theme styles. + * Override the theme's QR code foreground color. */ qrForeground: "#000000", /** * @param {number} margin * - * Override the default QR code margin. + * Override the theme's QR code margin value. */ qrMargin: 2, /** * @param {string} loginButtonBackground * - * Override the theme's button background color. This is the button that's shown in the `next-auth` login screen. + * Override the theme's button background color. This is the button that's shown in the + * `next-auth` login screen alongside your other providers. */ loginButtonBackground: "#24292f", /** * @param {string} loginButtonText * - * Override the theme's button text color. This is the button that's shown in the `next-auth` login screen. + * Override the theme's button text color. This is the button that's shown in the + * `next-auth` login screen alongside your other providers. */ loginButtonText: "#ffffff", }, diff --git a/TODO.md b/TODO.md index 15ee2f4..8cca6b5 100644 --- a/TODO.md +++ b/TODO.md @@ -12,17 +12,18 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - carefully run through the auth and data flow to look for bugs or oversights - ensure that peer dependencies are met and npm throws errors if not +- add jest tests for all utils +- consider how to clean up old and unused lnauth session data that was created but never reached success state ### Tertiary +- consider / investigate how to SSR react components so the `vanilla.ts` shim can be depricated - add JSDocs comments to functions / hooks etc - decide on terminology (avatar or image or picture) - add more example repos - add spinner to Loading component - open PR on `next-auth` -- consider how to clean up old and unused lnauth session data that was created but never reached success state - add `auto` color scheme that uses browsers dark/light settings -- add jest tests for all utils - cancel inflight api requests if hook unmounts - consider adding various styles of avatar and name generators - support multiple file types for avatar and qr diff --git a/examples/README.md b/examples/README.md index 97ae54d..9c03bbc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,6 +4,14 @@ The [examples/login-page/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page) example demonstrates how to configure a custom Lightning login page UI. +### App Router + +The [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router) example demonstrates how to configure a `next-auth-lightning-provider` using the Next.js App Router. + +### Vanilla JavaScript + +> COMING SOON + ### KV The [examples/kv/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/kv) example demonstrates how to configure `next-auth-lightning-provider` using Vercel [KV](https://vercel.com/docs/storage/vercel-kv) for storage of the lnurl auth data. From 87ce2145d1c9460831a69ef57c9426b4aadff453 Mon Sep 17 00:00:00 2001 From: jowo Date: Sat, 18 Nov 2023 22:22:12 +0100 Subject: [PATCH 07/49] fixing typo --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 9c03bbc..52fabe3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,7 +6,7 @@ The [examples/login-page/](https://github.com/jowo-io/next-auth-lightning-provid ### App Router -The [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router) example demonstrates how to configure a `next-auth-lightning-provider` using the Next.js App Router. +The [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router) example demonstrates how to configure `next-auth-lightning-provider` using the Next.js App Router. ### Vanilla JavaScript From ea5e50590f60fd16fc9091e6ed1b3886d77bbe19 Mon Sep 17 00:00:00 2001 From: jowo Date: Sun, 19 Nov 2023 08:25:35 +0100 Subject: [PATCH 08/49] adding basic auth flow diagram --- README.md | 6 ++++++ diagram.jpeg | Bin 0 -> 71108 bytes 2 files changed, 6 insertions(+) create mode 100644 diagram.jpeg diff --git a/README.md b/README.md index 3073160..3f8c537 100644 --- a/README.md +++ b/README.md @@ -387,3 +387,9 @@ const config: NextAuthLightningConfig = { # Examples See working examples in the [examples folder](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples). + +# Diagram + +Here's a diagram of what happens at sign-in in the Lightning OAuth authorization flow: + +![diagram of Lightning OAuth authorization flow](https://github.com/jowo-io/next-auth-lightning-provider/blob/main/diagram.jpeg?raw=true) diff --git a/diagram.jpeg b/diagram.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f5d0b13bb7ce47445cc7029e9662960bffad9898 GIT binary patch literal 71108 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%0D>Xt$*w7bmY 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 literal 0 HcmV?d00001 From f4906061013927b0322d4668378a866e3e3ad86d Mon Sep 17 00:00:00 2001 From: jowo Date: Sun, 19 Nov 2023 08:27:39 +0100 Subject: [PATCH 09/49] adding todos --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 8cca6b5..93baf3b 100644 --- a/TODO.md +++ b/TODO.md @@ -30,8 +30,9 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Readme -- explain the flow / add a diagram showing user/api/wallet flow. +- add yellow notes to diagram. - add info about deterministic generation of image and name - carefully scan for errors and typos - see if config docs can be moved into TS file and autogenerated - add examples/images showing generator outputs. e.g. bottts images +- ensure consistent formatting is used. full stops, caps, etc From 8fe42182d29607bcf1268fd8754f654db41f9d6b Mon Sep 17 00:00:00 2001 From: jowo Date: Sun, 19 Nov 2023 19:53:24 +0100 Subject: [PATCH 10/49] adding app router support * heavily refactoring APIs so they support the new app router req/res objects * removing react-dom in favour of preact (this mimics how next-auth renders ssr pages) * removing React exports from library and simplifying the `login-page` example * dynamic import of `lnurl` to fix errors it was throwing in App Router APIs * --- README.md | 22 +- TODO.md | 7 +- examples/app-router/.env.example | 3 + examples/app-router/.gitignore | 41 + examples/app-router/README.md | 25 + .../app/api/auth/[...nextauth]/config.ts | 7 + .../app/api/auth/[...nextauth]/route.ts | 6 + .../app/api/lnauth/[...lnauth]/config.ts | 51 + .../app/api/lnauth/[...lnauth]/route.ts | 3 + examples/app-router/app/components/Login.tsx | 16 + examples/app-router/app/layout.tsx | 27 + examples/app-router/app/page.tsx | 33 + examples/app-router/env.mjs | 68 + examples/app-router/next.config.js | 4 + examples/app-router/package-lock.json | 4057 +++++++++++++++++ examples/app-router/package.json | 28 + examples/app-router/tsconfig.json | 27 + examples/drizzle/next.config.js | 5 +- examples/drizzle/package-lock.json | 26 +- examples/drizzle/package.json | 2 - examples/kv/next.config.js | 5 +- examples/kv/package-lock.json | 26 +- examples/kv/package.json | 2 - .../login-page/components/CompleteWrapper.tsx | 61 - .../components/CustomComponents.tsx | 83 - .../login-page/components/ExposedHook.tsx | 70 - .../{PlainJSX.tsx => LightningLogin.tsx} | 8 +- examples/login-page/next.config.js | 5 +- examples/login-page/package-lock.json | 26 +- examples/login-page/package.json | 2 - examples/login-page/pages/login.tsx | 51 +- package-lock.json | 41 +- package.json | 11 +- src/main/config/types.ts | 15 +- src/main/handlers/callback.ts | 82 +- src/main/handlers/create.ts | 80 +- src/main/handlers/image.ts | 53 +- src/main/handlers/login.tsx | 77 +- src/main/handlers/poll.ts | 52 +- src/main/handlers/qr.ts | 51 +- src/main/handlers/token.ts | 124 +- src/main/index.ts | 22 +- src/main/utils/params.ts | 8 + src/main/utils/router.ts | 36 + src/react/components/Button.tsx | 4 +- src/react/components/CopyCode.tsx | 2 +- src/react/components/LnAuthLogin.tsx | 2 +- src/react/components/LnAuthLoginWrapper.tsx | 38 - src/react/components/Loading.tsx | 2 +- src/react/components/QrCode.tsx | 4 +- src/react/components/Title.tsx | 2 +- src/react/hooks/useLnUrl.ts | 2 + src/react/index.ts | 11 - src/react/utils/query.ts | 10 + tsconfig.json | 2 +- 55 files changed, 4964 insertions(+), 564 deletions(-) create mode 100644 examples/app-router/.env.example create mode 100644 examples/app-router/.gitignore create mode 100644 examples/app-router/README.md create mode 100644 examples/app-router/app/api/auth/[...nextauth]/config.ts create mode 100644 examples/app-router/app/api/auth/[...nextauth]/route.ts create mode 100644 examples/app-router/app/api/lnauth/[...lnauth]/config.ts create mode 100644 examples/app-router/app/api/lnauth/[...lnauth]/route.ts create mode 100644 examples/app-router/app/components/Login.tsx create mode 100644 examples/app-router/app/layout.tsx create mode 100644 examples/app-router/app/page.tsx create mode 100644 examples/app-router/env.mjs create mode 100644 examples/app-router/next.config.js create mode 100644 examples/app-router/package-lock.json create mode 100644 examples/app-router/package.json create mode 100644 examples/app-router/tsconfig.json delete mode 100644 examples/login-page/components/CompleteWrapper.tsx delete mode 100644 examples/login-page/components/CustomComponents.tsx delete mode 100644 examples/login-page/components/ExposedHook.tsx rename examples/login-page/components/{PlainJSX.tsx => LightningLogin.tsx} (90%) create mode 100644 src/main/utils/params.ts create mode 100644 src/main/utils/router.ts delete mode 100644 src/react/components/LnAuthLoginWrapper.tsx diff --git a/README.md b/README.md index 3f8c537..7196bc4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > > This project is currently under construction 👷🏗️🚧 > -> It is not recommended to use it in production apps. It may be insecure! +> It is not recommended to use it in production apps. It may be buggy or insecure! > > See [this issue](https://github.com/nextauthjs/next-auth/issues/7872) for updates and more info. @@ -24,8 +24,8 @@ As well as providing the basic authentication functionality that you'd expect, ` # Compatibility -- `next-auth-lightning-provider` only supports the `next-auth` version 4, `next-auth` version 5 support **coming soon**. -- `next-auth-lightning-provider` only supports the next pages directory at the moment, app router support **coming soon**. +- `next-auth-lightning-provider` currently only supports `next@v13` and higher. +- `next-auth-lightning-provider` currently only supports `next-auth@v4`. # Getting started @@ -111,6 +111,8 @@ export const lightningProvider = provider; export default handler; ``` +> NOTE: The above example uses the Pages Router. If your app uses the App Router then take a look at the [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router/) example app. + This API will handle all of the Lightning auth API requests, such as generating QRs, handling callbacks, polling and issuing JWT auth tokens. ### Provider @@ -384,12 +386,22 @@ const config: NextAuthLightningConfig = { }; ``` +# Next.js Routers + +With the release of `next@v13` comes the App Router. + +This package supports both the [Pages Router](https://nextjs.org/docs/pages) and the [App Router](https://nextjs.org/docs/app). + +If your app uses the App Router, see the [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router/) app. + +If your app uses the Pages Router, see any of the other [examples/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/) apps. + # Examples -See working examples in the [examples folder](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples). +See working examples in the [examples/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples) folder. # Diagram -Here's a diagram of what happens at sign-in in the Lightning OAuth authorization flow: +Here's a diagram illustrating what's happening under the hood during the Lightning OAuth authorization flow: ![diagram of Lightning OAuth authorization flow](https://github.com/jowo-io/next-auth-lightning-provider/blob/main/diagram.jpeg?raw=true) diff --git a/TODO.md b/TODO.md index 93baf3b..1d76901 100644 --- a/TODO.md +++ b/TODO.md @@ -5,11 +5,11 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Primary - support `next-auth@4` and `next-auth@5` -- support Next.js app directory and pages directory (if possible) - investigate CSRF for next-auth ### Secondary +- add a plain js app to the examples folder (without typescript) - carefully run through the auth and data flow to look for bugs or oversights - ensure that peer dependencies are met and npm throws errors if not - add jest tests for all utils @@ -27,6 +27,11 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - cancel inflight api requests if hook unmounts - consider adding various styles of avatar and name generators - support multiple file types for avatar and qr +- rename `useLnUrl` to `useLightningAuthUrl` +- see if TS generics can be used for NextRequest/NextApiRequest etc +- error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc +- make generators return strings instead of objects `{ qr: "..." }` +- consider standardising APIs so they're all either POST or GET ### Readme diff --git a/examples/app-router/.env.example b/examples/app-router/.env.example new file mode 100644 index 0000000..1ce182b --- /dev/null +++ b/examples/app-router/.env.example @@ -0,0 +1,3 @@ +# next-auth +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET="********" diff --git a/examples/app-router/.gitignore b/examples/app-router/.gitignore new file mode 100644 index 0000000..c0b6f3a --- /dev/null +++ b/examples/app-router/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# env +.env + +# session +.node-persist \ No newline at end of file diff --git a/examples/app-router/README.md b/examples/app-router/README.md new file mode 100644 index 0000000..cd8f852 --- /dev/null +++ b/examples/app-router/README.md @@ -0,0 +1,25 @@ +## About + +This example demonstrates configuring `next-auth-lightning-provider` using the Next.js App Router. + +> ⚠️ WARNING using `node-persist` is not recommended in lambda or edge environments. +> +> The reason not to use `node-persist` is that it stores session data locally in files, and in most lambda / cloud environments those files will not persist across sessions. +> +> Instead you should use persistent storage such as a database, a document store, or session storage. +> +> See the other examples in the examples folder for more info. + +## Getting Started + +#### Building `next-auth-lightning-provider` + +Before you can run this example, you must build `next-auth-lightning-provider`. + +Essentially all that's required is running `npm i` and `npm run build` from the directory root. + +#### Running this examples + +Run `npm i` to install dependencies + +Run `npm run dev` to launch the dev server and visit `localhost:3000` to view the app. diff --git a/examples/app-router/app/api/auth/[...nextauth]/config.ts b/examples/app-router/app/api/auth/[...nextauth]/config.ts new file mode 100644 index 0000000..659d835 --- /dev/null +++ b/examples/app-router/app/api/auth/[...nextauth]/config.ts @@ -0,0 +1,7 @@ +import { AuthOptions } from "next-auth"; + +import { lightningProvider } from "../../lnauth/[...lnauth]/config"; + +export const authOptions: AuthOptions = { + providers: [lightningProvider], +}; diff --git a/examples/app-router/app/api/auth/[...nextauth]/route.ts b/examples/app-router/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..584400d --- /dev/null +++ b/examples/app-router/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; +import { authOptions } from "./config"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts new file mode 100644 index 0000000..b56e803 --- /dev/null +++ b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts @@ -0,0 +1,51 @@ +import NextAuthLightning, { + LnAuthData, + NextAuthLightningConfig, +} from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; + +import { env } from "@/env.mjs"; + +import storage from "node-persist"; // ⚠️ WARNING using node-persist is not recommended in lambda or edge environments. + +await storage.init(); + +const config: NextAuthLightningConfig = { + // required + siteUrl: env.NEXTAUTH_URL, + secret: env.NEXTAUTH_SECRET, + storage: { + async set({ k1, data }) { + await storage.setItem(`k1:${k1}`, data); + }, + async get({ k1 }) { + const results = await storage.getItem(`k1:${k1}`); + + if (!results) throw new Error("Couldn't find item by k1"); + + return results as LnAuthData; + }, + async update({ k1, data }) { + await storage.setItem(`k1:${k1}`, data); + }, + async delete({ k1 }) { + await storage.removeItem(`k1:${k1}`); + }, + }, + generateQr, + + // optional + generateName, + generateAvatar, + theme: { + colorScheme: "dark", + }, +}; + +const { provider, handler } = NextAuthLightning(config); + +export const lightningProvider = provider; + +export { handler }; diff --git a/examples/app-router/app/api/lnauth/[...lnauth]/route.ts b/examples/app-router/app/api/lnauth/[...lnauth]/route.ts new file mode 100644 index 0000000..beae393 --- /dev/null +++ b/examples/app-router/app/api/lnauth/[...lnauth]/route.ts @@ -0,0 +1,3 @@ +import { handler } from "./config"; + +export { handler as GET, handler as POST }; diff --git a/examples/app-router/app/components/Login.tsx b/examples/app-router/app/components/Login.tsx new file mode 100644 index 0000000..f958ac5 --- /dev/null +++ b/examples/app-router/app/components/Login.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { Session } from "next-auth"; +import { signOut, signIn } from "next-auth/react"; + +export const Login = async (session: { session: Session | null }) => { + return ( +
+ {session ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/examples/app-router/app/layout.tsx b/examples/app-router/app/layout.tsx new file mode 100644 index 0000000..24617b0 --- /dev/null +++ b/examples/app-router/app/layout.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from "react"; +import { Metadata } from "next"; + +/** + * Default metadata. + * + * @see https://nextjs.org/docs/app/api-reference/file-conventions/metadata + */ +export const metadata: Metadata = { + title: "Lightning Login", +}; + +/** + * The homepage root layout. + * + * @see https://nextjs.org/docs/app/api-reference/file-conventions/layout + */ +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + +
{children}
+ + + ); +} diff --git a/examples/app-router/app/page.tsx b/examples/app-router/app/page.tsx new file mode 100644 index 0000000..aed9109 --- /dev/null +++ b/examples/app-router/app/page.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { getServerSession } from "next-auth"; + +import { authOptions } from "@/app/api/auth/[...nextauth]/config"; +import { Login } from "./components/Login"; + +const Home = async () => { + const session = await getServerSession(authOptions); + + return ( +
+

Auth session:

+ + {session?.user?.image && ( + <> + {`Avatar +
+ + )} + +
{JSON.stringify(session, null, 2)}
+ + +
+ ); +}; + +export default Home; diff --git a/examples/app-router/env.mjs b/examples/app-router/env.mjs new file mode 100644 index 0000000..99be2c0 --- /dev/null +++ b/examples/app-router/env.mjs @@ -0,0 +1,68 @@ +import { z } from "zod"; + +/** @type {Record | keyof z.infer, string | undefined>} */ +const processEnv = { + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + + NODE_ENV: process.env.NODE_ENV, +}; + +const client = z.object({ + NEXTAUTH_URL: z.string().min(1), + + NODE_ENV: z.enum(["development", "test", "production"]), +}); + +const server = z.object({ + NEXTAUTH_URL: z.string().min(1), + NEXTAUTH_SECRET: z.string(), + + NODE_ENV: z.enum(["development", "test", "production"]), +}); + +// Don't touch the part below +// -------------------------- + +const merged = server.merge(client); + +/** @typedef {z.input} MergedInput */ +/** @typedef {z.infer} MergedOutput */ +/** @typedef {z.SafeParseReturnType} MergedSafeParseReturn */ + +let env = /** @type {MergedOutput} */ (process.env); + +if (!!process.env.SKIP_ENV_VALIDATION == false) { + const isServer = typeof window === "undefined"; + + const parsed = /** @type {MergedSafeParseReturn} */ ( + isServer + ? merged.safeParse(processEnv) // on server we can validate all env vars + : client.safeParse(processEnv) // on client we can only validate the ones that are exposed + ); + + if (parsed.success === false) { + console.error( + "❌ Invalid environment variables:", + parsed.error.flatten().fieldErrors + ); + throw new Error("Invalid environment variables"); + } + + env = new Proxy(parsed.data, { + get(target, prop) { + if (typeof prop !== "string") return undefined; + // Throw a descriptive error if a server-side env var is accessed on the client + // Otherwise it would just be returning `undefined` and be annoying to debug + if (!isServer && !prop.startsWith("NEXT_PUBLIC_") && prop !== "NODE_ENV") + throw new Error( + process.env.NODE_ENV === "production" + ? "❌ Attempted to access a server-side environment variable on the client" + : `❌ Attempted to access server-side environment variable '${prop}' on the client` + ); + return target[/** @type {keyof typeof target} */ (prop)]; + }, + }); +} + +export { env }; diff --git a/examples/app-router/next.config.js b/examples/app-router/next.config.js new file mode 100644 index 0000000..658404a --- /dev/null +++ b/examples/app-router/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/examples/app-router/package-lock.json b/examples/app-router/package-lock.json new file mode 100644 index 0000000..17d6cb2 --- /dev/null +++ b/examples/app-router/package-lock.json @@ -0,0 +1,4057 @@ +{ + "name": "kv", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kv", + "version": "1.0.0", + "dependencies": { + "next": "^14.0.1", + "next-auth": "^4.24.4", + "next-auth-lightning-provider": "file:../../", + "node-persist": "^3.1.3", + "react": "18.2.0", + "react-dom": "^18.2.0", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/node": "20.5.9", + "@types/node-persist": "^3.1.6", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "dotenv": "^16.3.1", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19", + "typescript": "5.2.2" + } + }, + "../..": { + "version": "1.0.0-alpha.16", + "license": "ISC", + "dependencies": { + "@dicebear/collection": "^7.0.1", + "@dicebear/core": "^7.0.1", + "jose": "^5.1.0", + "lnurl": "^0.25.1", + "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", + "qrcode": "^1.5.3", + "unique-names-generator": "^4.7.1", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/jest": "^29.5.8", + "@types/lodash.merge": "^4.6.9", + "@types/node": "20.5.9", + "@types/qrcode": "^1.5.4", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "eslint": "8.48.0", + "jest": "^29.7.0", + "next": "^14", + "next-auth": "^4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "ts-jest": "^29.1.1", + "typescript": "^5.2.2" + }, + "peerDependencies": { + "next": "^14", + "next-auth": "^4", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@next/env": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.2.tgz", + "integrity": "sha512-HAW1sljizEaduEOes/m84oUqeIDAUYBR1CDwu2tobNlNDFP3cSm9d6QsOsGeNlIppU1p/p1+bWbYCbvwjFiceA==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", + "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", + "dev": true, + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.2.tgz", + "integrity": "sha512-i+jQY0fOb8L5gvGvojWyZMfQoQtDVB2kYe7fufOEiST6sicvzI2W5/EXo4lX5bLUjapHKe+nFxuVv7BA+Pd7LQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.2.tgz", + "integrity": "sha512-zRCAO0d2hW6gBEa4wJaLn+gY8qtIqD3gYd9NjruuN98OCI6YyelmhWVVLlREjS7RYrm9OUQIp/iVJFeB6kP1hg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.2.tgz", + "integrity": "sha512-tSJmiaon8YaKsVhi7GgRizZoV0N1Sx5+i+hFTrCKKQN7s3tuqW0Rov+RYdPhAv/pJl4qiG+XfSX4eJXqpNg3dA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.2.tgz", + "integrity": "sha512-dXJLMSEOwqJKcag1BeX1C+ekdPPJ9yXbWIt3nAadhbLx5CjACoB2NQj9Xcqu2tmdr5L6m34fR+fjGPs+ZVPLzA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.2.tgz", + "integrity": "sha512-WC9KAPSowj6as76P3vf1J3mf2QTm3Wv3FBzQi7UJ+dxWjK3MhHVWsWUo24AnmHx9qDcEtHM58okgZkXVqeLB+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.2.tgz", + "integrity": "sha512-KSSAwvUcjtdZY4zJFa2f5VNJIwuEVnOSlqYqbQIawREJA+gUI6egeiRu290pXioQXnQHYYdXmnVNZ4M+VMB7KQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.2.tgz", + "integrity": "sha512-2/O0F1SqJ0bD3zqNuYge0ok7OEWCQwk55RPheDYD0va5ij7kYwrFkq5ycCRN0TLjLfxSF6xI5NM6nC5ux7svEQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.2.tgz", + "integrity": "sha512-vJI/x70Id0oN4Bq/R6byBqV1/NS5Dl31zC+lowO8SDu1fHmUxoAdILZR5X/sKbiJpuvKcCrwbYgJU8FF/Gh50Q==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.2.tgz", + "integrity": "sha512-Ut4LXIUvC5m8pHTe2j0vq/YDnTEyq6RSR9vHYPqnELrDapPhLNz9Od/L5Ow3J8RNDWpEnfCiQXuVdfjlNEJ7ug==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", + "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.5.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "dev": true + }, + "node_modules/@types/node-persist": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/node-persist/-/node-persist-3.1.6.tgz", + "integrity": "sha512-/8bxAHLTG80mzaktMmlnOCAIrUj4/5NR6LecvxX/jRxBHgGUGGdTqCZ1rDF/uEyHX/aGz4NKc5yvqgnwLhOtXw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", + "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", + "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", + "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "13.4.19", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/next": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.2.tgz", + "integrity": "sha512-jsAU2CkYS40GaQYOiLl9m93RTv2DA/tTJ0NRlmZIBIL87YwQ/xR8k796z7IqgM3jydI8G25dXvyYMC9VDIevIg==", + "dependencies": { + "@next/env": "14.0.2", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.0.2", + "@next/swc-darwin-x64": "14.0.2", + "@next/swc-linux-arm64-gnu": "14.0.2", + "@next/swc-linux-arm64-musl": "14.0.2", + "@next/swc-linux-x64-gnu": "14.0.2", + "@next/swc-linux-x64-musl": "14.0.2", + "@next/swc-win32-arm64-msvc": "14.0.2", + "@next/swc-win32-ia32-msvc": "14.0.2", + "@next/swc-win32-x64-msvc": "14.0.2" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-auth": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", + "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth-lightning-provider": { + "resolved": "../..", + "link": true + }, + "node_modules/next-auth/node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-persist": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.3.tgz", + "integrity": "sha512-CaFv+kSZtsc+VeDRldK1yR47k1vPLBpzYB9re2z7LIwITxwBtljMq3s8VQnnr+x3E8pQfHbc5r2IyJsBLJhtXg==", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openid-client": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/app-router/package.json b/examples/app-router/package.json new file mode 100644 index 0000000..21131a4 --- /dev/null +++ b/examples/app-router/package.json @@ -0,0 +1,28 @@ +{ + "name": "kv", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "^14.0.1", + "next-auth": "^4.24.4", + "next-auth-lightning-provider": "file:../../", + "node-persist": "^3.1.3", + "react": "18.2.0", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/node": "20.5.9", + "@types/node-persist": "^3.1.6", + "@types/react": "18.2.21", + "dotenv": "^16.3.1", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19", + "typescript": "5.2.2" + } +} diff --git a/examples/app-router/tsconfig.json b/examples/app-router/tsconfig.json new file mode 100644 index 0000000..53e74ab --- /dev/null +++ b/examples/app-router/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/drizzle/next.config.js b/examples/drizzle/next.config.js index 4ce6c9f..b505a32 100644 --- a/examples/drizzle/next.config.js +++ b/examples/drizzle/next.config.js @@ -2,16 +2,13 @@ var path = require("path"); /** @type {import('next').NextConfig} */ const nextConfig = { - webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => { + webpack: (config) => { // These alias configurations are not required. They are only needed for development in the mono repo's example/ folder config.resolve.alias["next"] = path.resolve("./node_modules/next"); config.resolve.alias["next-auth"] = path.resolve( "./node_modules/next-auth" ); config.resolve.alias["react"] = path.resolve("./node_modules/react"); - config.resolve.alias["react-dom"] = path.resolve( - "./node_modules/react-dom" - ); return config; }, }; diff --git a/examples/drizzle/package-lock.json b/examples/drizzle/package-lock.json index b676ec6..7fafd83 100644 --- a/examples/drizzle/package-lock.json +++ b/examples/drizzle/package-lock.json @@ -14,13 +14,11 @@ "next-auth": "^4.24.4", "next-auth-lightning-provider": "file:../../", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { "@types/node": "20.5.9", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "drizzle-kit": "^0.19.13", "eslint": "8.48.0", @@ -29,8 +27,7 @@ } }, "../..": { - "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.7", + "version": "1.0.0-alpha.16", "license": "ISC", "dependencies": { "@dicebear/collection": "^7.0.1", @@ -38,28 +35,30 @@ "jose": "^5.1.0", "lnurl": "^0.25.1", "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", "qrcode": "^1.5.3", "unique-names-generator": "^4.7.1", "zod": "^3.22.2" }, "devDependencies": { + "@types/jest": "^29.5.8", "@types/lodash.merge": "^4.6.9", "@types/node": "20.5.9", "@types/qrcode": "^1.5.4", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "eslint": "8.48.0", + "jest": "^29.7.0", "next": "^14", "next-auth": "^4", "react": "^18.2.0", - "react-dom": "^18.2.0", + "ts-jest": "^29.1.1", "typescript": "^5.2.2" }, "peerDependencies": { "next": "^14", "next-auth": "^4", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -870,15 +869,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/scheduler": { "version": "0.16.6", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", @@ -4138,6 +4128,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -4360,6 +4351,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/examples/drizzle/package.json b/examples/drizzle/package.json index 1c552d4..d7f2872 100644 --- a/examples/drizzle/package.json +++ b/examples/drizzle/package.json @@ -18,7 +18,6 @@ "next-auth": "^4.24.4", "next-auth-lightning-provider": "file:../../", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { @@ -27,7 +26,6 @@ "eslint-config-next": "13.4.19", "@types/node": "20.5.9", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "drizzle-kit": "^0.19.13" } diff --git a/examples/kv/next.config.js b/examples/kv/next.config.js index 4ce6c9f..b505a32 100644 --- a/examples/kv/next.config.js +++ b/examples/kv/next.config.js @@ -2,16 +2,13 @@ var path = require("path"); /** @type {import('next').NextConfig} */ const nextConfig = { - webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => { + webpack: (config) => { // These alias configurations are not required. They are only needed for development in the mono repo's example/ folder config.resolve.alias["next"] = path.resolve("./node_modules/next"); config.resolve.alias["next-auth"] = path.resolve( "./node_modules/next-auth" ); config.resolve.alias["react"] = path.resolve("./node_modules/react"); - config.resolve.alias["react-dom"] = path.resolve( - "./node_modules/react-dom" - ); return config; }, }; diff --git a/examples/kv/package-lock.json b/examples/kv/package-lock.json index 486b23d..8b5bf27 100644 --- a/examples/kv/package-lock.json +++ b/examples/kv/package-lock.json @@ -13,13 +13,11 @@ "next-auth": "^4.24.4", "next-auth-lightning-provider": "file:../../", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { "@types/node": "20.5.9", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "eslint": "8.48.0", "eslint-config-next": "13.4.19", @@ -27,8 +25,7 @@ } }, "../..": { - "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.7", + "version": "1.0.0-alpha.16", "license": "ISC", "dependencies": { "@dicebear/collection": "^7.0.1", @@ -36,28 +33,30 @@ "jose": "^5.1.0", "lnurl": "^0.25.1", "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", "qrcode": "^1.5.3", "unique-names-generator": "^4.7.1", "zod": "^3.22.2" }, "devDependencies": { + "@types/jest": "^29.5.8", "@types/lodash.merge": "^4.6.9", "@types/node": "20.5.9", "@types/qrcode": "^1.5.4", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "eslint": "8.48.0", + "jest": "^29.7.0", "next": "^14", "next-auth": "^4", "react": "^18.2.0", - "react-dom": "^18.2.0", + "ts-jest": "^29.1.1", "typescript": "^5.2.2" }, "peerDependencies": { "next": "^14", "next-auth": "^4", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -490,15 +489,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/scheduler": { "version": "0.16.6", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", @@ -3250,6 +3240,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -3467,6 +3458,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/examples/kv/package.json b/examples/kv/package.json index 1284da2..dbf433b 100644 --- a/examples/kv/package.json +++ b/examples/kv/package.json @@ -14,13 +14,11 @@ "next-auth": "^4.24.4", "next-auth-lightning-provider": "file:../../", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { "@types/node": "20.5.9", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "eslint": "8.48.0", "eslint-config-next": "13.4.19", diff --git a/examples/login-page/components/CompleteWrapper.tsx b/examples/login-page/components/CompleteWrapper.tsx deleted file mode 100644 index bac39ea..0000000 --- a/examples/login-page/components/CompleteWrapper.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { LnAuthLoginWrapper } from "next-auth-lightning-provider/react"; - -export default function CompleteWrapper({ - redirectUri, - state, -}: { - redirectUri: string; - state: string; -}) { - return ( - - ); -} diff --git a/examples/login-page/components/CustomComponents.tsx b/examples/login-page/components/CustomComponents.tsx deleted file mode 100644 index 8554d92..0000000 --- a/examples/login-page/components/CustomComponents.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { - Button, - CopyCode, - Loading, - QrCode, - Title, - useLnUrl, -} from "next-auth-lightning-provider/react"; - -export default function CustomComponents({ - redirectUri, - state, -}: { - redirectUri: string; - state: string; -}) { - const { lnurl } = useLnUrl({ redirectUri, state }); - - if (!lnurl) { - return ; - } - - return ( -
- - Custom Components - - - -
- ); -} diff --git a/examples/login-page/components/ExposedHook.tsx b/examples/login-page/components/ExposedHook.tsx deleted file mode 100644 index ae2ac7c..0000000 --- a/examples/login-page/components/ExposedHook.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { - LnAuthLogin, - Loading, - useLnUrl, -} from "next-auth-lightning-provider/react"; - -export default function ExposedHook({ - redirectUri, - state, -}: { - redirectUri: string; - state: string; -}) { - const { lnurl } = useLnUrl({ redirectUri, state }); - - if (!lnurl) { - return ; - } - - return ( - - ); -} diff --git a/examples/login-page/components/PlainJSX.tsx b/examples/login-page/components/LightningLogin.tsx similarity index 90% rename from examples/login-page/components/PlainJSX.tsx rename to examples/login-page/components/LightningLogin.tsx index 1f35956..c8465fb 100644 --- a/examples/login-page/components/PlainJSX.tsx +++ b/examples/login-page/components/LightningLogin.tsx @@ -1,6 +1,6 @@ import { useLnUrl } from "next-auth-lightning-provider/react"; -export default function PlainJSX({ +export default function LightningLogin({ redirectUri, state, }: { @@ -10,12 +10,16 @@ export default function PlainJSX({ const { lnurl, qr, button } = useLnUrl({ redirectUri, state }); if (!lnurl) { - return
Loading ...
; + return ( +
loading...
+ ); } return (
{ + webpack: (config) => { // These alias configurations are not required. They are only needed for development in the mono repo's example/ folder config.resolve.alias["next"] = path.resolve("./node_modules/next"); config.resolve.alias["next-auth"] = path.resolve( "./node_modules/next-auth" ); config.resolve.alias["react"] = path.resolve("./node_modules/react"); - config.resolve.alias["react-dom"] = path.resolve( - "./node_modules/react-dom" - ); return config; }, }; diff --git a/examples/login-page/package-lock.json b/examples/login-page/package-lock.json index f123f01..fe3078c 100644 --- a/examples/login-page/package-lock.json +++ b/examples/login-page/package-lock.json @@ -13,14 +13,12 @@ "next-auth-lightning-provider": "file:../../", "node-persist": "^3.1.3", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { "@types/node": "20.5.9", "@types/node-persist": "^3.1.6", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "eslint": "8.48.0", "eslint-config-next": "13.4.19", @@ -28,8 +26,7 @@ } }, "../..": { - "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.10", + "version": "1.0.0-alpha.16", "license": "ISC", "dependencies": { "@dicebear/collection": "^7.0.1", @@ -37,28 +34,30 @@ "jose": "^5.1.0", "lnurl": "^0.25.1", "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", "qrcode": "^1.5.3", "unique-names-generator": "^4.7.1", "zod": "^3.22.2" }, "devDependencies": { + "@types/jest": "^29.5.8", "@types/lodash.merge": "^4.6.9", "@types/node": "20.5.9", "@types/qrcode": "^1.5.4", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "eslint": "8.48.0", + "jest": "^29.7.0", "next": "^14", "next-auth": "^4", "react": "^18.2.0", - "react-dom": "^18.2.0", + "ts-jest": "^29.1.1", "typescript": "^5.2.2" }, "peerDependencies": { "next": "^14", "next-auth": "^4", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -500,15 +499,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/scheduler": { "version": "0.16.6", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", @@ -3244,6 +3234,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -3461,6 +3452,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/examples/login-page/package.json b/examples/login-page/package.json index 898996d..21131a4 100644 --- a/examples/login-page/package.json +++ b/examples/login-page/package.json @@ -14,14 +14,12 @@ "next-auth-lightning-provider": "file:../../", "node-persist": "^3.1.3", "react": "18.2.0", - "react-dom": "^18.2.0", "zod": "^3.22.2" }, "devDependencies": { "@types/node": "20.5.9", "@types/node-persist": "^3.1.6", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "dotenv": "^16.3.1", "eslint": "8.48.0", "eslint-config-next": "13.4.19", diff --git a/examples/login-page/pages/login.tsx b/examples/login-page/pages/login.tsx index fce9caa..ac35b1d 100644 --- a/examples/login-page/pages/login.tsx +++ b/examples/login-page/pages/login.tsx @@ -1,11 +1,8 @@ import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; - import { extractQuery } from "next-auth-lightning-provider/react"; -import CompleteWrapper from "@/components/CompleteWrapper"; -import ExposedHook from "@/components/ExposedHook"; -import CustomComponents from "@/components/CustomComponents"; -import PlainJSX from "@/components/PlainJSX"; + +import LightningLogin from "@/components/LightningLogin"; export default function LoginPage() { const { isReady, query } = useRouter(); @@ -13,8 +10,10 @@ export default function LoginPage() { const { redirectUri, state } = extractQuery(query); - if (!isReady) { - return
loading...
; + if (!isReady || session.status === "loading") { + return ( +
loading...
+ ); } if (session.data) { @@ -35,43 +34,7 @@ export default function LoginPage() { return (
-

- There are 4 main ways to render a custom Lightning login page. -

-
-
- -

- The complete wrapper is the easiest, but least customizable. It does - everything for you. You can simple import it in your page layout and - let it do the rest. -

-
-
- -

- The exposed hook approach is easy to implement and allows you to - access the lnurl data from the `useLnUrl` hook, as well as giving - you the ability to render a custom loading state. -

-
-
- -

- The custom components approach gives you access to all of individual - elements of the loading UI via the helper components. This lets you - compose your page in a highly flexible way. -

-
-
- -

- The plain JSX approach is the most customizable. You can compose the - page however you wish using your own components in whichever way you - want. -

-
-
+
); } diff --git a/package-lock.json b/package-lock.json index ec8e08f..bb66815 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.11", + "version": "1.0.0-alpha.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.11", + "version": "1.0.0-alpha.16", "license": "ISC", "dependencies": { "@dicebear/collection": "^7.0.1", @@ -14,6 +14,8 @@ "jose": "^5.1.0", "lnurl": "^0.25.1", "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", "qrcode": "^1.5.3", "unique-names-generator": "^4.7.1", "zod": "^3.22.2" @@ -30,15 +32,13 @@ "next": "^14", "next-auth": "^4", "react": "^18.2.0", - "react-dom": "^18.2.0", "ts-jest": "^29.1.1", "typescript": "^5.2.2" }, "peerDependencies": { "next": "^14", "next-auth": "^4", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -5489,6 +5489,18 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/next-auth/node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "dev": true, + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -5884,20 +5896,18 @@ } }, "node_modules/preact": { - "version": "10.19.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.1.tgz", - "integrity": "sha512-ZSsUr6EFlwWH0btdXMj6+X+hJAZ1v+aUzKlfwBGokKB1ZO6Shz+D16LxQhM8f+E/UgkKbVe2tsWXtGTUMCkGpQ==", - "dev": true, + "version": "10.19.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz", + "integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, "node_modules/preact-render-to-string": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", - "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", - "dev": true, + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.3.1.tgz", + "integrity": "sha512-NQ28WrjLtWY6lKDlTxnFpKHZdpjfF+oE6V4tZ0rTrunHrtZp6Dm0oFrcJalt/5PNeqJz4j1DuZDS0Y6rCBoqDA==", "dependencies": { "pretty-format": "^3.8.0" }, @@ -5917,8 +5927,7 @@ "node_modules/pretty-format": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", - "dev": true + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, "node_modules/prompts": { "version": "2.4.2", @@ -6060,6 +6069,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -6266,6 +6276,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index 6090b9f..66fd943 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.13", + "version": "1.0.0-alpha.16", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", @@ -30,6 +30,8 @@ "lnurl-auth" ], "files": [ + "generators/**/*.d.ts*", + "generators/**/*.js", "react/**/*.d.ts*", "react/**/*.js", "main/**/*.d.ts*", @@ -48,8 +50,7 @@ "peerDependencies": { "next": "^14", "next-auth": "^4", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0" }, "dependencies": { "@dicebear/collection": "^7.0.1", @@ -57,6 +58,8 @@ "jose": "^5.1.0", "lnurl": "^0.25.1", "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", "qrcode": "^1.5.3", "unique-names-generator": "^4.7.1", "zod": "^3.22.2" @@ -67,13 +70,11 @@ "@types/node": "20.5.9", "@types/qrcode": "^1.5.4", "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", "eslint": "8.48.0", "jest": "^29.7.0", "next": "^14", "next-auth": "^4", "react": "^18.2.0", - "react-dom": "^18.2.0", "ts-jest": "^29.1.1", "typescript": "^5.2.2" } diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 6b90964..5ea2e8a 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -1,4 +1,5 @@ import { NextApiRequest } from "next"; +import { NextRequest } from "next/server"; export type HardConfig = { apis: { @@ -55,9 +56,12 @@ export type RequiredConfig = { state: string; }; }, - req: NextApiRequest + req: NextApiRequest | NextRequest ) => Promise; - get: (args: { k1: string }, req: NextApiRequest) => Promise; + get: ( + args: { k1: string }, + req: NextApiRequest | NextRequest + ) => Promise; update: ( args: { k1: string; @@ -67,9 +71,12 @@ export type RequiredConfig = { success: boolean; }; }, - req: NextApiRequest + req: NextApiRequest | NextRequest + ) => Promise; + delete: ( + args: { k1: string }, + req: NextApiRequest | NextRequest ) => Promise; - delete: (args: { k1: string }, req: NextApiRequest) => Promise; }; generateQr: (data: string, config: Config) => Promise<{ qr: string }>; }; diff --git a/src/main/handlers/callback.ts b/src/main/handlers/callback.ts index e63fbab..000172f 100644 --- a/src/main/handlers/callback.ts +++ b/src/main/handlers/callback.ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next/types"; -import lnurl from "lnurl"; +import { NextRequest, NextResponse } from "next/server"; import { callbackValidation, @@ -8,37 +8,69 @@ import { } from "../validation/lnauth.js"; import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; +import { paramsToObject } from "../utils/params.js"; -export default async function handler( +async function logic( + query: Record, + req: NextApiRequest | NextRequest, + config: Config +) { + const { + k1, + key: pubkey, + sig, + } = callbackValidation.parse(query, { errorMap }); + + const lnurl = require("lnurl"); + const authorize = await lnurl.verifyAuthorizationSignature(sig, k1, pubkey); + if (!authorize) { + throw new Error("Error in keys"); + } + + await config.storage.update( + { k1, data: { pubkey, sig, success: true } }, + req + ); + + return { + status: "OK", + success: true, + k1, + }; +} + +async function pagesHandler( req: NextApiRequest, res: NextApiResponse, config: Config ) { try { - const { - k1, - key: pubkey, - sig, - } = callbackValidation.parse(req.query, { errorMap }); - - const authorize = await lnurl.verifyAuthorizationSignature(sig, k1, pubkey); - if (!authorize) { - throw new Error("Error in keys"); - } - - await config.storage.update( - { k1, data: { pubkey, sig, success: true } }, - req - ); - - res.send( - JSON.stringify({ - status: "OK", - success: true, - k1, - }) - ); + const result = await logic(req.query, req, config); + + res.send(JSON.stringify(result)); } catch (e: any) { res.status(500).send(formatErrorMessage(e)); } } + +async function appHandler(req: NextRequest, config: Config) { + const query = paramsToObject(req.nextUrl.searchParams); + + const result = await logic(query, req, config); + + return Response.json(result); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, config); + } + return await pagesHandler(req, res, config); +} diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index cf39b83..941ffb8 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -1,5 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next/types"; -import lnurl from "lnurl"; +import { NextRequest, NextResponse } from "next/server"; +import { cookies } from "next/headers"; import { randomBytes } from "crypto"; import { @@ -7,36 +8,75 @@ import { errorMap, formatErrorMessage, } from "../validation/lnauth.js"; - import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; +import { paramsToObject } from "../utils/params.js"; -export default async function handler( +async function logic( + query: Record, + req: NextApiRequest | NextRequest, + config: Config +) { + const { state } = createValidation.parse(query, { errorMap }); + + const k1 = randomBytes(32).toString("hex"); + + let inputUrl = new URL(config.siteUrl + config.apis.callback); + inputUrl.searchParams.append("k1", k1); + inputUrl.searchParams.append("tag", "login"); + + const lnurl = require("lnurl"); + const encoded = lnurl.encode(inputUrl.toString()).toUpperCase(); + + await config.storage.set({ k1, data: { k1, state } }, req); + + return { + status: "OK", + success: true, + k1, + lnurl: encoded, + }; +} + +async function pagesHandler( req: NextApiRequest, res: NextApiResponse, config: Config ) { try { - const { state } = createValidation.parse(req.query, { errorMap }); + if (req.cookies["next-auth.session-token"]) { + throw new Error("You are already logged in"); + } + const result = await logic(req.query, req, config); + + res.send(JSON.stringify(result)); + } catch (e: any) { + res.status(500).send(formatErrorMessage(e)); + } +} + +async function appHandler(req: NextRequest, config: Config) { + const cookieStore = cookies(); + if (cookieStore.get("next-auth.session-token")) { + throw new Error("You are already logged in"); + } - const k1 = randomBytes(32).toString("hex"); + const query = paramsToObject(req.nextUrl.searchParams); - let inputUrl = new URL(config.siteUrl + config.apis.callback); - inputUrl.searchParams.append("k1", k1); - inputUrl.searchParams.append("tag", "login"); + const result = await logic(query, req, config); - const encoded = lnurl.encode(inputUrl.toString()).toUpperCase(); + return Response.json(result); +} - await config.storage.set({ k1, data: { k1, state } }, req); +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, routerType } = formatRouter(request, response); - res.send( - JSON.stringify({ - status: "OK", - success: true, - k1, - lnurl: encoded, - }) - ); - } catch (e: any) { - res.status(500).send(formatErrorMessage(e)); + if (routerType === "APP") { + return await appHandler(req, config); } + return await pagesHandler(req, res, config); } diff --git a/src/main/handlers/image.ts b/src/main/handlers/image.ts index 3f9b7ff..a19cc49 100644 --- a/src/main/handlers/image.ts +++ b/src/main/handlers/image.ts @@ -1,27 +1,58 @@ -import path from "path"; import { NextApiRequest, NextApiResponse } from "next/types"; +import { NextRequest, NextResponse } from "next/server"; import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; const cacheDuration = 24 * 60 * 60; // 1 day cache duration -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { +async function logic(path: string, config: Config) { if (!config.generateAvatar) throw new Error("Avatars are not enabled"); - const url = req.url?.toString(); - if (!url) throw new Error("Invalid url"); + if (!path) throw new Error("Invalid url"); - const { name, ext } = path.parse(url); + const [name, ext] = path.split("/").slice(-1)[0].split("."); if (!name) throw new Error("Invalid file name"); - if (ext !== ".svg") throw new Error("Invalid file type"); + if (ext !== "svg") throw new Error("Invalid file type"); const { image } = await config.generateAvatar(name, config); + return image; +} + +async function pagesHandler( + req: NextApiRequest, + res: NextApiResponse, + path: string, + config: Config +) { + const image = await logic(path, config); res.setHeader("content-type", "image/svg+xml"); res.setHeader("cache-control", `public, max-age=${cacheDuration}`); - res.send(image); + res.end(image); +} + +async function appHandler(req: NextRequest, path: string, config: Config) { + const image = await logic(path, config); + + return new Response(image, { + status: 200, + headers: { + "content-type": "image/svg+xml", + "cache-control": `public, max-age=${cacheDuration}`, + }, + }); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, path, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, path, config); + } + return await pagesHandler(req, res, path, config); } diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 84b615d..b664263 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -1,9 +1,15 @@ -import { renderToStaticMarkup } from "react-dom/server"; +import { renderToStaticMarkup } from "preact-render-to-string"; import { NextApiRequest, NextApiResponse } from "next/types"; +import { cookies } from "next/headers"; +import { NextRequest, NextResponse } from "next/server"; import { hardConfig, Config } from "../config/index.js"; import { vanilla } from "../utils/vanilla.js"; -import { LnAuthLogin, Loading, extractQuery } from "../../react/index.js"; + +import { LnAuthLogin } from "../../react/components/LnAuthLogin.js"; +import { Loading } from "../../react/components/Loading.js"; +import { extractQuery, extractSearchParams } from "../../react/utils/query.js"; +import { formatRouter } from "../utils/router.js"; function AuthPage({ config }: { config: Config }) { return ( @@ -83,29 +89,21 @@ function AuthPage({ config }: { config: Config }) { ); } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, +async function logic( + query: Record, + req: NextApiRequest | NextRequest, config: Config ) { - if (req.cookies["next-auth.session-token"]) { - throw new Error("You are already logged in"); - } - const title = config.title || config.siteUrl; const errorUrl = config.siteUrl + config.pages.error; - const query = extractQuery(req.query); - const html = renderToStaticMarkup(); if (!query.redirectUri || !query.state) { throw new Error("Missing required query param"); } - res.setHeader("Content-Type", "text/html"); - res.send( - ` + return ` ${title} ${html} @@ -116,6 +114,53 @@ export default async function handler( init(${JSON.stringify({ hardConfig, query, errorUrl })}) }; - ` - ); + `; +} + +async function pagesHandler( + req: NextApiRequest, + res: NextApiResponse, + config: Config +) { + if (req.cookies["next-auth.session-token"]) { + throw new Error("You are already logged in"); + } + + const query = extractQuery(req.query); + + const result = await logic(query, req, config); + + res.setHeader("content-type", "text/html"); + res.send(result); +} + +async function appHandler(req: NextRequest, config: Config) { + const cookieStore = cookies(); + if (cookieStore.get("next-auth.session-token")) { + throw new Error("You are already logged in"); + } + + const query = extractSearchParams(req.nextUrl.searchParams); + + const result = await logic(query, req, config); + + return new Response(result, { + status: 200, + headers: { + "content-type": "text/html", + }, + }); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, config); + } + return await pagesHandler(req, res, config); } diff --git a/src/main/handlers/poll.ts b/src/main/handlers/poll.ts index 2af0f95..8c897fc 100644 --- a/src/main/handlers/poll.ts +++ b/src/main/handlers/poll.ts @@ -1,30 +1,60 @@ import { NextApiRequest, NextApiResponse } from "next/types"; +import { NextRequest, NextResponse } from "next/server"; import { pollValidation, formatErrorMessage, errorMap, } from "../validation/lnauth.js"; - import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; -export default async function handler( +async function logic( + body: Record, + req: NextApiRequest | NextRequest, + config: Config +) { + const { k1 } = pollValidation.parse(body, { errorMap }); + + const { success = false } = await config.storage.get({ k1 }, req); + + return { + status: "OK", + success, + }; +} + +async function pagesHandler( req: NextApiRequest, res: NextApiResponse, config: Config ) { try { - const { k1 } = pollValidation.parse(req.body, { errorMap }); + const result = await logic(req.body, req, config); - const { success = false } = await config.storage.get({ k1 }, req); - - res.send( - JSON.stringify({ - status: "OK", - success, - }) - ); + res.send(JSON.stringify(result)); } catch (e: any) { res.status(500).send(formatErrorMessage(e)); } } + +async function appHandler(req: NextRequest, config: Config) { + const body = await req.json(); + + const result = await logic(body, req, config); + + return Response.json(result); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, config); + } + return await pagesHandler(req, res, config); +} diff --git a/src/main/handlers/qr.ts b/src/main/handlers/qr.ts index ff17f1a..afbe84b 100644 --- a/src/main/handlers/qr.ts +++ b/src/main/handlers/qr.ts @@ -1,27 +1,58 @@ -import path from "path"; import { NextApiRequest, NextApiResponse } from "next/types"; +import { NextRequest, NextResponse } from "next/server"; import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; const cacheDuration = 5 * 60; // short cache duration for the QR since it's short lived -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { +async function logic(path: string, config: Config) { if (!config.generateQr) throw new Error("QRs are not enabled"); - const url = req.url?.toString(); - if (!url) throw new Error("Invalid url"); + if (!path) throw new Error("Invalid url"); - const { name, ext } = path.parse(url); + const [name, ext] = path.split("/").slice(-1)[0].split("."); if (!name) throw new Error("Invalid file name"); - if (ext !== ".svg") throw new Error("Invalid file type"); + if (ext !== "svg") throw new Error("Invalid file type"); const { qr } = await config.generateQr(`lightning:${name}`, config); + return qr; +} + +async function pagesHandler( + req: NextApiRequest, + res: NextApiResponse, + path: string, + config: Config +) { + const qr = await logic(path, config); res.setHeader("content-type", "image/svg+xml"); res.setHeader("cache-control", `public, max-age=${cacheDuration}`); res.end(qr); } + +async function appHandler(req: NextRequest, path: string, config: Config) { + const qr = await logic(path, config); + + return new Response(qr, { + status: 200, + headers: { + "content-type": "image/svg+xml", + "cache-control": `public, max-age=${cacheDuration}`, + }, + }); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, path, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, path, config); + } + return await pagesHandler(req, res, path, config); +} diff --git a/src/main/handlers/token.ts b/src/main/handlers/token.ts index 5aff0e4..92f2fcb 100644 --- a/src/main/handlers/token.ts +++ b/src/main/handlers/token.ts @@ -1,4 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next/types"; +import { NextRequest, NextResponse } from "next/server"; import { tokenValidation, @@ -11,10 +12,12 @@ import { verifyRefreshToken, } from "../utils/jwt.js"; import { Config } from "../config/index.js"; +import { formatRouter } from "../utils/router.js"; +import { paramsToObject } from "../utils/params.js"; const handleAuthCode = async function ( k1: string, - req: NextApiRequest, + req: NextApiRequest | NextRequest, config: Config ) { const { pubkey, success } = await config.storage.get({ k1 }, req); @@ -28,7 +31,7 @@ const handleAuthCode = async function ( const handleRefreshToken = async function ( refreshToken: string, - req: NextApiRequest, + req: NextApiRequest | NextRequest, config: Config ) { const { pubkey } = await verifyRefreshToken(refreshToken, config); @@ -36,55 +39,86 @@ const handleRefreshToken = async function ( return pubkey; }; -export default async function handler( +async function logic( + body: Record, + req: NextApiRequest | NextRequest, + config: Config +) { + const { + grant_type: grantType, + code: k1, + refresh_token: refreshToken, + } = tokenValidation.parse(body, { errorMap }); + + let pubkey; + if (grantType === "authorization_code") { + if (!k1) throw new Error("Missing code"); + + pubkey = await handleAuthCode(k1, req, config); + } else if (grantType === "refresh_token") { + if (!refreshToken) throw new Error("Missing refresh token"); + + pubkey = await handleRefreshToken(refreshToken, req, config); + } else { + throw new Error("Invalid grant type"); + } + + if (!pubkey) throw new Error("Missing pubkey"); + + const token = { + // meta + token_type: "Bearer", + scope: "user", + + // id token + expires_in: config.intervals.idToken, + expires_at: Math.floor(Date.now() / 1000 + config.intervals.idToken), + id_token: await generateIdToken(pubkey, config), + + // refresh token + refresh_token: await generateRefreshToken(pubkey, config), + }; + + return { + status: "OK", + success: true, + ...token, + }; +} + +async function pagesHandler( req: NextApiRequest, res: NextApiResponse, config: Config ) { try { - const { - grant_type: grantType, - code: k1, - refresh_token: refreshToken, - } = tokenValidation.parse(req.body, { errorMap }); - - let pubkey; - if (grantType === "authorization_code") { - if (!k1) throw new Error("Missing code"); - - pubkey = await handleAuthCode(k1, req, config); - } else if (grantType === "refresh_token") { - if (!refreshToken) throw new Error("Missing refresh token"); - - pubkey = await handleRefreshToken(refreshToken, req, config); - } else { - throw new Error("Invalid grant type"); - } - - if (!pubkey) throw new Error("Missing pubkey"); - - const token = { - // meta - token_type: "Bearer", - scope: "user", - - // id token - expires_in: config.intervals.idToken, - expires_at: Math.floor(Date.now() / 1000 + config.intervals.idToken), - id_token: await generateIdToken(pubkey, config), - - // refresh token - refresh_token: await generateRefreshToken(pubkey, config), - }; - - res.send( - JSON.stringify({ - status: "OK", - success: true, - ...token, - }) - ); + const result = await logic(req.body, req, config); + + res.send(JSON.stringify(result)); } catch (e: any) { res.status(500).send(formatErrorMessage(e)); } } + +async function appHandler(req: NextRequest, config: Config) { + const text = await req.text(); + const searchParams = new URLSearchParams(text); + const body = paramsToObject(searchParams); + + const result = await logic(body, req, config); + + return Response.json(result); +} + +export default async function handler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config +) { + const { req, res, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, config); + } + return await pagesHandler(req, res, config); +} diff --git a/src/main/index.ts b/src/main/index.ts index 4262251..425ce2e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -15,6 +15,8 @@ import imageHandler from "./handlers/image.js"; import qrHandler from "./handlers/qr.js"; import { formatConfig, UserConfig } from "./config/index.js"; +import { NextRequest, NextResponse } from "next/server"; +import { formatRouter } from "./utils/router.js"; export default function NextAuthLightning(userConfig: UserConfig) { const config = formatConfig(userConfig); @@ -50,25 +52,27 @@ export default function NextAuthLightning(userConfig: UserConfig) { }; const handler = async function lnAuthHandler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest | NextRequest, + res: NextApiResponse | NextResponse ) { - if (req.url?.indexOf(config.apis.create) === 0) { + const { path } = formatRouter(req, res); + + if (path?.indexOf(config.apis.create) === 0) { return await createHandler(req, res, config); - } else if (req.url?.indexOf(config.apis.poll) === 0) { + } else if (path?.indexOf(config.apis.poll) === 0) { return await pollHandler(req, res, config); - } else if (req.url?.indexOf(config.apis.callback) === 0) { + } else if (path?.indexOf(config.apis.callback) === 0) { return await callbackHandler(req, res, config); - } else if (req.url?.indexOf(config.apis.token) === 0) { + } else if (path?.indexOf(config.apis.token) === 0) { return await tokenHandler(req, res, config); } else if ( config.pages?.signIn === config.apis.signIn && - req.url?.indexOf(config.apis.signIn) === 0 + path?.indexOf(config.apis.signIn) === 0 ) { return await loginHandler(req, res, config); - } else if (req.url?.indexOf(config.apis.image) === 0) { + } else if (path?.indexOf(config.apis.image) === 0) { return await imageHandler(req, res, config); - } else if (req.url?.indexOf(config.apis.qr) === 0) { + } else if (path?.indexOf(config.apis.qr) === 0) { return await qrHandler(req, res, config); } diff --git a/src/main/utils/params.ts b/src/main/utils/params.ts new file mode 100644 index 0000000..bab0a83 --- /dev/null +++ b/src/main/utils/params.ts @@ -0,0 +1,8 @@ +export function paramsToObject(searchParams: URLSearchParams) { + const entries = searchParams.entries(); + const result: Record = {}; + for (const [key, value] of entries) { + result[key] = value; + } + return result; +} diff --git a/src/main/utils/router.ts b/src/main/utils/router.ts new file mode 100644 index 0000000..ae49b6f --- /dev/null +++ b/src/main/utils/router.ts @@ -0,0 +1,36 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { NextRequest, NextResponse } from "next/server"; + +type Return = + | { + req: NextRequest; + res: NextResponse; + routerType: "APP"; + path: string; + } + | { + req: NextApiRequest; + res: NextApiResponse; + routerType: "PAGES"; + path: string; + }; + +export function formatRouter( + req: NextApiRequest | NextRequest, + res: NextApiResponse | NextResponse +): Return { + if ((res as any)?.params) { + return { + req: req as NextRequest, + res: res as NextResponse, + routerType: "APP", + path: new URL((req as NextRequest).nextUrl).pathname, + }; + } + return { + req: req as NextApiRequest, + res: res as NextApiResponse, + routerType: "PAGES", + path: req.url || "", + }; +} diff --git a/src/react/components/Button.tsx b/src/react/components/Button.tsx index 0630448..66800a5 100644 --- a/src/react/components/Button.tsx +++ b/src/react/components/Button.tsx @@ -1,4 +1,4 @@ -import { AnchorHTMLAttributes } from "react"; +import { HTMLAttributes } from "preact/compat"; import { formatLnAuth } from "../utils/lnurl.js"; import { hardConfig } from "../../main/config/hard.js"; @@ -8,7 +8,7 @@ export function Button({ ...props }: { lnurl: string; -} & AnchorHTMLAttributes) { +} & HTMLAttributes) { const { button } = formatLnAuth(lnurl); return ( diff --git a/src/react/components/CopyCode.tsx b/src/react/components/CopyCode.tsx index 89038cc..b3ff373 100644 --- a/src/react/components/CopyCode.tsx +++ b/src/react/components/CopyCode.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from "react"; +import { HTMLAttributes } from "preact/compat"; import { hardConfig } from "../../main/config/hard.js"; diff --git a/src/react/components/LnAuthLogin.tsx b/src/react/components/LnAuthLogin.tsx index 979fe1e..db22e8e 100644 --- a/src/react/components/LnAuthLogin.tsx +++ b/src/react/components/LnAuthLogin.tsx @@ -1,4 +1,4 @@ -import { CSSProperties } from "react"; +import { CSSProperties } from "preact/compat"; import { Title } from "./Title.js"; import { QrCode } from "./QrCode.js"; diff --git a/src/react/components/LnAuthLoginWrapper.tsx b/src/react/components/LnAuthLoginWrapper.tsx deleted file mode 100644 index c4058c5..0000000 --- a/src/react/components/LnAuthLoginWrapper.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { CSSProperties } from "react"; - -import { useLnUrl } from "../hooks/useLnUrl.js"; -import { Loading } from "./Loading.js"; -import { LnAuthLogin } from "./LnAuthLogin.js"; - -export function LnAuthLoginWrapper({ - title, - redirectUri, - errorUri, - state, - theme, -}: { - title?: string | null; - redirectUri: string; - errorUri?: string; - state: string; - theme?: { - loading?: CSSProperties; - wrapper?: CSSProperties; - title?: CSSProperties; - qr?: CSSProperties; - copy?: CSSProperties; - button?: CSSProperties; - }; -}) { - const { lnurl } = useLnUrl({ redirectUri, errorUri, state }); - - if (!lnurl) { - return ; - } - - return ( -
- -
- ); -} diff --git a/src/react/components/Loading.tsx b/src/react/components/Loading.tsx index c176683..fd42eb2 100644 --- a/src/react/components/Loading.tsx +++ b/src/react/components/Loading.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from "react"; +import { HTMLAttributes } from "preact/compat"; import { hardConfig } from "../../main/config/hard.js"; diff --git a/src/react/components/QrCode.tsx b/src/react/components/QrCode.tsx index 402226c..66abbf0 100644 --- a/src/react/components/QrCode.tsx +++ b/src/react/components/QrCode.tsx @@ -1,4 +1,4 @@ -import { ImgHTMLAttributes } from "react"; +import { HTMLAttributes } from "preact/compat"; import { formatLnAuth } from "../utils/lnurl.js"; import { hardConfig } from "../../main/config/hard.js"; @@ -8,7 +8,7 @@ export function QrCode({ ...props }: { lnurl: string; -} & ImgHTMLAttributes) { +} & HTMLAttributes) { const { qr } = formatLnAuth(lnurl); return ( diff --git a/src/react/components/Title.tsx b/src/react/components/Title.tsx index 33d4e56..5408449 100644 --- a/src/react/components/Title.tsx +++ b/src/react/components/Title.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from "react"; +import { HTMLAttributes } from "preact/compat"; import { hardConfig } from "../../main/config/hard.js"; diff --git a/src/react/hooks/useLnUrl.ts b/src/react/hooks/useLnUrl.ts index cb65eb7..30431d1 100644 --- a/src/react/hooks/useLnUrl.ts +++ b/src/react/hooks/useLnUrl.ts @@ -1,3 +1,5 @@ +"use client"; + import { useState, useEffect } from "react"; import { createApiRequest, pollApiRequest } from "../utils/api.js"; diff --git a/src/react/index.ts b/src/react/index.ts index a80d01f..2dbb8c9 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -1,14 +1,3 @@ -// composite components -export * from "./components/LnAuthLoginWrapper.js"; -export * from "./components/LnAuthLogin.js"; - -// simple components -export * from "./components/Button.js"; -export * from "./components/CopyCode.js"; -export * from "./components/Loading.js"; -export * from "./components/QrCode.js"; -export * from "./components/Title.js"; - // hooks export * from "./hooks/useLnUrl.js"; diff --git a/src/react/utils/query.ts b/src/react/utils/query.ts index b86e99f..5d01ccb 100644 --- a/src/react/utils/query.ts +++ b/src/react/utils/query.ts @@ -6,3 +6,13 @@ export function extractQuery(query: any) { return { redirectUri, state }; } + +export function extractSearchParams(searchParams: URLSearchParams) { + let redirectUri = searchParams.get("redirect_uri"); + let state = searchParams.get("state"); + + if (Array.isArray(redirectUri)) redirectUri = redirectUri[0]; + if (Array.isArray(state)) state = state[0]; + + return { redirectUri, state }; +} diff --git a/tsconfig.json b/tsconfig.json index 493d46f..d368b88 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "moduleResolution": "node", "lib": ["dom", "esnext"], "jsx": "react-jsx", + "jsxImportSource": "preact", "target": "ES2020", "module": "ESNext", @@ -26,7 +27,6 @@ "next": ["node_modules/next"], "next-auth": ["node_modules/next-auth"], "react": ["node_modules/react"], - "react-dom": ["node_modules/react-dom"] } }, "include": ["src"], From 6d4471bac4e591d52f138e531f0db859b10cb49f Mon Sep 17 00:00:00 2001 From: jowo Date: Sun, 19 Nov 2023 19:53:45 +0100 Subject: [PATCH 11/49] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66fd943..8fe2f1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", From 6ab87f39036938e9c2a06a37c77976924eddc4ba Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 07:41:07 +0100 Subject: [PATCH 12/49] adding plain js example --- TODO.md | 1 - examples/README.md | 4 +- examples/app-router/app/page.tsx | 2 +- examples/plain-js/.env.example | 3 + examples/plain-js/.gitignore | 42 + examples/plain-js/README.md | 25 + examples/plain-js/next.config.js | 15 + examples/plain-js/package-lock.json | 3998 +++++++++++++++++ examples/plain-js/package.json | 24 + examples/plain-js/pages/_app.jsx | 12 + .../plain-js/pages/api/auth/[...nextauth].js | 9 + .../plain-js/pages/api/lnauth/[...lnauth].js | 47 + examples/plain-js/pages/index.jsx | 34 + 13 files changed, 4212 insertions(+), 4 deletions(-) create mode 100644 examples/plain-js/.env.example create mode 100644 examples/plain-js/.gitignore create mode 100644 examples/plain-js/README.md create mode 100644 examples/plain-js/next.config.js create mode 100644 examples/plain-js/package-lock.json create mode 100644 examples/plain-js/package.json create mode 100644 examples/plain-js/pages/_app.jsx create mode 100644 examples/plain-js/pages/api/auth/[...nextauth].js create mode 100644 examples/plain-js/pages/api/lnauth/[...lnauth].js create mode 100644 examples/plain-js/pages/index.jsx diff --git a/TODO.md b/TODO.md index 1d76901..6e7e8a4 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Secondary -- add a plain js app to the examples folder (without typescript) - carefully run through the auth and data flow to look for bugs or oversights - ensure that peer dependencies are met and npm throws errors if not - add jest tests for all utils diff --git a/examples/README.md b/examples/README.md index 52fabe3..99fe402 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,9 +8,9 @@ The [examples/login-page/](https://github.com/jowo-io/next-auth-lightning-provid The [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router) example demonstrates how to configure `next-auth-lightning-provider` using the Next.js App Router. -### Vanilla JavaScript +### Plain JavaScript -> COMING SOON +The [examples/plain-js/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/plain-js) example demonstrates how to configure `next-auth-lightning-provider` in a plain JavaScript app (without TypeScript). ### KV diff --git a/examples/app-router/app/page.tsx b/examples/app-router/app/page.tsx index aed9109..c22ace1 100644 --- a/examples/app-router/app/page.tsx +++ b/examples/app-router/app/page.tsx @@ -23,7 +23,7 @@ const Home = async () => { )} -
{JSON.stringify(session, null, 2)}
+
{JSON.stringify({ session }, null, 2)}
diff --git a/examples/plain-js/.env.example b/examples/plain-js/.env.example new file mode 100644 index 0000000..1ce182b --- /dev/null +++ b/examples/plain-js/.env.example @@ -0,0 +1,3 @@ +# next-auth +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET="********" diff --git a/examples/plain-js/.gitignore b/examples/plain-js/.gitignore new file mode 100644 index 0000000..9bc9d71 --- /dev/null +++ b/examples/plain-js/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +**/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +# env +.env + +# session +.node-persist + +src/examples/plain-js \ No newline at end of file diff --git a/examples/plain-js/README.md b/examples/plain-js/README.md new file mode 100644 index 0000000..ead5835 --- /dev/null +++ b/examples/plain-js/README.md @@ -0,0 +1,25 @@ +## About + +This example demonstrates how to configure `next-auth-lightning-provider` in a plain JavaScript app (without TypeScript). + +> ⚠️ WARNING using `node-persist` is not recommended in lambda or edge environments. +> +> The reason not to use `node-persist` is that it stores session data locally in files, and in most lambda / cloud environments those files will not persist across sessions. +> +> Instead you should use persistent storage such as a database, a document store, or session storage. +> +> See the other examples in the examples folder for more info. + +## Getting Started + +#### Building `next-auth-lightning-provider` + +Before you can run this example, you must build `next-auth-lightning-provider`. + +Essentially all that's required is running `npm i` and `npm run build` from the directory root. + +#### Running this examples + +Run `npm i` to install dependencies + +Run `npm run dev` to launch the dev server and visit `localhost:3000` to view the app. diff --git a/examples/plain-js/next.config.js b/examples/plain-js/next.config.js new file mode 100644 index 0000000..bb18af1 --- /dev/null +++ b/examples/plain-js/next.config.js @@ -0,0 +1,15 @@ +var path = require("path"); + +const nextConfig = { + webpack: (config) => { + // These alias configurations are not required. They are only needed for development in the mono repo's example/ folder + config.resolve.alias["next"] = path.resolve("./node_modules/next"); + config.resolve.alias["next-auth"] = path.resolve( + "./node_modules/next-auth" + ); + config.resolve.alias["react"] = path.resolve("./node_modules/react"); + return config; + }, +}; + +module.exports = nextConfig; diff --git a/examples/plain-js/package-lock.json b/examples/plain-js/package-lock.json new file mode 100644 index 0000000..75adb05 --- /dev/null +++ b/examples/plain-js/package-lock.json @@ -0,0 +1,3998 @@ +{ + "name": "kv", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kv", + "version": "1.0.0", + "dependencies": { + "next": "^14.0.1", + "next-auth": "^4.24.4", + "next-auth-lightning-provider": "file:../../", + "node-persist": "^3.1.3", + "react": "18.2.0", + "zod": "^3.22.2" + }, + "devDependencies": { + "dotenv": "^16.3.1", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19" + } + }, + "../..": { + "version": "1.0.0-alpha.17", + "license": "ISC", + "dependencies": { + "@dicebear/collection": "^7.0.1", + "@dicebear/core": "^7.0.1", + "jose": "^5.1.0", + "lnurl": "^0.25.1", + "lodash.merge": "^4.6.2", + "preact": "^10.19.2", + "preact-render-to-string": "^6.3.1", + "qrcode": "^1.5.3", + "unique-names-generator": "^4.7.1", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/jest": "^29.5.8", + "@types/lodash.merge": "^4.6.9", + "@types/node": "20.5.9", + "@types/qrcode": "^1.5.4", + "@types/react": "18.2.21", + "eslint": "8.48.0", + "jest": "^29.7.0", + "next": "^14", + "next-auth": "^4", + "react": "^18.2.0", + "ts-jest": "^29.1.1", + "typescript": "^5.2.2" + }, + "peerDependencies": { + "next": "^14", + "next-auth": "^4", + "react": "^18.2.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@next/env": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.2.tgz", + "integrity": "sha512-HAW1sljizEaduEOes/m84oUqeIDAUYBR1CDwu2tobNlNDFP3cSm9d6QsOsGeNlIppU1p/p1+bWbYCbvwjFiceA==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", + "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", + "dev": true, + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.2.tgz", + "integrity": "sha512-i+jQY0fOb8L5gvGvojWyZMfQoQtDVB2kYe7fufOEiST6sicvzI2W5/EXo4lX5bLUjapHKe+nFxuVv7BA+Pd7LQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.2.tgz", + "integrity": "sha512-zRCAO0d2hW6gBEa4wJaLn+gY8qtIqD3gYd9NjruuN98OCI6YyelmhWVVLlREjS7RYrm9OUQIp/iVJFeB6kP1hg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.2.tgz", + "integrity": "sha512-tSJmiaon8YaKsVhi7GgRizZoV0N1Sx5+i+hFTrCKKQN7s3tuqW0Rov+RYdPhAv/pJl4qiG+XfSX4eJXqpNg3dA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.2.tgz", + "integrity": "sha512-dXJLMSEOwqJKcag1BeX1C+ekdPPJ9yXbWIt3nAadhbLx5CjACoB2NQj9Xcqu2tmdr5L6m34fR+fjGPs+ZVPLzA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.2.tgz", + "integrity": "sha512-WC9KAPSowj6as76P3vf1J3mf2QTm3Wv3FBzQi7UJ+dxWjK3MhHVWsWUo24AnmHx9qDcEtHM58okgZkXVqeLB+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.2.tgz", + "integrity": "sha512-KSSAwvUcjtdZY4zJFa2f5VNJIwuEVnOSlqYqbQIawREJA+gUI6egeiRu290pXioQXnQHYYdXmnVNZ4M+VMB7KQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.2.tgz", + "integrity": "sha512-2/O0F1SqJ0bD3zqNuYge0ok7OEWCQwk55RPheDYD0va5ij7kYwrFkq5ycCRN0TLjLfxSF6xI5NM6nC5ux7svEQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.2.tgz", + "integrity": "sha512-vJI/x70Id0oN4Bq/R6byBqV1/NS5Dl31zC+lowO8SDu1fHmUxoAdILZR5X/sKbiJpuvKcCrwbYgJU8FF/Gh50Q==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.2.tgz", + "integrity": "sha512-Ut4LXIUvC5m8pHTe2j0vq/YDnTEyq6RSR9vHYPqnELrDapPhLNz9Od/L5Ow3J8RNDWpEnfCiQXuVdfjlNEJ7ug==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", + "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.10.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", + "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "13.4.19", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/next": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.2.tgz", + "integrity": "sha512-jsAU2CkYS40GaQYOiLl9m93RTv2DA/tTJ0NRlmZIBIL87YwQ/xR8k796z7IqgM3jydI8G25dXvyYMC9VDIevIg==", + "dependencies": { + "@next/env": "14.0.2", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.0.2", + "@next/swc-darwin-x64": "14.0.2", + "@next/swc-linux-arm64-gnu": "14.0.2", + "@next/swc-linux-arm64-musl": "14.0.2", + "@next/swc-linux-x64-gnu": "14.0.2", + "@next/swc-linux-x64-musl": "14.0.2", + "@next/swc-win32-arm64-msvc": "14.0.2", + "@next/swc-win32-ia32-msvc": "14.0.2", + "@next/swc-win32-x64-msvc": "14.0.2" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-auth": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", + "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth-lightning-provider": { + "resolved": "../..", + "link": true + }, + "node_modules/next-auth/node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-persist": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.3.tgz", + "integrity": "sha512-CaFv+kSZtsc+VeDRldK1yR47k1vPLBpzYB9re2z7LIwITxwBtljMq3s8VQnnr+x3E8pQfHbc5r2IyJsBLJhtXg==", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openid-client": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/plain-js/package.json b/examples/plain-js/package.json new file mode 100644 index 0000000..9bd2d5b --- /dev/null +++ b/examples/plain-js/package.json @@ -0,0 +1,24 @@ +{ + "name": "kv", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "^14.0.1", + "next-auth": "^4.24.4", + "next-auth-lightning-provider": "file:../../", + "node-persist": "^3.1.3", + "react": "18.2.0", + "zod": "^3.22.2" + }, + "devDependencies": { + "dotenv": "^16.3.1", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19" + } +} diff --git a/examples/plain-js/pages/_app.jsx b/examples/plain-js/pages/_app.jsx new file mode 100644 index 0000000..819ee56 --- /dev/null +++ b/examples/plain-js/pages/_app.jsx @@ -0,0 +1,12 @@ +import { SessionProvider } from "next-auth/react"; + +export default function App({ + Component, + pageProps: { session, ...pageProps }, +}) { + return ( + + + + ); +} diff --git a/examples/plain-js/pages/api/auth/[...nextauth].js b/examples/plain-js/pages/api/auth/[...nextauth].js new file mode 100644 index 0000000..fec1316 --- /dev/null +++ b/examples/plain-js/pages/api/auth/[...nextauth].js @@ -0,0 +1,9 @@ +import NextAuth, { AuthOptions } from "next-auth"; + +import { lightningProvider } from "../lnauth/[...lnauth]"; + +export const authOptions = { + providers: [lightningProvider], +}; + +export default NextAuth(authOptions); diff --git a/examples/plain-js/pages/api/lnauth/[...lnauth].js b/examples/plain-js/pages/api/lnauth/[...lnauth].js new file mode 100644 index 0000000..9b9544a --- /dev/null +++ b/examples/plain-js/pages/api/lnauth/[...lnauth].js @@ -0,0 +1,47 @@ +import NextAuthLightning from "next-auth-lightning-provider"; +import { generateQr } from "next-auth-lightning-provider/generators/qr"; +import { generateName } from "next-auth-lightning-provider/generators/name"; +import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; + +import storage from "node-persist"; // ⚠️ WARNING using node-persist is not recommended in lambda or edge environments. + +await storage.init(); + +const config = { + // required + siteUrl: process.env.NEXTAUTH_URL, + secret: process.env.NEXTAUTH_SECRET, + storage: { + async set({ k1, data }) { + await storage.setItem(`k1:${k1}`, data); + }, + async get({ k1 }) { + const results = await storage.getItem(`k1:${k1}`); + + if (!results) throw new Error("Couldn't find item by k1"); + + return results; + }, + async update({ k1, data }) { + await storage.setItem(`k1:${k1}`, data); + }, + async delete({ k1 }) { + await storage.removeItem(`k1:${k1}`); + }, + }, + generateQr, + + // optional + generateName, + generateAvatar, + + theme: { + colorScheme: "dark", + }, +}; + +const { provider, handler } = NextAuthLightning(config); + +export const lightningProvider = provider; + +export default handler; diff --git a/examples/plain-js/pages/index.jsx b/examples/plain-js/pages/index.jsx new file mode 100644 index 0000000..daaeb04 --- /dev/null +++ b/examples/plain-js/pages/index.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useSession } from "next-auth/react"; +import { signOut, signIn } from "next-auth/react"; + +const Home = () => { + const session = useSession(); + + return ( +
+

Auth session:

+ {session?.data?.user?.image && ( + <> + {`Avatar +
+ + )} + +
{JSON.stringify(session, null, 2)}
+ + {session && session.data ? ( + + ) : ( + + )} +
+ ); +}; + +export default Home; From ddf6e77038e8eeee19372fd60796a248111f292c Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 08:27:08 +0100 Subject: [PATCH 13/49] updating peer dependencies --- README.md | 12 ++++++++++-- TODO.md | 1 - package.json | 5 +++-- src/main/handlers/create.ts | 3 +-- src/main/handlers/login.tsx | 3 +-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7196bc4..d5baff0 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,16 @@ As well as providing the basic authentication functionality that you'd expect, ` # Compatibility -- `next-auth-lightning-provider` currently only supports `next@v13` and higher. -- `next-auth-lightning-provider` currently only supports `next-auth@v4`. +```json +{ + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "next-auth": "^4", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + } +} +``` # Getting started diff --git a/TODO.md b/TODO.md index 6e7e8a4..e0ce4c3 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Secondary - carefully run through the auth and data flow to look for bugs or oversights -- ensure that peer dependencies are met and npm throws errors if not - add jest tests for all utils - consider how to clean up old and unused lnauth session data that was created but never reached success state diff --git a/package.json b/package.json index 8fe2f1a..f941c9f 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,10 @@ "prebuild": "tsc --build --clean" }, "peerDependencies": { - "next": "^14", + "next": "^12.2.5 || ^13 || ^14", "next-auth": "^4", - "react": "^18.2.0" + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" }, "dependencies": { "@dicebear/collection": "^7.0.1", diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 941ffb8..eacada9 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -1,6 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next/types"; import { NextRequest, NextResponse } from "next/server"; -import { cookies } from "next/headers"; import { randomBytes } from "crypto"; import { @@ -56,7 +55,7 @@ async function pagesHandler( } async function appHandler(req: NextRequest, config: Config) { - const cookieStore = cookies(); + const cookieStore = require("next/headers").cookies; // using `require` so that next@12 is supported if (cookieStore.get("next-auth.session-token")) { throw new Error("You are already logged in"); } diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index b664263..1d08e1e 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -1,6 +1,5 @@ import { renderToStaticMarkup } from "preact-render-to-string"; import { NextApiRequest, NextApiResponse } from "next/types"; -import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { hardConfig, Config } from "../config/index.js"; @@ -135,7 +134,7 @@ async function pagesHandler( } async function appHandler(req: NextRequest, config: Config) { - const cookieStore = cookies(); + const cookieStore = require("next/headers").cookies; // using `require` so that next@12 is supported if (cookieStore.get("next-auth.session-token")) { throw new Error("You are already logged in"); } From 6b85adf6beff991f73b83facb180b5fca8873801 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 08:27:48 +0100 Subject: [PATCH 14/49] fixing typos --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index e0ce4c3..ada6c5c 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Tertiary -- consider / investigate how to SSR react components so the `vanilla.ts` shim can be depricated +- consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated - add JSDocs comments to functions / hooks etc - decide on terminology (avatar or image or picture) - add more example repos @@ -29,7 +29,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - see if TS generics can be used for NextRequest/NextApiRequest etc - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - make generators return strings instead of objects `{ qr: "..." }` -- consider standardising APIs so they're all either POST or GET +- consider standardizing APIs so they're all either POST or GET ### Readme From c0a52cd4c68a030be006ca0b60550c1cc0c197ff Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 08:45:32 +0100 Subject: [PATCH 15/49] fixing app router cookies --- src/main/handlers/create.ts | 3 +-- src/main/handlers/login.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index eacada9..94d1d59 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -55,8 +55,7 @@ async function pagesHandler( } async function appHandler(req: NextRequest, config: Config) { - const cookieStore = require("next/headers").cookies; // using `require` so that next@12 is supported - if (cookieStore.get("next-auth.session-token")) { + if (req.cookies.get("next-auth.session-token")?.value) { throw new Error("You are already logged in"); } diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 1d08e1e..37fe231 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -134,8 +134,7 @@ async function pagesHandler( } async function appHandler(req: NextRequest, config: Config) { - const cookieStore = require("next/headers").cookies; // using `require` so that next@12 is supported - if (cookieStore.get("next-auth.session-token")) { + if (req.cookies.get("next-auth.session-token")?.value) { throw new Error("You are already logged in"); } From ff5053783f993a3befac8ba970f4495dd9d4c30f Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 08:48:14 +0100 Subject: [PATCH 16/49] fixing app router session prop --- examples/app-router/app/components/Login.tsx | 6 +++--- src/generators/qr.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/app-router/app/components/Login.tsx b/examples/app-router/app/components/Login.tsx index f958ac5..6d0125b 100644 --- a/examples/app-router/app/components/Login.tsx +++ b/examples/app-router/app/components/Login.tsx @@ -3,13 +3,13 @@ import { Session } from "next-auth"; import { signOut, signIn } from "next-auth/react"; -export const Login = async (session: { session: Session | null }) => { +export const Login = async ({ session }: { session: Session | null }) => { return (
{session ? ( - - ) : ( + ) : ( + )}
); diff --git a/src/generators/qr.ts b/src/generators/qr.ts index beda63a..f2adf55 100644 --- a/src/generators/qr.ts +++ b/src/generators/qr.ts @@ -36,9 +36,9 @@ export const generateQr = async (data: string, config: Config) => { const options = merge(themeOptions, qrOptions); return { - qr: (await QRCode.toString(data, { + qr: await QRCode.toBuffer(data, { ...options, - type: "svg", - })) as unknown as string, + type: "png", + }), }; }; From 5c575a84cd7d107d66accb9b8917ca81add7d189 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 10:21:49 +0100 Subject: [PATCH 17/49] adding support for png and jpg files format for generators --- README.md | 18 ++++++++--- TODO.md | 2 -- src/generators/avatar.ts | 7 ++-- src/generators/name.ts | 4 +-- src/generators/qr.ts | 11 ++++--- src/main/config/types.ts | 23 +++++++++---- src/main/handlers/image.ts | 66 ++++++++++++++++++++++++++------------ src/main/handlers/qr.ts | 65 +++++++++++++++++++++++++------------ src/main/utils/jwt.ts | 2 +- src/main/utils/vanilla.ts | 2 +- src/react/utils/lnurl.ts | 2 +- 11 files changed, 134 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index d5baff0..577480b 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ const config: NextAuthLightningConfig = { * @param {function} qr.generateQr * * Define the QR code generator function. - * It must return a correctly formatted string containing svg XML markup. + * It must return a base64 encoded png/jpg OR svg XML markup. * * A default QR code generator is provided. It can be imported from: * import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -249,7 +249,11 @@ const config: NextAuthLightningConfig = { */ async generateQr(data, config) { return { - qr: '...........' + data: "data:image/png;base64,iVBO.....CYII=", + type: "png", + // or + data: ".....", + type: "svg" }; }, @@ -288,7 +292,7 @@ const config: NextAuthLightningConfig = { * @param {function | null} generateAvatar * * Define the default deterministic avatar generator. - * It must return a correctly formatted string containing svg XML markup. + * It must return a base64 encoded png/jpg OR svg XML markup. * Or, it can be set to null to disable avatars. * * A default avatar generator is provided. It can be imported from: @@ -297,9 +301,13 @@ const config: NextAuthLightningConfig = { * The default avatar generation library that's used is dicebear's bottts style. * @see https://www.dicebear.com/styles/bottts/ */ - async generateAvatar(seed) { + async generateAvatar(data, config) { return { - image: '...........' + data: "data:image/png;base64,iVBO.....CYII=", + type: "png", + // or + data: ".....", + type: "svg" }; }, diff --git a/TODO.md b/TODO.md index ada6c5c..15ebae3 100644 --- a/TODO.md +++ b/TODO.md @@ -24,11 +24,9 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - add `auto` color scheme that uses browsers dark/light settings - cancel inflight api requests if hook unmounts - consider adding various styles of avatar and name generators -- support multiple file types for avatar and qr - rename `useLnUrl` to `useLightningAuthUrl` - see if TS generics can be used for NextRequest/NextApiRequest etc - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc -- make generators return strings instead of objects `{ qr: "..." }` - consider standardizing APIs so they're all either POST or GET ### Readme diff --git a/src/generators/avatar.ts b/src/generators/avatar.ts index c7982a4..e56306e 100644 --- a/src/generators/avatar.ts +++ b/src/generators/avatar.ts @@ -1,10 +1,11 @@ import { createAvatar } from "@dicebear/core"; import { bottts } from "@dicebear/collection"; -import { Config } from "../main/config/types.js"; +import { AvatarGenerator } from "../main/config/types.js"; -export const generateAvatar = async (seed: string, config: Config) => { +export const generateAvatar: AvatarGenerator = async (seed, config) => { return { - image: createAvatar(bottts, { seed }).toString(), + data: createAvatar(bottts, { seed }).toString(), + type: "svg", }; }; diff --git a/src/generators/name.ts b/src/generators/name.ts index cbb4be4..4979b7b 100644 --- a/src/generators/name.ts +++ b/src/generators/name.ts @@ -5,9 +5,9 @@ import { animals, } from "unique-names-generator"; -import { Config } from "../main/config/types.js"; +import { NameGenerator } from "../main/config/types.js"; -export const generateName = async (seed: string, config: Config) => { +export const generateName: NameGenerator = async (seed, config) => { return { name: uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals], diff --git a/src/generators/qr.ts b/src/generators/qr.ts index f2adf55..0d88e0e 100644 --- a/src/generators/qr.ts +++ b/src/generators/qr.ts @@ -2,9 +2,9 @@ import merge from "lodash.merge"; import QRCode from "qrcode"; -import { Config } from "../main/config/types.js"; +import { QRGenerator } from "../main/config/types.js"; -export const generateQr = async (data: string, config: Config) => { +export const generateQr: QRGenerator = async (data, config) => { // generic preset theme options const themeOptions = config.theme.colorScheme === "dark" @@ -36,9 +36,10 @@ export const generateQr = async (data: string, config: Config) => { const options = merge(themeOptions, qrOptions); return { - qr: await QRCode.toBuffer(data, { + data: (await QRCode.toString(data, { ...options, - type: "png", - }), + type: "svg", + })) as unknown as string, + type: "svg", }; }; diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 5ea2e8a..341bc19 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -44,6 +44,19 @@ export type LnAuthData = { [key: string | number | symbol]: unknown; }; +export type QRGenerator = ( + data: string, + config: Config +) => Promise<{ data: string; type: "svg" | "png" | "jpg" }>; +export type AvatarGenerator = ( + seed: string, + config: Config +) => Promise<{ data: string; type: "svg" | "png" | "jpg" }>; +export type NameGenerator = ( + seed: string, + config: Config +) => Promise<{ name: string }>; + export type RequiredConfig = { siteUrl: string; secret: string; @@ -78,7 +91,7 @@ export type RequiredConfig = { req: NextApiRequest | NextRequest ) => Promise; }; - generateQr: (data: string, config: Config) => Promise<{ qr: string }>; + generateQr: QRGenerator; }; export type ThemeStyles = { @@ -98,12 +111,8 @@ export type OptionalConfig = { error?: string; }; title: string | null; - generateAvatar: - | ((seed: string, config: Config) => Promise<{ image: string }>) - | null; - generateName: - | ((seed: string, config: Config) => Promise<{ name: string }>) - | null; + generateAvatar: AvatarGenerator | null; + generateName: NameGenerator | null; theme: { colorScheme?: "dark" | "light"; diff --git a/src/main/handlers/image.ts b/src/main/handlers/image.ts index a19cc49..7f67938 100644 --- a/src/main/handlers/image.ts +++ b/src/main/handlers/image.ts @@ -5,18 +5,50 @@ import { Config } from "../config/index.js"; import { formatRouter } from "../utils/router.js"; const cacheDuration = 24 * 60 * 60; // 1 day cache duration +const base64Regex = /^data:image\/(svg\+xml|png|jpeg|jpg);base64,/; -async function logic(path: string, config: Config) { - if (!config.generateAvatar) throw new Error("Avatars are not enabled"); +const fileTypeHeaders = { + png: "image/png", + jpg: "image/jpg", + svg: "image/svg+xml", +}; +async function logic( + path: string, + config: Config +): Promise<{ + headers: HeadersInit; + data: string | Buffer; +}> { + if (!config.generateAvatar) throw new Error("Avatars are not enabled"); if (!path) throw new Error("Invalid url"); - const [name, ext] = path.split("/").slice(-1)[0].split("."); - if (!name) throw new Error("Invalid file name"); - if (ext !== "svg") throw new Error("Invalid file type"); + const pubkey = path.split("/").slice(-1)[0]; + if (!pubkey) throw new Error("Invalid pubkey"); + + const { data, type } = await config.generateAvatar(pubkey, config); - const { image } = await config.generateAvatar(name, config); - return image; + if (base64Regex.test(data)) { + const buffer = data.replace(base64Regex, ""); + return { + headers: { + "content-type": fileTypeHeaders[type], + "content-length": buffer.length.toString(), + "cache-control": `public, max-age=${cacheDuration}`, + }, + data: Buffer.from(buffer, "base64"), + }; + } else if (type === "svg") { + return { + headers: { + "content-type": fileTypeHeaders[type], + "cache-control": `public, max-age=${cacheDuration}`, + }, + data, + }; + } + + throw new Error("Something went wrong"); } async function pagesHandler( @@ -25,25 +57,17 @@ async function pagesHandler( path: string, config: Config ) { - const image = await logic(path, config); + const { data, headers } = await logic(path, config); - res.setHeader("content-type", "image/svg+xml"); - res.setHeader("cache-control", `public, max-age=${cacheDuration}`); - res.end(image); + Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value)); + res.end(data); } async function appHandler(req: NextRequest, path: string, config: Config) { - const image = await logic(path, config); - - return new Response(image, { - status: 200, - headers: { - "content-type": "image/svg+xml", - "cache-control": `public, max-age=${cacheDuration}`, - }, - }); -} + const { data, headers } = await logic(path, config); + return new Response(data, { status: 200, headers }); +} export default async function handler( request: NextApiRequest | NextRequest, response: NextApiResponse | NextResponse, diff --git a/src/main/handlers/qr.ts b/src/main/handlers/qr.ts index afbe84b..a6b16c8 100644 --- a/src/main/handlers/qr.ts +++ b/src/main/handlers/qr.ts @@ -5,18 +5,50 @@ import { Config } from "../config/index.js"; import { formatRouter } from "../utils/router.js"; const cacheDuration = 5 * 60; // short cache duration for the QR since it's short lived +const base64Regex = /^data:image\/(png|jpeg|jpg);base64,/; -async function logic(path: string, config: Config) { - if (!config.generateQr) throw new Error("QRs are not enabled"); +const fileTypeHeaders = { + png: "image/png", + jpg: "image/jpg", + svg: "image/svg+xml", +}; +async function logic( + path: string, + config: Config +): Promise<{ + headers: HeadersInit; + data: string | Buffer; +}> { + if (!config.generateQr) throw new Error("QRs are not enabled"); if (!path) throw new Error("Invalid url"); - const [name, ext] = path.split("/").slice(-1)[0].split("."); - if (!name) throw new Error("Invalid file name"); - if (ext !== "svg") throw new Error("Invalid file type"); + const k1 = path.split("/").slice(-1)[0]; + if (!k1) throw new Error("Invalid k1"); - const { qr } = await config.generateQr(`lightning:${name}`, config); - return qr; + const { data, type } = await config.generateQr(`lightning:${k1}`, config); + + if (base64Regex.test(data)) { + const buffer = data.replace(base64Regex, ""); + return { + headers: { + "content-type": fileTypeHeaders[type], + "content-length": buffer.length.toString(), + "cache-control": `public, max-age=${cacheDuration}`, + }, + data: Buffer.from(buffer, "base64"), + }; + } else if (type === "svg") { + return { + headers: { + "content-type": fileTypeHeaders[type], + "cache-control": `public, max-age=${cacheDuration}`, + }, + data, + }; + } + + throw new Error("Something went wrong"); } async function pagesHandler( @@ -25,23 +57,16 @@ async function pagesHandler( path: string, config: Config ) { - const qr = await logic(path, config); + const { data, headers } = await logic(path, config); - res.setHeader("content-type", "image/svg+xml"); - res.setHeader("cache-control", `public, max-age=${cacheDuration}`); - res.end(qr); + Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value)); + res.end(data); } async function appHandler(req: NextRequest, path: string, config: Config) { - const qr = await logic(path, config); - - return new Response(qr, { - status: 200, - headers: { - "content-type": "image/svg+xml", - "cache-control": `public, max-age=${cacheDuration}`, - }, - }); + const { data, headers } = await logic(path, config); + + return new Response(data, { status: 200, headers }); } export default async function handler( diff --git a/src/main/utils/jwt.ts b/src/main/utils/jwt.ts index be570b4..c24ac27 100644 --- a/src/main/utils/jwt.ts +++ b/src/main/utils/jwt.ts @@ -10,7 +10,7 @@ export async function generateIdToken(pubkey: string, config: Config) { : { name: "" }; const image = config?.generateAvatar - ? `${config.siteUrl}${config.apis.image}/${pubkey}.svg` + ? `${config.siteUrl}${config.apis.image}/${pubkey}` : ""; const expires = Math.floor(Date.now() / 1000 + config.intervals.idToken); diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 93ec985..2a7f8c8 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -103,7 +103,7 @@ export const vanilla = function ({ // inject qr src var qr = document.getElementById(hardConfig.ids.qr) as HTMLImageElement; if (qr) { - qr.src = hardConfig.apis.qr + "/" + data.lnurl + ".svg"; + qr.src = hardConfig.apis.qr + "/" + data.lnurl; qr.onerror = error.bind( undefined, new Error("Failed to load QR code") diff --git a/src/react/utils/lnurl.ts b/src/react/utils/lnurl.ts index 87877c5..d4d9549 100644 --- a/src/react/utils/lnurl.ts +++ b/src/react/utils/lnurl.ts @@ -5,7 +5,7 @@ export function formatLnAuth(lnurl?: string | null) { return { lnurl, qr: "", button: "" }; } - const qr = `${hardConfig.apis.qr}/${lnurl}.svg`; + const qr = `${hardConfig.apis.qr}/${lnurl}`; const button = `lightning:${lnurl}`; return { lnurl, qr, button }; From 4a3f0d5fae3f7506b7e2324ef409f3553fc9dd14 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:15:31 +0100 Subject: [PATCH 18/49] resorting todo list --- TODO.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index 15ebae3..53a4070 100644 --- a/TODO.md +++ b/TODO.md @@ -12,23 +12,23 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - carefully run through the auth and data flow to look for bugs or oversights - add jest tests for all utils - consider how to clean up old and unused lnauth session data that was created but never reached success state - -### Tertiary - -- consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated -- add JSDocs comments to functions / hooks etc - decide on terminology (avatar or image or picture) -- add more example repos +- add JSDocs comments to functions / hooks etc - add spinner to Loading component - open PR on `next-auth` -- add `auto` color scheme that uses browsers dark/light settings -- cancel inflight api requests if hook unmounts -- consider adding various styles of avatar and name generators - rename `useLnUrl` to `useLightningAuthUrl` - see if TS generics can be used for NextRequest/NextApiRequest etc - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - consider standardizing APIs so they're all either POST or GET +### Tertiary + +- cancel inflight api requests if hook unmounts +- consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated +- add more example repos +- add `auto` color scheme that uses browsers dark/light settings +- consider adding various styles of avatar and name generators + ### Readme - add yellow notes to diagram. From 4e3465efadd48a9d869c250360f9fc20baaa2328 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:19:19 +0100 Subject: [PATCH 19/49] renaming image api to avatar --- TODO.md | 2 -- src/main/config/hard.ts | 2 +- src/main/config/types.ts | 4 ++-- src/main/handlers/{image.ts => avatar.ts} | 0 src/main/index.ts | 4 ++-- src/main/utils/jwt.ts | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) rename src/main/handlers/{image.ts => avatar.ts} (100%) diff --git a/TODO.md b/TODO.md index 53a4070..adec049 100644 --- a/TODO.md +++ b/TODO.md @@ -32,8 +32,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Readme - add yellow notes to diagram. -- add info about deterministic generation of image and name - carefully scan for errors and typos - see if config docs can be moved into TS file and autogenerated -- add examples/images showing generator outputs. e.g. bottts images - ensure consistent formatting is used. full stops, caps, etc diff --git a/src/main/config/hard.ts b/src/main/config/hard.ts index 3c2ea53..309fc41 100644 --- a/src/main/config/hard.ts +++ b/src/main/config/hard.ts @@ -14,7 +14,7 @@ export const hardConfig: HardConfig = { signIn: "/api/lnauth/login", // images - image: "/api/lnauth/image", + avatar: "/api/lnauth/avatar", qr: "/api/lnauth/qr", }, ids: { diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 341bc19..577fdea 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -12,8 +12,8 @@ export type HardConfig = { // pages signIn: string; - // misc - image: string; + // images + avatar: string; qr: string; }; ids: { diff --git a/src/main/handlers/image.ts b/src/main/handlers/avatar.ts similarity index 100% rename from src/main/handlers/image.ts rename to src/main/handlers/avatar.ts diff --git a/src/main/index.ts b/src/main/index.ts index 425ce2e..38d43ae 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -11,7 +11,7 @@ import tokenHandler from "./handlers/token.js"; import loginHandler from "./handlers/login.js"; // misc -import imageHandler from "./handlers/image.js"; +import imageHandler from "./handlers/avatar.js"; import qrHandler from "./handlers/qr.js"; import { formatConfig, UserConfig } from "./config/index.js"; @@ -70,7 +70,7 @@ export default function NextAuthLightning(userConfig: UserConfig) { path?.indexOf(config.apis.signIn) === 0 ) { return await loginHandler(req, res, config); - } else if (path?.indexOf(config.apis.image) === 0) { + } else if (path?.indexOf(config.apis.avatar) === 0) { return await imageHandler(req, res, config); } else if (path?.indexOf(config.apis.qr) === 0) { return await qrHandler(req, res, config); diff --git a/src/main/utils/jwt.ts b/src/main/utils/jwt.ts index c24ac27..557ccb3 100644 --- a/src/main/utils/jwt.ts +++ b/src/main/utils/jwt.ts @@ -10,7 +10,7 @@ export async function generateIdToken(pubkey: string, config: Config) { : { name: "" }; const image = config?.generateAvatar - ? `${config.siteUrl}${config.apis.image}/${pubkey}` + ? `${config.siteUrl}${config.apis.avatar}/${pubkey}` : ""; const expires = Math.floor(Date.now() / 1000 + config.intervals.idToken); From 2e64572d03e80330d8f3e613bf6b06f066faa026 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:37:55 +0100 Subject: [PATCH 20/49] adding basic loading spinner --- TODO.md | 2 -- src/main/handlers/login.tsx | 32 ++++++++++++++++++++++++++++++-- src/react/components/Loading.tsx | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index adec049..a00ea56 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - consider how to clean up old and unused lnauth session data that was created but never reached success state - decide on terminology (avatar or image or picture) - add JSDocs comments to functions / hooks etc -- add spinner to Loading component - open PR on `next-auth` - rename `useLnUrl` to `useLightningAuthUrl` - see if TS generics can be used for NextRequest/NextApiRequest etc @@ -33,5 +32,4 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - add yellow notes to diagram. - carefully scan for errors and typos -- see if config docs can be moved into TS file and autogenerated - ensure consistent formatting is used. full stops, caps, etc diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 37fe231..17e069b 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -30,7 +30,19 @@ function AuthPage({ config }: { config: Config }) { >
{/* loading component is rendered and shown initially, before window.onload is triggered */} - + tag + }} + /> {/* login component is rendered with display: none, after window.onload is triggered */} + return ` + + + ${title} + ${html} diff --git a/src/react/components/Loading.tsx b/src/react/components/Loading.tsx index fd42eb2..f117a48 100644 --- a/src/react/components/Loading.tsx +++ b/src/react/components/Loading.tsx @@ -5,7 +5,7 @@ import { hardConfig } from "../../main/config/hard.js"; export function Loading({ ...props }: {} & HTMLAttributes) { return (
- Loading ... + Loading...
); } From 1f194dc14be2d148fde016100231f8ccfdffb35c Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:40:41 +0100 Subject: [PATCH 21/49] rename `useLnUrl` to `useLightningAuth` --- TODO.md | 1 - examples/login-page/components/LightningLogin.tsx | 4 ++-- src/react/hooks/{useLnUrl.ts => useLightningAuth.ts} | 2 +- src/react/index.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) rename src/react/hooks/{useLnUrl.ts => useLightningAuth.ts} (98%) diff --git a/TODO.md b/TODO.md index a00ea56..eec9589 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - decide on terminology (avatar or image or picture) - add JSDocs comments to functions / hooks etc - open PR on `next-auth` -- rename `useLnUrl` to `useLightningAuthUrl` - see if TS generics can be used for NextRequest/NextApiRequest etc - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - consider standardizing APIs so they're all either POST or GET diff --git a/examples/login-page/components/LightningLogin.tsx b/examples/login-page/components/LightningLogin.tsx index c8465fb..7fdad8e 100644 --- a/examples/login-page/components/LightningLogin.tsx +++ b/examples/login-page/components/LightningLogin.tsx @@ -1,4 +1,4 @@ -import { useLnUrl } from "next-auth-lightning-provider/react"; +import { useLightningAuth } from "next-auth-lightning-provider/react"; export default function LightningLogin({ redirectUri, @@ -7,7 +7,7 @@ export default function LightningLogin({ redirectUri: string; state: string; }) { - const { lnurl, qr, button } = useLnUrl({ redirectUri, state }); + const { lnurl, qr, button } = useLightningAuth({ redirectUri, state }); if (!lnurl) { return ( diff --git a/src/react/hooks/useLnUrl.ts b/src/react/hooks/useLightningAuth.ts similarity index 98% rename from src/react/hooks/useLnUrl.ts rename to src/react/hooks/useLightningAuth.ts index 30431d1..5e6d102 100644 --- a/src/react/hooks/useLnUrl.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -6,7 +6,7 @@ import { createApiRequest, pollApiRequest } from "../utils/api.js"; import { hardConfig } from "../../main/config/hard.js"; import { formatLnAuth } from "../utils/lnurl.js"; -export function useLnUrl({ +export function useLightningAuth({ redirectUri, errorUri, state, diff --git a/src/react/index.ts b/src/react/index.ts index 2dbb8c9..0a22502 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -1,5 +1,5 @@ // hooks -export * from "./hooks/useLnUrl.js"; +export * from "./hooks/useLightningAuth.js"; // utils export * from "./utils/query.js"; From 813500e344a090c08e53295ffdf3179bda715f5f Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:49:24 +0100 Subject: [PATCH 22/49] bumping package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f941c9f..2913991 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.17", + "version": "1.0.0-alpha.18", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", From accc96323b2e5895b231e1d292a855ef3c6a3963 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:49:30 +0100 Subject: [PATCH 23/49] tweaking todo --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index eec9589..68bf574 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - decide on terminology (avatar or image or picture) - add JSDocs comments to functions / hooks etc - open PR on `next-auth` -- see if TS generics can be used for NextRequest/NextApiRequest etc - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - consider standardizing APIs so they're all either POST or GET @@ -26,6 +25,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - add more example repos - add `auto` color scheme that uses browsers dark/light settings - consider adding various styles of avatar and name generators +- see if TS generics can be used for NextRequest/NextApiRequest etc ### Readme From 5c0e79d10e1173bdf7fcc8be704f0d7fa5cde137 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:52:23 +0100 Subject: [PATCH 24/49] tweaking readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 577480b..3118e53 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ const config: NextAuthLightningConfig = { /** * @param {function | null} generateAvatar * - * Define the default deterministic avatar generator. + * Define the deterministic avatar generator. * It must return a base64 encoded png/jpg OR svg XML markup. * Or, it can be set to null to disable avatars. * @@ -314,7 +314,7 @@ const config: NextAuthLightningConfig = { /** * @param {function | null} generateName * - * Define the default deterministic name generator. + * Define the deterministic name generator. * Or, it can be set to null to disable names * * A default name generator is provided. It can be imported from: From b6b73505f5db528fd06fdd5639f5e6551d3dd88a Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 11:53:54 +0100 Subject: [PATCH 25/49] removing todo item --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index 68bf574..d6dbb49 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - carefully run through the auth and data flow to look for bugs or oversights - add jest tests for all utils - consider how to clean up old and unused lnauth session data that was created but never reached success state -- decide on terminology (avatar or image or picture) - add JSDocs comments to functions / hooks etc - open PR on `next-auth` - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc From 3222bf5938ed5d6d41be614b5a1bf106c31d93c7 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 12:08:18 +0100 Subject: [PATCH 26/49] using `application/x-www-form-urlencoded` instead of `application/json` so that content type is the same for this package's APIs as the `next-auth` APIs --- TODO.md | 1 - src/main/handlers/create.ts | 12 +++++++----- src/main/handlers/poll.ts | 5 ++++- src/main/utils/params.ts | 4 ++-- src/main/utils/vanilla.ts | 19 +++++++++++++------ src/react/hooks/useLightningAuth.ts | 8 ++++---- src/react/utils/api.ts | 15 ++++++++++----- src/react/utils/query.ts | 6 +++--- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/TODO.md b/TODO.md index d6dbb49..0d153bf 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - add JSDocs comments to functions / hooks etc - open PR on `next-auth` - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc -- consider standardizing APIs so they're all either POST or GET ### Tertiary diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 94d1d59..09d8a5c 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -12,11 +12,11 @@ import { formatRouter } from "../utils/router.js"; import { paramsToObject } from "../utils/params.js"; async function logic( - query: Record, + body: Record, req: NextApiRequest | NextRequest, config: Config ) { - const { state } = createValidation.parse(query, { errorMap }); + const { state } = createValidation.parse(body, { errorMap }); const k1 = randomBytes(32).toString("hex"); @@ -46,7 +46,7 @@ async function pagesHandler( if (req.cookies["next-auth.session-token"]) { throw new Error("You are already logged in"); } - const result = await logic(req.query, req, config); + const result = await logic(req.body, req, config); res.send(JSON.stringify(result)); } catch (e: any) { @@ -59,9 +59,11 @@ async function appHandler(req: NextRequest, config: Config) { throw new Error("You are already logged in"); } - const query = paramsToObject(req.nextUrl.searchParams); + const text = await req.text(); + const params = new URLSearchParams(text); + const body = paramsToObject(params); - const result = await logic(query, req, config); + const result = await logic(body, req, config); return Response.json(result); } diff --git a/src/main/handlers/poll.ts b/src/main/handlers/poll.ts index 8c897fc..d36fa50 100644 --- a/src/main/handlers/poll.ts +++ b/src/main/handlers/poll.ts @@ -8,6 +8,7 @@ import { } from "../validation/lnauth.js"; import { Config } from "../config/index.js"; import { formatRouter } from "../utils/router.js"; +import { paramsToObject } from "../utils/params.js"; async function logic( body: Record, @@ -39,7 +40,9 @@ async function pagesHandler( } async function appHandler(req: NextRequest, config: Config) { - const body = await req.json(); + const text = await req.text(); + const params = new URLSearchParams(text); + const body = paramsToObject(params); const result = await logic(body, req, config); diff --git a/src/main/utils/params.ts b/src/main/utils/params.ts index bab0a83..70186de 100644 --- a/src/main/utils/params.ts +++ b/src/main/utils/params.ts @@ -1,5 +1,5 @@ -export function paramsToObject(searchParams: URLSearchParams) { - const entries = searchParams.entries(); +export function paramsToObject(params: URLSearchParams) { + const entries = params.entries(); const result: Record = {}; for (const [key, value] of entries) { result[key] = value; diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 2a7f8c8..c9dd334 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -33,12 +33,14 @@ export const vanilla = function ({ if (!data || !data.k1) return; const k1 = data.k1; + const params = new URLSearchParams({ k1 }); + return fetch(hardConfig.apis.poll, { method: "POST", headers: { - "Content-Type": "application/json", + "content-type": "application/x-www-form-urlencoded", }, - body: JSON.stringify({ k1 }), + body: params, cache: "default", }) .then(function (r) { @@ -69,11 +71,16 @@ export const vanilla = function ({ // create a new lnurl and inject content into dom function create() { - const params = new URLSearchParams({ - state: query.state, - }); + const params = new URLSearchParams({ state: query.state }); - return fetch(`http://localhost:3000/api/lnauth/create?${params.toString()}`) + return fetch("http://localhost:3000/api/lnauth/create", { + method: "POST", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + body: params, + cache: "default", + }) .then(function (r) { return r.json(); }) diff --git a/src/react/hooks/useLightningAuth.ts b/src/react/hooks/useLightningAuth.ts index 5e6d102..757eaa8 100644 --- a/src/react/hooks/useLightningAuth.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -34,11 +34,11 @@ export function useLightningAuth({ // redirect user to error page if something goes wrong const error = (e: any) => { - const searchParams = new URLSearchParams(); - searchParams.append("error", "OAuthSignin"); - if (e?.message) searchParams.append("message", e.message); + const params = new URLSearchParams(); + params.append("error", "OAuthSignin"); + if (e?.message) params.append("message", e.message); window.location.replace( - (errorUri || "/api/auth/signin") + "?" + searchParams.toString() + (errorUri || "/api/auth/signin") + "?" + params.toString() ); }; diff --git a/src/react/utils/api.ts b/src/react/utils/api.ts index 5b02696..487255b 100644 --- a/src/react/utils/api.ts +++ b/src/react/utils/api.ts @@ -4,13 +4,15 @@ export const pollApiRequest = (function () { var networkRequestCount: number = 0; return async function (k1: string): Promise { + const params = new URLSearchParams({ k1 }); + return new Promise((resolve, reject) => { fetch(hardConfig.apis.poll, { method: "POST", headers: { - "Content-Type": "application/json", + "content-type": "application/x-www-form-urlencoded", }, - body: JSON.stringify({ k1 }), + body: params, cache: "default", }) .then(function (r) { @@ -39,12 +41,15 @@ export const pollApiRequest = (function () { })(); export async function createApiRequest(state: string): Promise { - const searchParams = new URLSearchParams({ state }); + const params = new URLSearchParams({ state }); + return new Promise((resolve, reject) => { - fetch(`${hardConfig.apis.create}?${searchParams.toString()}`, { + fetch(hardConfig.apis.create, { + method: "POST", headers: { - "Content-Type": "application/json", + "content-type": "application/x-www-form-urlencoded", }, + body: params, cache: "default", }) .then(function (r) { diff --git a/src/react/utils/query.ts b/src/react/utils/query.ts index 5d01ccb..9de8f80 100644 --- a/src/react/utils/query.ts +++ b/src/react/utils/query.ts @@ -7,9 +7,9 @@ export function extractQuery(query: any) { return { redirectUri, state }; } -export function extractSearchParams(searchParams: URLSearchParams) { - let redirectUri = searchParams.get("redirect_uri"); - let state = searchParams.get("state"); +export function extractSearchParams(params: URLSearchParams) { + let redirectUri = params.get("redirect_uri"); + let state = params.get("state"); if (Array.isArray(redirectUri)) redirectUri = redirectUri[0]; if (Array.isArray(state)) state = state[0]; From b0c0fe1e242d9ec7396e857e1d671d5b247083ac Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 12:43:44 +0100 Subject: [PATCH 27/49] aborting inflight api requests when hook unmounts or a poll request was returned successfull --- TODO.md | 1 - src/react/hooks/useLightningAuth.ts | 16 ++++++++++++---- src/react/utils/api.ts | 9 +++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index 0d153bf..60d397e 100644 --- a/TODO.md +++ b/TODO.md @@ -18,7 +18,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Tertiary -- cancel inflight api requests if hook unmounts - consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated - add more example repos - add `auto` color scheme that uses browsers dark/light settings diff --git a/src/react/hooks/useLightningAuth.ts b/src/react/hooks/useLightningAuth.ts index 757eaa8..e7a3015 100644 --- a/src/react/hooks/useLightningAuth.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -25,11 +25,15 @@ export function useLightningAuth({ let data: { k1?: string; lnurl?: string } | null; let pollTimeoutId: NodeJS.Timeout | undefined; let createIntervalId: NodeJS.Timeout | undefined; + const pollController = new AbortController(); + const createController = new AbortController(); // cleanup when the hook unmounts of polling is successful const cleanup = () => { clearTimeout(pollTimeoutId); clearInterval(createIntervalId); + pollController.abort(); + createController.abort(); }; // redirect user to error page if something goes wrong @@ -47,7 +51,7 @@ export function useLightningAuth({ const k1 = data?.k1; try { if (k1) { - const { success } = await pollApiRequest(k1); + const { success } = await pollApiRequest(k1, pollController.signal); if (success) { cleanup(); let url = new URL(redirectUri); @@ -58,17 +62,21 @@ export function useLightningAuth({ } pollTimeoutId = setTimeout(poll, hardConfig.intervals.poll); } catch (e: any) { - error(e); + if (!createController.signal.aborted) { + error(e); + } } }; // create a new lnurl and set it to state const create = async () => { try { - data = await createApiRequest(state); + data = await createApiRequest(state, createController.signal); setUrl(data?.lnurl || null); } catch (e: any) { - error(e); + if (!createController.signal.aborted) { + error(e); + } } }; diff --git a/src/react/utils/api.ts b/src/react/utils/api.ts index 487255b..c457408 100644 --- a/src/react/utils/api.ts +++ b/src/react/utils/api.ts @@ -3,7 +3,7 @@ import { hardConfig } from "../../main/config/hard.js"; export const pollApiRequest = (function () { var networkRequestCount: number = 0; - return async function (k1: string): Promise { + return async function (k1: string, signal?: AbortSignal): Promise { const params = new URLSearchParams({ k1 }); return new Promise((resolve, reject) => { @@ -14,6 +14,7 @@ export const pollApiRequest = (function () { }, body: params, cache: "default", + signal, }) .then(function (r) { return r.json(); @@ -40,7 +41,10 @@ export const pollApiRequest = (function () { }; })(); -export async function createApiRequest(state: string): Promise { +export async function createApiRequest( + state: string, + signal?: AbortSignal +): Promise { const params = new URLSearchParams({ state }); return new Promise((resolve, reject) => { @@ -51,6 +55,7 @@ export async function createApiRequest(state: string): Promise { }, body: params, cache: "default", + signal, }) .then(function (r) { return r.json(); From c9383bc67aa52e65de6d0d783d5ab9703884e587 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 15:54:21 +0100 Subject: [PATCH 28/49] removing query/param utils and moving code inline into the handlers because it's simple enough not to need utils --- examples/login-page/pages/login.tsx | 8 +++++--- src/main/handlers/login.tsx | 13 ++++++++----- src/react/components/Button.tsx | 4 ++-- src/react/components/QrCode.tsx | 4 ++-- src/react/hooks/useLightningAuth.ts | 4 ++-- src/react/index.ts | 3 --- src/react/utils/lnurl.ts | 2 +- src/react/utils/query.test.ts | 26 -------------------------- src/react/utils/query.ts | 18 ------------------ 9 files changed, 20 insertions(+), 62 deletions(-) delete mode 100644 src/react/utils/query.test.ts delete mode 100644 src/react/utils/query.ts diff --git a/examples/login-page/pages/login.tsx b/examples/login-page/pages/login.tsx index ac35b1d..105d30b 100644 --- a/examples/login-page/pages/login.tsx +++ b/examples/login-page/pages/login.tsx @@ -1,6 +1,5 @@ import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; -import { extractQuery } from "next-auth-lightning-provider/react"; import LightningLogin from "@/components/LightningLogin"; @@ -8,7 +7,7 @@ export default function LoginPage() { const { isReady, query } = useRouter(); const session = useSession(); - const { redirectUri, state } = extractQuery(query); + const { redirect_uri: redirectUri, state } = query; if (!isReady || session.status === "loading") { return ( @@ -34,7 +33,10 @@ export default function LoginPage() { return (
- +
); } diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 17e069b..0ca8dac 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -7,7 +7,6 @@ import { vanilla } from "../utils/vanilla.js"; import { LnAuthLogin } from "../../react/components/LnAuthLogin.js"; import { Loading } from "../../react/components/Loading.js"; -import { extractQuery, extractSearchParams } from "../../react/utils/query.js"; import { formatRouter } from "../utils/router.js"; function AuthPage({ config }: { config: Config }) { @@ -153,8 +152,10 @@ async function pagesHandler( throw new Error("You are already logged in"); } - const query = extractQuery(req.query); - + const query = { + redirectUri: req.query.redirect_uri, + state: req.query.state, + }; const result = await logic(query, req, config); res.setHeader("content-type", "text/html"); @@ -166,8 +167,10 @@ async function appHandler(req: NextRequest, config: Config) { throw new Error("You are already logged in"); } - const query = extractSearchParams(req.nextUrl.searchParams); - + const query = { + redirectUri: req.nextUrl.searchParams.get("redirect_uri"), + state: req.nextUrl.searchParams.get("state"), + }; const result = await logic(query, req, config); return new Response(result, { diff --git a/src/react/components/Button.tsx b/src/react/components/Button.tsx index 66800a5..9007250 100644 --- a/src/react/components/Button.tsx +++ b/src/react/components/Button.tsx @@ -1,6 +1,6 @@ import { HTMLAttributes } from "preact/compat"; -import { formatLnAuth } from "../utils/lnurl.js"; +import { formatLightningAuth } from "../utils/lnurl.js"; import { hardConfig } from "../../main/config/hard.js"; export function Button({ @@ -9,7 +9,7 @@ export function Button({ }: { lnurl: string; } & HTMLAttributes) { - const { button } = formatLnAuth(lnurl); + const { button } = formatLightningAuth(lnurl); return ( diff --git a/src/react/components/QrCode.tsx b/src/react/components/QrCode.tsx index 66abbf0..fa61694 100644 --- a/src/react/components/QrCode.tsx +++ b/src/react/components/QrCode.tsx @@ -1,6 +1,6 @@ import { HTMLAttributes } from "preact/compat"; -import { formatLnAuth } from "../utils/lnurl.js"; +import { formatLightningAuth } from "../utils/lnurl.js"; import { hardConfig } from "../../main/config/hard.js"; export function QrCode({ @@ -9,7 +9,7 @@ export function QrCode({ }: { lnurl: string; } & HTMLAttributes) { - const { qr } = formatLnAuth(lnurl); + const { qr } = formatLightningAuth(lnurl); return ( cleanup(); }, []); - const { qr, button } = formatLnAuth(lnurl); + const { qr, button } = formatLightningAuth(lnurl); return { lnurl, qr, button }; } diff --git a/src/react/index.ts b/src/react/index.ts index 0a22502..892b8a0 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -1,5 +1,2 @@ // hooks export * from "./hooks/useLightningAuth.js"; - -// utils -export * from "./utils/query.js"; diff --git a/src/react/utils/lnurl.ts b/src/react/utils/lnurl.ts index d4d9549..6bdf2d2 100644 --- a/src/react/utils/lnurl.ts +++ b/src/react/utils/lnurl.ts @@ -1,6 +1,6 @@ import { hardConfig } from "../../main/config/hard.js"; -export function formatLnAuth(lnurl?: string | null) { +export function formatLightningAuth(lnurl?: string | null) { if (!lnurl) { return { lnurl, qr: "", button: "" }; } diff --git a/src/react/utils/query.test.ts b/src/react/utils/query.test.ts deleted file mode 100644 index 4240a78..0000000 --- a/src/react/utils/query.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, test } from "@jest/globals"; - -import { extractQuery } from "./query"; - -describe("extractQuery", () => { - test("that undefined values are returned", () => { - const query = {}; - const output = extractQuery(query); - const expected = { redirectUri: undefined, state: undefined }; - expect(output).toEqual(expected); - }); - - test("that strings are returned", () => { - const query = { redirect_uri: "uri", state: "s" }; - const output = extractQuery(query); - const expected = { redirectUri: "uri", state: "s" }; - expect(output).toEqual(expected); - }); - - test("that the first item in the array is returned", () => { - const query = { redirect_uri: ["0", "1", "2"], state: ["a", "b", "c"] }; - const output = extractQuery(query); - const expected = { redirectUri: "0", state: "a" }; - expect(output).toEqual(expected); - }); -}); diff --git a/src/react/utils/query.ts b/src/react/utils/query.ts deleted file mode 100644 index 9de8f80..0000000 --- a/src/react/utils/query.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function extractQuery(query: any) { - let { redirect_uri: redirectUri, state } = query; - - if (Array.isArray(redirectUri)) redirectUri = redirectUri[0]; - if (Array.isArray(state)) state = state[0]; - - return { redirectUri, state }; -} - -export function extractSearchParams(params: URLSearchParams) { - let redirectUri = params.get("redirect_uri"); - let state = params.get("state"); - - if (Array.isArray(redirectUri)) redirectUri = redirectUri[0]; - if (Array.isArray(state)) state = state[0]; - - return { redirectUri, state }; -} From 0dbefa5c5490c7ca552ff736dd4128c70278b1c0 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 16:16:56 +0100 Subject: [PATCH 29/49] adding default values to readme config section --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3118e53..bafe1e7 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ const config: NextAuthLightningConfig = { }, /** - * @param {function} qr.generateQr + * @param {function} generateQr * * Define the QR code generator function. * It must return a base64 encoded png/jpg OR svg XML markup. @@ -267,8 +267,10 @@ const config: NextAuthLightningConfig = { * you can configure a custom Next.js page and customize the UI. * * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page/ + * + * @default "/api/lnauth/login" */ - signIn: "/login" + signIn: "/example-custom-login" /** * @param {string} error @@ -276,8 +278,10 @@ const config: NextAuthLightningConfig = { * By default users will be redirected to the `next-auth` login page * and shown an error message there. If you want a custom error page, * you can define the path here. + * + * @default "/api/auth/signin" */ - error: "/error" + error: "/example-custom-error" }, /** @@ -285,8 +289,10 @@ const config: NextAuthLightningConfig = { * * Override the default title shown above the QR code in the * Lighting Login page. Or, it can be set to null to hide the title. + * + * @default "Login with Lightning" */ - title: "Lightning Login", + title: "Your custom title", /** * @param {function | null} generateAvatar @@ -300,6 +306,8 @@ const config: NextAuthLightningConfig = { * * The default avatar generation library that's used is dicebear's bottts style. * @see https://www.dicebear.com/styles/bottts/ + * + * @default null */ async generateAvatar(data, config) { return { @@ -322,6 +330,8 @@ const config: NextAuthLightningConfig = { * * The default name generation library used is `unique-names-generator` * @see https://www.npmjs.com/package/unique-names-generator + * + * @default null */ async generateName(seed) { return { @@ -337,6 +347,8 @@ const config: NextAuthLightningConfig = { * @param {string} colorScheme * * Define a color scheme for the "Login with Lightning" UI. + * + * @default "light" */ colorScheme: "dark" | "light"; @@ -344,59 +356,84 @@ const config: NextAuthLightningConfig = { * @param {string} background * * Override the theme's background color. + * + * @default light "#ececec" + * @default dark "#161b22" */ - background: "#ececec", + background: "#00ffff", /** * @param {string} backgroundCard * * Override the theme's main content card background color. + * + * @default light "#ffffff" + * @default dark "#0d1117" */ - backgroundCard: "#ffffff", + backgroundCard: "#ffff00", /** * @param {string} text * * Override the theme's main text color. + * + * @default light "#000000" + * @default dark "#ffffff" */ - text: "#000000", + text: "#0000ff", /** * @param {object} color * * Override the theme's background color. + * + * @default light "#0d1117" + * @default dark "#ffffff" */ - qrBackground: "#ffffff", + qrBackground: "#ff0000", /** * @param {object} color * * Override the theme's QR code foreground color. + * + * @default light "#ffffff" + * @default dark "#0d1117" */ - qrForeground: "#000000", + qrForeground: "#0000ff", /** * @param {number} margin * * Override the theme's QR code margin value. + * Scale factor. A value of `1` means 1px per modules (black dots). + * + * @default light 0 + * @default dark 0.5 */ - qrMargin: 2, + qrMargin: 1, /** * @param {string} loginButtonBackground * * Override the theme's button background color. This is the button that's shown in the * `next-auth` login screen alongside your other providers. + * + * @default light "#24292f" + * @default dark "#24292f" */ - loginButtonBackground: "#24292f", + loginButtonBackground: "#00ff00", /** * @param {string} loginButtonText * * Override the theme's button text color. This is the button that's shown in the * `next-auth` login screen alongside your other providers. + * + * @default light "#ffffff" + * @default dark "#ffffff" */ - loginButtonText: "#ffffff", + loginButtonText: "#ff00ff", }, }; From 34f96e53d7749acdce567d204ede19a2ee23f4be Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 16:20:15 +0100 Subject: [PATCH 30/49] simplifying qr generator config options --- README.md | 4 ++-- TODO.md | 2 +- src/generators/qr.ts | 26 +------------------------- src/main/config/default.ts | 8 ++++---- src/main/utils/vanilla.ts | 8 ++------ src/react/utils/api.ts | 8 ++------ 6 files changed, 12 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index bafe1e7..4c2ba90 100644 --- a/README.md +++ b/README.md @@ -387,8 +387,8 @@ const config: NextAuthLightningConfig = { * * Override the theme's background color. * - * @default light "#0d1117" - * @default dark "#ffffff" + * @default light "#ffffff" + * @default dark "#0d1117" */ qrBackground: "#ff0000", diff --git a/TODO.md b/TODO.md index 60d397e..cb915a8 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Primary -- support `next-auth@4` and `next-auth@5` +- Once `next-auth@v5` is out of beta, ensure it's supported. - investigate CSRF for next-auth ### Secondary diff --git a/src/generators/qr.ts b/src/generators/qr.ts index 0d88e0e..a4df4f1 100644 --- a/src/generators/qr.ts +++ b/src/generators/qr.ts @@ -1,30 +1,9 @@ -import merge from "lodash.merge"; - import QRCode from "qrcode"; import { QRGenerator } from "../main/config/types.js"; export const generateQr: QRGenerator = async (data, config) => { - // generic preset theme options - const themeOptions = - config.theme.colorScheme === "dark" - ? { - margin: 0.5, - color: { - dark: config.theme.background, - light: config.theme.text, - }, - } - : { - margin: 0, - color: { - dark: config.theme.text, - light: config.theme.background, - }, - }; - - // qr specific option overrides - const qrOptions: any = { + const options: any = { color: { dark: config.theme.qrForeground, light: config.theme.qrBackground, @@ -32,9 +11,6 @@ export const generateQr: QRGenerator = async (data, config) => { margin: config.theme.qrMargin, }; - // merge options, prioritize explicit qrOptions - const options = merge(themeOptions, qrOptions); - return { data: (await QRCode.toString(data, { ...options, diff --git a/src/main/config/default.ts b/src/main/config/default.ts index 8779f22..be335eb 100644 --- a/src/main/config/default.ts +++ b/src/main/config/default.ts @@ -8,9 +8,9 @@ const colorSchemeLight: ThemeStyles = { background: "#ececec", backgroundCard: "#ffffff", text: "#000000", - qrBackground: "#0d1117", - qrForeground: "#ffffff", - qrMargin: 1, + qrBackground: "#ffffff", + qrForeground: "#0d1117", + qrMargin: 0, loginButtonBackground: "#24292f", loginButtonText: "#ffffff", }; @@ -21,7 +21,7 @@ const colorSchemeDark: ThemeStyles = { text: "#ffffff", qrBackground: "#ffffff", qrForeground: "#0d1117", - qrMargin: 1, + qrMargin: 0.5, loginButtonBackground: "#24292f", loginButtonText: "#ffffff", }; diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index c9dd334..4a19764 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -37,9 +37,7 @@ export const vanilla = function ({ return fetch(hardConfig.apis.poll, { method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded", - }, + headers: { "content-type": "application/x-www-form-urlencoded" }, body: params, cache: "default", }) @@ -75,9 +73,7 @@ export const vanilla = function ({ return fetch("http://localhost:3000/api/lnauth/create", { method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded", - }, + headers: { "content-type": "application/x-www-form-urlencoded" }, body: params, cache: "default", }) diff --git a/src/react/utils/api.ts b/src/react/utils/api.ts index c457408..6636088 100644 --- a/src/react/utils/api.ts +++ b/src/react/utils/api.ts @@ -9,9 +9,7 @@ export const pollApiRequest = (function () { return new Promise((resolve, reject) => { fetch(hardConfig.apis.poll, { method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded", - }, + headers: { "content-type": "application/x-www-form-urlencoded" }, body: params, cache: "default", signal, @@ -50,9 +48,7 @@ export async function createApiRequest( return new Promise((resolve, reject) => { fetch(hardConfig.apis.create, { method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded", - }, + headers: { "content-type": "application/x-www-form-urlencoded" }, body: params, cache: "default", signal, From ecf6b8bb6d4448cd75376eaf1b5330b6818a3229 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:06:46 +0000 Subject: [PATCH 31/49] reorganising todo list --- TODO.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index cb915a8..13746ca 100644 --- a/TODO.md +++ b/TODO.md @@ -9,23 +9,26 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Secondary +- error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - carefully run through the auth and data flow to look for bugs or oversights -- add jest tests for all utils -- consider how to clean up old and unused lnauth session data that was created but never reached success state +- add jest tests where applicable - add JSDocs comments to functions / hooks etc - open PR on `next-auth` -- error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc ### Tertiary -- consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated - add more example repos -- add `auto` color scheme that uses browsers dark/light settings -- consider adding various styles of avatar and name generators -- see if TS generics can be used for NextRequest/NextApiRequest etc ### Readme - add yellow notes to diagram. - carefully scan for errors and typos - ensure consistent formatting is used. full stops, caps, etc +- add suggestion: cleaning up old and unused lnauth session data that was created but never reached success state. + +### back-burner + +- add `auto` color scheme that uses browsers dark/light settings +- see if TS generics can be used for NextRequest/NextApiRequest etc +- consider adding various styles of avatar and name generators +- consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated From 946573aae5278cd47db3e10f7095d3ec493d7f41 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:21:43 +0000 Subject: [PATCH 32/49] adding JSDocs comment to hook --- src/react/hooks/useLightningAuth.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/react/hooks/useLightningAuth.ts b/src/react/hooks/useLightningAuth.ts index 82ab0bf..4972134 100644 --- a/src/react/hooks/useLightningAuth.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -6,14 +6,29 @@ import { createApiRequest, pollApiRequest } from "../utils/api.js"; import { hardConfig } from "../../main/config/hard.js"; import { formatLightningAuth } from "../utils/lnurl.js"; +/** + * A React hook that, on mount, will trigger an API request and create a new Lightning login session. + * Thereafter, it'll poll the API and checks if the Lightning auth QR has been scanned. + * If enough time elapses without a login attempt, the Lightning login session will be refreshed. + * Once a success status is received from polling, the user will be redirected to the `redirectUri`. + * + * @param {String} redirectUri - the `redirect_uri` query param generated by `next-auth` + * @param {String} state - the `state` query param generated by `next-auth` + * @param {String} errorUri - a path or url which the user should be redirected to if there's an error - optional + * + * @returns {Object} + * @returns {String} lnurl - the raw LNURL, should be made available for copy-pasting + * @returns {String} qr - a url pointing the lnurl-auth QR Code image, should be used in the src prop of img tags + * @returns {String} button - a deep-link that will open in Lightning enabled wallets, should be used in the href prop of anchor tags + */ export function useLightningAuth({ redirectUri, - errorUri, state, + errorUri, }: { redirectUri: string; - errorUri?: string; state: string; + errorUri?: string; }): { lnurl: string | null; qr: string; From aa03bf631cfdee319f9c7ef363c01aca5f6e41ab Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:29:51 +0000 Subject: [PATCH 33/49] optionally pass old k1 to `/create` endpoint and delete it. --- src/main/handlers/create.ts | 5 +++++ src/main/utils/vanilla.ts | 3 +++ src/react/hooks/useLightningAuth.ts | 10 ++++++++-- src/react/utils/api.ts | 10 ++++++++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 09d8a5c..8624ecf 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -18,6 +18,11 @@ async function logic( ) { const { state } = createValidation.parse(body, { errorMap }); + // if an old k1 is provided, delete it + if (body.k1) { + await config.storage.delete({ k1: body.k1 }, req); + } + const k1 = randomBytes(32).toString("hex"); let inputUrl = new URL(config.siteUrl + config.apis.callback); diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 4a19764..01941b1 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -70,6 +70,9 @@ export const vanilla = function ({ // create a new lnurl and inject content into dom function create() { const params = new URLSearchParams({ state: query.state }); + if (data?.k1) { + params.append("k1", data.k1); + } return fetch("http://localhost:3000/api/lnauth/create", { method: "POST", diff --git a/src/react/hooks/useLightningAuth.ts b/src/react/hooks/useLightningAuth.ts index 4972134..53277f0 100644 --- a/src/react/hooks/useLightningAuth.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -66,7 +66,10 @@ export function useLightningAuth({ const k1 = data?.k1; try { if (k1) { - const { success } = await pollApiRequest(k1, pollController.signal); + const { success } = await pollApiRequest( + { k1 }, + pollController.signal + ); if (success) { cleanup(); let url = new URL(redirectUri); @@ -86,7 +89,10 @@ export function useLightningAuth({ // create a new lnurl and set it to state const create = async () => { try { - data = await createApiRequest(state, createController.signal); + data = await createApiRequest( + { state, k1: data?.k1 }, + createController.signal + ); setUrl(data?.lnurl || null); } catch (e: any) { if (!createController.signal.aborted) { diff --git a/src/react/utils/api.ts b/src/react/utils/api.ts index 6636088..3848a43 100644 --- a/src/react/utils/api.ts +++ b/src/react/utils/api.ts @@ -3,7 +3,10 @@ import { hardConfig } from "../../main/config/hard.js"; export const pollApiRequest = (function () { var networkRequestCount: number = 0; - return async function (k1: string, signal?: AbortSignal): Promise { + return async function ( + { k1 }: { k1: string }, + signal?: AbortSignal + ): Promise { const params = new URLSearchParams({ k1 }); return new Promise((resolve, reject) => { @@ -40,10 +43,13 @@ export const pollApiRequest = (function () { })(); export async function createApiRequest( - state: string, + { state, k1 }: { state: string; k1?: string }, signal?: AbortSignal ): Promise { const params = new URLSearchParams({ state }); + if (k1) { + params.append("k1", k1); + } return new Promise((resolve, reject) => { fetch(hardConfig.apis.create, { From e1550722f2f3f3d80d1251a8177f81e00e6c8094 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:44:52 +0000 Subject: [PATCH 34/49] adding JSDoc comment block to all the generators --- src/generators/avatar.ts | 10 ++++++++++ src/generators/name.ts | 9 +++++++++ src/generators/qr.ts | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/generators/avatar.ts b/src/generators/avatar.ts index e56306e..bc52ed0 100644 --- a/src/generators/avatar.ts +++ b/src/generators/avatar.ts @@ -3,6 +3,16 @@ import { bottts } from "@dicebear/collection"; import { AvatarGenerator } from "../main/config/types.js"; +/** + * An async function that generates a user avatar. + * + * @param {String} seed - the seed (the users pubkey) + * @param {String} config - the `next-auth-lightning-provider` config object + * + * @returns {Object} + * @returns {String} data - a base64 encoded png/jpg OR svg XML markup + * @returns {String} type - image type: "svg" | "png" | "jpg" + */ export const generateAvatar: AvatarGenerator = async (seed, config) => { return { data: createAvatar(bottts, { seed }).toString(), diff --git a/src/generators/name.ts b/src/generators/name.ts index 4979b7b..f6146ee 100644 --- a/src/generators/name.ts +++ b/src/generators/name.ts @@ -7,6 +7,15 @@ import { import { NameGenerator } from "../main/config/types.js"; +/** + * An async function that generates a username. + * + * @param {String} seed - the seed (the users pubkey) + * @param {String} config - the `next-auth-lightning-provider` config object + * + * @returns {Object} + * @returns {String} name - a deterministically generated username + */ export const generateName: NameGenerator = async (seed, config) => { return { name: uniqueNamesGenerator({ diff --git a/src/generators/qr.ts b/src/generators/qr.ts index a4df4f1..1cbbdd3 100644 --- a/src/generators/qr.ts +++ b/src/generators/qr.ts @@ -2,6 +2,16 @@ import QRCode from "qrcode"; import { QRGenerator } from "../main/config/types.js"; +/** + * An async function that generates a QR code. + * + * @param {String} data - data to be made into a QR code + * @param {String} config - the `next-auth-lightning-provider` config object + * + * @returns {Object} + * @returns {String} data - a base64 encoded png/jpg OR svg XML markup + * @returns {String} type - image type: "svg" | "png" | "jpg" + */ export const generateQr: QRGenerator = async (data, config) => { const options: any = { color: { From e6ad3cfba57a7738f578c17326a399d9bcbeae1e Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:49:04 +0000 Subject: [PATCH 35/49] adding JSDoc comment block to NextAuthLightning --- src/main/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index 38d43ae..db83248 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -18,6 +18,15 @@ import { formatConfig, UserConfig } from "./config/index.js"; import { NextRequest, NextResponse } from "next/server"; import { formatRouter } from "./utils/router.js"; +/** + * Generate a provider and handler to setup Lightning auth. + * + * @param {Object} userConfig - config options, see the package README for details + * + * @returns {Object} + * @returns {String} provider - a provider that can be added to the `next-auth` config's providerArray + * @returns {String} handler - an API handler to be exported in the pages/api/lnauth/[...lnauth] folder + */ export default function NextAuthLightning(userConfig: UserConfig) { const config = formatConfig(userConfig); From 066c3a6a89bf5f08c35738659059910d672a9a87 Mon Sep 17 00:00:00 2001 From: jowo Date: Mon, 20 Nov 2023 19:53:10 +0000 Subject: [PATCH 36/49] updating readme --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 13746ca..c461da1 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc - carefully run through the auth and data flow to look for bugs or oversights - add jest tests where applicable -- add JSDocs comments to functions / hooks etc - open PR on `next-auth` ### Tertiary @@ -32,3 +31,4 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - see if TS generics can be used for NextRequest/NextApiRequest etc - consider adding various styles of avatar and name generators - consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated +- add JSDocs comments to all internally used functions From 5bdc4dd151b665847a0c68a059a21f00a7c81263 Mon Sep 17 00:00:00 2001 From: jowo Date: Tue, 21 Nov 2023 13:29:54 +0000 Subject: [PATCH 37/49] large refactor of handlers, removing the individual appRouter and pagesRouter functions that were in each handler and making them into a util that can be configured for each route. this will make things more reliable and less likely for bugs to slip into individual handlers --- TODO.md | 3 + src/main/config/types.ts | 20 ++-- src/main/handlers/avatar.ts | 60 +++-------- src/main/handlers/callback.ts | 76 ++++---------- src/main/handlers/create.ts | 83 ++++----------- src/main/handlers/login.tsx | 115 +++++++++------------ src/main/handlers/poll.ts | 71 +++---------- src/main/handlers/qr.ts | 63 +++--------- src/main/handlers/token.ts | 117 +++++---------------- src/main/index.ts | 23 ++--- src/main/utils/handlers.ts | 184 ++++++++++++++++++++++++++++++++++ src/main/utils/params.ts | 27 ++++- src/main/utils/vanilla.ts | 4 +- src/main/validation/lnauth.ts | 13 ++- 14 files changed, 395 insertions(+), 464 deletions(-) create mode 100644 src/main/utils/handlers.ts diff --git a/TODO.md b/TODO.md index c461da1..4ab4ea0 100644 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,9 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - carefully run through the auth and data flow to look for bugs or oversights - add jest tests where applicable - open PR on `next-auth` +- rename login to signup everywhere (so it matches `next-auth`) +- manual testing. pre beta release make list of all test criteria and go through them +- consider deleting cookie instead of throwing error "You are already logged in" ### Tertiary diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 577fdea..5a746be 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -69,11 +69,13 @@ export type RequiredConfig = { state: string; }; }, - req: NextApiRequest | NextRequest + path: string, + config: Config ) => Promise; get: ( args: { k1: string }, - req: NextApiRequest | NextRequest + path: string, + config: Config ) => Promise; update: ( args: { @@ -84,11 +86,13 @@ export type RequiredConfig = { success: boolean; }; }, - req: NextApiRequest | NextRequest + path: string, + config: Config ) => Promise; delete: ( args: { k1: string }, - req: NextApiRequest | NextRequest + path: string, + config: Config ) => Promise; }; generateQr: QRGenerator; @@ -106,10 +110,10 @@ export type ThemeStyles = { }; export type OptionalConfig = { - pages: { - signIn?: string; - error?: string; - }; + pages: Partial<{ + signIn: string; + error: string; + }>; title: string | null; generateAvatar: AvatarGenerator | null; generateName: NameGenerator | null; diff --git a/src/main/handlers/avatar.ts b/src/main/handlers/avatar.ts index 7f67938..43c68e2 100644 --- a/src/main/handlers/avatar.ts +++ b/src/main/handlers/avatar.ts @@ -1,8 +1,4 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; - -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; const cacheDuration = 24 * 60 * 60; // 1 day cache duration const base64Regex = /^data:image\/(svg\+xml|png|jpeg|jpg);base64,/; @@ -13,18 +9,18 @@ const fileTypeHeaders = { svg: "image/svg+xml", }; -async function logic( - path: string, - config: Config -): Promise<{ - headers: HeadersInit; - data: string | Buffer; -}> { - if (!config.generateAvatar) throw new Error("Avatars are not enabled"); - if (!path) throw new Error("Invalid url"); +export default async function handler({ + query, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { + if (!config.generateAvatar) return { error: "Avatars are not enabled" }; + if (!path) return { error: "Invalid url" }; const pubkey = path.split("/").slice(-1)[0]; - if (!pubkey) throw new Error("Invalid pubkey"); + if (!pubkey) return { error: "Invalid pubkey" }; const { data, type } = await config.generateAvatar(pubkey, config); @@ -36,7 +32,7 @@ async function logic( "content-length": buffer.length.toString(), "cache-control": `public, max-age=${cacheDuration}`, }, - data: Buffer.from(buffer, "base64"), + response: Buffer.from(buffer, "base64"), }; } else if (type === "svg") { return { @@ -44,39 +40,9 @@ async function logic( "content-type": fileTypeHeaders[type], "cache-control": `public, max-age=${cacheDuration}`, }, - data, + response: data, }; } throw new Error("Something went wrong"); } - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - path: string, - config: Config -) { - const { data, headers } = await logic(path, config); - - Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value)); - res.end(data); -} - -async function appHandler(req: NextRequest, path: string, config: Config) { - const { data, headers } = await logic(path, config); - - return new Response(data, { status: 200, headers }); -} -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, path, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, path, config); - } - return await pagesHandler(req, res, path, config); -} diff --git a/src/main/handlers/callback.ts b/src/main/handlers/callback.ts index 000172f..664dba2 100644 --- a/src/main/handlers/callback.ts +++ b/src/main/handlers/callback.ts @@ -1,21 +1,13 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; - -import { - callbackValidation, - formatErrorMessage, - errorMap, -} from "../validation/lnauth.js"; - -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; -import { paramsToObject } from "../utils/params.js"; - -async function logic( - query: Record, - req: NextApiRequest | NextRequest, - config: Config -) { +import { callbackValidation, errorMap } from "../validation/lnauth.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; + +export default async function handler({ + query, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { const { k1, key: pubkey, @@ -24,53 +16,21 @@ async function logic( const lnurl = require("lnurl"); const authorize = await lnurl.verifyAuthorizationSignature(sig, k1, pubkey); + if (!authorize) { - throw new Error("Error in keys"); + return { error: "Error in keys" }; } await config.storage.update( { k1, data: { pubkey, sig, success: true } }, - req + path, + config ); return { - status: "OK", - success: true, - k1, + response: { + success: true, + k1, + }, }; } - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { - try { - const result = await logic(req.query, req, config); - - res.send(JSON.stringify(result)); - } catch (e: any) { - res.status(500).send(formatErrorMessage(e)); - } -} - -async function appHandler(req: NextRequest, config: Config) { - const query = paramsToObject(req.nextUrl.searchParams); - - const result = await logic(query, req, config); - - return Response.json(result); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, config); - } - return await pagesHandler(req, res, config); -} diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 8624ecf..9fa6715 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -1,26 +1,20 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; import { randomBytes } from "crypto"; -import { - createValidation, - errorMap, - formatErrorMessage, -} from "../validation/lnauth.js"; -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; -import { paramsToObject } from "../utils/params.js"; +import { createValidation, errorMap } from "../validation/lnauth.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; -async function logic( - body: Record, - req: NextApiRequest | NextRequest, - config: Config -) { +export default async function handler({ + body, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { const { state } = createValidation.parse(body, { errorMap }); // if an old k1 is provided, delete it - if (body.k1) { - await config.storage.delete({ k1: body.k1 }, req); + if (typeof body?.k1 === "string") { + await config.storage.delete({ k1: body.k1 }, path, config); } const k1 = randomBytes(32).toString("hex"); @@ -32,56 +26,13 @@ async function logic( const lnurl = require("lnurl"); const encoded = lnurl.encode(inputUrl.toString()).toUpperCase(); - await config.storage.set({ k1, data: { k1, state } }, req); + await config.storage.set({ k1, data: { k1, state } }, path, config); return { - status: "OK", - success: true, - k1, - lnurl: encoded, + response: { + success: true, + k1, + lnurl: encoded, + }, }; } - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { - try { - if (req.cookies["next-auth.session-token"]) { - throw new Error("You are already logged in"); - } - const result = await logic(req.body, req, config); - - res.send(JSON.stringify(result)); - } catch (e: any) { - res.status(500).send(formatErrorMessage(e)); - } -} - -async function appHandler(req: NextRequest, config: Config) { - if (req.cookies.get("next-auth.session-token")?.value) { - throw new Error("You are already logged in"); - } - - const text = await req.text(); - const params = new URLSearchParams(text); - const body = paramsToObject(params); - - const result = await logic(body, req, config); - - return Response.json(result); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, config); - } - return await pagesHandler(req, res, config); -} diff --git a/src/main/handlers/login.tsx b/src/main/handlers/login.tsx index 0ca8dac..dc2cf4f 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/login.tsx @@ -1,13 +1,16 @@ import { renderToStaticMarkup } from "preact-render-to-string"; -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; import { hardConfig, Config } from "../config/index.js"; import { vanilla } from "../utils/vanilla.js"; import { LnAuthLogin } from "../../react/components/LnAuthLogin.js"; import { Loading } from "../../react/components/Loading.js"; -import { formatRouter } from "../utils/router.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; +import { + loginValidation, + errorMap, + formatErrorMessage, +} from "../validation/lnauth.js"; function AuthPage({ config }: { config: Config }) { return ( @@ -99,21 +102,42 @@ function AuthPage({ config }: { config: Config }) { ); } -async function logic( - query: Record, - req: NextApiRequest | NextRequest, - config: Config -) { - const title = config.title || config.siteUrl; - const errorUrl = config.siteUrl + config.pages.error; - - const html = renderToStaticMarkup(); - - if (!query.redirectUri || !query.state) { - throw new Error("Missing required query param"); - } - - return ` +export default async function handler({ + query, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { + try { + try { + loginValidation.parse(query, { errorMap }); + } catch (e: any) { + return { error: formatErrorMessage(e), isRedirect: true }; + } + + if (cookies.sessionToken) { + return { error: "You are already logged in", isRedirect: true }; + } + + // if a custom login page is specified, send them there if they try and access this API + if (config.pages.signIn !== config.apis.signIn) { + const params = url.searchParams.toString(); + return { + redirect: new URL(`${config.siteUrl}${config.pages.signIn}?${params}`), + }; + } + + const title = config.title || config.siteUrl; + const errorUrl = config.siteUrl + config.pages.error; + const html = renderToStaticMarkup(); + + return { + status: 200, + headers: { + "content-type": "text/html", + }, + response: ` @@ -140,56 +164,9 @@ async function logic( init(${JSON.stringify({ hardConfig, query, errorUrl })}) }; - `; -} - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { - if (req.cookies["next-auth.session-token"]) { - throw new Error("You are already logged in"); - } - - const query = { - redirectUri: req.query.redirect_uri, - state: req.query.state, - }; - const result = await logic(query, req, config); - - res.setHeader("content-type", "text/html"); - res.send(result); -} - -async function appHandler(req: NextRequest, config: Config) { - if (req.cookies.get("next-auth.session-token")?.value) { - throw new Error("You are already logged in"); - } - - const query = { - redirectUri: req.nextUrl.searchParams.get("redirect_uri"), - state: req.nextUrl.searchParams.get("state"), - }; - const result = await logic(query, req, config); - - return new Response(result, { - status: 200, - headers: { - "content-type": "text/html", - }, - }); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, config); + `, + }; + } catch (e: any) { + return { error: e.message || "Something went wrong", isRedirect: true }; } - return await pagesHandler(req, res, config); } diff --git a/src/main/handlers/poll.ts b/src/main/handlers/poll.ts index d36fa50..5117c49 100644 --- a/src/main/handlers/poll.ts +++ b/src/main/handlers/poll.ts @@ -1,63 +1,20 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; - -import { - pollValidation, - formatErrorMessage, - errorMap, -} from "../validation/lnauth.js"; -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; -import { paramsToObject } from "../utils/params.js"; - -async function logic( - body: Record, - req: NextApiRequest | NextRequest, - config: Config -) { +import { pollValidation, errorMap } from "../validation/lnauth.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; + +export default async function handler({ + body, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { const { k1 } = pollValidation.parse(body, { errorMap }); - const { success = false } = await config.storage.get({ k1 }, req); + const { success = false } = await config.storage.get({ k1 }, path, config); return { - status: "OK", - success, + response: { + success, + }, }; } - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { - try { - const result = await logic(req.body, req, config); - - res.send(JSON.stringify(result)); - } catch (e: any) { - res.status(500).send(formatErrorMessage(e)); - } -} - -async function appHandler(req: NextRequest, config: Config) { - const text = await req.text(); - const params = new URLSearchParams(text); - const body = paramsToObject(params); - - const result = await logic(body, req, config); - - return Response.json(result); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, config); - } - return await pagesHandler(req, res, config); -} diff --git a/src/main/handlers/qr.ts b/src/main/handlers/qr.ts index a6b16c8..d636257 100644 --- a/src/main/handlers/qr.ts +++ b/src/main/handlers/qr.ts @@ -1,8 +1,4 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; - -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; const cacheDuration = 5 * 60; // short cache duration for the QR since it's short lived const base64Regex = /^data:image\/(png|jpeg|jpg);base64,/; @@ -13,18 +9,18 @@ const fileTypeHeaders = { svg: "image/svg+xml", }; -async function logic( - path: string, - config: Config -): Promise<{ - headers: HeadersInit; - data: string | Buffer; -}> { - if (!config.generateQr) throw new Error("QRs are not enabled"); - if (!path) throw new Error("Invalid url"); +export default async function handler({ + query, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { + if (!config.generateQr) return { error: "QRs are not enabled" }; + if (!path) return { error: "Invalid url" }; const k1 = path.split("/").slice(-1)[0]; - if (!k1) throw new Error("Invalid k1"); + if (!k1) return { error: "Invalid k1" }; const { data, type } = await config.generateQr(`lightning:${k1}`, config); @@ -36,7 +32,7 @@ async function logic( "content-length": buffer.length.toString(), "cache-control": `public, max-age=${cacheDuration}`, }, - data: Buffer.from(buffer, "base64"), + response: Buffer.from(buffer, "base64"), }; } else if (type === "svg") { return { @@ -44,40 +40,9 @@ async function logic( "content-type": fileTypeHeaders[type], "cache-control": `public, max-age=${cacheDuration}`, }, - data, + response: data, }; } - throw new Error("Something went wrong"); -} - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - path: string, - config: Config -) { - const { data, headers } = await logic(path, config); - - Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value)); - res.end(data); -} - -async function appHandler(req: NextRequest, path: string, config: Config) { - const { data, headers } = await logic(path, config); - - return new Response(data, { status: 200, headers }); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, path, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, path, config); - } - return await pagesHandler(req, res, path, config); + return { error: "Something went wrong" }; } diff --git a/src/main/handlers/token.ts b/src/main/handlers/token.ts index 92f2fcb..6a00275 100644 --- a/src/main/handlers/token.ts +++ b/src/main/handlers/token.ts @@ -1,70 +1,41 @@ -import { NextApiRequest, NextApiResponse } from "next/types"; -import { NextRequest, NextResponse } from "next/server"; - -import { - tokenValidation, - formatErrorMessage, - errorMap, -} from "../validation/lnauth.js"; +import { tokenValidation, errorMap } from "../validation/lnauth.js"; import { generateIdToken, generateRefreshToken, verifyRefreshToken, } from "../utils/jwt.js"; -import { Config } from "../config/index.js"; -import { formatRouter } from "../utils/router.js"; -import { paramsToObject } from "../utils/params.js"; - -const handleAuthCode = async function ( - k1: string, - req: NextApiRequest | NextRequest, - config: Config -) { - const { pubkey, success } = await config.storage.get({ k1 }, req); - - if (!success) throw new Error("Login was not successful"); - - await config.storage.delete({ k1 }, req); - - return pubkey; -}; - -const handleRefreshToken = async function ( - refreshToken: string, - req: NextApiRequest | NextRequest, - config: Config -) { - const { pubkey } = await verifyRefreshToken(refreshToken, config); - - return pubkey; -}; - -async function logic( - body: Record, - req: NextApiRequest | NextRequest, - config: Config -) { +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; + +export default async function handler({ + body, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { const { grant_type: grantType, code: k1, refresh_token: refreshToken, } = tokenValidation.parse(body, { errorMap }); - let pubkey; + let pubkey: string; if (grantType === "authorization_code") { - if (!k1) throw new Error("Missing code"); - - pubkey = await handleAuthCode(k1, req, config); + if (!k1) return { error: "Missing code" }; + const data = await config.storage.get({ k1 }, path, config); + if (!data?.success) return { error: "Login was not successful" }; + if (!data?.pubkey) return { error: "Missing pubkey" }; + pubkey = data.pubkey; + await config.storage.delete({ k1 }, path, config); } else if (grantType === "refresh_token") { - if (!refreshToken) throw new Error("Missing refresh token"); - - pubkey = await handleRefreshToken(refreshToken, req, config); + if (!refreshToken) return { error: "Missing refresh token" }; + const data = await verifyRefreshToken(refreshToken, config); + if (!data?.pubkey) return { error: "Missing pubkey" }; + pubkey = data.pubkey; } else { - throw new Error("Invalid grant type"); + return { error: "Invalid grant type" }; } - if (!pubkey) throw new Error("Missing pubkey"); - const token = { // meta token_type: "Bearer", @@ -80,45 +51,9 @@ async function logic( }; return { - status: "OK", - success: true, - ...token, + response: { + success: true, + ...token, + }, }; } - -async function pagesHandler( - req: NextApiRequest, - res: NextApiResponse, - config: Config -) { - try { - const result = await logic(req.body, req, config); - - res.send(JSON.stringify(result)); - } catch (e: any) { - res.status(500).send(formatErrorMessage(e)); - } -} - -async function appHandler(req: NextRequest, config: Config) { - const text = await req.text(); - const searchParams = new URLSearchParams(text); - const body = paramsToObject(searchParams); - - const result = await logic(body, req, config); - - return Response.json(result); -} - -export default async function handler( - request: NextApiRequest | NextRequest, - response: NextApiResponse | NextResponse, - config: Config -) { - const { req, res, routerType } = formatRouter(request, response); - - if (routerType === "APP") { - return await appHandler(req, config); - } - return await pagesHandler(req, res, config); -} diff --git a/src/main/index.ts b/src/main/index.ts index db83248..13ebd1f 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -11,13 +11,15 @@ import tokenHandler from "./handlers/token.js"; import loginHandler from "./handlers/login.js"; // misc -import imageHandler from "./handlers/avatar.js"; +import avatarHandler from "./handlers/avatar.js"; import qrHandler from "./handlers/qr.js"; import { formatConfig, UserConfig } from "./config/index.js"; import { NextRequest, NextResponse } from "next/server"; import { formatRouter } from "./utils/router.js"; +import dynamicHandler from "./utils/handlers.js"; + /** * Generate a provider and handler to setup Lightning auth. * @@ -67,22 +69,19 @@ export default function NextAuthLightning(userConfig: UserConfig) { const { path } = formatRouter(req, res); if (path?.indexOf(config.apis.create) === 0) { - return await createHandler(req, res, config); + return await dynamicHandler(req, res, config, createHandler); } else if (path?.indexOf(config.apis.poll) === 0) { - return await pollHandler(req, res, config); + return await dynamicHandler(req, res, config, pollHandler); } else if (path?.indexOf(config.apis.callback) === 0) { - return await callbackHandler(req, res, config); + return await dynamicHandler(req, res, config, callbackHandler); } else if (path?.indexOf(config.apis.token) === 0) { - return await tokenHandler(req, res, config); - } else if ( - config.pages?.signIn === config.apis.signIn && - path?.indexOf(config.apis.signIn) === 0 - ) { - return await loginHandler(req, res, config); + return await dynamicHandler(req, res, config, tokenHandler); + } else if (path?.indexOf(config.apis.signIn) === 0) { + return await dynamicHandler(req, res, config, loginHandler); } else if (path?.indexOf(config.apis.avatar) === 0) { - return await imageHandler(req, res, config); + return await dynamicHandler(req, res, config, avatarHandler); } else if (path?.indexOf(config.apis.qr) === 0) { - return await qrHandler(req, res, config); + return await dynamicHandler(req, res, config, qrHandler); } throw new Error("Unknown path"); diff --git a/src/main/utils/handlers.ts b/src/main/utils/handlers.ts new file mode 100644 index 0000000..24a6339 --- /dev/null +++ b/src/main/utils/handlers.ts @@ -0,0 +1,184 @@ +import { NextApiRequest, NextApiResponse } from "next/types"; +import { NextRequest, NextResponse } from "next/server"; +import { redirect } from "next/navigation"; + +import { Config } from "../config/index.js"; + +import { formatRouter } from "../utils/router.js"; +import { cleanParams, paramsToObject } from "./params.js"; +import { formatErrorMessage } from "../validation/lnauth.js"; + +export type HandlerArguments = { + query?: Record; + body?: Record; + cookies: { + sessionToken?: string; + }; + path: string; + url: URL; + config: Config; +}; + +export type HandlerReturn = { + // redirect + redirect?: URL; + + // error + isRedirect?: boolean; + error?: string; + + // response + status?: number; + headers?: Record; + response?: + | string + | Buffer + | Record; +}; + +async function pagesHandler( + req: NextApiRequest, + res: NextApiResponse, + path: string, + config: Config, + handler: (args: HandlerArguments) => Promise +) { + const query = cleanParams(req.query); + const body = req.body || {}; + + const url = new URL(config.siteUrl + req.url); + + const args: HandlerArguments = { + query, + body, + cookies: { + sessionToken: req.cookies["next-auth.session-token"], + }, + path, + url, + config, + }; + + let output: HandlerReturn; + try { + output = await handler(args); + } catch (e: any) { + output = { error: formatErrorMessage(e) }; + } + + if (output.error) { + if (output.isRedirect) { + const errorUrl = new URL(config.siteUrl + config.pages.error); + errorUrl.searchParams.append("error", "OAuthSignin"); + errorUrl.searchParams.append("message", output.error); + return res.redirect(errorUrl.toString()).end(); + } else { + Object.entries(output.headers || {}).forEach(([key, value]) => + res.setHeader(key, value) + ); + res.status(output.status || 500); + return res.end(output.error); + } + } + + if (output.redirect) { + return res.redirect(output.redirect.toString()).end(); + } + + if ( + typeof output.response === "string" || + output.response instanceof Buffer + ) { + Object.entries(output.headers || {}).forEach(([key, value]) => + res.setHeader(key, value) + ); + res.status(output.status || 200); + return res.end(output.response); + } else if (typeof output.response === "object") { + Object.entries(output.headers || {}).forEach(([key, value]) => + res.setHeader(key, value) + ); + res.status(output.status || 200); + return res.send(JSON.stringify(output.response)); + } +} + +async function appHandler( + req: NextRequest, + path: string, + config: Config, + handler: (args: HandlerArguments) => Promise +) { + const query = paramsToObject(req.nextUrl.searchParams); + const text = await req.text(); + const params = new URLSearchParams(text); + const body = paramsToObject(params); + + const url = new URL(req.nextUrl); + + const args: HandlerArguments = { + query, + body, + cookies: { + sessionToken: req.cookies.get("next-auth.session-token")?.value, + }, + path, + url, + config, + }; + + let output: HandlerReturn; + try { + output = await handler(args); + } catch (e: any) { + output = { error: formatErrorMessage(e) }; + } + + if (output.error) { + if (output.isRedirect) { + const errorUrl = new URL(config.siteUrl + config.pages.error); + errorUrl.searchParams.append("error", "OAuthSignin"); + errorUrl.searchParams.append("message", output.error); + redirect(errorUrl.toString()); + } else { + return new Response(output.error, { + status: output.status || 500, + headers: output.headers || {}, + }); + } + } + + if (output.redirect) { + redirect(output.redirect.toString()); + } + + if ( + typeof output.response === "string" || + output.response instanceof Buffer + ) { + return new Response(output.response, { + status: output.status || 200, + headers: output.headers || {}, + }); + } else if (typeof output.response === "object") { + return Response.json(output.response, { + status: output.status || 200, + headers: output.headers || {}, + }); + } +} + +export default async function dynamicHandler( + request: NextApiRequest | NextRequest, + response: NextApiResponse | NextResponse, + config: Config, + handler: (args: HandlerArguments) => Promise +) { + const { req, res, path, routerType } = formatRouter(request, response); + + if (routerType === "APP") { + return await appHandler(req, path, config, handler); + } else { + return await pagesHandler(req, res, path, config, handler); + } +} diff --git a/src/main/utils/params.ts b/src/main/utils/params.ts index 70186de..695c796 100644 --- a/src/main/utils/params.ts +++ b/src/main/utils/params.ts @@ -1,8 +1,31 @@ -export function paramsToObject(params: URLSearchParams) { +export function pickFirstQueryParam(queryParam: string | string[] | undefined) { + if (Array.isArray(queryParam)) { + return queryParam[0]; + } + return queryParam; +} + +export function paramsToObject(params?: URLSearchParams) { + if (!params) return {}; + const entries = params.entries(); const result: Record = {}; for (const [key, value] of entries) { - result[key] = value; + result[key] = pickFirstQueryParam(value); } return result; } + +export function cleanParams( + params?: Record +): Record { + if (!params) return {}; + + return Object.entries(params).reduce( + (acc, [key, value]) => ({ + ...acc, + [key]: pickFirstQueryParam(value), + }), + {} + ); +} diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 01941b1..22c6967 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -6,7 +6,7 @@ export const vanilla = function ({ errorUrl, }: { hardConfig: HardConfig; - query: { redirectUri: string; state: string }; + query: { redirect_uri: string; state: string }; errorUrl: string; }) { var data: { k1?: string; lnurl?: string } | null; @@ -58,7 +58,7 @@ export const vanilla = function ({ if (d && d.success) { cleanup(); - let url = new URL(query.redirectUri); + let url = new URL(query.redirect_uri); url.searchParams.append("state", query.state); url.searchParams.append("code", k1); window.location.replace(url); diff --git a/src/main/validation/lnauth.ts b/src/main/validation/lnauth.ts index f79d436..8e5fd25 100644 --- a/src/main/validation/lnauth.ts +++ b/src/main/validation/lnauth.ts @@ -14,6 +14,13 @@ export const createValidation = z.object({ export type CreateValidation = z.infer; +export const loginValidation = z.object({ + state: z.string().min(1), + redirect_uri: z.string().min(1), +}); + +export type LoginValidation = z.infer; + export const pollValidation = z.object({ k1: z.string().min(1), }); @@ -43,10 +50,10 @@ export function errorMap(issue: IssueData) { }; } -export function formatErrorMessage(e: any): { message: string } { +export function formatErrorMessage(e: any): string { console.error(e); - let message; + let message = "Something went wrong"; if (typeof e?.message === "string") { // regular Error type @@ -66,5 +73,5 @@ export function formatErrorMessage(e: any): { message: string } { } } - return { message: message || "Something went wrong" }; + return message; } From b5a0f3d13b7aa71c2419ac0ecdadd3c28b35a611 Mon Sep 17 00:00:00 2001 From: jowo Date: Tue, 21 Nov 2023 14:25:35 +0000 Subject: [PATCH 38/49] breaking down error handling into sublist --- TODO.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 4ab4ea0..b12e9ce 100644 --- a/TODO.md +++ b/TODO.md @@ -9,13 +9,21 @@ Below is a TODO list for further development of `next-auth-lightning-provider` ### Secondary -- error handling: of App Router APIs, of error thrown in `storage.get` and other storage methods, of error at end of API if no paths matched, "You are already logged in" error. etc +- error handling: of + - App Router APIs + - error thrown in `storage.get` and other storage methods + - error at end of API if no paths matched + - "You are already logged in" error, passing. etc + - making error messages user friendly + - adding error http status + - adding custom error code query param + - documenting the error types - carefully run through the auth and data flow to look for bugs or oversights - add jest tests where applicable - open PR on `next-auth` - rename login to signup everywhere (so it matches `next-auth`) - manual testing. pre beta release make list of all test criteria and go through them -- consider deleting cookie instead of throwing error "You are already logged in" +- deploy app to vercel and test remotely ### Tertiary @@ -35,3 +43,4 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - consider adding various styles of avatar and name generators - consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated - add JSDocs comments to all internally used functions +- consider deleting cookie instead of throwing error "You are already logged in" From c726b6c690456aee1eb75804d1314cedaf736254 Mon Sep 17 00:00:00 2001 From: jowo Date: Tue, 21 Nov 2023 19:45:51 +0000 Subject: [PATCH 39/49] renaming various terms. mainly changing "login" to either "signin" or "auth" --- README.md | 28 +++++++++---------- TODO.md | 1 - examples/README.md | 4 +-- .../app/api/lnauth/[...lnauth]/config.ts | 8 +++--- examples/app-router/app/components/Login.tsx | 16 ----------- examples/app-router/app/components/SignIn.tsx | 16 +++++++++++ examples/app-router/app/layout.tsx | 2 +- examples/app-router/app/page.tsx | 4 +-- .../{login-page => auth-page}/.env.example | 0 examples/{login-page => auth-page}/.gitignore | 0 examples/{login-page => auth-page}/README.md | 2 +- .../components/LightningAuth.tsx} | 2 +- examples/{login-page => auth-page}/env.mjs | 0 .../{login-page => auth-page}/next.config.js | 0 .../package-lock.json | 0 .../{login-page => auth-page}/package.json | 0 .../{login-page => auth-page}/pages/_app.tsx | 0 .../pages/api/auth/[...nextauth].ts | 0 .../pages/api/lnauth/[...lnauth].ts | 10 +++---- .../{login-page => auth-page}/pages/index.tsx | 4 +-- .../login.tsx => auth-page/pages/signin.tsx} | 4 +-- .../{login-page => auth-page}/tsconfig.json | 0 examples/drizzle/pages/index.tsx | 4 +-- examples/kv/pages/api/lnauth/[...lnauth].ts | 8 +++--- examples/kv/pages/index.tsx | 4 +-- examples/plain-js/pages/index.jsx | 4 +-- src/index.ts | 2 +- src/main/config/default.ts | 14 +++++----- src/main/config/hard.ts | 2 +- src/main/config/index.ts | 7 ++++- src/main/config/types.ts | 8 +++--- src/main/handlers/{login.tsx => signin.tsx} | 16 +++++------ src/main/handlers/token.ts | 8 +++--- src/main/index.ts | 8 +++--- src/main/utils/vanilla.ts | 2 +- src/main/validation/lnauth.ts | 4 +-- .../{LnAuthLogin.tsx => LightningAuth.tsx} | 2 +- src/react/components/QrCode.tsx | 2 +- src/react/hooks/useLightningAuth.ts | 6 ++-- 39 files changed, 103 insertions(+), 99 deletions(-) delete mode 100644 examples/app-router/app/components/Login.tsx create mode 100644 examples/app-router/app/components/SignIn.tsx rename examples/{login-page => auth-page}/.env.example (100%) rename examples/{login-page => auth-page}/.gitignore (100%) rename examples/{login-page => auth-page}/README.md (92%) rename examples/{login-page/components/LightningLogin.tsx => auth-page/components/LightningAuth.tsx} (97%) rename examples/{login-page => auth-page}/env.mjs (100%) rename examples/{login-page => auth-page}/next.config.js (100%) rename examples/{login-page => auth-page}/package-lock.json (100%) rename examples/{login-page => auth-page}/package.json (100%) rename examples/{login-page => auth-page}/pages/_app.tsx (100%) rename examples/{login-page => auth-page}/pages/api/auth/[...nextauth].ts (100%) rename examples/{login-page => auth-page}/pages/api/lnauth/[...lnauth].ts (84%) rename examples/{login-page => auth-page}/pages/index.tsx (84%) rename examples/{login-page/pages/login.tsx => auth-page/pages/signin.tsx} (91%) rename examples/{login-page => auth-page}/tsconfig.json (100%) rename src/main/handlers/{login.tsx => signin.tsx} (90%) rename src/react/components/{LnAuthLogin.tsx => LightningAuth.tsx} (96%) diff --git a/README.md b/README.md index 4c2ba90..42d1375 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A light-weight Lightning auth provider for your Next.js app that's entirely self Install the package and add two code snippets to your app (as shown below). It's that simple. -Your users will then be shown an additional login option in the `next-auth` login page. When they click the new option they'll be presented with a QR code. The QR code can be scanned with any Bitcoin Lightning wallet that supports `lnurl-auth`. After scanning, they'll be securely logged in! No username or password required. +Your users will then be shown an additional auth option in the `next-auth` sign in page. When they click the new option they'll be presented with a QR code. The QR code can be scanned with any Bitcoin Lightning wallet that supports `lnurl-auth`. After scanning, they'll be securely logged in! No username or password required. Behind the scenes `next-auth-lightning-provider` sets up several API endpoint which act as a basic OAuth server. The API will authorize users with [lnurl-auth](https://fiatjaf.com/e0a35204.html) and then issue a JWT token to them. @@ -77,7 +77,7 @@ Create a new API route under `pages/api/lnauth/[...lnauth].ts` // @/pages/api/lnauth/[...lnauth].ts import NextAuthLightning, { - LnAuthData, + LightningAuthSession, NextAuthLightningConfig, } from "next-auth-lightning-provider"; import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -262,20 +262,20 @@ const config: NextAuthLightningConfig = { /** * @param {string} signIn * - * A Lightning login page will be automatically generated unless the + * A Lightning auth page will be automatically generated unless the * `signIn` path is specified. It lets you define your own page where * you can configure a custom Next.js page and customize the UI. * - * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page/ + * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/auth-page/ * - * @default "/api/lnauth/login" + * @default "/api/lnauth/signin" */ - signIn: "/example-custom-login" + signIn: "/example-custom-signin" /** * @param {string} error * - * By default users will be redirected to the `next-auth` login page + * By default users will be redirected to the `next-auth` sign in page * and shown an error message there. If you want a custom error page, * you can define the path here. * @@ -288,7 +288,7 @@ const config: NextAuthLightningConfig = { * @param {string | null} title * * Override the default title shown above the QR code in the - * Lighting Login page. Or, it can be set to null to hide the title. + * Lighting auth page. Or, it can be set to null to hide the title. * * @default "Login with Lightning" */ @@ -414,26 +414,26 @@ const config: NextAuthLightningConfig = { qrMargin: 1, /** - * @param {string} loginButtonBackground + * @param {string} signInButtonBackground * * Override the theme's button background color. This is the button that's shown in the - * `next-auth` login screen alongside your other providers. + * `next-auth` auth page alongside your other providers. * * @default light "#24292f" * @default dark "#24292f" */ - loginButtonBackground: "#00ff00", + signInButtonBackground: "#00ff00", /** - * @param {string} loginButtonText + * @param {string} signInButtonText * * Override the theme's button text color. This is the button that's shown in the - * `next-auth` login screen alongside your other providers. + * `next-auth` auth page alongside your other providers. * * @default light "#ffffff" * @default dark "#ffffff" */ - loginButtonText: "#ff00ff", + signInButtonText: "#ff00ff", }, }; diff --git a/TODO.md b/TODO.md index b12e9ce..10df020 100644 --- a/TODO.md +++ b/TODO.md @@ -21,7 +21,6 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - carefully run through the auth and data flow to look for bugs or oversights - add jest tests where applicable - open PR on `next-auth` -- rename login to signup everywhere (so it matches `next-auth`) - manual testing. pre beta release make list of all test criteria and go through them - deploy app to vercel and test remotely diff --git a/examples/README.md b/examples/README.md index 99fe402..0e702b9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,8 +1,8 @@ # Examples -### Custom Login screen +### Custom Auth page -The [examples/login-page/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/login-page) example demonstrates how to configure a custom Lightning login page UI. +The [examples/auth-page/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/auth-page) example demonstrates how to configure a custom Lightning auth page UI. ### App Router diff --git a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts index b56e803..8ac4312 100644 --- a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts +++ b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts @@ -1,5 +1,5 @@ import NextAuthLightning, { - LnAuthData, + LightningAuthSession, NextAuthLightningConfig, } from "next-auth-lightning-provider"; import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -21,11 +21,11 @@ const config: NextAuthLightningConfig = { await storage.setItem(`k1:${k1}`, data); }, async get({ k1 }) { - const results = await storage.getItem(`k1:${k1}`); + const session = await storage.getItem(`k1:${k1}`); - if (!results) throw new Error("Couldn't find item by k1"); + if (!session) throw new Error("Couldn't find item by k1"); - return results as LnAuthData; + return session as LightningAuthSession; }, async update({ k1, data }) { await storage.setItem(`k1:${k1}`, data); diff --git a/examples/app-router/app/components/Login.tsx b/examples/app-router/app/components/Login.tsx deleted file mode 100644 index 6d0125b..0000000 --- a/examples/app-router/app/components/Login.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import { Session } from "next-auth"; -import { signOut, signIn } from "next-auth/react"; - -export const Login = async ({ session }: { session: Session | null }) => { - return ( -
- {session ? ( - - ) : ( - - )} -
- ); -}; diff --git a/examples/app-router/app/components/SignIn.tsx b/examples/app-router/app/components/SignIn.tsx new file mode 100644 index 0000000..acfe3ca --- /dev/null +++ b/examples/app-router/app/components/SignIn.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { Session } from "next-auth"; +import { signOut, signIn } from "next-auth/react"; + +export const SignIn = async ({ session }: { session: Session | null }) => { + return ( +
+ {session ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/examples/app-router/app/layout.tsx b/examples/app-router/app/layout.tsx index 24617b0..5cebe9c 100644 --- a/examples/app-router/app/layout.tsx +++ b/examples/app-router/app/layout.tsx @@ -7,7 +7,7 @@ import { Metadata } from "next"; * @see https://nextjs.org/docs/app/api-reference/file-conventions/metadata */ export const metadata: Metadata = { - title: "Lightning Login", + title: "Login with Lightning", }; /** diff --git a/examples/app-router/app/page.tsx b/examples/app-router/app/page.tsx index c22ace1..ec83440 100644 --- a/examples/app-router/app/page.tsx +++ b/examples/app-router/app/page.tsx @@ -2,7 +2,7 @@ import React from "react"; import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/config"; -import { Login } from "./components/Login"; +import { SignIn } from "./components/SignIn"; const Home = async () => { const session = await getServerSession(authOptions); @@ -25,7 +25,7 @@ const Home = async () => {
{JSON.stringify({ session }, null, 2)}
- +
); }; diff --git a/examples/login-page/.env.example b/examples/auth-page/.env.example similarity index 100% rename from examples/login-page/.env.example rename to examples/auth-page/.env.example diff --git a/examples/login-page/.gitignore b/examples/auth-page/.gitignore similarity index 100% rename from examples/login-page/.gitignore rename to examples/auth-page/.gitignore diff --git a/examples/login-page/README.md b/examples/auth-page/README.md similarity index 92% rename from examples/login-page/README.md rename to examples/auth-page/README.md index f3f47d0..abd1709 100644 --- a/examples/login-page/README.md +++ b/examples/auth-page/README.md @@ -1,6 +1,6 @@ ## About -This example demonstrates implementing a custom Lightning login page UI. +This example demonstrates implementing a custom Lightning auth page UI. > ⚠️ WARNING using `node-persist` is not recommended in lambda or edge environments. > diff --git a/examples/login-page/components/LightningLogin.tsx b/examples/auth-page/components/LightningAuth.tsx similarity index 97% rename from examples/login-page/components/LightningLogin.tsx rename to examples/auth-page/components/LightningAuth.tsx index 7fdad8e..c383953 100644 --- a/examples/login-page/components/LightningLogin.tsx +++ b/examples/auth-page/components/LightningAuth.tsx @@ -1,6 +1,6 @@ import { useLightningAuth } from "next-auth-lightning-provider/react"; -export default function LightningLogin({ +export default function LightningAuth({ redirectUri, state, }: { diff --git a/examples/login-page/env.mjs b/examples/auth-page/env.mjs similarity index 100% rename from examples/login-page/env.mjs rename to examples/auth-page/env.mjs diff --git a/examples/login-page/next.config.js b/examples/auth-page/next.config.js similarity index 100% rename from examples/login-page/next.config.js rename to examples/auth-page/next.config.js diff --git a/examples/login-page/package-lock.json b/examples/auth-page/package-lock.json similarity index 100% rename from examples/login-page/package-lock.json rename to examples/auth-page/package-lock.json diff --git a/examples/login-page/package.json b/examples/auth-page/package.json similarity index 100% rename from examples/login-page/package.json rename to examples/auth-page/package.json diff --git a/examples/login-page/pages/_app.tsx b/examples/auth-page/pages/_app.tsx similarity index 100% rename from examples/login-page/pages/_app.tsx rename to examples/auth-page/pages/_app.tsx diff --git a/examples/login-page/pages/api/auth/[...nextauth].ts b/examples/auth-page/pages/api/auth/[...nextauth].ts similarity index 100% rename from examples/login-page/pages/api/auth/[...nextauth].ts rename to examples/auth-page/pages/api/auth/[...nextauth].ts diff --git a/examples/login-page/pages/api/lnauth/[...lnauth].ts b/examples/auth-page/pages/api/lnauth/[...lnauth].ts similarity index 84% rename from examples/login-page/pages/api/lnauth/[...lnauth].ts rename to examples/auth-page/pages/api/lnauth/[...lnauth].ts index d58b026..e9521a7 100644 --- a/examples/login-page/pages/api/lnauth/[...lnauth].ts +++ b/examples/auth-page/pages/api/lnauth/[...lnauth].ts @@ -1,5 +1,5 @@ import NextAuthLightning, { - LnAuthData, + LightningAuthSession, NextAuthLightningConfig, } from "next-auth-lightning-provider"; import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -21,11 +21,11 @@ const config: NextAuthLightningConfig = { await storage.setItem(`k1:${k1}`, data); }, async get({ k1 }) { - const results = await storage.getItem(`k1:${k1}`); + const session = await storage.getItem(`k1:${k1}`); - if (!results) throw new Error("Couldn't find item by k1"); + if (!session) throw new Error("Couldn't find item by k1"); - return results as LnAuthData; + return session as LightningAuthSession; }, async update({ k1, data }) { await storage.setItem(`k1:${k1}`, data); @@ -40,7 +40,7 @@ const config: NextAuthLightningConfig = { generateName, generateAvatar, pages: { - signIn: "/login", + signIn: "/signin", }, theme: { colorScheme: "dark", diff --git a/examples/login-page/pages/index.tsx b/examples/auth-page/pages/index.tsx similarity index 84% rename from examples/login-page/pages/index.tsx rename to examples/auth-page/pages/index.tsx index daaeb04..1d0fdbd 100644 --- a/examples/login-page/pages/index.tsx +++ b/examples/auth-page/pages/index.tsx @@ -23,9 +23,9 @@ const Home = () => {
{JSON.stringify(session, null, 2)}
{session && session.data ? ( - + ) : ( - + )} ); diff --git a/examples/login-page/pages/login.tsx b/examples/auth-page/pages/signin.tsx similarity index 91% rename from examples/login-page/pages/login.tsx rename to examples/auth-page/pages/signin.tsx index 105d30b..b5b2a0d 100644 --- a/examples/login-page/pages/login.tsx +++ b/examples/auth-page/pages/signin.tsx @@ -1,7 +1,7 @@ import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; -import LightningLogin from "@/components/LightningLogin"; +import LightningAuth from "@/components/LightningAuth"; export default function LoginPage() { const { isReady, query } = useRouter(); @@ -33,7 +33,7 @@ export default function LoginPage() { return (
- diff --git a/examples/login-page/tsconfig.json b/examples/auth-page/tsconfig.json similarity index 100% rename from examples/login-page/tsconfig.json rename to examples/auth-page/tsconfig.json diff --git a/examples/drizzle/pages/index.tsx b/examples/drizzle/pages/index.tsx index daaeb04..1d0fdbd 100644 --- a/examples/drizzle/pages/index.tsx +++ b/examples/drizzle/pages/index.tsx @@ -23,9 +23,9 @@ const Home = () => {
{JSON.stringify(session, null, 2)}
{session && session.data ? ( - + ) : ( - + )}
); diff --git a/examples/kv/pages/api/lnauth/[...lnauth].ts b/examples/kv/pages/api/lnauth/[...lnauth].ts index 2d3890f..0d05820 100644 --- a/examples/kv/pages/api/lnauth/[...lnauth].ts +++ b/examples/kv/pages/api/lnauth/[...lnauth].ts @@ -1,5 +1,5 @@ import NextAuthLightning, { - LnAuthData, + LightningAuthSession, NextAuthLightningConfig, } from "next-auth-lightning-provider"; import { generateQr } from "next-auth-lightning-provider/generators/qr"; @@ -19,11 +19,11 @@ const config: NextAuthLightningConfig = { await kv.set(`k1:${k1}`, data); }, async get({ k1 }) { - const results = await kv.get(`k1:${k1}`); + const session = await kv.get(`k1:${k1}`); - if (!results) throw new Error("Couldn't find item by k1"); + if (!session) throw new Error("Couldn't find item by k1"); - return results as LnAuthData; + return session as LightningAuthSession; }, async update({ k1, data }) { await kv.set(`k1:${k1}`, data); diff --git a/examples/kv/pages/index.tsx b/examples/kv/pages/index.tsx index daaeb04..1d0fdbd 100644 --- a/examples/kv/pages/index.tsx +++ b/examples/kv/pages/index.tsx @@ -23,9 +23,9 @@ const Home = () => {
{JSON.stringify(session, null, 2)}
{session && session.data ? ( - + ) : ( - + )} ); diff --git a/examples/plain-js/pages/index.jsx b/examples/plain-js/pages/index.jsx index daaeb04..1d0fdbd 100644 --- a/examples/plain-js/pages/index.jsx +++ b/examples/plain-js/pages/index.jsx @@ -23,9 +23,9 @@ const Home = () => {
{JSON.stringify(session, null, 2)}
{session && session.data ? ( - + ) : ( - + )} ); diff --git a/src/index.ts b/src/index.ts index 1bf13f8..6a32eb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export type { - LnAuthData, + LightningAuthSession, UserConfig as NextAuthLightningConfig, } from "./main/config/types.js"; export { default } from "./main/index.js"; diff --git a/src/main/config/default.ts b/src/main/config/default.ts index be335eb..d306247 100644 --- a/src/main/config/default.ts +++ b/src/main/config/default.ts @@ -11,8 +11,8 @@ const colorSchemeLight: ThemeStyles = { qrBackground: "#ffffff", qrForeground: "#0d1117", qrMargin: 0, - loginButtonBackground: "#24292f", - loginButtonText: "#ffffff", + signInButtonBackground: "#24292f", + signInButtonText: "#ffffff", }; const colorSchemeDark: ThemeStyles = { @@ -22,13 +22,13 @@ const colorSchemeDark: ThemeStyles = { qrBackground: "#ffffff", qrForeground: "#0d1117", qrMargin: 0.5, - loginButtonBackground: "#24292f", - loginButtonText: "#ffffff", + signInButtonBackground: "#24292f", + signInButtonText: "#ffffff", }; const defaultConfig: Partial = { pages: { - signIn: "/api/lnauth/login", // pre-configured qr lightning login + signIn: "/api/lnauth/signin", // default lightning auth page error: "/api/auth/signin", // default next-auth error page }, title: "Login with Lightning", @@ -67,8 +67,8 @@ const configValidation = z backgroundCard: z.string().optional(), text: z.string().optional(), error: z.string().optional(), - loginButtonBackground: z.string().optional(), - loginButtonText: z.string().optional(), + signInButtonBackground: z.string().optional(), + signInButtonText: z.string().optional(), qrBackground: z.string().optional(), qrForeground: z.string().optional(), qrMargin: z.number().optional(), diff --git a/src/main/config/hard.ts b/src/main/config/hard.ts index 309fc41..90b243d 100644 --- a/src/main/config/hard.ts +++ b/src/main/config/hard.ts @@ -11,7 +11,7 @@ export const hardConfig: HardConfig = { token: "/api/lnauth/token", // pages - signIn: "/api/lnauth/login", + signIn: "/api/lnauth/signin", // images avatar: "/api/lnauth/avatar", diff --git a/src/main/config/index.ts b/src/main/config/index.ts index 94ba4a2..ba08cc2 100644 --- a/src/main/config/index.ts +++ b/src/main/config/index.ts @@ -1,4 +1,9 @@ -export type { LnAuthData, UserConfig, Config, HardConfig } from "./types.js"; +export type { + LightningAuthSession, + UserConfig, + Config, + HardConfig, +} from "./types.js"; export { formatConfig } from "./default.js"; export { hardConfig } from "./hard.js"; diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 5a746be..7d0b741 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -32,7 +32,7 @@ export type HardConfig = { }; }; -export type LnAuthData = { +export type LightningAuthSession = { k1: string; state: string; @@ -76,7 +76,7 @@ export type RequiredConfig = { args: { k1: string }, path: string, config: Config - ) => Promise; + ) => Promise; update: ( args: { k1: string; @@ -105,8 +105,8 @@ export type ThemeStyles = { qrBackground: string; qrForeground: string; qrMargin: number; - loginButtonBackground: string; - loginButtonText: string; + signInButtonBackground: string; + signInButtonText: string; }; export type OptionalConfig = { diff --git a/src/main/handlers/login.tsx b/src/main/handlers/signin.tsx similarity index 90% rename from src/main/handlers/login.tsx rename to src/main/handlers/signin.tsx index dc2cf4f..8dcf8a4 100644 --- a/src/main/handlers/login.tsx +++ b/src/main/handlers/signin.tsx @@ -3,16 +3,16 @@ import { renderToStaticMarkup } from "preact-render-to-string"; import { hardConfig, Config } from "../config/index.js"; import { vanilla } from "../utils/vanilla.js"; -import { LnAuthLogin } from "../../react/components/LnAuthLogin.js"; +import { LightningAuth } from "../../react/components/LightningAuth.js"; import { Loading } from "../../react/components/Loading.js"; import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; import { - loginValidation, + signInValidation, errorMap, formatErrorMessage, } from "../validation/lnauth.js"; -function AuthPage({ config }: { config: Config }) { +function LightningAuthPage({ config }: { config: Config }) { return ( - {/* login component is rendered with display: none, after window.onload is triggered */} - { try { try { - loginValidation.parse(query, { errorMap }); + signInValidation.parse(query, { errorMap }); } catch (e: any) { return { error: formatErrorMessage(e), isRedirect: true }; } @@ -120,7 +120,7 @@ export default async function handler({ return { error: "You are already logged in", isRedirect: true }; } - // if a custom login page is specified, send them there if they try and access this API + // if a custom auth page is specified, send them there if they try and access this API if (config.pages.signIn !== config.apis.signIn) { const params = url.searchParams.toString(); return { @@ -130,7 +130,7 @@ export default async function handler({ const title = config.title || config.siteUrl; const errorUrl = config.siteUrl + config.pages.error; - const html = renderToStaticMarkup(); + const html = renderToStaticMarkup(); return { status: 200, diff --git a/src/main/handlers/token.ts b/src/main/handlers/token.ts index 6a00275..83b73f1 100644 --- a/src/main/handlers/token.ts +++ b/src/main/handlers/token.ts @@ -22,10 +22,10 @@ export default async function handler({ let pubkey: string; if (grantType === "authorization_code") { if (!k1) return { error: "Missing code" }; - const data = await config.storage.get({ k1 }, path, config); - if (!data?.success) return { error: "Login was not successful" }; - if (!data?.pubkey) return { error: "Missing pubkey" }; - pubkey = data.pubkey; + const session = await config.storage.get({ k1 }, path, config); + if (!session?.success) return { error: "Login was not successful" }; + if (!session?.pubkey) return { error: "Missing pubkey" }; + pubkey = session.pubkey; await config.storage.delete({ k1 }, path, config); } else if (grantType === "refresh_token") { if (!refreshToken) return { error: "Missing refresh token" }; diff --git a/src/main/index.ts b/src/main/index.ts index 13ebd1f..a9cffc4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,7 +8,7 @@ import callbackHandler from "./handlers/callback.js"; import tokenHandler from "./handlers/token.js"; // pages -import loginHandler from "./handlers/login.js"; +import signInHandler from "./handlers/signin.js"; // misc import avatarHandler from "./handlers/avatar.js"; @@ -57,8 +57,8 @@ export default function NextAuthLightning(userConfig: UserConfig) { clientSecret: config.secret, style: { logo: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgyIiBoZWlnaHQ9IjI4MiIgdmlld0JveD0iMCAwIDI4MiAyODIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiPgo8Y2lyY2xlIGN4PSIxNDAuOTgzIiBjeT0iMTQxLjAwMyIgcj0iMTQxIiBmaWxsPSIjN0IxQUY3Ii8+CjxwYXRoIGQ9Ik03OS43NjA5IDE0NC4wNDdMMTczLjc2MSA2My4wNDY2QzE3Ny44NTcgNjAuNDIzNSAxODEuNzYxIDYzLjA0NjYgMTc5LjI2MSA2Ny41NDY2TDE0OS4yNjEgMTI2LjU0N0gyMDIuNzYxQzIwMi43NjEgMTI2LjU0NyAyMTEuMjYxIDEyNi41NDcgMjAyLjc2MSAxMzMuNTQ3TDExMC4yNjEgMjE1LjA0N0MxMDMuNzYxIDIyMC41NDcgOTkuMjYxIDIxNy41NDcgMTAzLjc2MSAyMDkuMDQ3TDEzMi43NjEgMTUxLjU0N0g3OS43NjA5Qzc5Ljc2MDkgMTUxLjU0NyA3MS4yNjA5IDE1MS41NDcgNzkuNzYwOSAxNDQuMDQ3WiIgZmlsbD0id2hpdGUiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMCI+CjxyZWN0IHdpZHRoPSIyODIiIGhlaWdodD0iMjgyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=", - bg: config.theme.loginButtonBackground, - text: config.theme.loginButtonText, + bg: config.theme.signInButtonBackground, + text: config.theme.signInButtonText, }, }; @@ -77,7 +77,7 @@ export default function NextAuthLightning(userConfig: UserConfig) { } else if (path?.indexOf(config.apis.token) === 0) { return await dynamicHandler(req, res, config, tokenHandler); } else if (path?.indexOf(config.apis.signIn) === 0) { - return await dynamicHandler(req, res, config, loginHandler); + return await dynamicHandler(req, res, config, signInHandler); } else if (path?.indexOf(config.apis.avatar) === 0) { return await dynamicHandler(req, res, config, avatarHandler); } else if (path?.indexOf(config.apis.qr) === 0) { diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 22c6967..9140c6f 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -28,7 +28,7 @@ export const vanilla = function ({ window.location.replace(url); } - // poll the api to see if successful login has occurred + // poll the api to see if the user has successfully authenticated function poll() { if (!data || !data.k1) return; const k1 = data.k1; diff --git a/src/main/validation/lnauth.ts b/src/main/validation/lnauth.ts index 8e5fd25..3f7f9af 100644 --- a/src/main/validation/lnauth.ts +++ b/src/main/validation/lnauth.ts @@ -14,12 +14,12 @@ export const createValidation = z.object({ export type CreateValidation = z.infer; -export const loginValidation = z.object({ +export const signInValidation = z.object({ state: z.string().min(1), redirect_uri: z.string().min(1), }); -export type LoginValidation = z.infer; +export type SignInValidation = z.infer; export const pollValidation = z.object({ k1: z.string().min(1), diff --git a/src/react/components/LnAuthLogin.tsx b/src/react/components/LightningAuth.tsx similarity index 96% rename from src/react/components/LnAuthLogin.tsx rename to src/react/components/LightningAuth.tsx index db22e8e..13d78c0 100644 --- a/src/react/components/LnAuthLogin.tsx +++ b/src/react/components/LightningAuth.tsx @@ -7,7 +7,7 @@ import { Button } from "./Button.js"; import { hardConfig } from "../../main/config/hard.js"; -export function LnAuthLogin({ +export function LightningAuth({ title, lnurl, theme, diff --git a/src/react/components/QrCode.tsx b/src/react/components/QrCode.tsx index fa61694..6a80071 100644 --- a/src/react/components/QrCode.tsx +++ b/src/react/components/QrCode.tsx @@ -15,7 +15,7 @@ export function QrCode({ Login with Lightning - QRCode { const k1 = data?.k1; try { From 12b9234efcc501da97c1bcfce5ca442776ae2f93 Mon Sep 17 00:00:00 2001 From: jowo Date: Tue, 21 Nov 2023 19:50:59 +0000 Subject: [PATCH 40/49] reformatting todo list --- TODO.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 10df020..7a7eefc 100644 --- a/TODO.md +++ b/TODO.md @@ -2,13 +2,9 @@ Below is a TODO list for further development of `next-auth-lightning-provider` -### Primary +### Alpha -- Once `next-auth@v5` is out of beta, ensure it's supported. - investigate CSRF for next-auth - -### Secondary - - error handling: of - App Router APIs - error thrown in `storage.get` and other storage methods @@ -18,15 +14,20 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - adding error http status - adding custom error code query param - documenting the error types -- carefully run through the auth and data flow to look for bugs or oversights - add jest tests where applicable -- open PR on `next-auth` + +### Beta + +- carefully run through the auth and data flow to look for bugs or oversights - manual testing. pre beta release make list of all test criteria and go through them - deploy app to vercel and test remotely +- tidy up READMEs (see list bellow) -### Tertiary +### Release +- open PR on `next-auth` - add more example repos +- Once `next-auth@v5` is out of beta, ensure it's supported. ### Readme @@ -35,11 +36,12 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - ensure consistent formatting is used. full stops, caps, etc - add suggestion: cleaning up old and unused lnauth session data that was created but never reached success state. -### back-burner +### Back-burner + +Stuff I may or may not get around to: -- add `auto` color scheme that uses browsers dark/light settings -- see if TS generics can be used for NextRequest/NextApiRequest etc +- add `auto` color scheme that uses browser's preferred dark/light settings - consider adding various styles of avatar and name generators - consider / investigate how to SSR react components so the `vanilla.ts` shim can be deprecated - add JSDocs comments to all internally used functions -- consider deleting cookie instead of throwing error "You are already logged in" +- consider alternatives to throwing error "You are already logged in" From 6500561c4ce0df978a835627a4188ef17d2877a1 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 08:30:51 +0000 Subject: [PATCH 41/49] adding diagnostics page route for testing storage methods and tidying up some parts of error handling --- README.md | 62 +++- TODO.md | 7 +- .../app/api/lnauth/[...lnauth]/config.ts | 14 +- .../auth-page/pages/api/lnauth/[...lnauth].ts | 14 +- .../drizzle/pages/api/lnauth/[...lnauth].ts | 10 +- examples/kv/pages/api/lnauth/[...lnauth].ts | 14 +- src/main/config/hard.ts | 3 +- src/main/config/types.ts | 9 +- src/main/handlers/diagnostics.ts | 315 ++++++++++++++++++ src/main/handlers/poll.ts | 4 +- src/main/index.ts | 10 +- 11 files changed, 408 insertions(+), 54 deletions(-) create mode 100644 src/main/handlers/diagnostics.ts diff --git a/README.md b/README.md index 42d1375..7e253c9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the [next-auth](https://github.com/nextauthjs/next-auth) framework. -> This package is built for Next.js apps that use [next-auth](https://github.com/nextauthjs/next-auth). It's not compatible with other authentication libraries. +> ℹ️ This package is built for Next.js apps that use [next-auth](https://github.com/nextauthjs/next-auth). It's not compatible with other authentication libraries. # How it works @@ -89,13 +89,13 @@ const config: NextAuthLightningConfig = { siteUrl: process.env.NEXTAUTH_URL, secret: process.env.NEXTAUTH_SECRET, storage: { - async set({ k1, data }) { + async set({ k1, session }) { // save lnurl auth session data based on k1 id }, async get({ k1 }) { // lookup and return lnurl auth session data based on k1 id }, - async update({ k1, data }) { + async update({ k1, session }) { // update lnurl auth session data based on k1 id }, async delete({ k1 }) { @@ -119,7 +119,7 @@ export const lightningProvider = provider; export default handler; ``` -> NOTE: The above example uses the Pages Router. If your app uses the App Router then take a look at the [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router/) example app. +> ℹ️ The above example uses the Pages Router. If your app uses the App Router then take a look at the [examples/app-router/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/app-router/) example app. This API will handle all of the Lightning auth API requests, such as generating QRs, handling callbacks, polling and issuing JWT auth tokens. @@ -143,7 +143,7 @@ export default NextAuth(authOptions); # Generators -Normally if you authenticate a user with lnurl-auth, all you'd know about the user is their unique ID (a pubkey). This package goes a step further and provides several generator functions that can be used to deterministically (the pubkey is used as a seed) generate avatars and usernames. That means you can show users a unique name and image that'll be associated with their account! +Normally if you authenticate a user with `lnurl-auth`, all you'd know about the user is their unique ID (a `pubkey`). This package goes a step further and provides several generator functions that can be used to deterministically (the `pubkey` is used as a seed) generate avatars and usernames. That means you can show users a unique name and image that'll be associated with their account! As well as the avatar and image generators, there's also a QR code generator. @@ -155,7 +155,7 @@ import { generateName } from "next-auth-lightning-provider/generators/name"; import { generateAvatar } from "next-auth-lightning-provider/generators/avatar"; ``` -> Note: you can write your own generator functions if those provided don't suit your needs! +> ℹ️ You can write your own generator functions if those provided don't suit your needs! # Configuration @@ -186,10 +186,13 @@ const config: NextAuthLightningConfig = { /** * @param {object} storage * - * The lnurl-auth spec requires that a user's Lightning wallet triggers a + * The lnurl-auth spec requires that a user's Lightning wallet trigger a * callback as part of the authentication flow. So, we require session storage to * persist some data and ensure it's available when the callback is triggered. - * Data can be stored in any medium of your choice. + * Data can be stored in a medium of your choice. + * + * Once you have configured the storage methods you can test them on the diagnostics page: + * @see http://localhost:3000/api/lnauth/diagnostics * * @see https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples/ */ @@ -200,7 +203,7 @@ const config: NextAuthLightningConfig = { * An async function that receives a k1 and data arguments. * The k1 is a unique key that's used to store the data for later use. */ - async set({ k1, data }) { + async set({ k1, session }) { // save lnurl auth session data based on k1 id }, @@ -220,7 +223,7 @@ const config: NextAuthLightningConfig = { * An async function that receives a k1 and data arguments. * The k1 is used to find and update data previously stored under it. */ - async update({ k1, data }) { + async update({ k1, session }) { // update lnurl auth session data based on k1 id }, @@ -439,6 +442,45 @@ const config: NextAuthLightningConfig = { }; ``` +# Storage + +The `lnurl-auth` spec requires that a user's Lightning wallet trigger a callback as part of the authentication flow. For this reason, it may be that the device that scan the QR (e.g. a mobile) is not the same as the device trying to authenticate (e.g. a desktop). So, we require session storage to persist some data across devices, and ensure it's available when the callback is triggered. + +Data can be stored in a medium of your choice. For example a database, a document store, or a session store. Here's an example using [Vercel KV](https://vercel.com/docs/storage/vercel-kv): + +```typescript +import { kv } from "@vercel/kv"; + +const config: NextAuthLightningConfig = { + // ... + storage: { + async set({ k1, session }) { + await kv.set(`k1:${k1}`, session); + }, + async get({ k1 }) { + return await kv.get(`k1:${k1}`); + }, + async update({ k1, session }) { + await kv.set(`k1:${k1}`, session); + }, + async delete({ k1 }) { + await kv.del(`k1:${k1}`); + }, + }, + // ... +}; +``` + +See more working examples in the [examples/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples) folder. + +Once you have configured the storage methods you can launch your dev server test them locally on the diagnostics page: +`http://localhost:3000/api/lnauth/diagnostics` + +You can also pass in your own custom values in the query params: +`http://localhost:3000/api/lnauth/diagnostics?k1=custom-k1&state=custom-state&pubkey=custom-pubkey&sig=custom-sig` + +> ℹ️ The diagnostics page will be **disabled** for production builds. + # Next.js Routers With the release of `next@v13` comes the App Router. diff --git a/TODO.md b/TODO.md index 7a7eefc..75e4872 100644 --- a/TODO.md +++ b/TODO.md @@ -6,12 +6,13 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - investigate CSRF for next-auth - error handling: of - - App Router APIs + - Pages and App Router APIs handle errors consistently ✅ + - error at end of API if no paths matched ✅ - error thrown in `storage.get` and other storage methods - - error at end of API if no paths matched + - check individual logic handlers and ensure all error cases that should be handler are, and that they are handled correctly. - "You are already logged in" error, passing. etc - making error messages user friendly - - adding error http status + - adding error http statuses are correct across all error instances - adding custom error code query param - documenting the error types - add jest tests where applicable diff --git a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts index 8ac4312..3272505 100644 --- a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts +++ b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts @@ -17,18 +17,14 @@ const config: NextAuthLightningConfig = { siteUrl: env.NEXTAUTH_URL, secret: env.NEXTAUTH_SECRET, storage: { - async set({ k1, data }) { - await storage.setItem(`k1:${k1}`, data); + async set({ k1, session }) { + await storage.setItem(`k1:${k1}`, session); }, async get({ k1 }) { - const session = await storage.getItem(`k1:${k1}`); - - if (!session) throw new Error("Couldn't find item by k1"); - - return session as LightningAuthSession; + return await storage.getItem(`k1:${k1}`); }, - async update({ k1, data }) { - await storage.setItem(`k1:${k1}`, data); + async update({ k1, session }) { + await storage.setItem(`k1:${k1}`, session); }, async delete({ k1 }) { await storage.removeItem(`k1:${k1}`); diff --git a/examples/auth-page/pages/api/lnauth/[...lnauth].ts b/examples/auth-page/pages/api/lnauth/[...lnauth].ts index e9521a7..ca94cdd 100644 --- a/examples/auth-page/pages/api/lnauth/[...lnauth].ts +++ b/examples/auth-page/pages/api/lnauth/[...lnauth].ts @@ -17,18 +17,14 @@ const config: NextAuthLightningConfig = { siteUrl: env.NEXTAUTH_URL, secret: env.NEXTAUTH_SECRET, storage: { - async set({ k1, data }) { - await storage.setItem(`k1:${k1}`, data); + async set({ k1, session }) { + await storage.setItem(`k1:${k1}`, session); }, async get({ k1 }) { - const session = await storage.getItem(`k1:${k1}`); - - if (!session) throw new Error("Couldn't find item by k1"); - - return session as LightningAuthSession; + return await storage.getItem(`k1:${k1}`); }, - async update({ k1, data }) { - await storage.setItem(`k1:${k1}`, data); + async update({ k1, session }) { + await storage.setItem(`k1:${k1}`, session); }, async delete({ k1 }) { await storage.removeItem(`k1:${k1}`); diff --git a/examples/drizzle/pages/api/lnauth/[...lnauth].ts b/examples/drizzle/pages/api/lnauth/[...lnauth].ts index 8545c69..6c9f314 100644 --- a/examples/drizzle/pages/api/lnauth/[...lnauth].ts +++ b/examples/drizzle/pages/api/lnauth/[...lnauth].ts @@ -16,8 +16,8 @@ const config: NextAuthLightningConfig = { siteUrl: env.NEXTAUTH_URL, secret: env.NEXTAUTH_SECRET, storage: { - async set({ k1, data }) { - await db.insert(lnAuthTable).values(data); + async set({ k1, session }) { + await db.insert(lnAuthTable).values(session); }, async get({ k1 }) { const results: LnAuth[] = await db @@ -25,12 +25,10 @@ const config: NextAuthLightningConfig = { .from(lnAuthTable) .where(eq(lnAuthTable.k1, k1)); - if (!results[0]) throw new Error("Couldn't find item by k1"); - return results[0]; }, - async update({ k1, data }) { - await db.update(lnAuthTable).set(data).where(eq(lnAuthTable.k1, k1)); + async update({ k1, session }) { + await db.update(lnAuthTable).set(session).where(eq(lnAuthTable.k1, k1)); }, async delete({ k1 }) { await db.delete(lnAuthTable).where(eq(lnAuthTable.k1, k1)); diff --git a/examples/kv/pages/api/lnauth/[...lnauth].ts b/examples/kv/pages/api/lnauth/[...lnauth].ts index 0d05820..9e3fa6a 100644 --- a/examples/kv/pages/api/lnauth/[...lnauth].ts +++ b/examples/kv/pages/api/lnauth/[...lnauth].ts @@ -15,18 +15,14 @@ const config: NextAuthLightningConfig = { siteUrl: env.NEXTAUTH_URL, secret: env.NEXTAUTH_SECRET, storage: { - async set({ k1, data }) { - await kv.set(`k1:${k1}`, data); + async set({ k1, session }) { + await kv.set(`k1:${k1}`, session); }, async get({ k1 }) { - const session = await kv.get(`k1:${k1}`); - - if (!session) throw new Error("Couldn't find item by k1"); - - return session as LightningAuthSession; + return await kv.get(`k1:${k1}`); }, - async update({ k1, data }) { - await kv.set(`k1:${k1}`, data); + async update({ k1, session }) { + await kv.set(`k1:${k1}`, session); }, async delete({ k1 }) { await kv.del(`k1:${k1}`); diff --git a/src/main/config/hard.ts b/src/main/config/hard.ts index 90b243d..10d54e6 100644 --- a/src/main/config/hard.ts +++ b/src/main/config/hard.ts @@ -13,9 +13,10 @@ export const hardConfig: HardConfig = { // pages signIn: "/api/lnauth/signin", - // images + // misc avatar: "/api/lnauth/avatar", qr: "/api/lnauth/qr", + diagnostics: "/api/lnauth/diagnostics", }, ids: { title: `${idPrefix}---title`, diff --git a/src/main/config/types.ts b/src/main/config/types.ts index 7d0b741..46cdba1 100644 --- a/src/main/config/types.ts +++ b/src/main/config/types.ts @@ -12,9 +12,10 @@ export type HardConfig = { // pages signIn: string; - // images + // misc avatar: string; qr: string; + diagnostics: string; }; ids: { wrapper: string; @@ -64,7 +65,7 @@ export type RequiredConfig = { set: ( args: { k1: string; - data: { + session: { k1: string; state: string; }; @@ -76,11 +77,11 @@ export type RequiredConfig = { args: { k1: string }, path: string, config: Config - ) => Promise; + ) => Promise; update: ( args: { k1: string; - data: { + session: { pubkey: string; sig: string; success: boolean; diff --git a/src/main/handlers/diagnostics.ts b/src/main/handlers/diagnostics.ts new file mode 100644 index 0000000..94f8d44 --- /dev/null +++ b/src/main/handlers/diagnostics.ts @@ -0,0 +1,315 @@ +import { randomBytes } from "crypto"; + +import { LightningAuthSession } from "../config/types.js"; +import { HandlerArguments, HandlerReturn } from "../utils/handlers.js"; + +type Check = { + state: "success" | "failed"; + method: "get" | "set" | "update" | "delete" | null; + message: string; +}; + +export function testField( + expected: Record, + received: Record, + field: "k1" | "state" | "pubkey" | "sig" | "success", + method: "update" | "set" +): Check { + const state = received[field] !== expected[field] ? "failed" : "success"; + return { + state, + method, + message: `Expected 'session.${field}' to be '${expected[field]}', found '${received[field]}'.`, + }; +} + +export async function testSet( + setMethod: () => Promise +): Promise { + const checks: Check[] = []; + try { + await setMethod(); + + checks.push({ + state: "success", + method: "set", + message: "Successfully ran to completion.", + }); + return checks; + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "set", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } +} + +export async function testGet( + expectedSession: { k1: string; state: string }, + getMethod: () => Promise +): Promise { + const checks: Check[] = []; + + try { + const receivedSession = await getMethod(); + + if (!receivedSession) { + checks.push({ + state: "failed", + method: "set", + message: "Session data not found.", + }); + return checks; + } + checks.push(testField(receivedSession, expectedSession, "k1", "set")); + checks.push(testField(receivedSession, expectedSession, "state", "set")); + + checks.push({ + state: "success", + method: "get", + message: + "Successfully ran to completion and returned the expected session data.", + }); + return checks; + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "get", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } +} + +export async function testUpdate( + expectedSession: { k1: string; state: string }, + updateMethod: () => Promise, + getMethod: () => Promise +): Promise { + const checks: Check[] = []; + try { + await updateMethod(); + + let receivedSession; + try { + receivedSession = await getMethod(); + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "get", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } + + if (!receivedSession) { + checks.push({ + state: "failed", + method: "update", + message: "Session data not found.", + }); + return checks; + } + checks.push(testField(receivedSession, expectedSession, "k1", "update")); + checks.push(testField(receivedSession, expectedSession, "state", "update")); + checks.push( + testField(receivedSession, expectedSession, "pubkey", "update") + ); + checks.push(testField(receivedSession, expectedSession, "sig", "update")); + checks.push( + testField(receivedSession, expectedSession, "success", "update") + ); + + checks.push({ + state: "success", + method: "update", + message: + "Successfully ran to completion and returned the updated session data.", + }); + return checks; + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "update", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } +} + +export async function testDelete( + deleteMethod: () => Promise, + getMethod: () => Promise +): Promise { + const checks: Check[] = []; + + try { + await deleteMethod(); + + let receivedSession; + try { + receivedSession = await getMethod(); + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "get", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } + + if (receivedSession) { + checks.push({ + state: "failed", + method: "delete", + message: "Session data was not deleted.", + }); + return checks; + } + + checks.push({ + state: "success", + method: "delete", + message: "Successfully ran to completion.", + }); + return checks; + } catch (e: any) { + console.error(e); + checks.push({ + state: "failed", + method: "update", + message: e.message || "An unexpected error occurred.", + }); + return checks; + } +} + +export default async function handler({ + query, + cookies, + path, + url, + config, +}: HandlerArguments): Promise { + const checks: Check[] = []; + + try { + const k1 = + typeof query?.k1 === "string" ? query.k1 : randomBytes(6).toString("hex"); + const state = + typeof query?.state === "string" + ? query.state + : randomBytes(6).toString("hex"); + const pubkey = + typeof query?.pubkey === "string" + ? query.pubkey + : randomBytes(6).toString("hex"); + const sig = + typeof query?.sig === "string" + ? query.sig + : randomBytes(6).toString("hex"); + + const setSession = { k1, state }; + const updateSession = { k1, state, pubkey, sig, success: true }; + + const setMethod = async () => + await config.storage.set({ k1, session: setSession }, path, config); + const getMethod = async () => + await config.storage.get({ k1 }, path, config); + const updateMethod = async () => + await config.storage.update({ k1, session: updateSession }, path, config); + const deleteMethod = async () => + await config.storage.delete({ k1 }, path, config); + + // set + checks.push(...(await testSet(setMethod))); + + // get + checks.push(...(await testGet(setSession, getMethod))); + + // update + checks.push(...(await testUpdate(updateSession, updateMethod, getMethod))); + + // delete + checks.push(...(await testDelete(deleteMethod, getMethod))); + + // generic throw + } catch (e) { + console.error(e); + checks.push({ + state: "failed", + method: null, + message: "Something went wrong running diagnostics.", + }); + } + + return { + status: 200, + headers: { + "content-type": "text/html", + }, + response: ` + + + + Diagnostics + + + +
+
+ Status + Method + Message +
+
+ ${checks + .map( + ({ state, method, message }) => + `
+ ${state.toUpperCase()} + ${ + method ? `storage.${method}` : "unknown" + } + ${message} +
` + ) + .join("")} +
+ + `, + }; +} diff --git a/src/main/handlers/poll.ts b/src/main/handlers/poll.ts index 5117c49..13908fb 100644 --- a/src/main/handlers/poll.ts +++ b/src/main/handlers/poll.ts @@ -10,11 +10,11 @@ export default async function handler({ }: HandlerArguments): Promise { const { k1 } = pollValidation.parse(body, { errorMap }); - const { success = false } = await config.storage.get({ k1 }, path, config); + const session = await config.storage.get({ k1 }, path, config); return { response: { - success, + success: session?.success || false, }, }; } diff --git a/src/main/index.ts b/src/main/index.ts index a9cffc4..b6f4d9d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,6 +13,7 @@ import signInHandler from "./handlers/signin.js"; // misc import avatarHandler from "./handlers/avatar.js"; import qrHandler from "./handlers/qr.js"; +import diagnosticsHandler from "./handlers/diagnostics.js"; import { formatConfig, UserConfig } from "./config/index.js"; import { NextRequest, NextResponse } from "next/server"; @@ -82,9 +83,16 @@ export default function NextAuthLightning(userConfig: UserConfig) { return await dynamicHandler(req, res, config, avatarHandler); } else if (path?.indexOf(config.apis.qr) === 0) { return await dynamicHandler(req, res, config, qrHandler); + } else if ( + path?.indexOf(config.apis.diagnostics) === 0 && + process.env.NODE_ENV === "development" + ) { + return await dynamicHandler(req, res, config, diagnosticsHandler); } - throw new Error("Unknown path"); + return await dynamicHandler(req, res, config, async () => ({ + error: "Unknown path", + })); }; return { From 9f6b6c584e6a802521bbd96f76664ecb911671b8 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 08:31:20 +0000 Subject: [PATCH 42/49] bumping version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2913991..ad78b48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-auth-lightning-provider", - "version": "1.0.0-alpha.18", + "version": "1.0.0-alpha.19", "type": "module", "description": "A light-weight Lightning auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.", "license": "ISC", From 971176845ce8a61e1e30ea28ab670260aa261801 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 09:00:55 +0000 Subject: [PATCH 43/49] fixing a few variable names and adding OK status back to callback --- src/main/handlers/avatar.ts | 2 +- src/main/handlers/callback.ts | 3 ++- src/main/handlers/create.ts | 2 +- src/main/handlers/diagnostics.ts | 2 +- src/main/handlers/signin.tsx | 2 ++ src/main/utils/handlers.ts | 7 +++++-- src/main/utils/vanilla.ts | 24 ++++++++++++------------ src/main/validation/lnauth.ts | 2 +- src/react/hooks/useLightningAuth.ts | 12 +++++++----- src/react/utils/api.ts | 6 +++--- 10 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/main/handlers/avatar.ts b/src/main/handlers/avatar.ts index 43c68e2..773142f 100644 --- a/src/main/handlers/avatar.ts +++ b/src/main/handlers/avatar.ts @@ -44,5 +44,5 @@ export default async function handler({ }; } - throw new Error("Something went wrong"); + return { error: "Something went wrong" }; } diff --git a/src/main/handlers/callback.ts b/src/main/handlers/callback.ts index 664dba2..5512d54 100644 --- a/src/main/handlers/callback.ts +++ b/src/main/handlers/callback.ts @@ -22,13 +22,14 @@ export default async function handler({ } await config.storage.update( - { k1, data: { pubkey, sig, success: true } }, + { k1, session: { pubkey, sig, success: true } }, path, config ); return { response: { + status: "OK", // important status, confirms to wallet that auth was success success: true, k1, }, diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 9fa6715..59e3cf1 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -26,7 +26,7 @@ export default async function handler({ const lnurl = require("lnurl"); const encoded = lnurl.encode(inputUrl.toString()).toUpperCase(); - await config.storage.set({ k1, data: { k1, state } }, path, config); + await config.storage.set({ k1, session: { k1, state } }, path, config); return { response: { diff --git a/src/main/handlers/diagnostics.ts b/src/main/handlers/diagnostics.ts index 94f8d44..529ce28 100644 --- a/src/main/handlers/diagnostics.ts +++ b/src/main/handlers/diagnostics.ts @@ -241,7 +241,7 @@ export default async function handler({ checks.push(...(await testDelete(deleteMethod, getMethod))); // generic throw - } catch (e) { + } catch (e: any) { console.error(e); checks.push({ state: "failed", diff --git a/src/main/handlers/signin.tsx b/src/main/handlers/signin.tsx index 8dcf8a4..1822d20 100644 --- a/src/main/handlers/signin.tsx +++ b/src/main/handlers/signin.tsx @@ -113,6 +113,7 @@ export default async function handler({ try { signInValidation.parse(query, { errorMap }); } catch (e: any) { + console.error(e); return { error: formatErrorMessage(e), isRedirect: true }; } @@ -167,6 +168,7 @@ export default async function handler({ `, }; } catch (e: any) { + console.error(e); return { error: e.message || "Something went wrong", isRedirect: true }; } } diff --git a/src/main/utils/handlers.ts b/src/main/utils/handlers.ts index 24a6339..5efd40a 100644 --- a/src/main/utils/handlers.ts +++ b/src/main/utils/handlers.ts @@ -63,10 +63,12 @@ async function pagesHandler( try { output = await handler(args); } catch (e: any) { + console.error(e); output = { error: formatErrorMessage(e) }; } if (output.error) { + console.error(output.error); if (output.isRedirect) { const errorUrl = new URL(config.siteUrl + config.pages.error); errorUrl.searchParams.append("error", "OAuthSignin"); @@ -77,7 +79,7 @@ async function pagesHandler( res.setHeader(key, value) ); res.status(output.status || 500); - return res.end(output.error); + return res.send(output.error); } } @@ -93,7 +95,7 @@ async function pagesHandler( res.setHeader(key, value) ); res.status(output.status || 200); - return res.end(output.response); + return res.send(output.response); } else if (typeof output.response === "object") { Object.entries(output.headers || {}).forEach(([key, value]) => res.setHeader(key, value) @@ -131,6 +133,7 @@ async function appHandler( try { output = await handler(args); } catch (e: any) { + console.error(e); output = { error: formatErrorMessage(e) }; } diff --git a/src/main/utils/vanilla.ts b/src/main/utils/vanilla.ts index 9140c6f..afef5eb 100644 --- a/src/main/utils/vanilla.ts +++ b/src/main/utils/vanilla.ts @@ -9,7 +9,7 @@ export const vanilla = function ({ query: { redirect_uri: string; state: string }; errorUrl: string; }) { - var data: { k1?: string; lnurl?: string } | null; + var session: { k1?: string; lnurl?: string } | null; var pollTimeoutId: NodeJS.Timeout | undefined; var createIntervalId: NodeJS.Timeout | undefined; var networkRequestCount: number = 0; @@ -30,8 +30,8 @@ export const vanilla = function ({ // poll the api to see if the user has successfully authenticated function poll() { - if (!data || !data.k1) return; - const k1 = data.k1; + if (!session || !session.k1) return; + const k1 = session.k1; const params = new URLSearchParams({ k1 }); @@ -70,8 +70,8 @@ export const vanilla = function ({ // create a new lnurl and inject content into dom function create() { const params = new URLSearchParams({ state: query.state }); - if (data?.k1) { - params.append("k1", data.k1); + if (session?.k1) { + params.append("k1", session.k1); } return fetch("http://localhost:3000/api/lnauth/create", { @@ -83,10 +83,10 @@ export const vanilla = function ({ .then(function (r) { return r.json(); }) - .then(function (d) { - if (d && d.message) throw new Error(d.message); - data = d; - if (!data || !data.lnurl) return; + .then(function (data) { + if (data && data.message) throw new Error(data.message); + session = data; + if (!session || !session.lnurl) return; // show wrapper var wrapper = document.getElementById(hardConfig.ids.wrapper); @@ -103,13 +103,13 @@ export const vanilla = function ({ // inject copy text var copy = document.getElementById(hardConfig.ids.copy); if (copy) { - copy.innerHTML = data.lnurl; + copy.innerHTML = session.lnurl; } // inject qr src var qr = document.getElementById(hardConfig.ids.qr) as HTMLImageElement; if (qr) { - qr.src = hardConfig.apis.qr + "/" + data.lnurl; + qr.src = hardConfig.apis.qr + "/" + session.lnurl; qr.onerror = error.bind( undefined, new Error("Failed to load QR code") @@ -121,7 +121,7 @@ export const vanilla = function ({ hardConfig.ids.button ) as HTMLAnchorElement; if (button) { - button.href = `lightning:${data.lnurl}`; + button.href = `lightning:${session.lnurl}`; } }) .catch(error); diff --git a/src/main/validation/lnauth.ts b/src/main/validation/lnauth.ts index 3f7f9af..9235a80 100644 --- a/src/main/validation/lnauth.ts +++ b/src/main/validation/lnauth.ts @@ -68,7 +68,7 @@ export function formatErrorMessage(e: any): string { message = zodError[0].message; } } - } catch (e) { + } catch (e: any) { console.error(e); } } diff --git a/src/react/hooks/useLightningAuth.ts b/src/react/hooks/useLightningAuth.ts index dc72b4b..7f725b8 100644 --- a/src/react/hooks/useLightningAuth.ts +++ b/src/react/hooks/useLightningAuth.ts @@ -37,7 +37,7 @@ export function useLightningAuth({ const [lnurl, setUrl] = useState(null); useEffect(() => { - let data: { k1?: string; lnurl?: string } | null; + let session: { k1?: string; lnurl?: string } | null; let pollTimeoutId: NodeJS.Timeout | undefined; let createIntervalId: NodeJS.Timeout | undefined; const pollController = new AbortController(); @@ -63,7 +63,7 @@ export function useLightningAuth({ // poll the api to see if the user has successfully authenticated const poll = async () => { - const k1 = data?.k1; + const k1 = session?.k1; try { if (k1) { const { success } = await pollApiRequest( @@ -80,6 +80,7 @@ export function useLightningAuth({ } pollTimeoutId = setTimeout(poll, hardConfig.intervals.poll); } catch (e: any) { + console.error(e); if (!createController.signal.aborted) { error(e); } @@ -89,12 +90,13 @@ export function useLightningAuth({ // create a new lnurl and set it to state const create = async () => { try { - data = await createApiRequest( - { state, k1: data?.k1 }, + session = await createApiRequest( + { state, k1: session?.k1 }, createController.signal ); - setUrl(data?.lnurl || null); + setUrl(session?.lnurl || null); } catch (e: any) { + console.error(e); if (!createController.signal.aborted) { error(e); } diff --git a/src/react/utils/api.ts b/src/react/utils/api.ts index 3848a43..5a4825d 100644 --- a/src/react/utils/api.ts +++ b/src/react/utils/api.ts @@ -62,9 +62,9 @@ export async function createApiRequest( .then(function (r) { return r.json(); }) - .then((d) => { - if (d.message) throw new Error(d.message); - resolve(d); + .then((data) => { + if (data.message) throw new Error(data.message); + resolve(data); }) .catch(function (e) { reject(e); From 5bc6692086aa3252063d3c4012d886765b80d544 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 10:51:04 +0000 Subject: [PATCH 44/49] handling errors thrown by storage methods and fixing some bugs with diagnostics page --- TODO.md | 3 +- .../app/api/lnauth/[...lnauth]/config.ts | 3 +- .../auth-page/pages/api/lnauth/[...lnauth].ts | 3 +- src/main/handlers/callback.ts | 21 +++-- src/main/handlers/create.ts | 25 +++++- src/main/handlers/diagnostics.ts | 90 ++++++++++--------- src/main/handlers/poll.ts | 14 ++- src/main/handlers/token.ts | 27 +++++- 8 files changed, 131 insertions(+), 55 deletions(-) diff --git a/TODO.md b/TODO.md index 75e4872..c082fb9 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - error handling: of - Pages and App Router APIs handle errors consistently ✅ - error at end of API if no paths matched ✅ - - error thrown in `storage.get` and other storage methods + - error thrown in `storage.get` and other storage methods ✅ - check individual logic handlers and ensure all error cases that should be handler are, and that they are handled correctly. - "You are already logged in" error, passing. etc - making error messages user friendly @@ -16,6 +16,7 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - adding custom error code query param - documenting the error types - add jest tests where applicable +- test diagnostics API with KV and Drizzle ### Beta diff --git a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts index 3272505..cb199a6 100644 --- a/examples/app-router/app/api/lnauth/[...lnauth]/config.ts +++ b/examples/app-router/app/api/lnauth/[...lnauth]/config.ts @@ -24,7 +24,8 @@ const config: NextAuthLightningConfig = { return await storage.getItem(`k1:${k1}`); }, async update({ k1, session }) { - await storage.setItem(`k1:${k1}`, session); + const old = await storage.getItem(`k1:${k1}`); + await storage.updateItem(`k1:${k1}`, { ...old, ...session }); }, async delete({ k1 }) { await storage.removeItem(`k1:${k1}`); diff --git a/examples/auth-page/pages/api/lnauth/[...lnauth].ts b/examples/auth-page/pages/api/lnauth/[...lnauth].ts index ca94cdd..7973ff2 100644 --- a/examples/auth-page/pages/api/lnauth/[...lnauth].ts +++ b/examples/auth-page/pages/api/lnauth/[...lnauth].ts @@ -24,7 +24,8 @@ const config: NextAuthLightningConfig = { return await storage.getItem(`k1:${k1}`); }, async update({ k1, session }) { - await storage.setItem(`k1:${k1}`, session); + const old = await storage.getItem(`k1:${k1}`); + await storage.updateItem(`k1:${k1}`, { ...old, ...session }); }, async delete({ k1 }) { await storage.removeItem(`k1:${k1}`); diff --git a/src/main/handlers/callback.ts b/src/main/handlers/callback.ts index 5512d54..df07ab5 100644 --- a/src/main/handlers/callback.ts +++ b/src/main/handlers/callback.ts @@ -21,11 +21,22 @@ export default async function handler({ return { error: "Error in keys" }; } - await config.storage.update( - { k1, session: { pubkey, sig, success: true } }, - path, - config - ); + try { + await config.storage.update( + { k1, session: { pubkey, sig, success: true } }, + path, + config + ); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.update method. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + return { error: "Something went wrong" }; + } return { response: { diff --git a/src/main/handlers/create.ts b/src/main/handlers/create.ts index 59e3cf1..3661aba 100644 --- a/src/main/handlers/create.ts +++ b/src/main/handlers/create.ts @@ -14,7 +14,17 @@ export default async function handler({ // if an old k1 is provided, delete it if (typeof body?.k1 === "string") { - await config.storage.delete({ k1: body.k1 }, path, config); + try { + await config.storage.delete({ k1: body.k1 }, path, config); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.delete method. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + } } const k1 = randomBytes(32).toString("hex"); @@ -26,7 +36,18 @@ export default async function handler({ const lnurl = require("lnurl"); const encoded = lnurl.encode(inputUrl.toString()).toUpperCase(); - await config.storage.set({ k1, session: { k1, state } }, path, config); + try { + await config.storage.set({ k1, session: { k1, state } }, path, config); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.set method. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + return { error: "Something went wrong" }; + } return { response: { diff --git a/src/main/handlers/diagnostics.ts b/src/main/handlers/diagnostics.ts index 529ce28..7febee6 100644 --- a/src/main/handlers/diagnostics.ts +++ b/src/main/handlers/diagnostics.ts @@ -12,14 +12,13 @@ type Check = { export function testField( expected: Record, received: Record, - field: "k1" | "state" | "pubkey" | "sig" | "success", - method: "update" | "set" + field: "k1" | "state" | "pubkey" | "sig" | "success" ): Check { const state = received[field] !== expected[field] ? "failed" : "success"; return { state, - method, - message: `Expected 'session.${field}' to be '${expected[field]}', found '${received[field]}'.`, + method: "get", + message: `Expected 'session.${field}' to be '${expected[field]}', received '${received[field]}'.`, }; } @@ -29,19 +28,19 @@ export async function testSet( const checks: Check[] = []; try { await setMethod(); - checks.push({ state: "success", method: "set", - message: "Successfully ran to completion.", + message: "Invoked without throwing an error.", }); + return checks; } catch (e: any) { console.error(e); checks.push({ state: "failed", method: "set", - message: e.message || "An unexpected error occurred.", + message: "An unexpected error occurred.", }); return checks; } @@ -55,31 +54,30 @@ export async function testGet( try { const receivedSession = await getMethod(); + checks.push({ + state: "success", + method: "get", + message: "Invoked without throwing an error.", + }); if (!receivedSession) { checks.push({ state: "failed", - method: "set", - message: "Session data not found.", + method: "get", + message: "Session data not defined.", }); return checks; } - checks.push(testField(receivedSession, expectedSession, "k1", "set")); - checks.push(testField(receivedSession, expectedSession, "state", "set")); + checks.push(testField(receivedSession, expectedSession, "k1")); + checks.push(testField(receivedSession, expectedSession, "state")); - checks.push({ - state: "success", - method: "get", - message: - "Successfully ran to completion and returned the expected session data.", - }); return checks; } catch (e: any) { console.error(e); checks.push({ state: "failed", method: "get", - message: e.message || "An unexpected error occurred.", + message: "An unexpected error occurred.", }); return checks; } @@ -93,6 +91,11 @@ export async function testUpdate( const checks: Check[] = []; try { await updateMethod(); + checks.push({ + state: "success", + method: "update", + message: "Invoked without throwing an error.", + }); let receivedSession; try { @@ -102,7 +105,7 @@ export async function testUpdate( checks.push({ state: "failed", method: "get", - message: e.message || "An unexpected error occurred.", + message: "An unexpected error occurred.", }); return checks; } @@ -110,34 +113,24 @@ export async function testUpdate( if (!receivedSession) { checks.push({ state: "failed", - method: "update", + method: "get", message: "Session data not found.", }); return checks; } - checks.push(testField(receivedSession, expectedSession, "k1", "update")); - checks.push(testField(receivedSession, expectedSession, "state", "update")); - checks.push( - testField(receivedSession, expectedSession, "pubkey", "update") - ); - checks.push(testField(receivedSession, expectedSession, "sig", "update")); - checks.push( - testField(receivedSession, expectedSession, "success", "update") - ); + checks.push(testField(receivedSession, expectedSession, "k1")); + checks.push(testField(receivedSession, expectedSession, "state")); + checks.push(testField(receivedSession, expectedSession, "pubkey")); + checks.push(testField(receivedSession, expectedSession, "sig")); + checks.push(testField(receivedSession, expectedSession, "success")); - checks.push({ - state: "success", - method: "update", - message: - "Successfully ran to completion and returned the updated session data.", - }); return checks; } catch (e: any) { console.error(e); checks.push({ state: "failed", method: "update", - message: e.message || "An unexpected error occurred.", + message: "An unexpected error occurred.", }); return checks; } @@ -152,6 +145,12 @@ export async function testDelete( try { await deleteMethod(); + checks.push({ + state: "success", + method: "delete", + message: "Invoked without throwing an error.", + }); + let receivedSession; try { receivedSession = await getMethod(); @@ -160,10 +159,11 @@ export async function testDelete( checks.push({ state: "failed", method: "get", - message: e.message || "An unexpected error occurred.", + message: "An unexpected error occurred.", }); return checks; } + console.log({ receivedSession }); if (receivedSession) { checks.push({ @@ -177,15 +177,15 @@ export async function testDelete( checks.push({ state: "success", method: "delete", - message: "Successfully ran to completion.", + message: "Session data was deleted.", }); return checks; } catch (e: any) { console.error(e); checks.push({ state: "failed", - method: "update", - message: e.message || "An unexpected error occurred.", + method: "delete", + message: "An unexpected error occurred.", }); return checks; } @@ -217,7 +217,7 @@ export default async function handler({ : randomBytes(6).toString("hex"); const setSession = { k1, state }; - const updateSession = { k1, state, pubkey, sig, success: true }; + const updateSession = { pubkey, sig, success: true }; const setMethod = async () => await config.storage.set({ k1, session: setSession }, path, config); @@ -235,7 +235,13 @@ export default async function handler({ checks.push(...(await testGet(setSession, getMethod))); // update - checks.push(...(await testUpdate(updateSession, updateMethod, getMethod))); + checks.push( + ...(await testUpdate( + { ...setSession, ...updateSession }, + updateMethod, + getMethod + )) + ); // delete checks.push(...(await testDelete(deleteMethod, getMethod))); diff --git a/src/main/handlers/poll.ts b/src/main/handlers/poll.ts index 13908fb..b11c600 100644 --- a/src/main/handlers/poll.ts +++ b/src/main/handlers/poll.ts @@ -10,7 +10,19 @@ export default async function handler({ }: HandlerArguments): Promise { const { k1 } = pollValidation.parse(body, { errorMap }); - const session = await config.storage.get({ k1 }, path, config); + let session; + try { + session = await config.storage.get({ k1 }, path, config); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.get get. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + return { error: "Something went wrong" }; + } return { response: { diff --git a/src/main/handlers/token.ts b/src/main/handlers/token.ts index 83b73f1..499b084 100644 --- a/src/main/handlers/token.ts +++ b/src/main/handlers/token.ts @@ -22,11 +22,34 @@ export default async function handler({ let pubkey: string; if (grantType === "authorization_code") { if (!k1) return { error: "Missing code" }; - const session = await config.storage.get({ k1 }, path, config); + let session; + try { + session = await config.storage.get({ k1 }, path, config); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.get method. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + return { error: "Something went wrong" }; + } if (!session?.success) return { error: "Login was not successful" }; if (!session?.pubkey) return { error: "Missing pubkey" }; pubkey = session.pubkey; - await config.storage.delete({ k1 }, path, config); + + try { + await config.storage.delete({ k1 }, path, config); + } catch (e) { + console.error(e); + if (process.env.NODE_ENV === "development") + console.warn( + `An error occurred in the storage.delete method. To debug the error see: ${ + config.siteUrl + config.apis.diagnostics + }` + ); + } } else if (grantType === "refresh_token") { if (!refreshToken) return { error: "Missing refresh token" }; const data = await verifyRefreshToken(refreshToken, config); From 8d911627b6aef0f1498c7022c362fd50d93ee0ee Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 11:02:16 +0000 Subject: [PATCH 45/49] tweaking storage section of readme --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e253c9..8b24da4 100644 --- a/README.md +++ b/README.md @@ -444,7 +444,7 @@ const config: NextAuthLightningConfig = { # Storage -The `lnurl-auth` spec requires that a user's Lightning wallet trigger a callback as part of the authentication flow. For this reason, it may be that the device that scan the QR (e.g. a mobile) is not the same as the device trying to authenticate (e.g. a desktop). So, we require session storage to persist some data across devices, and ensure it's available when the callback is triggered. +The `lnurl-auth` spec requires that a user's Lightning wallet trigger a callback as part of the authentication flow. For this reason, it may be that the device that scan the QR (e.g. a mobile) is not the same device that's trying to authenticate (e.g. a desktop). So, we require session storage to persist some data and make it available across devices and ensure it's available when the callback is triggered. Data can be stored in a medium of your choice. For example a database, a document store, or a session store. Here's an example using [Vercel KV](https://vercel.com/docs/storage/vercel-kv): @@ -473,11 +473,17 @@ const config: NextAuthLightningConfig = { See more working examples in the [examples/](https://github.com/jowo-io/next-auth-lightning-provider/tree/main/examples) folder. -Once you have configured the storage methods you can launch your dev server test them locally on the diagnostics page: -`http://localhost:3000/api/lnauth/diagnostics` +Once you have configured the storage methods you can launch your dev server and test them locally on the diagnostics page: -You can also pass in your own custom values in the query params: -`http://localhost:3000/api/lnauth/diagnostics?k1=custom-k1&state=custom-state&pubkey=custom-pubkey&sig=custom-sig` +``` +http://localhost:3000/api/lnauth/diagnostics +``` + +You can also pass in your own custom session values via the query params: + +``` +http://localhost:3000/api/lnauth/diagnostics?k1=custom-k1&state=custom-state&pubkey=custom-pubkey&sig=custom-sig +``` > ℹ️ The diagnostics page will be **disabled** for production builds. From 2253fde48df37be5232c339a86a8364241431d58 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 11:04:39 +0000 Subject: [PATCH 46/49] tweak todo --- TODO.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index c082fb9..1c691c6 100644 --- a/TODO.md +++ b/TODO.md @@ -9,14 +9,11 @@ Below is a TODO list for further development of `next-auth-lightning-provider` - Pages and App Router APIs handle errors consistently ✅ - error at end of API if no paths matched ✅ - error thrown in `storage.get` and other storage methods ✅ - - check individual logic handlers and ensure all error cases that should be handler are, and that they are handled correctly. - - "You are already logged in" error, passing. etc + - check each handler's logic to ensure all edge case errors are handled. E.g. validation, undefined vars, etc - making error messages user friendly - - adding error http statuses are correct across all error instances - - adding custom error code query param - - documenting the error types + - adding extra custom error code query param and documenting them - add jest tests where applicable -- test diagnostics API with KV and Drizzle +- test diagnostics API with KV and ### Beta From 4c7ef6c19be67e76a5ff631934118abcdad2ed6f Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 11:11:29 +0000 Subject: [PATCH 47/49] remove type import into js file --- examples/plain-js/pages/api/auth/[...nextauth].js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plain-js/pages/api/auth/[...nextauth].js b/examples/plain-js/pages/api/auth/[...nextauth].js index fec1316..654a4a2 100644 --- a/examples/plain-js/pages/api/auth/[...nextauth].js +++ b/examples/plain-js/pages/api/auth/[...nextauth].js @@ -1,4 +1,4 @@ -import NextAuth, { AuthOptions } from "next-auth"; +import NextAuth from "next-auth"; import { lightningProvider } from "../lnauth/[...lnauth]"; From f2e0341afca61bfc92db19d16f717e4f8923df46 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 11:11:42 +0000 Subject: [PATCH 48/49] making update method match latest pattern --- examples/plain-js/pages/api/lnauth/[...lnauth].js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/plain-js/pages/api/lnauth/[...lnauth].js b/examples/plain-js/pages/api/lnauth/[...lnauth].js index 9b9544a..7173899 100644 --- a/examples/plain-js/pages/api/lnauth/[...lnauth].js +++ b/examples/plain-js/pages/api/lnauth/[...lnauth].js @@ -16,14 +16,11 @@ const config = { await storage.setItem(`k1:${k1}`, data); }, async get({ k1 }) { - const results = await storage.getItem(`k1:${k1}`); - - if (!results) throw new Error("Couldn't find item by k1"); - - return results; + return await storage.getItem(`k1:${k1}`); }, async update({ k1, data }) { - await storage.setItem(`k1:${k1}`, data); + const old = await storage.getItem(`k1:${k1}`); + await storage.updateItem(`k1:${k1}`, { ...old, ...session }); }, async delete({ k1 }) { await storage.removeItem(`k1:${k1}`); From 5fc6a1713e7fbc92213ed23d61e6ca1ab14ab675 Mon Sep 17 00:00:00 2001 From: jowo Date: Wed, 22 Nov 2023 13:54:15 +0000 Subject: [PATCH 49/49] fixing build clean script so it fully nukes all the build files and also updating tsconfig to ensure test files are excluded --- package.json | 4 ++-- tsconfig.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ad78b48..53bcd91 100644 --- a/package.json +++ b/package.json @@ -42,10 +42,10 @@ "main": "./index.js", "scripts": { "build": "tsc", - "clean": "rm -rf dist", + "clean": "rm -rf main react generators index.d.ts index.d.ts.map index.js", "dev": "tsc -w", "test": "jest", - "prebuild": "tsc --build --clean" + "prebuild": "npm run clean" }, "peerDependencies": { "next": "^12.2.5 || ^13 || ^14", diff --git a/tsconfig.json b/tsconfig.json index d368b88..5eac4e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,5 @@ } }, "include": ["src"], - "exclude": ["*.js", "*.d.ts", "dist", "examples"] + "exclude": ["**/*.js", "**/*.d.ts", "**/*.test.ts", "main", "generators", "react", "examples"] }