From c50509a6062a625ddaccc3d5d58ec0e9ed2ba841 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 1 May 2025 18:38:15 -0500 Subject: [PATCH 001/154] Custom Domains CRUD, Verification - ACM support - custom domains crud, resolvers, fragments - custom domains form, guidelines - custom domains context - domain verification every 5 minutes via pgboss - domain validation schema - basic custom domains middleware, to be completed - TODOs tracings --- .env.development | 9 +- api/acm/index.js | 53 +++++ api/resolvers/domain.js | 105 ++++++++ api/resolvers/index.js | 3 +- api/resolvers/sub.js | 3 + api/ssrApollo.js | 12 + api/typeDefs/domain.js | 41 ++++ api/typeDefs/index.js | 3 +- api/typeDefs/sub.js | 2 +- components/form.js | 10 +- components/item.module.css | 28 +++ components/territory-domains.js | 224 ++++++++++++++++++ components/territory-form.js | 11 + components/territory-header.js | 1 + docker-compose.yml | 14 +- docker/{s3 => aws}/cors.json | 0 docker/{s3 => aws}/init-s3.sh | 0 docs/dev/custom-domains-base.md | 123 ++++++++++ fragments/domains.js | 68 ++++++ fragments/subs.js | 5 + lib/domain-verification.js | 112 +++++++++ lib/domains.js | 28 +++ lib/validate.js | 9 + middleware.js | 61 ++++- pages/_app.js | 61 ++--- pages/api/domains/index.js | 40 ++++ .../migration.sql | 44 ++++ prisma/schema.prisma | 17 ++ worker/domainVerification.js | 168 +++++++++++++ worker/index.js | 2 + 30 files changed, 1212 insertions(+), 45 deletions(-) create mode 100644 api/acm/index.js create mode 100644 api/resolvers/domain.js create mode 100644 api/typeDefs/domain.js create mode 100644 components/territory-domains.js rename docker/{s3 => aws}/cors.json (100%) rename docker/{s3 => aws}/init-s3.sh (100%) create mode 100644 docs/dev/custom-domains-base.md create mode 100644 fragments/domains.js create mode 100644 lib/domain-verification.js create mode 100644 lib/domains.js create mode 100644 pages/api/domains/index.js create mode 100644 prisma/migrations/20250501200326_custom_domains/migration.sql create mode 100644 worker/domainVerification.js diff --git a/.env.development b/.env.development index 7572da564..b2f9e8245 100644 --- a/.env.development +++ b/.env.development @@ -114,7 +114,7 @@ NEXT_PUBLIC_EXTRA_LONG_POLL_INTERVAL=300000 # containers can't use localhost, so we need to use the container name IMGPROXY_URL_DOCKER=http://imgproxy:8080 -MEDIA_URL_DOCKER=http://s3:4566/uploads +MEDIA_URL_DOCKER=http://aws:4566/uploads # postgres container stuff POSTGRES_PASSWORD=password @@ -177,6 +177,7 @@ AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY PERSISTENCE=1 SKIP_SSL_CERT_DOWNLOAD=1 +LOCALSTACK_ENDPOINT=http://aws:4566 # tor proxy TOR_PROXY=http://tor:7050/ @@ -190,4 +191,8 @@ CPU_SHARES_IMPORTANT=1024 CPU_SHARES_MODERATE=512 CPU_SHARES_LOW=256 -NEXT_TELEMETRY_DISABLED=1 \ No newline at end of file +NEXT_TELEMETRY_DISABLED=1 + +# custom domains stuff +# DNS resolver for custom domain verification +DNS_RESOLVER=1.1.1.1 \ No newline at end of file diff --git a/api/acm/index.js b/api/acm/index.js new file mode 100644 index 000000000..cf76f4160 --- /dev/null +++ b/api/acm/index.js @@ -0,0 +1,53 @@ +import AWS from 'aws-sdk' + +AWS.config.update({ + region: 'us-east-1' +}) + +const config = {} + +export async function requestCertificate (domain) { + // for local development, we use the LOCALSTACK_ENDPOINT + if (process.env.NODE_ENV === 'development') { + config.endpoint = process.env.LOCALSTACK_ENDPOINT + } + + const acm = new AWS.ACM(config) + const params = { + DomainName: domain, + ValidationMethod: 'DNS', + Tags: [ + { + Key: 'ManagedBy', + Value: 'stacker.news' + } + ] + } + + const certificate = await acm.requestCertificate(params).promise() + return certificate.CertificateArn +} + +export async function describeCertificate (certificateArn) { + if (process.env.NODE_ENV === 'development') { + config.endpoint = process.env.LOCALSTACK_ENDPOINT + } + const acm = new AWS.ACM(config) + const certificate = await acm.describeCertificate({ CertificateArn: certificateArn }).promise() + return certificate +} + +export async function getCertificateStatus (certificateArn) { + const certificate = await describeCertificate(certificateArn) + return certificate.Certificate.Status +} + +export async function deleteCertificate (certificateArn) { + if (process.env.NODE_ENV === 'development') { + config.endpoint = process.env.LOCALSTACK_ENDPOINT + } + const acm = new AWS.ACM(config) + const result = await acm.deleteCertificate({ CertificateArn: certificateArn }).promise() + console.log(`delete certificate attempt for ${certificateArn}, result: ${JSON.stringify(result)}`) + return result +} diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js new file mode 100644 index 000000000..6a60f2aa0 --- /dev/null +++ b/api/resolvers/domain.js @@ -0,0 +1,105 @@ +import { validateSchema, customDomainSchema } from '@/lib/validate' +import { GqlAuthenticationError, GqlInputError } from '@/lib/error' +import { randomBytes } from 'node:crypto' +import { getDomainMapping } from '@/lib/domains' + +export default { + Query: { + customDomain: async (parent, { subName }, { models }) => { + return models.customDomain.findUnique({ where: { subName } }) + }, + domainMapping: async (parent, { domain }, { models }) => { + const mapping = await getDomainMapping(domain) + return mapping + } + }, + Mutation: { + setCustomDomain: async (parent, { subName, domain }, { me, models }) => { + if (!me) { + throw new GqlAuthenticationError() + } + + const sub = await models.sub.findUnique({ where: { name: subName } }) + if (!sub) { + throw new GqlInputError('sub not found') + } + + if (sub.userId !== me.id) { + throw new GqlInputError('you do not own this sub') + } + + domain = domain.trim() // protect against trailing spaces + if (domain && !validateSchema(customDomainSchema, { domain })) { + throw new GqlInputError('invalid domain format') + } + + const existing = await models.customDomain.findUnique({ where: { subName } }) + + if (domain) { + if (existing && existing.domain === domain && existing.status !== 'HOLD') { + throw new GqlInputError('domain already set') + } + + const initializeDomain = { + domain, + createdAt: new Date(), + status: 'PENDING', + verification: { + dns: { + state: 'PENDING', + cname: 'stacker.news', + // generate a random txt record only if it's a new domain + txt: existing?.domain === domain && existing.verification.dns.txt + ? existing.verification.dns.txt + : randomBytes(32).toString('base64') + }, + ssl: { + state: 'WAITING', + arn: null, + cname: null, + value: null + } + } + } + + const updatedDomain = await models.customDomain.upsert({ + where: { subName }, + update: { + ...initializeDomain + }, + create: { + ...initializeDomain, + sub: { + connect: { name: subName } + } + } + }) + + // schedule domain verification in 30 seconds + await models.$executeRaw` + INSERT INTO pgboss.job (name, data, retrylimit, retrydelay, startafter, keepuntil) + VALUES ('domainVerification', + jsonb_build_object('domainId', ${updatedDomain.id}::INTEGER), + 3, + 30, + now() + interval '30 seconds', + now() + interval '2 days')` + + return updatedDomain + } else { + try { + // delete any existing domain verification jobs + await models.$queryRaw` + DELETE FROM pgboss.job + WHERE name = 'domainVerification' + AND data->>'domainId' = ${existing.id}::TEXT` + + return await models.customDomain.delete({ where: { subName } }) + } catch (error) { + console.error(error) + throw new GqlInputError('failed to delete domain') + } + } + } + } +} diff --git a/api/resolvers/index.js b/api/resolvers/index.js index eccfaf1d0..b503518b6 100644 --- a/api/resolvers/index.js +++ b/api/resolvers/index.js @@ -20,6 +20,7 @@ import { GraphQLScalarType, Kind } from 'graphql' import { createIntScalar } from 'graphql-scalar' import paidAction from './paidAction' import vault from './vault' +import domain from './domain' const date = new GraphQLScalarType({ name: 'Date', @@ -56,4 +57,4 @@ const limit = createIntScalar({ export default [user, item, message, wallet, lnurl, notifications, invite, sub, upload, search, growth, rewards, referrals, price, admin, blockHeight, chainFee, - { JSONObject }, { Date: date }, { Limit: limit }, paidAction, vault] + domain, { JSONObject }, { Date: date }, { Limit: limit }, paidAction, vault] diff --git a/api/resolvers/sub.js b/api/resolvers/sub.js index 320670b66..aea327325 100644 --- a/api/resolvers/sub.js +++ b/api/resolvers/sub.js @@ -310,6 +310,9 @@ export default { return sub.SubSubscription?.length > 0 }, + customDomain: async (sub, args, { models }) => { + return models.customDomain.findUnique({ where: { subName: sub.name } }) + }, createdAt: sub => sub.createdAt || sub.created_at } } diff --git a/api/ssrApollo.js b/api/ssrApollo.js index d5513b7d9..23c4a8546 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -152,6 +152,17 @@ export function getGetServerSideProps ( const client = await getSSRApolloClient({ req, res }) + const isCustomDomain = req.headers.host !== process.env.NEXT_PUBLIC_URL.replace(/^https?:\/\//, '') + const subName = req.headers['x-stacker-news-subname'] || null + let customDomain = null + if (isCustomDomain && subName) { + customDomain = { + domain: req.headers.host, + subName + // TODO: custom branding + } + } + let { data: { me } } = await client.query({ query: ME }) // required to redirect to /signup on page reload @@ -216,6 +227,7 @@ export function getGetServerSideProps ( return { props: { ...props, + customDomain, me, price, blockHeight, diff --git a/api/typeDefs/domain.js b/api/typeDefs/domain.js new file mode 100644 index 000000000..d59fb3e38 --- /dev/null +++ b/api/typeDefs/domain.js @@ -0,0 +1,41 @@ +import { gql } from 'graphql-tag' + +export default gql` + extend type Query { + customDomain(subName: String!): CustomDomain + domainMapping(domain: String!): DomainMapping + } + extend type Mutation { + setCustomDomain(subName: String!, domain: String!): CustomDomain + } + type CustomDomain { + createdAt: Date! + updatedAt: Date! + domain: String! + subName: String! + lastVerifiedAt: Date + failedAttempts: Int + status: String + verification: CustomDomainVerification + } + + type DomainMapping { + domain: String! + subName: String! + } + type CustomDomainVerification { + dns: CustomDomainVerificationDNS + ssl: CustomDomainVerificationSSL + } + type CustomDomainVerificationDNS { + state: String + cname: String + txt: String + } + type CustomDomainVerificationSSL { + state: String + arn: String + cname: String + value: String + } +` diff --git a/api/typeDefs/index.js b/api/typeDefs/index.js index eb4e1e427..0acd7dc44 100644 --- a/api/typeDefs/index.js +++ b/api/typeDefs/index.js @@ -19,6 +19,7 @@ import blockHeight from './blockHeight' import chainFee from './chainFee' import paidAction from './paidAction' import vault from './vault' +import domain from './domain' const common = gql` type Query { @@ -39,4 +40,4 @@ const common = gql` ` export default [common, user, item, itemForward, message, wallet, lnurl, notifications, invite, - sub, upload, growth, rewards, referrals, price, admin, blockHeight, chainFee, paidAction, vault] + sub, upload, growth, rewards, referrals, price, admin, blockHeight, chainFee, domain, paidAction, vault] diff --git a/api/typeDefs/sub.js b/api/typeDefs/sub.js index 8401f1854..97ae104db 100644 --- a/api/typeDefs/sub.js +++ b/api/typeDefs/sub.js @@ -55,7 +55,7 @@ export default gql` nposts(when: String, from: String, to: String): Int! ncomments(when: String, from: String, to: String): Int! meSubscription: Boolean! - + customDomain: CustomDomain optional: SubOptional! } diff --git a/components/form.js b/components/form.js index c429ab7c9..12497a9b9 100644 --- a/components/form.js +++ b/components/form.js @@ -77,7 +77,7 @@ export function SubmitButton ({ ) } -function CopyButton ({ value, icon, ...props }) { +export function CopyButton ({ value, icon, append, ...props }) { const toaster = useToast() const [copied, setCopied] = useState(false) @@ -100,6 +100,14 @@ function CopyButton ({ value, icon, ...props }) { ) } + if (append) { + return ( + + {append} + + ) + } + return ( + ) + : verify} {data?.domain && data?.domain?.status !== 'ACTIVE' && ( From 9021358b26ad1e9ba5f05e25b960f27dc6f82522 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 31 Mar 2026 17:36:48 +0200 Subject: [PATCH 049/154] temp: snFetch dynamic getAgent import to avoid pulling node's http lib into edge middleware --- lib/fetch.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index db4e23255..3f451bf9b 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -1,6 +1,5 @@ import { TimeoutError, timeoutSignal } from '@/lib/time' import crossFetch from 'cross-fetch' -import { getAgent } from '@/lib/proxy' import { TOR_REGEXP } from '@/lib/url' export class FetchTimeoutError extends TimeoutError { @@ -61,7 +60,10 @@ export async function snFetch (url, { path, protocol = 'https', timeout = 10000, } // Server-only: get proxy agent for Tor and custom certs (unless agent: false) - const fetchAgent = isServer && agent !== false ? getAgent({ hostname: urlObj.hostname, cert, protocol: urlObj.protocol }) : undefined + // TODO/ADDRESS BEFORE REVIEW: dynamic import to avoid pulling Node.js 'http' into the edge runtime via middleware + const fetchAgent = isServer && agent !== false + ? (await import('@/lib/proxy')).getAgent({ hostname: urlObj.hostname, cert, protocol: urlObj.protocol }) + : undefined // Server-only: add user-agent header const headers = isServer ? snUserAgent(options.headers) : options.headers From 167eac34015497a52f7e09d0f2bcf80b451e8e15 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 1 Apr 2026 17:55:06 +0200 Subject: [PATCH 050/154] pin localstack to 4.12, enable only s3 and acm localstack mocks --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 877851c18..1b620b388 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -150,7 +150,7 @@ services: cpu_shares: "${CPU_SHARES_LOW}" aws: container_name: aws - image: localstack/localstack:latest + image: localstack/localstack:4.12 # healthcheck: # test: ["CMD-SHELL", "awslocal", "s3", "ls", "s3://uploads"] # interval: 10s @@ -164,6 +164,8 @@ services: env_file: *env_file environment: - DEBUG=1 + # we only need s3 and acm emulation, ELBv2 mocks are not provided by localstack + - SERVICES=s3,acm ports: - "4566:4566" expose: From a492bc9e152801f393e3d75f5cc1c62c379ffa5e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 1 Apr 2026 18:21:16 +0200 Subject: [PATCH 051/154] update: change NORMAL_POLL_INTERVAL to NORMAL_POLL_INTERVAL_MS --- components/territory-domains.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 3fe067d97..e8f79ad4c 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -3,7 +3,7 @@ import { Form, Input, SubmitButton, CopyButton } from './form' import { useMutation, useQuery } from '@apollo/client' import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' -import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants' +import { NORMAL_POLL_INTERVAL_MS, SSR } from '@/lib/constants' import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' import { useEffect, createContext, useContext, useState } from 'react' import Moon from '@/svgs/moon-fill.svg' @@ -150,7 +150,7 @@ export default function CustomDomainForm ({ sub }) { ? {} : { variables: { subName: sub.name }, - pollInterval: NORMAL_POLL_INTERVAL, + pollInterval: NORMAL_POLL_INTERVAL_MS, nextFetchPolicy: 'cache-and-network', onCompleted: ({ domain }) => { if (domain?.status !== 'PENDING') { From e270827f6c95a5b023838ebe2bdc1e751839af47 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 14:32:59 +0200 Subject: [PATCH 052/154] fix: delete old domains that have been on HOLD FOR 30 days or more --- worker/domainVerification.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index cc6697901..a6c3eeffc 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -92,7 +92,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss async function verifyDomain (domain, models) { // if we're still here and it has been 48 hours, put the domain on HOLD, stopping the verification process - if (datePivot(new Date(), { days: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { + if (datePivot(new Date(), { minutes: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { // delete certificate infos if any, it will trigger a deleteCertificate job // an ACM certificate would expire in 72 hours anyway, it's best to delete it await models.domainCertificate.delete({ where: { domainId: domain.id } }) @@ -296,11 +296,15 @@ async function logAttempt ({ domain, models, record, stage, status, message }) { export async function clearLongHeldDomains () { const models = createPrisma({ connectionParams: { connection_limit: 1 } }) try { - await models.domain.deleteMany({ - where: { status: 'HOLD', updatedAt: { lt: datePivot(new Date(), { days: 30 }) } } + const deleted = await models.domain.deleteMany({ + where: { status: 'HOLD', updatedAt: { lt: datePivot(new Date(), { days: -30 }) } } // 30 days ago }) + + if (deleted.count > 0) { + console.log(`cleared ${deleted.count} custom domains that have been on HOLD for 30 days or more`) + } } catch (error) { - console.error(`couldn't clear long held domains: ${error.message}`) + console.error(`couldn't clear old domains that have been on HOLD: ${error.message}`) } finally { await models.$disconnect() } From 4a35e94b4133ff4000f3e2c68fe32219653aec1d Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:03:04 +0200 Subject: [PATCH 053/154] domain creation: compact resuming and creation logic, fix duplicate domain creation attempt on resume --- api/resolvers/domain.js | 82 ++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 13ee5ffb9..9c868a99e 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -2,6 +2,8 @@ import { validateSchema, customDomainSchema } from '@/lib/validate' import { GqlAuthenticationError, GqlInputError } from '@/lib/error' import { SN_ADMIN_IDS } from '@/lib/constants' +const VERIFICATION_DELAY = 30000 // 30 seconds + async function cleanDomainVerificationJobs (domain, models) { // delete any existing domain verification job left await models.$queryRaw` @@ -10,6 +12,26 @@ async function cleanDomainVerificationJobs (domain, models) { AND data->>'domainId' = ${domain.id}::TEXT` } +/** + * Schedule a domain verification job to run in a given amount of time. + * @param {Object} options - The options for scheduling the job. + * @param {number} options.domainId - The ID of the domain to verify. + * @param {number} [options.startAfter=VERIFICATION_DELAY] - The amount of time to wait before running the job in milliseconds. + * @param {models} models - prisma models or transaction + */ +async function scheduleDomainVerificationJob ({ domainId, startAfter = VERIFICATION_DELAY }, models) { + await models.$executeRaw` + INSERT INTO pgboss.job (name, data, retrylimit, retrydelay, startafter, keepuntil, singletonkey) + VALUES ('domainVerification', + jsonb_build_object('domainId', ${domainId}::INTEGER), + 3, + 60, + now() + interval '${startAfter} milliseconds', + now() + interval '2 days', + 'domainVerification:' || ${domainId}::TEXT -- domain <-> job isolation + )` +} + export default { Query: { domain: async (parent, { subName }, { models }) => { @@ -45,63 +67,49 @@ export default { }) if (domainName) { - // validate the domain name - domainName = domainName.trim() // protect against trailing spaces + domainName = domainName.trim() await validateSchema(customDomainSchema, { domainName }) - // updating the domain name and recovering from HOLD is allowed + // updating the domain name, recovering from HOLD is allowed if (existing && existing.domainName === domainName && existing.status !== 'HOLD') { throw new GqlInputError('domain already set') } - // we should always make sure to get a new updatedAt timestamp - // to know when should we put the domain in HOLD during verification + // fresh updatedAt so we know when to put the domain in HOLD during verification const initializeDomain = { domainName, updatedAt: new Date(), status: 'PENDING' } + const resuming = existing?.status === 'HOLD' + const updatedDomain = await models.$transaction(async tx => { - // we're changing the domain name, delete the domain if it exists if (existing) { - // delete any existing domain verification job left await cleanDomainVerificationJobs(existing, tx) - // delete the domain if we're not resuming from HOLD - if (existing.status !== 'HOLD') { + if (!resuming) { await tx.domain.delete({ where: { subName } }) } } - const domain = await tx.domain.create({ - data: { - ...initializeDomain, - sub: { connect: { name: subName } } - } - }) - - // create the CNAME verification record - await tx.domainVerificationRecord.create({ - data: { - domainId: domain.id, - type: 'CNAME', - recordName: domainName, - recordValue: new URL(process.env.NEXT_PUBLIC_URL).host - } - }) - - // create the job to verify the domain in 30 seconds - await tx.$executeRaw` - INSERT INTO pgboss.job (name, data, retrylimit, retrydelay, startafter, keepuntil, singletonkey) - VALUES ('domainVerification', - jsonb_build_object('domainId', ${domain.id}::INTEGER), - 3, - 60, - now() + interval '30 seconds', - now() + interval '2 days', - 'domainVerification:' || ${domain.id}::TEXT -- domain <-> job isolation - )` + const domain = resuming + ? await tx.domain.update({ where: { id: existing.id }, data: initializeDomain }) + : await tx.domain.create({ + data: { ...initializeDomain, sub: { connect: { name: subName } } } + }) + + if (!resuming) { + await tx.domainVerificationRecord.create({ + data: { + domainId: domain.id, + type: 'CNAME', + recordName: domainName, + recordValue: new URL(process.env.NEXT_PUBLIC_URL).host + } + }) + } + await scheduleDomainVerificationJob({ domainId: domain.id }, tx) return domain }) From 686ba8059bbd25d5dadb72ef69e33299a3742fba Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:18:31 +0200 Subject: [PATCH 054/154] [domain-verification] consistent error object return instead of only its message --- lib/domain-verification.js | 22 +++++++++++----------- worker/domainVerification.js | 12 ++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/domain-verification.js b/lib/domain-verification.js index 677e26ca1..ab883695a 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -9,7 +9,7 @@ export async function issueDomainCertificate (domainName) { return { certificateArn, error: null } } catch (error) { console.error(`Failed to issue certificate for domain ${domainName}:`, error) - return { certificateArn: null, error: error.message } + return { certificateArn: null, error } } } @@ -21,7 +21,7 @@ export async function checkCertificateStatus (certificateArn) { return { certStatus, error: null } } catch (error) { console.error(`Certificate status check failed: ${error.message}`) - return { certStatus: 'FAILED', error: error.message } + return { certStatus: 'FAILED', error } } } @@ -32,7 +32,7 @@ export async function certDetails (certificateArn) { return { certificate, error: null } } catch (error) { console.error(`Certificate description failed: ${error.message}`) - return { certificate: null, error: error.message } + return { certificate: null, error } } } @@ -44,7 +44,7 @@ export async function getValidationValues (certificateArn) { } if (!certificate || !certificate.Certificate || !certificate.Certificate.DomainValidationOptions) { - return { cname: null, value: null, error: 'Certificate not found' } + return { cname: null, value: null, error: { message: 'Certificate not found' } } } return { @@ -60,7 +60,7 @@ export async function attachDomainCertificate (certificateArn) { return { error: null } } catch (error) { console.error(`Failed to attach certificate to elb: ${error.message}`) - return { error: error.message } + return { error } } } @@ -86,7 +86,7 @@ export async function verifyDNSRecord (type, recordName, recordValue) { resolver.setServers(dnsServers) } catch (error) { console.error(`[node:dns] Failed to set DNS servers: ${error.message}`) - return { valid: false, error: error.message } + return { valid: false, error } } let domainRecords = null @@ -98,13 +98,13 @@ export async function verifyDNSRecord (type, recordName, recordValue) { record.includes(recordValue) ) } else { - result.error = `Invalid DNS record type: ${type}` + result.error = { message: `Invalid DNS record type: ${type}` } } } catch (error) { if (error.code === 'ENODATA' || error.code === 'ENOTFOUND') { - result.error = `DNS record not found: ${error.code}` + result.error = { message: `DNS record not found: ${error.code}` } } else { - result.error = `DNS error: ${error.message}` + result.error = { message: `DNS error: ${error.message}` } } } @@ -118,7 +118,7 @@ export async function detachDomainCertificate (certificateArn) { return { error: null } } catch (error) { console.error(`Failed to detach certificate from elb: ${error.message}`) - return { error: error.message } + return { error } } } @@ -129,6 +129,6 @@ export async function deleteDomainCertificate (certificateArn) { return { error: null } } catch (error) { console.error(`Failed to delete certificate: ${error.message}`) - return { error: error.message } + return { error } } } diff --git a/worker/domainVerification.js b/worker/domainVerification.js index a6c3eeffc..26317000d 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -146,7 +146,7 @@ async function verifyDomain (domain, models) { async function verifyRecord (type, record, domain, models) { const result = await verifyDNSRecord(type, record.recordName, record.recordValue) const status = result.valid ? 'VERIFIED' : 'PENDING' - const message = result.valid ? `${type} record verified` : result.error || `${type} record is not valid` + const message = result.valid ? `${type} record verified` : result.error?.message || `${type} record is not valid` // log the record verification attempt await logAttempt({ domain, models, record, stage: type, status, message }) @@ -158,13 +158,13 @@ async function requestCertificate (domain, models) { let message = null // ask ACM to request a certificate for the domain - const { certificateArn, error } = await issueDomainCertificate(domain.domainName) + const { certificateArn, error: requestError } = await issueDomainCertificate(domain.domainName) if (certificateArn) { // check the status of the just created certificate const { certStatus, error: checkError } = await checkCertificateStatus(certificateArn) if (checkError) { - message = 'Could not check certificate status: ' + checkError + message = 'Could not check certificate status: ' + checkError?.message throw new Error(message) } else { try { @@ -188,7 +188,7 @@ async function requestCertificate (domain, models) { } } } else { - message = 'Could not request an ACM certificate: ' + error + message = 'Could not request an ACM certificate: ' + requestError?.message throw new Error(message) } @@ -224,7 +224,7 @@ async function getACMValidationValues (domain, models, certificateArn) { } } } else { - message = 'Could not get validation values: ' + error + message = 'Could not get validation values: ' + error?.message throw new Error(message) } @@ -264,7 +264,7 @@ async function attachACMCertificateToELB (domain, models, certificateArn) { if (!error) { message = `Certificate ${certificateArn} is now attached to ELB listener` } else { - message = `Could not attach certificate ${certificateArn} to ELB listener: ${error.message}` + message = `Could not attach certificate ${certificateArn} to ELB listener: ${error?.message}` throw new Error(message) } From 66e6d229d5b995a0f729245f2576298098f57e89 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:29:50 +0200 Subject: [PATCH 055/154] [domain-query] don't expose domain certificates to domain query, only allow territory owner and admins --- api/resolvers/domain.js | 23 ++++++++++++++++++----- fragments/domains.js | 4 ---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 9c868a99e..2d05f765d 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -1,5 +1,5 @@ import { validateSchema, customDomainSchema } from '@/lib/validate' -import { GqlAuthenticationError, GqlInputError } from '@/lib/error' +import { GqlAuthenticationError, GqlInputError, GqlAuthorizationError } from '@/lib/error' import { SN_ADMIN_IDS } from '@/lib/constants' const VERIFICATION_DELAY = 30000 // 30 seconds @@ -34,10 +34,23 @@ async function scheduleDomainVerificationJob ({ domainId, startAfter = VERIFICAT export default { Query: { - domain: async (parent, { subName }, { models }) => { + domain: async (parent, { subName }, { me, models }) => { + if (!me) { + throw new GqlAuthenticationError() + } + + if (!SN_ADMIN_IDS.includes(Number(me.id))) { + throw new GqlAuthorizationError('not an admin') + } + + const sub = await models.sub.findUnique({ where: { name: subName } }) + if (sub.userId !== me.id) { + throw new GqlAuthorizationError('you do not own this sub') + } + return models.domain.findUnique({ where: { subName }, - include: { records: true, attempts: true, certificate: true } + include: { records: true, attempts: true } }) } }, @@ -48,7 +61,7 @@ export default { } if (!SN_ADMIN_IDS.includes(Number(me.id))) { - throw new Error('not an admin') + throw new GqlAuthorizationError('not an admin') } const sub = await models.sub.findUnique({ where: { name: subName } }) @@ -57,7 +70,7 @@ export default { } if (sub.userId !== me.id) { - throw new GqlInputError('you do not own this sub') + throw new GqlAuthorizationError('you do not own this sub') } // we need to get the existing domain if we're updating or re-verifying diff --git a/fragments/domains.js b/fragments/domains.js index ccbfcd372..68188a069 100644 --- a/fragments/domains.js +++ b/fragments/domains.js @@ -53,7 +53,6 @@ export const DOMAIN_FULL_FIELDS = gql` ${DOMAIN_FIELDS} ${DOMAIN_VERIFICATION_RECORD_MAP_FIELDS} ${DOMAIN_VERIFICATION_ATTEMPT_FIELDS} - ${DOMAIN_CERTIFICATE_FIELDS} fragment DomainFullFields on Domain { ...DomainFields records { @@ -62,9 +61,6 @@ export const DOMAIN_FULL_FIELDS = gql` attempts { ...DomainVerificationAttemptFields } - certificate { - ...DomainCertificateFields - } } ` From de8c792e1794392de0c07dadc20831bb3c383cf2 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:30:37 +0200 Subject: [PATCH 056/154] [domain-form] ux: disable domain name input field if domain is registered --- components/territory-domains.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/territory-domains.js b/components/territory-domains.js index e8f79ad4c..26faa1395 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -193,6 +193,7 @@ export default function CustomDomainForm ({ sub }) { >
} name='domainName' From ef3bd56f42862f278ed78ed153d7deb96a371e42 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:33:04 +0200 Subject: [PATCH 057/154] [domain-form] gql: allow null domain names (for removal) --- api/typeDefs/domain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/typeDefs/domain.js b/api/typeDefs/domain.js index 9b8c5828f..0cc0766c2 100644 --- a/api/typeDefs/domain.js +++ b/api/typeDefs/domain.js @@ -6,7 +6,7 @@ export default gql` } extend type Mutation { - setDomain(subName: String!, domainName: String!): Domain + setDomain(subName: String!, domainName: String): Domain } type Domain { From b5bee87b73ddb32f4a9176bbc0bca8cb3bc7fd53 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:45:37 +0200 Subject: [PATCH 058/154] [domain-verification] check for null records during DNS verification --- worker/domainVerification.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 26317000d..1c8723741 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -144,6 +144,12 @@ async function verifyDomain (domain, models) { // verify a single record, logs the result and returns true if the record is valid async function verifyRecord (type, record, domain, models) { + if (!record) { + const message = `${type} record not found` + await logAttempt({ domain, models, record: null, stage: type, status: 'PENDING', message }) + return false + } + const result = await verifyDNSRecord(type, record.recordName, record.recordValue) const status = result.valid ? 'VERIFIED' : 'PENDING' const message = result.valid ? `${type} record verified` : result.error?.message || `${type} record is not valid` From 76d266ceb183ac4a2db182266c4307da061f2f69 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:46:03 +0200 Subject: [PATCH 059/154] [domain-verification] cleanup: better verification interval naming --- worker/domainVerification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 1c8723741..e506bfaa9 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -10,7 +10,7 @@ import { } from '@/lib/domain-verification' import { datePivot } from '@/lib/time' -const VERIFICATION_INTERVAL = (updatedAt) => { +const getVerificationInterval = (updatedAt) => { const pivot = datePivot(new Date(), { hours: -1 }) // 1 hour ago // after 1 hour, the verification interval is 5 minutes if (pivot > updatedAt) return 60 * 5 @@ -58,7 +58,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss if (result.status === 'PENDING') { // we still need to verify the domain, schedule the job to run again const newJobId = await boss.sendDebounced('domainVerification', { domainId }, { - startAfter: VERIFICATION_INTERVAL(domain.updatedAt), + startAfter: getVerificationInterval(domain.updatedAt), retryLimit: 3, retryDelay: 60 // on critical errors, retry every minute }, 30, `domainVerification:${domainId}`) From 3512f37424efc0fda58596f7e7ec0446efe1aa57 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:49:17 +0200 Subject: [PATCH 060/154] [domain-verification] fix: avoid record not found error when trying to delete certificates of a domain about to be put in HOLD (certs never issued) --- worker/domainVerification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index e506bfaa9..6d6e5ef57 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -95,7 +95,7 @@ async function verifyDomain (domain, models) { if (datePivot(new Date(), { minutes: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { // delete certificate infos if any, it will trigger a deleteCertificate job // an ACM certificate would expire in 72 hours anyway, it's best to delete it - await models.domainCertificate.delete({ where: { domainId: domain.id } }) + await models.domainCertificate.deleteMany({ where: { domainId: domain.id } }) return { status: 'HOLD', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it in 48 hours` } } From c03caa48e3a72996705d9d530ae1d971c39f6bd2 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 15:58:20 +0200 Subject: [PATCH 061/154] [domain-verification] fix: don't verify domains on HOLD, handles edge case of manual HOLD --- worker/domainVerification.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 6d6e5ef57..f4b4d2906 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -19,6 +19,13 @@ const getVerificationInterval = (updatedAt) => { } const VERIFICATION_HOLD_THRESHOLD = -2 // 2 days ago +// delete all related domain verification jobs for a domain +async function deleteDomainJobs (domainId, models) { + await models.$queryRaw` + DELETE FROM pgboss.job + WHERE name = 'domainVerification' AND data->>'domainId' = ${domainId}::TEXT` +} + export async function domainVerification ({ id: jobId, data: { domainId }, boss }) { // establish connection to database const models = createPrisma({ connectionParams: { connection_limit: 1 } }) @@ -40,6 +47,14 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss return } + // when a domain gets put on HOLD, we delete any remaining domain verification jobs + // this handles the edge case where a domain is put on HOLD manually or for some other reason + if (domain.status === 'HOLD') { + console.log(`domain ${domain.domainName} is on HOLD, skipping verification and deleting any remaining domain verification jobs`) + await deleteDomainJobs(domainId, models) + return + } + // start verification process const result = await verifyDomain(domain, models) console.log(`domain verification result: ${JSON.stringify(result)}`) @@ -77,10 +92,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) // delete any related domain verification jobs that may exist - await models.$queryRaw` - DELETE FROM pgboss.job - WHERE name = 'domainVerification' - AND data->>'domainId' = ${domainId}::TEXT` + await deleteDomainJobs(domainId, models) } throw error From 7a459b9c76c7ecd8004563e7a4691d42a0df38dc Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 16:16:18 +0200 Subject: [PATCH 062/154] [domain-form] fix: manual start and stop polling --- components/territory-domains.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 26faa1395..581a902f4 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -146,18 +146,21 @@ export default function CustomDomainForm ({ sub }) { const [setDomain] = useMutation(SET_DOMAIN) // Get the custom domain and poll for changes - const { data, refetch } = useQuery(GET_DOMAIN, SSR + const { data, refetch, stopPolling, startPolling } = useQuery(GET_DOMAIN, SSR ? {} : { variables: { subName: sub.name }, - pollInterval: NORMAL_POLL_INTERVAL_MS, - nextFetchPolicy: 'cache-and-network', - onCompleted: ({ domain }) => { - if (domain?.status !== 'PENDING') { - return { pollInterval: 0 } - } - } + nextFetchPolicy: 'cache-and-network' }) + + useEffect(() => { + if (data?.domain?.status !== 'PENDING') { + stopPolling() + } else { + startPolling(NORMAL_POLL_INTERVAL_MS) + } + }, [data?.domain?.status]) + const toaster = useToast() const { domainName, status } = data?.domain || {} From fe3b6192ea2398398fe14d9541870fdd8d8144c1 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 16:19:08 +0200 Subject: [PATCH 063/154] [domain-context]: always set the ssrDomain as the current custom domain --- components/territory-domains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 581a902f4..1836745a8 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -24,7 +24,7 @@ export const DomainProvider = ({ domain: ssrDomain, children }) => { // maintain the custom domain state across re-renders useEffect(() => { - if (ssrDomain && !domain) { + if (ssrDomain) { setDomain(ssrDomain) } }, [ssrDomain]) From 66aba73ee812dff3b2448ea865e67b94404690bf Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 16:29:52 +0200 Subject: [PATCH 064/154] [domain-verification] fix verification threshold minutes->days typo --- worker/domainVerification.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index f4b4d2906..af6c431e9 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -17,6 +17,7 @@ const getVerificationInterval = (updatedAt) => { // before 1 hour, the verification interval is 30 seconds return 30 } + const VERIFICATION_HOLD_THRESHOLD = -2 // 2 days ago // delete all related domain verification jobs for a domain @@ -104,7 +105,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss async function verifyDomain (domain, models) { // if we're still here and it has been 48 hours, put the domain on HOLD, stopping the verification process - if (datePivot(new Date(), { minutes: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { + if (datePivot(new Date(), { days: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { // delete certificate infos if any, it will trigger a deleteCertificate job // an ACM certificate would expire in 72 hours anyway, it's best to delete it await models.domainCertificate.deleteMany({ where: { domainId: domain.id } }) From 7fbd291ff2f629bf3c4d8aa49832c2f5cce8b4af Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 16:47:44 +0200 Subject: [PATCH 065/154] [domain-verification] remove startAfter customizable parameter from domain verification job scheduling --- api/resolvers/domain.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 2d05f765d..f3b75ec34 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -2,31 +2,28 @@ import { validateSchema, customDomainSchema } from '@/lib/validate' import { GqlAuthenticationError, GqlInputError, GqlAuthorizationError } from '@/lib/error' import { SN_ADMIN_IDS } from '@/lib/constants' -const VERIFICATION_DELAY = 30000 // 30 seconds - -async function cleanDomainVerificationJobs (domain, models) { +async function cleanDomainVerificationJobs (domainId, models) { // delete any existing domain verification job left await models.$queryRaw` DELETE FROM pgboss.job WHERE name = 'domainVerification' - AND data->>'domainId' = ${domain.id}::TEXT` + AND data->>'domainId' = ${domainId}::TEXT` } /** * Schedule a domain verification job to run in a given amount of time. * @param {Object} options - The options for scheduling the job. * @param {number} options.domainId - The ID of the domain to verify. - * @param {number} [options.startAfter=VERIFICATION_DELAY] - The amount of time to wait before running the job in milliseconds. * @param {models} models - prisma models or transaction */ -async function scheduleDomainVerificationJob ({ domainId, startAfter = VERIFICATION_DELAY }, models) { +async function scheduleDomainVerificationJob (domainId, models) { await models.$executeRaw` INSERT INTO pgboss.job (name, data, retrylimit, retrydelay, startafter, keepuntil, singletonkey) VALUES ('domainVerification', jsonb_build_object('domainId', ${domainId}::INTEGER), 3, 60, - now() + interval '${startAfter} milliseconds', + now() + interval '30 seconds', now() + interval '2 days', 'domainVerification:' || ${domainId}::TEXT -- domain <-> job isolation )` @@ -99,7 +96,7 @@ export default { const updatedDomain = await models.$transaction(async tx => { if (existing) { - await cleanDomainVerificationJobs(existing, tx) + await cleanDomainVerificationJobs(existing.id, tx) if (!resuming) { await tx.domain.delete({ where: { subName } }) } @@ -122,7 +119,7 @@ export default { }) } - await scheduleDomainVerificationJob({ domainId: domain.id }, tx) + await scheduleDomainVerificationJob(domain.id, tx) return domain }) @@ -132,7 +129,7 @@ export default { if (existing) { return await models.$transaction(async tx => { // delete any existing domain verification job left - await cleanDomainVerificationJobs(existing, tx) + await cleanDomainVerificationJobs(existing.id, tx) // delete the domain return await tx.domain.delete({ where: { subName } }) }) From 08398bbf98c54efab779f5714bb4cb1e3d1ea9ad Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 16:52:59 +0200 Subject: [PATCH 066/154] [domain-query] protect from null territory, nullable domainName in SET_DOMAIN fragment --- api/resolvers/domain.js | 4 ++++ fragments/domains.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index f3b75ec34..d87c3e5e0 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -41,6 +41,10 @@ export default { } const sub = await models.sub.findUnique({ where: { name: subName } }) + if (!sub) { + throw new GqlInputError('sub not found') + } + if (sub.userId !== me.id) { throw new GqlAuthorizationError('you do not own this sub') } diff --git a/fragments/domains.js b/fragments/domains.js index 68188a069..fae6ca393 100644 --- a/fragments/domains.js +++ b/fragments/domains.js @@ -74,7 +74,7 @@ export const GET_DOMAIN = gql` ` export const SET_DOMAIN = gql` - mutation SetDomain($subName: String!, $domainName: String!) { + mutation SetDomain($subName: String!, $domainName: String) { setDomain(subName: $subName, domainName: $domainName) { domainName } From 1d69243310b5ce6d65ceb0fea31803d6803e506d Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 17:48:35 +0200 Subject: [PATCH 067/154] [domain-verification] also remove ACM certificates when a domain transitions to HOLD --- .../migration.sql | 16 ++++++++++++++++ worker/domainVerification.js | 4 +--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index a846c00d6..111110bdb 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -183,6 +183,22 @@ FOR EACH ROW WHEN (NEW."userId" != OLD."userId") EXECUTE FUNCTION clear_domain_on_sub_takeover(); +-- SCENARIO: Domain transitions to HOLD +-- delete any associated certificate so the ACM cleanup trigger fires +CREATE OR REPLACE FUNCTION delete_certificate_on_domain_hold() +RETURNS TRIGGER AS $$ +BEGIN + DELETE FROM "DomainCertificate" WHERE "domainId" = NEW.id; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_delete_certificate_on_domain_hold +AFTER UPDATE ON "Domain" +FOR EACH ROW +WHEN (NEW.status = 'HOLD' AND OLD.status IS DISTINCT FROM 'HOLD') +EXECUTE FUNCTION delete_certificate_on_domain_hold(); + -- ask ACM to delete the certificate CREATE OR REPLACE FUNCTION ask_acm_to_delete_certificate() RETURNS TRIGGER AS $$ diff --git a/worker/domainVerification.js b/worker/domainVerification.js index af6c431e9..0876e56a1 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -105,10 +105,8 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss async function verifyDomain (domain, models) { // if we're still here and it has been 48 hours, put the domain on HOLD, stopping the verification process + // the DB trigger will delete the certificate (if any), which cascades into the ACM cleanup trigger if (datePivot(new Date(), { days: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { - // delete certificate infos if any, it will trigger a deleteCertificate job - // an ACM certificate would expire in 72 hours anyway, it's best to delete it - await models.domainCertificate.deleteMany({ where: { domainId: domain.id } }) return { status: 'HOLD', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it in 48 hours` } } From 16cfd542c121e55f5454ebc4a2e2c6ee216847c9 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 18:11:18 +0200 Subject: [PATCH 068/154] [domain-form] show 'active' when domain is fully verified --- components/territory-domains.js | 25 +++++++++++-------- .../migration.sql | 4 +-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 1836745a8..d2f030ffc 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -58,21 +58,23 @@ const DomainLabel = ({ domain, polling }) => { custom domain {domain && (
- {status !== 'HOLD' + {status === 'PENDING' ? ( <> {getStatusBadge('CNAME', records?.CNAME?.status)} {getStatusBadge('SSL', records?.SSL?.status)} ) - : ( - <> - HOLD - - - - - )} + : status === 'HOLD' + ? ( + <> + HOLD + + + + + ) + : active} {polling && }
)} @@ -130,10 +132,13 @@ const DomainGuidelines = ({ domain }) => { {dnsRecord({ record: records?.CNAME })}
)} + {records?.CNAME?.status === 'VERIFIED' && !records?.SSL && ( +

CNAME verified. Requesting SSL certificate...

+ )} {records?.SSL?.status === 'PENDING' && (
Step 2: Prepare your domain for SSL
-

We issued an SSL certificate for your domain. To validate it, add the following CNAME record:

+

We've issued an SSL certificate for your domain. To validate it, add the following CNAME record:

CNAME
{dnsRecord({ record: records?.SSL })}
diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index 111110bdb..44280f3f3 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -147,7 +147,7 @@ AFTER INSERT ON "DomainVerificationAttempt" FOR EACH ROW EXECUTE FUNCTION update_record_status_from_attempt(); --- SCENARIO: Territory got stopped after grace period +-- SCENARIO: Territory gets stopped after grace period -- HOLD the domain when the sub is stopped -- this is to prevent the domain from being used by another sub; -- won't delete anything but it will require a new verification attempt if the sub is resumed @@ -165,7 +165,7 @@ FOR EACH ROW WHEN (NEW.status = 'STOPPED') EXECUTE FUNCTION hold_domain_on_sub_stop(); --- SCENARIO: Territory got taken over by a different user +-- SCENARIO: Territory gets taken over by a different user -- clear the domain when the sub is taken over by a different user; -- this will delete the domain, its certificates, verification attempts, DNS records and brandings. -- will also trigger a request to ACM to delete the certificate because the domain is being deleted From 0c267cb6b019aaa53a01df1274017875e16cbf4b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 2 Apr 2026 19:03:41 +0200 Subject: [PATCH 069/154] [domain-form] re-verify domain on HOLD --- components/item.module.css | 10 ++++++- components/territory-domains.js | 52 ++++++++++++++++----------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/components/item.module.css b/components/item.module.css index 12bbd2f71..9a7c37366 100644 --- a/components/item.module.css +++ b/components/item.module.css @@ -314,11 +314,19 @@ a.link:visited { .refresh { cursor: pointer; + color: var(--theme-grey); + padding: 0; + margin-bottom: 2px; + transition: color 0.07s ease-in-out; +} + +.refresh svg { fill: var(--theme-grey); width: 1rem; height: 1rem; + transition: fill 0.07s ease-in-out; } -.refresh:hover { +.refresh:hover svg { fill: var(--bs-primary); } diff --git a/components/territory-domains.js b/components/territory-domains.js index d2f030ffc..bd522744f 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -8,7 +8,6 @@ import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' import { useEffect, createContext, useContext, useState } from 'react' import Moon from '@/svgs/moon-fill.svg' import ClipboardLine from '@/svgs/clipboard-line.svg' -import RefreshLine from '@/svgs/refresh-line.svg' import styles from './item.module.css' // Domain context for custom domains @@ -66,14 +65,7 @@ const DomainLabel = ({ domain, polling }) => { ) : status === 'HOLD' - ? ( - <> - HOLD - - - - - ) + ? HOLD : active} {polling && } @@ -200,27 +192,35 @@ export default function CustomDomainForm ({ sub }) { className='mb-2' >
- } - name='domainName' - placeholder='www.example.com' - /> - {data?.domain +
+ } + name='domainName' + placeholder='www.example.com' + /> +
+ {data?.domain && ( + + )} + {!data?.domain ? ( - + verify ) - : verify} + : data?.domain?.status === 'HOLD' + ? ( + re-verify + ) + : null}
- {data?.domain && data?.domain?.status !== 'ACTIVE' && ( + {data?.domain && data?.domain?.status === 'PENDING' && ( )} From 276b3db7307657ab614b18a4a70cd97db4d6a27b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 3 Apr 2026 14:24:49 +0200 Subject: [PATCH 070/154] [middleware][navigation] usePrefix and useNavKeys to support custom domains across territory and item navigation; signal external territory with icon and open in a new tab --- components/item-info.js | 23 ++++++++++++++++------- components/nav/desktop/second-bar.js | 4 +++- components/nav/index.js | 16 ++++++---------- components/nav/mobile/footer.js | 11 +++++++---- components/new-header.js | 3 ++- components/search.js | 7 ++----- components/territory-domains.js | 15 +++++++++++++++ components/territory-form.js | 3 ++- components/territory-header.js | 6 ++++-- components/top-header.js | 4 ++-- components/use-item-submit.js | 7 +++++-- lib/rss.js | 28 +++++++++++++++++++--------- lib/url.js | 10 ++++++++-- middleware.js | 25 ++++++++++++++----------- svgs/link-external.svg | 1 + 15 files changed, 106 insertions(+), 57 deletions(-) create mode 100644 svgs/link-external.svg diff --git a/components/item-info.js b/components/item-info.js index 187616077..f0b6d7299 100644 --- a/components/item-info.js +++ b/components/item-info.js @@ -31,6 +31,8 @@ import useCanEdit from './use-can-edit' import { getRetryPayInFailureUpdate, useRetryPayInByType } from './payIn/hooks/use-retry-pay-in' import { willAutoRetryPayIn } from './payIn/hooks/use-auto-retry-pay-ins' import { gql } from '@apollo/client' +import { useDomain } from './territory-domains' +import LinkExternal from '@/svgs/link-external.svg' function itemTitle (item) { let title = '' @@ -79,6 +81,7 @@ export default function ItemInfo ({ setDisableRetry, disableRetry, updatePayIn }) { const { me } = useMe() + const { domain } = useDomain() const router = useRouter() const showModal = useShowModal() const [hasNewComments, setHasNewComments] = useState(false) @@ -161,13 +164,19 @@ export default function ItemInfo ({ } - {item.subNames?.map(subName => ( - - - {' '}{subName} - - - ))} + {item.subNames?.map(subName => { + const isExternal = domain && subName !== domain.subName + const href = domain ? (isExternal ? `${process.env.NEXT_PUBLIC_URL}/~${subName}` : '/') : `/~${subName}` + + return ( + + {/* eslint-disable-next-line */} + + {' '}{subName} {isExternal && } + + + ) + })} {sub?.nsfw && nsfw} {item.freebie && !item.position && diff --git a/components/nav/desktop/second-bar.js b/components/nav/desktop/second-bar.js index 4120c3065..bfdffaa32 100644 --- a/components/nav/desktop/second-bar.js +++ b/components/nav/desktop/second-bar.js @@ -1,9 +1,11 @@ import { Nav, Navbar } from 'react-bootstrap' import { NavSelect, PostItem, Sorts, hasNavSelect } from '../common' import styles from '../../header.module.css' +import { useDomain } from '../../territory-domains' export default function SecondBar (props) { const { prefix, topNavKey, sub } = props + const { domain } = useDomain() if (!hasNavSelect(props)) return null return ( @@ -11,7 +13,7 @@ export default function SecondBar (props) { className={styles.navbarNav} activeKey={topNavKey} > - + {!domain && }
diff --git a/components/nav/index.js b/components/nav/index.js index 892d7d683..007e14276 100644 --- a/components/nav/index.js +++ b/components/nav/index.js @@ -3,23 +3,19 @@ import DesktopHeader from './desktop/header' import MobileHeader from './mobile/header' import StickyBar from './sticky-bar' import { PriceCarouselProvider } from './price-carousel' -import { useDomain } from '../territory-domains' +import { usePrefix, useNavKeys } from '../territory-domains' export default function Navigation ({ sub }) { const router = useRouter() - const { domain } = useDomain() const path = router.asPath.split('?')[0] + const prefix = usePrefix(sub) + const { topNavKey, dropNavKey } = useNavKeys(path, sub) const props = { - prefix: sub ? `/~${sub}` : '', + prefix, path, pathname: router.pathname, - topNavKey: domain - // on custom domains, the nav key is in the first path segment - ? path.split('/')[1] ?? '' - : path.split('/')[sub ? 2 : 1] ?? '', - dropNavKey: domain - ? path.split('/').slice(1).join('/') - : path.split('/').slice(sub ? 2 : 1).join('/'), + topNavKey, + dropNavKey, sub } diff --git a/components/nav/mobile/footer.js b/components/nav/mobile/footer.js index 6c6e3cd8e..a372c9436 100644 --- a/components/nav/mobile/footer.js +++ b/components/nav/mobile/footer.js @@ -6,6 +6,7 @@ import classNames from 'classnames' import Offcanvas from './offcanvas' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' +import { usePrefix, useNavKeys } from '../../territory-domains' function useDetectKeyboardOpen (minKeyboardHeight = 300, defaultValue) { const [isKeyboardOpen, setIsKeyboardOpen] = useState(defaultValue) @@ -32,17 +33,19 @@ export default function BottomBar ({ sub }) { const router = useRouter() const { me } = useMe() const isKeyboardOpen = useDetectKeyboardOpen(200, false) + const path = router.asPath.split('?')[0] + const prefix = usePrefix(sub) + const { topNavKey, dropNavKey } = useNavKeys(path, sub) if (isKeyboardOpen) { return null } - const path = router.asPath.split('?')[0] const props = { - prefix: sub ? `/~${sub}` : '', + prefix, path, - topNavKey: path.split('/')[sub ? 2 : 1] ?? '', - dropNavKey: path.split('/').slice(sub ? 2 : 1).join('/'), + topNavKey, + dropNavKey, sub } diff --git a/components/new-header.js b/components/new-header.js index 29cc5135c..c869c7fbd 100644 --- a/components/new-header.js +++ b/components/new-header.js @@ -2,6 +2,7 @@ import { ITEM_TYPES, ITEM_TYPES_UNIVERSAL } from '@/lib/constants' import BootstrapForm from 'react-bootstrap/Form' import { Select } from './form' import { useRouter } from 'next/router' +import { usePrefix } from './territory-domains' function ActiveBountiesCheckbox ({ prefix }) { const router = useRouter() @@ -28,7 +29,7 @@ function ActiveBountiesCheckbox ({ prefix }) { export default function NewHeader ({ type, sub }) { const router = useRouter() - const prefix = sub ? `/~${sub.name}` : '' + const prefix = usePrefix(sub?.name) const items = sub ? ITEM_TYPES_UNIVERSAL.concat(sub.postTypes.map(p => diff --git a/components/search.js b/components/search.js index ae83e600a..9b2505f22 100644 --- a/components/search.js +++ b/components/search.js @@ -16,10 +16,12 @@ import { whenToFrom } from '@/lib/time' import { useMe } from './me' import { useField } from 'formik' import { searchSchema } from '@/lib/validate' +import { usePrefix } from './territory-domains' export default function Search ({ sub }) { const router = useRouter() const { me } = useMe() + const prefix = usePrefix(sub) const q = typeof router.query.q === 'string' ? router.query.q : '' const from = typeof router.query.from === 'string' ? router.query.from : '' const to = typeof router.query.to === 'string' ? router.query.to : '' @@ -28,11 +30,6 @@ export default function Search ({ sub }) { const queryWhen = typeof router.query.when === 'string' ? router.query.when : '' const search = async values => { - let prefix = '' - if (sub) { - prefix = `/~${sub}` - } - const query = values.q?.trim() if (query) { const nextValues = { ...values, q: query } diff --git a/components/territory-domains.js b/components/territory-domains.js index bd522744f..cbaabaef8 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -40,6 +40,21 @@ export const DomainProvider = ({ domain: ssrDomain, children }) => { export const useDomain = () => useContext(DomainContext) +export function usePrefix (sub) { + const { domain } = useDomain() + if (domain) return '' + return sub ? `/~${sub}` : '' +} + +export function useNavKeys (path, sub) { + const { domain } = useDomain() + const offset = domain ? 1 : (sub ? 2 : 1) + return { + topNavKey: path.split('/')[offset] ?? '', + dropNavKey: path.split('/').slice(offset).join('/') + } +} + const getStatusBadge = (type, status) => { switch (status) { case 'VERIFIED': diff --git a/components/territory-form.js b/components/territory-form.js index da718531a..ab09623b0 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -17,6 +17,7 @@ import TerritoryDomains, { useDomain } from './territory-domains' import Link from 'next/link' import usePayInMutation from '@/components/payIn/hooks/use-pay-in-mutation' import { UNARCHIVE_TERRITORY, UPSERT_SUB } from '@/fragments/payIn' +import LinkExternal from '@/svgs/link-external.svg' function SatFilterRanges () { const { values } = useFormikContext() @@ -310,7 +311,7 @@ export default function TerritoryForm ({ sub }) { body={} /> } - {sub && domain && domain settings on stacker.news} + {sub && domain && domain settings on stacker.news } } ) diff --git a/components/territory-header.js b/components/territory-header.js index 21fb38b9a..afa9e4693 100644 --- a/components/territory-header.js +++ b/components/territory-header.js @@ -13,6 +13,7 @@ import { gql, useMutation } from '@apollo/client' import { useToast } from './toast' import ActionDropdown from './action-dropdown' import { TerritoryTransferDropdownItem } from './territory-transfer' +import { usePrefix } from './territory-domains' const SubscribeTerritoryContext = createContext({ refetchQueries: [] }) @@ -92,6 +93,7 @@ export function TerritoryInfo ({ sub, includeLink, truncated }) { export default function TerritoryHeader ({ sub }) { const { me } = useMe() const toaster = useToast() + const prefix = usePrefix(sub.name) const [toggleMuteSub] = useMutation( gql` @@ -119,12 +121,12 @@ export default function TerritoryHeader ({ sub }) {
{sub.name} - + {me && <> {(isMine ? ( - + ) : ( diff --git a/components/top-header.js b/components/top-header.js index cb075ac51..e6db33686 100644 --- a/components/top-header.js +++ b/components/top-header.js @@ -2,9 +2,11 @@ import { useRouter } from 'next/router' import { Form, Select, DatePicker } from './form' import { ITEM_SORTS, SUB_SORTS, USER_SORTS, WHENS } from '@/lib/constants' import { whenToFrom } from '@/lib/time' +import { usePrefix } from './territory-domains' export default function TopHeader ({ sub, cat }) { const router = useRouter() + const prefix = usePrefix(sub) const top = async values => { const { what, when, ...query } = values @@ -16,8 +18,6 @@ export default function TopHeader ({ sub, cat }) { return } - const prefix = sub ? `/~${sub}` : '' - if (typeof query.by !== 'undefined') { if (query.by === '' || (what === 'stackers' && (query.by === 'stacked' || !USER_SORTS.includes(query.by))) || diff --git a/components/use-item-submit.js b/components/use-item-submit.js index 44204d34b..d5ddd18e7 100644 --- a/components/use-item-submit.js +++ b/components/use-item-submit.js @@ -7,6 +7,7 @@ import { normalizeForwards, toastUpsertSuccessMessages } from '@/lib/form' import { USER_ID } from '@/lib/constants' import { composeCallbacks } from '@/lib/compose-callbacks' import { useMe } from './me' +import { useDomain } from './territory-domains' // this is intented to be compatible with upsert item mutations // so that it can be reused for all post types and comments and we don't have @@ -22,6 +23,7 @@ export default function useItemSubmit (mutation, const crossposter = useCrossposter() const [upsertItem] = usePayInMutation(mutation) const { me } = useMe() + const { domain } = useDomain() return useCallback( async ({ subNames: submittedSubNames, crosspost, title, options, bounty, status, ...values }, { resetForm }) => { @@ -107,11 +109,12 @@ export default function useItemSubmit (mutation, if (item) { await router.push(`/items/${item.id}`) } else { - await router.push(subNames.length === 1 ? `/~${subNames[0]}/new` : '/new') + const prefix = domain ? '' : (subNames.length === 1 ? `/~${subNames[0]}` : '') + await router.push(prefix + '/new') } } }, [me, upsertItem, router, crossposter, item, onSuccessfulSubmit, - navigateOnSubmit, extraValues, payInMutationOptions] + navigateOnSubmit, extraValues, payInMutationOptions, domain] ) } diff --git a/lib/rss.js b/lib/rss.js index 8c0580255..3cf9b6c7e 100644 --- a/lib/rss.js +++ b/lib/rss.js @@ -1,6 +1,6 @@ import getSSRApolloClient from '@/api/ssrApollo' -const SITE_URL = 'https://stacker.news' +const DEFAULT_SITE_URL = 'https://stacker.news' const SITE_TITLE = 'stacker news' const SITE_SUBTITLE = 'moderating forums with money' @@ -16,14 +16,14 @@ function escapeXml (unsafe) { }) } -const generateRssItem = (item) => { - const guid = `${SITE_URL}/items/${item.id}` +const generateRssItem = (item, siteUrl) => { + const guid = `${siteUrl}/items/${item.id}` const link = item.url || guid let title = item.title if (item.isJob) { title = item.title + ' \\ ' + item.company + ' \\ ' + `${item.location || ''}${item.location && item.remote ? ' or ' : ''}${item.remote ? 'Remote' : ''}` } - const category = item.subNames?.map(subName => `${subName}`).join('') ?? '' + const category = item.subNames?.map(subName => `${subName}`).join('') ?? '' return ` ${guid} @@ -38,23 +38,32 @@ const generateRssItem = (item) => { ` } -function generateRssFeed (items, sub = null) { - const itemsList = items.map(generateRssItem) +function generateRssFeed (items, sub = null, siteUrl = DEFAULT_SITE_URL) { + const itemsList = items.map(item => generateRssItem(item, siteUrl)) + const subPath = sub ? `/~${sub}` : '' return ` ${SITE_TITLE}${sub ? ` ~${sub}` : ''} - ${SITE_URL}${sub ? `/~${sub}` : ''} + ${siteUrl}${subPath} ${SITE_SUBTITLE} en ${new Date().toUTCString()} - + ${itemsList.join('')} ` } +function getSiteUrl (req) { + if (req.headers['x-stacker-news-subname']) { + const proto = req.headers['x-forwarded-proto'] || 'https' + return `${proto}://${req.headers.host}` + } + return DEFAULT_SITE_URL +} + export default function getGetRssServerSideProps (query, variables = null) { return async function ({ req, res, query: params }) { const emptyProps = { props: {} } // to avoid server side warnings @@ -65,8 +74,9 @@ export default function getGetRssServerSideProps (query, variables = null) { if (!items || error) return emptyProps + const siteUrl = getSiteUrl(req) res.setHeader('Content-Type', 'text/xml; charset=utf-8') - res.write(generateRssFeed(items, params?.sub)) + res.write(generateRssFeed(items, params?.sub, siteUrl)) res.end() return emptyProps diff --git a/lib/url.js b/lib/url.js index a8cbd5eb5..9b1c7c8f7 100644 --- a/lib/url.js +++ b/lib/url.js @@ -24,7 +24,10 @@ export function ensureProtocol (value) { } export function isExternal (url) { - return !url.startsWith(process.env.NEXT_PUBLIC_URL + '/') && !url.startsWith('/') + if (url.startsWith('/')) return false + if (url.startsWith(process.env.NEXT_PUBLIC_URL + '/')) return false + if (typeof window !== 'undefined' && url.startsWith(window.location.origin + '/')) return false + return true } export function isHashLink (url) { @@ -71,8 +74,11 @@ export function parseInternalLinks (href) { const internalURL = process.env.NEXT_PUBLIC_URL const { pathname, searchParams } = url + const isInternal = url.origin === internalURL || + (typeof window !== 'undefined' && url.origin === window.location.origin) + // ignore empty parts which exist due to pathname starting with '/' - if (isItemPath(pathname) && url.origin === internalURL) { + if (isItemPath(pathname) && isInternal) { const parts = pathname.split('/').filter(part => !!part) const itemId = parts[1] // check for valid item page due to referral links like /items/123456/r/ekzyis diff --git a/middleware.js b/middleware.js index 9b34060ac..e5e9a7083 100644 --- a/middleware.js +++ b/middleware.js @@ -15,7 +15,7 @@ const SN_REFEREE_LANDING = 'sn_referee_landing' // main domain const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) // territory paths that needs to be rewritten to ~subname -const SN_TERRITORY_PATHS = ['/~', '/recent', '/random', '/top', '/post', '/edit'] +const SN_TERRITORY_PATHS = ['/~', '/new', '/top', '/post', '/edit', '/rss'] async function customDomainMiddleware (request, domain, subName) { // clone the url to build on top of it @@ -28,20 +28,13 @@ async function customDomainMiddleware (request, domain, subName) { // TEST console.log('[domains] custom domain', domain, 'with subname', subName) // TEST - console.log('[domains] main domain', SN_MAIN_DOMAIN) // TEST + console.log('[domains] main domain', JSON.stringify(SN_MAIN_DOMAIN)) // TEST console.log('[domains] pathname', pathname) // TEST - console.log('[domains] searchParams', searchParams) // TEST + console.log('[domains] searchParams', JSON.stringify(searchParams)) // TEST + console.log('[domains] search', url.search) // TODO: handle auth sync - // if sub param exists and doesn't match the domain's subname, update it - if (searchParams.has('sub') && searchParams.get('sub') !== subName) { - console.log('[domains] setting sub to', subName) // TEST - searchParams.set('sub', subName) - url.search = searchParams.toString() - return NextResponse.redirect(url, { headers }) - } - // clean up the pathname from any subname if (pathname.startsWith('/~')) { const cleanPath = pathname.replace(/^\/~[^/]+/, '') || '/' @@ -51,6 +44,16 @@ async function customDomainMiddleware (request, domain, subName) { return NextResponse.redirect(url, { headers }) } + // if sub param exists and doesn't match the domain's subname, update it + if (searchParams.has('sub') && searchParams.get('sub') !== subName) { + console.log('[domains] setting sub to', subName) // TEST + searchParams.set('sub', subName) + url.search = searchParams.toString() + console.log('[domains] new searchParams', url.search) + console.log('[domains] new url', url) + return NextResponse.redirect(url, { headers }) + } + // if we're at the root or on some territory path, hide the subname by rewriting if (pathname === '/' || SN_TERRITORY_PATHS.some(p => pathname.startsWith(p))) { url.pathname = `/~${subName}${pathname === '/' ? '' : pathname}` diff --git a/svgs/link-external.svg b/svgs/link-external.svg new file mode 100644 index 000000000..a96dd42ef --- /dev/null +++ b/svgs/link-external.svg @@ -0,0 +1 @@ + \ No newline at end of file From 846914f5093c5717cc420a48d9439924ecc83345 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 5 Apr 2026 16:28:01 +0200 Subject: [PATCH 071/154] chore: upgrade Next.js from 14.2.25 to 15.5.14; upgrade to React 19; upgrade Apollo Client/Server, other libs that didn't support React 19 - Next.js 14.2.25 -> 15.5.14 - React 18.3.1 -> 19.2.4 - GraphQL 16.9.0 -> 16.13.2 - Apollo Client 3.11.8 -> 4.1.6 - Apollo Server 4.11.0 -> 5.5.0 - Apollo Server Integrations for Nextjs 3.1.0 -> 4.1.0 - \@yudiel/react-qr-scanner 2.0.8 -> 2.5.1 - qrcode.react 4.0.1 -> 4.2.0 - react-avatar-editor 13.0.2 -> 15.1.0 - recharts 2.13.0 -> 2.15.4 - react-is 19.2.4 override - can be removed after upgrade to recharts 3.x - eslint-plugin-next 14.2.15 -> 15.5.14 + eslint-config-next 15.5.14 --- api/ssrApollo.js | 16 +- components/account.js | 2 +- components/avatar.js | 3 +- components/block-height.js | 2 +- components/bookmark.js | 2 +- components/chain-fee.js | 2 +- components/comment.js | 3 +- components/delete.js | 2 +- components/editor/plugins/mentions.js | 2 +- components/editor/plugins/upload.js | 41 +- components/fee-button.js | 3 +- components/file-upload.js | 2 +- components/footer-rewards.js | 3 +- components/form.js | 21 +- components/invite.js | 3 +- components/invoice.js | 2 +- components/item-popover.js | 17 +- components/items.js | 2 +- components/lightning-auth.js | 3 +- components/link-form.js | 3 +- components/me.js | 2 +- components/mute.js | 2 +- components/nostr-auth.js | 3 +- components/notifications.js | 3 +- .../payIn/hooks/use-auto-retry-pay-ins.js | 2 +- components/payIn/hooks/use-pay-in-helper.js | 2 +- components/payIn/hooks/use-pay-in-mutation.js | 4 +- components/payIn/hooks/use-watch-pay-in.js | 2 +- components/price.js | 2 +- components/serviceworker.js | 3 +- components/snl.js | 3 +- components/sub-popover.js | 10 +- components/sub-select.js | 2 +- components/subscribe.js | 2 +- components/subscribeUser.js | 2 +- components/territory-form.js | 3 +- components/territory-header.js | 3 +- components/territory-list.js | 2 +- components/territory-payment-due.js | 2 +- components/territory-transfer.js | 3 +- components/upvote.js | 3 +- components/use-comments-view.js | 2 +- components/use-crossposter.js | 3 +- components/use-has-new-notes.js | 17 +- components/use-live-comments.js | 2 +- components/use-post-form-shared.js | 2 +- components/use-zap.js | 3 +- components/user-header.js | 6 +- components/user-list.js | 2 +- components/user-popover.js | 10 +- eslint.config.mjs | 16 + lib/apollo.js | 19 +- next.config.js | 8 +- package-lock.json | 5600 ++++++++++------- package.json | 29 +- pages/[name]/[type].js | 2 +- pages/[name]/index.js | 2 +- pages/[name]/territories.js | 2 +- pages/_app.js | 3 +- pages/invites/index.js | 3 +- pages/items/[id]/edit.js | 2 +- pages/items/[id]/index.js | 2 +- pages/items/[id]/ots.js | 2 +- pages/items/[id]/related.js | 2 +- pages/live.js | 2 +- pages/notifications.js | 2 +- pages/referrals/[when].js | 2 +- pages/rewards/[...when].js | 2 +- pages/rewards/index.js | 2 +- pages/satistics/graphs/[when].js | 3 +- pages/satistics/index.js | 2 +- pages/settings/index.js | 3 +- pages/settings/logins.js | 3 +- pages/settings/wallets.js | 2 +- pages/stackers/[sub]/[when].js | 3 +- pages/wallets/[type].js | 2 +- pages/withdraw.js | 2 +- pages/~/edit.js | 2 +- pages/~/index.js | 2 +- pages/~/new/[type].js | 2 +- pages/~/post.js | 2 +- wallets/client/hooks/diagnostics.js | 2 +- wallets/client/hooks/logger.js | 3 +- wallets/client/hooks/query.js | 3 +- worker/index.js | 20 +- 85 files changed, 3607 insertions(+), 2392 deletions(-) create mode 100644 eslint.config.mjs diff --git a/api/ssrApollo.js b/api/ssrApollo.js index 012a57463..302c7ac4d 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -1,4 +1,5 @@ import { ApolloClient, InMemoryCache } from '@apollo/client' +import { LocalState } from '@apollo/client/local-state' import { SchemaLink } from '@apollo/client/link/schema' import { makeExecutableSchema } from '@graphql-tools/schema' import resolvers from './resolvers' @@ -24,9 +25,10 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { if (req) { req = await multiAuthMiddleware(req, res) } - const session = req && await getServerSession(req, res, getAuthOptions(req)) + const session = req && (await getServerSession(req, res, getAuthOptions(req))) const client = new ApolloClient({ ssrMode: true, + link: new SchemaLink({ schema: makeExecutableSchema({ typeDefs, @@ -49,10 +51,13 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { } })() }), + cache: new InMemoryCache({ freezeResults: true }), + assumeImmutableResults: true, + defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', @@ -64,7 +69,14 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { nextFetchPolicy: 'no-cache', ssr: true } - } + }, + + /* + Inserted by Apollo Client 3->4 migration codemod. + If you are not using the `@client` directive in your application, + you can safely remove this option. + */ + localState: new LocalState({}) }) await client.clearStore() diff --git a/components/account.js b/components/account.js index 177ee3fed..308a12d63 100644 --- a/components/account.js +++ b/components/account.js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import { USER_ID } from '@/lib/constants' import { USER } from '@/fragments/users' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { UserListRow } from '@/components/user-list' import useCookie from '@/components/use-cookie' import Link from 'next/link' diff --git a/components/avatar.js b/components/avatar.js index c677a10d1..88fb40665 100644 --- a/components/avatar.js +++ b/components/avatar.js @@ -6,7 +6,8 @@ import EditImage from '@/svgs/image-edit-fill.svg' import Moon from '@/svgs/moon-fill.svg' import { useShowModal } from './modal' import { FileUpload } from './file-upload' -import { gql, useMutation } from '@apollo/client' +import { gql } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { useToast } from './toast' export default function Avatar ({ onSuccess }) { diff --git a/components/block-height.js b/components/block-height.js index 4c69e3f68..27098ebe5 100644 --- a/components/block-height.js +++ b/components/block-height.js @@ -1,5 +1,5 @@ import { createContext, useContext, useMemo } from 'react' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { NORMAL_POLL_INTERVAL_MS, SSR } from '@/lib/constants' import { BLOCK_HEIGHT } from '@/fragments/blockHeight' import { datePivot } from '@/lib/time' diff --git a/components/bookmark.js b/components/bookmark.js index b62c624c4..7952a27f1 100644 --- a/components/bookmark.js +++ b/components/bookmark.js @@ -1,4 +1,4 @@ -import { useMutation } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { gql } from 'graphql-tag' import Dropdown from 'react-bootstrap/Dropdown' import { useToast } from './toast' diff --git a/components/chain-fee.js b/components/chain-fee.js index 1925b07cc..4131e1c1f 100644 --- a/components/chain-fee.js +++ b/components/chain-fee.js @@ -1,5 +1,5 @@ import { createContext, useContext, useMemo } from 'react' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { NORMAL_POLL_INTERVAL_MS, SSR } from '@/lib/constants' import { CHAIN_FEE } from '@/fragments/chainFee' diff --git a/components/comment.js b/components/comment.js index bf795dc79..1c4748080 100644 --- a/components/comment.js +++ b/components/comment.js @@ -25,7 +25,8 @@ import { commentSubTreeRootId } from '@/lib/item' import Pin from '@/svgs/pushpin-fill.svg' import LinkToContext from './link-to-context' import Boost from './boost-button' -import { gql, useApolloClient } from '@apollo/client' +import { gql } from '@apollo/client' +import { useApolloClient } from '@apollo/client/react' import classNames from 'classnames' import useCallbackRef from './use-callback-ref' diff --git a/components/delete.js b/components/delete.js index 118927651..84897f71a 100644 --- a/components/delete.js +++ b/components/delete.js @@ -1,4 +1,4 @@ -import { useMutation } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { gql } from 'graphql-tag' import { useState } from 'react' import Alert from 'react-bootstrap/Alert' diff --git a/components/editor/plugins/mentions.js b/components/editor/plugins/mentions.js index 33c20b99b..f273ddc15 100644 --- a/components/editor/plugins/mentions.js +++ b/components/editor/plugins/mentions.js @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { createPortal } from 'react-dom' import Dropdown from 'react-bootstrap/Dropdown' -import { useLazyQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client/react' import { LexicalTypeaheadMenuPlugin, MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import useDebounceCallback from '@/components/use-debounce-callback' diff --git a/components/editor/plugins/upload.js b/components/editor/plugins/upload.js index 187ae9722..0b808788e 100644 --- a/components/editor/plugins/upload.js +++ b/components/editor/plugins/upload.js @@ -16,7 +16,8 @@ import { } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import { gql, useLazyQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useLazyQuery } from '@apollo/client/react' import { useFeeButton } from '@/components/fee-button' import { FileUpload } from '@/components/file-upload' import useDebounceCallback from '@/components/use-debounce-callback' @@ -262,25 +263,23 @@ function useLexicalUploadFees (editor) { const [updateUploadFees] = useLazyQuery(UPLOAD_FEES_QUERY, { fetchPolicy: 'no-cache', - nextFetchPolicy: 'no-cache', - onError: (err) => { - console.error(err) - }, - onCompleted: ({ uploadFees }) => { - const { uploadFees: feePerUpload, nUnpaid } = uploadFees - const totalFees = feePerUpload * nUnpaid - merge({ - uploadFees: { - term: `+ ${numWithUnits(feePerUpload, { abbreviate: false })} x ${nUnpaid}`, - label: 'upload fee', - op: '+', - modifier: cost => cost + totalFees, - omit: !totalFees - } - }) - } + nextFetchPolicy: 'no-cache' }) + const handleUploadFeesData = useCallback(({ data }) => { + const { uploadFees: feePerUpload, nUnpaid } = data.uploadFees + const totalFees = feePerUpload * nUnpaid + merge({ + uploadFees: { + term: `+ ${numWithUnits(feePerUpload, { abbreviate: false })} x ${nUnpaid}`, + label: 'upload fee', + op: '+', + modifier: cost => cost + totalFees, + omit: !totalFees + } + }) + }, [merge]) + // extracts S3 keys from text and updates upload fees const $refreshUploadFees = useCallback(() => { const isMarkdown = isMarkdownMode(editor) @@ -288,14 +287,18 @@ function useLexicalUploadFees (editor) { const text = $getRoot().getTextContent() || '' const s3Keys = [...text.matchAll(AWS_S3_URL_REGEXP)].map(m => Number(m[1])) updateUploadFees({ variables: { s3Keys } }) + .then(handleUploadFeesData) + .catch(err => console.error(err)) } else { const mediaNodes = $nodesOfType(MediaNode) const s3Keys = mediaNodes .flatMap(node => [...node.getSrc().matchAll(AWS_S3_URL_REGEXP)]) .map(m => Number(m[1])) updateUploadFees({ variables: { s3Keys } }) + .then(handleUploadFeesData) + .catch(err => console.error(err)) } - }, [updateUploadFees]) + }, [updateUploadFees, handleUploadFeesData]) // debounced version for update listener const refreshUploadFeesDebounced = useDebounceCallback(() => { diff --git a/components/fee-button.js b/components/fee-button.js index ba7926887..51cb65915 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -3,7 +3,8 @@ import Table from 'react-bootstrap/Table' import ActionTooltip from './action-tooltip' import Info from './info' import styles from './fee-button.module.css' -import { gql, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { ANON_FEE_MULTIPLIER, FAST_POLL_INTERVAL_MS, SSR } from '@/lib/constants' import { numWithUnits } from '@/lib/format' import { useMe } from './me' diff --git a/components/file-upload.js b/components/file-upload.js index a7287aa0f..b2e41f704 100644 --- a/components/file-upload.js +++ b/components/file-upload.js @@ -2,7 +2,7 @@ import { Fragment, useCallback, forwardRef, useRef } from 'react' import { UPLOAD_TYPES_ALLOW, MEDIA_URL } from '@/lib/constants' import { useToast } from './toast' import gql from 'graphql-tag' -import { useMutation } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import piexif from 'piexifjs' export const FileUpload = forwardRef(({ children, className, onSelect, onUpload, onSuccess, onError, multiple, avatar, allow }, ref) => { diff --git a/components/footer-rewards.js b/components/footer-rewards.js index 5d9b76cfc..9bdfbcf86 100644 --- a/components/footer-rewards.js +++ b/components/footer-rewards.js @@ -1,4 +1,5 @@ -import { gql, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import Link from 'next/link' import { RewardLine } from '@/pages/rewards' import { LONG_POLL_INTERVAL_MS, SSR } from '@/lib/constants' diff --git a/components/form.js b/components/form.js index 21d0d3e70..e2a05a593 100644 --- a/components/form.js +++ b/components/form.js @@ -10,7 +10,7 @@ import Row from 'react-bootstrap/Row' import styles from './form.module.css' import AddIcon from '@/svgs/add-fill.svg' import CloseIcon from '@/svgs/close-line.svg' -import { useLazyQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client/react' import { USER_SUGGESTIONS } from '@/fragments/users' import { SUB_SUGGESTIONS } from '@/fragments/subs' import { useToast } from './toast' @@ -452,16 +452,7 @@ export function BaseSuggest ({ getSuggestionsQuery, queryName, itemsField, children }) { - const [getSuggestions] = useLazyQuery(getSuggestionsQuery, { - onCompleted: data => { - query !== undefined && setSuggestions({ - array: data[itemsField] - .filter((...args) => filterItems(query, ...args)) - .map(transformItem), - index: 0 - }) - } - }) + const [getSuggestions] = useLazyQuery(getSuggestionsQuery) const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS) const resetSuggestions = useCallback(() => setSuggestions(INITIAL_SUGGESTIONS), []) useEffect(() => { @@ -469,6 +460,14 @@ export function BaseSuggest ({ // remove the leading character and any trailing spaces const q = query?.replace(/^[@ ~]+|[ ]+$/g, '').replace(/@[^\s]*$/, '').replace(/~[^\s]*$/, '') getSuggestions({ variables: { q, limit: 5 } }) + .then(({ data }) => { + query !== undefined && setSuggestions({ + array: data[itemsField] + .filter((...args) => filterItems(query, ...args)) + .map(transformItem), + index: 0 + }) + }) } else { resetSuggestions() } diff --git a/components/invite.js b/components/invite.js index 9eddbc434..64bbe328a 100644 --- a/components/invite.js +++ b/components/invite.js @@ -1,5 +1,6 @@ import { CopyInput } from './form' -import { gql, useMutation } from '@apollo/client' +import { gql } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { INVITE_FIELDS } from '@/fragments/invites' import styles from '@/styles/invites.module.css' import { useToast } from '@/components/toast' diff --git a/components/invoice.js b/components/invoice.js index fa6f76c2d..ffc5dd27a 100644 --- a/components/invoice.js +++ b/components/invoice.js @@ -5,7 +5,7 @@ import Qr, { QrSkeleton } from './qr' import { CompactLongCountdown } from './countdown' import PayerData from './payer-data' import Bolt11Info from './bolt11-info' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { INVOICE } from '@/fragments/invoice' import { FAST_POLL_INTERVAL_MS, SSR } from '@/lib/constants' import { WalletConfigurationError, WalletPaymentAggregateError } from '@/wallets/client/errors' diff --git a/components/item-popover.js b/components/item-popover.js index bf7de5dab..6b0fa89ea 100644 --- a/components/item-popover.js +++ b/components/item-popover.js @@ -1,18 +1,19 @@ import { ITEM } from '@/fragments/items' import errorStyles from '@/styles/error.module.css' -import { useLazyQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client/react' import classNames from 'classnames' import HoverablePopover from './hoverable-popover' import { ItemSkeleton, ItemSummary } from './item' +import { useCallback } from 'react' export default function ItemPopover ({ id, children }) { - const [getItem, { loading, data }] = useLazyQuery( - ITEM, - { - variables: { id }, - fetchPolicy: 'cache-first' - } - ) + const [execute, { loading, data }] = useLazyQuery(ITEM, { + fetchPolicy: 'cache-first' + }) + + const getItem = useCallback(() => { + execute({ variables: { id } }) + }, [execute, id]) return ( { + execute({ variables: { sub } }) + }, [execute, sub]) + return ( { - if (!hasNewNotes) { - clearNotifications() - } - } + nextFetchPolicy: 'cache-and-network' }) + useEffect(() => { + if (data && !data.hasNewNotes) { + clearNotifications() + } + }, [data?.hasNewNotes]) + return ( {children} diff --git a/components/use-live-comments.js b/components/use-live-comments.js index de5c5729e..37eb02332 100644 --- a/components/use-live-comments.js +++ b/components/use-live-comments.js @@ -1,5 +1,5 @@ import { useEffect, useState, useCallback } from 'react' -import { useQuery, useApolloClient } from '@apollo/client' +import { useApolloClient, useQuery } from '@apollo/client/react' import { SSR } from '../lib/constants' import preserveScroll from './preserve-scroll' import { GET_NEW_COMMENTS } from '../fragments/comments' diff --git a/components/use-post-form-shared.js b/components/use-post-form-shared.js index 51ebabb17..bb49419a9 100644 --- a/components/use-post-form-shared.js +++ b/components/use-post-form-shared.js @@ -3,7 +3,7 @@ import { AdvPostInitial } from './adv-post-form' import { SubSelectInitial } from './sub-select' import { normalizeForwards } from '@/lib/form' import useItemSubmit from './use-item-submit' -import { useApolloClient } from '@apollo/client' +import { useApolloClient } from '@apollo/client/react' import { useRouter } from 'next/router' /** diff --git a/components/use-zap.js b/components/use-zap.js index b66fbb495..558c6eb05 100644 --- a/components/use-zap.js +++ b/components/use-zap.js @@ -1,4 +1,5 @@ -import { gql, useApolloClient } from '@apollo/client' +import { gql } from '@apollo/client' +import { useApolloClient } from '@apollo/client/react' import { useCallback, useEffect, useRef, useState } from 'react' import { useAnimation } from '@/components/animation' import usePayInMutation from '@/components/payIn/hooks/use-pay-in-mutation' diff --git a/components/user-header.js b/components/user-header.js index daf38ccae..f6bc4e2a4 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -6,7 +6,8 @@ import { useRouter } from 'next/router' import Nav from 'react-bootstrap/Nav' import { useState, useEffect } from 'react' import { Form, Input, SubmitButton } from './form' -import { gql, useApolloClient, useMutation } from '@apollo/client' +import { gql } from '@apollo/client' +import { useApolloClient, useMutation } from '@apollo/client/react' import styles from './user-header.module.css' import navStyles from '@/styles/nav.module.css' import { useMe } from './me' @@ -238,8 +239,7 @@ function SocialLink ({ name, id }) { return ( // eslint-disable-next-line - - @{id} + @{id} ) } diff --git a/components/user-list.js b/components/user-list.js index 573663e6c..338b3e0b4 100644 --- a/components/user-list.js +++ b/components/user-list.js @@ -4,7 +4,7 @@ import { abbrNum, numWithUnits } from '@/lib/format' import styles from './item.module.css' import userStyles from './user-header.module.css' import React, { useEffect, useMemo, useState } from 'react' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import MoreFooter from './more-footer' import { useData } from './use-data' import Badges from './badge' diff --git a/components/user-popover.js b/components/user-popover.js index 11d7d65bf..848878f18 100644 --- a/components/user-popover.js +++ b/components/user-popover.js @@ -1,11 +1,12 @@ import { USER } from '@/fragments/users' import errorStyles from '@/styles/error.module.css' -import { useLazyQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client/react' import classNames from 'classnames' import Link from 'next/link' import HoverablePopover from './hoverable-popover' import ItemPopover from './item-popover' import { UserBase, UserSkeleton } from './user-list' +import { useCallback } from 'react' function StackingSince ({ since }) { return ( @@ -23,14 +24,17 @@ function StackingSince ({ since }) { } export default function UserPopover ({ name, children }) { - const [getUser, { loading, data }] = useLazyQuery( + const [execute, { loading, data }] = useLazyQuery( USER, { - variables: { name }, fetchPolicy: 'cache-first' } ) + const getUser = useCallback(() => { + execute({ variables: { name } }) + }, [execute, name]) + return ( 4 migration codemod. + If you are not using the `@client` directive in your application, + you can safely remove this option. + */ + localState: new LocalState({}) }) } diff --git a/next.config.js b/next.config.js index 7aa5fd462..f806e77b1 100644 --- a/next.config.js +++ b/next.config.js @@ -163,10 +163,6 @@ module.exports = withPlausibleProxy()({ source: '/.well-known/web-app-origin-association', destination: '/api/web-app-origin-association' }, - { - source: '/~:sub/:slug*\\?:query*', - destination: '/~/:slug*?:query*&sub=:sub' - }, { source: '/~:sub/:slug*', destination: '/~/:slug*?sub=:sub' @@ -213,8 +209,8 @@ module.exports = withPlausibleProxy()({ } ] }, - webpack: (config, { isServer, dev, defaultLoaders }) => { - if (isServer) { + webpack: (config, { isServer, dev, defaultLoaders, nextRuntime }) => { + if (isServer && nextRuntime === 'nodejs') { const workboxPlugin = new InjectManifest({ include: [/\/(icons|maskable|splash)\//, /\.(webp|mp4|woff|woff2)$/], swDest: '../../public/sw.js', diff --git a/package-lock.json b/package-lock.json index 0862eb073..5807ea70e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "stackernews", "version": "0.1.0", "dependencies": { - "@apollo/client": "^3.11.8", - "@apollo/server": "^4.11.0", - "@as-integrations/next": "^3.1.0", + "@apollo/client": "^4.1.6", + "@apollo/server": "^5.5.0", + "@as-integrations/next": "^4.1.0", "@auth/prisma-adapter": "^2.7.0", "@cashu/cashu-ts": "^2.4.1", "@graphile/depth-limit": "^0.3.1", @@ -36,7 +36,7 @@ "@shocknet/clink-sdk": "^1.4.0", "@slack/web-api": "^7.6.0", "@svgr/webpack": "^8.1.0", - "@yudiel/react-qr-scanner": "^2.0.8", + "@yudiel/react-qr-scanner": "^2.5.1", "acorn": "^8.12.1", "ajv": "^8.17.1", "async-mutex": "^0.5.0", @@ -58,7 +58,7 @@ "formik": "^2.4.6", "github-slugger": "^2.0.0", "google-protobuf": "^3.21.4", - "graphql": "^16.9.0", + "graphql": "^16.13.2", "graphql-scalar": "^0.1.0", "graphql-tag": "^2.12.6", "graphql-type-json": "^0.3.2", @@ -77,7 +77,7 @@ "mdast-util-to-string": "^4.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", - "next": "^14.2.25", + "next": "15.5.14", "next-auth": "^4.24.8", "next-plausible": "^3.12.2", "next-seo": "^6.6.0", @@ -90,21 +90,22 @@ "pg-boss": "^9.0.3", "piexifjs": "^1.0.6", "prisma": "^5.20.0", - "qrcode.react": "^4.0.1", - "react": "^18.3.1", - "react-avatar-editor": "^13.0.2", + "qrcode.react": "^4.2.0", + "react": "19.2.4", + "react-avatar-editor": "^15.1.0", "react-bootstrap": "^2.10.5", "react-countdown": "^2.3.6", "react-datepicker": "^7.4.0", - "react-dom": "^18.3.1", + "react-dom": "19.2.4", "react-ios-pwa-prompt": "^1.8.4", "react-lite-youtube-embed": "^3.3.3", "react-particles": "^2.12.2", "react-select": "^5.10.2", "react-string-replace": "^1.1.1", "react-twitter-embed": "^4.0.4", - "recharts": "^2.13.0", + "recharts": "^2.15.4", "remove-markdown": "^0.5.5", + "rxjs": "^7.8.2", "sass": "^1.79.5", "serviceworker-storage": "^0.1.0", "source-map": "^0.8.0-beta.0", @@ -130,8 +131,9 @@ "yup": "^1.4.0" }, "devDependencies": { - "@next/eslint-plugin-next": "^14.2.15", + "@next/eslint-plugin-next": "15.5.14", "eslint": "^9.12.0", + "eslint-config-next": "^15.5.14", "jest": "^29.7.0", "standard": "^17.1.2" }, @@ -186,30 +188,30 @@ } }, "node_modules/@apollo/client": { - "version": "3.11.8", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.11.8.tgz", - "integrity": "sha512-CgG1wbtMjsV2pRGe/eYITmV5B8lXUCYljB2gB/6jWTFQcrvirUVvKg7qtFdjYkQSFbIffU1IDyxgeaN81eTjbA==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.1.6.tgz", + "integrity": "sha512-ak8uzqmKeX3u9BziGf83RRyODAJKFkPG72hTNvEj4WjMWFmuKW2gGN1i3OfajKT6yuGjvo+n23ES2zqWDKFCZg==", + "license": "MIT", + "workspaces": [ + "dist", + "codegen", + "scripts/codemods/ac3-to-ac4" + ], "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", "@wry/equality": "^0.5.6", "@wry/trie": "^0.5.0", "graphql-tag": "^2.12.6", - "hoist-non-react-statics": "^3.3.2", "optimism": "^0.18.0", - "prop-types": "^15.7.2", - "rehackt": "^0.1.0", - "response-iterator": "^0.2.6", - "symbol-observable": "^4.0.0", - "ts-invariant": "^0.10.3", - "tslib": "^2.3.0", - "zen-observable-ts": "^1.2.5" + "tslib": "^2.3.0" }, "peerDependencies": { - "graphql": "^15.0.0 || ^16.0.0", - "graphql-ws": "^5.5.5", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", + "graphql": "^16.0.0", + "graphql-ws": "^5.5.5 || ^6.0.3", + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "rxjs": "^7.3.0", "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" }, "peerDependenciesMeta": { @@ -228,54 +230,50 @@ } }, "node_modules/@apollo/server": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.13.0.tgz", - "integrity": "sha512-t4GzaRiYIcPwYy40db6QjZzgvTr9ztDKBddykUXmBb2SVjswMKXbkaJ5nPeHqmT3awr9PAaZdCZdZhRj55I/8A==", - "deprecated": "Apollo Server v4 is end-of-life since January 26, 2026. As long as you are already using a non-EOL version of Node.js, upgrading to v5 should take only a few minutes. See https://www.apollographql.com/docs/apollo-server/previous-versions for details.", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", + "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", "license": "MIT", "dependencies": { "@apollo/cache-control-types": "^1.0.3", - "@apollo/server-gateway-interface": "^1.1.1", + "@apollo/server-gateway-interface": "^2.0.0", "@apollo/usage-reporting-protobuf": "^4.1.1", - "@apollo/utils.createhash": "^2.0.2", - "@apollo/utils.fetcher": "^2.0.0", - "@apollo/utils.isnodelike": "^2.0.0", - "@apollo/utils.keyvaluecache": "^2.1.0", - "@apollo/utils.logger": "^2.0.0", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", "@apollo/utils.usagereporting": "^2.1.0", - "@apollo/utils.withrequired": "^2.0.0", - "@graphql-tools/schema": "^9.0.0", - "@types/express": "^4.17.13", - "@types/express-serve-static-core": "^4.17.30", - "@types/node-fetch": "^2.6.1", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", "async-retry": "^1.2.1", + "body-parser": "^2.2.2", "content-type": "^1.0.5", "cors": "^2.8.5", - "express": "^4.21.1", + "finalhandler": "^2.1.0", "loglevel": "^1.6.8", - "lru-cache": "^7.10.1", - "negotiator": "^0.6.3", - "node-abort-controller": "^3.1.1", - "node-fetch": "^2.6.7", - "uuid": "^9.0.0", - "whatwg-mimetype": "^3.0.0" + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=14.16.0" + "node": ">=20" }, "peerDependencies": { - "graphql": "^16.6.0" + "graphql": "^16.11.0" } }, "node_modules/@apollo/server-gateway-interface": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-1.1.1.tgz", - "integrity": "sha512-pGwCl/po6+rxRmDMFgozKQo2pbsSwE91TpsDBAOgf74CRDPXHHtM88wbwjab0wMMZh95QfR45GGyDIdhY24bkQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "license": "MIT", "dependencies": { "@apollo/usage-reporting-protobuf": "^4.1.1", - "@apollo/utils.fetcher": "^2.0.0", - "@apollo/utils.keyvaluecache": "^2.1.0", - "@apollo/utils.logger": "^2.0.0" + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" @@ -358,62 +356,35 @@ "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/server/node_modules/@graphql-tools/merge": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", - "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@apollo/server/node_modules/@graphql-tools/schema": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", - "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", - "dependencies": { - "@graphql-tools/merge": "^8.4.1", - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@apollo/server/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", - "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@apollo/server/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" + "node": "20 || >=22" } }, "node_modules/@apollo/server/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@apollo/server/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/@apollo/usage-reporting-protobuf": { @@ -449,81 +420,87 @@ } }, "node_modules/@apollo/utils.createhash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-2.0.2.tgz", - "integrity": "sha512-UkS3xqnVFLZ3JFpEmU/2cM2iKJotQXMoSTgxXsfQgXLC5gR1WaepoXagmYnPSA7Q/2cmnyTYK5OgAgoC4RULPg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", "license": "MIT", "dependencies": { - "@apollo/utils.isnodelike": "^2.0.1", + "@apollo/utils.isnodelike": "^3.0.0", "sha.js": "^2.4.11" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@apollo/utils.fetcher": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-2.0.1.tgz", - "integrity": "sha512-jvvon885hEyWXd4H6zpWeN3tl88QcWnHp5gWF5OPF34uhvoR+DFqcNxs9vrRaBBSY3qda3Qe0bdud7tz2zGx1A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@apollo/utils.isnodelike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-2.0.1.tgz", - "integrity": "sha512-w41XyepR+jBEuVpoRM715N2ZD0xMD413UiJx8w5xnAZD2ZkSJnMJBoIzauK83kJpSgNuR6ywbV29jG9NmxjK0Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==", "license": "MIT", "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@apollo/utils.keyvaluecache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-2.1.1.tgz", - "integrity": "sha512-qVo5PvUUMD8oB9oYvq4ViCjYAMWnZ5zZwEjNF37L2m1u528x5mueMlU+Cr1UinupCgdB78g+egA1G98rbJ03Vw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", "dependencies": { - "@apollo/utils.logger": "^2.0.1", - "lru-cache": "^7.14.1" + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/@apollo/utils.keyvaluecache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" + "node": "20 || >=22" } }, "node_modules/@apollo/utils.logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-2.0.1.tgz", - "integrity": "sha512-YuplwLHaHf1oviidB7MxnCXAdHp3IqYV8n0momZ3JfLniae92eYqMIx+j5qJFX6WKJPs6q7bczmV4lXIsTu5Pg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@apollo/utils.withrequired": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-2.0.1.tgz", - "integrity": "sha512-YBDiuAX9i1lLc6GeTy1m7DGLFn/gMnvXqlalOIMjM7DeOgIacEjjfwPqb0M1CQ2v11HhR15d1NmxJoRCfrNqcA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/@as-integrations/next": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@as-integrations/next/-/next-3.1.0.tgz", - "integrity": "sha512-grNdI/LgUebsYSc2wmoIFBpF+d7RjRO4nUWrM+8hVhSFzJlLd5vBfTcvFQ0LaOnhHpQmDBqNFIss+97Q+NZHiQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@as-integrations/next/-/next-4.1.0.tgz", + "integrity": "sha512-QZ+bGlLn1ahYpVt60CofkgJUB0XhG6JghkoCywCnzueQM9qoQct0WPGMNZuWXyfBxXJGCbeF/tsIlATlHT1HoQ==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { - "@apollo/server": "^4.0.0", - "next": "^12.0.0 || ^13.0.0 || ^14.0.0" + "@apollo/server": "^5.0.0", + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@auth/core": { @@ -760,22 +737,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -1983,25 +1944,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.10.tgz", - "integrity": "sha512-6jrMilUAJhktTr56kACL8LnWC5hx3Lf27BS0R0DSyW/OoJfb/iTHeE96V3b1dgKG3FSFdd/0culnYWMkjcKCig==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", @@ -2524,6 +2466,39 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -3488,622 +3463,599 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@istanbuljs/load-nyc-config": { + "node_modules/@img/colour": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" + "funding": { + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/console/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" - }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/console/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" - }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/console/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/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/console/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/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@jest/console/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/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "funding": { + "url": "https://opencollective.com/libvips" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/@jest/core/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" - }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/@jest/core/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" - }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, - "node_modules/@jest/core/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" - }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, - "node_modules/@jest/core/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/@jest/core/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } }, - "node_modules/@jest/core/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, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "dependencies": { - "jest-get-type": "^29.6.3" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/reporters/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==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "p-locate": "^4.1.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/reporters/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/@jest/reporters/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/@jest/reporters/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@jest/reporters/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==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/transform": { + "node_modules/@jest/console": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", + "@types/node": "*", "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", + "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { + "node_modules/@jest/console/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==", @@ -4118,7 +4070,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/chalk": { + "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4134,7 +4086,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/color-convert": { + "node_modules/@jest/console/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==", @@ -4146,13 +4098,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { + "node_modules/@jest/console/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/@jest/transform/node_modules/supports-color": { + "node_modules/@jest/console/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==", @@ -4164,24 +4116,54 @@ "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/types/node_modules/ansi-styles": { + "node_modules/@jest/core/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==", @@ -4196,7 +4178,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/types/node_modules/chalk": { + "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4212,7 +4194,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/types/node_modules/color-convert": { + "node_modules/@jest/core/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==", @@ -4224,13 +4206,39 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/types/node_modules/color-name": { + "node_modules/@jest/core/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/@jest/types/node_modules/supports-color": { + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/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==", @@ -4242,459 +4250,899 @@ "node": ">=8" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "engines": { - "node": ">=6.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, "engines": { - "node": ">=6.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@lexical/clipboard": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.41.0.tgz", - "integrity": "sha512-Ex5lPkb4NBBX1DCPzOAIeHBJFH1bJcmATjREaqpnTfxCbuOeQkt44wchezUA0oDl+iAxNZ3+pLLWiUju9icoSA==", - "license": "MIT", + "node_modules/@jest/reporters/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": { - "@lexical/html": "0.41.0", - "@lexical/list": "0.41.0", - "@lexical/selection": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@lexical/code": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.41.0.tgz", - "integrity": "sha512-0hoNi1KC9/N3SBOGcOcFqnT0OpwmcRRAhfxTKMGqfCtCvAMzULVwZ8RWc9/NV9bKYESgBTW5D9xkDANP2mspHg==", - "license": "MIT", + "node_modules/@jest/reporters/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": { - "@lexical/utils": "0.41.0", - "lexical": "0.41.0", - "prismjs": "^1.30.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lexical/code-shiki": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/code-shiki/-/code-shiki-0.41.0.tgz", - "integrity": "sha512-j6z0/ZVtZsYvIOpOukZzyImMt+SyRhMVO+2b5LKjDtIFQbLv0WD6V5EdKd9h0TQxSiFIAu5vyUQpNGesXbN2Qw==", - "license": "MIT", + "node_modules/@jest/reporters/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": { - "@lexical/code": "0.41.0", - "@lexical/utils": "0.41.0", - "@shikijs/core": "^3.7.0", - "@shikijs/engine-javascript": "^3.7.0", - "@shikijs/langs": "^3.7.0", - "@shikijs/themes": "^3.7.0", - "lexical": "0.41.0", - "shiki": "^3.7.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@lexical/devtools-core": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.41.0.tgz", - "integrity": "sha512-FzJtluBhBc8bKS11TUZe72KoZN/hnzIyiiM0SPJAsPwGpoXuM01jqpXQGybWf/1bWB+bmmhOae7O4Nywi/Csuw==", - "license": "MIT", + "node_modules/@jest/reporters/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/@jest/reporters/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, "dependencies": { - "@lexical/html": "0.41.0", - "@lexical/link": "0.41.0", - "@lexical/mark": "0.41.0", - "@lexical/table": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "peerDependencies": { - "react": ">=17.x", - "react-dom": ">=17.x" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/dragon": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.41.0.tgz", - "integrity": "sha512-gBEqkk8Q6ZPruvDaRcOdF1EK9suCVBODzOCcR+EnoJTaTjfDkCM7pkPAm4w90Wa1wCZEtFHvCfas+jU9MDSumg==", - "license": "MIT", + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "@lexical/extension": "0.41.0", - "lexical": "0.41.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@lexical/extension": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.41.0.tgz", - "integrity": "sha512-sF4SPiP72yXvIGchmmIZ7Yg2XZTxNLOpFEIIzdqG7X/1fa1Ham9P/T7VbrblWpF6Ei5LJtK9JgNVB0hb4l3o1g==", - "license": "MIT", + "node_modules/@jest/reporters/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": { - "@lexical/utils": "0.41.0", - "@preact/signals-core": "^1.11.0", - "lexical": "0.41.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@lexical/hashtag": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.41.0.tgz", - "integrity": "sha512-tFWM74RW4KU0E/sj2aowfWl26vmLUTp331CgVESnhQKcZBfT40KJYd57HEqBDTfQKn4MUhylQCCA0hbpw6EeFQ==", - "license": "MIT", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "dependencies": { - "@lexical/text": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/headless": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/headless/-/headless-0.41.0.tgz", - "integrity": "sha512-MH8oDuUKdM/Jq0c9vlEEkCL9pEQg4SwyrABBGIbFf+87VBJ5EWDdG9g1vJq7fKSDxfhFux7F5+i+zgUnxOQR/g==", - "license": "MIT", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, "dependencies": { - "happy-dom": "^20.0.0", - "lexical": "0.41.0" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/history": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.41.0.tgz", - "integrity": "sha512-kGoVWsiOn62+RMjRolRa+NXZl8jFwxav6GNDiHH8yzivtoaH8n1SwUfLJELXCzeqzs81HySqD4q30VLJVTGoDg==", - "license": "MIT", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, "dependencies": { - "@lexical/extension": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/html": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.41.0.tgz", - "integrity": "sha512-3RyZy+H/IDKz2D66rNN/NqYx87xVFrngfEbyu1OWtbY963RUFnopiVHCQvsge/8kT04QSZ7U/DzjVFqeNS6clg==", - "license": "MIT", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, "dependencies": { - "@lexical/selection": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/link": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.41.0.tgz", - "integrity": "sha512-Rjtx5cGWAkKcnacncbVsZ1TqRnUB2Wm4eEVKpaAEG41+kHgqghzM2P+UGT15yROroxJu8KvAC9ISiYFiU4XE1w==", - "license": "MIT", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, "dependencies": { - "@lexical/extension": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/list": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.41.0.tgz", - "integrity": "sha512-RXvB+xcbzVoQLGRDOBRCacztG7V+bI95tdoTwl8pz5xvgPtAaRnkZWMDP+yMNzMJZsqEChdtpxbf0NgtMkun6g==", - "license": "MIT", + "node_modules/@jest/transform/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": { - "@lexical/extension": "0.41.0", - "@lexical/selection": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@lexical/mark": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.41.0.tgz", - "integrity": "sha512-UO5WVs9uJAYIKHSlYh4Z1gHrBBchTOi21UCYBIZ7eAs4suK84hPzD+3/LAX5CB7ZltL6ke5Sly3FOwNXv/wfpA==", - "license": "MIT", + "node_modules/@jest/transform/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": { - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lexical/markdown": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.41.0.tgz", - "integrity": "sha512-bzI73JMXpjGFhqUWNV6KqfjWcgAWzwFT+J3RHtbCF5rysC8HLldBYojOgAAtPfXqfxyv2mDzsY7SoJ75s9uHZA==", - "license": "MIT", + "node_modules/@jest/transform/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": { - "@lexical/code": "0.41.0", - "@lexical/link": "0.41.0", - "@lexical/list": "0.41.0", - "@lexical/rich-text": "0.41.0", - "@lexical/text": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@lexical/offset": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.41.0.tgz", - "integrity": "sha512-2RHBXZqC8gm3X9C0AyRb0M8w7zJu5dKiasrif+jSKzsxPjAUeF1m95OtIOsWs1XLNUgASOSUqGovDZxKJslZfA==", - "license": "MIT", - "dependencies": { - "lexical": "0.41.0" - } + "node_modules/@jest/transform/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/@lexical/overflow": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.41.0.tgz", - "integrity": "sha512-Iy6ZiJip8X14EBYt1zKPOrXyQ4eG9JLBEoPoSVBTiSbVd+lYicdUvaOThT0k0/qeVTN9nqTaEltBjm56IrVKCQ==", - "license": "MIT", + "node_modules/@jest/transform/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": { - "lexical": "0.41.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@lexical/plain-text": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.41.0.tgz", - "integrity": "sha512-HIsGgmFUYRUNNyvckun33UQfU7LRzDlxymHUq67+Bxd5bXqdZOrStEKJXuDX+LuLh/GXZbaWNbDLqwLBObfbQg==", - "license": "MIT", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "dependencies": { - "@lexical/clipboard": "0.41.0", - "@lexical/dragon": "0.41.0", - "@lexical/selection": "0.41.0", - "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lexical/react": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.41.0.tgz", - "integrity": "sha512-7+GUdZUm6sofWm+zdsWAs6cFBwKNsvsHezZTrf6k8jrZxL461ZQmbz/16b4DvjCGL9r5P1fR7md9/LCmk8TiCg==", - "license": "MIT", + "node_modules/@jest/types/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": { - "@floating-ui/react": "^0.27.16", - "@lexical/devtools-core": "0.41.0", - "@lexical/dragon": "0.41.0", - "@lexical/extension": "0.41.0", - "@lexical/hashtag": "0.41.0", - "@lexical/history": "0.41.0", - "@lexical/link": "0.41.0", - "@lexical/list": "0.41.0", - "@lexical/mark": "0.41.0", - "@lexical/markdown": "0.41.0", - "@lexical/overflow": "0.41.0", - "@lexical/plain-text": "0.41.0", - "@lexical/rich-text": "0.41.0", - "@lexical/table": "0.41.0", - "@lexical/text": "0.41.0", - "@lexical/utils": "0.41.0", - "@lexical/yjs": "0.41.0", - "lexical": "0.41.0", - "react-error-boundary": "^6.0.0" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "react": ">=17.x", - "react-dom": ">=17.x" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@lexical/react/node_modules/@floating-ui/react": { - "version": "0.27.16", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", - "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", - "license": "MIT", + "node_modules/@jest/types/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": { - "@floating-ui/react-dom": "^2.1.6", - "@floating-ui/utils": "^0.2.10", - "tabbable": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lexical/rich-text": { + "node_modules/@jest/types/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/@jest/types/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/@jest/types/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/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@lexical/clipboard": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.41.0.tgz", - "integrity": "sha512-yUcr7ZaaVTZNi8bow4CK1M8jy2qyyls1Vr+5dVjwBclVShOL/F/nFyzBOSb6RtXXRbd3Ahuk9fEleppX/RNIdw==", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.41.0.tgz", + "integrity": "sha512-Ex5lPkb4NBBX1DCPzOAIeHBJFH1bJcmATjREaqpnTfxCbuOeQkt44wchezUA0oDl+iAxNZ3+pLLWiUju9icoSA==", "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.41.0", - "@lexical/dragon": "0.41.0", + "@lexical/html": "0.41.0", + "@lexical/list": "0.41.0", "@lexical/selection": "0.41.0", "@lexical/utils": "0.41.0", "lexical": "0.41.0" } }, - "node_modules/@lexical/selection": { + "node_modules/@lexical/code": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.41.0.tgz", - "integrity": "sha512-1s7/kNyRzcv5uaTwsUL28NpiisqTf5xZ1zNukLsCN1xY+TWbv9RE9OxIv+748wMm4pxNczQe/UbIBODkbeknLw==", + "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.41.0.tgz", + "integrity": "sha512-0hoNi1KC9/N3SBOGcOcFqnT0OpwmcRRAhfxTKMGqfCtCvAMzULVwZ8RWc9/NV9bKYESgBTW5D9xkDANP2mspHg==", "license": "MIT", "dependencies": { - "lexical": "0.41.0" + "@lexical/utils": "0.41.0", + "lexical": "0.41.0", + "prismjs": "^1.30.0" } }, - "node_modules/@lexical/table": { + "node_modules/@lexical/code-shiki": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.41.0.tgz", - "integrity": "sha512-d3SPThBAr+oZ8O74TXU0iXM3rLbrAVC7/HcOnSAq7/AhWQW8yMutT51JQGN+0fMLP9kqoWSAojNtkdvzXfU/+A==", + "resolved": "https://registry.npmjs.org/@lexical/code-shiki/-/code-shiki-0.41.0.tgz", + "integrity": "sha512-j6z0/ZVtZsYvIOpOukZzyImMt+SyRhMVO+2b5LKjDtIFQbLv0WD6V5EdKd9h0TQxSiFIAu5vyUQpNGesXbN2Qw==", "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.41.0", - "@lexical/extension": "0.41.0", + "@lexical/code": "0.41.0", "@lexical/utils": "0.41.0", - "lexical": "0.41.0" + "@shikijs/core": "^3.7.0", + "@shikijs/engine-javascript": "^3.7.0", + "@shikijs/langs": "^3.7.0", + "@shikijs/themes": "^3.7.0", + "lexical": "0.41.0", + "shiki": "^3.7.0" } }, - "node_modules/@lexical/text": { + "node_modules/@lexical/devtools-core": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.41.0.tgz", - "integrity": "sha512-gGA+Anc7ck110EXo4KVKtq6Ui3M7Vz3OpGJ4QE6zJHWW8nV5h273koUGSutAMeoZgRVb6t01Izh3ORoFt/j1CA==", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.41.0.tgz", + "integrity": "sha512-FzJtluBhBc8bKS11TUZe72KoZN/hnzIyiiM0SPJAsPwGpoXuM01jqpXQGybWf/1bWB+bmmhOae7O4Nywi/Csuw==", "license": "MIT", "dependencies": { + "@lexical/html": "0.41.0", + "@lexical/link": "0.41.0", + "@lexical/mark": "0.41.0", + "@lexical/table": "0.41.0", + "@lexical/utils": "0.41.0", "lexical": "0.41.0" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" } }, - "node_modules/@lexical/utils": { + "node_modules/@lexical/dragon": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.41.0.tgz", - "integrity": "sha512-Wlsokr5NQCq83D+7kxZ9qs5yQ3dU3Qaf2M+uXxLRoPoDaXqW8xTWZq1+ZFoEzsHzx06QoPa4Vu/40BZR91uQPg==", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.41.0.tgz", + "integrity": "sha512-gBEqkk8Q6ZPruvDaRcOdF1EK9suCVBODzOCcR+EnoJTaTjfDkCM7pkPAm4w90Wa1wCZEtFHvCfas+jU9MDSumg==", "license": "MIT", "dependencies": { - "@lexical/selection": "0.41.0", + "@lexical/extension": "0.41.0", "lexical": "0.41.0" } }, - "node_modules/@lexical/yjs": { + "node_modules/@lexical/extension": { "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.41.0.tgz", - "integrity": "sha512-PaKTxSbVC4fpqUjQ7vUL9RkNF1PjL8TFl5jRe03PqoPYpE33buf3VXX6+cOUEfv9+uknSqLCPHoBS/4jN3a97w==", + "resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.41.0.tgz", + "integrity": "sha512-sF4SPiP72yXvIGchmmIZ7Yg2XZTxNLOpFEIIzdqG7X/1fa1Ham9P/T7VbrblWpF6Ei5LJtK9JgNVB0hb4l3o1g==", "license": "MIT", "dependencies": { - "@lexical/offset": "0.41.0", - "@lexical/selection": "0.41.0", + "@lexical/utils": "0.41.0", + "@preact/signals-core": "^1.11.0", "lexical": "0.41.0" - }, - "peerDependencies": { - "yjs": ">=13.5.22" } }, - "node_modules/@lightninglabs/lnc-core": { - "version": "0.3.2-alpha", - "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.2-alpha.tgz", - "integrity": "sha512-H6tG+X9txCIdxTR+GPsbImzP2Juo+6Uvq/Ipaijd7xPISzgEU4J4GNE5PEHuIZqbnBo1RmpuXnFG6dmsl3PTzQ==" - }, - "node_modules/@lightninglabs/lnc-web": { - "version": "0.3.2-alpha", - "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.2-alpha.tgz", - "integrity": "sha512-3aCBugBf0NzczpJqmHn03Oq2Ju9W5n0+nOdAe+Y/Zhf6YLXdqG1PTJ2J+7TXncpiogfPYDCw95tVQqSi4Zi/ZA==", + "node_modules/@lexical/hashtag": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.41.0.tgz", + "integrity": "sha512-tFWM74RW4KU0E/sj2aowfWl26vmLUTp331CgVESnhQKcZBfT40KJYd57HEqBDTfQKn4MUhylQCCA0hbpw6EeFQ==", + "license": "MIT", "dependencies": { - "@lightninglabs/lnc-core": "0.3.2-alpha", - "crypto-js": "4.2.0" + "@lexical/text": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" } }, - "node_modules/@next/env": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", - "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.35.tgz", - "integrity": "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==", - "dev": true, + "node_modules/@lexical/headless": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/headless/-/headless-0.41.0.tgz", + "integrity": "sha512-MH8oDuUKdM/Jq0c9vlEEkCL9pEQg4SwyrABBGIbFf+87VBJ5EWDdG9g1vJq7fKSDxfhFux7F5+i+zgUnxOQR/g==", "license": "MIT", "dependencies": { - "glob": "10.3.10" + "happy-dom": "^20.0.0", + "lexical": "0.41.0" } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", - "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", - "cpu": [ - "arm64" - ], + "node_modules/@lexical/history": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.41.0.tgz", + "integrity": "sha512-kGoVWsiOn62+RMjRolRa+NXZl8jFwxav6GNDiHH8yzivtoaH8n1SwUfLJELXCzeqzs81HySqD4q30VLJVTGoDg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "@lexical/extension": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", - "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", - "cpu": [ - "x64" - ], + "node_modules/@lexical/html": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.41.0.tgz", + "integrity": "sha512-3RyZy+H/IDKz2D66rNN/NqYx87xVFrngfEbyu1OWtbY963RUFnopiVHCQvsge/8kT04QSZ7U/DzjVFqeNS6clg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" + "dependencies": { + "@lexical/selection": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/link": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.41.0.tgz", + "integrity": "sha512-Rjtx5cGWAkKcnacncbVsZ1TqRnUB2Wm4eEVKpaAEG41+kHgqghzM2P+UGT15yROroxJu8KvAC9ISiYFiU4XE1w==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/list": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.41.0.tgz", + "integrity": "sha512-RXvB+xcbzVoQLGRDOBRCacztG7V+bI95tdoTwl8pz5xvgPtAaRnkZWMDP+yMNzMJZsqEChdtpxbf0NgtMkun6g==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.41.0", + "@lexical/selection": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/mark": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.41.0.tgz", + "integrity": "sha512-UO5WVs9uJAYIKHSlYh4Z1gHrBBchTOi21UCYBIZ7eAs4suK84hPzD+3/LAX5CB7ZltL6ke5Sly3FOwNXv/wfpA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.41.0.tgz", + "integrity": "sha512-bzI73JMXpjGFhqUWNV6KqfjWcgAWzwFT+J3RHtbCF5rysC8HLldBYojOgAAtPfXqfxyv2mDzsY7SoJ75s9uHZA==", + "license": "MIT", + "dependencies": { + "@lexical/code": "0.41.0", + "@lexical/link": "0.41.0", + "@lexical/list": "0.41.0", + "@lexical/rich-text": "0.41.0", + "@lexical/text": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/offset": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.41.0.tgz", + "integrity": "sha512-2RHBXZqC8gm3X9C0AyRb0M8w7zJu5dKiasrif+jSKzsxPjAUeF1m95OtIOsWs1XLNUgASOSUqGovDZxKJslZfA==", + "license": "MIT", + "dependencies": { + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.41.0.tgz", + "integrity": "sha512-Iy6ZiJip8X14EBYt1zKPOrXyQ4eG9JLBEoPoSVBTiSbVd+lYicdUvaOThT0k0/qeVTN9nqTaEltBjm56IrVKCQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.41.0.tgz", + "integrity": "sha512-HIsGgmFUYRUNNyvckun33UQfU7LRzDlxymHUq67+Bxd5bXqdZOrStEKJXuDX+LuLh/GXZbaWNbDLqwLBObfbQg==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.41.0", + "@lexical/dragon": "0.41.0", + "@lexical/selection": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/react": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.41.0.tgz", + "integrity": "sha512-7+GUdZUm6sofWm+zdsWAs6cFBwKNsvsHezZTrf6k8jrZxL461ZQmbz/16b4DvjCGL9r5P1fR7md9/LCmk8TiCg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "@lexical/devtools-core": "0.41.0", + "@lexical/dragon": "0.41.0", + "@lexical/extension": "0.41.0", + "@lexical/hashtag": "0.41.0", + "@lexical/history": "0.41.0", + "@lexical/link": "0.41.0", + "@lexical/list": "0.41.0", + "@lexical/mark": "0.41.0", + "@lexical/markdown": "0.41.0", + "@lexical/overflow": "0.41.0", + "@lexical/plain-text": "0.41.0", + "@lexical/rich-text": "0.41.0", + "@lexical/table": "0.41.0", + "@lexical/text": "0.41.0", + "@lexical/utils": "0.41.0", + "@lexical/yjs": "0.41.0", + "lexical": "0.41.0", + "react-error-boundary": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/react/node_modules/@floating-ui/react": { + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.41.0.tgz", + "integrity": "sha512-yUcr7ZaaVTZNi8bow4CK1M8jy2qyyls1Vr+5dVjwBclVShOL/F/nFyzBOSb6RtXXRbd3Ahuk9fEleppX/RNIdw==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.41.0", + "@lexical/dragon": "0.41.0", + "@lexical/selection": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/selection": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.41.0.tgz", + "integrity": "sha512-1s7/kNyRzcv5uaTwsUL28NpiisqTf5xZ1zNukLsCN1xY+TWbv9RE9OxIv+748wMm4pxNczQe/UbIBODkbeknLw==", + "license": "MIT", + "dependencies": { + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/table": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.41.0.tgz", + "integrity": "sha512-d3SPThBAr+oZ8O74TXU0iXM3rLbrAVC7/HcOnSAq7/AhWQW8yMutT51JQGN+0fMLP9kqoWSAojNtkdvzXfU/+A==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.41.0", + "@lexical/extension": "0.41.0", + "@lexical/utils": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/text": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.41.0.tgz", + "integrity": "sha512-gGA+Anc7ck110EXo4KVKtq6Ui3M7Vz3OpGJ4QE6zJHWW8nV5h273koUGSutAMeoZgRVb6t01Izh3ORoFt/j1CA==", + "license": "MIT", + "dependencies": { + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/utils": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.41.0.tgz", + "integrity": "sha512-Wlsokr5NQCq83D+7kxZ9qs5yQ3dU3Qaf2M+uXxLRoPoDaXqW8xTWZq1+ZFoEzsHzx06QoPa4Vu/40BZR91uQPg==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.41.0", + "lexical": "0.41.0" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.41.0.tgz", + "integrity": "sha512-PaKTxSbVC4fpqUjQ7vUL9RkNF1PjL8TFl5jRe03PqoPYpE33buf3VXX6+cOUEfv9+uknSqLCPHoBS/4jN3a97w==", + "license": "MIT", + "dependencies": { + "@lexical/offset": "0.41.0", + "@lexical/selection": "0.41.0", + "lexical": "0.41.0" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, + "node_modules/@lightninglabs/lnc-core": { + "version": "0.3.2-alpha", + "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.2-alpha.tgz", + "integrity": "sha512-H6tG+X9txCIdxTR+GPsbImzP2Juo+6Uvq/Ipaijd7xPISzgEU4J4GNE5PEHuIZqbnBo1RmpuXnFG6dmsl3PTzQ==" + }, + "node_modules/@lightninglabs/lnc-web": { + "version": "0.3.2-alpha", + "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.2-alpha.tgz", + "integrity": "sha512-3aCBugBf0NzczpJqmHn03Oq2Ju9W5n0+nOdAe+Y/Zhf6YLXdqG1PTJ2J+7TXncpiogfPYDCw95tVQqSi4Zi/ZA==", + "dependencies": { + "@lightninglabs/lnc-core": "0.3.2-alpha", + "crypto-js": "4.2.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", + "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.14.tgz", + "integrity": "sha512-ogBjgsFrPPz19abP3VwcYSahbkUOMMvJjxCOYWYndw+PydeMuLuB4XrvNkNutFrTjC9St2KFULRdKID8Sd/CMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", + "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", + "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" ], "engines": { "node": ">= 10" } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", - "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", + "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", "cpu": [ "arm64" ], @@ -4708,9 +5156,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", - "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", + "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", "cpu": [ "arm64" ], @@ -4724,9 +5172,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", - "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", + "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", "cpu": [ "x64" ], @@ -4740,9 +5188,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", - "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", + "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", "cpu": [ "x64" ], @@ -4756,9 +5204,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", - "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", + "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", "cpu": [ "arm64" ], @@ -4771,28 +5219,12 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", - "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", + "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", - "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", - "cpu": [ - "x64" + "x64" ], "license": "MIT", "optional": true, @@ -5058,6 +5490,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@nostr-dev-kit/ndk": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.12.2.tgz", @@ -5857,6 +6299,20 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "license": "MIT" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -6430,18 +6886,13 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@szmarczak/http-timer": { @@ -6455,6 +6906,17 @@ "node": ">=10" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -6504,15 +6966,6 @@ "@types/node": "*" } }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -6538,14 +6991,6 @@ "@types/filesystem": "*" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -6639,15 +7084,11 @@ "@types/ms": "*" } }, - "node_modules/@types/dom-webcodecs": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", - "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==" - }, "node_modules/@types/emscripten": { - "version": "1.39.13", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", - "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==" + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "license": "MIT" }, "node_modules/@types/eslint": { "version": "9.6.1", @@ -6675,28 +7116,6 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, - "node_modules/@types/express": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", - "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/@types/filesystem": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", @@ -6775,7 +7194,8 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/katex": { "version": "0.16.7", @@ -6804,11 +7224,6 @@ "@types/unist": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -6823,15 +7238,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -6843,16 +7249,6 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, "node_modules/@types/react": { "version": "18.0.25", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz", @@ -6907,85 +7303,631 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/unist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", + "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, "dependencies": { - "@types/mime": "*", - "@types/node": "*" + "@types/yargs-parser": "*" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "license": "MIT" + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/warning": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", - "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/whatwg-mimetype": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", - "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", - "license": "MIT" + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/node": "*" + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", @@ -7190,28 +8132,17 @@ "license": "Apache-2.0" }, "node_modules/@yudiel/react-qr-scanner": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-2.0.8.tgz", - "integrity": "sha512-/7WHsdC1a/Z909J2zZxqgpUQ1iI554fZxIagucH/tFu1MhZkNIeykYI1OdZgDEwV4CzuSi+h90wwNrhmETcmRw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-2.5.1.tgz", + "integrity": "sha512-FWzHaCneu30mQpE80VNWx4IPtBjXFEiTzhwWunZy3afvvAy/x0aVIgYijJKEbROoaAeDfcJ/gIyUCqPBP7bMOw==", + "license": "MIT", "dependencies": { - "barcode-detector": "^2.2.7", - "webrtc-adapter": "9.0.1" + "barcode-detector": "3.0.8", + "webrtc-adapter": "9.0.3" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/acorn": { @@ -7364,13 +8295,24 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -7379,23 +8321,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7424,16 +8364,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7443,15 +8406,16 @@ } }, "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==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7477,18 +8441,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -7508,12 +8472,28 @@ "safer-buffer": "^2.1.0" } }, + "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, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/async-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", @@ -7646,6 +8626,16 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, + "node_modules/axe-core": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.2.tgz", + "integrity": "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", @@ -7657,6 +8647,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -7817,42 +8817,6 @@ "node": ">=10" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", - "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.2", - "core-js-compat": "^3.21.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -7898,12 +8862,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/barcode-detector": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.2.11.tgz", - "integrity": "sha512-N50XZ6Rav2sxTgHXOc38/mkpVJMan11GZ8Yqi1pPMZpTJSXuZ/FpIee6OtLehZX/Vs4ZOzGbp1DgXzFCfKggWA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.0.8.tgz", + "integrity": "sha512-Z9jzzE8ngEDyN9EU7lWdGgV07mcnEQnrX8W9WecXDqD2v+5CcVjt9+a134a5zb+kICvpsrDx6NYA6ay4LGFs8A==", + "license": "MIT", "dependencies": { - "@types/dom-webcodecs": "^0.1.13", - "zxing-wasm": "1.2.14" + "zxing-wasm": "2.2.4" } }, "node_modules/base64-js": { @@ -8002,72 +8966,30 @@ "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/bolt07": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/bolt07/-/bolt07-1.9.4.tgz", @@ -8310,17 +9232,6 @@ "node": ">=10" } }, - "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/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -8592,7 +9503,8 @@ "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==" + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" }, "node_modules/clipboard-copy": { "version": "4.0.1", @@ -8716,40 +9628,11 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8767,11 +9650,6 @@ "node": ">=18" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "node_modules/copy-webpack-plugin": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", @@ -9268,14 +10146,22 @@ "node": ">=12" } }, + "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, + "license": "BSD-2-Clause" + }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9285,27 +10171,29 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -9332,9 +10220,10 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -9494,6 +10383,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9506,15 +10396,6 @@ "node": ">=6" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -9711,7 +10592,8 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/ejs": { "version": "3.1.10", @@ -9770,6 +10652,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9815,56 +10698,65 @@ } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -9949,22 +10841,27 @@ } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "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==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -10065,7 +10962,8 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-latex": { "version": "1.2.0", @@ -10132,6 +11030,34 @@ } } }, + "node_modules/eslint-config-next": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.14.tgz", + "integrity": "sha512-lmJ5F8ZgOYogq0qtH4L5SpxuASY2SPdOzqUprN2/56+P3GPsIpXaUWIJC66kYIH+yZdsM4nkHE5MIBP6s1NiBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.5.14", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/eslint-config-standard": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", @@ -10186,14 +11112,15 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -10205,11 +11132,71 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -10227,6 +11214,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -10251,32 +11239,37 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/debug": { @@ -10300,6 +11293,43 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/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, + "license": "MIT" + }, "node_modules/eslint-plugin-n": { "version": "15.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", @@ -10399,6 +11429,19 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -10711,14 +11754,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -10786,107 +11821,21 @@ "node": ">= 0.8.0" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, "node_modules/ext": { "version": "1.7.0", @@ -10903,13 +11852,44 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-equals": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", - "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "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", @@ -11015,35 +11995,26 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -11161,14 +12132,6 @@ "react": ">=16.8.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -11181,14 +12144,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -11224,14 +12179,17 @@ } }, "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==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -11343,13 +12301,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -11359,9 +12318,10 @@ } }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -11496,9 +12456,10 @@ "dev": true }, "node_modules/graphql": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", - "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -11569,18 +12530,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -11610,9 +12559,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -11830,18 +12783,23 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http2-wrapper": { @@ -11878,15 +12836,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/idb": { @@ -11978,13 +12940,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12023,14 +12986,6 @@ "node": ">=18" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -12047,12 +13002,14 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -12067,12 +13024,16 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "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, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12082,23 +13043,28 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "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==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12107,6 +13073,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -12119,9 +13108,10 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -12133,10 +13123,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -12147,11 +13140,13 @@ } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12174,12 +13169,15 @@ } }, "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, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12240,7 +13238,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12274,11 +13272,13 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12306,12 +13306,15 @@ } }, "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==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -12333,7 +13336,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12342,11 +13344,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -12367,11 +13370,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12381,11 +13386,14 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12418,7 +13426,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12427,24 +13435,28 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", - "dev": true, + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -12755,12 +13767,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-circus/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12996,12 +14002,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-config/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13104,12 +14104,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-diff/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13225,12 +14219,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-each/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13348,12 +14336,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -13444,12 +14426,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-matcher-utils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13557,12 +14533,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-message-util/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14051,12 +15021,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -14263,12 +15227,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-validate/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14464,6 +15422,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -14568,6 +15527,26 @@ "node": ">=6" } }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "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, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -15182,11 +16161,12 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memoize-one": { @@ -15195,25 +16175,19 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "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, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 8" } }, "node_modules/micromark": { @@ -15782,17 +16756,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -15892,6 +16855,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -15899,9 +16878,10 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -15912,41 +16892,40 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", - "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", + "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", "license": "MIT", "dependencies": { - "@next/env": "14.2.35", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", + "@next/env": "15.5.14", + "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=18.17.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.33", - "@next/swc-darwin-x64": "14.2.33", - "@next/swc-linux-arm64-gnu": "14.2.33", - "@next/swc-linux-arm64-musl": "14.2.33", - "@next/swc-linux-x64-gnu": "14.2.33", - "@next/swc-linux-x64-musl": "14.2.33", - "@next/swc-win32-arm64-msvc": "14.2.33", - "@next/swc-win32-ia32-msvc": "14.2.33", - "@next/swc-win32-x64-msvc": "14.2.33" + "@next/swc-darwin-arm64": "15.5.14", + "@next/swc-darwin-x64": "15.5.14", + "@next/swc-linux-arm64-gnu": "15.5.14", + "@next/swc-linux-arm64-musl": "15.5.14", + "@next/swc-linux-x64-gnu": "15.5.14", + "@next/swc-linux-x64-musl": "15.5.14", + "@next/swc-win32-arm64-msvc": "15.5.14", + "@next/swc-win32-x64-msvc": "15.5.14", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -15956,6 +16935,9 @@ "@playwright/test": { "optional": true }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } @@ -16046,16 +17028,30 @@ "integrity": "sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==", "deprecated": "Switch to namespaced @noble/secp256k1 for security and feature updates" }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" - }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -16267,13 +17263,16 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -16284,14 +17283,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -16315,14 +17316,31 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, "engines": { @@ -16344,6 +17362,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -16444,6 +17463,23 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -16610,6 +17646,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16662,12 +17699,6 @@ "node": "20 || >=22" } }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -17223,18 +18254,6 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -17300,17 +18319,18 @@ } }, "node_modules/qrcode.react": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.0.1.tgz", - "integrity": "sha512-Lpj0tPBn561WiQ3QQWXbkx8xTtB8BZkJeMZWLJIL8iaPBCoWzW1IpCeU3gY5MDqsb0+efCvEGkl9O0naP64crA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -17362,81 +18382,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-avatar-editor": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-13.0.2.tgz", - "integrity": "sha512-a4ajbi7lwDh98kgEtSEeKMu0vs0CHTczkq4Xcxr1EiwMFH1GlgHCEtwGU8q/H5W8SeLnH4KPK8LUjEEaZXklxQ==", - "dependencies": { - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/runtime": "^7.12.5", - "prop-types": "^15.7.2" - }, + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-15.1.0.tgz", + "integrity": "sha512-Zto7u9l6Wd5LPPtjeFJ+7uwoT4bs01OSgkN2kxD18lWl8IiZ0GY3nWCbKPx4qIU7Au1vENsMJm19rfVWHHayaQ==", + "license": "MIT", "peerDependencies": { - "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-bootstrap": { @@ -17496,15 +18473,15 @@ } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.4" } }, "node_modules/react-error-boundary": { @@ -17534,9 +18511,10 @@ } }, "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==" + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -17602,17 +18580,18 @@ } }, "node_modules/react-smooth": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", - "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-string-replace": { @@ -17688,15 +18667,16 @@ } }, "node_modules/recharts": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.0.tgz", - "integrity": "sha512-sbfxjWQ+oLWSZEWmvbq/DFVdeRLqqA6d0CDjKx2PkxVVdoXo16jvENCE+u/x7HxOO+/fwx//nYRwb8p8X6s/lQ==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", - "react-smooth": "^4.0.0", + "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" @@ -17705,8 +18685,8 @@ "node": ">=14" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/recharts-scale": { @@ -17717,24 +18697,20 @@ "decimal.js-light": "^2.4.1" } }, - "node_modules/recharts/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" - }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", - "dev": true, + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -17792,13 +18768,16 @@ "license": "MIT" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -17855,23 +18834,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/rehackt": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.1.0.tgz", - "integrity": "sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==", - "peerDependencies": { - "@types/react": "*", - "react": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/remove-markdown": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.5.tgz", @@ -17960,14 +18922,6 @@ "node": ">=10" } }, - "node_modules/response-iterator": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz", - "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -18060,14 +19014,25 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -18080,21 +19045,44 @@ "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -18131,12 +19119,10 @@ "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/schema-utils": { "version": "4.3.3", @@ -18163,9 +19149,10 @@ "integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==" }, "node_modules/sdp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", - "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz", + "integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==", + "license": "MIT" }, "node_modules/secp256k1": { "version": "4.0.4", @@ -18199,50 +19186,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-error": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", @@ -18277,20 +19220,6 @@ "node": ">=20.0.0" } }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/serviceworker-storage": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/serviceworker-storage/-/serviceworker-storage-0.1.0.tgz", @@ -18326,10 +19255,25 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/sha.js": { "version": "2.4.12", @@ -18371,6 +19315,74 @@ ], "license": "MIT" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -18628,6 +19640,13 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -18998,19 +20017,25 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/streamsearch": { + "node_modules/stop-iteration-iterator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">= 0.4" } }, "node_modules/string_decoder": { @@ -19047,6 +20072,21 @@ "node": ">=8" } }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -19083,14 +20123,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -19100,14 +20144,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -19217,9 +20266,10 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", "dependencies": { "client-only": "0.0.1" }, @@ -19227,7 +20277,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -19309,14 +20359,6 @@ "node": ">=11.0.0" } }, - "node_modules/symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "engines": { - "node": ">=0.10" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -19501,13 +20543,13 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -19605,6 +20647,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -19628,25 +20671,28 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ts-invariant": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", - "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", - "dependencies": { - "tslib": "^2.1.0" - }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } @@ -19656,6 +20702,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -19672,9 +20719,10 @@ "license": "MIT" }, "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tsparticles-basic": { "version": "2.12.0", @@ -19954,17 +21002,44 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -19980,15 +21055,16 @@ } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -19998,16 +21074,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -20017,16 +21095,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -20092,14 +21171,18 @@ } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -20263,10 +21346,46 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/unzipper": { "version": "0.12.3", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", @@ -20496,14 +21615,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -20744,9 +21855,10 @@ } }, "node_modules/webrtc-adapter": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz", - "integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.3.tgz", + "integrity": "sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==", + "license": "BSD-3-Clause", "dependencies": { "sdp": "^3.2.0" }, @@ -20829,38 +21941,43 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", "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" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-builtin-type": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", - "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -20873,13 +21990,13 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "license": "MIT" }, "node_modules/which-collection": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, + "license": "MIT", "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -21350,19 +22467,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "node_modules/zen-observable-ts": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz", - "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==", - "dependencies": { - "zen-observable": "0.8.15" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -21374,11 +22478,31 @@ } }, "node_modules/zxing-wasm": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.2.14.tgz", - "integrity": "sha512-UaYfzSmFPIEmYDt/KyPvs/H02t8jO470BBFHUIlvtmloAm8f2zdAmOL93iWYQ5QYfSnTyFPg0yzZwznlBjfg4A==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-2.2.4.tgz", + "integrity": "sha512-1gq5zs4wuNTs5umWLypzNNeuJoluFvwmvjiiT3L9z/TMlVveeJRWy7h90xyUqCe+Qq0zL0w7o5zkdDMWDr9aZA==", + "license": "MIT", + "dependencies": { + "@types/emscripten": "^1.41.5", + "type-fest": "^5.2.0" + }, + "peerDependencies": { + "@types/emscripten": ">=1.39.6" + } + }, + "node_modules/zxing-wasm/node_modules/type-fest": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", + "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", + "license": "(MIT OR CC0-1.0)", "dependencies": { - "@types/emscripten": "^1.39.13" + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } } } diff --git a/package.json b/package.json index bfc43f481..dcfa3e879 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "worker:dev": "tsx --tsconfig jsconfig.json --trace-warnings --watch worker/index.js" }, "dependencies": { - "@apollo/client": "^3.11.8", - "@apollo/server": "^4.11.0", - "@as-integrations/next": "^3.1.0", + "@apollo/client": "^4.1.6", + "@apollo/server": "^5.5.0", + "@as-integrations/next": "^4.1.0", "@auth/prisma-adapter": "^2.7.0", "@cashu/cashu-ts": "^2.4.1", "@graphile/depth-limit": "^0.3.1", @@ -42,7 +42,7 @@ "@shocknet/clink-sdk": "^1.4.0", "@slack/web-api": "^7.6.0", "@svgr/webpack": "^8.1.0", - "@yudiel/react-qr-scanner": "^2.0.8", + "@yudiel/react-qr-scanner": "^2.5.1", "acorn": "^8.12.1", "ajv": "^8.17.1", "async-mutex": "^0.5.0", @@ -64,7 +64,7 @@ "formik": "^2.4.6", "github-slugger": "^2.0.0", "google-protobuf": "^3.21.4", - "graphql": "^16.9.0", + "graphql": "^16.13.2", "graphql-scalar": "^0.1.0", "graphql-tag": "^2.12.6", "graphql-type-json": "^0.3.2", @@ -83,7 +83,7 @@ "mdast-util-to-string": "^4.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", - "next": "^14.2.25", + "next": "15.5.14", "next-auth": "^4.24.8", "next-plausible": "^3.12.2", "next-seo": "^6.6.0", @@ -96,21 +96,22 @@ "pg-boss": "^9.0.3", "piexifjs": "^1.0.6", "prisma": "^5.20.0", - "qrcode.react": "^4.0.1", - "react": "^18.3.1", - "react-avatar-editor": "^13.0.2", + "qrcode.react": "^4.2.0", + "react": "19.2.4", + "react-avatar-editor": "^15.1.0", "react-bootstrap": "^2.10.5", "react-countdown": "^2.3.6", "react-datepicker": "^7.4.0", - "react-dom": "^18.3.1", + "react-dom": "19.2.4", "react-ios-pwa-prompt": "^1.8.4", "react-lite-youtube-embed": "^3.3.3", "react-particles": "^2.12.2", "react-select": "^5.10.2", "react-string-replace": "^1.1.1", "react-twitter-embed": "^4.0.4", - "recharts": "^2.13.0", + "recharts": "^2.15.4", "remove-markdown": "^0.5.5", + "rxjs": "^7.8.2", "sass": "^1.79.5", "serviceworker-storage": "^0.1.0", "source-map": "^0.8.0-beta.0", @@ -147,8 +148,9 @@ ] }, "devDependencies": { - "@next/eslint-plugin-next": "^14.2.15", + "@next/eslint-plugin-next": "15.5.14", "eslint": "^9.12.0", + "eslint-config-next": "^15.5.14", "jest": "^29.7.0", "standard": "^17.1.2" }, @@ -157,6 +159,7 @@ "serialize-javascript": ">=7.0.3", "glob": ">=13.0.6", "form-data": ">=4.0.5", - "bn.js": ">=4.12.3" + "bn.js": ">=4.12.3", + "react-is": "19.2.4" } } diff --git a/pages/[name]/[type].js b/pages/[name]/[type].js index 6576e493c..a7d51efb9 100644 --- a/pages/[name]/[type].js +++ b/pages/[name]/[type].js @@ -2,7 +2,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import Items from '@/components/items' import { useRouter } from 'next/router' import { USER, USER_WITH_ITEMS } from '@/fragments/users' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { COMMENT_TYPE_QUERY, ITEM_SORTS, ITEM_TYPES_USER, WHENS } from '@/lib/constants' import PageLoading from '@/components/page-loading' import { UserLayout } from '.' diff --git a/pages/[name]/index.js b/pages/[name]/index.js index 9f8531332..05b739a59 100644 --- a/pages/[name]/index.js +++ b/pages/[name]/index.js @@ -1,5 +1,5 @@ import Layout from '@/components/layout' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import UserHeader from '@/components/user-header' import Button from 'react-bootstrap/Button' import styles from '@/styles/user.module.css' diff --git a/pages/[name]/territories.js b/pages/[name]/territories.js index e005f0b9d..d93ef7a80 100644 --- a/pages/[name]/territories.js +++ b/pages/[name]/territories.js @@ -1,7 +1,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import { useRouter } from 'next/router' import { USER, USER_WITH_SUBS } from '@/fragments/users' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' import { UserLayout } from '.' import TerritoryList from '@/components/territory-list' diff --git a/pages/_app.js b/pages/_app.js index 09620f7fc..8d9848778 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,6 +1,7 @@ import '@/styles/globals.scss' import '@/styles/text.scss' -import { ApolloProvider, gql } from '@apollo/client' +import { gql } from '@apollo/client' +import { ApolloProvider } from '@apollo/client/react' import { MeProvider } from '@/components/me' import PlausibleProvider from 'next-plausible' import getApolloClient from '@/lib/apollo.js' diff --git a/pages/invites/index.js b/pages/invites/index.js index fbccd5284..99ae3432d 100644 --- a/pages/invites/index.js +++ b/pages/invites/index.js @@ -1,7 +1,8 @@ import Layout from '@/components/layout' import { Form, Input, SubmitButton } from '@/components/form' import InputGroup from 'react-bootstrap/InputGroup' -import { gql, useMutation, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { INVITE_FIELDS } from '@/fragments/invites' import AccordianItem from '@/components/accordian-item' import styles from '@/styles/invites.module.css' diff --git a/pages/items/[id]/edit.js b/pages/items/[id]/edit.js index a71ad071f..d250ef62d 100644 --- a/pages/items/[id]/edit.js +++ b/pages/items/[id]/edit.js @@ -7,7 +7,7 @@ import JobForm from '@/components/job-form' import { PollForm } from '@/components/poll-form' import { BountyForm } from '@/components/bounty-form' import { useEffect, useState } from 'react' -import { useLazyQuery, useQuery } from '@apollo/client' +import { useLazyQuery, useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' import PageLoading from '@/components/page-loading' import { FeeButtonProvider } from '@/components/fee-button' diff --git a/pages/items/[id]/index.js b/pages/items/[id]/index.js index 7d5d1c07f..147c586aa 100644 --- a/pages/items/[id]/index.js +++ b/pages/items/[id]/index.js @@ -2,7 +2,7 @@ import Layout from '@/components/layout' import { ITEM_FULL } from '@/fragments/items' import ItemFull from '@/components/item-full' import { getGetServerSideProps } from '@/api/ssrApollo' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' import PageLoading from '@/components/page-loading' import { CommentsNavigatorProvider } from '@/components/use-comments-navigator' diff --git a/pages/items/[id]/ots.js b/pages/items/[id]/ots.js index aefe37316..68f07a504 100644 --- a/pages/items/[id]/ots.js +++ b/pages/items/[id]/ots.js @@ -3,7 +3,7 @@ import { ITEM_OTS } from '@/fragments/items' import { getGetServerSideProps } from '@/api/ssrApollo' import stringifyCanon from 'canonical-json' import Button from 'react-bootstrap/Button' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' import PageLoading from '@/components/page-loading' diff --git a/pages/items/[id]/related.js b/pages/items/[id]/related.js index feed42299..c1e6cbc82 100644 --- a/pages/items/[id]/related.js +++ b/pages/items/[id]/related.js @@ -6,7 +6,7 @@ import Items from '@/components/items' import Layout from '@/components/layout' import { useRouter } from 'next/router' import Item from '@/components/item' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' export const getServerSideProps = getGetServerSideProps({ diff --git a/pages/live.js b/pages/live.js index e4ada619e..c43fc62a6 100644 --- a/pages/live.js +++ b/pages/live.js @@ -2,7 +2,7 @@ import Button from 'react-bootstrap/Button' import { CenterLayout } from '@/components/layout' import Snl from '@/components/snl' import { gql } from 'graphql-tag' -import { useMutation, useQuery } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' // force SSR to include CSP nonces diff --git a/pages/notifications.js b/pages/notifications.js index f57c1beba..2ae1bb0ad 100644 --- a/pages/notifications.js +++ b/pages/notifications.js @@ -3,7 +3,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import Notifications, { NotificationAlert } from '@/components/notifications' import { HAS_NOTIFICATIONS, NOTIFICATIONS } from '@/fragments/notifications' -import { useApolloClient } from '@apollo/client' +import { useApolloClient } from '@apollo/client/react' import { clearNotifications } from '@/components/serviceworker' export const getServerSideProps = getGetServerSideProps({ query: NOTIFICATIONS, authRequired: true }) diff --git a/pages/referrals/[when].js b/pages/referrals/[when].js index c434161fc..b678dc928 100644 --- a/pages/referrals/[when].js +++ b/pages/referrals/[when].js @@ -5,7 +5,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import { CopyInput, Select, DatePicker } from '@/components/form' import { CenterLayout } from '@/components/layout' import { useMe } from '@/components/me' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' import { WHENS } from '@/lib/constants' import dynamic from 'next/dynamic' diff --git a/pages/rewards/[...when].js b/pages/rewards/[...when].js index fb1d0c510..1973bd611 100644 --- a/pages/rewards/[...when].js +++ b/pages/rewards/[...when].js @@ -1,4 +1,4 @@ -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' import { ME_REWARDS } from '@/fragments/rewards' import { CenterLayout } from '@/components/layout' diff --git a/pages/rewards/index.js b/pages/rewards/index.js index d84ece13e..240daa8a6 100644 --- a/pages/rewards/index.js +++ b/pages/rewards/index.js @@ -4,7 +4,7 @@ import InputGroup from 'react-bootstrap/InputGroup' import { getGetServerSideProps } from '@/api/ssrApollo' import { Form, Input, SubmitButton } from '@/components/form' import Layout from '@/components/layout' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import Link from 'next/link' import { amountSchema } from '@/lib/validate' import { msatsToSats, numWithUnits } from '@/lib/format' diff --git a/pages/satistics/graphs/[when].js b/pages/satistics/graphs/[when].js index c2e7954db..af6b3b782 100644 --- a/pages/satistics/graphs/[when].js +++ b/pages/satistics/graphs/[when].js @@ -1,4 +1,5 @@ -import { gql, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import Col from 'react-bootstrap/Col' diff --git a/pages/satistics/index.js b/pages/satistics/index.js index e0a60909b..01f7afba0 100644 --- a/pages/satistics/index.js +++ b/pages/satistics/index.js @@ -1,4 +1,4 @@ -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import MoreFooter from '@/components/more-footer' diff --git a/pages/settings/index.js b/pages/settings/index.js index a88ef1a57..3d27b9497 100644 --- a/pages/settings/index.js +++ b/pages/settings/index.js @@ -4,7 +4,8 @@ import InputGroup from 'react-bootstrap/InputGroup' import Nav from 'react-bootstrap/Nav' import Layout from '@/components/layout' import { useMemo } from 'react' -import { gql, useMutation, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' import { SETTINGS, SET_SETTINGS } from '@/fragments/users' import { useRouter } from 'next/router' diff --git a/pages/settings/logins.js b/pages/settings/logins.js index 273ce4263..c86c65148 100644 --- a/pages/settings/logins.js +++ b/pages/settings/logins.js @@ -3,7 +3,8 @@ import Alert from 'react-bootstrap/Alert' import Button from 'react-bootstrap/Button' import Layout from '@/components/layout' import { useState } from 'react' -import { gql, useMutation, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' import LoginButton from '@/components/login-button' import { signIn } from 'next-auth/react' diff --git a/pages/settings/wallets.js b/pages/settings/wallets.js index c0b091b20..c3d371fd7 100644 --- a/pages/settings/wallets.js +++ b/pages/settings/wallets.js @@ -1,7 +1,7 @@ import { useCallback } from 'react' import InputGroup from 'react-bootstrap/InputGroup' import { useField } from 'formik' -import { useMutation, useQuery } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { Form, Input, SubmitButton } from '@/components/form' import Info from '@/components/info' import { useToast } from '@/components/toast' diff --git a/pages/stackers/[sub]/[when].js b/pages/stackers/[sub]/[when].js index bf75971fd..87bcac9b7 100644 --- a/pages/stackers/[sub]/[when].js +++ b/pages/stackers/[sub]/[when].js @@ -1,4 +1,5 @@ -import { gql, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import Col from 'react-bootstrap/Col' diff --git a/pages/wallets/[type].js b/pages/wallets/[type].js index 9b49b7c9f..f68f7f907 100644 --- a/pages/wallets/[type].js +++ b/pages/wallets/[type].js @@ -4,7 +4,7 @@ import { WalletMultiStepForm } from '@/wallets/client/components' import { WALLET } from '@/wallets/client/fragments' import { useDecryptedWallet } from '@/wallets/client/hooks' import { unurlify } from '@/wallets/lib/util' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' const variablesFunc = params => { diff --git a/pages/withdraw.js b/pages/withdraw.js index 7553deeb8..e07b8d6cc 100644 --- a/pages/withdraw.js +++ b/pages/withdraw.js @@ -4,7 +4,7 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { InputGroup, Nav } from 'react-bootstrap' import styles from '@/styles/nav.module.css' -import { useMutation } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { CREATE_WITHDRAWL, SEND_TO_LNADDR } from '@/fragments/withdrawal' import { requestProvider } from 'webln' import { useEffect, useState } from 'react' diff --git a/pages/~/edit.js b/pages/~/edit.js index 818b608ee..033cea926 100644 --- a/pages/~/edit.js +++ b/pages/~/edit.js @@ -3,7 +3,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import { CenterLayout } from '@/components/layout' import TerritoryForm from '@/components/territory-form' import PageLoading from '@/components/page-loading' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' import TerritoryPaymentDue from '@/components/territory-payment-due' diff --git a/pages/~/index.js b/pages/~/index.js index b2b5162b4..62bf3d2d0 100644 --- a/pages/~/index.js +++ b/pages/~/index.js @@ -4,7 +4,7 @@ import Items from '@/components/items' import Layout from '@/components/layout' import { SUB_FULL, SUB_ITEMS } from '@/fragments/subs' import Snl from '@/components/snl' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' import TerritoryHeader from '@/components/territory-header' diff --git a/pages/~/new/[type].js b/pages/~/new/[type].js index 96e57df1a..0969f8ed1 100644 --- a/pages/~/new/[type].js +++ b/pages/~/new/[type].js @@ -5,7 +5,7 @@ import NewHeader from '@/components/new-header' import { useRouter } from 'next/router' import { SUB_FULL, SUB_ITEMS } from '@/fragments/subs' import { COMMENT_TYPE_QUERY } from '@/lib/constants' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import PageLoading from '@/components/page-loading' const staticVariables = { sort: 'new' } diff --git a/pages/~/post.js b/pages/~/post.js index fd4d7f8d9..809bab6d6 100644 --- a/pages/~/post.js +++ b/pages/~/post.js @@ -2,7 +2,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import { SUBS } from '@/fragments/subs' import { CenterLayout } from '@/components/layout' import Post from '@/components/post' -import { useQuery } from '@apollo/client' +import { useQuery } from '@apollo/client/react' import { useRouter } from 'next/router' import PageLoading from '@/components/page-loading' import { subNamesFromSlug } from '@/lib/subs' diff --git a/wallets/client/hooks/diagnostics.js b/wallets/client/hooks/diagnostics.js index 572e79c37..b7c1e0444 100644 --- a/wallets/client/hooks/diagnostics.js +++ b/wallets/client/hooks/diagnostics.js @@ -1,5 +1,5 @@ import { useCallback } from 'react' -import { useMutation } from '@apollo/client' +import { useMutation } from '@apollo/client/react' import { useMe } from '@/components/me' import { useToast } from '@/components/toast' import { SET_DIAGNOSTICS } from '@/fragments/users' diff --git a/wallets/client/hooks/logger.js b/wallets/client/hooks/logger.js index 578c7445d..7e8d2839e 100644 --- a/wallets/client/hooks/logger.js +++ b/wallets/client/hooks/logger.js @@ -1,4 +1,4 @@ -import { useMutation, useLazyQuery } from '@apollo/client' +import { useLazyQuery, useMutation } from '@apollo/client/react' import { ADD_WALLET_LOG, WALLET_LOGS, DELETE_WALLET_LOGS } from '@/wallets/client/fragments' import { createContext, useCallback, useContext, useMemo, useState, useEffect } from 'react' import { useShowModal } from '@/components/modal' @@ -96,7 +96,6 @@ export function useWalletLogs (protocol, debug) { // if we're configuring a protocol template, there are no logs to fetch const noFetch = protocol && isTemplate(protocol) const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, { - variables: { protocolId, debug }, skip: noFetch, fetchPolicy: 'network-only' }) diff --git a/wallets/client/hooks/query.js b/wallets/client/hooks/query.js index 83ece5eac..1f9252f5a 100644 --- a/wallets/client/hooks/query.js +++ b/wallets/client/hooks/query.js @@ -37,7 +37,8 @@ import { TEST_WALLET_RECEIVE_CLINK, DELETE_WALLET } from '@/wallets/client/fragments' -import { gql, useApolloClient, useMutation, useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import { useApolloClient, useMutation, useQuery } from '@apollo/client/react' import { useTemplates, useWallets } from '@/wallets/client/hooks/global' import { useDecryption, useEncryption, useSetKey } from '@/wallets/client/hooks/crypto' import { useWalletLoggerFactory } from '@/wallets/client/hooks/logger' diff --git a/worker/index.js b/worker/index.js index 1ac8a34bd..2d9981397 100644 --- a/worker/index.js +++ b/worker/index.js @@ -15,6 +15,8 @@ import { repin } from './repin' import { trust } from './trust' import { earn, earnRefill } from './earn' import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client' +import { Defer20220824Handler } from '@apollo/client/incremental' +import { LocalState } from '@apollo/client/local-state' import { indexItem, indexAllItems } from './search' import { timestampItem } from './ots' import { computeStreaks, checkStreak } from './streak' @@ -59,7 +61,9 @@ async function work () { uri: `${process.env.SELF_URL}/api/graphql`, fetch }), + cache: new InMemoryCache(), + defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', @@ -69,7 +73,21 @@ async function work () { fetchPolicy: 'no-cache', nextFetchPolicy: 'no-cache' } - } + }, + + /* + Inserted by Apollo Client 3->4 migration codemod. + If you are not using the `@client` directive in your application, + you can safely remove this option. + */ + localState: new LocalState({}), + + /* + Inserted by Apollo Client 3->4 migration codemod. + If you are not using the `@defer` directive in your application, + you can safely remove this option. + */ + incrementalHandler: new Defer20220824Handler() }) const { lnd } = authenticatedLndGrpc({ From e8fedc8f7aabfb8180fddd0714b4d71dbadb32f0 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 5 Apr 2026 17:06:29 +0200 Subject: [PATCH 072/154] upgrade Bootstrap to 5.3.8; upgrade react-boostrap to 2.10.10; silence boostrap sass deprecation warnings --- next.config.js | 6 ++++ package-lock.json | 76 ++++++++++++++++++++++++++++++---------------- package.json | 4 +-- pages/_document.js | 2 +- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/next.config.js b/next.config.js index f806e77b1..8e735eea2 100644 --- a/next.config.js +++ b/next.config.js @@ -60,6 +60,12 @@ module.exports = withPlausibleProxy()({ scrollRestoration: true, serverSourceMaps: true }, + // suppress deprecation warnings of bootstrap sass + // https://github.com/twbs/bootstrap/issues/40962 + sassOptions: { + quietDeps: true, + silenceDeprecations: ['legacy-js-api', 'color-functions'] + }, reactStrictMode: true, productionBrowserSourceMaps: true, generateBuildId: commitHash ? async () => commitHash : undefined, diff --git a/package-lock.json b/package-lock.json index 5807ea70e..b3320840e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "aws-sdk": "^2.1691.0", "bech32": "^2.0.0", "bolt11": "^1.4.1", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.8", "canonical-json": "0.0.4", "classnames": "^2.5.1", "clipboard-copy": "^4.0.1", @@ -93,7 +93,7 @@ "qrcode.react": "^4.2.0", "react": "19.2.4", "react-avatar-editor": "^15.1.0", - "react-bootstrap": "^2.10.5", + "react-bootstrap": "^2.10.10", "react-countdown": "^2.3.6", "react-datepicker": "^7.4.0", "react-dom": "19.2.4", @@ -5900,6 +5900,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -6027,9 +6028,10 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@react-aria/ssr": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.6.tgz", - "integrity": "sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" }, @@ -6037,7 +6039,7 @@ "node": ">= 12" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-spring/animated": { @@ -6110,6 +6112,7 @@ "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", "dependencies": { "dequal": "^2.0.3" }, @@ -6118,18 +6121,19 @@ } }, "node_modules/@restart/ui": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.8.0.tgz", - "integrity": "sha512-xJEOXUOTmT4FngTmhdjKFRrVVF0hwCLNPdatLCHkyS4dkiSK12cEu1Y0fjxktjJrdst9jJIc5J6ihMJCoWEN/g==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@popperjs/core": "^2.11.6", + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", "@react-aria/ssr": "^3.5.0", - "@restart/hooks": "^0.4.9", - "@types/warning": "^3.0.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", "dequal": "^2.0.3", "dom-helpers": "^5.2.0", - "uncontrollable": "^8.0.1", + "uncontrollable": "^8.0.4", "warning": "^4.0.3" }, "peerDependencies": { @@ -6137,10 +6141,23 @@ "react-dom": ">=16.14.0" } }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@restart/ui/node_modules/uncontrollable": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", "peerDependencies": { "react": ">=16.14.0" } @@ -7245,9 +7262,10 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.0.25", @@ -7326,9 +7344,10 @@ "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, "node_modules/@types/warning": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", - "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.4.tgz", + "integrity": "sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==", + "license": "MIT" }, "node_modules/@types/whatwg-mimetype": { "version": "3.0.2", @@ -9034,9 +9053,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "funding": [ { "type": "github", @@ -9047,6 +9066,7 @@ "url": "https://opencollective.com/bootstrap" } ], + "license": "MIT", "peerDependencies": { "@popperjs/core": "^2.11.8" } @@ -18417,13 +18437,15 @@ } }, "node_modules/react-bootstrap": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", - "integrity": "sha512-XueAOEn64RRkZ0s6yzUTdpFtdUXs5L5491QU//8ZcODKJNDLt/r01tNyriZccjgRImH1REynUc9pqjiRMpDLWQ==", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.7", "@restart/hooks": "^0.4.9", - "@restart/ui": "^1.6.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", "@types/react-transition-group": "^4.4.6", "classnames": "^2.3.2", "dom-helpers": "^5.2.1", diff --git a/package.json b/package.json index dcfa3e879..d4d8fe372 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "aws-sdk": "^2.1691.0", "bech32": "^2.0.0", "bolt11": "^1.4.1", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.8", "canonical-json": "0.0.4", "classnames": "^2.5.1", "clipboard-copy": "^4.0.1", @@ -99,7 +99,7 @@ "qrcode.react": "^4.2.0", "react": "19.2.4", "react-avatar-editor": "^15.1.0", - "react-bootstrap": "^2.10.5", + "react-bootstrap": "^2.10.10", "react-countdown": "^2.3.6", "react-datepicker": "^7.4.0", "react-dom": "19.2.4", diff --git a/pages/_document.js b/pages/_document.js index f46b678e9..2a8928fa3 100644 --- a/pages/_document.js +++ b/pages/_document.js @@ -27,7 +27,7 @@ class MyDocument extends Document { render () { const { nonce } = this.props return ( - + From 8f7e221f8798f3841b2178f024c3ecf90c02f1d4 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 5 Apr 2026 17:26:05 +0200 Subject: [PATCH 073/154] upgrade Next.js to 16.2.2, rename middleware.js to proxy.js --- package-lock.json | 213 ++++++++++++++++++++++++++------------ package.json | 10 +- middleware.js => proxy.js | 2 +- 3 files changed, 151 insertions(+), 74 deletions(-) rename middleware.js => proxy.js (99%) diff --git a/package-lock.json b/package-lock.json index b3320840e..f8a263eef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ "mdast-util-to-string": "^4.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", - "next": "15.5.14", + "next": "16.2.2", "next-auth": "^4.24.8", "next-plausible": "^3.12.2", "next-seo": "^6.6.0", @@ -131,9 +131,9 @@ "yup": "^1.4.0" }, "devDependencies": { - "@next/eslint-plugin-next": "15.5.14", + "@next/eslint-plugin-next": "16.2.2", "eslint": "^9.12.0", - "eslint-config-next": "^15.5.14", + "eslint-config-next": "16.2.2", "jest": "^29.7.0", "standard": "^17.1.2" }, @@ -5092,15 +5092,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", - "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.2.tgz", + "integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.14.tgz", - "integrity": "sha512-ogBjgsFrPPz19abP3VwcYSahbkUOMMvJjxCOYWYndw+PydeMuLuB4XrvNkNutFrTjC9St2KFULRdKID8Sd/CMQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.2.tgz", + "integrity": "sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw==", "dev": true, "license": "MIT", "dependencies": { @@ -5108,9 +5108,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", - "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz", + "integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==", "cpu": [ "arm64" ], @@ -5124,9 +5124,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", - "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz", + "integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==", "cpu": [ "x64" ], @@ -5140,9 +5140,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", - "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz", + "integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==", "cpu": [ "arm64" ], @@ -5156,9 +5156,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", - "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz", + "integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==", "cpu": [ "arm64" ], @@ -5172,9 +5172,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", - "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz", + "integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==", "cpu": [ "x64" ], @@ -5188,9 +5188,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", - "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz", + "integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==", "cpu": [ "x64" ], @@ -5204,9 +5204,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", - "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz", + "integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==", "cpu": [ "arm64" ], @@ -5220,9 +5220,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", - "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz", + "integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==", "cpu": [ "x64" ], @@ -6323,13 +6323,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", - "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", - "dev": true, - "license": "MIT" - }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -11051,25 +11044,24 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.14.tgz", - "integrity": "sha512-lmJ5F8ZgOYogq0qtH4L5SpxuASY2SPdOzqUprN2/56+P3GPsIpXaUWIJC66kYIH+yZdsM4nkHE5MIBP6s1NiBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.2.tgz", + "integrity": "sha512-6VlvEhwoug2JpVgjZDhyXrJXUEuPY++TddzIpTaIRvlvlXXFgvQUtm3+Zr84IjFm0lXtJt73w19JA08tOaZVwg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.14", - "@rushstack/eslint-patch": "^1.10.3", - "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@next/eslint-plugin-next": "16.2.2", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" }, "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "peerDependenciesMeta": { @@ -11078,6 +11070,19 @@ } } }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-config-standard": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", @@ -11450,13 +11455,20 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" @@ -12716,6 +12728,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -16912,13 +16941,14 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", - "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.2.tgz", + "integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==", "license": "MIT", "dependencies": { - "@next/env": "15.5.14", + "@next/env": "16.2.2", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -16927,18 +16957,18 @@ "next": "dist/bin/next" }, "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.14", - "@next/swc-darwin-x64": "15.5.14", - "@next/swc-linux-arm64-gnu": "15.5.14", - "@next/swc-linux-arm64-musl": "15.5.14", - "@next/swc-linux-x64-gnu": "15.5.14", - "@next/swc-linux-x64-musl": "15.5.14", - "@next/swc-win32-arm64-msvc": "15.5.14", - "@next/swc-win32-x64-msvc": "15.5.14", - "sharp": "^0.34.3" + "@next/swc-darwin-arm64": "16.2.2", + "@next/swc-darwin-x64": "16.2.2", + "@next/swc-linux-arm64-gnu": "16.2.2", + "@next/swc-linux-arm64-musl": "16.2.2", + "@next/swc-linux-x64-gnu": "16.2.2", + "@next/swc-linux-x64-musl": "16.2.2", + "@next/swc-win32-arm64-msvc": "16.2.2", + "@next/swc-win32-x64-msvc": "16.2.2", + "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -21171,6 +21201,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/typescript-lru-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz", @@ -22489,6 +22543,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index d4d8fe372..53b4faa61 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "private": true, "scripts": { "audit:blast-radius": "node scripts/audit-blast-radius.js", - "dev": "NODE_OPTIONS='--trace-warnings --enable-source-maps' next dev", - "build": "next build", + "dev": "NODE_OPTIONS='--trace-warnings --enable-source-maps' next dev --webpack", + "build": "next build --webpack", "migrate": "prisma migrate deploy", "start": "NODE_OPTIONS='--trace-warnings --max-old-space-size=4096 --max-semi-space-size=128' next start -p $PORT --keepAliveTimeout 120000", "lint": "standard", @@ -83,7 +83,7 @@ "mdast-util-to-string": "^4.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", - "next": "15.5.14", + "next": "16.2.2", "next-auth": "^4.24.8", "next-plausible": "^3.12.2", "next-seo": "^6.6.0", @@ -148,9 +148,9 @@ ] }, "devDependencies": { - "@next/eslint-plugin-next": "15.5.14", + "@next/eslint-plugin-next": "16.2.2", "eslint": "^9.12.0", - "eslint-config-next": "^15.5.14", + "eslint-config-next": "16.2.2", "jest": "^29.7.0", "standard": "^17.1.2" }, diff --git a/middleware.js b/proxy.js similarity index 99% rename from middleware.js rename to proxy.js index 69aa58313..d71d82610 100644 --- a/middleware.js +++ b/proxy.js @@ -84,7 +84,7 @@ function referrerMiddleware (request) { return response } -export function middleware (request) { +export function proxy (request) { const resp = referrerMiddleware(request) const isDev = process.env.NODE_ENV === 'development' From fd7cb96663195f57d123c9dbd51dc0c3d0b1ed58 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 5 Apr 2026 17:33:58 +0200 Subject: [PATCH 074/154] polyfill URLPattern and add to js-standard lint globals --- package-lock.json | 7 +++++++ package.json | 4 ++++ proxy.js | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index f8a263eef..2ab2e50fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,6 +118,7 @@ "unist-util-visit": "^5.0.0", "unzipper": "^0.12.3", "url-unshort": "^6.1.0", + "urlpattern-polyfill": "^10.1.0", "web-push": "^3.6.7", "webln": "^0.3.2", "webpack": "^5.95.0", @@ -21626,6 +21627,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "license": "MIT" + }, "node_modules/use-debounce": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz", diff --git a/package.json b/package.json index 53b4faa61..d1b1fb9c8 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "unist-util-visit": "^5.0.0", "unzipper": "^0.12.3", "url-unshort": "^6.1.0", + "urlpattern-polyfill": "^10.1.0", "web-push": "^3.6.7", "webln": "^0.3.2", "webpack": "^5.95.0", @@ -145,6 +146,9 @@ ], "extends": [ "next" + ], + "globals": [ + "URLPattern" ] }, "devDependencies": { diff --git a/proxy.js b/proxy.js index d71d82610..e9d472fef 100644 --- a/proxy.js +++ b/proxy.js @@ -1,4 +1,5 @@ -import { NextResponse, URLPattern } from 'next/server' +import 'urlpattern-polyfill' +import { NextResponse } from 'next/server' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) From 29ab41b95d6f2aebf258c8cf4c0bef17d2bc4551 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 5 Apr 2026 23:39:45 +0200 Subject: [PATCH 075/154] upgrade to next-plausible 4.0.0; remove PlausibleProvider dead props --- next.config.js | 2 +- package-lock.json | 15 ++++++++------- package.json | 2 +- pages/_app.js | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/next.config.js b/next.config.js index 8e735eea2..94da8d559 100644 --- a/next.config.js +++ b/next.config.js @@ -46,7 +46,7 @@ try { commitHash = '0000' } -module.exports = withPlausibleProxy()({ +module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi3E-sauvdFABb.js' })({ env: { NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, diff --git a/package-lock.json b/package-lock.json index 2ab2e50fb..d194b4df5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "micromark-extension-math": "^3.1.0", "next": "16.2.2", "next-auth": "^4.24.8", - "next-plausible": "^3.12.2", + "next-plausible": "^4.0.0", "next-seo": "^6.6.0", "node-s3-url-encode": "^0.0.4", "nodemailer": "^8.0.1", @@ -17036,16 +17036,17 @@ } }, "node_modules/next-plausible": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.12.2.tgz", - "integrity": "sha512-jyOYLAdwaZZR6nrzFhN9xfVjzYOG6mIQ/LLDCfdAS99ELy759cKfNxluGo+pI2Xh9cYxTFYYZGlnkyR3IPy4yg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-4.0.0.tgz", + "integrity": "sha512-tC48VscREZ4fEvas9T4oj5qJwnpPlms0Wih1Unbgi/ozG08yN1w0IAPGp+/cHB8n6qzEAL5J0MlAS0FOr132jA==", + "license": "MIT", "funding": { "url": "https://github.com/4lejandrito/next-plausible?sponsor=1" }, "peerDependencies": { - "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 ", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/next-seo": { diff --git a/package.json b/package.json index d1b1fb9c8..caf2a377a 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "micromark-extension-math": "^3.1.0", "next": "16.2.2", "next-auth": "^4.24.8", - "next-plausible": "^3.12.2", + "next-plausible": "^4.0.0", "next-seo": "^6.6.0", "node-s3-url-encode": "^0.0.4", "nodemailer": "^8.0.1", diff --git a/pages/_app.js b/pages/_app.js index 8d9848778..da36bb388 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -111,7 +111,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) { - + From 0cf9f685fc0972cf6cb68b4fbf4a076415acacbc Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 01:27:46 +0200 Subject: [PATCH 076/154] fix: run withPlausibleProxy through webpack instead of treating it as a server external DefinePlugin can't otherwise replace the env var internally used by next-plausible to determine if we're using the proxy or not --- next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/next.config.js b/next.config.js index 94da8d559..d7bb54d75 100644 --- a/next.config.js +++ b/next.config.js @@ -55,6 +55,7 @@ module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi // so we need to resolve the relative path to the lightning module LIGHTNING_MODULE_PATH: require('path').relative(process.cwd(), require.resolve('lightning')) }, + transpilePackages: ['next-plausible'], compress: false, experimental: { scrollRestoration: true, From da8299776a8627f9430af6af7c5d60225cd98128 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 01:37:55 +0200 Subject: [PATCH 077/154] apollo: useLazyQuery now throws AbortError, add errorPolicy 'all' to get the full error where errors are manually handled --- components/form.js | 1 + components/item-popover.js | 2 +- components/link-form.js | 4 +- .../payIn/hooks/use-auto-retry-pay-ins.js | 2 +- components/sub-popover.js | 2 +- components/sub-select.js | 10 +++-- components/territory-form.js | 8 +++- components/user-popover.js | 2 +- pages/items/[id]/edit.js | 4 +- wallets/client/hooks/logger.js | 38 +++++++++++-------- 10 files changed, 45 insertions(+), 28 deletions(-) diff --git a/components/form.js b/components/form.js index e2a05a593..5934d1a99 100644 --- a/components/form.js +++ b/components/form.js @@ -468,6 +468,7 @@ export function BaseSuggest ({ index: 0 }) }) + .catch(err => console.error(err)) } else { resetSuggestions() } diff --git a/components/item-popover.js b/components/item-popover.js index 6b0fa89ea..c4708416b 100644 --- a/components/item-popover.js +++ b/components/item-popover.js @@ -12,7 +12,7 @@ export default function ItemPopover ({ id, children }) { }) const getItem = useCallback(() => { - execute({ variables: { id } }) + execute({ variables: { id } }).catch(err => console.error(err)) }, [execute, id]) return ( diff --git a/components/link-form.js b/components/link-form.js index 192acab5c..4fbd8ac86 100644 --- a/components/link-form.js +++ b/components/link-form.js @@ -68,8 +68,8 @@ export function LinkForm ({ item, subs, EditInfo, children }) { ...ItemFields } }`) - const getPageTitleAndUnshortedDebounce = useDebounceCallback((...args) => getPageTitleAndUnshorted(...args), LOOKUP_DEBOUNCE_MS, [getPageTitleAndUnshorted]) - const getDupesDebounce = useDebounceCallback((...args) => getDupes(...args), DUPES_DEBOUNCE_MS, [getDupes]) + const getPageTitleAndUnshortedDebounce = useDebounceCallback((...args) => getPageTitleAndUnshorted(...args).catch(err => console.error(err)), LOOKUP_DEBOUNCE_MS, [getPageTitleAndUnshorted]) + const getDupesDebounce = useDebounceCallback((...args) => getDupes(...args).catch(err => console.error(err)), DUPES_DEBOUNCE_MS, [getDupes]) useEffect(() => { if (!dupesLoading) { diff --git a/components/payIn/hooks/use-auto-retry-pay-ins.js b/components/payIn/hooks/use-auto-retry-pay-ins.js index 12778eebc..a226013fb 100644 --- a/components/payIn/hooks/use-auto-retry-pay-ins.js +++ b/components/payIn/hooks/use-auto-retry-pay-ins.js @@ -20,7 +20,7 @@ export function willAutoRetryPayIn (payIn) { export function useAutoRetryPayIns () { const waitForWalletPayment = useWalletPayment() const payInHelper = usePayInHelper() - const [getFailedPayIns] = useLazyQuery(FAILED_PAY_INS, { fetchPolicy: 'network-only', nextFetchPolicy: 'network-only' }) + const [getFailedPayIns] = useLazyQuery(FAILED_PAY_INS, { fetchPolicy: 'network-only', nextFetchPolicy: 'network-only', errorPolicy: 'all' }) const { me } = useMe() const retry = useCallback(async (payIn) => { diff --git a/components/sub-popover.js b/components/sub-popover.js index db52e23ce..260b2bc55 100644 --- a/components/sub-popover.js +++ b/components/sub-popover.js @@ -16,7 +16,7 @@ export default function SubPopover ({ sub, children }) { ) const getSub = useCallback(() => { - execute({ variables: { sub } }) + execute({ variables: { sub } }).catch(err => console.error(err)) }, [execute, sub]) return ( diff --git a/components/sub-select.js b/components/sub-select.js index a71fbd9dd..03c214acc 100644 --- a/components/sub-select.js +++ b/components/sub-select.js @@ -153,9 +153,13 @@ export function SubMultiSelect ({ prependSubs, subs, onChange, size, appendSubs, const [getSub] = useLazyQuery(SUB_FULL) const handleTerritoryClick = async (subName) => { - const { data } = await getSub({ variables: { sub: subName } }) - if (data?.sub) { - showModal(() => ) + try { + const { data } = await getSub({ variables: { sub: subName } }) + if (data?.sub) { + showModal(() => ) + } + } catch (err) { + console.error(err) } } diff --git a/components/territory-form.js b/components/territory-form.js index dbd22da3c..766342394 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -56,8 +56,12 @@ export default function TerritoryForm ({ sub }) { // never show "territory archived" warning during edits if (sub) return const name = e.target.value - const { data } = await fetchSub({ variables: { sub: name } }) - setArchived(data?.sub?.status === 'STOPPED') + try { + const { data } = await fetchSub({ variables: { sub: name } }) + setArchived(data?.sub?.status === 'STOPPED') + } catch (err) { + console.error(err) + } }, [fetchSub, setArchived]) const onSubmit = useCallback( diff --git a/components/user-popover.js b/components/user-popover.js index 848878f18..a8d60442d 100644 --- a/components/user-popover.js +++ b/components/user-popover.js @@ -32,7 +32,7 @@ export default function UserPopover ({ name, children }) { ) const getUser = useCallback(() => { - execute({ variables: { name } }) + execute({ variables: { name } }).catch(err => console.error(err)) }, [execute, name]) return ( diff --git a/pages/items/[id]/edit.js b/pages/items/[id]/edit.js index d250ef62d..af83aad54 100644 --- a/pages/items/[id]/edit.js +++ b/pages/items/[id]/edit.js @@ -66,8 +66,8 @@ export default function PostEdit ({ ssrData }) { ...territoryAdds } }) - }) - }, [subs]) + }).catch(err => console.error(err)).catch(err => console.error(err)) + }, [subs, fetchSubs]) const [,, editThreshold] = useCanEdit(item) const EditInfo = editThreshold && item.payIn?.payInState === 'PAID' diff --git a/wallets/client/hooks/logger.js b/wallets/client/hooks/logger.js index 7e8d2839e..5bb72eeaa 100644 --- a/wallets/client/hooks/logger.js +++ b/wallets/client/hooks/logger.js @@ -96,23 +96,27 @@ export function useWalletLogs (protocol, debug) { // if we're configuring a protocol template, there are no logs to fetch const noFetch = protocol && isTemplate(protocol) const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, { - skip: noFetch, - fetchPolicy: 'network-only' + fetchPolicy: 'network-only', + errorPolicy: 'all' }) useEffect(() => { if (noFetch) return const interval = setInterval(async () => { - const { data, error } = await fetchLogs({ variables: { protocolId, debug } }) - if (error) { - console.error('failed to fetch wallet logs:', error.message) - return - } - const { entries: updatedLogs, cursor } = data.walletLogs - setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs]) - if (!called) { - setCursor(cursor) + try { + const { data, error } = await fetchLogs({ variables: { protocolId, debug } }) + if (error) { + console.error('failed to fetch wallet logs:', error.message) + return + } + const { entries: updatedLogs, cursor } = data.walletLogs + setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs]) + if (!called) { + setCursor(cursor) + } + } catch (err) { + console.error('failed to fetch wallet logs:', err) } }, FAST_POLL_INTERVAL_MS) @@ -120,10 +124,14 @@ export function useWalletLogs (protocol, debug) { }, [fetchLogs, protocolId, called, noFetch, debug]) const loadMore = useCallback(async () => { - const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } }) - const { entries: cursorLogs, cursor: newCursor } = data.walletLogs - setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))]) - setCursor(newCursor) + try { + const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } }) + const { entries: cursorLogs, cursor: newCursor } = data.walletLogs + setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))]) + setCursor(newCursor) + } catch (err) { + console.error('failed to load more wallet logs:', err) + } }, [fetchLogs, cursor, protocolId, debug]) const clearLogs = useCallback(() => { From 3813e00ab375d2eb5c468cd545f1a4a53608f145 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 13:26:21 +0200 Subject: [PATCH 078/154] apollo migration: remove @client and @defer directives support from Apollo Client where unnecessary --- api/ssrApollo.js | 7 +------ lib/apollo.js | 7 +------ worker/index.js | 18 +----------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/api/ssrApollo.js b/api/ssrApollo.js index 302c7ac4d..b8a3c82bc 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -70,12 +70,7 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { ssr: true } }, - - /* - Inserted by Apollo Client 3->4 migration codemod. - If you are not using the `@client` directive in your application, - you can safely remove this option. - */ + // @client directive support localState: new LocalState({}) }) diff --git a/lib/apollo.js b/lib/apollo.js index 1e34dd224..f1a88b8ec 100644 --- a/lib/apollo.js +++ b/lib/apollo.js @@ -393,12 +393,7 @@ function getClient (uri) { ssr: SSR } }, - - /* - Inserted by Apollo Client 3->4 migration codemod. - If you are not using the `@client` directive in your application, - you can safely remove this option. - */ + // @client directive support localState: new LocalState({}) }) } diff --git a/worker/index.js b/worker/index.js index 2d9981397..a1ad933eb 100644 --- a/worker/index.js +++ b/worker/index.js @@ -15,8 +15,6 @@ import { repin } from './repin' import { trust } from './trust' import { earn, earnRefill } from './earn' import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client' -import { Defer20220824Handler } from '@apollo/client/incremental' -import { LocalState } from '@apollo/client/local-state' import { indexItem, indexAllItems } from './search' import { timestampItem } from './ots' import { computeStreaks, checkStreak } from './streak' @@ -73,21 +71,7 @@ async function work () { fetchPolicy: 'no-cache', nextFetchPolicy: 'no-cache' } - }, - - /* - Inserted by Apollo Client 3->4 migration codemod. - If you are not using the `@client` directive in your application, - you can safely remove this option. - */ - localState: new LocalState({}), - - /* - Inserted by Apollo Client 3->4 migration codemod. - If you are not using the `@defer` directive in your application, - you can safely remove this option. - */ - incrementalHandler: new Defer20220824Handler() + } }) const { lnd } = authenticatedLndGrpc({ From 4717770964e3b7c92285556ef870af7e669dc855 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 13:57:41 +0200 Subject: [PATCH 079/154] apollo migration: switch now-internal getOperationName with graphql's getOperationAST --- components/payIn/hooks/use-pay-in-mutation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/payIn/hooks/use-pay-in-mutation.js b/components/payIn/hooks/use-pay-in-mutation.js index 8d3446fcd..fb59f811a 100644 --- a/components/payIn/hooks/use-pay-in-mutation.js +++ b/components/payIn/hooks/use-pay-in-mutation.js @@ -5,7 +5,7 @@ import { useCallback, useState } from 'react' import { InvoiceCanceledError } from '@/wallets/client/errors' import { useApolloClient, useMutation } from '@apollo/client/react' import usePayPayIn from '@/components/payIn/hooks/use-pay-pay-in' -import { getOperationName } from '@apollo/client/utilities/internal' +import { getOperationAST } from 'graphql' import { useMe } from '@/components/me' import { USER_ID } from '@/lib/constants' import { willAutoRetryPayIn } from './use-auto-retry-pay-ins' @@ -38,7 +38,7 @@ export default function usePayInMutation (mutation, { onCompleted, ...options } // innerResult is used to store/control the result of the mutation when innerMutate runs const [innerResult, setInnerResult] = useState(result) const payPayIn = usePayPayIn() - const mutationName = getOperationName(mutation) + const mutationName = getOperationAST(mutation)?.name?.value const innerMutate = useCallback(async ({ onCompleted: innerOnCompleted, ...innerOptions } = {}) => { const hookCachePhases = getCachePhases(options) @@ -156,7 +156,7 @@ function getCachePhases (options = {}) { // all paid actions need these fields and they're easy to forget function addOptimisticResponseExtras (mutation, payInOptimisticResponse, me) { if (!payInOptimisticResponse) return payInOptimisticResponse - const mutationName = getOperationName(mutation) + const mutationName = getOperationAST(mutation)?.name?.value const payerPrivates = payInOptimisticResponse.payerPrivates?.result ? { ...payInOptimisticResponse.payerPrivates, From 7a6367c53eabb174c0699e3049be7f90fc1e8eae Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 13:58:59 +0200 Subject: [PATCH 080/154] remove Apollo's execute double catch --- pages/items/[id]/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/items/[id]/edit.js b/pages/items/[id]/edit.js index af83aad54..4debd7422 100644 --- a/pages/items/[id]/edit.js +++ b/pages/items/[id]/edit.js @@ -66,7 +66,7 @@ export default function PostEdit ({ ssrData }) { ...territoryAdds } }) - }).catch(err => console.error(err)).catch(err => console.error(err)) + }).catch(err => console.error(err)) }, [subs, fetchSubs]) const [,, editThreshold] = useCanEdit(item) From d55624417166390212c78d66084cb67163366ea3 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 14:17:45 +0200 Subject: [PATCH 081/154] remove temporary nodejs nextjs 15 middleware workaround --- next.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/next.config.js b/next.config.js index d7bb54d75..5b79319a4 100644 --- a/next.config.js +++ b/next.config.js @@ -216,8 +216,8 @@ module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi } ] }, - webpack: (config, { isServer, dev, defaultLoaders, nextRuntime }) => { - if (isServer && nextRuntime === 'nodejs') { + webpack: (config, { isServer, dev, defaultLoaders }) => { + if (isServer) { const workboxPlugin = new InjectManifest({ include: [/\/(icons|maskable|splash)\//, /\.(webp|mp4|woff|woff2)$/], swDest: '../../public/sw.js', From cb5b2fd91f4ddf4911e78e269ce9feafe15c916f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 6 Apr 2026 14:20:38 +0200 Subject: [PATCH 082/154] cleanup: linting --- api/ssrApollo.js | 6 +----- lib/apollo.js | 3 --- worker/index.js | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/api/ssrApollo.js b/api/ssrApollo.js index b8a3c82bc..b240bccec 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -25,10 +25,9 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { if (req) { req = await multiAuthMiddleware(req, res) } - const session = req && (await getServerSession(req, res, getAuthOptions(req))) + const session = req && await getServerSession(req, res, getAuthOptions(req)) const client = new ApolloClient({ ssrMode: true, - link: new SchemaLink({ schema: makeExecutableSchema({ typeDefs, @@ -51,13 +50,10 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { } })() }), - cache: new InMemoryCache({ freezeResults: true }), - assumeImmutableResults: true, - defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', diff --git a/lib/apollo.js b/lib/apollo.js index f1a88b8ec..fdc0bdf8b 100644 --- a/lib/apollo.js +++ b/lib/apollo.js @@ -55,7 +55,6 @@ function getClient (uri) { link, ssrMode: SSR, devtools: { enabled: process.env.NODE_ENV !== 'production' }, - cache: new InMemoryCache({ freezeResults: true, // https://github.com/apollographql/apollo-client/issues/7648 @@ -375,10 +374,8 @@ function getClient (uri) { } } }), - assumeImmutableResults: true, queryDeduplication: true, - defaultOptions: { watchQuery: { initialFetchPolicy: defaultFetchPolicy, diff --git a/worker/index.js b/worker/index.js index a1ad933eb..1ac8a34bd 100644 --- a/worker/index.js +++ b/worker/index.js @@ -59,9 +59,7 @@ async function work () { uri: `${process.env.SELF_URL}/api/graphql`, fetch }), - cache: new InMemoryCache(), - defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', From e6f2f97b53592cd0942c016ff8a1515150aee846 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 7 Apr 2026 17:07:56 +0200 Subject: [PATCH 083/154] export cleanDomainVerificationJobs for resolver and worker remove subname header at the start of middleware --- api/resolvers/domain.js | 2 +- middleware.js | 7 ++++++- worker/domainVerification.js | 14 ++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index d87c3e5e0..40239e7eb 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -2,7 +2,7 @@ import { validateSchema, customDomainSchema } from '@/lib/validate' import { GqlAuthenticationError, GqlInputError, GqlAuthorizationError } from '@/lib/error' import { SN_ADMIN_IDS } from '@/lib/constants' -async function cleanDomainVerificationJobs (domainId, models) { +export async function cleanDomainVerificationJobs (domainId, models) { // delete any existing domain verification job left await models.$queryRaw` DELETE FROM pgboss.job diff --git a/middleware.js b/middleware.js index e5e9a7083..ef22faf94 100644 --- a/middleware.js +++ b/middleware.js @@ -201,7 +201,12 @@ function applySecurityHeaders (resp) { return resp } -export async function middleware (request) { +export async function middleware (req) { + // clear subname header to prevent potential spoofing + const headers = new Headers(req.headers) + headers.delete('x-stacker-news-subname') + const request = new Request(req, { headers }) + const referrerResp = referrerMiddleware(request) // TODO: check if we actually need this, and WHY if (referrerResp.headers.get('Location')) { diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 0876e56a1..79c30e98e 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -8,6 +8,7 @@ import { deleteDomainCertificate, detachDomainCertificate } from '@/lib/domain-verification' +import { cleanDomainVerificationJobs } from '@/api/resolvers/domain' import { datePivot } from '@/lib/time' const getVerificationInterval = (updatedAt) => { @@ -20,13 +21,6 @@ const getVerificationInterval = (updatedAt) => { const VERIFICATION_HOLD_THRESHOLD = -2 // 2 days ago -// delete all related domain verification jobs for a domain -async function deleteDomainJobs (domainId, models) { - await models.$queryRaw` - DELETE FROM pgboss.job - WHERE name = 'domainVerification' AND data->>'domainId' = ${domainId}::TEXT` -} - export async function domainVerification ({ id: jobId, data: { domainId }, boss }) { // establish connection to database const models = createPrisma({ connectionParams: { connection_limit: 1 } }) @@ -52,7 +46,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss // this handles the edge case where a domain is put on HOLD manually or for some other reason if (domain.status === 'HOLD') { console.log(`domain ${domain.domainName} is on HOLD, skipping verification and deleting any remaining domain verification jobs`) - await deleteDomainJobs(domainId, models) + await cleanDomainVerificationJobs(domainId, models) return } @@ -93,7 +87,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) // delete any related domain verification jobs that may exist - await deleteDomainJobs(domainId, models) + await cleanDomainVerificationJobs(domainId, models) } throw error @@ -254,7 +248,7 @@ async function checkACMValidation (domain, models, record) { let message = null const { certStatus, error } = await checkCertificateStatus(domain.certificate.certificateArn) - if (certStatus) { + if (!error) { if (certStatus !== domain.certificate.status) { console.log(`certificate status for ${domain.domainName} has changed from ${domain.certificate.status} to ${certStatus}`) await models.domainCertificate.update({ From f04aa912de7fe17975c759ffe616daacbca4523f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 7 Apr 2026 17:27:46 +0200 Subject: [PATCH 084/154] adapt to Next.js 16 and Apollo Client 4 changes - useMutation, useQuery imports from @apollo/client/react - restore lib/fetch.js import of getAgent - use NextRequest for request re-creation --- components/territory-domains.js | 2 +- lib/fetch.js | 6 ++---- proxy.js | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index cbaabaef8..70ee8dbcf 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -1,6 +1,6 @@ import { Badge, Button } from 'react-bootstrap' import { Form, Input, SubmitButton, CopyButton } from './form' -import { useMutation, useQuery } from '@apollo/client' +import { useMutation, useQuery } from '@apollo/client/react' import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' import { NORMAL_POLL_INTERVAL_MS, SSR } from '@/lib/constants' diff --git a/lib/fetch.js b/lib/fetch.js index 3f451bf9b..a0f8f6e6b 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -1,6 +1,7 @@ import { TimeoutError, timeoutSignal } from '@/lib/time' import crossFetch from 'cross-fetch' import { TOR_REGEXP } from '@/lib/url' +import { getAgent } from '@/lib/proxy' export class FetchTimeoutError extends TimeoutError { constructor (method, url, timeout) { @@ -60,10 +61,7 @@ export async function snFetch (url, { path, protocol = 'https', timeout = 10000, } // Server-only: get proxy agent for Tor and custom certs (unless agent: false) - // TODO/ADDRESS BEFORE REVIEW: dynamic import to avoid pulling Node.js 'http' into the edge runtime via middleware - const fetchAgent = isServer && agent !== false - ? (await import('@/lib/proxy')).getAgent({ hostname: urlObj.hostname, cert, protocol: urlObj.protocol }) - : undefined + const fetchAgent = isServer && agent !== false ? getAgent({ hostname: urlObj.hostname, cert, protocol: urlObj.protocol }) : undefined // Server-only: add user-agent header const headers = isServer ? snUserAgent(options.headers) : options.headers diff --git a/proxy.js b/proxy.js index 74e78bfe7..3b0cb042c 100644 --- a/proxy.js +++ b/proxy.js @@ -1,5 +1,5 @@ import 'urlpattern-polyfill' -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { getDomainMapping } from '@/lib/domains' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) @@ -206,7 +206,7 @@ export async function proxy (req) { // clear subname header to prevent potential spoofing const headers = new Headers(req.headers) headers.delete('x-stacker-news-subname') - const request = new Request(req, { headers }) + const request = new NextRequest(req, { headers }) const referrerResp = referrerMiddleware(request) // TODO: check if we actually need this, and WHY From 41a0307e72b15b15509c4354fd6dca27a2d31c72 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 12:54:52 +0200 Subject: [PATCH 085/154] remove legacyBehavior and passHref, use NextLink as anchor for components that by default use their own --- components/nav/common.js | 89 ++++++++++-------------------- components/nav/mobile/offcanvas.js | 36 ++++-------- components/user-header.js | 36 +++++------- pages/satistics/index.js | 8 +-- pages/settings/index.js | 20 ++----- pages/withdraw.js | 8 +-- 6 files changed, 63 insertions(+), 134 deletions(-) diff --git a/components/nav/common.js b/components/nav/common.js index 6c84a7e3d..a9395bbc3 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -26,11 +26,9 @@ import { numWithUnits } from '@/lib/format' export function Brand ({ className }) { return ( - - - - - + + + ) } @@ -84,11 +82,9 @@ export function BackOrBrand ({ className }) { export function SearchItem ({ prefix, className }) { return ( - - - - - + + + ) } @@ -120,13 +116,11 @@ export function NavNotifications ({ className }) { return ( <> - - - - - - - + + + + + ) } @@ -149,11 +143,9 @@ export function NavWalletSummary ({ className }) { return ( - - - - - + + + ) } @@ -193,34 +185,20 @@ export function MeDropdown ({ me, dropNavKey }) {
- - - profile - - - - bookmarks - - - - wallets - - - - credits - - - satistics - + + profile + + bookmarks + + wallets + + credits + satistics - - invites - + invites
- - settings - + settings
@@ -395,24 +373,13 @@ export function Sorts ({ sub, prefix, className }) { return ( <> - - lit - + lit - - new - + new - - top - + top ) diff --git a/components/nav/mobile/offcanvas.js b/components/nav/mobile/offcanvas.js index ed08e8798..6b41b2722 100644 --- a/components/nav/mobile/offcanvas.js +++ b/components/nav/mobile/offcanvas.js @@ -54,34 +54,20 @@ export default function OffCanvas ({ me, dropNavKey }) { {me ? ( <> - - - profile - - - - bookmarks - - - - wallets - - - - credits - - - satistics - + + profile + + bookmarks + + wallets + + credits + satistics - - invites - + invites
- - settings - + settings
diff --git a/components/user-header.js b/components/user-header.js index f6bc4e2a4..ae53b13df 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -50,32 +50,26 @@ export default function UserHeader ({ user }) { activeKey={activeKey} > - - bio - + bio - - - {numWithUnits(user.nitems, { - abbreviate: false, - unitSingular: 'item', - unitPlural: 'items' - })} - - + + {numWithUnits(user.nitems, { + abbreviate: false, + unitSingular: 'item', + unitPlural: 'items' + })} + {showTerritoriesTab && ( - - - {numWithUnits(user.nterritories, { - abbreviate: false, - unitSingular: 'territory', - unitPlural: 'territories' - })} - - + + {numWithUnits(user.nterritories, { + abbreviate: false, + unitSingular: 'territory', + unitPlural: 'territories' + })} + )} diff --git a/pages/satistics/index.js b/pages/satistics/index.js index 01f7afba0..e0377d3c1 100644 --- a/pages/satistics/index.js +++ b/pages/satistics/index.js @@ -24,14 +24,10 @@ export function SatisticsHeader () { activeKey={activeKey} > - - history - + history - - graphs - + graphs diff --git a/pages/settings/index.js b/pages/settings/index.js index 3d27b9497..bd3a76a57 100644 --- a/pages/settings/index.js +++ b/pages/settings/index.js @@ -50,29 +50,19 @@ export function SettingsHeader () { activeKey={activeKey} > - - general - + general - - logins - + logins - - wallets - + wallets - - subscriptions - + subscriptions - - mutes - + mutes diff --git a/pages/withdraw.js b/pages/withdraw.js index e07b8d6cc..640d15360 100644 --- a/pages/withdraw.js +++ b/pages/withdraw.js @@ -49,14 +49,10 @@ function WithdrawForm () { activeKey={router.query.type ?? 'invoice'} > - - invoice - + invoice - - lightning address - + lightning address From f9e541f3fa3047be2eb08e06155457f08c0dc1bc Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 13:35:37 +0200 Subject: [PATCH 086/154] remove custom domains API endpoint, check domains via cached direct query to db --- lib/domains.js | 28 ++++++++++++++++++---------- pages/api/domains/index.js | 35 ----------------------------------- proxy.js | 8 ++++---- 3 files changed, 22 insertions(+), 49 deletions(-) delete mode 100644 pages/api/domains/index.js diff --git a/lib/domains.js b/lib/domains.js index 48fa492fb..36b0a4bbb 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -1,24 +1,32 @@ import { cachedFetcher } from '@/lib/fetch' +import prisma from '@/api/models' export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMappings () { - const url = `${process.env.NEXT_PUBLIC_URL}/api/domains` - console.log('[domains] cache miss, fetching domain mappings from', url) // TEST try { - const response = await fetch(url) - if (!response.ok) { - console.log('[domains] error fetching domain mappings', response.statusText) // TEST - return null - } + const domains = await prisma.domain.findMany({ + select: { + domainName: true, + subName: true + }, + where: { + status: 'ACTIVE' + } + }) - const data = await response.json() - return Object.keys(data).length > 0 ? data : null + if (!domains.length) return null + + return domains.reduce((acc, domain) => { + acc[domain.domainName.toLowerCase()] = { subName: domain.subName } + return acc + }, {}) } catch (error) { - console.error('[domains] error fetching domain mappings', error) // TEST + console.error('[domains] error fetching domain mappings', error) return null } }, { cacheExpiry: 1000 * 60 * 5, // 5 minutes cache forceRefreshThreshold: 1000 * 60 * 10, // 10 minutes before cache expiry + debug: true, // TEST keyGenerator: () => 'domain_mappings' }) diff --git a/pages/api/domains/index.js b/pages/api/domains/index.js deleted file mode 100644 index dc85e6b71..000000000 --- a/pages/api/domains/index.js +++ /dev/null @@ -1,35 +0,0 @@ -import prisma from '@/api/models' - -// API Endpoint for getting all VERIFIED custom domains, used by a cachedFetcher -export default async function handler (req, res) { - // Only allow GET requests - if (req.method !== 'GET') { - return res.status(405).json({ error: 'Method not allowed' }) - } - - try { - // fetch all VERIFIED custom domains from the database - const domains = await prisma.domain.findMany({ - select: { - domainName: true, - subName: true - }, - where: { - status: 'ACTIVE' - } - }) - - // map domains to a key-value pair - const domainMappings = domains.reduce((acc, domain) => { - acc[domain.domainName.toLowerCase()] = { - subName: domain.subName - } - return acc - }, {}) - - return res.status(200).json(domainMappings) - } catch (error) { - console.error('cannot fetch domains:', error) - return res.status(500).json({ error: 'Failed to fetch domains' }) - } -} diff --git a/proxy.js b/proxy.js index 3b0cb042c..d6243201f 100644 --- a/proxy.js +++ b/proxy.js @@ -221,10 +221,10 @@ export async function proxy (req) { // in development we might have a port in the domain const domainToMap = process.env.NODE_ENV === 'development' ? domain.split(':')[0] : domain // check if we have a mapping for this domain - const subName = await getDomainMapping(domainToMap) - if (subName) { - console.log('[domains] allowed custom domain', domain, 'detected, pointing to', subName) // TEST - const resp = await customDomainMiddleware(request, domain, subName.subName) + const mapping = await getDomainMapping(domainToMap) + if (mapping?.subName) { + console.log('[domains] allowed custom domain', domain, 'detected, pointing to', mapping.subName) // TEST + const resp = await customDomainMiddleware(request, domain, mapping.subName) // apply referrer cookies to the custom domain response const referredResp = applyReferrerCookies(resp, referrerResp) // finally apply security headers From d466483e58c91b5c919f021d707480636a6f8f5f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 13:36:10 +0200 Subject: [PATCH 087/154] fix: workaround to enable HMR on custom domains introduces a new environment variable `ALLOWED_DEV_ORIGINS` that is used by `next.config.js` to determine if an origin (in dev) can be trusted, re-enabling HMR. --- .env.development | 4 ++++ next.config.js | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/.env.development b/.env.development index c8f3f8566..e78e340d0 100644 --- a/.env.development +++ b/.env.development @@ -203,6 +203,10 @@ NEXT_TELEMETRY_DISABLED=1 # local DNS server for custom domain verification, by default it's dnsmasq. # reachable by containers on 172.30.0.2(:53), outside of docker with 0.0.0.0:5353 DOMAINS_DNS_SERVER=172.30.0.2 +# comma-separated list of additional allowed origins for webpack HMR in dev +# you can either use suffixes (.pizza,.lightning) or full domains (pizza.point.com,news.stacker) +# .sndev is always included +ALLOWED_DEV_ORIGINS= # MDAST pipeline debugging NEXT_PUBLIC_MDAST_DEBUG=0 \ No newline at end of file diff --git a/next.config.js b/next.config.js index 5b79319a4..827dea7a6 100644 --- a/next.config.js +++ b/next.config.js @@ -47,6 +47,13 @@ try { } module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi3E-sauvdFABb.js' })({ + // enables Next.js HMR for custom domains in development + allowedDevOrigins: isProd + ? undefined + : [ + '.sndev', + ...(process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || []) + ], env: { NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, From 0d749ac6e367c01cffe878e2488fc3be65f1baeb Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 13:40:06 +0200 Subject: [PATCH 088/154] remove default `.sndev` suffix from `next.config.js`, move to `.env.development` --- .env.development | 2 +- next.config.js | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.env.development b/.env.development index e78e340d0..d65b93d67 100644 --- a/.env.development +++ b/.env.development @@ -206,7 +206,7 @@ DOMAINS_DNS_SERVER=172.30.0.2 # comma-separated list of additional allowed origins for webpack HMR in dev # you can either use suffixes (.pizza,.lightning) or full domains (pizza.point.com,news.stacker) # .sndev is always included -ALLOWED_DEV_ORIGINS= +ALLOWED_DEV_ORIGINS=.sndev # MDAST pipeline debugging NEXT_PUBLIC_MDAST_DEBUG=0 \ No newline at end of file diff --git a/next.config.js b/next.config.js index 827dea7a6..cd5d5a18f 100644 --- a/next.config.js +++ b/next.config.js @@ -50,10 +50,7 @@ module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi // enables Next.js HMR for custom domains in development allowedDevOrigins: isProd ? undefined - : [ - '.sndev', - ...(process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || []) - ], + : process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || [], env: { NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, From fa17f5b539cea63ff7326e8dc0cca2fb2ae6703e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 13:46:42 +0200 Subject: [PATCH 089/154] remove old comment about sndev being always included --- .env.development | 1 - 1 file changed, 1 deletion(-) diff --git a/.env.development b/.env.development index d65b93d67..98ef76b4f 100644 --- a/.env.development +++ b/.env.development @@ -205,7 +205,6 @@ NEXT_TELEMETRY_DISABLED=1 DOMAINS_DNS_SERVER=172.30.0.2 # comma-separated list of additional allowed origins for webpack HMR in dev # you can either use suffixes (.pizza,.lightning) or full domains (pizza.point.com,news.stacker) -# .sndev is always included ALLOWED_DEV_ORIGINS=.sndev # MDAST pipeline debugging From 0d154c05744dc21e29856ad2c03d511df90ef4aa Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 14:10:46 +0200 Subject: [PATCH 090/154] correct again allowedDevOrigins usage --- .env.development | 4 ++-- next.config.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.development b/.env.development index 98ef76b4f..1cc62afaf 100644 --- a/.env.development +++ b/.env.development @@ -204,8 +204,8 @@ NEXT_TELEMETRY_DISABLED=1 # reachable by containers on 172.30.0.2(:53), outside of docker with 0.0.0.0:5353 DOMAINS_DNS_SERVER=172.30.0.2 # comma-separated list of additional allowed origins for webpack HMR in dev -# you can either use suffixes (.pizza,.lightning) or full domains (pizza.point.com,news.stacker) -ALLOWED_DEV_ORIGINS=.sndev +# use ** wildcard for subdomains (**.pizza,**.lightning) or full domains (pizza.point.com,www.news.stacker) +ALLOWED_DEV_ORIGINS=**.sndev # MDAST pipeline debugging NEXT_PUBLIC_MDAST_DEBUG=0 \ No newline at end of file diff --git a/next.config.js b/next.config.js index cd5d5a18f..a4283c161 100644 --- a/next.config.js +++ b/next.config.js @@ -50,7 +50,7 @@ module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi // enables Next.js HMR for custom domains in development allowedDevOrigins: isProd ? undefined - : process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || [], + : process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || undefined, env: { NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, From c0d6934ed7711a2c6ed70d5d64e4e0cf96ecbac8 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 14:14:04 +0200 Subject: [PATCH 091/154] correct forceRefreshThreshold and cacheExpiry usage; lower to 5 minutes of cache expiration and 2 minutes background refreshes --- lib/domains.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/domains.js b/lib/domains.js index 36b0a4bbb..c209f2e24 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -24,8 +24,8 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap return null } }, { - cacheExpiry: 1000 * 60 * 5, // 5 minutes cache - forceRefreshThreshold: 1000 * 60 * 10, // 10 minutes before cache expiry + forceRefreshThreshold: 1000 * 60 * 2, // 2 minutes before cache expiry + cacheExpiry: 1000 * 60 * 5, // 5 minutes cache expiry debug: true, // TEST keyGenerator: () => 'domain_mappings' }) From 8c25df342b7f92fbccfbdcd667bd8aa280ed22f7 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 14:25:12 +0200 Subject: [PATCH 092/154] simple logger for custom domains gated by NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG env var, mirrored from cachedFetcher's logger --- .env.development | 2 ++ lib/domains.js | 21 ++++++++++++++++++++- proxy.js | 28 ++++++++++++++-------------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/.env.development b/.env.development index 1cc62afaf..280b930ed 100644 --- a/.env.development +++ b/.env.development @@ -206,6 +206,8 @@ DOMAINS_DNS_SERVER=172.30.0.2 # comma-separated list of additional allowed origins for webpack HMR in dev # use ** wildcard for subdomains (**.pizza,**.lightning) or full domains (pizza.point.com,www.news.stacker) ALLOWED_DEV_ORIGINS=**.sndev +# custom domains debugging +NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG=1 # MDAST pipeline debugging NEXT_PUBLIC_MDAST_DEBUG=0 \ No newline at end of file diff --git a/lib/domains.js b/lib/domains.js index c209f2e24..e54e3579b 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -26,7 +26,7 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap }, { forceRefreshThreshold: 1000 * 60 * 2, // 2 minutes before cache expiry cacheExpiry: 1000 * 60 * 5, // 5 minutes cache expiry - debug: true, // TEST + debug: process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG, keyGenerator: () => 'domain_mappings' }) @@ -34,3 +34,22 @@ export const getDomainMapping = async (domain) => { const domainsMappings = await domainsMappingsCache() return domainsMappings?.[domain?.toLowerCase()] } + +export function createDomainsDebugLogger (domainName, debug = process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG) { + const noop = () => {} + + if (!debug) { + return { + log: noop, + errorLog: noop + } + } + + const log = (message, ...args) => console.log(`[DOMAINS:${domainName}] ${message}`, ...args) + const errorLog = (message, ...args) => console.error(`[DOMAINS:${domainName}] ${message}`, ...args) + + return { + log, + errorLog + } +} diff --git a/proxy.js b/proxy.js index d6243201f..8044905f0 100644 --- a/proxy.js +++ b/proxy.js @@ -1,6 +1,6 @@ import 'urlpattern-polyfill' import { NextRequest, NextResponse } from 'next/server' -import { getDomainMapping } from '@/lib/domains' +import { getDomainMapping, createDomainsDebugLogger } from '@/lib/domains' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) @@ -19,6 +19,8 @@ const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) const SN_TERRITORY_PATHS = ['/~', '/new', '/top', '/post', '/edit', '/rss'] async function customDomainMiddleware (request, domain, subName) { + // logger is enabled if NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG == 1 + const logger = createDomainsDebugLogger(domain) // clone the url to build on top of it const url = request.nextUrl.clone() // we need pathname, searchParams and origin @@ -27,12 +29,11 @@ async function customDomainMiddleware (request, domain, subName) { const headers = new Headers(request.headers) headers.set('x-stacker-news-subname', subName) - // TEST - console.log('[domains] custom domain', domain, 'with subname', subName) // TEST - console.log('[domains] main domain', JSON.stringify(SN_MAIN_DOMAIN)) // TEST - console.log('[domains] pathname', pathname) // TEST - console.log('[domains] searchParams', JSON.stringify(searchParams)) // TEST - console.log('[domains] search', url.search) + logger.log('custom domain', domain, 'with subname', subName) + logger.log('main domain', JSON.stringify(SN_MAIN_DOMAIN)) + logger.log('pathname', pathname) + logger.log('searchParams', JSON.stringify(searchParams)) + logger.log('search', url.search) // TODO: handle auth sync @@ -40,25 +41,25 @@ async function customDomainMiddleware (request, domain, subName) { if (pathname.startsWith('/~')) { const cleanPath = pathname.replace(/^\/~[^/]+/, '') || '/' url.pathname = cleanPath - console.log('[domains] redirecting to clean url:', url) // TEST + logger.log('redirecting to clean url:', url) // redirect to the clean path return NextResponse.redirect(url, { headers }) } // if sub param exists and doesn't match the domain's subname, update it if (searchParams.has('sub') && searchParams.get('sub') !== subName) { - console.log('[domains] setting sub to', subName) // TEST + logger.log('setting sub to', subName) searchParams.set('sub', subName) url.search = searchParams.toString() - console.log('[domains] new searchParams', url.search) - console.log('[domains] new url', url) + logger.log('new searchParams', url.search) + logger.log('new url', url) return NextResponse.redirect(url, { headers }) } // if we're at the root or on some territory path, hide the subname by rewriting if (pathname === '/' || SN_TERRITORY_PATHS.some(p => pathname.startsWith(p))) { url.pathname = `/~${subName}${pathname === '/' ? '' : pathname}` - console.log('[domains] rewrite to:', url.pathname) // TEST + logger.log('rewrite to:', url.pathname) // rewrite to the territory path return NextResponse.rewrite(url, { headers }) } @@ -151,7 +152,6 @@ function applyReferrerCookies (response, referrer) { } ) } - console.log('[domains] response.cookies', response.cookies) // TEST return response } @@ -223,7 +223,7 @@ export async function proxy (req) { // check if we have a mapping for this domain const mapping = await getDomainMapping(domainToMap) if (mapping?.subName) { - console.log('[domains] allowed custom domain', domain, 'detected, pointing to', mapping.subName) // TEST + console.log('[domains] allowed custom domain', domain, 'detected, pointing to', mapping.subName) const resp = await customDomainMiddleware(request, domain, mapping.subName) // apply referrer cookies to the custom domain response const referredResp = applyReferrerCookies(resp, referrerResp) From c8119e821d2dfc46d27795bc05a243116b48078f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 14:44:49 +0200 Subject: [PATCH 093/154] merge custom-domains-authsync --- components/login.js | 14 ++- components/nav/common.js | 6 +- components/territory-domains.js | 2 - docs/dev/custom-domains.md | 28 ++++++ pages/api/auth/sync.js | 155 ++++++++++++++++++++++++++++++++ pages/login.js | 9 +- proxy.js | 61 ++++++++++++- 7 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 pages/api/auth/sync.js diff --git a/components/login.js b/components/login.js index fe01a7f36..cceeeff91 100644 --- a/components/login.js +++ b/components/login.js @@ -11,8 +11,9 @@ import { emailSchema } from '@/lib/validate' import { OverlayTrigger, Tooltip } from 'react-bootstrap' import { datePivot } from '@/lib/time' import * as cookie from 'cookie' -import { cookieOptions } from '@/lib/auth' +import { cookieOptions, MULTI_AUTH_ANON, MULTI_AUTH_POINTER } from '@/lib/auth' import Link from 'next/link' +import useCookie from './use-cookie' export function EmailLoginForm ({ text, callbackUrl, multiAuth }) { const disabled = multiAuth @@ -73,9 +74,18 @@ export function authErrorMessage (error, signin) { const multiAuthProviders = ['Lightning', 'Nostr'] -export default function Login ({ providers, callbackUrl, multiAuth, error, text, Header, Footer, signin }) { +export default function Login ({ providers, callbackUrl, multiAuth, error, text, Header, Footer, signin, syncSignup }) { const [errorMessage, setErrorMessage] = useState(authErrorMessage(error, signin)) const router = useRouter() + const [, setPointerCookie] = useCookie(MULTI_AUTH_POINTER) + + // we can't signup if we're already logged in to another account + // for signups with auth sync, we first need to switch to anon. + useEffect(() => { + if (syncSignup) { + setPointerCookie(MULTI_AUTH_ANON, cookieOptions({ httpOnly: false })) + } + }, [syncSignup, setPointerCookie]) multiAuth = typeof multiAuth === 'string' ? multiAuth === 'true' : !!multiAuth diff --git a/components/nav/common.js b/components/nav/common.js index a9395bbc3..16bb50344 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -23,6 +23,7 @@ import SwitchAccountList, { nextAccount, useAccounts, useIsLurker } from '@/comp import { useShowModal } from '@/components/modal' import { ObstacleButtons } from '@/components/obstacle' import { numWithUnits } from '@/lib/format' +import { useDomain } from '@/components/territory-domains' export function Brand ({ className }) { return ( @@ -258,6 +259,7 @@ export default function LoginButton () { function LogoutObstacle ({ onClose }) { const { registration: swRegistration, togglePushSubscription } = useServiceWorker() const router = useRouter() + const { domain } = useDomain() const handleLogout = async () => { const next = await nextAccount() @@ -275,7 +277,9 @@ function LogoutObstacle ({ onClose }) { await togglePushSubscription().catch(console.error) } - await signOut({ callbackUrl: '/' }) + onClose() + await signOut({ callbackUrl: '/', redirect: !domain }) + domain && router.push('/') } return ( diff --git a/components/territory-domains.js b/components/territory-domains.js index 70ee8dbcf..16247c9e0 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -28,8 +28,6 @@ export const DomainProvider = ({ domain: ssrDomain, children }) => { } }, [ssrDomain]) - // TODO: Placeholder for Auth Sync - return ( {/* TODO: Placeholder for Branding */} diff --git a/docs/dev/custom-domains.md b/docs/dev/custom-domains.md index 22eadc208..f4614bad8 100644 --- a/docs/dev/custom-domains.md +++ b/docs/dev/custom-domains.md @@ -181,6 +181,34 @@ It detaches the ACM certificate from our ALB listener and then deletes the ACM c It's a necessary step to ensure that we don't waste AWS resources and also provide safety regarding the custom domain access to Stacker News. +# Auth Sync + +Cross-domain JWT authentication is a complex issue due to browser security restrictions, mainly because cookies: +- are bound to specific domains +- -- cookie property of `stacker.news` - **DON'T EAT** + +and + +- can't be set for another domain +- -- `stacker.news` <- cookie -> `pizza.com` 🚫 + +Instead of fighting these restrictions, Auth Sync works with them by creating a whole new session: +- user visits `pizza.com/login` +- middleware redirects to auth sync **on the main domain** accessing that domain cookies +- -- `https://stacker.news/api/auth/sync?domain=pizza.com&redirectUri=/items/212142` +- checks if pizza.com is an **allowed domain** +- checks if there's a session +- -- if not: redirects to `stacker.news/login` with `/api/auth/sync` as callback to continue syncing +- auth sync creates a short-lived verification token and redirects back to the custom domain with the `token` parameter +- -- `https://pizza.com/?token=42424242&redirectUri=/items/212142` +- middleware exchanges this token for a session, **setting the session cookie** on pizza.com +- -- `POST: https://stacker.news/api/auth/sync; token: 42424242` + + +This design focuses on security as the verification token is a one-time code that dies in **5 minutes** and has **256 bits** of entropy. The JWT is then generated server-side and applied to the final middleware response. + + + # Neat stuff ### Let's go HTTPS with a reverse proxy diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js new file mode 100644 index 000000000..0081dc38b --- /dev/null +++ b/pages/api/auth/sync.js @@ -0,0 +1,155 @@ +import models from '@/api/models' +import { randomBytes } from 'node:crypto' +import { encode as encodeJWT, getToken } from 'next-auth/jwt' +import { validateSchema, customDomainSchema } from '@/lib/validate' + +const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) +const SYNC_TOKEN_MAX_AGE = 60 * 5 // 5 minutes +const VERIFICATION_TOKEN_EXPIRY = 1000 * 60 * 5 // 5 minutes in milliseconds + +export default async function handler (req, res) { + try { + if (req.method === 'POST') { + const { verificationToken } = req.body + if (!verificationToken) { + return res.status(400).json({ status: 'ERROR', reason: 'verification token is required' }) + } + + const verificationResult = await consumeVerificationToken(verificationToken) + if (verificationResult.status === 'ERROR') { + return res.status(400).json(verificationResult) + } + + const sessionTokenResult = await createEphemeralSessionToken(verificationResult.userId) + if (sessionTokenResult.status === 'ERROR') { + return res.status(500).json(sessionTokenResult) + } + + return res.status(200).json({ status: 'OK', sessionToken: sessionTokenResult.sessionToken }) + } + + if (req.method === 'GET') { + const { domain, redirectUri, signup } = req.query + if (!domain || !redirectUri?.startsWith('/')) { + return res.status(400).json({ status: 'ERROR', reason: 'domain and a correct redirectUri are required' }) + } + + const domainValidation = await checkDomainValidity(domain) + if (domainValidation.status === 'ERROR') { + return res.status(400).json(domainValidation) + } + + if (signup) { + return handleNoSession(res, domain, redirectUri, signup) + } + + const sessionToken = await getToken({ req }) + if (!sessionToken) { + return handleNoSession(res, domain, redirectUri) + } + + const newVerificationToken = await createVerificationToken(sessionToken) + if (newVerificationToken.status === 'ERROR') { + return res.status(500).json(newVerificationToken) + } + + return redirectToDomain(res, domain, newVerificationToken.token, redirectUri) + } + } catch (error) { + return res.status(500).json({ status: 'ERROR', reason: 'auth sync broke its legs' }) + } +} + +async function checkDomainValidity (domainName) { + try { + await validateSchema(customDomainSchema, { domainName }) + const domain = await models.domain.findUnique({ + where: { domainName, status: 'ACTIVE' } + }) + + if (!domain) { + return { status: 'ERROR', reason: 'domain not allowed' } + } + + return { status: 'OK' } + } catch (error) { + return { status: 'ERROR', reason: 'domain is not valid' } + } +} + +function handleNoSession (res, domainName, redirectUri, signup = false) { + const syncUrl = new URL('/api/auth/sync', SN_MAIN_DOMAIN) + syncUrl.searchParams.set('domain', domainName) + syncUrl.searchParams.set('redirectUri', redirectUri) + + const loginRedirectUrl = new URL(signup ? '/signup' : '/login', SN_MAIN_DOMAIN) + if (signup) loginRedirectUrl.searchParams.set('syncSignup', 'true') + loginRedirectUrl.searchParams.set('callbackUrl', syncUrl.href) + + res.redirect(302, loginRedirectUrl.href) +} + +async function createVerificationToken (token) { + try { + const verificationToken = await models.verificationToken.create({ + data: { + identifier: token.id.toString(), + token: randomBytes(32).toString('hex'), + expires: new Date(Date.now() + VERIFICATION_TOKEN_EXPIRY) + } + }) + return { status: 'OK', token: verificationToken.token } + } catch (error) { + return { status: 'ERROR', reason: 'failed to create verification token' } + } +} + +async function redirectToDomain (res, domainName, verificationToken, redirectUri) { + try { + const protocol = process.env.NODE_ENV === 'development' ? 'http' : 'https' + const target = new URL(`${protocol}://${domainName}`) + + target.searchParams.set('sync_token', verificationToken) + target.searchParams.set('redirectUri', redirectUri) + + res.redirect(302, target.href) + } catch (error) { + return { status: 'ERROR', reason: 'cannot construct the URL' } + } +} + +async function consumeVerificationToken (verificationToken) { + try { + const identifier = await models.$transaction(async tx => { + const token = await tx.verificationToken.findFirst({ + where: { + token: verificationToken, + expires: { gt: new Date() } + } + }) + if (!token) throw new Error('invalid verification token') + + await tx.verificationToken.delete({ where: { id: token.id } }) + + return token.identifier + }) + + return { status: 'OK', userId: Number(identifier) } + } catch (error) { + return { status: 'ERROR', reason: 'cannot validate verification token' } + } +} + +async function createEphemeralSessionToken (userId) { + try { + const sessionToken = await encodeJWT({ + token: { id: userId, sub: userId }, + secret: process.env.NEXTAUTH_SECRET, + maxAge: SYNC_TOKEN_MAX_AGE + }) + + return { status: 'OK', sessionToken } + } catch (error) { + return { status: 'ERROR', reason: 'failed to create ephemeral session token' } + } +} diff --git a/pages/login.js b/pages/login.js index fced474d3..48067ad24 100644 --- a/pages/login.js +++ b/pages/login.js @@ -7,7 +7,7 @@ import Login from '@/components/login' import { isExternal } from '@/lib/url' import { MULTI_AUTH_ANON, MULTI_AUTH_POINTER } from '@/lib/auth' -export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, error = null } }) { +export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, syncSignup = null, error = null } }) { let session = await getServerSession(req, res, getAuthOptions(req)) // required to prevent infinite redirect loops if we switch to anon @@ -30,9 +30,9 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult callbackUrl = '/' } - if (session && callbackUrl && !multiAuth) { + if (session && callbackUrl && !multiAuth && !syncSignup) { // in the case of auth linking we want to pass the error back to settings - // in the case of multi auth, don't redirect if there is already a session + // in the case of multi auth or auth sync signup, don't redirect if there is already a session if (error) { const url = new URL(callbackUrl, process.env.NEXT_PUBLIC_URL) url.searchParams.set('error', error) @@ -54,7 +54,8 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult providers, callbackUrl, error, - multiAuth + multiAuth, + syncSignup } } } diff --git a/proxy.js b/proxy.js index 8044905f0..988342f82 100644 --- a/proxy.js +++ b/proxy.js @@ -1,6 +1,7 @@ import 'urlpattern-polyfill' import { NextRequest, NextResponse } from 'next/server' import { getDomainMapping, createDomainsDebugLogger } from '@/lib/domains' +import { SESSION_COOKIE, cookieOptions } from '@/lib/auth' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) @@ -35,7 +36,12 @@ async function customDomainMiddleware (request, domain, subName) { logger.log('searchParams', JSON.stringify(searchParams)) logger.log('search', url.search) - // TODO: handle auth sync + // Auth Sync + if (pathname.startsWith('/login') || pathname.startsWith('/signup')) { + const signup = pathname.startsWith('/signup') + return redirectToAuthSync(searchParams, domain, signup, headers) + } + if (searchParams.has('sync_token')) return establishAuthSync(request, searchParams, headers) // clean up the pathname from any subname if (pathname.startsWith('/~')) { @@ -68,6 +74,59 @@ async function customDomainMiddleware (request, domain, subName) { return NextResponse.next({ request: { headers } }) } +async function redirectToAuthSync (searchParams, domain, signup, headers) { + const syncUrl = new URL('/api/auth/sync', SN_MAIN_DOMAIN) + syncUrl.searchParams.set('domain', domain) + + if (signup) { + syncUrl.searchParams.set('signup', 'true') + } + + if (searchParams.has('callbackUrl')) { + const callbackUrl = searchParams.get('callbackUrl') + const redirectUri = callbackUrl.startsWith('http') + ? new URL(callbackUrl).pathname + : callbackUrl + syncUrl.searchParams.set('redirectUri', redirectUri) + } + + return NextResponse.redirect(syncUrl, { headers }) +} + +async function establishAuthSync (request, searchParams, headers) { + const token = searchParams.get('sync_token') + const redirectUri = searchParams.get('redirectUri') || '/' + const res = NextResponse.redirect(new URL(redirectUri, request.url), { headers }) + + try { + const body = JSON.stringify({ verificationToken: token }) + const fetchHeaders = new Headers(headers) + fetchHeaders.set('Content-Type', 'application/json') + + const response = await fetch(`${SN_MAIN_DOMAIN.origin}/api/auth/sync`, { + method: 'POST', + headers: fetchHeaders, + body, + signal: AbortSignal.timeout(10000) + }) + + if (!response.ok) { + throw new Error(response.status) + } + + const data = await response.json() + if (data.status === 'ERROR') { + throw new Error(data.reason) + } + + res.cookies.set(SESSION_COOKIE, data.sessionToken, cookieOptions()) + return res + } catch (error) { + console.error('[auth sync] cannot establish auth sync:', error.message) + return NextResponse.redirect(new URL('/error', request.url), { headers }) + } +} + function getContentReferrer (request, url) { if (itemPattern.test(url)) { let id = request.nextUrl.searchParams.get('commentId') From d999ba009ebdb07110dd243b5561f973bdf6907b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 15:01:06 +0200 Subject: [PATCH 094/154] sync: support local custom domains with port, default / redirectUri --- pages/api/auth/sync.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js index 0081dc38b..b479e3d5e 100644 --- a/pages/api/auth/sync.js +++ b/pages/api/auth/sync.js @@ -29,7 +29,7 @@ export default async function handler (req, res) { } if (req.method === 'GET') { - const { domain, redirectUri, signup } = req.query + const { domain, redirectUri = '/', signup } = req.query if (!domain || !redirectUri?.startsWith('/')) { return res.status(400).json({ status: 'ERROR', reason: 'domain and a correct redirectUri are required' }) } @@ -60,7 +60,9 @@ export default async function handler (req, res) { } } -async function checkDomainValidity (domainName) { +async function checkDomainValidity (receivedDomain) { + const domainName = process.env.NODE_ENV === 'development' ? receivedDomain.split(':')[0] : receivedDomain + try { await validateSchema(customDomainSchema, { domainName }) const domain = await models.domain.findUnique({ @@ -73,6 +75,7 @@ async function checkDomainValidity (domainName) { return { status: 'OK' } } catch (error) { + console.error('[auth sync] domain is not valid', error) return { status: 'ERROR', reason: 'domain is not valid' } } } From 2c35d71e1c8f805a5e88222e0069b36ab2b4e275 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 8 Apr 2026 15:02:54 +0200 Subject: [PATCH 095/154] remove Sorts spacings on the desktop second top bar to compensate for SubSelect absence --- components/nav/desktop/second-bar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/nav/desktop/second-bar.js b/components/nav/desktop/second-bar.js index bfdffaa32..59aa4467e 100644 --- a/components/nav/desktop/second-bar.js +++ b/components/nav/desktop/second-bar.js @@ -14,7 +14,9 @@ export default function SecondBar (props) { activeKey={topNavKey} > {!domain && } -
+
+ +
From af6db57b1ec55456c64608301fa25a6ab53887b7 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 9 Apr 2026 16:23:01 +0200 Subject: [PATCH 096/154] fix: case-insensitive domains per RFC 4343, domain saved to lower case --- api/resolvers/domain.js | 2 +- lib/validate.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 40239e7eb..1f10f7101 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -81,7 +81,7 @@ export default { }) if (domainName) { - domainName = domainName.trim() + domainName = domainName.trim().toLowerCase() await validateSchema(customDomainSchema, { domainName }) // updating the domain name, recovering from HOLD is allowed diff --git a/lib/validate.js b/lib/validate.js index c97e825d0..2fca55230 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -298,7 +298,7 @@ export function territoryTransferSchema ({ me, ...args }) { // Custom Domain validation schema export function customDomainSchema (args) { return object({ - domainName: string().matches(/^(?:[a-z0-9-]+\.){2,}[a-z]{2,}$/, { + domainName: string().matches(/^(?:[a-z0-9-]+\.){2,}[a-z]{2,}$/i, { message: 'CNAME records only support subdomains (e.g., www.example.com, sub.example.com)' }).nullable() }) From 4987a25f21c8358994ee8d38e6c4b00cd3a8ce16 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 9 Apr 2026 16:32:54 +0200 Subject: [PATCH 097/154] fix: invert cacheExpiry and forceRefreshThreshold values --- lib/domains.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/domains.js b/lib/domains.js index e54e3579b..ed9cc0bd2 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -24,8 +24,8 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap return null } }, { - forceRefreshThreshold: 1000 * 60 * 2, // 2 minutes before cache expiry - cacheExpiry: 1000 * 60 * 5, // 5 minutes cache expiry + forceRefreshThreshold: 1000 * 60 * 5, + cacheExpiry: 1000 * 60 * 2, debug: process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG, keyGenerator: () => 'domain_mappings' }) From d8c06307deda198bd52eabb059ff0c264e2861b8 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 9 Apr 2026 16:33:37 +0200 Subject: [PATCH 098/154] cast string `me.id` to `Number` --- api/resolvers/domain.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 1f10f7101..cd358b0e4 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -45,7 +45,7 @@ export default { throw new GqlInputError('sub not found') } - if (sub.userId !== me.id) { + if (sub.userId !== Number(me.id)) { throw new GqlAuthorizationError('you do not own this sub') } @@ -70,7 +70,7 @@ export default { throw new GqlInputError('sub not found') } - if (sub.userId !== me.id) { + if (sub.userId !== Number(me.id)) { throw new GqlAuthorizationError('you do not own this sub') } From e5463810f5141f204c1118c78f8fc13559a7d074 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 9 Apr 2026 16:37:37 +0200 Subject: [PATCH 099/154] fix: return null for empty domain records instead of an empty array (gql consistency) --- api/resolvers/domain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index cd358b0e4..05372ac7b 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -148,7 +148,7 @@ export default { }, Domain: { records: async (domain) => { - if (!domain.records) return [] + if (!domain.records?.length) return null // O(1) lookups by type, simpler checks for CNAME and ACM validation records return Object.fromEntries(domain.records.map(record => [record.type, record])) From 96fd1f08466306bf23e3ac80823537e6766a2d42 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 15:16:07 +0200 Subject: [PATCH 100/154] fix: allowedDevOrigins starts by default with NEXT_PUBLIC_URL, new ALLOWED_DEV_ORIGINS env var for additional allowed origins --- .env.development | 6 ++++++ next.config.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.env.development b/.env.development index ffc390f37..eca5850ca 100644 --- a/.env.development +++ b/.env.development @@ -60,6 +60,12 @@ NEXTAUTH_SECRET=3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI JWT_SIGNING_PRIVATE_KEY={"kty":"oct","kid":"FvD__hmeKoKHu2fKjUrWbRKfhjimIM4IKshyrJG4KSM","alg":"HS512","k":"3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI"} INVOICE_HMAC_KEY=a4c1d9c81edb87b79d28809876a18cf72293eadb39f92f3f4f2f1cfbdf907c91 +# comma-separated list of additional allowed origins for webpack HMR in dev +# wildcards are supported, e.g. **.local for every level, or *.local for the top level +# full domains are also supported, e.g. sndev.local,linuxpc.local +# when empty, by default, localhost is allowed +ALLOWED_DEV_ORIGINS= + # lnd # xxd -p -c0 docker/lnd/sn/regtest/admin.macaroon LND_CERT=2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943516a4343416569674177494241674951484f4a69597458736c72592f4931376933574c444354414b42676771686b6a4f50515144416a41344d5238770a485159445651514b45785a73626d5167595856306232646c626d56795958526c5a43426a5a584a304d52557745775944565151444577777a4e54526d4d574e690a4f546b7a595451774868634e4d6a55774e54497a4d4467784d444d345768634e4d6a59774e7a45344d4467784d444d34576a41344d523877485159445651514b0a45785a73626d5167595856306232646c626d56795958526c5a43426a5a584a304d52557745775944565151444577777a4e54526d4d574e694f546b7a595451770a5754415442676371686b6a4f5051494242676771686b6a4f50514d4242774e434141524b6d733131422b4e58554e642f54574347492b4b2b5046686b485a31410a5449647732566e766a344f6130784c696c515a4d7779647149586c7a724641485064646a3566697934584c456f43364d4e427636585277706f3448544d4948510a4d41344741315564447745422f775145417749437044415442674e56485355454444414b4267677242674546425163444154415042674e5648524d42416638450a425441444151482f4d42304741315564446751574242526f433554634e58746366464f7458393171364364337a6930327a54423542674e5648524545636a42770a6767777a4e54526d4d574e694f546b7a5954534343577876593246736147397a64494947633235666247356b6768526f62334e304c6d52765932746c636935700a626e526c636d356862494945645735706549494b64573570654842685932746c64494948596e566d59323975626f6345667741414159635141414141414141410a4141414141414141414141414159634572424941427a414b42676771686b6a4f5051514441674e494144424641694541324941462b32436746704a754e5445750a34524f63322f70625870476f4934365573724a65525972614d33414349423974424c6759777a597a2b596b5a4e7a417a7077454c754935564f505959724a6f6b0a7270754d32316b690a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a diff --git a/next.config.js b/next.config.js index e2b6df03a..cca5e0677 100644 --- a/next.config.js +++ b/next.config.js @@ -46,8 +46,26 @@ try { commitHash = '0000' } +const getAllowedDevOrigins = () => { + if (isProd) return undefined + + const devOrigins = [] + try { + // NEXT_PUBLIC_URL is `http://domain:3000`, we just need the domain + devOrigins.push(new URL(process.env.NEXT_PUBLIC_URL).hostname) + } catch {} + + // extra origins, comma separated, can be set in the environment variable ALLOWED_DEV_ORIGINS + const extraOrigins = process.env.ALLOWED_DEV_ORIGINS?.split(',').map(s => s.trim()).filter(Boolean) || [] + devOrigins.push(...extraOrigins) + + // if no origins are found, localhost is always allowed + return devOrigins +} + module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi3E-sauvdFABb.js' })({ env: { + allowedDevOrigins: getAllowedDevOrigins(), NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, NEXT_PUBLIC_ASSET_PREFIX: isProd ? 'https://a.stacker.news' : '', From d7e6c3afcebaf4a696049d7b0511baffd4e89c50 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 15:20:08 +0200 Subject: [PATCH 101/154] fix: put allowedDevOrigins in the right next.config.js section --- next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next.config.js b/next.config.js index cca5e0677..32b00475c 100644 --- a/next.config.js +++ b/next.config.js @@ -64,8 +64,8 @@ const getAllowedDevOrigins = () => { } module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi3E-sauvdFABb.js' })({ + allowedDevOrigins: getAllowedDevOrigins(), env: { - allowedDevOrigins: getAllowedDevOrigins(), NEXT_PUBLIC_COMMIT_HASH: commitHash, NEXT_PUBLIC_LND_CONNECT_ADDRESS: process.env.LND_CONNECT_ADDRESS, NEXT_PUBLIC_ASSET_PREFIX: isProd ? 'https://a.stacker.news' : '', From 4fe5edc14250da57ceb98ed5e79965ee20df6a38 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 15:44:14 +0200 Subject: [PATCH 102/154] fix: normalize custom domains debug env var in constants.js --- lib/constants.js | 3 +++ lib/domains.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/constants.js b/lib/constants.js index 1a59dc0dc..ce578d2e4 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -228,3 +228,6 @@ export const BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' // MDAST pipeline debugging export const MDAST_DEBUG = Number(process.env.NEXT_PUBLIC_MDAST_DEBUG) === 1 + +// custom domains debugging +export const CUSTOM_DOMAINS_DEBUG = Number(process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG) === 1 diff --git a/lib/domains.js b/lib/domains.js index ed9cc0bd2..6fc79ca65 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -1,5 +1,6 @@ import { cachedFetcher } from '@/lib/fetch' import prisma from '@/api/models' +import { CUSTOM_DOMAINS_DEBUG } from '@/lib/constants' export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMappings () { try { @@ -35,7 +36,7 @@ export const getDomainMapping = async (domain) => { return domainsMappings?.[domain?.toLowerCase()] } -export function createDomainsDebugLogger (domainName, debug = process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG) { +export function createDomainsDebugLogger (domainName, debug = CUSTOM_DOMAINS_DEBUG) { const noop = () => {} if (!debug) { From 3686e8d5de6a6d603796c4a17587616470864aad Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 16:20:29 +0200 Subject: [PATCH 103/154] fix: don't treat domain name changes as resuming from HOLD (even if status is HOLD); pgpsql: also delete domain verification records when transitioning to HOLD --- api/resolvers/domain.js | 6 ++++-- .../20250504202129_custom_domains_base/migration.sql | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 05372ac7b..9a2a80d79 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -84,8 +84,10 @@ export default { domainName = domainName.trim().toLowerCase() await validateSchema(customDomainSchema, { domainName }) + const sameDomain = existing && existing.domainName === domainName + // updating the domain name, recovering from HOLD is allowed - if (existing && existing.domainName === domainName && existing.status !== 'HOLD') { + if (sameDomain && existing.status !== 'HOLD') { throw new GqlInputError('domain already set') } @@ -96,7 +98,7 @@ export default { status: 'PENDING' } - const resuming = existing?.status === 'HOLD' + const resuming = sameDomain && existing?.status === 'HOLD' const updatedDomain = await models.$transaction(async tx => { if (existing) { diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index 44280f3f3..4aef1ecd5 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -185,19 +185,21 @@ EXECUTE FUNCTION clear_domain_on_sub_takeover(); -- SCENARIO: Domain transitions to HOLD -- delete any associated certificate so the ACM cleanup trigger fires -CREATE OR REPLACE FUNCTION delete_certificate_on_domain_hold() +-- delete any associated verification records +CREATE OR REPLACE FUNCTION delete_certificate_and_verification_records_on_domain_hold() RETURNS TRIGGER AS $$ BEGIN DELETE FROM "DomainCertificate" WHERE "domainId" = NEW.id; + DELETE FROM "DomainVerificationRecord" WHERE "domainId" = NEW.id; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER trigger_delete_certificate_on_domain_hold +CREATE TRIGGER trigger_delete_certificate_and_verification_records_on_domain_hold AFTER UPDATE ON "Domain" FOR EACH ROW WHEN (NEW.status = 'HOLD' AND OLD.status IS DISTINCT FROM 'HOLD') -EXECUTE FUNCTION delete_certificate_on_domain_hold(); +EXECUTE FUNCTION delete_certificate_and_verification_records_on_domain_hold(); -- ask ACM to delete the certificate CREATE OR REPLACE FUNCTION ask_acm_to_delete_certificate() From d72d3a37254f6c44967f30ee598cd2273df5a990 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 16:21:01 +0200 Subject: [PATCH 104/154] cleanup: explicit territory-domains form steps --- components/territory-domains.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 70ee8dbcf..a8027c392 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -129,6 +129,11 @@ const DomainGuidelines = ({ domain }) => { ) } + /** + * CNAME pending -> show only Step 1 + * CNAME verified but no SSL -> show placeholder + * CNAME verified and SSL is pending -> show Step 2 + */ return (
{records?.CNAME?.status === 'PENDING' && ( @@ -142,7 +147,7 @@ const DomainGuidelines = ({ domain }) => { {records?.CNAME?.status === 'VERIFIED' && !records?.SSL && (

CNAME verified. Requesting SSL certificate...

)} - {records?.SSL?.status === 'PENDING' && ( + {records?.CNAME?.status === 'VERIFIED' && records?.SSL?.status === 'PENDING' && (
Step 2: Prepare your domain for SSL

We've issued an SSL certificate for your domain. To validate it, add the following CNAME record:

From 59c5e763e6f88d4af73816b49a73d9fb73fbf6b6 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 22:03:12 +0200 Subject: [PATCH 105/154] disable lurker signup button on custom domains (conflicts with one-click-login path) --- components/account.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/account.js b/components/account.js index 308a12d63..cf39f22d1 100644 --- a/components/account.js +++ b/components/account.js @@ -7,6 +7,7 @@ import useCookie from '@/components/use-cookie' import Link from 'next/link' import AddIcon from '@/svgs/add-fill.svg' import { cookieOptions, MULTI_AUTH_ANON, MULTI_AUTH_LIST, MULTI_AUTH_POINTER } from '@/lib/auth' +import { useDomain } from '@/components/territory-domains' const b64Decode = str => Buffer.from(str, 'base64').toString('utf-8') @@ -93,6 +94,10 @@ const AccountListRow = ({ account, selected, ...props }) => { } export const useIsLurker = () => { + // TODO, signup button for lurkers conflicts with one-click-login path + const { domain } = useDomain() + if (domain) return false + const accounts = useAccounts() return accounts.length === 0 } From 7728d00f16cd904315f81b2e23ca3c00749b5f08 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 23:40:18 +0200 Subject: [PATCH 106/154] cleanup: make some sense out of the various timings --- api/resolvers/domain.js | 16 ++++++++----- components/territory-domains.js | 6 +++-- lib/constants.js | 9 ++++++++ worker/domainVerification.js | 40 ++++++++++++++++++++------------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 9a2a80d79..ba1532fdf 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -1,6 +1,12 @@ import { validateSchema, customDomainSchema } from '@/lib/validate' import { GqlAuthenticationError, GqlInputError, GqlAuthorizationError } from '@/lib/error' -import { SN_ADMIN_IDS } from '@/lib/constants' +import { + DOMAIN_VERIFICATION_HOLD_AFTER_DAYS, + DOMAIN_VERIFICATION_INTERVAL_SECONDS, + DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS, + DOMAIN_VERIFICATION_RETRY_LIMIT, + SN_ADMIN_IDS +} from '@/lib/constants' export async function cleanDomainVerificationJobs (domainId, models) { // delete any existing domain verification job left @@ -21,10 +27,10 @@ async function scheduleDomainVerificationJob (domainId, models) { INSERT INTO pgboss.job (name, data, retrylimit, retrydelay, startafter, keepuntil, singletonkey) VALUES ('domainVerification', jsonb_build_object('domainId', ${domainId}::INTEGER), - 3, - 60, - now() + interval '30 seconds', - now() + interval '2 days', + ${DOMAIN_VERIFICATION_RETRY_LIMIT}, + ${DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS}, + now() + ${DOMAIN_VERIFICATION_INTERVAL_SECONDS} * interval '1 second', + now() + ${DOMAIN_VERIFICATION_HOLD_AFTER_DAYS} * interval '1 day', 'domainVerification:' || ${domainId}::TEXT -- domain <-> job isolation )` } diff --git a/components/territory-domains.js b/components/territory-domains.js index a8027c392..fe4d0efb5 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -3,13 +3,15 @@ import { Form, Input, SubmitButton, CopyButton } from './form' import { useMutation, useQuery } from '@apollo/client/react' import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' -import { NORMAL_POLL_INTERVAL_MS, SSR } from '@/lib/constants' +import { SSR } from '@/lib/constants' import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' import { useEffect, createContext, useContext, useState } from 'react' import Moon from '@/svgs/moon-fill.svg' import ClipboardLine from '@/svgs/clipboard-line.svg' import styles from './item.module.css' +const DOMAIN_POLL_INTERVAL_MS = 10_000 + // Domain context for custom domains const DomainContext = createContext({ domain: { @@ -174,7 +176,7 @@ export default function CustomDomainForm ({ sub }) { if (data?.domain?.status !== 'PENDING') { stopPolling() } else { - startPolling(NORMAL_POLL_INTERVAL_MS) + startPolling(DOMAIN_POLL_INTERVAL_MS) } }, [data?.domain?.status]) diff --git a/lib/constants.js b/lib/constants.js index ce578d2e4..207d12b61 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -206,6 +206,15 @@ export const NORMAL_POLL_INTERVAL_MS = Number(process.env.NEXT_PUBLIC_NORMAL_POL export const LONG_POLL_INTERVAL_MS = Number(process.env.NEXT_PUBLIC_LONG_POLL_INTERVAL_MS) export const EXTRA_LONG_POLL_INTERVAL_MS = Number(process.env.NEXT_PUBLIC_EXTRA_LONG_POLL_INTERVAL_MS) +// custom domains +export const DOMAIN_VERIFICATION_INTERVAL_SECONDS = 30 +export const DOMAIN_VERIFICATION_SLOW_INTERVAL_SECONDS = 5 * 60 +export const DOMAIN_VERIFICATION_SLOW_AFTER_HOURS = 1 +export const DOMAIN_VERIFICATION_RETRY_LIMIT = 3 +export const DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS = 60 +export const DOMAIN_VERIFICATION_HOLD_AFTER_DAYS = 2 +export const DOMAIN_HOLD_RETENTION_DAYS = 30 + export const ZAP_UNDO_DELAY_MS = 5_000 export const ZAP_DEBOUNCE_MS = 1_000 diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 79c30e98e..404b55430 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -8,19 +8,24 @@ import { deleteDomainCertificate, detachDomainCertificate } from '@/lib/domain-verification' +import { + DOMAIN_HOLD_RETENTION_DAYS, + DOMAIN_VERIFICATION_HOLD_AFTER_DAYS, + DOMAIN_VERIFICATION_INTERVAL_SECONDS, + DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS, + DOMAIN_VERIFICATION_RETRY_LIMIT, + DOMAIN_VERIFICATION_SLOW_AFTER_HOURS, + DOMAIN_VERIFICATION_SLOW_INTERVAL_SECONDS +} from '@/lib/constants' import { cleanDomainVerificationJobs } from '@/api/resolvers/domain' import { datePivot } from '@/lib/time' const getVerificationInterval = (updatedAt) => { - const pivot = datePivot(new Date(), { hours: -1 }) // 1 hour ago - // after 1 hour, the verification interval is 5 minutes - if (pivot > updatedAt) return 60 * 5 - // before 1 hour, the verification interval is 30 seconds - return 30 + const pivot = datePivot(new Date(), { hours: -DOMAIN_VERIFICATION_SLOW_AFTER_HOURS }) + if (pivot > updatedAt) return DOMAIN_VERIFICATION_SLOW_INTERVAL_SECONDS + return DOMAIN_VERIFICATION_INTERVAL_SECONDS } -const VERIFICATION_HOLD_THRESHOLD = -2 // 2 days ago - export async function domainVerification ({ id: jobId, data: { domainId }, boss }) { // establish connection to database const models = createPrisma({ connectionParams: { connection_limit: 1 } }) @@ -69,9 +74,9 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss // we still need to verify the domain, schedule the job to run again const newJobId = await boss.sendDebounced('domainVerification', { domainId }, { startAfter: getVerificationInterval(domain.updatedAt), - retryLimit: 3, - retryDelay: 60 // on critical errors, retry every minute - }, 30, `domainVerification:${domainId}`) + retryLimit: DOMAIN_VERIFICATION_RETRY_LIMIT, + retryDelay: DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS + }, DOMAIN_VERIFICATION_INTERVAL_SECONDS, `domainVerification:${domainId}`) console.log(`domain ${domain.domainName} is still pending verification, created job with ID ${newJobId}`) } } catch (error) { @@ -98,10 +103,10 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss } async function verifyDomain (domain, models) { - // if we're still here and it has been 48 hours, put the domain on HOLD, stopping the verification process + // put the domain on HOLD if it has been on PENDING for too long // the DB trigger will delete the certificate (if any), which cascades into the ACM cleanup trigger - if (datePivot(new Date(), { days: VERIFICATION_HOLD_THRESHOLD }) > domain.updatedAt) { - return { status: 'HOLD', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it in 48 hours` } + if (datePivot(new Date(), { days: -DOMAIN_VERIFICATION_HOLD_AFTER_DAYS }) > domain.updatedAt) { + return { status: 'HOLD', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it for the last ${DOMAIN_VERIFICATION_HOLD_AFTER_DAYS} days` } } const status = 'PENDING' @@ -303,16 +308,19 @@ async function logAttempt ({ domain, models, record, stage, status, message }) { }) } -// clear domains that have been on HOLD for 30 days or more +// clear domains that have been on HOLD past the retention window export async function clearLongHeldDomains () { const models = createPrisma({ connectionParams: { connection_limit: 1 } }) try { const deleted = await models.domain.deleteMany({ - where: { status: 'HOLD', updatedAt: { lt: datePivot(new Date(), { days: -30 }) } } // 30 days ago + where: { + status: 'HOLD', + updatedAt: { lt: datePivot(new Date(), { days: -DOMAIN_HOLD_RETENTION_DAYS }) } + } }) if (deleted.count > 0) { - console.log(`cleared ${deleted.count} custom domains that have been on HOLD for 30 days or more`) + console.log(`cleared ${deleted.count} custom domains that have been on HOLD for ${DOMAIN_HOLD_RETENTION_DAYS} days or more`) } } catch (error) { console.error(`couldn't clear old domains that have been on HOLD: ${error.message}`) From dde60f166add92f2179639cef7fd5e257cec9ff2 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 10 Apr 2026 23:41:37 +0200 Subject: [PATCH 107/154] fix: update the domain status only if it has changed --- worker/domainVerification.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 404b55430..0e89cb415 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -59,11 +59,13 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss const result = await verifyDomain(domain, models) console.log(`domain verification result: ${JSON.stringify(result)}`) - // update the domain with the result and register the attempt - await models.domain.update({ - where: { id: domainId }, - data: { status: result.status } - }) + // update the domain status only if it has changed + if (domain.status !== result.status) { + await models.domain.update({ + where: { id: domainId }, + data: { status: result.status } + }) + } // log the general verification attempt await logAttempt({ domain, models, stage: 'VERIFICATION_COMPLETE', status: result.status, message: result.message }) From 5ff714441284ed867779c75203eb519d1080d35f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 13 Apr 2026 13:54:11 +0200 Subject: [PATCH 108/154] security: domain-specific JWTs, remove pre-existing custom domain headers on each request (always replace with ours) --- pages/api/auth/[...nextauth].js | 11 +++++++++++ pages/api/auth/sync.js | 12 ++++++------ proxy.js | 9 +++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 8fcfb290d..35df5bad8 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -119,6 +119,17 @@ function getCallbacks (req, res) { } } + // tokens created for a custom domain should only be used on that domain + if (token?.domainName) { + try { + const currentDomain = new URL(req.headers.referer).host || req.headers['x-stacker-news-domain'] + if (currentDomain !== token.domainName) return null + } catch (error) { + console.error('cannot verify domain', error) + return null + } + } + if (token?.id) { // HACK token.sub is used by nextjs v4 internally and is used like a userId // setting it here allows us to link multiple auth method to an account diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js index b479e3d5e..181766662 100644 --- a/pages/api/auth/sync.js +++ b/pages/api/auth/sync.js @@ -10,9 +10,9 @@ const VERIFICATION_TOKEN_EXPIRY = 1000 * 60 * 5 // 5 minutes in milliseconds export default async function handler (req, res) { try { if (req.method === 'POST') { - const { verificationToken } = req.body - if (!verificationToken) { - return res.status(400).json({ status: 'ERROR', reason: 'verification token is required' }) + const { verificationToken, domainName } = req.body + if (!verificationToken || !domainName) { + return res.status(400).json({ status: 'ERROR', reason: 'verification token and domain name are required' }) } const verificationResult = await consumeVerificationToken(verificationToken) @@ -20,7 +20,7 @@ export default async function handler (req, res) { return res.status(400).json(verificationResult) } - const sessionTokenResult = await createEphemeralSessionToken(verificationResult.userId) + const sessionTokenResult = await createEphemeralSessionToken(domainName, verificationResult.userId) if (sessionTokenResult.status === 'ERROR') { return res.status(500).json(sessionTokenResult) } @@ -143,10 +143,10 @@ async function consumeVerificationToken (verificationToken) { } } -async function createEphemeralSessionToken (userId) { +async function createEphemeralSessionToken (domainName, userId) { try { const sessionToken = await encodeJWT({ - token: { id: userId, sub: userId }, + token: { id: userId, sub: userId, domainName }, secret: process.env.NEXTAUTH_SECRET, maxAge: SYNC_TOKEN_MAX_AGE }) diff --git a/proxy.js b/proxy.js index 988342f82..ea18d5b1a 100644 --- a/proxy.js +++ b/proxy.js @@ -28,6 +28,7 @@ async function customDomainMiddleware (request, domain, subName) { const { pathname, searchParams } = url // set the subname in the request headers const headers = new Headers(request.headers) + headers.set('x-stacker-news-domain', domain) headers.set('x-stacker-news-subname', subName) logger.log('custom domain', domain, 'with subname', subName) @@ -41,7 +42,7 @@ async function customDomainMiddleware (request, domain, subName) { const signup = pathname.startsWith('/signup') return redirectToAuthSync(searchParams, domain, signup, headers) } - if (searchParams.has('sync_token')) return establishAuthSync(request, searchParams, headers) + if (searchParams.has('sync_token')) return establishAuthSync(request, searchParams, domain, headers) // clean up the pathname from any subname if (pathname.startsWith('/~')) { @@ -93,13 +94,13 @@ async function redirectToAuthSync (searchParams, domain, signup, headers) { return NextResponse.redirect(syncUrl, { headers }) } -async function establishAuthSync (request, searchParams, headers) { +async function establishAuthSync (request, searchParams, domain, headers) { const token = searchParams.get('sync_token') const redirectUri = searchParams.get('redirectUri') || '/' const res = NextResponse.redirect(new URL(redirectUri, request.url), { headers }) try { - const body = JSON.stringify({ verificationToken: token }) + const body = JSON.stringify({ verificationToken: token, domainName: domain }) const fetchHeaders = new Headers(headers) fetchHeaders.set('Content-Type', 'application/json') @@ -264,9 +265,9 @@ function applySecurityHeaders (resp) { export async function proxy (req) { // clear subname header to prevent potential spoofing const headers = new Headers(req.headers) + headers.delete('x-stacker-news-domain') headers.delete('x-stacker-news-subname') const request = new NextRequest(req, { headers }) - const referrerResp = referrerMiddleware(request) // TODO: check if we actually need this, and WHY if (referrerResp.headers.get('Location')) { From 85d3e809fe54c8b2ad051ba06afb2309c473aca4 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 14 Apr 2026 17:47:28 +0200 Subject: [PATCH 109/154] fix: switch back to notifyOnNetworkStatusChange false to get the original Apollo Client 3 behavior (don't re-render components unless data changes) --- lib/apollo.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/apollo.js b/lib/apollo.js index 7f3ff1184..e43dafc16 100644 --- a/lib/apollo.js +++ b/lib/apollo.js @@ -381,6 +381,7 @@ function getClient (uri) { initialFetchPolicy: defaultFetchPolicy, fetchPolicy: defaultFetchPolicy, nextFetchPolicy: defaultNextFetchPolicy, + notifyOnNetworkStatusChange: false, ssr: SSR }, query: { From 1a2b1295cba26c69e48c588da86a06d42d5dba1f Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 14 Apr 2026 18:00:22 +0200 Subject: [PATCH 110/154] remove dangling sub prop --- components/nav/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nav/common.js b/components/nav/common.js index a9395bbc3..1864006f5 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -369,7 +369,7 @@ export function AnonDropdown ({ path }) { ) } -export function Sorts ({ sub, prefix, className }) { +export function Sorts ({ prefix, className }) { return ( <> From e5c99c6c1b77713762f8f255268724329ffa48b3 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 14 Apr 2026 20:11:18 +0200 Subject: [PATCH 111/154] filter AbortError from useLazyQuery executions --- components/editor/plugins/upload.js | 5 +++-- components/form.js | 3 ++- components/item-popover.js | 3 ++- components/link-form.js | 5 +++-- components/payIn/hooks/use-auto-retry-pay-ins.js | 3 ++- components/sub-popover.js | 3 ++- components/sub-select.js | 3 ++- components/territory-form.js | 3 ++- components/use-crossposter.js | 3 ++- components/user-popover.js | 3 ++- lib/error.js | 4 ++++ pages/items/[id]/edit.js | 3 ++- wallets/client/hooks/logger.js | 3 ++- 13 files changed, 30 insertions(+), 14 deletions(-) diff --git a/components/editor/plugins/upload.js b/components/editor/plugins/upload.js index 0b808788e..ce114f348 100644 --- a/components/editor/plugins/upload.js +++ b/components/editor/plugins/upload.js @@ -1,4 +1,5 @@ import { useEffect, useRef, useCallback } from 'react' +import { isAbortError } from '@/lib/error' import { COMMAND_PRIORITY_EDITOR, $getRoot, @@ -288,7 +289,7 @@ function useLexicalUploadFees (editor) { const s3Keys = [...text.matchAll(AWS_S3_URL_REGEXP)].map(m => Number(m[1])) updateUploadFees({ variables: { s3Keys } }) .then(handleUploadFeesData) - .catch(err => console.error(err)) + .catch(err => !isAbortError(err) && console.error(err)) } else { const mediaNodes = $nodesOfType(MediaNode) const s3Keys = mediaNodes @@ -296,7 +297,7 @@ function useLexicalUploadFees (editor) { .map(m => Number(m[1])) updateUploadFees({ variables: { s3Keys } }) .then(handleUploadFeesData) - .catch(err => console.error(err)) + .catch(err => !isAbortError(err) && console.error(err)) } }, [updateUploadFees, handleUploadFeesData]) diff --git a/components/form.js b/components/form.js index 9be60db82..ccad8066e 100644 --- a/components/form.js +++ b/components/form.js @@ -1,6 +1,7 @@ import Button from 'react-bootstrap/Button' import InputGroup from 'react-bootstrap/InputGroup' import BootstrapForm from 'react-bootstrap/Form' +import { isAbortError } from '@/lib/error' import { Formik, Form as FormikForm, useFormikContext, useField, FieldArray } from 'formik' import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' import copy from 'clipboard-copy' @@ -475,7 +476,7 @@ export function BaseSuggest ({ index: 0 }) }) - .catch(err => console.error(err)) + .catch(err => !isAbortError(err) && console.error(err)) } else { resetSuggestions() } diff --git a/components/item-popover.js b/components/item-popover.js index c4708416b..03c0e24a0 100644 --- a/components/item-popover.js +++ b/components/item-popover.js @@ -1,4 +1,5 @@ import { ITEM } from '@/fragments/items' +import { isAbortError } from '@/lib/error' import errorStyles from '@/styles/error.module.css' import { useLazyQuery } from '@apollo/client/react' import classNames from 'classnames' @@ -12,7 +13,7 @@ export default function ItemPopover ({ id, children }) { }) const getItem = useCallback(() => { - execute({ variables: { id } }).catch(err => console.error(err)) + execute({ variables: { id } }).catch(err => !isAbortError(err) && console.error(err)) }, [execute, id]) return ( diff --git a/components/link-form.js b/components/link-form.js index 4fbd8ac86..adb6018c8 100644 --- a/components/link-form.js +++ b/components/link-form.js @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react' +import { isAbortError } from '@/lib/error' import { Form, Input, SNInput } from '@/components/form' import { useRouter } from 'next/router' import { gql } from '@apollo/client' @@ -68,8 +69,8 @@ export function LinkForm ({ item, subs, EditInfo, children }) { ...ItemFields } }`) - const getPageTitleAndUnshortedDebounce = useDebounceCallback((...args) => getPageTitleAndUnshorted(...args).catch(err => console.error(err)), LOOKUP_DEBOUNCE_MS, [getPageTitleAndUnshorted]) - const getDupesDebounce = useDebounceCallback((...args) => getDupes(...args).catch(err => console.error(err)), DUPES_DEBOUNCE_MS, [getDupes]) + const getPageTitleAndUnshortedDebounce = useDebounceCallback((...args) => getPageTitleAndUnshorted(...args).catch(err => !isAbortError(err) && console.error(err)), LOOKUP_DEBOUNCE_MS, [getPageTitleAndUnshorted]) + const getDupesDebounce = useDebounceCallback((...args) => getDupes(...args).catch(err => !isAbortError(err) && console.error(err)), DUPES_DEBOUNCE_MS, [getDupes]) useEffect(() => { if (!dupesLoading) { diff --git a/components/payIn/hooks/use-auto-retry-pay-ins.js b/components/payIn/hooks/use-auto-retry-pay-ins.js index 5b16322f2..11914d7c3 100644 --- a/components/payIn/hooks/use-auto-retry-pay-ins.js +++ b/components/payIn/hooks/use-auto-retry-pay-ins.js @@ -1,4 +1,5 @@ import { usePreferredSendProtocolId, useWalletPayment } from '@/wallets/client/hooks' +import { isAbortError } from '@/lib/error' import usePayInHelper from './use-pay-in-helper' import { useLazyQuery } from '@apollo/client/react' import { FAILED_PAY_INS } from '@/fragments/payIn' @@ -42,7 +43,7 @@ export function useAutoRetryPayIns () { if (error) throw error failedPayIns = data.failedPayIns } catch (err) { - console.error('failed to fetch invoices to retry:', err) + !isAbortError(err) && console.error('failed to fetch invoices to retry:', err) return } diff --git a/components/sub-popover.js b/components/sub-popover.js index 260b2bc55..64766ece0 100644 --- a/components/sub-popover.js +++ b/components/sub-popover.js @@ -1,4 +1,5 @@ import { SUB_FULL } from '@/fragments/subs' +import { isAbortError } from '@/lib/error' import errorStyles from '@/styles/error.module.css' import { useLazyQuery } from '@apollo/client/react' import classNames from 'classnames' @@ -16,7 +17,7 @@ export default function SubPopover ({ sub, children }) { ) const getSub = useCallback(() => { - execute({ variables: { sub } }).catch(err => console.error(err)) + execute({ variables: { sub } }).catch(err => !isAbortError(err) && console.error(err)) }, [execute, sub]) return ( diff --git a/components/sub-select.js b/components/sub-select.js index 03c214acc..6e6b36ecb 100644 --- a/components/sub-select.js +++ b/components/sub-select.js @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { isAbortError } from '@/lib/error' import { useRouter } from 'next/router' import { MultiSelect, Select } from './form' import { EXTRA_LONG_POLL_INTERVAL_MS, SSR } from '@/lib/constants' @@ -159,7 +160,7 @@ export function SubMultiSelect ({ prependSubs, subs, onChange, size, appendSubs, showModal(() => ) } } catch (err) { - console.error(err) + !isAbortError(err) && console.error(err) } } diff --git a/components/territory-form.js b/components/territory-form.js index 766342394..2db3e4542 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -1,4 +1,5 @@ import AccordianItem from './accordian-item' +import { isAbortError } from '@/lib/error' import { Col, InputGroup, Row, Form as BootstrapForm, Badge } from 'react-bootstrap' import { Checkbox, CheckboxGroup, Form, Input, SNInput, Range } from './form' import { useFormikContext } from 'formik' @@ -60,7 +61,7 @@ export default function TerritoryForm ({ sub }) { const { data } = await fetchSub({ variables: { sub: name } }) setArchived(data?.sub?.status === 'STOPPED') } catch (err) { - console.error(err) + !isAbortError(err) && console.error(err) } }, [fetchSub, setArchived]) diff --git a/components/use-crossposter.js b/components/use-crossposter.js index b04a9dbad..fa06492f0 100644 --- a/components/use-crossposter.js +++ b/components/use-crossposter.js @@ -1,4 +1,5 @@ import { useCallback } from 'react' +import { isAbortError } from '@/lib/error' import { useToast } from './toast' import { Button } from 'react-bootstrap' import Nostr, { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr' @@ -189,7 +190,7 @@ export default function useCrossposter () { return data?.item } catch (e) { - console.error(e) + !isAbortError(e) && console.error(e) return null } } diff --git a/components/user-popover.js b/components/user-popover.js index a8d60442d..ac24cb01a 100644 --- a/components/user-popover.js +++ b/components/user-popover.js @@ -1,4 +1,5 @@ import { USER } from '@/fragments/users' +import { isAbortError } from '@/lib/error' import errorStyles from '@/styles/error.module.css' import { useLazyQuery } from '@apollo/client/react' import classNames from 'classnames' @@ -32,7 +33,7 @@ export default function UserPopover ({ name, children }) { ) const getUser = useCallback(() => { - execute({ variables: { name } }).catch(err => console.error(err)) + execute({ variables: { name } }).catch(err => !isAbortError(err) && console.error(err)) }, [execute, name]) return ( diff --git a/lib/error.js b/lib/error.js index 3ba455210..0bccfe1a8 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,5 +1,9 @@ import { GraphQLError } from 'graphql' +export function isAbortError (err) { + return err?.name === 'AbortError' +} + export const E_FORBIDDEN = 'E_FORBIDDEN' export const E_UNAUTHENTICATED = 'E_UNAUTHENTICATED' export const E_BAD_INPUT = 'E_BAD_INPUT' diff --git a/pages/items/[id]/edit.js b/pages/items/[id]/edit.js index 4debd7422..045712a4b 100644 --- a/pages/items/[id]/edit.js +++ b/pages/items/[id]/edit.js @@ -1,4 +1,5 @@ import { ITEM } from '@/fragments/items' +import { isAbortError } from '@/lib/error' import { getGetServerSideProps } from '@/api/ssrApollo' import { DiscussionForm } from '@/components/discussion-form' import { LinkForm } from '@/components/link-form' @@ -66,7 +67,7 @@ export default function PostEdit ({ ssrData }) { ...territoryAdds } }) - }).catch(err => console.error(err)) + }).catch(err => !isAbortError(err) && console.error(err)) }, [subs, fetchSubs]) const [,, editThreshold] = useCanEdit(item) diff --git a/wallets/client/hooks/logger.js b/wallets/client/hooks/logger.js index 9e2de5f66..b5438f3da 100644 --- a/wallets/client/hooks/logger.js +++ b/wallets/client/hooks/logger.js @@ -1,4 +1,5 @@ import { useLazyQuery, useMutation } from '@apollo/client/react' +import { isAbortError } from '@/lib/error' import { ADD_WALLET_LOG, WALLET_LOGS, DELETE_WALLET_LOGS } from '@/wallets/client/fragments' import { createContext, useCallback, useContext, useMemo, useState, useEffect } from 'react' import { useShowModal } from '@/components/modal' @@ -107,7 +108,7 @@ export function useWalletLogs (protocol, debug, payInId, { poll = true, pollInte variables: cursor ? { ...logFilters, cursor } : logFilters })) } catch (err) { - console.error(err) + !isAbortError(err) && console.error(err) return } From 2ca0b780a18aacffd0d5bf010934fddb1a43c2a6 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 16 Apr 2026 13:22:00 +0200 Subject: [PATCH 112/154] fix: create domain verification records even when resuming from HOLD --- api/resolvers/domain.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index ba1532fdf..9605772e9 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -120,16 +120,15 @@ export default { data: { ...initializeDomain, sub: { connect: { name: subName } } } }) - if (!resuming) { - await tx.domainVerificationRecord.create({ - data: { - domainId: domain.id, - type: 'CNAME', - recordName: domainName, - recordValue: new URL(process.env.NEXT_PUBLIC_URL).host - } - }) - } + // prepare the CNAME record for verification + await tx.domainVerificationRecord.create({ + data: { + domainId: domain.id, + type: 'CNAME', + recordName: domainName, + recordValue: new URL(process.env.NEXT_PUBLIC_URL).host + } + }) await scheduleDomainVerificationJob(domain.id, tx) return domain From 5960ac4849942eae6395bac150a83eb4f8349f47 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 16 Apr 2026 13:22:45 +0200 Subject: [PATCH 113/154] fix: normalize retrieved DNS CNAME records, hostname equality check --- lib/domain-verification.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/domain-verification.js b/lib/domain-verification.js index ab883695a..dafde5964 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -94,8 +94,10 @@ export async function verifyDNSRecord (type, recordName, recordValue) { try { if (type === 'CNAME') { domainRecords = await resolver.resolveCname(recordName) + // remove trailing dot if any + const normalize = s => (s.endsWith('.') ? s.slice(0, -1) : s) result.valid = domainRecords.some(record => - record.includes(recordValue) + normalize(record) === normalize(recordValue) ) } else { result.error = { message: `Invalid DNS record type: ${type}` } From 108bd454d60fb49b539491819afda91e2f66e103 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 16 Apr 2026 13:26:35 +0200 Subject: [PATCH 114/154] fix: update updated_at of Domain when transitioning to HOLD via db trigger --- .../20250504202129_custom_domains_base/migration.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index 4aef1ecd5..9fcdaed58 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -154,7 +154,10 @@ EXECUTE FUNCTION update_record_status_from_attempt(); CREATE OR REPLACE FUNCTION hold_domain_on_sub_stop() RETURNS TRIGGER AS $$ BEGIN - UPDATE "Domain" SET "status" = 'HOLD' WHERE "subName" = NEW.name; + UPDATE "Domain" + SET "status" = 'HOLD', + "updated_at" = NOW() + WHERE "subName" = NEW.name; RETURN NEW; END; $$ LANGUAGE plpgsql; From 2b103059ea99f72366d1050ebf1618fe79e63ccf Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 16 Apr 2026 13:27:12 +0200 Subject: [PATCH 115/154] remove useless/unused Sub.domain --- components/territory-domains.js | 2 +- fragments/subs.js | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index fe4d0efb5..0e014771d 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -208,7 +208,7 @@ export default function CustomDomainForm ({ sub }) { return ( <>
Date: Thu, 16 Apr 2026 13:36:00 +0200 Subject: [PATCH 116/154] don't consider x-forwarded-host --- proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy.js b/proxy.js index 8044905f0..b71b38c18 100644 --- a/proxy.js +++ b/proxy.js @@ -216,7 +216,7 @@ export async function proxy (req) { } // if we're on a custom domain, handle it - const domain = request.headers.get('x-forwarded-host') || request.headers.get('host') + const domain = request.headers.get('host') if (domain !== SN_MAIN_DOMAIN?.host) { // we don't need middleware to fail if dev messes up ENVs // in development we might have a port in the domain const domainToMap = process.env.NODE_ENV === 'development' ? domain.split(':')[0] : domain From c2ea2e5df5307272a53f7c6214513e0dceea4b62 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 01:23:17 +0200 Subject: [PATCH 117/154] protect from multiple CNAMEs at the same name --- lib/domain-verification.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/domain-verification.js b/lib/domain-verification.js index dafde5964..9415a0afe 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -94,11 +94,16 @@ export async function verifyDNSRecord (type, recordName, recordValue) { try { if (type === 'CNAME') { domainRecords = await resolver.resolveCname(recordName) - // remove trailing dot if any - const normalize = s => (s.endsWith('.') ? s.slice(0, -1) : s) - result.valid = domainRecords.some(record => - normalize(record) === normalize(recordValue) - ) + if (domainRecords.length !== 1) { + result.error = { message: `Invalid DNS configuration: expected 1 CNAME record, got ${domainRecords.length}` } + } else { + // remove trailing dot if any + const normalize = s => (s.endsWith('.') ? s.slice(0, -1) : s) + result.valid = normalize(domainRecords[0]) === normalize(recordValue) + if (!result.valid) { + result.error = { message: `CNAME points to ${domainRecords[0]}, expected ${recordValue}` } + } + } } else { result.error = { message: `Invalid DNS record type: ${type}` } } From 8d71482935a006c97057c1344b4a76b792204e63 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 14:06:07 +0200 Subject: [PATCH 118/154] protect from token hijacking with versioned JWTs and periodic DNS checks --- docs/dev/custom-domains.md | 155 ++++++------------ lib/domains.js | 10 +- pages/api/auth/[...nextauth].js | 13 +- pages/api/auth/sync.js | 16 +- .../migration.sql | 37 +++++ prisma/schema.prisma | 1 + proxy.js | 3 +- worker/domainVerification.js | 58 +++++++ worker/index.js | 2 + 9 files changed, 189 insertions(+), 106 deletions(-) create mode 100644 prisma/migrations/20260415170000_custom_domains_auth/migration.sql diff --git a/docs/dev/custom-domains.md b/docs/dev/custom-domains.md index f4614bad8..64990bcbb 100644 --- a/docs/dev/custom-domains.md +++ b/docs/dev/custom-domains.md @@ -158,6 +158,19 @@ Every midnight, the `clearLongHeldDomains` job gets executed to remove domains t A domain removal also means the certificate removal, which triggers **Ask ACM to delete certificate**. +### Active Domain DNS Drift Check +A pgboss cron `checkActiveDomainsDNS` runs every 5 minutes (`*/5 * * * *`) and, for each `ACTIVE` domain: +- re-resolves the stored `CNAME` `DomainVerificationRecord` against live DNS via the same `verifyDNSRecord` helper used during initial verification +- on a clean drift (record present but mismatched), flips the domain to `HOLD` +- on a temporary resolver error (i.e. timeout), logs and skips + +Switching to `HOLD` cascades into: +1. **Bump token version** — a db trigger on `Domain` increments `tokenVersion` whenever the domain switches from or to `ACTIVE`. [see token revocation via `tokenVersion`](#token-revocation-via-tokenversion). +2. **Delete cert + verification records** +3. **Ask ACM to delete certificate** — chained from the cert deletion + +The territory owner can re-verify and the domain returns to `ACTIVE`, but with a higher `tokenVersion` than any token issued before the drift. + ### Update `DomainVerificationRecord` status The `DomainVerification` job logs every step into `DomainVerificationAttempt`, when it comes to steps that involves DNS records like the `CNAME` record or ACM validation records, a connection between `DomainVerificationAttempt` and `DomainVerificationRecord` gets established. @@ -185,7 +198,6 @@ It's a necessary step to ensure that we don't waste AWS resources and also provi Cross-domain JWT authentication is a complex issue due to browser security restrictions, mainly because cookies: - are bound to specific domains -- -- cookie property of `stacker.news` - **DON'T EAT** and @@ -205,126 +217,67 @@ Instead of fighting these restrictions, Auth Sync works with them by creating a - -- `POST: https://stacker.news/api/auth/sync; token: 42424242` -This design focuses on security as the verification token is a one-time code that dies in **5 minutes** and has **256 bits** of entropy. The JWT is then generated server-side and applied to the final middleware response. +The verification token is a one-time code that dies in **5 minutes** and has **256 bits** of entropy. The JWT is then generated server-side and applied to the final middleware response. +### Token revocation via `domainId` + `tokenVersion` +JWTs are stateless, so once a session cookie has been set on `pizza.com` we cannot un-issue it: the cookie remains valid in every browser that ever signed in until it expires (30 days by default). That is a problem the moment we suspect the domain itself is no longer trustworthy. -# Neat stuff +Every custom-domain JWT carries two claims that together make it revocable without abandoning the JWT model: -### Let's go HTTPS with a reverse proxy +- **`domainId`** — the primary key of the `Domain` row the token was minted against. Pins the JWT to a specific *row lifetime*. If the row is deleted and recreated (owner removes and re-adds the domain, takeover, etc.), the replacement row has a fresh autoincrement `id` that no pre-existing JWT can reference. +- **`domainVersion`** — this is the value of `Domain.tokenVersion` when the JWT was created. If the domain leaves and later returns to `ACTIVE`, `tokenVersion` increases. Old JWTs with a different version become invalid. -To set custom domains correctly we need to have a domain and SSL certificates. +A `BEFORE UPDATE` trigger on `Domain` (`bump_domain_token_version`) increments `tokenVersion` on **any transition to/from `ACTIVE`**. The trigger alone can't help across row lifetimes, which is exactly why `domainId` exists. -We'll cover a basic **NGINX** configuration with **Let's Encrypt/certbot** on Linux-based systems, but you have the freedom to experiment with other methods and platforms. +##### Where `domainId` and `tokenVersion` are read -#### Prerequisites -- a domain or a public hostname -- install [nginx](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/) -- install [certbot](https://certbot.eff.org/instructions?ws=nginx&os=pip) -- possibility to add `CNAME` and `TXT` records -- domain with an `A` record at your nginx host +Two sides read these, with different consistency requirements: +- **Mint side** — `createEphemeralSessionToken` in [pages/api/auth/sync.js](../../pages/api/auth/sync.js) reads the row **directly from the DB** (uncached) and snapshots both `id` and `tokenVersion` into the JWT. Since the minted cookie lives for up to 30 days, any staleness here could mint a token against an outdated row identity or revoked reign. +- **Verify side** — the next-auth `jwt` callback reads through `getDomainMapping`, which goes through `domainsMappingsCache` (same cache the proxy uses). This runs on every custom-domain request, so hitting the DB here would be expensive. Bounded staleness is acceptable because the mint side already guarantees that no *new* tokens can be minted with the old identity — the stale window only delays the rejection of pre-existing tokens. -### Step 1: Create a nginx site for your SN instance +##### Enforcement -Start creating a new site by editing `/etc/nginx/sites-available/your-domain.tld` with your editor of choice. +The check happens once per request, in [pages/api/auth/[...nextauth].js](../../pages/api/auth/[...nextauth].js)'s `jwt` callback, after the existing same-domain check: -
A sample nginx site configuration to prepare for certbot -Edit this configuration to match your configuration, you can have more domains. +```js +if (token?.domainName) { + // ... same-domain check ... -``` -server { - listen 80; - listen [::]:80; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - # for Let's Encrypt SSL issuance - location /.well-known/acme-challenge/ { - root /var/www/letsencrypt; - try_files $uri =404; - } + const mapping = await getDomainMapping(token.domainName) + if (!mapping) return null // domain is not ACTIVE right now + if (mapping.id !== token.domainId) return null // row was deleted and recreated + if (mapping.tokenVersion !== token.domainVersion) return null // ACTIVE reign has changed } ``` -
- -after editing, send `sudo systemctl restart nginx` - -### Step 2: Get a certificate for your domains -We can now get a certificate for your domain from Let's Encrypt/certbot. - -Edit the `-d` section to match your configuration. Every domain, sub-domain needs to have its own certificate. - -``` -sudo certbot certonly \ - --webroot -w /var/www/letsencrypt \ - -d your-domain.tld (-d sub.your-domain.tld -d another.your-domain.tld) \ - --email your@email.com \ - --agree-tos --no-eff-email \ - --deploy-hook "systemctl reload nginx" -``` -If everything went smooth, we should now have a domain with a valid SSL certificate. +`getDomainMapping` reads from `domainsMappingsCache` (the same cache the proxy uses). Both SSR (`getServerSession`) and `/api/graphql` go through `getAuthOptions` -> this callback. -### Step 3: Proxy everything to sndev! +##### Why all three checks? -Let's go back to `/etc/nginx/sites-available/your-domain.tld` to add a SSL proxy for our sndev instance +They cover different failure modes: +- `!mapping` — the domain is not `ACTIVE` **right now** (on HOLD, deleted, unknown). +- `mapping.id !== token.domainId` — the row was deleted and recreated since the token was minted. A fresh row always has a strictly greater autoincrement `id`, so old tokens can never match the new row regardless of what `tokenVersion` happens to land on. +- `mapping.tokenVersion !== token.domainVersion` — the domain has crossed the `ACTIVE` boundary at least once since the token was minted, within the same row lifetime. -
A sample nginx reverse proxy config -Edit this configuration to match your configuration, you can have more domains. +##### an attack scenario, prevented -``` -server { - listen 80; - listen [::]:80; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - # for Let's Encrypt SSL issuance - location /.well-known/acme-challenge/ { - root /var/www/letsencrypt; - try_files $uri =404; - } - - # 301 to HTTPS - location / { - return 301 https://$host$request_uri; - } -} +Two variants worth walking through, since they exercise different parts of the defense. -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - ssl_certificate /etc/letsencrypt/live/your-domain.tld/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your-domain.tld/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - - # proxy everything to sndev - location / { - proxy_pass http://sndev-instance-ip:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } - - # optional security headers - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Content-Type-Options "nosniff"; - add_header Referrer-Policy "no-referrer-when-downgrade"; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; -} -``` -
+**Variant A — DNS drift within a single row lifetime** (caught by `tokenVersion`): -### Step 4: Start sndev -Make sure to change your environment variables such as `.env.local` from something like `http://localhost:3000` to `https://your-domain.tld` +1. `pizza.com` is `ACTIVE` with `tokenVersion=3`. Alice signs in and gets a JWT carrying `{ domainName: 'pizza.com', domainId: 42, domainVersion: 3 }`. +2. The attacker hijacks DNS for `pizza.com` and exfiltrates her cookie. +3. Within ~5 minutes, `checkActiveDomainsDNS` notices the CNAME no longer matches and switches the domain to `HOLD`. The `ACTIVE -> HOLD` trigger bumps `tokenVersion` to `4`, and the on-HOLD trigger deletes the certificate and verification records. +4. Next request from Alice's browser **or** the attacker's stolen cookie, once the verifier's cache refreshes past the bump: `!mapping` is true -> the request is rejected and the user is `anon`. +5. The territory owner notices, fixes DNS, re-verifies. The domain goes back through `PENDING` and the `PENDING -> ACTIVE` trigger bumps `tokenVersion` again, to `5`. +6. The domain is `ACTIVE` again, so `!mapping` passes and `domainId` still matches (the row was updated, not recreated). **But** the cached `tokenVersion` is `5` while the JWT snapshots `3`, so the version check rejects them: `5 !== 3` -> both have to sign in again. -Start sndev with `./sndev start` and then navigate to your domain, you should see **Stacker News**! +**Variant B — owner removes and re-adds the domain** (caught by `domainId`): -If not, go back and make sure that everything is correct, you can encounter any kind of errors and **Internet can be of help**. +1. `pizza.com` is `ACTIVE`, row `id=42`, `tokenVersion=1`. Alice signs in and gets a JWT carrying `{ domainName: 'pizza.com', domainId: 42, domainVersion: 1 }`. The attacker steals her cookie and keeps it warm (actively replaying so it gets re-encoded with the default 30-day session maxAge). +2. The owner calls `setDomain(subName, null)`, which hard-deletes row `id=42` (cascading into cert cleanup). Alice's and the attacker's cookies start failing the `!mapping` check. +3. Weeks later, the owner re-adds `pizza.com`. A fresh row is created, `id=43`, `tokenVersion` defaulted to `0`. +4. Verification succeeds, the `PENDING -> ACTIVE` trigger bumps `tokenVersion` to `1`. +5. The attacker tries their stolen cookie again. The domain is `ACTIVE` (so `!mapping` passes) and the new `tokenVersion=1` happens to collide with the stolen JWT's `domainVersion=1`. Without `domainId`, **this would resurrect the stolen token**. With `domainId` in place: `mapping.id` is `43`, the JWT claims `42`, `43 !== 42` -> rejected. diff --git a/lib/domains.js b/lib/domains.js index 6fc79ca65..3d3a47ff9 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -6,8 +6,10 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap try { const domains = await prisma.domain.findMany({ select: { + id: true, domainName: true, - subName: true + subName: true, + tokenVersion: true }, where: { status: 'ACTIVE' @@ -17,7 +19,11 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap if (!domains.length) return null return domains.reduce((acc, domain) => { - acc[domain.domainName.toLowerCase()] = { subName: domain.subName } + acc[domain.domainName.toLowerCase()] = { + id: domain.id, // pins JWTs to a specific Domain row across delete/recreate cycles + subName: domain.subName, + tokenVersion: domain.tokenVersion // jwt revocability within a single row lifetime + } return acc }, {}) } catch (error) { diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 35df5bad8..70cabbbd3 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -12,6 +12,7 @@ import { schnorr } from '@noble/curves/secp256k1' import { notifyReferral } from '@/lib/webPush' import { hashEmail } from '@/lib/crypto' import { multiAuthMiddleware, setMultiAuthCookies } from '@/lib/auth' +import { getDomainMapping } from '@/lib/domains' import { BECH32_CHARSET } from '@/lib/constants' import { NodeNextRequest } from 'next/dist/server/base-http/node' import * as cookie from 'cookie' @@ -119,11 +120,21 @@ function getCallbacks (req, res) { } } - // tokens created for a custom domain should only be used on that domain + // custom domain session token validation if (token?.domainName) { try { const currentDomain = new URL(req.headers.referer).host || req.headers['x-stacker-news-domain'] + // tokens created for a custom domain should only be used on that domain if (currentDomain !== token.domainName) return null + + const mapping = await getDomainMapping(token.domainName) + // token is valid only if: + // - the domain is still ACTIVE (mapping exists) + // - it's the same Domain row the token was minted against (domainId match) + // - ACTIVE status hasn't changed since the token was minted (tokenVersion match) + if (!mapping) return null + if (mapping.id !== token.domainId) return null + if (mapping.tokenVersion !== token.domainVersion) return null } catch (error) { console.error('cannot verify domain', error) return null diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js index 181766662..bff070d6a 100644 --- a/pages/api/auth/sync.js +++ b/pages/api/auth/sync.js @@ -145,8 +145,22 @@ async function consumeVerificationToken (verificationToken) { async function createEphemeralSessionToken (domainName, userId) { try { + const domain = await models.domain.findUnique({ + where: { domainName, status: 'ACTIVE' }, + select: { id: true, tokenVersion: true } + }) + if (!domain) { + return { status: 'ERROR', reason: 'domain is no longer active' } + } + const sessionToken = await encodeJWT({ - token: { id: userId, sub: userId, domainName }, + token: { + id: userId, + sub: userId, + domainName, + domainId: domain.id, + domainVersion: domain.tokenVersion + }, secret: process.env.NEXTAUTH_SECRET, maxAge: SYNC_TOKEN_MAX_AGE }) diff --git a/prisma/migrations/20260415170000_custom_domains_auth/migration.sql b/prisma/migrations/20260415170000_custom_domains_auth/migration.sql new file mode 100644 index 000000000..e8d7e1151 --- /dev/null +++ b/prisma/migrations/20260415170000_custom_domains_auth/migration.sql @@ -0,0 +1,37 @@ +ALTER TABLE "Domain" +ADD COLUMN "tokenVersion" INTEGER NOT NULL DEFAULT 0; + +-- bump tokenVersion on any domain state transition +CREATE OR REPLACE FUNCTION bump_domain_token_version() +RETURNS TRIGGER AS $$ +BEGIN + NEW."tokenVersion" = OLD."tokenVersion" + 1; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_bump_domain_token_version +BEFORE UPDATE ON "Domain" +FOR EACH ROW +WHEN ( + (NEW.status = 'ACTIVE' AND OLD.status IS DISTINCT FROM 'ACTIVE') + OR (OLD.status = 'ACTIVE' AND NEW.status IS DISTINCT FROM 'ACTIVE') +) +EXECUTE FUNCTION bump_domain_token_version(); + +-- periodic DNS drift check for ACTIVE domains, every 5 minutes +CREATE OR REPLACE FUNCTION schedule_check_active_domains_dns() +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO pgboss.schedule (name, cron, timezone) + VALUES ('checkActiveDomainsDNS', '*/5 * * * *', 'America/Chicago') ON CONFLICT DO NOTHING; + return 0; +EXCEPTION WHEN OTHERS THEN + return 0; +END; +$$; + +SELECT schedule_check_active_domains_dns(); +DROP FUNCTION IF EXISTS schedule_check_active_domains_dns; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 342e2d299..083e406ee 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -918,6 +918,7 @@ model Domain { domainName String @unique @db.Citext subName String @unique @db.Citext status DomainVerificationStatus @default(PENDING) + tokenVersion Int @default(0) sub Sub @relation(fields: [subName], references: [name], onDelete: Cascade, onUpdate: Cascade) attempts DomainVerificationAttempt[] diff --git a/proxy.js b/proxy.js index 450ec9fb9..38511afb8 100644 --- a/proxy.js +++ b/proxy.js @@ -100,7 +100,8 @@ async function establishAuthSync (request, searchParams, domain, headers) { const res = NextResponse.redirect(new URL(redirectUri, request.url), { headers }) try { - const body = JSON.stringify({ verificationToken: token, domainName: domain }) + const domainName = process.env.NODE_ENV === 'development' ? domain.split(':')[0] : domain + const body = JSON.stringify({ verificationToken: token, domainName }) const fetchHeaders = new Headers(headers) fetchHeaders.set('Content-Type', 'application/json') diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 0e89cb415..9f261d9a8 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -310,6 +310,64 @@ async function logAttempt ({ domain, models, record, stage, status, message }) { }) } +// Checks active domains for DNS drift. If record has drifted puts the domain on HOLD +// a BEFORE UPDATE trigger on Domain bumps the tokenVersion, +// retroactively revoking every JWT associated with the domain. +export async function checkActiveDomainsDNS () { + const models = createPrisma({ connectionParams: { connection_limit: 1 } }) + try { + const domains = await models.domain.findMany({ + where: { status: 'ACTIVE' }, + include: { records: { where: { type: 'CNAME' } } } + }) + + for (const domain of domains) { + const cname = domain.records[0] + if (!cname) continue + + let drifted = false + let reason = null + try { + const result = await verifyDNSRecord('CNAME', cname.recordName, cname.recordValue) + if (!result.valid) { + drifted = true + reason = result.error?.message || 'CNAME record drifted' + } + } catch (error) { + // don't switch on temporary DNS errors + console.error(`[dns-drift] resolver error for ${domain.domainName}: ${error.message}`) + continue + } + + if (!drifted) continue + + console.log(`[dns-drift] ${domain.domainName} drifted (${reason}); switching to HOLD`) + + // switching a domain to HOLD triggers: + // - bump_domain_token_version -> revokes every JWT associated with the domain (fires on any ACTIVE boundary crossing) + // - delete_certificate_and_verification_records_on_domain_hold -> deletes cert + records + // - ask_acm_to_delete_certificate -> asks ACM to delete the certificate + await models.domain.update({ + where: { id: domain.id }, + data: { status: 'HOLD' } + }) + + await logAttempt({ + domain, + models, + stage: 'CNAME', + status: 'HOLD', + message: `DNS drift detected: ${reason}` + }) + } + } catch (error) { + console.error(`[dns-drift] check failed: ${error.message}`) + throw error + } finally { + await models.$disconnect() + } +} + // clear domains that have been on HOLD past the retention window export async function clearLongHeldDomains () { const models = createPrisma({ connectionParams: { connection_limit: 1 } }) diff --git a/worker/index.js b/worker/index.js index 5945b3713..d9d1e2472 100644 --- a/worker/index.js +++ b/worker/index.js @@ -42,6 +42,7 @@ import { postToSocial } from './socialPoster' import { domainVerification, deleteCertificateExternal, + checkActiveDomainsDNS, clearLongHeldDomains } from './domainVerification.js' import { untrackOldItems } from './untrackOldItems' @@ -139,6 +140,7 @@ async function work () { if (isServiceEnabled('domains')) { await boss.work('domainVerification', jobWrapper(domainVerification)) await boss.work('deleteDomainCertificate', jobWrapper(deleteCertificateExternal)) + await boss.work('checkActiveDomainsDNS', jobWrapper(checkActiveDomainsDNS)) await boss.work('clearLongHeldDomains', jobWrapper(clearLongHeldDomains)) } await boss.work('weeklyPost-*', jobWrapper(weeklyPost)) From da785ae81b8a0f79b8b329fdc6d81240f37d2654 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 14:37:14 +0200 Subject: [PATCH 119/154] update package-lock.json --- package-lock.json | 47 +++++------------------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 100f3e40a..f3dac9a35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7271,19 +7271,11 @@ "integrity": "sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==", "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", @@ -8595,16 +8587,6 @@ "node": ">= 0.4" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -16852,25 +16834,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array.prototype.flatmap": "^1.3.3", - "es-errors": "^1.3.0", - "object.entries": "^1.1.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", From ee74adb99e472bbe681eed4958c74622979a6374 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 18:39:55 +0200 Subject: [PATCH 120/154] cleanup: remove postponed changes to RSS (+seo), consistency with master --- lib/fetch.js | 2 +- lib/rss.js | 28 +++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index a0f8f6e6b..db4e23255 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -1,7 +1,7 @@ import { TimeoutError, timeoutSignal } from '@/lib/time' import crossFetch from 'cross-fetch' -import { TOR_REGEXP } from '@/lib/url' import { getAgent } from '@/lib/proxy' +import { TOR_REGEXP } from '@/lib/url' export class FetchTimeoutError extends TimeoutError { constructor (method, url, timeout) { diff --git a/lib/rss.js b/lib/rss.js index 3cf9b6c7e..8c0580255 100644 --- a/lib/rss.js +++ b/lib/rss.js @@ -1,6 +1,6 @@ import getSSRApolloClient from '@/api/ssrApollo' -const DEFAULT_SITE_URL = 'https://stacker.news' +const SITE_URL = 'https://stacker.news' const SITE_TITLE = 'stacker news' const SITE_SUBTITLE = 'moderating forums with money' @@ -16,14 +16,14 @@ function escapeXml (unsafe) { }) } -const generateRssItem = (item, siteUrl) => { - const guid = `${siteUrl}/items/${item.id}` +const generateRssItem = (item) => { + const guid = `${SITE_URL}/items/${item.id}` const link = item.url || guid let title = item.title if (item.isJob) { title = item.title + ' \\ ' + item.company + ' \\ ' + `${item.location || ''}${item.location && item.remote ? ' or ' : ''}${item.remote ? 'Remote' : ''}` } - const category = item.subNames?.map(subName => `${subName}`).join('') ?? '' + const category = item.subNames?.map(subName => `${subName}`).join('') ?? '' return ` ${guid} @@ -38,32 +38,23 @@ const generateRssItem = (item, siteUrl) => { ` } -function generateRssFeed (items, sub = null, siteUrl = DEFAULT_SITE_URL) { - const itemsList = items.map(item => generateRssItem(item, siteUrl)) - const subPath = sub ? `/~${sub}` : '' +function generateRssFeed (items, sub = null) { + const itemsList = items.map(generateRssItem) return ` ${SITE_TITLE}${sub ? ` ~${sub}` : ''} - ${siteUrl}${subPath} + ${SITE_URL}${sub ? `/~${sub}` : ''} ${SITE_SUBTITLE} en ${new Date().toUTCString()} - + ${itemsList.join('')} ` } -function getSiteUrl (req) { - if (req.headers['x-stacker-news-subname']) { - const proto = req.headers['x-forwarded-proto'] || 'https' - return `${proto}://${req.headers.host}` - } - return DEFAULT_SITE_URL -} - export default function getGetRssServerSideProps (query, variables = null) { return async function ({ req, res, query: params }) { const emptyProps = { props: {} } // to avoid server side warnings @@ -74,9 +65,8 @@ export default function getGetRssServerSideProps (query, variables = null) { if (!items || error) return emptyProps - const siteUrl = getSiteUrl(req) res.setHeader('Content-Type', 'text/xml; charset=utf-8') - res.write(generateRssFeed(items, params?.sub, siteUrl)) + res.write(generateRssFeed(items, params?.sub)) res.end() return emptyProps From aba427bcc76903b3c42014b3a1285770a115becf Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 18:42:40 +0200 Subject: [PATCH 121/154] cleanup: explicit 3 retries after unrecoverable domain verification fail --- worker/domainVerification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 0e89cb415..f822283a7 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -88,9 +88,9 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss const jobDetails = await boss.getJobById(jobId) console.log(`job details: ${JSON.stringify(jobDetails)}`) - // if we couldn't verify the domain after 3 attempts, put it on hold if it exists and delete any related verification jobs + // if we couldn't verify the domain after 3 retries, put it on hold if it exists and delete any related verification jobs if (jobDetails?.retrycount >= 3) { - console.log(`couldn't verify domain with ID ${domainId} for the third time, putting it on HOLD if it exists and deleting any related domain verification jobs`) + console.log(`couldn't verify domain with ID ${domainId} for the third retry, putting it on HOLD if it exists and deleting any related domain verification jobs`) await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) // delete any related domain verification jobs that may exist From 9a79de1953241a93a932718f6132b27289192f9a Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 18:47:58 +0200 Subject: [PATCH 122/154] fix: use CUSTOM_DOMAINS_DEBUG for domains cached fetcher debug --- lib/domains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/domains.js b/lib/domains.js index 6fc79ca65..283ce6ba4 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -27,7 +27,7 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap }, { forceRefreshThreshold: 1000 * 60 * 5, cacheExpiry: 1000 * 60 * 2, - debug: process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG, + debug: CUSTOM_DOMAINS_DEBUG, keyGenerator: () => 'domain_mappings' }) From da1a650c8fcdb16c9f81a70ac37df86895bcefc8 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 18:57:31 +0200 Subject: [PATCH 123/154] cleanup: use DOMAIN_VERIFICATION_RETRY_LIMIT to check max retrycount on pgboss retry jobs --- worker/domainVerification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index f822283a7..608ad819e 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -89,7 +89,7 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss console.log(`job details: ${JSON.stringify(jobDetails)}`) // if we couldn't verify the domain after 3 retries, put it on hold if it exists and delete any related verification jobs - if (jobDetails?.retrycount >= 3) { + if (jobDetails?.retrycount >= DOMAIN_VERIFICATION_RETRY_LIMIT) { console.log(`couldn't verify domain with ID ${domainId} for the third retry, putting it on HOLD if it exists and deleting any related domain verification jobs`) await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) From 3e8389ff33388a7e7245badf10c90bf0aae9552b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 19:01:47 +0200 Subject: [PATCH 124/154] wrap post-failure cleanup in a try/catch to avoid masking the original error if it fails --- worker/domainVerification.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 608ad819e..b327763de 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -84,19 +84,24 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss } catch (error) { console.error(`couldn't verify domain with ID ${domainId}: ${error.message}`) - // get the job details to get the retry count - const jobDetails = await boss.getJobById(jobId) - console.log(`job details: ${JSON.stringify(jobDetails)}`) + try { + // get the job details to get the retry count + const jobDetails = await boss.getJobById(jobId) + console.log(`job details: ${JSON.stringify(jobDetails)}`) - // if we couldn't verify the domain after 3 retries, put it on hold if it exists and delete any related verification jobs - if (jobDetails?.retrycount >= DOMAIN_VERIFICATION_RETRY_LIMIT) { - console.log(`couldn't verify domain with ID ${domainId} for the third retry, putting it on HOLD if it exists and deleting any related domain verification jobs`) - await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) + // if we exhausted retries, put the domain on HOLD and drop any lingering verification jobs + if (jobDetails?.retrycount >= DOMAIN_VERIFICATION_RETRY_LIMIT) { + console.log(`couldn't verify domain with ID ${domainId} after ${DOMAIN_VERIFICATION_RETRY_LIMIT} retries, putting it on HOLD and deleting any related domain verification jobs`) + await models.domain.update({ where: { id: domainId }, data: { status: 'HOLD' } }) - // delete any related domain verification jobs that may exist - await cleanDomainVerificationJobs(domainId, models) + // delete any related domain verification jobs + await cleanDomainVerificationJobs(domainId, models) + } + } catch (cleanupError) { + console.error(`post-failure cleanup for domain ${domainId} failed: ${cleanupError.message}`) } + // rethrow the original error throw error } finally { // close prisma connection From 63f44acfbddb6fcdc651d05d8a5ec67eff3c0bf9 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 20:59:49 +0200 Subject: [PATCH 125/154] safer territory path checks --- proxy.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy.js b/proxy.js index b71b38c18..bd72f6b73 100644 --- a/proxy.js +++ b/proxy.js @@ -16,7 +16,11 @@ const SN_REFEREE_LANDING = 'sn_referee_landing' // main domain const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) // territory paths that needs to be rewritten to ~subname -const SN_TERRITORY_PATHS = ['/~', '/new', '/top', '/post', '/edit', '/rss'] +const SN_TERRITORY_PATHS = ['/new', '/top', '/post', '/edit', '/rss'] + +function isTerritoryPath (pathname) { + return SN_TERRITORY_PATHS.some(p => pathname === p || pathname.startsWith(p + '/')) +} async function customDomainMiddleware (request, domain, subName) { // logger is enabled if NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG == 1 @@ -57,7 +61,7 @@ async function customDomainMiddleware (request, domain, subName) { } // if we're at the root or on some territory path, hide the subname by rewriting - if (pathname === '/' || SN_TERRITORY_PATHS.some(p => pathname.startsWith(p))) { + if (pathname === '/' || isTerritoryPath(pathname)) { url.pathname = `/~${subName}${pathname === '/' ? '' : pathname}` logger.log('rewrite to:', url.pathname) // rewrite to the territory path From bc9933c79943eed48667bb358d3451855927a53e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Fri, 17 Apr 2026 21:42:37 +0200 Subject: [PATCH 126/154] reduce AWS calls by reusing already-latest certificate description; migration: DomainStatus and RecordStatus enums; cleanup and docs consistency aws: - `requestCertificate` now calls `certDetails` once and returns the certificate other than its certificateArn - DNS validation values for SSL are now obtained by the certificate we got during request ^ schema: - remove `DomainVerificationStatus` - `DomainStatus` enum for domain lifecycle - `RecordStatus` enum for domain verification - adapt domainVerification worker to use the new enums --- api/typeDefs/domain.js | 17 ++- docs/dev/custom-domains.md | 6 +- lib/domain-verification.js | 34 ++++-- .../migration.sql | 11 +- prisma/schema.prisma | 17 ++- worker/domainVerification.js | 112 ++++++++++-------- 6 files changed, 121 insertions(+), 76 deletions(-) diff --git a/api/typeDefs/domain.js b/api/typeDefs/domain.js index 0cc0766c2..865640bc5 100644 --- a/api/typeDefs/domain.js +++ b/api/typeDefs/domain.js @@ -15,7 +15,7 @@ export default gql` updatedAt: Date! domainName: String! subName: String! - status: DomainVerificationStatus + status: DomainStatus records: DomainVerificationRecordMap attempts: [DomainVerificationAttempt] certificate: DomainCertificate @@ -30,7 +30,7 @@ export default gql` type: DomainRecordType recordName: String! recordValue: String! - status: DomainVerificationStatus + status: RecordStatus attempts: [DomainVerificationAttempt] } @@ -46,7 +46,7 @@ export default gql` domainId: Int! verificationRecordId: Int stage: DomainVerificationStage - status: DomainVerificationStatus + status: RecordStatus message: String } @@ -74,12 +74,17 @@ export default gql` SSL } - enum DomainVerificationStatus { + enum DomainStatus { PENDING - VERIFIED - FAILED ACTIVE HOLD + FAILED + } + + enum RecordStatus { + PENDING + VERIFIED + FAILED } enum DomainCertificateStatus { diff --git a/docs/dev/custom-domains.md b/docs/dev/custom-domains.md index 22eadc208..cd4a1fca4 100644 --- a/docs/dev/custom-domains.md +++ b/docs/dev/custom-domains.md @@ -164,11 +164,9 @@ The `DomainVerification` job logs every step into `DomainVerificationAttempt`, w If the result of a DNS verification on the `CNAME` record is `VERIFIED`, it triggers a field `status` update to the related `DomainVerificationRecord`, keeping the record **statuses** in sync with the `DomainVerification` job results. ### HOLD domain on territory STOP -Let's say the territory owner doesn't renew their territory, and they have a custom domain attached to it. We can't let the custom domain access Stacker News as the domain can be transferred or out of original owner's control. +When a domain enters `HOLD` the `delete_certificate_and_verification_records_on_domain_hold` trigger deletes the associated `DomainCertificate` (which cascades into `ask_acm_to_delete_certificate` and removes the cert from ACM + ALB) and all `DomainVerificationRecord` rows tied to that domain. -A territory stop triggers a function that puts the custom domain on `HOLD`, effectively stopping the custom domain functionality. - -If the territory owner comes back and renews, they have to repeat the Domain Verification process just to make sure that everything is alright. The verification values will be the same and the certificate hasn't been deleted, so it should just take 30 seconds. +If the territory owner comes back and renews, they have to repeat the Domain Verification process from scratch: a new ACM certificate is issued, new SSL validation values are generated and presented to the user, and the user must publish a fresh SSL `CNAME` before ACM can validate. Only the `subName` binding and the user's own `CNAME` to `stacker.news` (which lives in the user's DNS, not in our DB) survive across the HOLD. ### Clear domain on territory takeover If a new territory owner comes up, we delete every trace of the custom domain. This also deletes its certificates, verification attempts, DNS records and customizations. diff --git a/lib/domain-verification.js b/lib/domain-verification.js index 9415a0afe..851791811 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -36,21 +36,37 @@ export async function certDetails (certificateArn) { } } -// Get the validation values for a certificate for a custom domain -export async function getValidationValues (certificateArn) { - const { certificate, error } = await certDetails(certificateArn) - if (error) { - return { cname: null, value: null, error } +// derive the ACM status from a certificate +export function extractCertificateStatus (certificate) { + return certificate?.Certificate?.Status ?? 'FAILED' +} + +// derive the DNS validation values from a certificate +export function extractValidationValues (certificate) { + const record = certificate?.Certificate?.DomainValidationOptions?.[0]?.ResourceRecord + return { + cname: record?.Name ?? null, + value: record?.Value ?? null + } +} + +// Get the validation values for a certificate for a custom domain. +// reuse the certificate if provided, otherwise fetch it from ACM +export async function getValidationValues (certificateArn, certificate = null) { + if (!certificate) { + const { certificate: fetched, error } = await certDetails(certificateArn) + if (error) { + return { cname: null, value: null, error } + } + certificate = fetched } if (!certificate || !certificate.Certificate || !certificate.Certificate.DomainValidationOptions) { return { cname: null, value: null, error: { message: 'Certificate not found' } } } - return { - cname: certificate.Certificate.DomainValidationOptions[0]?.ResourceRecord?.Name || null, - value: certificate.Certificate.DomainValidationOptions[0]?.ResourceRecord?.Value || null - } + const { cname, value } = extractValidationValues(certificate) + return { cname, value } } // Attach a certificate to the ELB listener diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index 9fcdaed58..4cbf4a367 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -5,7 +5,10 @@ CREATE TYPE "DomainVerificationStage" AS ENUM ('GENERAL', 'CNAME', 'ACM_REQUEST_ CREATE TYPE "DomainRecordType" AS ENUM ('CNAME', 'SSL'); -- CreateEnum -CREATE TYPE "DomainVerificationStatus" AS ENUM ('PENDING', 'VERIFIED', 'FAILED', 'ACTIVE', 'HOLD'); +CREATE TYPE "DomainStatus" AS ENUM ('PENDING', 'ACTIVE', 'HOLD', 'FAILED'); + +-- CreateEnum +CREATE TYPE "RecordStatus" AS ENUM ('PENDING', 'VERIFIED', 'FAILED'); -- CreateEnum CREATE TYPE "DomainCertificateStatus" AS ENUM ('PENDING_VALIDATION', 'ISSUED', 'INACTIVE', 'EXPIRED', 'REVOKED', 'FAILED', 'VALIDATION_TIMED_OUT'); @@ -17,7 +20,7 @@ CREATE TABLE "Domain" ( "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "domainName" CITEXT NOT NULL, "subName" CITEXT NOT NULL, - "status" "DomainVerificationStatus" NOT NULL DEFAULT 'PENDING', + "status" "DomainStatus" NOT NULL DEFAULT 'PENDING', CONSTRAINT "Domain_pkey" PRIMARY KEY ("id") ); @@ -30,7 +33,7 @@ CREATE TABLE "DomainVerificationAttempt" ( "domainId" INTEGER NOT NULL, "verificationRecordId" INTEGER, "stage" "DomainVerificationStage" NOT NULL DEFAULT 'GENERAL', - "status" "DomainVerificationStatus" NOT NULL DEFAULT 'PENDING', + "status" "RecordStatus" NOT NULL DEFAULT 'PENDING', "message" TEXT, CONSTRAINT "DomainVerificationAttempt_pkey" PRIMARY KEY ("id") @@ -46,7 +49,7 @@ CREATE TABLE "DomainVerificationRecord" ( "type" "DomainRecordType" NOT NULL, "recordName" TEXT NOT NULL, "recordValue" TEXT NOT NULL, - "status" "DomainVerificationStatus" NOT NULL DEFAULT 'PENDING', + "status" "RecordStatus" NOT NULL DEFAULT 'PENDING', CONSTRAINT "DomainVerificationRecord_pkey" PRIMARY KEY ("id") ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 342e2d299..1a368b2a3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -917,7 +917,7 @@ model Domain { updatedAt DateTime @default(now()) @updatedAt @map("updated_at") domainName String @unique @db.Citext subName String @unique @db.Citext - status DomainVerificationStatus @default(PENDING) + status DomainStatus @default(PENDING) sub Sub @relation(fields: [subName], references: [name], onDelete: Cascade, onUpdate: Cascade) attempts DomainVerificationAttempt[] @@ -936,7 +936,7 @@ model DomainVerificationAttempt { domainId Int verificationRecordId Int? stage DomainVerificationStage @default(GENERAL) - status DomainVerificationStatus @default(PENDING) + status RecordStatus @default(PENDING) message String? domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade) @@ -955,7 +955,7 @@ model DomainVerificationRecord { type DomainRecordType recordName String recordValue String - status DomainVerificationStatus @default(PENDING) + status RecordStatus @default(PENDING) attempts DomainVerificationAttempt[] domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade) @@ -977,12 +977,17 @@ model DomainCertificate { @@unique([domainId]) } -enum DomainVerificationStatus { +enum DomainStatus { PENDING - VERIFIED - FAILED ACTIVE HOLD + FAILED +} + +enum RecordStatus { + PENDING + VERIFIED + FAILED } enum DomainVerificationStage { diff --git a/worker/domainVerification.js b/worker/domainVerification.js index b327763de..66693bcd5 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -3,6 +3,8 @@ import { verifyDNSRecord, issueDomainCertificate, checkCertificateStatus, + certDetails, + extractCertificateStatus, getValidationValues, attachDomainCertificate, deleteDomainCertificate, @@ -39,14 +41,22 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss } }) - console.log(`domainVerification: ${JSON.stringify(domain, null, 2)}`) - // if we can't find the domain, bail without scheduling a retry if (!domain) { console.log(`domain with ID ${domainId} not found`) return } + console.log(`domainVerification: ${JSON.stringify({ + id: domain.id, + domainName: domain.domainName, + subName: domain.subName, + status: domain.status, + recordCount: domain.records?.length ?? 0, + certificateStatus: domain.certificate?.status ?? null, + updatedAt: domain.updatedAt + })}`) + // when a domain gets put on HOLD, we delete any remaining domain verification jobs // this handles the edge case where a domain is put on HOLD manually or for some other reason if (domain.status === 'HOLD') { @@ -59,15 +69,19 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss const result = await verifyDomain(domain, models) console.log(`domain verification result: ${JSON.stringify(result)}`) + const domainStatus = result.status === 'VERIFIED' + ? 'ACTIVE' + : result.status === 'FAILED' ? 'HOLD' : 'PENDING' + // update the domain status only if it has changed - if (domain.status !== result.status) { + if (domain.status !== domainStatus) { await models.domain.update({ where: { id: domainId }, - data: { status: result.status } + data: { status: domainStatus } }) } - // log the general verification attempt + // log the overall verification attempt await logAttempt({ domain, models, stage: 'VERIFICATION_COMPLETE', status: result.status, message: result.message }) // if the result is PENDING it means we still have to verify the domain @@ -110,10 +124,10 @@ export async function domainVerification ({ id: jobId, data: { domainId }, boss } async function verifyDomain (domain, models) { - // put the domain on HOLD if it has been on PENDING for too long - // the DB trigger will delete the certificate (if any), which cascades into the ACM cleanup trigger + // bail with FAILED if the domain has been on PENDING for too long; domain will be put on HOLD, + // the DB trigger will delete the certificate (if any), which cascades into the ACM cleanup trigger. if (datePivot(new Date(), { days: -DOMAIN_VERIFICATION_HOLD_AFTER_DAYS }) > domain.updatedAt) { - return { status: 'HOLD', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it for the last ${DOMAIN_VERIFICATION_HOLD_AFTER_DAYS} days` } + return { status: 'FAILED', message: `Domain ${domain.domainName} has been put on HOLD because we couldn't verify it for the last ${DOMAIN_VERIFICATION_HOLD_AFTER_DAYS} days` } } const status = 'PENDING' @@ -132,13 +146,17 @@ async function verifyDomain (domain, models) { try { // STEP 2a: Request a certificate let certificateArn = domain.certificate?.certificateArn || null + // reuse the describeCertificate response across steps 2a and 2b when we just issued the cert + let freshCertificate = null if (!certificateArn) { - certificateArn = await requestCertificate(domain, models) + const issued = await requestCertificate(domain, models) + certificateArn = issued.certificateArn + freshCertificate = issued.certificate } // STEP 2b: Get the validation values for the certificate if (certificateArn && !recordMap.SSL) { - await getACMValidationValues(domain, models, certificateArn) + await getACMValidationValues(domain, models, certificateArn, freshCertificate) // return PENDING to check ACM validation later return { status, message: 'Certificate issued and validation values stored.' } @@ -155,8 +173,8 @@ async function verifyDomain (domain, models) { throw error } - // STEP 3: If everything is verified, update the domain status to ACTIVE - return { status: 'ACTIVE', message: `Domain ${domain.domainName} has been successfully verified` } + // STEP 3: domain is verified + return { status: 'VERIFIED', message: `Domain ${domain.domainName} has been successfully verified` } } // verify a single record, logs the result and returns true if the record is valid @@ -177,54 +195,56 @@ async function verifyRecord (type, record, domain, models) { } // request a certificate for the domain from ACM +// returns { certificateArn, certificate } async function requestCertificate (domain, models) { let message = null // ask ACM to request a certificate for the domain const { certificateArn, error: requestError } = await issueDomainCertificate(domain.domainName) - if (certificateArn) { - // check the status of the just created certificate - const { certStatus, error: checkError } = await checkCertificateStatus(certificateArn) - if (checkError) { - message = 'Could not check certificate status: ' + checkError?.message - throw new Error(message) - } else { - try { - // store the certificate in the database with its status - await models.domainCertificate.create({ - data: { - domain: { connect: { id: domain.id } }, - certificateArn, - status: certStatus - } - }) - message = 'An ACM certificate with arn ' + certificateArn + ' has been successfully requested' - } catch (e) { - // if record already exists, move on - if (e.code === 'P2002') { - message = 'Certificate already stored' - } else { - message = 'Could not store certificate in the database: ' + e.message - throw new Error(message) - } - } - } - } else { + if (!certificateArn) { message = 'Could not request an ACM certificate: ' + requestError?.message throw new Error(message) } - const status = certificateArn ? 'PENDING' : 'FAILED' - await logAttempt({ domain, models, stage: 'ACM_REQUEST_CERTIFICATE', status, message }) - return certificateArn + // describe the certificate to get the status and validation values + const { certificate, error: describeError } = await certDetails(certificateArn) + if (describeError) { + message = 'Could not check certificate status: ' + describeError?.message + throw new Error(message) + } + + const certStatus = extractCertificateStatus(certificate) + + try { + // store the certificate in the database with its status + await models.domainCertificate.create({ + data: { + domain: { connect: { id: domain.id } }, + certificateArn, + status: certStatus + } + }) + message = 'An ACM certificate with arn ' + certificateArn + ' has been successfully requested' + } catch (e) { + // if record already exists, move on + if (e.code === 'P2002') { + message = 'Certificate already stored' + } else { + message = 'Could not store certificate in the database: ' + e.message + throw new Error(message) + } + } + + await logAttempt({ domain, models, stage: 'ACM_REQUEST_CERTIFICATE', status: 'PENDING', message }) + return { certificateArn, certificate } } -async function getACMValidationValues (domain, models, certificateArn) { +async function getACMValidationValues (domain, models, certificateArn, certificate = null) { let message = null // get the validation values for the certificate - const { cname, value, error } = await getValidationValues(certificateArn) + const { cname, value, error } = await getValidationValues(certificateArn, certificate) if (cname && value) { try { // store the validation values in the database @@ -308,8 +328,6 @@ async function logAttempt ({ domain, models, record, stage, status, message }) { data.verificationRecord = { connect: { id: record.id } } } - console.log(`logAttempt: ${JSON.stringify(data, null, 2)}`) - return await models.domainVerificationAttempt.create({ data }) From 4defce76e7f014c8500dabef8dc0355809e6b807 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 14:57:47 +0200 Subject: [PATCH 127/154] remove orphan Sub.domain resolver and typedef --- api/resolvers/sub.js | 3 --- api/typeDefs/sub.js | 1 - 2 files changed, 4 deletions(-) diff --git a/api/resolvers/sub.js b/api/resolvers/sub.js index 285d83902..096b20810 100644 --- a/api/resolvers/sub.js +++ b/api/resolvers/sub.js @@ -373,9 +373,6 @@ export default { return sub.SubSubscription?.length > 0 }, - domain: async (sub, args, { models }) => { - return models.domain.findUnique({ where: { subName: sub.name } }) - }, createdAt: sub => sub.createdAt || sub.created_at, lexicalState: async (sub, args, { lexicalStateLoader }) => { if (!sub.desc) return null diff --git a/api/typeDefs/sub.js b/api/typeDefs/sub.js index df5486e0b..49d9b93c0 100644 --- a/api/typeDefs/sub.js +++ b/api/typeDefs/sub.js @@ -64,7 +64,6 @@ export default gql` nitems(when: String, from: String, to: String): Int! meSubscription: Boolean! - domain: Domain optional: SubOptional! } From c4dd809ffcf69c75538d0bdfadb4a8afe5b01b14 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 14:58:29 +0200 Subject: [PATCH 128/154] add IdempotencyToken to ACM certificate requests to always return the same certificate if requested multiple times in 1 hour --- api/acm/index.js | 3 ++- lib/domain-verification.js | 4 ++-- worker/domainVerification.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/acm/index.js b/api/acm/index.js index 322849f88..cc4befeeb 100644 --- a/api/acm/index.js +++ b/api/acm/index.js @@ -6,11 +6,12 @@ const config = { endpoint: process.env.NODE_ENV === 'development' ? process.env.LOCALSTACK_ENDPOINT : undefined } -export async function requestCertificate (domain) { +export async function requestCertificate (domain, idempotencyToken) { const acm = new AWS.ACM(config) const params = { DomainName: domain, ValidationMethod: 'DNS', + IdempotencyToken: String(idempotencyToken), Tags: [ { Key: 'ManagedBy', diff --git a/lib/domain-verification.js b/lib/domain-verification.js index 851791811..fa692cab2 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -3,9 +3,9 @@ import { detachCertificateFromElb, attachCertificateToElb } from '@/api/elb' import { Resolver } from 'node:dns/promises' // Issue a certificate for a custom domain -export async function issueDomainCertificate (domainName) { +export async function issueDomainCertificate (domainName, domainId) { try { - const certificateArn = await requestCertificate(domainName) + const certificateArn = await requestCertificate(domainName, domainId) return { certificateArn, error: null } } catch (error) { console.error(`Failed to issue certificate for domain ${domainName}:`, error) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 66693bcd5..e151b3a02 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -200,7 +200,7 @@ async function requestCertificate (domain, models) { let message = null // ask ACM to request a certificate for the domain - const { certificateArn, error: requestError } = await issueDomainCertificate(domain.domainName) + const { certificateArn, error: requestError } = await issueDomainCertificate(domain.domainName, domain.id) if (!certificateArn) { message = 'Could not request an ACM certificate: ' + requestError?.message From f731808b29e326c24b801ef811fefe26ec1abf69 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:04:43 +0200 Subject: [PATCH 129/154] cleanup: remove dead code in attachACMCertificateToELB --- worker/domainVerification.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index e151b3a02..45d8cd2f6 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -311,9 +311,8 @@ async function attachACMCertificateToELB (domain, models, certificateArn) { throw new Error(message) } - const status = !error ? 'VERIFIED' : 'FAILED' - await logAttempt({ domain, models, stage: 'ELB_ATTACH_CERTIFICATE', status, message }) - return status !== 'FAILED' + await logAttempt({ domain, models, stage: 'ELB_ATTACH_CERTIFICATE', status: 'VERIFIED', message }) + return true } async function logAttempt ({ domain, models, record, stage, status, message }) { From a4b0e1aeb482b800a261d0edc6e34a8d74882eb8 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:08:55 +0200 Subject: [PATCH 130/154] cleanup: simplify DomainProvider, remove useEffect no-op previously used for personalization --- components/territory-domains.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index 0e014771d..b74b05459 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -5,7 +5,7 @@ import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' import { SSR } from '@/lib/constants' import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' -import { useEffect, createContext, useContext, useState } from 'react' +import { useEffect, createContext, useContext } from 'react' import Moon from '@/svgs/moon-fill.svg' import ClipboardLine from '@/svgs/clipboard-line.svg' import styles from './item.module.css' @@ -20,21 +20,9 @@ const DomainContext = createContext({ } }) -export const DomainProvider = ({ domain: ssrDomain, children }) => { - const [domain, setDomain] = useState(ssrDomain || null) - - // maintain the custom domain state across re-renders - useEffect(() => { - if (ssrDomain) { - setDomain(ssrDomain) - } - }, [ssrDomain]) - - // TODO: Placeholder for Auth Sync - +export const DomainProvider = ({ domain, children }) => { return ( - {/* TODO: Placeholder for Branding */} {children} ) From 6e372f17c83d8889f87e1d152d9df1fff501451e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:10:33 +0200 Subject: [PATCH 131/154] cleanup: basic rel for external territory badges --- components/item-info.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/item-info.js b/components/item-info.js index dd1485725..9a549dcd3 100644 --- a/components/item-info.js +++ b/components/item-info.js @@ -170,8 +170,7 @@ export default function ItemInfo ({ return ( - {/* eslint-disable-next-line */} - + {' '}{subName} {isExternal && } From da0c969fb771049d3f2a596dc18805fc766d47b3 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:11:59 +0200 Subject: [PATCH 132/154] cleanup: remove useless domainName and subName indexes on Domain, they're already unique with their own indexes --- .../20250504202129_custom_domains_base/migration.sql | 6 ------ prisma/schema.prisma | 2 -- 2 files changed, 8 deletions(-) diff --git a/prisma/migrations/20250504202129_custom_domains_base/migration.sql b/prisma/migrations/20250504202129_custom_domains_base/migration.sql index 4cbf4a367..32f15ce4c 100644 --- a/prisma/migrations/20250504202129_custom_domains_base/migration.sql +++ b/prisma/migrations/20250504202129_custom_domains_base/migration.sql @@ -72,15 +72,9 @@ CREATE UNIQUE INDEX "Domain_domainName_key" ON "Domain"("domainName"); -- CreateIndex CREATE UNIQUE INDEX "Domain_subName_key" ON "Domain"("subName"); --- CreateIndex -CREATE INDEX "Domain_domainName_idx" ON "Domain"("domainName"); - -- CreateIndex CREATE INDEX "Domain_created_at_idx" ON "Domain"("created_at"); --- CreateIndex -CREATE INDEX "Domain_subName_idx" ON "Domain"("subName"); - -- CreateIndex CREATE INDEX "DomainVerificationAttempt_domainId_idx" ON "DomainVerificationAttempt"("domainId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1a368b2a3..ac60dbb75 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -924,9 +924,7 @@ model Domain { records DomainVerificationRecord[] certificate DomainCertificate? - @@index([domainName]) @@index([createdAt]) - @@index([subName]) } model DomainVerificationAttempt { From cc3d494da488f2538268dcce4cf30532ac917870 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:18:12 +0200 Subject: [PATCH 133/154] correct localstack docker hostname for media-check comment --- capture/media-check.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capture/media-check.js b/capture/media-check.js index 1384d62c5..c911a899e 100644 --- a/capture/media-check.js +++ b/capture/media-check.js @@ -80,7 +80,7 @@ export default async function mediaCheck (req, res) { try { // in development, the capture container can't reach the public media url, - // so we need to replace it with its docker equivalent, e.g. http://s3:4566/uploads + // so we need to replace it with its docker equivalent, e.g. http://aws:4566/uploads if (url.startsWith(process.env.NEXT_PUBLIC_MEDIA_URL) && process.env.NODE_ENV === 'development') { url = url.replace(process.env.NEXT_PUBLIC_MEDIA_URL, process.env.MEDIA_URL_DOCKER) } From 0884742cc1a9aca69baf82b1149367625c0b561b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:19:05 +0200 Subject: [PATCH 134/154] cleanup: update docs --- docs/dev/custom-domains.md | 124 +------------------------------------ 1 file changed, 3 insertions(+), 121 deletions(-) diff --git a/docs/dev/custom-domains.md b/docs/dev/custom-domains.md index cd4a1fca4..c2e85027d 100644 --- a/docs/dev/custom-domains.md +++ b/docs/dev/custom-domains.md @@ -19,7 +19,7 @@ Since SN has several paths that depends on the `sub` parameter or the `~subName/ - Redirects paths that uses `~subName` to `/` The territory paths are the following: -`['/~', '/recent', '/random', '/top', '/post', '/edit']` +`['/new', '/top', '/post', '/edit', '/rss']` Rewriting `~subName` to `/` gives custom domains an **independent-like look**, so that things like `/~subName/post` can now look like `/post`, etc. # Domain Verification @@ -88,6 +88,8 @@ AWS operations are handled within the verification job. Each steps logs attempts After DNS checks, if we don't have a certificate already, we request ACM a new certificate for the domain. ACM will return a `certificateArn`, which is the unique ID of an ACM certificate, that is immediately used to check its status. These informations are then stored in the `DomainCertificate` table. +note: we provide an `IdempotencyToken` to AWS ACM in order to avoid creating new certificates for the same domain within 1 hour. + ##### Certificate validation values ACM needs to verify domain ownership in order to validate the certificate, in this case we use the DNS method. @@ -178,123 +180,3 @@ Whenever a domain or domain certificate gets deleted, we run a job called `delet It detaches the ACM certificate from our ALB listener and then deletes the ACM certificate from ACM. It's a necessary step to ensure that we don't waste AWS resources and also provide safety regarding the custom domain access to Stacker News. - -# Neat stuff - -### Let's go HTTPS with a reverse proxy - -To set custom domains correctly we need to have a domain and SSL certificates. - -We'll cover a basic **NGINX** configuration with **Let's Encrypt/certbot** on Linux-based systems, but you have the freedom to experiment with other methods and platforms. - -#### Prerequisites -- a domain or a public hostname -- install [nginx](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/) -- install [certbot](https://certbot.eff.org/instructions?ws=nginx&os=pip) -- possibility to add `CNAME` and `TXT` records -- domain with an `A` record at your nginx host - - -### Step 1: Create a nginx site for your SN instance - -Start creating a new site by editing `/etc/nginx/sites-available/your-domain.tld` with your editor of choice. - -
A sample nginx site configuration to prepare for certbot -Edit this configuration to match your configuration, you can have more domains. - -``` -server { - listen 80; - listen [::]:80; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - # for Let's Encrypt SSL issuance - location /.well-known/acme-challenge/ { - root /var/www/letsencrypt; - try_files $uri =404; - } -} -``` -
- -after editing, send `sudo systemctl restart nginx` - -### Step 2: Get a certificate for your domains -We can now get a certificate for your domain from Let's Encrypt/certbot. - -Edit the `-d` section to match your configuration. Every domain, sub-domain needs to have its own certificate. - -``` -sudo certbot certonly \ - --webroot -w /var/www/letsencrypt \ - -d your-domain.tld (-d sub.your-domain.tld -d another.your-domain.tld) \ - --email your@email.com \ - --agree-tos --no-eff-email \ - --deploy-hook "systemctl reload nginx" -``` - -If everything went smooth, we should now have a domain with a valid SSL certificate. - -### Step 3: Proxy everything to sndev! - -Let's go back to `/etc/nginx/sites-available/your-domain.tld` to add a SSL proxy for our sndev instance - -
A sample nginx reverse proxy config -Edit this configuration to match your configuration, you can have more domains. - -``` -server { - listen 80; - listen [::]:80; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - # for Let's Encrypt SSL issuance - location /.well-known/acme-challenge/ { - root /var/www/letsencrypt; - try_files $uri =404; - } - - # 301 to HTTPS - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name your-domain.tld (sub.your-domain.tld, another.your-domain.tld); - - ssl_certificate /etc/letsencrypt/live/your-domain.tld/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your-domain.tld/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - - # proxy everything to sndev - location / { - proxy_pass http://sndev-instance-ip:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } - - # optional security headers - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Content-Type-Options "nosniff"; - add_header Referrer-Policy "no-referrer-when-downgrade"; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; -} -``` -
- -### Step 4: Start sndev -Make sure to change your environment variables such as `.env.local` from something like `http://localhost:3000` to `https://your-domain.tld` - -Start sndev with `./sndev start` and then navigate to your domain, you should see **Stacker News**! - -If not, go back and make sure that everything is correct, you can encounter any kind of errors and **Internet can be of help**. From d8416a5daa3c653f976e2cc9f676b724a43be54c Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:30:56 +0200 Subject: [PATCH 135/154] comprehensive subdomain regex --- lib/validate.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/validate.js b/lib/validate.js index 2fca55230..c1b7544e0 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -297,10 +297,19 @@ export function territoryTransferSchema ({ me, ...args }) { // Custom Domain validation schema export function customDomainSchema (args) { + // a subdomain must have at least 3 labels (www.example.com at minimum) + // each label must be 1-63 chars + // the total length must be at most 253 chars + // and no leading/trailing dashes + const regex = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){2,}[a-z]{2,63}$/i return object({ - domainName: string().matches(/^(?:[a-z0-9-]+\.){2,}[a-z]{2,}$/i, { - message: 'CNAME records only support subdomains (e.g., www.example.com, sub.example.com)' - }).nullable() + domainName: string() + .trim() + .max(253, 'domain must be at most 253 characters') + .matches(regex, { + message: 'CNAME records only support subdomains (e.g., www.example.com, sub.example.com), no leading/trailing dashes, each label 1-63 chars, at least two dots' + }) + .nullable() }) } From c80b68e1cd0d9a3f0f84c33888834bca654e169e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Sun, 19 Apr 2026 15:36:39 +0200 Subject: [PATCH 136/154] fix: me can be null, protect from crash --- components/territory-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/territory-form.js b/components/territory-form.js index f90b942c2..55ce919b4 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -308,7 +308,7 @@ export default function TerritoryForm ({ sub }) { />
- {SN_ADMIN_IDS.includes(Number(me.id)) && + {SN_ADMIN_IDS.includes(Number(me?.id)) && <> {sub && !domain &&
From 080a63838707359eadcc5ffe2b6292beab5e96e4 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 20 Apr 2026 12:16:02 +0200 Subject: [PATCH 137/154] cleanup: simplify DomainProvider --- components/territory-domains.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index b74b05459..daaa675fe 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -5,7 +5,7 @@ import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' import { SSR } from '@/lib/constants' import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' -import { useEffect, createContext, useContext } from 'react' +import { useEffect, createContext, useContext, useMemo } from 'react' import Moon from '@/svgs/moon-fill.svg' import ClipboardLine from '@/svgs/clipboard-line.svg' import styles from './item.module.css' @@ -13,16 +13,12 @@ import styles from './item.module.css' const DOMAIN_POLL_INTERVAL_MS = 10_000 // Domain context for custom domains -const DomainContext = createContext({ - domain: { - domainName: null, - subName: null - } -}) +const DomainContext = createContext({ domain: null }) export const DomainProvider = ({ domain, children }) => { + const value = useMemo(() => ({ domain }), [domain]) return ( - + {children} ) From 5b8ce0b4536a3428292f995bde424ddb0d21efce Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 20 Apr 2026 14:57:51 +0200 Subject: [PATCH 138/154] terminal ACM states now stops domain verification, will put domain on HOLD --- lib/domain-verification.js | 7 +++++++ worker/domainVerification.js | 23 ++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/domain-verification.js b/lib/domain-verification.js index fa692cab2..97f5939f5 100644 --- a/lib/domain-verification.js +++ b/lib/domain-verification.js @@ -2,6 +2,13 @@ import { requestCertificate, getCertificateStatus, describeCertificate, deleteCe import { detachCertificateFromElb, attachCertificateToElb } from '@/api/elb' import { Resolver } from 'node:dns/promises' +export const ACM_TERMINAL_FAILED_STATUSES = new Set([ + 'FAILED', + 'VALIDATION_TIMED_OUT', + 'REVOKED', + 'EXPIRED' +]) + // Issue a certificate for a custom domain export async function issueDomainCertificate (domainName, domainId) { try { diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 45d8cd2f6..2d5f3a721 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -8,7 +8,8 @@ import { getValidationValues, attachDomainCertificate, deleteDomainCertificate, - detachDomainCertificate + detachDomainCertificate, + ACM_TERMINAL_FAILED_STATUSES } from '@/lib/domain-verification' import { DOMAIN_HOLD_RETENTION_DAYS, @@ -163,8 +164,11 @@ async function verifyDomain (domain, models) { } // STEP 2c: Check ACM validation - const sslVerified = await checkACMValidation(domain, models, recordMap.SSL) - if (!sslVerified) return { status, message: 'ACM validation is still pending.' } + const sslStatus = await checkACMValidation(domain, models, recordMap.SSL) + if (sslStatus === 'FAILED') { + return { status: 'FAILED', message: `ACM certificate for ${domain.domainName} is in a terminal failed state.` } + } + if (sslStatus !== 'VERIFIED') return { status, message: 'ACM validation is still pending.' } // STEP 2d: Attach the certificate to the ELB listener await attachACMCertificateToELB(domain, models, certificateArn) @@ -294,9 +298,18 @@ async function checkACMValidation (domain, models, record) { throw new Error(message) } - const status = certStatus === 'ISSUED' ? 'VERIFIED' : 'PENDING' + let status + if (certStatus === 'ISSUED') { + status = 'VERIFIED' + } else if (ACM_TERMINAL_FAILED_STATUSES.has(certStatus)) { + status = 'FAILED' + message = `ACM certificate is in a terminal failed state: ${certStatus}` + } else { + status = 'PENDING' + } + await logAttempt({ domain, models, record, stage: 'ACM_VALIDATION', status, message }) - return status === 'VERIFIED' + return status } async function attachACMCertificateToELB (domain, models, certificateArn) { From db798b36dc490575edc60786d6931ba63be9912a Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 20 Apr 2026 15:00:29 +0200 Subject: [PATCH 139/154] territory form: required non-empty domain input field --- lib/validate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validate.js b/lib/validate.js index c1b7544e0..409c9b2ea 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -305,11 +305,11 @@ export function customDomainSchema (args) { return object({ domainName: string() .trim() + .required('domain name is required') .max(253, 'domain must be at most 253 characters') .matches(regex, { message: 'CNAME records only support subdomains (e.g., www.example.com, sub.example.com), no leading/trailing dashes, each label 1-63 chars, at least two dots' }) - .nullable() }) } From 742a53dd3878fe06e447e8971d71fe0afbcbd7c5 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 20 Apr 2026 15:09:38 +0200 Subject: [PATCH 140/154] simplify getACMValidationValues return --- worker/domainVerification.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/worker/domainVerification.js b/worker/domainVerification.js index 2d5f3a721..0cb825025 100644 --- a/worker/domainVerification.js +++ b/worker/domainVerification.js @@ -275,9 +275,8 @@ async function getACMValidationValues (domain, models, certificateArn, certifica throw new Error(message) } - const status = cname && value ? 'PENDING' : 'FAILED' - await logAttempt({ domain, models, stage: 'ACM_REQUEST_VALIDATION_VALUES', status, message }) - return status !== 'FAILED' + await logAttempt({ domain, models, stage: 'ACM_REQUEST_VALIDATION_VALUES', status: 'PENDING', message }) + return true } async function checkACMValidation (domain, models, record) { From 832b462f17dd4792a9520fd1a0af7604b11e2653 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 20 Apr 2026 15:10:11 +0200 Subject: [PATCH 141/154] export NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG env var to service worker --- next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/next.config.js b/next.config.js index bf0e9a984..6326c2dac 100644 --- a/next.config.js +++ b/next.config.js @@ -272,6 +272,7 @@ module.exports = withPlausibleProxy({ src: 'https://plausible.io/js/pa-EScEhWlTi 'process.env.NEXT_PUBLIC_LONG_POLL_INTERVAL_MS': JSON.stringify(process.env.NEXT_PUBLIC_LONG_POLL_INTERVAL_MS), 'process.env.NEXT_PUBLIC_EXTRA_LONG_POLL_INTERVAL_MS': JSON.stringify(process.env.NEXT_PUBLIC_EXTRA_LONG_POLL_INTERVAL_MS), 'process.env.NEXT_PUBLIC_MDAST_DEBUG': JSON.stringify(process.env.NEXT_PUBLIC_MDAST_DEBUG), + 'process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG': JSON.stringify(process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG), 'process.env.SANCTIONED_COUNTRY_CODES': JSON.stringify(process.env.SANCTIONED_COUNTRY_CODES), 'process.env.NEXT_IS_EXPORT_WORKER': 'true' }) From e6a966a2a5026fec934946f8a55df57d58f87280 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 22 Apr 2026 17:40:41 +0200 Subject: [PATCH 142/154] explicit DOMAIN_BETA_IDS for custom domains access --- api/resolvers/domain.js | 12 +++++++----- components/territory-form.js | 4 ++-- lib/constants.js | 7 ++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/api/resolvers/domain.js b/api/resolvers/domain.js index 9605772e9..b34113f89 100644 --- a/api/resolvers/domain.js +++ b/api/resolvers/domain.js @@ -5,7 +5,7 @@ import { DOMAIN_VERIFICATION_INTERVAL_SECONDS, DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS, DOMAIN_VERIFICATION_RETRY_LIMIT, - SN_ADMIN_IDS + DOMAIN_BETA_IDS } from '@/lib/constants' export async function cleanDomainVerificationJobs (domainId, models) { @@ -42,8 +42,9 @@ export default { throw new GqlAuthenticationError() } - if (!SN_ADMIN_IDS.includes(Number(me.id))) { - throw new GqlAuthorizationError('not an admin') + // beta access + if (!DOMAIN_BETA_IDS.includes(Number(me.id))) { + throw new GqlAuthorizationError('not allowed') } const sub = await models.sub.findUnique({ where: { name: subName } }) @@ -67,8 +68,9 @@ export default { throw new GqlAuthenticationError() } - if (!SN_ADMIN_IDS.includes(Number(me.id))) { - throw new GqlAuthorizationError('not an admin') + // beta access + if (!DOMAIN_BETA_IDS.includes(Number(me.id))) { + throw new GqlAuthorizationError('not allowed') } const sub = await models.sub.findUnique({ where: { name: subName } }) diff --git a/components/territory-form.js b/components/territory-form.js index 55ce919b4..f1d81edfe 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -8,7 +8,7 @@ import { gql } from '@apollo/client' import { useApolloClient, useLazyQuery } from '@apollo/client/react' import { useCallback, useMemo, useState } from 'react' import { useRouter } from 'next/router' -import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, SN_ADMIN_IDS, TERRITORY_BILLING_OPTIONS, TERRITORY_PERIOD_COST } from '@/lib/constants' +import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, DOMAIN_BETA_IDS, TERRITORY_BILLING_OPTIONS, TERRITORY_PERIOD_COST } from '@/lib/constants' import { territorySchema } from '@/lib/validate' import { useMe } from './me' import Info from './info' @@ -308,7 +308,7 @@ export default function TerritoryForm ({ sub }) { />
- {SN_ADMIN_IDS.includes(Number(me?.id)) && + {DOMAIN_BETA_IDS.includes(Number(me?.id)) && <> {sub && !domain &&
diff --git a/lib/constants.js b/lib/constants.js index 207d12b61..987c289a7 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -214,6 +214,10 @@ export const DOMAIN_VERIFICATION_RETRY_LIMIT = 3 export const DOMAIN_VERIFICATION_RETRY_DELAY_SECONDS = 60 export const DOMAIN_VERIFICATION_HOLD_AFTER_DAYS = 2 export const DOMAIN_HOLD_RETENTION_DAYS = 30 +// custom domains debugging +export const CUSTOM_DOMAINS_DEBUG = Number(process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG) === 1 +// custom domains beta access ids +export const DOMAIN_BETA_IDS = [USER_ID.k00b, USER_ID.sox, USER_ID.scoresby, USER_ID.sn] export const ZAP_UNDO_DELAY_MS = 5_000 export const ZAP_DEBOUNCE_MS = 1_000 @@ -237,6 +241,3 @@ export const BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' // MDAST pipeline debugging export const MDAST_DEBUG = Number(process.env.NEXT_PUBLIC_MDAST_DEBUG) === 1 - -// custom domains debugging -export const CUSTOM_DOMAINS_DEBUG = Number(process.env.NEXT_PUBLIC_CUSTOM_DOMAINS_DEBUG) === 1 From a65d7b46d65957c7ffd3da3ef84f10f5e1ea02fd Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 22 Apr 2026 17:42:26 +0200 Subject: [PATCH 143/154] getDomainMappingFromRequest helper to get and validate custom domain data, used in place of headers in async contexts; cleanup --- api/ssrApollo.js | 19 ++++++++-------- components/item-info.js | 3 ++- components/territory-domains.js | 1 + fragments/subs.js | 1 + lib/domains.js | 13 +++++++++++ prisma/schema.prisma | 1 - proxy.js | 39 ++++++++++++--------------------- 7 files changed, 40 insertions(+), 37 deletions(-) diff --git a/api/ssrApollo.js b/api/ssrApollo.js index 3f8a0f9aa..9280294f7 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -19,6 +19,7 @@ import { satsToMsats } from '@/lib/format' import { MULTI_AUTH_ANON, MULTI_AUTH_LIST, MULTI_AUTH_POINTER, multiAuthMiddleware } from '@/lib/auth' import { lexicalStateLoader } from '@/lib/lexical/server/loader' import { createUserLoader, createSubLoader } from '@/api/loaders' +import { getDomainMappingFromRequest } from '@/lib/domains' export default async function getSSRApolloClient ({ req, res, me = null }) { // switch session cookie before getting session on SSR @@ -169,16 +170,14 @@ export function getGetServerSideProps ( const client = await getSSRApolloClient({ req, res }) - const isCustomDomain = req.headers.host !== process.env.NEXT_PUBLIC_URL.replace(/^https?:\/\//, '') - const subName = req.headers['x-stacker-news-subname'] || null - let domain = null - if (isCustomDomain && subName) { - domain = { - domainName: req.headers.host, - subName - // TODO: custom branding - } - } + // inject custom domain data if any + const domainMapping = await getDomainMappingFromRequest(req) + const domain = domainMapping + ? { + domainName: domainMapping.domainName, + subName: domainMapping.subName + } + : null let { data: { me } } = await client.query({ query: ME }) diff --git a/components/item-info.js b/components/item-info.js index 9a549dcd3..f4eeb3b7d 100644 --- a/components/item-info.js +++ b/components/item-info.js @@ -164,8 +164,9 @@ export default function ItemInfo ({ } + {/* XXX: ideally we would use the proxy middleware to handle external subnames, but it would break local dev: https://github.com/vercel/next.js/issues/44482 */} {item.subNames?.map(subName => { - const isExternal = domain && subName !== domain.subName + const isExternal = domain && (subName !== domain.subName) const href = domain ? (isExternal ? `${process.env.NEXT_PUBLIC_URL}/~${subName}` : '/') : `/~${subName}` return ( diff --git a/components/territory-domains.js b/components/territory-domains.js index daaa675fe..c94edcc95 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -24,6 +24,7 @@ export const DomainProvider = ({ domain, children }) => { ) } +/** returns domain data with this shape: { domainName, subName } */ export const useDomain = () => useContext(DomainContext) export function usePrefix (sub) { diff --git a/fragments/subs.js b/fragments/subs.js index 4225b92d7..0027aee25 100644 --- a/fragments/subs.js +++ b/fragments/subs.js @@ -1,6 +1,7 @@ import { gql } from '@apollo/client' import { ITEM_FIELDS, ITEM_FULL_FIELDS } from './items' import { COMMENTS_ITEM_EXT_FIELDS } from './comments' + // we can't import from users because of circular dependency const STREAK_FIELDS = gql` fragment StreakFields on User { diff --git a/lib/domains.js b/lib/domains.js index 283ce6ba4..0571d392f 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -2,6 +2,9 @@ import { cachedFetcher } from '@/lib/fetch' import prisma from '@/api/models' import { CUSTOM_DOMAINS_DEBUG } from '@/lib/constants' +// main domain +export const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) + export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMappings () { try { const domains = await prisma.domain.findMany({ @@ -54,3 +57,13 @@ export function createDomainsDebugLogger (domainName, debug = CUSTOM_DOMAINS_DEB errorLog } } + +export async function getDomainMappingFromRequest (req) { + const host = req.headers.get('host') + const normalized = process.env.NODE_ENV === 'development' ? host.split(':')[0] : host + const mapping = normalized && normalized !== SN_MAIN_DOMAIN.host + ? await getDomainMapping(normalized) + : null + + return mapping +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ac60dbb75..939a39a5e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -683,7 +683,6 @@ model Sub { postsSatsFilter Int @default(10) nsfw Boolean @default(false) - parent Sub? @relation("ParentChildren", fields: [parentName], references: [name]) children Sub[] @relation("ParentChildren") user User @relation(fields: [userId], references: [id], onDelete: Cascade) diff --git a/proxy.js b/proxy.js index bd72f6b73..06c7c185b 100644 --- a/proxy.js +++ b/proxy.js @@ -1,6 +1,6 @@ import 'urlpattern-polyfill' import { NextRequest, NextResponse } from 'next/server' -import { getDomainMapping, createDomainsDebugLogger } from '@/lib/domains' +import { getDomainMappingFromRequest, createDomainsDebugLogger } from '@/lib/domains' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) @@ -13,8 +13,6 @@ const SN_REFERRER = 'sn_referrer' const SN_REFERRER_NONCE = 'sn_referrer_nonce' // key for referred pages const SN_REFEREE_LANDING = 'sn_referee_landing' -// main domain -const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) // territory paths that needs to be rewritten to ~subname const SN_TERRITORY_PATHS = ['/new', '/top', '/post', '/edit', '/rss'] @@ -29,12 +27,12 @@ async function customDomainMiddleware (request, domain, subName) { const url = request.nextUrl.clone() // we need pathname, searchParams and origin const { pathname, searchParams } = url - // set the subname in the request headers + // set the subname and domain in the request headers const headers = new Headers(request.headers) headers.set('x-stacker-news-subname', subName) + headers.set('x-stacker-news-domain', domain) logger.log('custom domain', domain, 'with subname', subName) - logger.log('main domain', JSON.stringify(SN_MAIN_DOMAIN)) logger.log('pathname', pathname) logger.log('searchParams', JSON.stringify(searchParams)) logger.log('search', url.search) @@ -210,33 +208,24 @@ export async function proxy (req) { // clear subname header to prevent potential spoofing const headers = new Headers(req.headers) headers.delete('x-stacker-news-subname') + headers.delete('x-stacker-news-domain') const request = new NextRequest(req, { headers }) - const referrerResp = referrerMiddleware(request) - // TODO: check if we actually need this, and WHY - if (referrerResp.headers.get('Location')) { - // this is a redirect, apply security headers - return applySecurityHeaders(referrerResp) + let resp = referrerMiddleware(request) + // if resp is a redirect, apply security headers immediately and return + if (resp.headers.get('Location')) { + return applySecurityHeaders(resp) } // if we're on a custom domain, handle it - const domain = request.headers.get('host') - if (domain !== SN_MAIN_DOMAIN?.host) { // we don't need middleware to fail if dev messes up ENVs - // in development we might have a port in the domain - const domainToMap = process.env.NODE_ENV === 'development' ? domain.split(':')[0] : domain - // check if we have a mapping for this domain - const mapping = await getDomainMapping(domainToMap) - if (mapping?.subName) { - console.log('[domains] allowed custom domain', domain, 'detected, pointing to', mapping.subName) - const resp = await customDomainMiddleware(request, domain, mapping.subName) - // apply referrer cookies to the custom domain response - const referredResp = applyReferrerCookies(resp, referrerResp) - // finally apply security headers - return applySecurityHeaders(referredResp) - } + const mapping = await getDomainMappingFromRequest(request) + if (mapping) { + const domainResp = await customDomainMiddleware(request, mapping.domainName, mapping.subName) + // apply referrer cookies to the custom domain response + resp = applyReferrerCookies(domainResp, resp) } - return applySecurityHeaders(referrerResp) + return applySecurityHeaders(resp) } export const config = { From d61a2a4e4d589baa43c7336fd6340d476c87bad0 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 22 Apr 2026 18:07:55 +0200 Subject: [PATCH 144/154] fix: support both NextRequest and Node requests; fix: return a domain mapping with domainName and subName --- lib/domains.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/domains.js b/lib/domains.js index 0571d392f..93eebe666 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -20,7 +20,7 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap if (!domains.length) return null return domains.reduce((acc, domain) => { - acc[domain.domainName.toLowerCase()] = { subName: domain.subName } + acc[domain.domainName.toLowerCase()] = { domainName: domain.domainName, subName: domain.subName } return acc }, {}) } catch (error) { @@ -59,7 +59,10 @@ export function createDomainsDebugLogger (domainName, debug = CUSTOM_DOMAINS_DEB } export async function getDomainMappingFromRequest (req) { - const host = req.headers.get('host') + // handles both NextRequest and Node request + const host = req.headers.host ?? req.headers.get('host') + if (!host) return null + const normalized = process.env.NODE_ENV === 'development' ? host.split(':')[0] : host const mapping = normalized && normalized !== SN_MAIN_DOMAIN.host ? await getDomainMapping(normalized) From a71337c14f80a7f8133817caf8080b347461fcc4 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 22 Apr 2026 19:47:07 +0200 Subject: [PATCH 145/154] fix: use hostname instead of host for custom domain <-> sn main domain comparison --- lib/domains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/domains.js b/lib/domains.js index 93eebe666..daaea8d35 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -64,7 +64,7 @@ export async function getDomainMappingFromRequest (req) { if (!host) return null const normalized = process.env.NODE_ENV === 'development' ? host.split(':')[0] : host - const mapping = normalized && normalized !== SN_MAIN_DOMAIN.host + const mapping = normalized && normalized !== SN_MAIN_DOMAIN.hostname ? await getDomainMapping(normalized) : null From 8d93f927e007a20662f0d085bf219bcff8604e7e Mon Sep 17 00:00:00 2001 From: Soxasora Date: Wed, 22 Apr 2026 23:42:50 +0200 Subject: [PATCH 146/154] revert a4b0e1a simplify DomainProvider --- components/territory-domains.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/territory-domains.js b/components/territory-domains.js index c94edcc95..32e00d253 100644 --- a/components/territory-domains.js +++ b/components/territory-domains.js @@ -5,7 +5,7 @@ import { customDomainSchema } from '@/lib/validate' import { useToast } from '@/components/toast' import { SSR } from '@/lib/constants' import { GET_DOMAIN, SET_DOMAIN } from '@/fragments/domains' -import { useEffect, createContext, useContext, useMemo } from 'react' +import { useEffect, useState, createContext, useContext, useMemo } from 'react' import Moon from '@/svgs/moon-fill.svg' import ClipboardLine from '@/svgs/clipboard-line.svg' import styles from './item.module.css' @@ -15,8 +15,18 @@ const DOMAIN_POLL_INTERVAL_MS = 10_000 // Domain context for custom domains const DomainContext = createContext({ domain: null }) -export const DomainProvider = ({ domain, children }) => { +export const DomainProvider = ({ domain: ssrDomain, children }) => { + const [domain, setDomain] = useState(ssrDomain ?? null) + + // maintain the custom domain state across re-renders and nodata navigations + useEffect(() => { + if (ssrDomain !== undefined) { + setDomain(ssrDomain) + } + }, [ssrDomain]) + const value = useMemo(() => ({ domain }), [domain]) + return ( {children} From c0c095378c1cd6f4c71a2eaa5e415172fb7dce4b Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 23 Apr 2026 19:24:37 +0200 Subject: [PATCH 147/154] exp: move auth UI to stacker.news, login with nym button --- components/login-button.js | 24 ++++++++++++++++++++++++ components/login.js | 19 +++++++++++++++++-- lib/domains.js | 20 ++++++++++++++++---- pages/api/auth/[...nextauth].js | 4 ++-- pages/api/auth/redirect.js | 14 ++++++++++++++ pages/api/auth/sync.js | 6 ++++-- pages/login.js | 20 ++++++++++++++------ proxy.js | 14 +++++++++----- 8 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 pages/api/auth/redirect.js diff --git a/components/login-button.js b/components/login-button.js index 8eab5a082..66f772eec 100644 --- a/components/login-button.js +++ b/components/login-button.js @@ -3,6 +3,10 @@ import TwitterIcon from '@/svgs/twitter-fill.svg' import LightningIcon from '@/svgs/bolt.svg' import NostrIcon from '@/svgs/nostr.svg' import Button from 'react-bootstrap/Button' +import useCookie from './use-cookie' +import { MULTI_AUTH_POINTER } from '@/lib/auth' +import { useAccounts } from './account' +import SNIcon from '@/svgs/sn.svg' export default function LoginButton ({ text, type, className, onClick, disabled }) { let Icon, variant @@ -38,3 +42,23 @@ export default function LoginButton ({ text, type, className, onClick, disabled ) } + +// TODO: better design +// TODO: implement multi-account support with a dropdown +export function LoginWithNymButton ({ className, onClick, disabled }) { + const accounts = useAccounts() + const [pointerCookie] = useCookie(MULTI_AUTH_POINTER) + + const account = accounts.find(account => account.id === Number(pointerCookie)) + + if (!account) return null + + return ( + + ) +} diff --git a/components/login.js b/components/login.js index cceeeff91..535ccda9f 100644 --- a/components/login.js +++ b/components/login.js @@ -6,7 +6,7 @@ import Alert from 'react-bootstrap/Alert' import { useRouter } from 'next/router' import { LightningAuthWithExplainer } from './lightning-auth' import { NostrAuthWithExplainer } from './nostr-auth' -import LoginButton from './login-button' +import LoginButton, { LoginWithNymButton } from './login-button' import { emailSchema } from '@/lib/validate' import { OverlayTrigger, Tooltip } from 'react-bootstrap' import { datePivot } from '@/lib/time' @@ -74,7 +74,7 @@ export function authErrorMessage (error, signin) { const multiAuthProviders = ['Lightning', 'Nostr'] -export default function Login ({ providers, callbackUrl, multiAuth, error, text, Header, Footer, signin, syncSignup }) { +export default function Login ({ providers, callbackUrl, multiAuth, error, text, Header, Footer, signin, syncSignup, domainData }) { const [errorMessage, setErrorMessage] = useState(authErrorMessage(error, signin)) const router = useRouter() const [, setPointerCookie] = useCookie(MULTI_AUTH_POINTER) @@ -126,6 +126,21 @@ export default function Login ({ providers, callbackUrl, multiAuth, error, text, dismissible >{errorMessage} } + {/** custom domain auth sync button */} + {domainData && ( + { + const callbackUrl = router.query.callbackUrl + const redirectUri = callbackUrl?.startsWith('http') + ? new URL(callbackUrl).pathname + : callbackUrl || '/' + // port is only present in local dev; in prod domainData.port is null and we send the bare host. + const domain = domainData.port ? `${domainData.domainName}:${domainData.port}` : domainData.domainName + router.push({ pathname: '/api/auth/sync', query: { domain, redirectUri } }) + }} + /> + )} {sortedProviders.map(provider => { switch (provider.name) { case 'Email': diff --git a/lib/domains.js b/lib/domains.js index 4ebcdb6bc..af941dfb9 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -42,8 +42,11 @@ export const domainsMappingsCache = cachedFetcher(async function fetchDomainsMap }) export const getDomainMapping = async (domain) => { + const { domainName } = normalizeDomain(domain) + if (!domainName) return null + const domainsMappings = await domainsMappingsCache() - return domainsMappings?.[domain?.toLowerCase()] + return domainsMappings?.[domainName?.toLowerCase()] } export function createDomainsDebugLogger (domainName, debug = CUSTOM_DOMAINS_DEBUG) { @@ -70,10 +73,19 @@ export async function getDomainMappingFromRequest (req) { const host = req.headers.host ?? req.headers.get('host') if (!host) return null - const normalized = process.env.NODE_ENV === 'development' ? host.split(':')[0] : host - const mapping = normalized && normalized !== SN_MAIN_DOMAIN.hostname - ? await getDomainMapping(normalized) + const { domainName, domainPort } = normalizeDomain(host) + + const mapping = domainName && domainName !== SN_MAIN_DOMAIN.hostname + ? { ...await getDomainMapping(domainName), port: domainPort } : null return mapping } + +export function normalizeDomain (domain) { + const parts = domain.split(':') + const domainName = parts[0] + const domainPort = parts[1] || null + + return { domainName, domainPort } +} diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 23d5c6a1a..25c744255 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -12,7 +12,7 @@ import { schnorr } from '@noble/curves/secp256k1' import { notifyReferral } from '@/lib/webPush' import { hashEmail } from '@/lib/crypto' import { multiAuthMiddleware, setMultiAuthCookies } from '@/lib/auth' -import { getDomainMapping } from '@/lib/domains' +import { getDomainMapping, normalizeDomain } from '@/lib/domains' import { BECH32_CHARSET } from '@/lib/constants' import { NodeNextRequest } from 'next/dist/server/base-http/node' import * as cookie from 'cookie' @@ -123,7 +123,7 @@ function getCallbacks (req, res) { // custom domain session token validation if (token?.domainName) { try { - const currentDomain = new URL(req.headers.referer).host || req.headers['x-stacker-news-domain'] + const { domainName: currentDomain } = normalizeDomain(req.headers.host) // tokens created for a custom domain should only be used on that domain if (currentDomain !== token.domainName) return null diff --git a/pages/api/auth/redirect.js b/pages/api/auth/redirect.js new file mode 100644 index 000000000..bc6ee801e --- /dev/null +++ b/pages/api/auth/redirect.js @@ -0,0 +1,14 @@ +import { SN_MAIN_DOMAIN } from '@/lib/domains' + +// TODO: experimental, middleware proxy can't redirect to absolute MAIN DOMAIN URLs in local dev +export default async function handler (req, res) { + const { domain } = req.query + if (!domain) { + return res.status(400).json({ status: 'ERROR', reason: 'domain is required' }) + } + + const redirectUrl = new URL('/login', SN_MAIN_DOMAIN) + redirectUrl.searchParams.set('domain', domain) + + res.redirect(302, redirectUrl.href) +} diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js index bff070d6a..e0569773b 100644 --- a/pages/api/auth/sync.js +++ b/pages/api/auth/sync.js @@ -2,8 +2,8 @@ import models from '@/api/models' import { randomBytes } from 'node:crypto' import { encode as encodeJWT, getToken } from 'next-auth/jwt' import { validateSchema, customDomainSchema } from '@/lib/validate' +import { SN_MAIN_DOMAIN, normalizeDomain } from '@/lib/domains' -const SN_MAIN_DOMAIN = new URL(process.env.NEXT_PUBLIC_URL) const SYNC_TOKEN_MAX_AGE = 60 * 5 // 5 minutes const VERIFICATION_TOKEN_EXPIRY = 1000 * 60 * 5 // 5 minutes in milliseconds @@ -61,7 +61,9 @@ export default async function handler (req, res) { } async function checkDomainValidity (receivedDomain) { - const domainName = process.env.NODE_ENV === 'development' ? receivedDomain.split(':')[0] : receivedDomain + // the received domain can carry a port in local dev (e.g. pizza.com:3000); + // the Domain row stores bare hostnames, so we always normalize before lookup. + const { domainName } = normalizeDomain(receivedDomain) try { await validateSchema(customDomainSchema, { domainName }) diff --git a/pages/login.js b/pages/login.js index 48067ad24..3fc3ce26c 100644 --- a/pages/login.js +++ b/pages/login.js @@ -6,14 +6,15 @@ import { StaticLayout } from '@/components/layout' import Login from '@/components/login' import { isExternal } from '@/lib/url' import { MULTI_AUTH_ANON, MULTI_AUTH_POINTER } from '@/lib/auth' +import { getDomainMapping, normalizeDomain } from '@/lib/domains' -export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, syncSignup = null, error = null } }) { +export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, syncSignup = null, domain = null, error = null } }) { let session = await getServerSession(req, res, getAuthOptions(req)) // required to prevent infinite redirect loops if we switch to anon // but are on a page that would redirect us to /signup. // without this code, /signup would redirect us back to the callbackUrl. - if (req.cookies[MULTI_AUTH_POINTER] === MULTI_AUTH_ANON) { + if (req.cookies[MULTI_AUTH_POINTER] === MULTI_AUTH_ANON || domain) { session = null } @@ -47,6 +48,12 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult } } + // the ?domain= query param carries the custom domain's host as-is (with its port in local dev). + // we pass the port alongside the mapping so the client can redirect back through /api/auth/sync + const mapping = domain ? await getDomainMapping(domain) : null + const { domainPort } = domain ? normalizeDomain(domain) : { domainPort: null } + const domainData = mapping ? { ...mapping, port: domainPort } : null + const providers = await getProviders() return { @@ -55,7 +62,8 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult callbackUrl, error, multiAuth, - syncSignup + syncSignup, + domainData } } } @@ -66,11 +74,11 @@ function LoginFooter ({ callbackUrl }) { ) } -function LoginHeader () { +function LoginHeader ({ domainData }) { return ( <>

- Log in + Log in {domainData && ` to ~${domainData.subName}`}

Nothing wrestles up a smile like a familiar face.
@@ -94,7 +102,7 @@ export default function LoginPage ({ multiAuth, ...props }) { } - Header={multiAuthBool ? () => : () => } + Header={multiAuthBool ? () => : () => } text='Log in' signin multiAuth={multiAuth} diff --git a/proxy.js b/proxy.js index 08f4a5096..89e2c785b 100644 --- a/proxy.js +++ b/proxy.js @@ -1,7 +1,7 @@ import 'urlpattern-polyfill' import { NextRequest, NextResponse } from 'next/server' import { SESSION_COOKIE, cookieOptions } from '@/lib/auth' -import { getDomainMappingFromRequest, createDomainsDebugLogger, SN_MAIN_DOMAIN } from '@/lib/domains' +import { getDomainMapping, createDomainsDebugLogger, SN_MAIN_DOMAIN, normalizeDomain } from '@/lib/domains' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) @@ -77,7 +77,7 @@ async function customDomainMiddleware (request, domain, subName) { } async function redirectToAuthSync (searchParams, domain, signup, headers) { - const syncUrl = new URL('/api/auth/sync', SN_MAIN_DOMAIN) + const syncUrl = new URL('/api/auth/redirect', SN_MAIN_DOMAIN) syncUrl.searchParams.set('domain', domain) if (signup) { @@ -100,8 +100,9 @@ async function establishAuthSync (request, searchParams, domain, headers) { const redirectUri = searchParams.get('redirectUri') || '/' const res = NextResponse.redirect(new URL(redirectUri, request.url), { headers }) + const { domainName } = normalizeDomain(domain) + try { - const domainName = process.env.NODE_ENV === 'development' ? domain.split(':')[0] : domain const body = JSON.stringify({ verificationToken: token, domainName }) const fetchHeaders = new Headers(headers) fetchHeaders.set('Content-Type', 'application/json') @@ -278,9 +279,12 @@ export async function proxy (req) { } // if we're on a custom domain, handle it - const mapping = await getDomainMappingFromRequest(request) + const domain = request.headers.get('host') + const mapping = await getDomainMapping(domain) if (mapping) { - const domainResp = await customDomainMiddleware(request, mapping.domainName, mapping.subName) + // domain can have a port (local dev), so we pass the whole domain to the middleware + // instead of the domain retrieved from the mapping + const domainResp = await customDomainMiddleware(request, domain, mapping.subName) // apply referrer cookies to the custom domain response resp = applyReferrerCookies(domainResp, resp) } From 76ced421521c9dc3759d6fa17f99fc57b2cd0ee7 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 23 Apr 2026 19:38:09 +0200 Subject: [PATCH 148/154] add support for callbackUrl, cleanup --- pages/api/auth/redirect.js | 8 ++++++-- proxy.js | 22 +++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pages/api/auth/redirect.js b/pages/api/auth/redirect.js index bc6ee801e..e2ce4aa69 100644 --- a/pages/api/auth/redirect.js +++ b/pages/api/auth/redirect.js @@ -2,13 +2,17 @@ import { SN_MAIN_DOMAIN } from '@/lib/domains' // TODO: experimental, middleware proxy can't redirect to absolute MAIN DOMAIN URLs in local dev export default async function handler (req, res) { - const { domain } = req.query + const { domain, signup, callbackUrl } = req.query if (!domain) { return res.status(400).json({ status: 'ERROR', reason: 'domain is required' }) } - const redirectUrl = new URL('/login', SN_MAIN_DOMAIN) + const redirectPath = signup ? '/signup' : '/login' + const redirectUrl = new URL(redirectPath, SN_MAIN_DOMAIN) redirectUrl.searchParams.set('domain', domain) + if (callbackUrl) { + redirectUrl.searchParams.set('callbackUrl', callbackUrl) + } res.redirect(302, redirectUrl.href) } diff --git a/proxy.js b/proxy.js index 89e2c785b..c8339c187 100644 --- a/proxy.js +++ b/proxy.js @@ -41,9 +41,9 @@ async function customDomainMiddleware (request, domain, subName) { // Auth Sync if (pathname.startsWith('/login') || pathname.startsWith('/signup')) { const signup = pathname.startsWith('/signup') - return redirectToAuthSync(searchParams, domain, signup, headers) + return redirectToAuth(searchParams, domain, signup, headers) } - if (searchParams.has('sync_token')) return establishAuthSync(request, searchParams, domain, headers) + if (searchParams.has('sync_token')) return syncAccount(request, searchParams, domain, headers) // clean up the pathname from any subname if (pathname.startsWith('/~')) { @@ -76,26 +76,22 @@ async function customDomainMiddleware (request, domain, subName) { return NextResponse.next({ request: { headers } }) } -async function redirectToAuthSync (searchParams, domain, signup, headers) { - const syncUrl = new URL('/api/auth/redirect', SN_MAIN_DOMAIN) - syncUrl.searchParams.set('domain', domain) +async function redirectToAuth (searchParams, domain, signup, headers) { + const loginUrl = new URL('/api/auth/redirect', SN_MAIN_DOMAIN) + loginUrl.searchParams.set('domain', domain) if (signup) { - syncUrl.searchParams.set('signup', 'true') + loginUrl.searchParams.set('signup', 'true') } if (searchParams.has('callbackUrl')) { - const callbackUrl = searchParams.get('callbackUrl') - const redirectUri = callbackUrl.startsWith('http') - ? new URL(callbackUrl).pathname - : callbackUrl - syncUrl.searchParams.set('redirectUri', redirectUri) + loginUrl.searchParams.set('callbackUrl', searchParams.get('callbackUrl')) } - return NextResponse.redirect(syncUrl, { headers }) + return NextResponse.redirect(loginUrl, { headers }) } -async function establishAuthSync (request, searchParams, domain, headers) { +async function syncAccount (request, searchParams, domain, headers) { const token = searchParams.get('sync_token') const redirectUri = searchParams.get('redirectUri') || '/' const res = NextResponse.redirect(new URL(redirectUri, request.url), { headers }) From 19008fce5ed1b981cc0ffed418dc089039aa7795 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 23 Apr 2026 20:02:17 +0200 Subject: [PATCH 149/154] fix signup, authRequired redirects and callbackUrl --- api/ssrApollo.js | 4 +++- components/login.js | 1 - pages/login.js | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/ssrApollo.js b/api/ssrApollo.js index 9280294f7..7bd56ff75 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -188,7 +188,9 @@ export function getGetServerSideProps ( } if (authRequired && !me) { - let callback = process.env.NEXT_PUBLIC_URL + req.url + // if we're on a custom domain, use the domain header instead of the main domain + const origin = domain ? req.headers['x-stacker-news-domain'] : process.env.NEXT_PUBLIC_URL + let callback = origin + req.url // On client-side routing, the callback is a NextJS URL // so we need to remove the NextJS stuff. // Example: /_next/data/development/territory.json diff --git a/components/login.js b/components/login.js index 535ccda9f..25b1dc26e 100644 --- a/components/login.js +++ b/components/login.js @@ -131,7 +131,6 @@ export default function Login ({ providers, callbackUrl, multiAuth, error, text, { - const callbackUrl = router.query.callbackUrl const redirectUri = callbackUrl?.startsWith('http') ? new URL(callbackUrl).pathname : callbackUrl || '/' diff --git a/pages/login.js b/pages/login.js index 3fc3ce26c..0b14b6c97 100644 --- a/pages/login.js +++ b/pages/login.js @@ -18,6 +18,12 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult session = null } + // the ?domain= query param carries the custom domain's host as-is (with its port in local dev). + // we pass the port alongside the mapping so the client can redirect back through /api/auth/sync + const mapping = domain ? await getDomainMapping(domain) : null + const { domainPort } = domain ? normalizeDomain(domain) : { domainPort: null } + const domainData = mapping ? { ...mapping, port: domainPort } : null + // prevent open redirects. See https://github.com/stackernews/stacker.news/issues/264 // let undefined urls through without redirect ... otherwise this interferes with multiple auth linking let external = true @@ -27,7 +33,7 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult console.error('error decoding callback:', callbackUrl, err) } - if (external) { + if (external && !domainData) { callbackUrl = '/' } @@ -48,12 +54,6 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult } } - // the ?domain= query param carries the custom domain's host as-is (with its port in local dev). - // we pass the port alongside the mapping so the client can redirect back through /api/auth/sync - const mapping = domain ? await getDomainMapping(domain) : null - const { domainPort } = domain ? normalizeDomain(domain) : { domainPort: null } - const domainData = mapping ? { ...mapping, port: domainPort } : null - const providers = await getProviders() return { From 043586102401e6cf9ea74061f5c1b5f4063c7fb4 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Thu, 23 Apr 2026 20:22:53 +0200 Subject: [PATCH 150/154] redirect to custom domain auth sync on login/signup via nextauth --- pages/api/auth/[...nextauth].js | 24 ++++++++++++++++++++++++ pages/login.js | 11 +++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 25c744255..cd6326505 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -164,6 +164,30 @@ function getCallbacks (req, res) { session.user.id = token.id return session + }, + // allow absolute callback URLs that point at an active custom domain. + async redirect ({ url, baseUrl }) { + if (url.startsWith('/')) return `${baseUrl}${url}` + + try { + const parsed = new URL(url) + if (parsed.origin === baseUrl) return url + + // redirect to the auth sync endpoint if on custom domain + // TODO: handle multi auth + const { domainName, domainPort } = normalizeDomain(parsed.host) + const mapping = await getDomainMapping(domainName) + if (mapping) { + const syncUrl = new URL('/api/auth/sync', baseUrl) + syncUrl.searchParams.set('domain', domainPort ? `${domainName}:${domainPort}` : domainName) + syncUrl.searchParams.set('redirectUri', (parsed.pathname || '/') + parsed.search + parsed.hash) + return syncUrl.href + } + } catch (error) { + console.error('[nextauth redirect] invalid callback URL', url, error) + } + + return baseUrl } } } diff --git a/pages/login.js b/pages/login.js index 0b14b6c97..04e04d072 100644 --- a/pages/login.js +++ b/pages/login.js @@ -27,13 +27,20 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult // prevent open redirects. See https://github.com/stackernews/stacker.news/issues/264 // let undefined urls through without redirect ... otherwise this interferes with multiple auth linking let external = true + let callbackHost = null try { - external = isExternal(decodeURIComponent(callbackUrl)) + const decoded = decodeURIComponent(callbackUrl) + external = isExternal(decoded) + if (external) callbackHost = new URL(decoded).host } catch (err) { console.error('error decoding callback:', callbackUrl, err) } - if (external && !domainData) { + // external callbackUrls are only allowed when they point at the custom domain + // we're syncing against (domainData). anything else is reset to avoid open redirects. + const matchesDomain = callbackHost && domainData && + normalizeDomain(callbackHost).domainName === domainData.domainName + if (external && !matchesDomain) { callbackUrl = '/' } From b896297f398911459ccab1313b8a84437ad99cf2 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Mon, 27 Apr 2026 14:50:16 +0200 Subject: [PATCH 151/154] reminder: unify with nextauth custom redirect --- components/login.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/login.js b/components/login.js index 25b1dc26e..c5b628e52 100644 --- a/components/login.js +++ b/components/login.js @@ -131,6 +131,7 @@ export default function Login ({ providers, callbackUrl, multiAuth, error, text, { + // TODO: unify with nextauth custom redirect const redirectUri = callbackUrl?.startsWith('http') ? new URL(callbackUrl).pathname : callbackUrl || '/' From f6908318a4220b8b2152e40c819e9dd50fa5ebd1 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 28 Apr 2026 13:55:54 +0200 Subject: [PATCH 152/154] better login with nym button, dropdown to select an account before syncing --- components/login-button.js | 59 +++++++++++++++++++++++++++++++------- components/login.js | 2 +- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/components/login-button.js b/components/login-button.js index 66f772eec..9ce080f99 100644 --- a/components/login-button.js +++ b/components/login-button.js @@ -4,9 +4,13 @@ import LightningIcon from '@/svgs/bolt.svg' import NostrIcon from '@/svgs/nostr.svg' import Button from 'react-bootstrap/Button' import useCookie from './use-cookie' -import { MULTI_AUTH_POINTER } from '@/lib/auth' +import { cookieOptions, MULTI_AUTH_POINTER } from '@/lib/auth' import { useAccounts } from './account' import SNIcon from '@/svgs/sn.svg' +import { ButtonGroup, Dropdown } from 'react-bootstrap' +import styles from '@/lib/lexical/theme/editor.module.css' +import ArrowDownIcon from '@/svgs/editor/toolbar/arrow-down.svg' +import classNames from 'classnames' export default function LoginButton ({ text, type, className, onClick, disabled }) { let Icon, variant @@ -43,22 +47,55 @@ export default function LoginButton ({ text, type, className, onClick, disabled ) } -// TODO: better design -// TODO: implement multi-account support with a dropdown +// TODO: it's maybe better to select an account and give it to the sync endpoint instead of switching accounts here export function LoginWithNymButton ({ className, onClick, disabled }) { const accounts = useAccounts() - const [pointerCookie] = useCookie(MULTI_AUTH_POINTER) + const [pointerCookie, setPointerCookie] = useCookie(MULTI_AUTH_POINTER) const account = accounts.find(account => account.id === Number(pointerCookie)) - if (!account) return null - return ( - ) + + if (accounts.length === 1) return mainButton + + return ( + + {mainButton} + { e.preventDefault(); e.stopPropagation() }} + title='select account' + style={{ maxWidth: '42px' }} + > + + + + {accounts.map(account => ( + setPointerCookie(account.id, cookieOptions({ httpOnly: false }))} + className={classNames(styles.dropdownExtraItem, Number(account.id) === Number(pointerCookie) && styles.active)} + > + {account.name} + + ))} + + + ) } diff --git a/components/login.js b/components/login.js index c5b628e52..15bf48dea 100644 --- a/components/login.js +++ b/components/login.js @@ -129,7 +129,7 @@ export default function Login ({ providers, callbackUrl, multiAuth, error, text, {/** custom domain auth sync button */} {domainData && ( { // TODO: unify with nextauth custom redirect const redirectUri = callbackUrl?.startsWith('http') From 104272fcb7e2bf0e0c15da3419f53b9ce904a2c7 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 28 Apr 2026 13:57:19 +0200 Subject: [PATCH 153/154] respect the rules of hooks --- components/account.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/account.js b/components/account.js index cf39f22d1..e5008031b 100644 --- a/components/account.js +++ b/components/account.js @@ -94,11 +94,12 @@ const AccountListRow = ({ account, selected, ...props }) => { } export const useIsLurker = () => { - // TODO, signup button for lurkers conflicts with one-click-login path + const accounts = useAccounts() const { domain } = useDomain() + + // TODO, signup button for lurkers conflicts with one-click-login path if (domain) return false - const accounts = useAccounts() return accounts.length === 0 } From dcf319234b472b8d2ef30e257f79416682aea0f6 Mon Sep 17 00:00:00 2001 From: Soxasora Date: Tue, 28 Apr 2026 14:12:30 +0200 Subject: [PATCH 154/154] protect redirectUri from hosting an open redirect --- lib/url.js | 15 +++++++++++++++ pages/api/auth/sync.js | 3 ++- proxy.js | 4 +++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/url.js b/lib/url.js index 9b1c7c8f7..feae3a3c1 100644 --- a/lib/url.js +++ b/lib/url.js @@ -34,6 +34,21 @@ export function isHashLink (url) { return url?.startsWith('#') } +// validates that a redirect URI is a same-origin path and cannot escape to +// another origin via protocol-relative URLs ("//evil.com") +export function isSafeRedirectPath (uri) { + if (typeof uri !== 'string' || uri.length === 0) return false + if (uri[0] !== '/') return false + if (uri[1] === '/' || uri[1] === '\\') return false + try { + // arbitrarily resolve against the main domain. if the origin changes, it's unsafe + const base = process.env.NEXT_PUBLIC_URL + return new URL(uri, base).origin === base + } catch { + return false + } +} + export function isInternalLink (url) { return isHashLink(url) || !isExternal(url) } diff --git a/pages/api/auth/sync.js b/pages/api/auth/sync.js index e0569773b..859fc5e7c 100644 --- a/pages/api/auth/sync.js +++ b/pages/api/auth/sync.js @@ -3,6 +3,7 @@ import { randomBytes } from 'node:crypto' import { encode as encodeJWT, getToken } from 'next-auth/jwt' import { validateSchema, customDomainSchema } from '@/lib/validate' import { SN_MAIN_DOMAIN, normalizeDomain } from '@/lib/domains' +import { isSafeRedirectPath } from '@/lib/url' const SYNC_TOKEN_MAX_AGE = 60 * 5 // 5 minutes const VERIFICATION_TOKEN_EXPIRY = 1000 * 60 * 5 // 5 minutes in milliseconds @@ -30,7 +31,7 @@ export default async function handler (req, res) { if (req.method === 'GET') { const { domain, redirectUri = '/', signup } = req.query - if (!domain || !redirectUri?.startsWith('/')) { + if (!domain || !isSafeRedirectPath(redirectUri)) { return res.status(400).json({ status: 'ERROR', reason: 'domain and a correct redirectUri are required' }) } diff --git a/proxy.js b/proxy.js index e001fef8a..39b40193d 100644 --- a/proxy.js +++ b/proxy.js @@ -2,6 +2,7 @@ import 'urlpattern-polyfill' import { NextRequest, NextResponse } from 'next/server' import { SESSION_COOKIE, cookieOptions } from '@/lib/auth' import { getDomainMapping, createDomainsDebugLogger, SN_MAIN_DOMAIN, normalizeDomain } from '@/lib/domains' +import { isSafeRedirectPath } from '@/lib/url' const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+){/:other(\\w+)}?' }) @@ -86,7 +87,8 @@ async function redirectToAuth (searchParams, domain, signup) { async function syncAccount (request, searchParams, domain, headers) { const token = searchParams.get('sync_token') - const redirectUri = searchParams.get('redirectUri') || '/' + const rawRedirectUri = searchParams.get('redirectUri') + const redirectUri = isSafeRedirectPath(rawRedirectUri) ? rawRedirectUri : '/' const res = NextResponse.redirect(new URL(redirectUri, request.url)) const { domainName } = normalizeDomain(domain)