diff --git a/.env b/.env
new file mode 100644
index 0000000..ce8cf55
--- /dev/null
+++ b/.env
@@ -0,0 +1,3 @@
+# Each demo has separate origin due to relying party restrictions (https://webauthn.wtf/how-it-works/relying-party)
+NEXT_PUBLIC_DEFAULT_EXAMPLE_ORIGIN="http://localhost:3000"
+NEXT_PUBLIC_UPGRADE_EXAMPLE_ORIGIN="http://localhost:3001"
\ No newline at end of file
diff --git a/.github/workflows/preview-common.yaml b/.github/workflows/preview-common.yaml
new file mode 100644
index 0000000..bbd1766
--- /dev/null
+++ b/.github/workflows/preview-common.yaml
@@ -0,0 +1,44 @@
+name: Reusable Preview Workflow
+
+on:
+ workflow_call:
+ inputs:
+ vercel_project_name:
+ required: true
+ type: string
+ vercel_scope:
+ required: true
+ type: string
+
+ secrets:
+ VERCEL_TOKEN:
+ required: true
+ SENTRY_AUTH_TOKEN:
+ required: true
+
+jobs:
+ shared-steps:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Enable Corepack
+ run: corepack enable
+
+ - name: Install Corepack
+ run: corepack install
+
+ - name: Link Project to Vercel
+ run: yarn dlx -q vercel link --project=${{ inputs.vercel_project_name }} --scope=${{ inputs.vercel_scope }} --yes --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Pull Vercel Environment Information
+ run: yarn dlx -q vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Build Project Artifacts
+ run: yarn dlx -q vercel build --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Deploy Project Artifacts to Vercel
+ run: yarn dlx -q vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml
index 9e5d987..6905422 100644
--- a/.github/workflows/preview.yaml
+++ b/.github/workflows/preview.yaml
@@ -1,37 +1,21 @@
name: Vercel Preview Deployment
-env:
- VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
- VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
- VERCEL_SCOPE: ${{ secrets.VERCEL_SCOPE }}
- VERCEL_PROJECT_NAME: ${{ secrets.VERCEL_PROJECT_NAME }}
+
on:
push:
branches:
- dev
-jobs:
- Deploy-Preview:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - name: Enable Corepack
- run: corepack enable
-
- - name: Install Corepack
- run: corepack install
- - name: Link Project to Vercel
- run: yarn dlx -q vercel link --project=$VERCEL_PROJECT_NAME --scope=$VERCEL_SCOPE --yes --token=$VERCEL_TOKEN
-
- - name: Pull Vercel Environment Information
- run: yarn dlx -q vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
-
- - name: Build Project Artifacts
- run: yarn dlx -q vercel build --token=${{ secrets.VERCEL_TOKEN }}
+jobs:
+ default-example:
+ uses: ./.github/workflows/preview-common.yaml
+ secrets: inherit
+ with:
+ vercel_project_name: ${{ vars.VERCEL_PROJECT_DEFAULT }}
+ vercel_scope: ${{ vars.VERCEL_SCOPE }}
- - name: Deploy Project Artifacts to Vercel
- run: yarn dlx -q vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
+ upgrade-example:
+ uses: ./.github/workflows/preview-common.yaml
+ secrets: inherit
+ with:
+ vercel_project_name: ${{ vars.VERCEL_PROJECT_UPGRADE }}
+ vercel_scope: ${{ vars.VERCEL_SCOPE }}
diff --git a/.github/workflows/production-common.yaml b/.github/workflows/production-common.yaml
new file mode 100644
index 0000000..4d255fd
--- /dev/null
+++ b/.github/workflows/production-common.yaml
@@ -0,0 +1,44 @@
+name: Reusable Production Workflow
+
+on:
+ workflow_call:
+ inputs:
+ vercel_project_name:
+ required: true
+ type: string
+ vercel_scope:
+ required: true
+ type: string
+
+ secrets:
+ VERCEL_TOKEN:
+ required: true
+ SENTRY_AUTH_TOKEN:
+ required: true
+
+jobs:
+ shared-steps:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Enable Corepack
+ run: corepack enable
+
+ - name: Install Corepack
+ run: corepack install
+
+ - name: Link Project to Vercel
+ run: yarn dlx -q vercel link --project=${{ inputs.vercel_project_name }} --scope=${{ inputs.vercel_scope }} --yes --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Pull Vercel Environment Information
+ run: yarn dlx -q vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Build Project Artifacts
+ run: yarn dlx -q vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
+
+ - name: Deploy Project Artifacts to Vercel
+ run: yarn dlx -q vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml
index 15fa5ac..d8d091c 100644
--- a/.github/workflows/production.yaml
+++ b/.github/workflows/production.yaml
@@ -1,37 +1,20 @@
name: Vercel Production Deployment
-env:
- VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
- VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
- VERCEL_SCOPE: ${{ secrets.VERCEL_SCOPE }}
- VERCEL_PROJECT_NAME: ${{ secrets.VERCEL_PROJECT_NAME }}
+
on:
push:
branches:
- main
-jobs:
- Deploy-Preview:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - name: Enable Corepack
- run: corepack enable
-
- - name: Install Corepack
- run: corepack install
- - name: Link Project to Vercel
- run: yarn dlx -q vercel link --project=$VERCEL_PROJECT_NAME --scope=$VERCEL_SCOPE --yes --token=$VERCEL_TOKEN
-
- - name: Pull Vercel Environment Information
- run: yarn dlx -q vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
-
- - name: Build Project Artifacts
- run: yarn dlx -q vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
+jobs:
+ default-example:
+ uses: ./.github/workflows/production-common.yaml
+ secrets: inherit
+ with:
+ vercel_project_name: ${{ vars.VERCEL_PROJECT_DEFAULT }}
+ vercel_scope: ${{ vars.VERCEL_SCOPE }}
- - name: Deploy Project Artifacts to Vercel
- run: yarn dlx -q vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
+ upgrade-example:
+ uses: ./.github/workflows/production-common.yaml
+ with:
+ vercel_project_name: ${{ secrets.VERCEL_PROJECT_UPGRADE }}
+ vercel_scope: ${{ vars.VERCEL_SCOPE }}
diff --git a/.nvmrc b/.nvmrc
index c130222..fdb2eaa 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20.18.0
\ No newline at end of file
+22.11.0
\ No newline at end of file
diff --git a/.yarnrc.yml b/.yarnrc.yml
index 1517771..9da7e23 100644
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -1,6 +1,6 @@
compressionLevel: 0
-defaultSemverRangePrefix: ""
+defaultSemverRangePrefix: ''
enableHardenedMode: true
@@ -17,3 +17,5 @@ nmSelfReferences: false
nodeLinker: node-modules
preferInteractive: true
+
+nmMode: hardlinks-global
diff --git a/README.md b/README.md
index 9a7a7f6..5f7bc4d 100644
--- a/README.md
+++ b/README.md
@@ -2,29 +2,37 @@
# With WebAuthn
-A repository with full stack WebAuthn API examples.
+A repository with full stack **WebAuthn API** examples.
## Examples
-1. **[Default WebAuthn Example - Passkeys with SimpleWebAuthn & Firebase](examples/webauthn-default/README.md)**
+1. **[Authenticate with passkeys - Passkeys with SimpleWebAuthn & Firebase](examples/webauthn-default/README.md)**
+
- Creating (user registration), retrieving (user login), linking multiple, and removing passkeys.
- - Issuing a JWT token via Firebase Auth once user is authenticated.
- - Passkes are stored in Firebase Firestore.
- - Formatting and parsing of WebAuthn API request / responses done via SimpleWebAuthn library.
+ - Passkeys autofill.
+ - Formatting and parsing of WebAuthn API request / responses done via [SimpleWebAuthn](https://simplewebauthn.dev) library.
+ - Built with [Firebase Auth](https://firebase.google.com/docs/auth/admin/create-custom-tokens) and Firestore SDKs.
- 👉 [**Check out the demo**](https://with-webauthn.dev)
+2. **[Upgrade to passkeys – From email/password to passkeys with SimpleWebAuthn & Firebase](examples/webauthn-upgrade/README.md)**
+ - A user registers with traditional email/password and verifies their email afterwards.
+ - Then the user can link passkey/s and therefore upgrades to MFA.
+ - The user can downgrade to single-factor authentication by removing all their passkeys.
+ - Built with [SimpleWebAuthn](https://simplewebauthn.dev), [Firebase Auth](https://firebase.google.com/docs/auth/admin/create-custom-tokens) and Firestore SDKs.
+ - 👉 [**Check out the demo**](https://upgrade.with-webauthn.dev)
+
---
## Development
### Common Stack notes:
-- The whole project is managed using tuborepo and yarn workspaces.
+- The whole project is managed using tuborepo.
- All examples are in NextJS (React) framework.
- API calls are handled with React Tanstack query on client.
-- API endpoints are build via NextJS API routes.
+- API endpoints are built via NextJS API routes.
- Forms are built with react-hook-form and validated with zod schemas.
- Material UI with styled components as UI SDK.
@@ -46,8 +54,11 @@ A repository with full stack WebAuthn API examples.
yarn install --immutable
```
-3. Then continue with final steps for specific example:
- - [Passkeys with SimpleWebAuthn & Firebase](examples/webauthn-default/README.md)
+3. Note that common code of each example is placed in `packages/common` (for client and server).
+
+4. Then continue with final steps for specific example:
+ - [Authenticate with passkeys](examples/webauthn-default/README.md)
+ - [Upgrade to passkeys](examples/webauthn-upgrade/README.md)
## Have you a found a bug?
diff --git a/examples/webauthn-default/README.md b/examples/webauthn-default/README.md
index c546975..e4af1f4 100644
--- a/examples/webauthn-default/README.md
+++ b/examples/webauthn-default/README.md
@@ -1,4 +1,4 @@
-# Default WebAuthn Example - Passkeys with SimpleWebAuthn & Firebase
+# Authenticate with passkeys example - Passkeys with SimpleWebAuthn & Firebase
- Creating (user registration), retrieving (user login), linking multiple, and removing passkeys.
- Issuing a JWT token via Firebase Auth once user is authenticated.
@@ -32,4 +32,30 @@ Assuming you've already finished [those steps in the main README](../../README.m
5. Create a Firebase firestore database
-2. Run `yarn dev` and checkout `http://localhost:3000` URL.
+ - Don't forget to set security `Rules`:
+
+ ```
+ rules_version = '2';
+
+ service cloud.firestore {
+ match /databases/{database}/documents {
+ // Deny all access by default
+ match /{document=**} {
+ allow read, write: if false;
+ }
+
+ // Match for users collection
+ match /users/{uid} {
+ allow read: if request.auth != null && request.auth.uid == uid;
+ }
+
+ // Match for passkeys collection
+ match /passkeys/{passkeyId} {
+ allow read: if request.auth != null && resource.data.userId == request.auth.uid;
+ }
+ }
+ }
+ ```
+
+2. Run `yarn dev` in **root repository** and checkout `http://localhost:3000` URL.
+3. Hey mate, welcome to the WebAuthn world. 🙌
diff --git a/examples/webauthn-default/next.config.ts b/examples/webauthn-default/next.config.ts
index 9bf73c3..1b467eb 100755
--- a/examples/webauthn-default/next.config.ts
+++ b/examples/webauthn-default/next.config.ts
@@ -1,12 +1,12 @@
import type { NextConfig } from 'next';
-import { withSentryConfig } from '@sentry/nextjs';
import { config } from 'dotenv';
import type { dependencies } from 'package.json';
+import { withDefinedSentryConfig } from '@workspace/sentry/next-config';
+
if (process.env.NODE_ENV === 'development') {
- config({
- path: '.env.local',
- });
+ config({ path: '.env.local' });
+ config({ path: '../../.env' });
}
type Dependency = keyof typeof dependencies;
@@ -28,42 +28,4 @@ const nextConfig: NextConfig = {
// ensure that your source maps include changes from all other Webpack plugins
export default process.env.NEXT_PUBLIC_DEV_SENTRY_DISABLED === 'true'
? nextConfig
- : withSentryConfig(nextConfig, {
- // For all available options, see:
- // https://github.com/getsentry/sentry-webpack-plugin#options
-
- org: process.env.SENTRY_ORG,
- project: process.env.SENTRY_PROJECT,
-
- // Only print logs for uploading source maps in CI
- silent: !process.env.CI,
-
- // For all available options, see:
- // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
-
- // Upload a larger set of source maps for prettier stack traces (increases build time)
- widenClientFileUpload: true,
-
- // Automatically annotate React components to show their full name in breadcrumbs and session replay
- reactComponentAnnotation: {
- enabled: true,
- },
-
- // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
- // This can increase your server load as well as your hosting bill.
- // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
- // side errors will fail.
- // tunnelRoute: "/monitoring",
-
- // Hides source maps from generated client bundles
- hideSourceMaps: true,
-
- // Automatically tree-shake Sentry logger statements to reduce bundle size
- disableLogger: true,
-
- // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
- // See the following for more information:
- // https://docs.sentry.io/product/crons/
- // https://vercel.com/docs/cron-jobs
- automaticVercelMonitors: true,
- });
+ : withDefinedSentryConfig(nextConfig);
diff --git a/examples/webauthn-default/package.json b/examples/webauthn-default/package.json
index aa595c5..d1f3496 100644
--- a/examples/webauthn-default/package.json
+++ b/examples/webauthn-default/package.json
@@ -3,9 +3,10 @@
"version": "0.0.1",
"type": "module",
"scripts": {
- "dev": "next dev --turbopack",
+ "types-check": "tsc --noEmit -w",
+ "dev": "next dev --turbopack -p 3000 & yarn types-check",
"build": "next build",
- "start": "next start",
+ "start": "next start -p 3000",
"lint": "eslint-lint --config=eslint.config.mjs ./src/**/*.{ts,tsx}",
"lint:fix": "yarn lint --fix",
"format": "prettier-format",
@@ -13,9 +14,11 @@
},
"dependencies": {
"@workspace/common": "workspace:*",
+ "@workspace/sentry": "workspace:*",
"next": "15.0.3",
"react": "18.3.1",
- "react-dom": "18.3.1"
+ "react-dom": "18.3.1",
+ "typescript": "5.7.2"
},
"devDependencies": {
"@tooling/eslint": "workspace:*",
diff --git a/examples/webauthn-default/sentry.client.config.ts b/examples/webauthn-default/sentry.client.config.ts
index 0ca6934..59a6448 100644
--- a/examples/webauthn-default/sentry.client.config.ts
+++ b/examples/webauthn-default/sentry.client.config.ts
@@ -2,29 +2,9 @@
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
-import * as Sentry from '@sentry/nextjs';
-
import { env } from '@workspace/common/client/env';
+import { initSentryForClient } from '@workspace/sentry/client';
-if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true') {
- Sentry.init({
- dsn: env.NEXT_PUBLIC_SENTRY_DSN,
-
- // Add optional integrations for additional features
- integrations: [Sentry.replayIntegration()],
-
- // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
- tracesSampleRate: 1,
-
- // Define how likely Replay events are sampled.
- // This sets the sample rate to be 10%. You may want this to be 100% while
- // in development and sample at a lower rate in production
- replaysSessionSampleRate: 0.1,
-
- // Define how likely Replay events are sampled when an error occurs.
- replaysOnErrorSampleRate: 1.0,
-
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
- });
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForClient(env.NEXT_PUBLIC_SENTRY_DSN);
}
diff --git a/examples/webauthn-default/sentry.edge.config.ts b/examples/webauthn-default/sentry.edge.config.ts
index ec28548..2d21a63 100644
--- a/examples/webauthn-default/sentry.edge.config.ts
+++ b/examples/webauthn-default/sentry.edge.config.ts
@@ -3,18 +3,9 @@
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
-import * as Sentry from '@sentry/nextjs';
-
import { env } from '@workspace/common/client/env';
+import { initSentryForEdge } from '@workspace/sentry/edge';
-if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true') {
- Sentry.init({
- dsn: env.NEXT_PUBLIC_SENTRY_DSN,
-
- // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
- tracesSampleRate: 1,
-
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
- });
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForEdge(env.NEXT_PUBLIC_SENTRY_DSN);
}
diff --git a/examples/webauthn-default/sentry.server.config.ts b/examples/webauthn-default/sentry.server.config.ts
index e2141fa..c0ebb5f 100644
--- a/examples/webauthn-default/sentry.server.config.ts
+++ b/examples/webauthn-default/sentry.server.config.ts
@@ -2,18 +2,9 @@
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
-import * as Sentry from '@sentry/nextjs';
-
import { env } from '@workspace/common/client/env';
+import { initSentryForServer } from '@workspace/sentry/server';
-if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true') {
- Sentry.init({
- dsn: env.NEXT_PUBLIC_SENTRY_DSN,
-
- // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
- tracesSampleRate: 1,
-
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
- });
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForServer(env.NEXT_PUBLIC_SENTRY_DSN);
}
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExample.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExample.tsx
index 6b81ea0..14addfc 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExample.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExample.tsx
@@ -2,8 +2,9 @@ import { useState } from 'react';
import { ExampleAuth, ExampleBody, ExampleFrame } from '@workspace/common/client/example/components';
-import { CurrentExampleRoute, DefaultExampleRouter } from './DefaultExampleRouter';
import { DefaultExampleTopBar } from './DefaultExampleTopBar';
+import { ResolveInitRoute } from './ResolveInitRoute';
+import { CurrentExampleRoute, ExampleRouter } from './router';
import { exampleRoutes } from './routes';
export const DefaultExample = () => {
@@ -11,15 +12,17 @@ export const DefaultExample = () => {
return (
-
-
+
+
+
+
-
-
+
+
);
};
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/DefaultExampleRouter.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/DefaultExampleRouter.tsx
deleted file mode 100644
index 3d7b6f0..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/DefaultExampleRouter.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { ReactNode } from 'react';
-
-import { createExampleRouter, useExampleAuthSession } from '@workspace/common/client/example/components';
-
-import type { ExampleRoutes } from '../routes';
-
-const { ExampleRouter, useExampleRouter, CurrentExampleRoute } = createExampleRouter
();
-
-export interface DefaultExampleRouterProps {
- children: ReactNode;
- routes: ExampleRoutes;
-}
-
-/**
- * Yes, this not so smart solution but it's just an example, so please focus on the WebAutn part. Thanks.
- */
-export const DefaultExampleRouter = ({ children, routes }: DefaultExampleRouterProps) => {
- const { session } = useExampleAuthSession();
-
- return (
-
- {children}
-
- );
-};
-
-export { CurrentExampleRoute, useExampleRouter };
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/index.ts
deleted file mode 100644
index 52f6941..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleRouter/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './DefaultExampleRouter';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx
index caf0833..25a6cfb 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx
@@ -1,6 +1,6 @@
import { ExampleTopBar, type ExampleTopBarProps } from '@workspace/common/client/example/components';
-import { useExampleRouter } from '../DefaultExampleRouter';
+import { useExampleRouter } from '../router';
import { useExampleRouteTitle } from './hooks/useExampleRouteTitle';
export interface DefaultExampleTopBarProps extends Pick {}
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts
index fb8ec07..c572539 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts
@@ -1,6 +1,6 @@
import { useExampleAuthSession } from '@workspace/common/client/example/components';
-import { useExampleRouter } from '../../DefaultExampleRouter';
+import { useExampleRouter } from '../../router';
import type { ExampleRoute } from '../../routes';
const routeTitles = {
@@ -13,7 +13,7 @@ export function useExampleRouteTitle() {
const { session } = useExampleAuthSession();
const { currentRoute } = useExampleRouter();
- if (session.state === 'loading') {
+ if (session.state === 'loading' || !currentRoute) {
return 'Loading...';
}
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/index.ts
deleted file mode 100644
index c8bdfba..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './LoginWithPasskey';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/LoginWithPasskey.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx
similarity index 97%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/LoginWithPasskey.tsx
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx
index e3d7c34..008be0a 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/LoginWithPasskey.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx
@@ -7,12 +7,12 @@ import { Alert, Box, Button, Divider, Words } from '@workspace/common/client/ui-
import { Fingerprint, InfoOutlined } from '@workspace/common/client/ui-kit/icons';
import { AuthFormContainer } from '../AuthFormContainer';
-import { useExampleRouter } from '../DefaultExampleRouter';
+import { useExampleRouter } from '../router';
import { useConditionalMediation } from './hooks/useConditionalMediation';
import { useLoginWithPasskey } from './hooks/useLoginWithPasskey';
import { loginFormSchema, type LoginFormSchema, type LoginFormValues } from './schema';
-export const LoginWithPasskey = () => {
+export const LoginWithPasskeyPage = () => {
const conditionalMediation = useConditionalMediation();
const loginWithPasskey = useLoginWithPasskey();
const { redirect } = useExampleRouter();
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useConditionalMediation.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useConditionalMediation.ts
similarity index 92%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useConditionalMediation.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useConditionalMediation.ts
index 1ed8c90..a41dcf9 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useConditionalMediation.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useConditionalMediation.ts
@@ -3,15 +3,15 @@ import { useQuery } from '@tanstack/react-query';
import { signInWithCustomToken } from 'firebase/auth';
import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
import { auth } from '@workspace/common/client/firebase/config';
import { useSnack } from '@workspace/common/client/snackbar/hooks';
-import { parseWebAuthnError } from '@workspace/common/client/webauthn/utils';
import { logger } from '@workspace/common/logger';
import type { StartLoginResponseData } from '~pages/api/webauthn/login/options';
import type { VerifyLoginRequestData, VerifyLoginResponseData } from '~pages/api/webauthn/login/verify';
-import { useExampleRouter } from '../../DefaultExampleRouter';
+import { useExampleRouter } from '../../router';
export function useConditionalMediation() {
const { redirect } = useExampleRouter();
@@ -61,7 +61,7 @@ export function useConditionalMediation() {
return true;
} catch (error) {
- const parsedError = await parseWebAuthnError(error);
+ const parsedError = await parseUnknownError(error);
if (parsedError.type !== 'ABORT_ERROR') {
snack('error', parsedError.message);
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useLoginWithPasskey.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts
similarity index 88%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useLoginWithPasskey.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts
index a615a3f..5cc2961 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskey/hooks/useLoginWithPasskey.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts
@@ -2,15 +2,15 @@ import { startAuthentication } from '@simplewebauthn/browser';
import { signInWithCustomToken } from 'firebase/auth';
import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
import { auth } from '@workspace/common/client/firebase/config';
import type { FormProps } from '@workspace/common/client/form/components';
-import { parseWebAuthnError } from '@workspace/common/client/webauthn/utils';
import { logger } from '@workspace/common/logger';
import type { StartLoginRequestData, StartLoginResponseData } from '~pages/api/webauthn/login/options';
import type { VerifyLoginRequestData, VerifyLoginResponseData } from '~pages/api/webauthn/login/verify';
-import { useExampleRouter } from '../../DefaultExampleRouter';
+import { useExampleRouter } from '../../router';
import type { LoginFormSchema, LoginFormValues } from '../schema';
export function useLoginWithPasskey(): FormProps['onSubmit'] {
@@ -32,7 +32,7 @@ export function useLoginWithPasskey(): FormProps({
method: 'POST',
@@ -50,7 +50,7 @@ export function useLoginWithPasskey(): FormProps ({
- marginBottom: theme.spacing(2),
- marginTop: theme.spacing(2),
- display: 'grid',
- justifyContent: 'space-between',
- alignItems: 'center',
- gridTemplateColumns: 'auto auto',
-}));
-
-export const PasskeysList = styled('section')(({ theme }) => ({
- display: 'grid',
- rowGap: theme.spacing(3.5),
-}));
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/Passkeys.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/Passkeys.tsx
deleted file mode 100644
index bafdbfa..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/Passkeys.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { QueryError, QueryLoader } from '@workspace/common/client/api/components';
-import { useDialog } from '@workspace/common/client/dialog/hooks';
-import { Passkey } from '@workspace/common/client/passkeys/components';
-import { Button, Words } from '@workspace/common/client/ui-kit';
-import { Fingerprint } from '@workspace/common/client/ui-kit/icons';
-
-import { useAddPasskey } from './hooks/useAddPasskey';
-import { useFetchPasskeys } from './hooks/useFetchPasskeys';
-import { useRemovePasskey } from './hooks/useRemovePasskey';
-import { PasskeysHeader, PasskeysList } from './Passkeys.styles';
-import { PostRemovalDialog, type PostRemovalDialogProps } from './PostRemovalDialog';
-
-export const Passkeys = () => {
- const passkeysResult = useFetchPasskeys();
- const addPasskey = useAddPasskey();
- const postRemovalDialog = useDialog();
- const removePasskey = useRemovePasskey(postRemovalDialog.openDialog);
-
- return (
- <>
-
- Passkeys
-
-
-
-
-
- {passkeysResult.data.map(passkey => (
- 1}
- removePasskey={removePasskey}
- />
- ))}
-
-
-
-
- >
- );
-};
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useFetchPasskeys.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useFetchPasskeys.ts
deleted file mode 100644
index b7d7d1d..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useFetchPasskeys.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-
-import { useAuthUser } from '@workspace/common/client/example/components';
-import { fetchUserPasskeys } from '@workspace/common/client/firebase/services/passkeys';
-
-export function useFetchPasskeys() {
- const authUser = useAuthUser();
- const uid = authUser!.uid;
-
- return useQuery({
- queryKey: ['passkeys', uid],
- queryFn: () => fetchUserPasskeys(uid),
- initialData: [],
- });
-}
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/index.ts
deleted file mode 100644
index c16da17..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './Passkeys';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/PasskeysPage.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/PasskeysPage.tsx
new file mode 100644
index 0000000..2471f9c
--- /dev/null
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/PasskeysPage.tsx
@@ -0,0 +1,28 @@
+import { useDialog } from '@workspace/common/client/dialog/hooks';
+import {
+ PasskeysHeader,
+ PasskeysList,
+ PostRemovalDialog,
+ type PostRemovalDialogProps,
+} from '@workspace/common/client/passkeys/components';
+
+import { useAddPasskey } from './hooks/useAddPasskey';
+import { useRemovePasskey } from './hooks/useRemovePasskey';
+
+export const PasskeysPage = () => {
+ const addPasskey = useAddPasskey();
+ const postRemovalDialog = useDialog();
+ const removePasskey = useRemovePasskey(postRemovalDialog.openDialog);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useAddPasskey.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useAddPasskey.ts
similarity index 85%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useAddPasskey.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useAddPasskey.ts
index 8356a96..b686615 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useAddPasskey.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useAddPasskey.ts
@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import { queryClient } from '@workspace/common/client/api/components';
import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
import { useSnack } from '@workspace/common/client/snackbar/hooks';
import { logger } from '@workspace/common/logger';
@@ -40,8 +41,12 @@ export function useAddPasskey() {
queryKey: ['passkeys'],
});
},
- onError(error: Error) {
- snack('error', error.message);
+ async onError(error: Error) {
+ const parsedError = await parseUnknownError(error);
+
+ logger.error(parsedError);
+
+ snack('error', parsedError.message);
},
onSuccess() {
snack('success', 'Passkey has been successfully added.');
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useRemovePasskey.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useRemovePasskey.ts
similarity index 77%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useRemovePasskey.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useRemovePasskey.ts
index 81feda2..e1326de 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/hooks/useRemovePasskey.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/hooks/useRemovePasskey.ts
@@ -3,15 +3,14 @@ import { useMutation } from '@tanstack/react-query';
import { queryClient } from '@workspace/common/client/api/components';
import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
import { useAuthUser } from '@workspace/common/client/example/components';
+import type { PostRemovalDialogProps } from '@workspace/common/client/passkeys/components';
import { useSnack } from '@workspace/common/client/snackbar/hooks';
import { logger } from '@workspace/common/logger';
-import type { Passkey } from '@workspace/common/types';
import type { StartRemovalResponseData } from '~pages/api/webauthn/remove/options';
-import type { VerifyRemovalRequestData } from '~pages/api/webauthn/remove/verify';
-
-import type { PostRemovalDialogProps } from '../PostRemovalDialog';
+import type { VerifyRemovalRequestData, VerifyRemovalResponseData } from '~pages/api/webauthn/remove/verify';
/**
* Remove a passkey from the user's account. User must verify their identity before removing the passkey.
@@ -36,9 +35,11 @@ export function useRemovePasskey(openDialog: (data: PostRemovalDialogProps['data
optionsJSON: publicKeyOptions,
});
- logger.info('Authentication result:', result);
+ logger.info('WebAuthn API result:', result);
- const { data: passkey } = await fetcher({
+ const {
+ data: { passkey },
+ } = await fetcher({
method: 'POST',
url: '/webauthn/remove/verify',
body: {
@@ -59,7 +60,11 @@ export function useRemovePasskey(openDialog: (data: PostRemovalDialogProps['data
queryKey: ['passkeys'],
});
} catch (error) {
- snack('error', (error as Error).message);
+ const parsedError = await parseUnknownError(error);
+
+ snack('error', parsedError.message);
+
+ logger.error(error);
}
},
});
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/index.ts
new file mode 100644
index 0000000..1d1082a
--- /dev/null
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/PasskeysPage/index.ts
@@ -0,0 +1 @@
+export * from './PasskeysPage';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/index.ts
deleted file mode 100644
index 2b614c0..0000000
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './RegisterWithPasskey';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/RegisterWithPasskey.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/RegisterWithPasskeyPage.tsx
similarity index 94%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/RegisterWithPasskey.tsx
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/RegisterWithPasskeyPage.tsx
index ab3df46..5e4ded8 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/RegisterWithPasskey.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/RegisterWithPasskeyPage.tsx
@@ -3,11 +3,11 @@ import { Box, Button, Divider, Words } from '@workspace/common/client/ui-kit';
import { Fingerprint } from '@workspace/common/client/ui-kit/icons';
import { AuthFormContainer } from '../AuthFormContainer';
-import { useExampleRouter } from '../DefaultExampleRouter';
+import { useExampleRouter } from '../router';
import { useRegisterWithPasskey } from './hooks';
import { registerFormSchema, type RegisterFormSchema, type RegisterFormValues } from './schema';
-export const RegisterWithPasskey = () => {
+export const RegisterWithPasskeyPage = () => {
const registerPasskey = useRegisterWithPasskey();
const { redirect } = useExampleRouter();
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/hooks/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/hooks/index.ts
similarity index 100%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/hooks/index.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/hooks/index.ts
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/hooks/useRegisterWithPasskey.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/hooks/useRegisterWithPasskey.ts
similarity index 91%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/hooks/useRegisterWithPasskey.ts
rename to examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/hooks/useRegisterWithPasskey.ts
index a0acd4d..25ce1f2 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskey/hooks/useRegisterWithPasskey.ts
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/RegisterWithPasskeyPage/hooks/useRegisterWithPasskey.ts
@@ -2,9 +2,9 @@ import { startRegistration } from '@simplewebauthn/browser';
import { signInWithCustomToken } from 'firebase/auth';
import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
import { auth } from '@workspace/common/client/firebase/config';
import type { FormProps } from '@workspace/common/client/form/components';
-import { parseWebAuthnError } from '@workspace/common/client/webauthn/utils';
import { logger } from '@workspace/common/logger';
import type { StartRegistrationRequestData, StartRegistrationResponseData } from '~pages/api/webauthn/register/options';
@@ -13,7 +13,7 @@ import type {
VerifyRegistrationResponseData,
} from '~pages/api/webauthn/register/verify';
-import { useExampleRouter } from '../../DefaultExampleRouter';
+import { useExampleRouter } from '../../router';
import type { RegisterFormSchema, RegisterFormValues } from '../schema';
export function useRegisterWithPasskey(): FormProps['onSubmit'] {
@@ -53,7 +53,7 @@ export function useRegisterWithPasskey(): FormProps {
+ const { currentRoute, redirect } = useExampleRouter();
+ const { session } = useExampleAuthSession();
+
+ useEffect(() => {
+ if (currentRoute !== null) {
+ return;
+ }
+
+ switch (session.state) {
+ case 'authenticated':
+ redirect('/passkeys');
+ break;
+ case 'unauthenticated':
+ redirect('/register');
+ break;
+ }
+ }, [session, currentRoute, redirect]);
+
+ return null;
+};
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/ResolveInitRoute/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/ResolveInitRoute/index.ts
new file mode 100644
index 0000000..06c3fba
--- /dev/null
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/ResolveInitRoute/index.ts
@@ -0,0 +1 @@
+export * from './ResolveInitRoute';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/router/index.ts b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/router/index.ts
new file mode 100644
index 0000000..58d5b70
--- /dev/null
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/router/index.ts
@@ -0,0 +1,7 @@
+import { createExampleRouter } from '@workspace/common/client/example/components';
+
+import type { ExampleRoutes } from '../routes';
+
+const { ExampleRouter, useExampleRouter, CurrentExampleRoute } = createExampleRouter();
+
+export { CurrentExampleRoute, ExampleRouter, useExampleRouter };
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/routes/index.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/routes/index.tsx
index fca83ab..81492b1 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/routes/index.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/routes/index.tsx
@@ -1,13 +1,13 @@
import type { UnknownRoutes } from '@workspace/common/client/example/components';
-import { LoginWithPasskey } from '../LoginWithPasskey';
-import { Passkeys } from '../Passkeys';
-import { RegisterWithPasskey } from '../RegisterWithPasskey';
+import { LoginWithPasskeyPage } from '../LoginWithPasskeyPage';
+import { PasskeysPage } from '../PasskeysPage';
+import { RegisterWithPasskeyPage } from '../RegisterWithPasskeyPage';
export const exampleRoutes = {
- '/register': () => ,
- '/login': () => ,
- '/passkeys': () => ,
+ '/register': RegisterWithPasskeyPage,
+ '/login': LoginWithPasskeyPage,
+ '/passkeys': PasskeysPage,
} as const satisfies UnknownRoutes;
export type ExampleRoutes = typeof exampleRoutes;
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/WebAuthnDefaultExamplePage.tsx b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/WebAuthnDefaultExamplePage.tsx
index 40b32de..6052c51 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/WebAuthnDefaultExamplePage.tsx
+++ b/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/WebAuthnDefaultExamplePage.tsx
@@ -1,7 +1,7 @@
import Link from 'next/link';
import { ExampleDescription, ExampleHeader, ExampleWrapper } from '@workspace/common/client/example/components';
-import { MainHeader } from '@workspace/common/client/layout/components';
+import { PageHeader } from '@workspace/common/client/layout/components';
import { Container } from '@workspace/common/client/ui-kit';
import { DefaultExample } from './DefaultExample';
@@ -9,7 +9,7 @@ import { DefaultExample } from './DefaultExample';
export const WebAuthnDefaultExamplePage = () => {
return (
<>
-
+
@@ -32,6 +32,7 @@ export const WebAuthnDefaultExamplePage = () => {
]}
/>
}
+ githubUrl='https://github.com/cermakjiri/with-webauthn/tree/dev/examples/webauthn-default'
/>
diff --git a/examples/webauthn-default/src/pages/api/webauthn/login/options.ts b/examples/webauthn-default/src/pages/api/webauthn/login/options.ts
index ebcd319..f969c0d 100644
--- a/examples/webauthn-default/src/pages/api/webauthn/login/options.ts
+++ b/examples/webauthn-default/src/pages/api/webauthn/login/options.ts
@@ -29,9 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const passkeys = await getUserPasskeys(body?.username);
- /**
- * Generate a random string with enough entropy to prevent replay attacks.
- */
+ // Generate a random string with enough entropy to prevent replay attacks.
const challenge = await generateChallenge();
const authenticationOptions = await generateAuthenticationOptions({
diff --git a/examples/webauthn-default/src/pages/api/webauthn/login/verify.ts b/examples/webauthn-default/src/pages/api/webauthn/login/verify.ts
index c7bde83..e787157 100644
--- a/examples/webauthn-default/src/pages/api/webauthn/login/verify.ts
+++ b/examples/webauthn-default/src/pages/api/webauthn/login/verify.ts
@@ -6,7 +6,7 @@ import { FieldValue } from 'firebase-admin/firestore';
import { env } from '@workspace/common/client/env';
import { logger } from '@workspace/common/logger';
-import { createCustomToken } from '@workspace/common/server/services/auth';
+import { auth } from '@workspace/common/server/config/firebase';
import { retrieveAndInvalidateChallengeSession } from '@workspace/common/server/services/challenge-session';
import { getPasskeyBy, updatePasskey } from '@workspace/common/server/services/passkeys';
@@ -70,7 +70,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
lastUsedAt: FieldValue.serverTimestamp(),
});
- const customToken = await createCustomToken(passkey.userId);
+ const customToken = await auth().createCustomToken(passkey.userId);
res.status(200).json({ customToken });
} catch (error) {
diff --git a/examples/webauthn-default/src/pages/api/webauthn/register/verify.ts b/examples/webauthn-default/src/pages/api/webauthn/register/verify.ts
index 9bdadec..7ddfa46 100644
--- a/examples/webauthn-default/src/pages/api/webauthn/register/verify.ts
+++ b/examples/webauthn-default/src/pages/api/webauthn/register/verify.ts
@@ -4,7 +4,7 @@ import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import { env } from '@workspace/common/client/env';
import { logger } from '@workspace/common/logger';
-import { createCustomToken } from '@workspace/common/server/services/auth';
+import { auth } from '@workspace/common/server/config/firebase';
import { retrieveAndInvalidateChallengeSession } from '@workspace/common/server/services/challenge-session';
import { createUserPasskey, findUserByUsername } from '@workspace/common/server/services/users';
@@ -56,7 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
* Creates a new Firebase custom token (JWT)
* that can be sent back to a client device to use to sign in with the client SDKs' signInWithCustomToken() methods.
*/
- const customToken = await createCustomToken(userId);
+ const customToken = await auth().createCustomToken(userId);
res.status(200).json({ customToken });
} catch (error) {
diff --git a/examples/webauthn-default/src/pages/api/webauthn/remove/options.ts b/examples/webauthn-default/src/pages/api/webauthn/remove/options.ts
index ea71de1..36b96d5 100644
--- a/examples/webauthn-default/src/pages/api/webauthn/remove/options.ts
+++ b/examples/webauthn-default/src/pages/api/webauthn/remove/options.ts
@@ -27,9 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const userId = idTokenResult.uid;
const passkeys = await getPasskeys(userId);
- /**
- * Generate a random string with enough entropy to prevent replay attacks.
- */
+ // Generate a random string with enough entropy to prevent replay attacks.
const challenge = await generateChallenge();
const authenticationOptions = await generateAuthenticationOptions({
diff --git a/examples/webauthn-default/src/pages/api/webauthn/remove/verify.ts b/examples/webauthn-default/src/pages/api/webauthn/remove/verify.ts
index dea17fa..5ff623d 100644
--- a/examples/webauthn-default/src/pages/api/webauthn/remove/verify.ts
+++ b/examples/webauthn-default/src/pages/api/webauthn/remove/verify.ts
@@ -16,10 +16,17 @@ export type VerifyRemovalRequestData = {
passkeyId: string;
};
+export type VerifyRemovalResponseData = {
+ /**
+ * Removed passkey.
+ */
+ passkey: Passkey;
+};
+
/**
* Verify the user's identity before removing the passkey.
*/
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const idTokenResult = await parseAndVerifyIdToken(req.headers.authorization);
@@ -71,7 +78,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await removeUserPasskey(passkeyForRemoval.userId, passkeyForRemoval.id);
- res.status(200).json(passkeyForRemoval);
+ res.status(200).json({ passkey: passkeyForRemoval });
} catch (error) {
logger.error(error);
diff --git a/examples/webauthn-upgrade/.env.template.local b/examples/webauthn-upgrade/.env.template.local
new file mode 100644
index 0000000..8f3f5a8
--- /dev/null
+++ b/examples/webauthn-upgrade/.env.template.local
@@ -0,0 +1,20 @@
+# Next.js
+NEXT_TELEMETRY_DISABLED=1
+
+NEXT_PUBLIC_CLIENT_ORIGIN=http://localhost:3000
+# NEXT_PUBLIC_CLIENT_ORIGIN=https://14e5-2a07-b242-101a-9700-15d2-5b2c-c72c-ab56.ngrok-free.app
+
+NEXT_PUBLIC_FIREBASE_API_KEY=""
+NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=""
+NEXT_PUBLIC_FIREBASE_PROJECT_ID= ""
+NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=""
+NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=""
+NEXT_PUBLIC_FIREBASE_APP_ID=""
+NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=""
+NEXT_PUBLIC_FIREBASE_DB_ID=""
+
+# Sentry
+NEXT_PUBLIC_DEV_SENTRY_DISABLED=true
+# NEXT_PUBLIC_SENTRY_DSN=""
+# SENTRY_ORG=""
+# SENTRY_PROJECT=""
\ No newline at end of file
diff --git a/examples/webauthn-upgrade/.env.template.server b/examples/webauthn-upgrade/.env.template.server
new file mode 100644
index 0000000..74ac659
--- /dev/null
+++ b/examples/webauthn-upgrade/.env.template.server
@@ -0,0 +1,4 @@
+FIREBASE_PROJECT_ID=""
+FIREBASE_PRIVATE_KEY=""
+FIREBASE_CLIENT_EMAIL=""
+FIREBASE_DB_ID=""
\ No newline at end of file
diff --git a/examples/webauthn-upgrade/.gitignore b/examples/webauthn-upgrade/.gitignore
new file mode 100755
index 0000000..b9e3329
--- /dev/null
+++ b/examples/webauthn-upgrade/.gitignore
@@ -0,0 +1,52 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+.pnp
+.pnp.js
+
+# testing
+coverage
+
+# storybook
+dist/
+
+# next.js
+.next
+out
+build-storybook.log
+
+# production
+build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+
+# Sentry
+.sentryclirc
+
+!src/translations/*.mock.json
+
+.turbo
+
+node_modules
+
+# Sentry Config File
+.env.sentry-build-plugin
+
+public/firebase-messaging-sw.*
+
+.env.server.jsonc
\ No newline at end of file
diff --git a/examples/webauthn-upgrade/.madgerc b/examples/webauthn-upgrade/.madgerc
new file mode 100644
index 0000000..53eda8b
--- /dev/null
+++ b/examples/webauthn-upgrade/.madgerc
@@ -0,0 +1,15 @@
+{
+ "fileExtensions": [
+ "ts",
+ "tsx"
+ ],
+ "tsConfig": "./tsconfig.json",
+ "detectiveOptions": {
+ "ts": {
+ "skipTypeImports": true
+ },
+ "tsx": {
+ "skipTypeImports": true
+ }
+ }
+}
diff --git a/examples/webauthn-upgrade/README.md b/examples/webauthn-upgrade/README.md
new file mode 100644
index 0000000..bc239d9
--- /dev/null
+++ b/examples/webauthn-upgrade/README.md
@@ -0,0 +1,78 @@
+# Upgrade to passkeys example - From email/password to passkeys
+
+- A user registers with traditional email/password and verifies their email afterwards.
+- Then the user can link passkey/s and therefore upgrades to MFA.
+- The user can downgrade to single-factor authentication by removing all their passkeys.
+- Built with [SimpleWebAuthn](https://simplewebauthn.dev), [Firebase Auth](https://firebase.google.com/docs/auth/admin/create-custom-tokens) and Firestore SDKs.
+
+A part from that, the demo includes:
+
+- Creating (user registration), retrieving (user login), linking multiple, and removing passkeys.
+- Issuing a JWT token via Firebase Auth once user is authenticated.
+- Passkes are stored in Firebase Firestore.
+- Formatting and parsing of WebAuthn API request / responses done via SimpleWebAuthn library.
+
+👉 **[Check out the demo](https://upgrade.with-webauthn.dev)**.
+
+## Development
+
+### How to start it locally?
+
+Assuming you've already finished [those steps in the main README](../../README.md), let's proceed:
+
+> Note you can use the same Firebase project for multiple examples.
+> However, each example requires its own Firestore database, see details below.
+
+1. Setup Firebase:
+
+ 1. Create a new Firebase project
+
+ 2. Initialize `Authentication`
+ - Enable `Email/Password` sign-in provider.
+ - Add `localhost` as Authorized domain in Firebase:
+ - `Firebase > Authentication > Settings > Authorised domains.`
+ - Set Email verification URL:
+ 1. `Firebase > Authentication > Templates > Email address verification`
+ 2. `Customize action URL`
+ 3. `http://localhost:3001`
+ 3. Create a Firebase firestore database
+
+ - Initialize a new database.
+ - Set security rules:
+
+ ```
+ rules_version = '2';
+
+ service cloud.firestore {
+ match /databases/{database}/documents {
+ // Deny all access by default
+ match /{document=**} {
+ allow read, write: if false;
+ }
+
+ // Match for users collection
+ match /users/{uid} {
+ allow read: if request.auth != null && request.auth.uid == uid && request.auth.token.email_verified == true;
+ }
+
+ // Match for passkeys collection
+ match /passkeys/{passkeyId} {
+ allow read: if request.auth != null && resource.data.userId == request.auth.uid && request.auth.token.email_verified == true && request.auth.token.mfa_enabled == true;
+ }
+ }
+ }
+ ```
+
+ 4. Copy `.env.template.local` to `.env.local`:
+
+ - Fill up all those `NEXT_PUBLIC_FIREBASE_` env. vars.
+ - Don't forget to set `NEXT_PUBLIC_FIREBASE_DB_ID=firestore-db-name`.
+
+ 5. Copy `.env.template.server` to `.env.server`:
+
+ - Create a new private key in `Firebase > Project settings > Service accounts`.
+ - Fill up all those `FIREBASE_` env. vars.
+ - Don't forget to set `FIREBASE_DB_ID=firestore-db-name`.
+
+2. Run `yarn dev` in **root repository** and checkout `http://localhost:3001` URL.
+3. Hey mate, welcome to the WebAuthn world. 🙌
diff --git a/examples/webauthn-upgrade/eslint.config.mjs b/examples/webauthn-upgrade/eslint.config.mjs
new file mode 100644
index 0000000..5355fa5
--- /dev/null
+++ b/examples/webauthn-upgrade/eslint.config.mjs
@@ -0,0 +1,3 @@
+import { nextjsConfig } from '@tooling/eslint/config';
+
+export default nextjsConfig;
diff --git a/examples/webauthn-upgrade/next-env.d.ts b/examples/webauthn-upgrade/next-env.d.ts
new file mode 100644
index 0000000..a4a7b3f
--- /dev/null
+++ b/examples/webauthn-upgrade/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
diff --git a/examples/webauthn-upgrade/next.config.ts b/examples/webauthn-upgrade/next.config.ts
new file mode 100755
index 0000000..1b467eb
--- /dev/null
+++ b/examples/webauthn-upgrade/next.config.ts
@@ -0,0 +1,31 @@
+import type { NextConfig } from 'next';
+import { config } from 'dotenv';
+import type { dependencies } from 'package.json';
+
+import { withDefinedSentryConfig } from '@workspace/sentry/next-config';
+
+if (process.env.NODE_ENV === 'development') {
+ config({ path: '.env.local' });
+ config({ path: '../../.env' });
+}
+
+type Dependency = keyof typeof dependencies;
+
+const nextConfig: NextConfig = {
+ reactStrictMode: true,
+
+ i18n: {
+ locales: ['en'],
+ defaultLocale: 'en',
+ },
+
+ transpilePackages: ['@workspace/common'] satisfies Dependency[],
+
+ redirects: async () => [],
+};
+
+// Make sure adding Sentry options is the last code to run before exporting, to
+// ensure that your source maps include changes from all other Webpack plugins
+export default process.env.NEXT_PUBLIC_DEV_SENTRY_DISABLED === 'true'
+ ? nextConfig
+ : withDefinedSentryConfig(nextConfig);
diff --git a/examples/webauthn-upgrade/package.json b/examples/webauthn-upgrade/package.json
new file mode 100644
index 0000000..62ee11f
--- /dev/null
+++ b/examples/webauthn-upgrade/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "webauthn-upgrade-example",
+ "version": "0.0.1",
+ "type": "module",
+ "scripts": {
+ "types-check": "tsc --noEmit -w",
+ "dev": "next dev --turbopack -p 3001 & yarn types-check",
+ "build": "next build",
+ "start": "next start -p 3001",
+ "lint": "eslint-lint --config=eslint.config.mjs ./src/**/*.{ts,tsx}",
+ "lint:fix": "yarn lint --fix",
+ "format": "prettier-format",
+ "cir-dep": "check-cir-deps ./src"
+ },
+ "dependencies": {
+ "@workspace/common": "workspace:*",
+ "@workspace/sentry": "workspace:*",
+ "next": "15.0.3",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "typescript": "5.7.2"
+ },
+ "devDependencies": {
+ "@tooling/eslint": "workspace:*",
+ "@tooling/madge": "workspace:*",
+ "@tooling/prettier": "workspace:*",
+ "@tooling/typescript": "workspace:*",
+ "browserslist-config-custom": "workspace:*"
+ },
+ "prettier": "@tooling/prettier/config",
+ "browserslist": [
+ "extends browserslist-config-custom"
+ ]
+}
diff --git a/examples/webauthn-upgrade/public/manifest.json b/examples/webauthn-upgrade/public/manifest.json
new file mode 100644
index 0000000..2af852b
--- /dev/null
+++ b/examples/webauthn-upgrade/public/manifest.json
@@ -0,0 +1,12 @@
+{
+ "theme_color": "#000000",
+ "background_color": "#ffffff",
+ "icons": [],
+ "orientation": "portrait",
+ "display": "standalone",
+ "dir": "auto",
+ "lang": "en-US",
+ "name": "With WebAuthn full stack example",
+ "short_name": "With WebAuthn",
+ "start_url": "/"
+}
diff --git a/examples/webauthn-upgrade/sentry.client.config.ts b/examples/webauthn-upgrade/sentry.client.config.ts
new file mode 100644
index 0000000..59a6448
--- /dev/null
+++ b/examples/webauthn-upgrade/sentry.client.config.ts
@@ -0,0 +1,10 @@
+// This file configures the initialization of Sentry on the client.
+// The config you add here will be used whenever a users loads a page in their browser.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { env } from '@workspace/common/client/env';
+import { initSentryForClient } from '@workspace/sentry/client';
+
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForClient(env.NEXT_PUBLIC_SENTRY_DSN);
+}
diff --git a/examples/webauthn-upgrade/sentry.edge.config.ts b/examples/webauthn-upgrade/sentry.edge.config.ts
new file mode 100644
index 0000000..2d21a63
--- /dev/null
+++ b/examples/webauthn-upgrade/sentry.edge.config.ts
@@ -0,0 +1,11 @@
+// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
+// The config you add here will be used whenever one of the edge features is loaded.
+// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { env } from '@workspace/common/client/env';
+import { initSentryForEdge } from '@workspace/sentry/edge';
+
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForEdge(env.NEXT_PUBLIC_SENTRY_DSN);
+}
diff --git a/examples/webauthn-upgrade/sentry.server.config.ts b/examples/webauthn-upgrade/sentry.server.config.ts
new file mode 100644
index 0000000..c0ebb5f
--- /dev/null
+++ b/examples/webauthn-upgrade/sentry.server.config.ts
@@ -0,0 +1,10 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever the server handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { env } from '@workspace/common/client/env';
+import { initSentryForServer } from '@workspace/sentry/server';
+
+if (env.NEXT_PUBLIC_DEV_SENTRY_DISABLED !== 'true' && env.NEXT_PUBLIC_SENTRY_DSN) {
+ initSentryForServer(env.NEXT_PUBLIC_SENTRY_DSN);
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/AuthFormContainer.styles.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/AuthFormContainer.styles.tsx
new file mode 100644
index 0000000..c8f7dc8
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/AuthFormContainer.styles.tsx
@@ -0,0 +1,10 @@
+import { styled } from '@workspace/common/client/ui-kit';
+
+export const AuthFormContainer = styled('div')(() => ({
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ height: '100%',
+ maxWidth: 400,
+ margin: 'auto',
+}));
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/index.ts
new file mode 100644
index 0000000..ab6fa1d
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/AuthFormContainer/index.ts
@@ -0,0 +1 @@
+export * from './AuthFormContainer.styles';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx
new file mode 100644
index 0000000..25a6cfb
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/DefaultExampleTopBar.tsx
@@ -0,0 +1,22 @@
+import { ExampleTopBar, type ExampleTopBarProps } from '@workspace/common/client/example/components';
+
+import { useExampleRouter } from '../router';
+import { useExampleRouteTitle } from './hooks/useExampleRouteTitle';
+
+export interface DefaultExampleTopBarProps extends Pick {}
+
+export const DefaultExampleTopBar = ({ expanded, onToggleExpand }: DefaultExampleTopBarProps) => {
+ const title = useExampleRouteTitle();
+ const { redirect } = useExampleRouter();
+
+ return (
+ {
+ redirect('/login');
+ }}
+ />
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts
new file mode 100644
index 0000000..379c04f
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/hooks/useExampleRouteTitle.ts
@@ -0,0 +1,22 @@
+import { useExampleAuthSession } from '@workspace/common/client/example/components';
+
+import { useExampleRouter } from '../../router';
+import type { ExampleRoute } from '../../routes';
+
+const routeTitles = {
+ '/passkeys': 'Registered passkeys',
+ '/register': 'Demo Registration',
+ '/login': 'Demo Login',
+ '/login-with-password': 'Demo Login',
+} as const satisfies Record;
+
+export function useExampleRouteTitle() {
+ const { session } = useExampleAuthSession();
+ const { currentRoute } = useExampleRouter();
+
+ if (session.state === 'loading' || !currentRoute) {
+ return 'Loading...';
+ }
+
+ return routeTitles[currentRoute];
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/index.ts
new file mode 100644
index 0000000..6670f03
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/DefaultExampleTopBar/index.ts
@@ -0,0 +1 @@
+export * from './DefaultExampleTopBar';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/EmailVerificationCode.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/EmailVerificationCode.tsx
new file mode 100644
index 0000000..9acad16
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/EmailVerificationCode.tsx
@@ -0,0 +1,51 @@
+import type { ReactNode } from 'react';
+import { useRouter } from 'next/router';
+import { useQuery } from '@tanstack/react-query';
+import { applyActionCode } from 'firebase/auth';
+import { parseAsString, parseAsStringLiteral, useQueryStates } from 'nuqs';
+
+import { QueryLoader } from '@workspace/common/client/api/components';
+import { env } from '@workspace/common/client/env';
+import { auth } from '@workspace/common/client/firebase/config';
+
+import { useExampleRouter } from '../router';
+
+export interface EmailVerificationCodeProps {
+ children: ReactNode;
+}
+
+/**
+ * Parse the email verification code from the query parameters and attempt to verify the email.
+ */
+export function EmailVerificationCode({ children }: EmailVerificationCodeProps) {
+ const [params] = useQueryStates({
+ apiKey: parseAsString,
+ mode: parseAsStringLiteral(['verifyEmail'] as const),
+ oobCode: parseAsString,
+ continueUrl: parseAsString,
+ });
+
+ const { push } = useRouter();
+ const { redirect } = useExampleRouter();
+
+ const result = useQuery({
+ queryKey: ['emailVerification', params],
+ queryFn: async () => {
+ const { mode, oobCode, continueUrl, apiKey } = params;
+
+ if (mode !== 'verifyEmail' || !oobCode || apiKey !== env.NEXT_PUBLIC_FIREBASE_API_KEY || !continueUrl) {
+ return null;
+ }
+
+ await applyActionCode(auth(), oobCode);
+
+ redirect('/login-with-password');
+
+ await push(continueUrl);
+
+ return null;
+ },
+ });
+
+ return {children};
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/index.ts
new file mode 100644
index 0000000..9df64dc
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerificationCode/index.ts
@@ -0,0 +1 @@
+export * from './EmailVerificationCode';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/EmailVerifiedAlert.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/EmailVerifiedAlert.tsx
new file mode 100644
index 0000000..0f15b0a
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/EmailVerifiedAlert.tsx
@@ -0,0 +1,32 @@
+import { parseAsBoolean, useQueryState } from 'nuqs';
+
+import { Alert, Divider, Words } from '@workspace/common/client/ui-kit';
+
+export interface EmailVerifiedAlertProps {}
+
+export const EmailVerifiedAlert = ({}: EmailVerifiedAlertProps) => {
+ const [verified, setVerified] = useQueryState('verified', parseAsBoolean);
+
+ if (verified !== true) {
+ return null;
+ }
+
+ return (
+ <>
+ {
+ setVerified(null);
+ }}
+ sx={{ mb: 3 }}
+ >
+
+ Email verified.
+
+ You can now add passkey to your account.
+
+
+
+ >
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/index.ts
new file mode 100644
index 0000000..3ae170c
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/EmailVerifiedAlert/index.ts
@@ -0,0 +1 @@
+export * from './EmailVerifiedAlert';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/LoginWithEmailAndPasswordPage.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/LoginWithEmailAndPasswordPage.tsx
new file mode 100644
index 0000000..cfa2c2d
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/LoginWithEmailAndPasswordPage.tsx
@@ -0,0 +1,54 @@
+import { useQueryState } from 'nuqs';
+
+import {
+ EmailField,
+ FieldsStack,
+ Form,
+ FormError,
+ PasswordField,
+ SubmitButton,
+} from '@workspace/common/client/form/components';
+import { Button } from '@workspace/common/client/ui-kit';
+import { ArrowBack, Send } from '@workspace/common/client/ui-kit/icons';
+
+import { AuthFormContainer } from '../AuthFormContainer';
+import { useExampleRouter } from '../router';
+import { useLoginWithEmailAndPassword } from './hooks/useLoginWithEmailAndPassword';
+import { loginFormSchema, type LoginFormSchema, type LoginFormValues } from './schema';
+
+export const LoginWithEmailAndPasswordPage = () => {
+ const [email, setEmail] = useQueryState('email');
+ const login = useLoginWithEmailAndPassword({
+ onSuccess() {
+ setEmail(null);
+ },
+ });
+
+ const { redirect } = useExampleRouter();
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/hooks/useLoginWithEmailAndPassword.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/hooks/useLoginWithEmailAndPassword.ts
new file mode 100644
index 0000000..05f9b68
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/hooks/useLoginWithEmailAndPassword.ts
@@ -0,0 +1,74 @@
+import { startAuthentication } from '@simplewebauthn/browser';
+import { signInWithCustomToken } from 'firebase/auth';
+
+import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
+import { auth } from '@workspace/common/client/firebase/config';
+import type { FormProps } from '@workspace/common/client/form/components';
+import { logger } from '@workspace/common/logger';
+
+import type { LoginRequestData, LoginResponseData } from '~pages/api/auth/login';
+import type { VerifyLoginRequestData, VerifyLoginResponseData } from '~pages/api/webauthn/login/verify';
+
+import { useExampleRouter } from '../../router';
+import type { LoginFormSchema, LoginFormValues } from '../schema';
+
+export interface UseLoginWithEmailAndPasswordProps {
+ onSuccess: () => void;
+}
+
+export function useLoginWithEmailAndPassword({
+ onSuccess,
+}: UseLoginWithEmailAndPasswordProps): FormProps['onSubmit'] {
+ const { redirect } = useExampleRouter();
+
+ return async function loginWithEmailAndPassword({ email, password }, { setError }) {
+ try {
+ const { data: loginResult } = await fetcher({
+ method: 'POST',
+ url: '/auth/login',
+ body: { email, password } satisfies LoginRequestData,
+ });
+
+ if (!loginResult.mfa) {
+ await signInWithCustomToken(auth(), loginResult.customToken);
+
+ redirect('/passkeys');
+
+ return;
+ }
+
+ logger.info('/auth/login', loginResult);
+
+ const webAuthnResult = await startAuthentication({
+ optionsJSON: loginResult.publicKeyOptions,
+ });
+
+ logger.info('WebAuthn API result:', webAuthnResult);
+
+ const { data: webAuthnVerifiedResult } = await fetcher({
+ method: 'POST',
+ url: '/webauthn/login/verify',
+ body: {
+ authenticationResponse: webAuthnResult,
+ } satisfies VerifyLoginRequestData,
+ });
+
+ logger.info('/webauthn/login/verify', webAuthnVerifiedResult);
+
+ await signInWithCustomToken(auth(), webAuthnVerifiedResult.customToken);
+
+ onSuccess();
+
+ redirect('/passkeys');
+ } catch (error) {
+ const parsedError = await parseUnknownError(error);
+
+ setError('root', {
+ message: parsedError.message,
+ });
+
+ logger.error(error);
+ }
+ };
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/index.ts
new file mode 100644
index 0000000..e2c1bf8
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/index.ts
@@ -0,0 +1 @@
+export * from './LoginWithEmailAndPasswordPage';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/schema/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/schema/index.ts
new file mode 100644
index 0000000..adc00f7
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithEmailAndPasswordPage/schema/index.ts
@@ -0,0 +1,12 @@
+import { z } from 'zod';
+
+import { email, password } from '@workspace/common/client/form/validators';
+
+export const loginFormSchema = z.object({
+ email,
+ password,
+});
+
+export type LoginFormSchema = typeof loginFormSchema;
+
+export type LoginFormValues = z.infer;
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx
new file mode 100644
index 0000000..76d105f
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/LoginWithPasskeyPage.tsx
@@ -0,0 +1,80 @@
+import { Alert, Box, Button, Divider, Stack, Words } from '@workspace/common/client/ui-kit';
+import { Email, Fingerprint, Google } from '@workspace/common/client/ui-kit/icons';
+
+import { AuthFormContainer } from '../AuthFormContainer';
+import { useExampleRouter } from '../router';
+import { useLoginWithPasskey } from './hooks/useLoginWithPasskey';
+
+export const LoginWithPasskeyPage = () => {
+ const loginWithPasskey = useLoginWithPasskey();
+ const { redirect } = useExampleRouter();
+
+ return (
+ <>
+
+
+ {loginWithPasskey.error && {loginWithPasskey.error.message}}
+
+ }
+ type='submit'
+ loading={loginWithPasskey.isPending}
+ onClick={() => loginWithPasskey.mutate()}
+ >
+ Login with Passkey
+
+
+
+
+ ({
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ })}
+ >
+ Or continue with
+
+
+ }
+ onClick={() => redirect('/login-with-password')}
+ >
+ Email
+
+ }
+ onClick={() =>
+ alert(
+ 'Not implemented. Just a UI example.\nYou can sign-in with your email/password or with your passkey.',
+ )
+ }
+ >
+ Google
+
+
+
+
+
+
+ Don't have any account yet?
+
+
+
+
+ >
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts
new file mode 100644
index 0000000..7caa101
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/hooks/useLoginWithPasskey.ts
@@ -0,0 +1,55 @@
+import { startAuthentication } from '@simplewebauthn/browser';
+import { useMutation } from '@tanstack/react-query';
+import { signInWithCustomToken } from 'firebase/auth';
+
+import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
+import { auth } from '@workspace/common/client/firebase/config';
+import { logger } from '@workspace/common/logger';
+
+import type { StartLoginResponseData } from '~pages/api/webauthn/login/options';
+import type { VerifyLoginRequestData, VerifyLoginResponseData } from '~pages/api/webauthn/login/verify';
+
+import { useExampleRouter } from '../../router';
+
+export function useLoginWithPasskey() {
+ const { redirect } = useExampleRouter();
+
+ return useMutation>>({
+ mutationFn: async () => {
+ try {
+ const {
+ data: { publicKeyOptions },
+ } = await fetcher({
+ method: 'GET',
+ url: '/webauthn/login/options',
+ });
+
+ logger.info('/webauthn/login/options', { publicKeyOptions });
+
+ const result = await startAuthentication({
+ optionsJSON: publicKeyOptions,
+ });
+
+ logger.info('WebAuthn API result:', result);
+
+ const { data } = await fetcher({
+ method: 'POST',
+ url: '/webauthn/login/verify',
+ body: {
+ authenticationResponse: result,
+ } satisfies VerifyLoginRequestData,
+ });
+
+ logger.info('/webauthn/login/verify', { data });
+
+ await signInWithCustomToken(auth(), data.customToken);
+
+ // NOTE: The Authorization header with ID token is set in request inceptor in AuthProvider.tsx component.
+ redirect('/passkeys');
+ } catch (error) {
+ throw await parseUnknownError(error);
+ }
+ },
+ });
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/index.ts
new file mode 100644
index 0000000..4f8ec35
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/LoginWithPasskeyPage/index.ts
@@ -0,0 +1 @@
+export * from './LoginWithPasskeyPage';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/PasskeysPage.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/PasskeysPage.tsx
new file mode 100644
index 0000000..e640fb5
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/PasskeysPage.tsx
@@ -0,0 +1,47 @@
+import { useDialog } from '@workspace/common/client/dialog/hooks';
+import { useAuthTokenClaims } from '@workspace/common/client/example/components';
+import {
+ PasskeysHeader,
+ PasskeysList,
+ PostRemovalDialog,
+ type PostRemovalDialogProps,
+} from '@workspace/common/client/passkeys/components';
+import { Words } from '@workspace/common/client/ui-kit';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+
+import { useAddPasskey } from './hooks/useAddPasskey';
+import { useRemovePasskey } from './hooks/useRemovePasskey';
+
+export const PasskeysPage = () => {
+ const addPasskey = useAddPasskey();
+ const postRemovalDialog = useDialog();
+ const removePasskey = useRemovePasskey(postRemovalDialog.openDialog);
+ const claims = useAuthTokenClaims();
+
+ return (
+ <>
+
+ {claims?.[tokenClaims.MFA_ENABLED] ? (
+ true} />
+ ) : (
+ ({
+ mt: 4,
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ })}
+ >
+ Enable multi-factor authentication by adding a passkey.
+
+ )}
+
+ >
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useAddPasskey.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useAddPasskey.ts
new file mode 100644
index 0000000..91fc056
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useAddPasskey.ts
@@ -0,0 +1,62 @@
+import { startRegistration } from '@simplewebauthn/browser';
+import { useMutation } from '@tanstack/react-query';
+
+import { queryClient } from '@workspace/common/client/api/components';
+import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
+import { useSnack } from '@workspace/common/client/snackbar/hooks';
+import { logger } from '@workspace/common/logger';
+
+import type { StartLinkingResponseData } from '~pages/api/webauthn/link/options';
+import type { VerifyLinkResponseData } from '~pages/api/webauthn/link/verify';
+
+import { reauthenticate } from '../utils/reauthenticate';
+
+export function useAddPasskey() {
+ const snack = useSnack();
+
+ return useMutation({
+ mutationFn: async () => {
+ const {
+ data: { publicKeyOptions },
+ } = await fetcher({
+ method: 'GET',
+ url: '/webauthn/link/options',
+ });
+
+ logger.info('/webauthn/link/options', publicKeyOptions);
+
+ const result = await startRegistration({
+ optionsJSON: publicKeyOptions,
+ });
+
+ logger.info('Registration result:', result);
+
+ const {
+ data: { customToken },
+ } = await fetcher({
+ method: 'POST',
+ url: '/webauthn/link/verify',
+ body: {
+ registrationResponse: result,
+ },
+ });
+
+ await reauthenticate(customToken);
+
+ await queryClient.invalidateQueries({
+ queryKey: ['passkeys'],
+ });
+ },
+ async onError(error: Error) {
+ const parsedError = await parseUnknownError(error);
+
+ logger.error(parsedError);
+
+ snack('error', parsedError.message);
+ },
+ onSuccess() {
+ snack('success', 'Passkey has been successfully added.');
+ },
+ });
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useRemovePasskey.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useRemovePasskey.ts
new file mode 100644
index 0000000..6b799a6
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/hooks/useRemovePasskey.ts
@@ -0,0 +1,77 @@
+import { startAuthentication } from '@simplewebauthn/browser';
+import { useMutation } from '@tanstack/react-query';
+
+import { queryClient } from '@workspace/common/client/api/components';
+import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
+import { useAuthUser } from '@workspace/common/client/example/components';
+import type { PostRemovalDialogProps } from '@workspace/common/client/passkeys/components';
+import { useSnack } from '@workspace/common/client/snackbar/hooks';
+import { logger } from '@workspace/common/logger';
+
+import type { StartRemovalResponseData } from '~pages/api/webauthn/remove/options';
+import type { VerifyRemovalRequestData, VerifyRemovalResponseData } from '~pages/api/webauthn/remove/verify';
+
+import { reauthenticate } from '../utils/reauthenticate';
+
+/**
+ * Remove a passkey from the user's account. User must verify their identity before removing the passkey.
+ */
+export function useRemovePasskey(openDialog: (data: PostRemovalDialogProps['data']) => void) {
+ const snack = useSnack();
+ const authUser = useAuthUser();
+
+ return useMutation({
+ mutationFn: async (passkeyId: string) => {
+ try {
+ const {
+ data: { publicKeyOptions },
+ } = await fetcher({
+ method: 'GET',
+ url: '/webauthn/remove/options',
+ });
+
+ logger.info('/webauthn/remove/options', publicKeyOptions);
+
+ const result = await startAuthentication({
+ optionsJSON: publicKeyOptions,
+ });
+
+ logger.info('WebAuthn API result:', result);
+
+ const { data: removalResult } = await fetcher({
+ method: 'POST',
+ url: '/webauthn/remove/verify',
+ body: {
+ authenticationResponse: result,
+ passkeyId,
+ } satisfies VerifyRemovalRequestData,
+ });
+
+ const { mfa, passkey } = removalResult;
+
+ if (!mfa) {
+ await reauthenticate(removalResult.customToken);
+ }
+
+ if (passkey.provider) {
+ openDialog({
+ provider: passkey.provider,
+ rpId: passkey.rpId,
+ username: authUser?.email!,
+ });
+ }
+
+ await queryClient.invalidateQueries({
+ queryKey: ['passkeys'],
+ });
+ } catch (error) {
+ const parsedError = await parseUnknownError(error);
+
+ snack('error', parsedError.message);
+
+ logger.error(error);
+ }
+ },
+ });
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/index.ts
new file mode 100644
index 0000000..1d1082a
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/index.ts
@@ -0,0 +1 @@
+export * from './PasskeysPage';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/utils/reauthenticate.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/utils/reauthenticate.ts
new file mode 100644
index 0000000..409812e
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/PasskeysPage/utils/reauthenticate.ts
@@ -0,0 +1,8 @@
+import { signInWithCustomToken, signOut } from '@firebase/auth';
+
+import { auth } from '@workspace/common/client/firebase/config';
+
+export async function reauthenticate(customToken: string) {
+ await signOut(auth());
+ await signInWithCustomToken(auth(), customToken);
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/RegisterWithEmailAndPasswordPage.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/RegisterWithEmailAndPasswordPage.tsx
new file mode 100644
index 0000000..9eb423e
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/RegisterWithEmailAndPasswordPage.tsx
@@ -0,0 +1,50 @@
+import {
+ EmailField,
+ FieldsStack,
+ Form,
+ FormError,
+ PasswordField,
+ SubmitButton,
+} from '@workspace/common/client/form/components';
+import { Box, Button, Divider, Words } from '@workspace/common/client/ui-kit';
+import { Send } from '@workspace/common/client/ui-kit/icons';
+
+import { AuthFormContainer } from '../AuthFormContainer';
+import { useExampleRouter } from '../router';
+import { useRegisterWithEmailAndPassword } from './hooks';
+import { registerFormSchema, type RegisterFormSchema, type RegisterFormValues } from './schema';
+
+export const RegisterWithEmailAndPasswordPage = () => {
+ const register = useRegisterWithEmailAndPassword();
+ const { redirect } = useExampleRouter();
+
+ return (
+
+
+
+
+
+
+ Already have an account?
+
+
+
+
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/index.ts
new file mode 100644
index 0000000..fe4a58e
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useRegisterWithEmailAndPassword';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/useRegisterWithEmailAndPassword.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/useRegisterWithEmailAndPassword.tsx
new file mode 100644
index 0000000..1a8364b
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/hooks/useRegisterWithEmailAndPassword.tsx
@@ -0,0 +1,74 @@
+import { sendEmailVerification, signInWithCustomToken, signOut, type User } from 'firebase/auth';
+
+import { fetcher } from '@workspace/common/client/api/fetcher';
+import { parseUnknownError } from '@workspace/common/client/errors';
+import { auth } from '@workspace/common/client/firebase/config';
+import type { FormProps } from '@workspace/common/client/form/components';
+import { useSnack } from '@workspace/common/client/snackbar/hooks';
+import { logger } from '@workspace/common/logger';
+
+import type { RegisterRequestData, RegisterResponseData } from '~pages/api/auth/register';
+
+import { useExampleRouter } from '../../router';
+import type { RegisterFormSchema, RegisterFormValues } from '../schema';
+
+async function sendUserEmailVerification(user: User) {
+ const returnUrl = new URL('/', window.location.origin);
+
+ returnUrl.searchParams.set('verified', 'true');
+ returnUrl.searchParams.set('email', user.email!);
+
+ await sendEmailVerification(user, {
+ handleCodeInApp: true,
+ url: returnUrl.toString(),
+ });
+}
+
+export function useRegisterWithEmailAndPassword(): FormProps['onSubmit'] {
+ const { redirect } = useExampleRouter();
+ const snack = useSnack();
+
+ return async function registerPasskey({ email, password }, { setError, reset }) {
+ try {
+ const {
+ data: { customToken },
+ } = await fetcher({
+ method: 'POST',
+ url: '/auth/register',
+ body: { email, password } satisfies RegisterRequestData,
+ });
+
+ const { user } = await signInWithCustomToken(auth(), customToken);
+
+ if (!user.emailVerified) {
+ await sendUserEmailVerification(user);
+ await signOut(auth());
+ } else {
+ redirect('/passkeys');
+ }
+
+ reset();
+
+ snack(
+ 'success',
+ <>
+ User created.
+ {!user.emailVerified && (
+ <>
+
+ Please check your inbox and click on the email verification link.
+ >
+ )}
+ >,
+ );
+ } catch (error) {
+ const parsedError = await parseUnknownError(error);
+
+ setError('root', {
+ message: parsedError.message,
+ });
+
+ logger.error(error);
+ }
+ };
+}
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/index.ts
new file mode 100644
index 0000000..8b34237
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/index.ts
@@ -0,0 +1 @@
+export * from './RegisterWithEmailAndPasswordPage';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/schema/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/schema/index.ts
new file mode 100644
index 0000000..4253596
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/RegisterWithEmailAndPasswordPage/schema/index.ts
@@ -0,0 +1,12 @@
+import { z } from 'zod';
+
+import { email, newPassword } from '@workspace/common/client/form/validators';
+
+export const registerFormSchema = z.object({
+ email,
+ password: newPassword,
+});
+
+export type RegisterFormSchema = typeof registerFormSchema;
+
+export type RegisterFormValues = z.infer;
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/ResolveInitRoute.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/ResolveInitRoute.tsx
new file mode 100644
index 0000000..eed1082
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/ResolveInitRoute.tsx
@@ -0,0 +1,27 @@
+import { useEffect } from 'react';
+
+import { useExampleAuthSession } from '@workspace/common/client/example/components';
+
+import { useExampleRouter } from '../router';
+
+export const ResolveInitRoute = () => {
+ const { currentRoute, redirect } = useExampleRouter();
+ const { session } = useExampleAuthSession();
+
+ useEffect(() => {
+ if (currentRoute !== null) {
+ return;
+ }
+
+ switch (session.state) {
+ case 'authenticated':
+ redirect('/passkeys');
+ break;
+ case 'unauthenticated':
+ redirect('/register');
+ break;
+ }
+ }, [session, currentRoute, redirect]);
+
+ return null;
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/index.ts
new file mode 100644
index 0000000..06c3fba
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/ResolveInitRoute/index.ts
@@ -0,0 +1 @@
+export * from './ResolveInitRoute';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/UpgradeExample.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/UpgradeExample.tsx
new file mode 100644
index 0000000..117a94d
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/UpgradeExample.tsx
@@ -0,0 +1,33 @@
+import { useState } from 'react';
+
+import { ExampleAuth, ExampleBody, ExampleFrame } from '@workspace/common/client/example/components';
+
+import { DefaultExampleTopBar } from './DefaultExampleTopBar';
+import { EmailVerificationCode } from './EmailVerificationCode';
+import { EmailVerifiedAlert } from './EmailVerifiedAlert';
+import { ResolveInitRoute } from './ResolveInitRoute';
+import { CurrentExampleRoute, ExampleRouter } from './router';
+import { exampleRoutes } from './routes';
+
+export const UpgradeExample = () => {
+ const [expanded, setExpanded] = useState(true);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/index.ts
new file mode 100644
index 0000000..f9a667d
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/index.ts
@@ -0,0 +1 @@
+export * from './UpgradeExample';
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/router/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/router/index.ts
new file mode 100644
index 0000000..58d5b70
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/router/index.ts
@@ -0,0 +1,7 @@
+import { createExampleRouter } from '@workspace/common/client/example/components';
+
+import type { ExampleRoutes } from '../routes';
+
+const { ExampleRouter, useExampleRouter, CurrentExampleRoute } = createExampleRouter();
+
+export { CurrentExampleRoute, ExampleRouter, useExampleRouter };
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/routes/index.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/routes/index.tsx
new file mode 100644
index 0000000..cc833fe
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/UpgradeExample/routes/index.tsx
@@ -0,0 +1,16 @@
+import type { UnknownRoutes } from '@workspace/common/client/example/components';
+
+import { LoginWithEmailAndPasswordPage } from '../LoginWithEmailAndPasswordPage';
+import { LoginWithPasskeyPage } from '../LoginWithPasskeyPage';
+import { PasskeysPage } from '../PasskeysPage';
+import { RegisterWithEmailAndPasswordPage } from '../RegisterWithEmailAndPasswordPage';
+
+export const exampleRoutes = {
+ '/register': RegisterWithEmailAndPasswordPage,
+ '/login': LoginWithPasskeyPage,
+ '/login-with-password': LoginWithEmailAndPasswordPage,
+ '/passkeys': PasskeysPage,
+} as const satisfies UnknownRoutes;
+
+export type ExampleRoutes = typeof exampleRoutes;
+export type ExampleRoute = keyof ExampleRoutes;
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/WebAuthnUpgradeExamplePage.tsx b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/WebAuthnUpgradeExamplePage.tsx
new file mode 100644
index 0000000..6d3e295
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/WebAuthnUpgradeExamplePage.tsx
@@ -0,0 +1,42 @@
+import Link from 'next/link';
+
+import { ExampleDescription, ExampleHeader, ExampleWrapper } from '@workspace/common/client/example/components';
+import { PageHeader } from '@workspace/common/client/layout/components';
+import { Container } from '@workspace/common/client/ui-kit';
+
+import { UpgradeExample } from './UpgradeExample';
+
+export const WebAuthnUpgradeExamplePage = () => {
+ return (
+ <>
+
+
+
+
+
+ Built with{' '}
+
+ SimpleWebAuthn
+ {' '}
+ , Firebase Auth and Firestore SDKs.
+ >,
+ ]}
+ />
+ }
+ githubUrl='https://github.com/cermakjiri/with-webauthn/tree/dev/examples/webauthn-upgrade'
+ />
+
+
+
+ >
+ );
+};
diff --git a/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/index.ts b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/index.ts
new file mode 100644
index 0000000..313d828
--- /dev/null
+++ b/examples/webauthn-upgrade/src/components/WebAuthnUpgradeExamplePage/index.ts
@@ -0,0 +1 @@
+export * from './WebAuthnUpgradeExamplePage';
diff --git a/examples/webauthn-upgrade/src/instrumentation.ts b/examples/webauthn-upgrade/src/instrumentation.ts
new file mode 100644
index 0000000..83253dc
--- /dev/null
+++ b/examples/webauthn-upgrade/src/instrumentation.ts
@@ -0,0 +1,13 @@
+import * as Sentry from '@sentry/nextjs';
+
+export async function register() {
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
+ await import('../sentry.server.config');
+ }
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ await import('../sentry.edge.config');
+ }
+}
+
+export const onRequestError = Sentry.captureRequestError;
diff --git a/examples/webauthn-upgrade/src/pages/_app.tsx b/examples/webauthn-upgrade/src/pages/_app.tsx
new file mode 100755
index 0000000..f7ebc4e
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/_app.tsx
@@ -0,0 +1,3 @@
+import { App } from '@workspace/common/client/core/components';
+
+export default App;
diff --git a/examples/webauthn-upgrade/src/pages/_document.tsx b/examples/webauthn-upgrade/src/pages/_document.tsx
new file mode 100644
index 0000000..30166d9
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/_document.tsx
@@ -0,0 +1,3 @@
+import { MyDocument } from '@workspace/common/client/core/components';
+
+export default MyDocument;
diff --git a/examples/webauthn-upgrade/src/pages/_error.jsx b/examples/webauthn-upgrade/src/pages/_error.jsx
new file mode 100644
index 0000000..df405cb
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/_error.jsx
@@ -0,0 +1,3 @@
+import { CustomError } from '@workspace/common/client/core/components';
+
+export default CustomError;
diff --git a/examples/webauthn-upgrade/src/pages/api/auth/login.ts b/examples/webauthn-upgrade/src/pages/api/auth/login.ts
new file mode 100644
index 0000000..6292a76
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/auth/login.ts
@@ -0,0 +1,124 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { generateAuthenticationOptions } from '@simplewebauthn/server';
+import { generateChallenge } from '@simplewebauthn/server/helpers';
+import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
+import { signInWithEmailAndPassword } from 'firebase/auth';
+import z from 'zod';
+
+import { env } from '@workspace/common/client/env';
+import { auth as clientAuth } from '@workspace/common/client/firebase/config';
+import { email, password } from '@workspace/common/client/form/validators';
+import { logger } from '@workspace/common/logger';
+import { auth as serverAuth } from '@workspace/common/server/config/firebase';
+import { initializeChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getPasskeys } from '@workspace/common/server/services/passkeys';
+import { getUserPasskeys } from '@workspace/common/server/services/users';
+import { getRpId } from '@workspace/common/server/utils';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+
+async function generateUserAuthenticationOptions(email: string, challenge: Uint8Array) {
+ const passkeys = await getUserPasskeys(email);
+
+ const authenticationOptions = await generateAuthenticationOptions({
+ /**
+ * Relying party ID: Hostname of your client app.
+ * E.g. "localhost", "example.com" or "auth.example.com".
+ */
+ rpID: getRpId(env.NEXT_PUBLIC_CLIENT_ORIGIN),
+
+ challenge,
+
+ /**
+ * We might not want to provide list of available passkeys to unauthenticated users for privacy reasons:
+ * - https://w3c.github.io/webauthn/#sctn-credential-id-privacy-leak
+ * - https://w3c.github.io/webauthn/#sctn-unprotected-account-detection (This is not relevent for this demo but, I believe, developers should be aware of this when implementing WebAuthn in production.)
+ */
+ allowCredentials: passkeys.map(({ credentialId, transports }) => ({
+ id: credentialId,
+ transports,
+ })),
+
+ /**
+ * User presense is not enough. Require user verification (e.g. PIN, fingerprint, face recognition) to verify user identity.
+ * If the authenticator does not support user verification, the registration will fail.
+ */
+ userVerification: 'required',
+
+ timeout: 300_000,
+ });
+
+ return authenticationOptions;
+}
+
+export const loginRequestBody = z.object({
+ email,
+ password,
+});
+
+export type LoginRequestData = z.infer;
+
+export type LoginResponseData =
+ | {
+ customToken: string;
+ mfa: false;
+ }
+ | {
+ publicKeyOptions: PublicKeyCredentialRequestOptionsJSON;
+ mfa: true;
+ };
+
+/**
+ * 1. Verifies email/password combination.
+ * 2. Retrieves passkeys for given user.
+ * 3. If the user has some passkeys, then generate options for for WebAuthn sign-in (i.e. for the `navigator.credentials.get` method).
+ * 4. Else send custom token with `mfa_enabled: false` encoded flag.
+ */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const { email, password } = loginRequestBody.parse(req.body);
+
+ const userCrendential = await signInWithEmailAndPassword(clientAuth(), email, password);
+
+ if (!userCrendential.user.emailVerified) {
+ res.status(400).end('Email not verified.');
+
+ return;
+ }
+
+ const passkeys = await getPasskeys(userCrendential.user.uid);
+
+ // User added has some passkeys (i.e. his account upgraded to MFA)
+ // The user must finish the authentication with passkeys.
+ if (passkeys.length > 0) {
+ // Generate a random string with enough entropy to prevent replay attacks.
+ const challenge = await generateChallenge();
+
+ const publicKeyOptions = await generateUserAuthenticationOptions(email, challenge);
+
+ await initializeChallengeSession(res, {
+ type: 'assertion',
+ timeout: publicKeyOptions.timeout!,
+ challenge,
+ });
+
+ res.send({ publicKeyOptions, mfa: true });
+
+ return;
+ }
+
+ const customToken = await serverAuth().createCustomToken(userCrendential.user.uid, {
+ [tokenClaims.MFA_ENABLED]: false,
+ });
+
+ res.send({ customToken, mfa: false });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/auth/register.ts b/examples/webauthn-upgrade/src/pages/api/auth/register.ts
new file mode 100644
index 0000000..f1c8d0e
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/auth/register.ts
@@ -0,0 +1,70 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { AuthErrorCodes } from 'firebase/auth';
+import z from 'zod';
+
+import { env } from '@workspace/common/client/env';
+import { email, password } from '@workspace/common/client/form/validators';
+import { logger } from '@workspace/common/logger';
+import { auth } from '@workspace/common/server/config/firebase';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+
+export const registerRequestBody = z.object({
+ email,
+ password,
+});
+
+export type RegisterRequestData = z.infer;
+
+export type RegisterResponseData = {
+ customToken: string;
+};
+
+async function findUserByEmail(email: string) {
+ try {
+ return await auth().getUserByEmail(email);
+ } catch (error) {
+ // @ts-expect-error Yes, it be could parsed in a better way.
+ if (error.errorInfo?.code === AuthErrorCodes.USER_DELETED) {
+ return null;
+ }
+
+ logger.error(error);
+
+ return null;
+ }
+}
+
+/**
+ * Classical email/password login if the user has not yet linked a passkey.
+ * Then, the it signs in the user with email/password and sends WebAuthn options for retrieving a passkey.
+ */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const { email, password } = registerRequestBody.parse(req.body);
+
+ const existingUser = await findUserByEmail(email);
+
+ if (existingUser) {
+ return res.status(403).end('User with this email already exists.');
+ }
+
+ const user = await auth().createUser({
+ email,
+ password,
+ emailVerified: false,
+ });
+
+ const customToken = await auth().createCustomToken(user.uid, { [tokenClaims.MFA_ENABLED]: false });
+
+ res.send({ customToken });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/link/options.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/link/options.ts
new file mode 100644
index 0000000..41b2d92
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/link/options.ts
@@ -0,0 +1,125 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { generateRegistrationOptions } from '@simplewebauthn/server';
+import { generateChallenge } from '@simplewebauthn/server/helpers';
+import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { auth } from '@workspace/common/server/config/firebase';
+import { RP_NAME } from '@workspace/common/server/constants/relyingParty';
+import { initializeChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getPasskeys } from '@workspace/common/server/services/passkeys';
+import { createUserWithNoPasskeys, getUser } from '@workspace/common/server/services/users';
+import { getRpId, parseAndVerifyIdToken } from '@workspace/common/server/utils';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+
+export type StartLinkingResponseData = {
+ publicKeyOptions: PublicKeyCredentialCreationOptionsJSON;
+};
+
+/**
+ * Upgrade from email/password auth. provider to MFA with passkeys.
+ * - Check if user provided valid token.
+ * - Create user with no passkeys if user does not exist.
+ * - Proceed with WebAuthn registration.
+ */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const idTokenResult = await parseAndVerifyIdToken(req.headers.authorization);
+
+ if (!idTokenResult || !idTokenResult.email_verified) {
+ logger.error('User not authenticated. No ID token or email not verified.');
+
+ return res.status(401).end('User not authenticated.');
+ }
+
+ const userId = idTokenResult.uid;
+ const authUser = await auth().getUser(userId);
+ const username = authUser.email!;
+
+ const user = await getUser(userId);
+
+ if (user === null) {
+ await createUserWithNoPasskeys(userId, username);
+ }
+
+ const passkeys = await getPasskeys(userId);
+
+ // ID token claims must include 'mfa_enabled: true' once at least one passkey has been added.
+ if (passkeys.length > 0 && !idTokenResult[tokenClaims.MFA_ENABLED]) {
+ logger.error('User has passkeys but MFA is not enabled.');
+
+ return res.status(401).end('User not authenticated.');
+ }
+
+ /**
+ * Generate a random string with enough entropy to be signed by the authenticator to prevent replay attacks.
+ */
+ const challenge = await generateChallenge();
+
+ const registrationOptions = await generateRegistrationOptions({
+ /**
+ * Relying party ID: Hostname of your client app.
+ * E.g. "localhost", "example.com" or "auth.example.com".
+ */
+ rpID: getRpId(env.NEXT_PUBLIC_CLIENT_ORIGIN),
+
+ /**
+ * Relying party name: Display name of your client app.
+ */
+ rpName: RP_NAME,
+
+ challenge,
+
+ userName: username,
+
+ /**
+ * Prevent creating multiple user passkeys on the same authenticator.
+ */
+ excludeCredentials: passkeys.map(({ credentialId, transports }) => ({
+ id: credentialId,
+ transports,
+ })),
+
+ /**
+ * Require authenticator to provide proof of its origin.
+ */
+ attestationType: 'direct',
+
+ authenticatorSelection: {
+ /**
+ * User presense is not enough. Require user verification (e.g. PIN, fingerprint) to verify user identity.
+ * If the authenticator does not support user verification, the registration will fail.
+ */
+ userVerification: 'required',
+ },
+
+ /**
+ * Recommended range: 300_000 milliseconds to 600_000 milliseconds.
+ * Recommended default value: 300_000 milliseconds (5 minutes).
+ * https://www.w3.org/TR/webauthn-3/#sctn-createCredential
+ */
+ timeout: 300_000,
+ });
+
+ await initializeChallengeSession(res, {
+ type: 'attestation',
+ timeout: registrationOptions.timeout!,
+ challenge,
+ username,
+ });
+
+ res.status(200).json({
+ publicKeyOptions: registrationOptions,
+ });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/link/verify.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/link/verify.ts
new file mode 100644
index 0000000..67930fc
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/link/verify.ts
@@ -0,0 +1,67 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { verifyRegistrationResponse } from '@simplewebauthn/server';
+import type { RegistrationResponseJSON } from '@simplewebauthn/types';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { retrieveAndInvalidateChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { addUserPasskey } from '@workspace/common/server/services/users';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+import { revokenAndCreateCustomUserToken } from '~server/services/auth';
+import { parseAndVerifyIdTokenForMFA } from '~server/utils/parseAndVerifyIdTokenForMFA';
+
+export type VerifyLinkRequestData = {
+ registrationResponse: RegistrationResponseJSON;
+};
+
+export type VerifyLinkResponseData = {
+ customToken: string;
+};
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const idTokenResult = await parseAndVerifyIdTokenForMFA(req.headers.authorization);
+
+ if (!idTokenResult) {
+ return res.status(401).end('User not authenticated.');
+ }
+
+ const { registrationResponse } = req.body as VerifyLinkRequestData;
+
+ const challengeSession = await retrieveAndInvalidateChallengeSession(req, res, 'attestation');
+
+ if (!challengeSession || challengeSession.type !== 'attestation') {
+ return res.status(401).end('Challenge session is not active. Please start the registration process again.');
+ }
+
+ const verifiedRegistrationResponse = await verifyRegistrationResponse({
+ response: registrationResponse,
+ expectedChallenge: challengeSession.challenge,
+ expectedOrigin: challengeSession.origin,
+ expectedRPID: challengeSession.rpId,
+ });
+
+ if (!verifiedRegistrationResponse.verified) {
+ return res.status(401).end('User not authenticated.');
+ }
+
+ logger.debug('verifiedRegistrationResponse', verifiedRegistrationResponse);
+
+ const userId = idTokenResult.uid;
+
+ await addUserPasskey(userId, verifiedRegistrationResponse.registrationInfo);
+
+ const customToken = await revokenAndCreateCustomUserToken(userId, { [tokenClaims.MFA_ENABLED]: true });
+
+ res.status(200).json({ customToken });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/login/options.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/login/options.ts
new file mode 100644
index 0000000..3295fcf
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/login/options.ts
@@ -0,0 +1,57 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { generateAuthenticationOptions } from '@simplewebauthn/server';
+import { generateChallenge } from '@simplewebauthn/server/helpers';
+import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { initializeChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getRpId } from '@workspace/common/server/utils';
+
+export type StartLoginResponseData = {
+ publicKeyOptions: PublicKeyCredentialRequestOptionsJSON;
+};
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ // Generate a random string with enough entropy to prevent replay attacks.
+ const challenge = await generateChallenge();
+
+ const authenticationOptions = await generateAuthenticationOptions({
+ /**
+ * Relying party ID: Hostname of your client app.
+ * E.g. "localhost", "example.com" or "auth.example.com".
+ */
+ rpID: getRpId(env.NEXT_PUBLIC_CLIENT_ORIGIN),
+
+ challenge,
+
+ allowCredentials: [],
+
+ /**
+ * User presense is not enough. Require user verification (e.g. PIN, fingerprint, face recognition) to verify user identity.
+ * If the authenticator does not support user verification, the registration will fail.
+ */
+ userVerification: 'required',
+
+ timeout: 300_000,
+ });
+
+ await initializeChallengeSession(res, {
+ type: 'assertion',
+ timeout: authenticationOptions.timeout!,
+ challenge,
+ });
+
+ res.status(200).json({
+ publicKeyOptions: authenticationOptions,
+ });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end((error as Error).message);
+ // error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ // ? error.message
+ // : 'Internal Server Error',
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/login/verify.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/login/verify.ts
new file mode 100644
index 0000000..9495b51
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/login/verify.ts
@@ -0,0 +1,87 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { base64URLStringToBuffer } from '@simplewebauthn/browser';
+import { verifyAuthenticationResponse } from '@simplewebauthn/server';
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
+import { FieldValue } from 'firebase-admin/firestore';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { retrieveAndInvalidateChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getPasskeyBy, updatePasskey } from '@workspace/common/server/services/passkeys';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+import { revokenAndCreateCustomUserToken } from '~server/services/auth';
+
+export type VerifyLoginRequestData = {
+ authenticationResponse: AuthenticationResponseJSON;
+};
+
+export type VerifyLoginResponseData = {
+ customToken: string;
+};
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const { authenticationResponse } = req.body as VerifyLoginRequestData;
+
+ const challengeSession = await retrieveAndInvalidateChallengeSession(req, res, 'assertion');
+
+ if (!challengeSession || challengeSession.type !== 'assertion') {
+ return res.status(401).end('Challenge session is not active. Please start the login process again.');
+ }
+
+ const passkey = await getPasskeyBy('credentialId', authenticationResponse.id);
+
+ // This might happen if the user has removed the passkey from the account
+ // but the private passkey source is still stored in a keychain / password manager.
+ if (!passkey) {
+ // Yes, this message would have been better however it could be a security risk (i.e. username enumeration):
+ // Checkout https://w3c.github.io/webauthn/#sctn-username-enumeration.
+ // return res.status(400).end('Passkey not found.');
+ return res.status(401).end('User not verified.');
+ }
+
+ const { transports, credentialId, credentialPublicKey, credentialCounter } = passkey;
+
+ const result = await verifyAuthenticationResponse({
+ response: authenticationResponse,
+ expectedRPID: challengeSession.rpId,
+ expectedOrigin: challengeSession.origin,
+ expectedChallenge: challengeSession.challenge,
+ credential: {
+ publicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
+ counter: credentialCounter,
+ transports,
+ id: credentialId,
+ },
+ requireUserVerification: true,
+ });
+
+ logger.debug('verifyAuthenticationResponse:', result);
+
+ if (!result.verified) {
+ return res.status(401).end('User not verified.');
+ }
+
+ // Just an example what you can do with the result, not needed for this current authentication process itself
+ // parseAutheticationResponse(authenticationResponse)
+
+ await updatePasskey(passkey.id, {
+ credentialCounter: result.authenticationInfo.newCounter,
+ // @ts-expect-error
+ lastUsedAt: FieldValue.serverTimestamp(),
+ });
+
+ const customToken = await revokenAndCreateCustomUserToken(passkey.userId, { [tokenClaims.MFA_ENABLED]: true });
+
+ res.status(200).json({ customToken });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/remove/options.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/remove/options.ts
new file mode 100644
index 0000000..708b142
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/remove/options.ts
@@ -0,0 +1,73 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { generateAuthenticationOptions } from '@simplewebauthn/server';
+import { generateChallenge } from '@simplewebauthn/server/helpers';
+import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { initializeChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getPasskeys } from '@workspace/common/server/services/passkeys';
+import { getRpId } from '@workspace/common/server/utils';
+
+import { parseAndVerifyIdTokenForMFA } from '~server/utils/parseAndVerifyIdTokenForMFA';
+
+export type StartRemovalResponseData = {
+ publicKeyOptions: PublicKeyCredentialCreationOptionsJSON;
+};
+
+/**
+ * Request options for WebAuthn verification before requesting passkey removal.
+ */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const idTokenResult = await parseAndVerifyIdTokenForMFA(req.headers.authorization);
+
+ if (!idTokenResult) {
+ return res.status(401).end('User not authenticated.');
+ }
+
+ const userId = idTokenResult.uid;
+ const passkeys = await getPasskeys(userId);
+
+ // Generate a random string with enough entropy to prevent replay attacks.
+ const challenge = await generateChallenge();
+
+ const authenticationOptions = await generateAuthenticationOptions({
+ /**
+ * Relying party ID: Hostname of your client app.
+ * E.g. "localhost", "example.com" or "auth.example.com".
+ */
+ rpID: getRpId(env.NEXT_PUBLIC_CLIENT_ORIGIN),
+
+ challenge,
+
+ allowCredentials: passkeys.map(({ credentialId, transports }) => ({ id: credentialId, transports })),
+
+ /**
+ * User presense is not enough. Require user verification (e.g. PIN, fingerprint, face recognition) to verify user identity.
+ * If the authenticator does not support user verification, the registration will fail.
+ */
+ userVerification: 'required',
+
+ timeout: 300_000,
+ });
+
+ await initializeChallengeSession(res, {
+ type: 'assertion',
+ timeout: authenticationOptions.timeout!,
+ challenge,
+ });
+
+ res.status(200).json({
+ publicKeyOptions: authenticationOptions,
+ });
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/api/webauthn/remove/verify.ts b/examples/webauthn-upgrade/src/pages/api/webauthn/remove/verify.ts
new file mode 100644
index 0000000..fd843a1
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/api/webauthn/remove/verify.ts
@@ -0,0 +1,125 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { base64URLStringToBuffer } from '@simplewebauthn/browser';
+import { verifyAuthenticationResponse } from '@simplewebauthn/server';
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
+
+import { env } from '@workspace/common/client/env';
+import { logger } from '@workspace/common/logger';
+import { retrieveAndInvalidateChallengeSession } from '@workspace/common/server/services/challenge-session';
+import { getPasskey, getPasskeyBy, getPasskeys } from '@workspace/common/server/services/passkeys';
+import { removeUserPasskey } from '@workspace/common/server/services/users';
+import type { Passkey } from '@workspace/common/types';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+import { revokenAndCreateCustomUserToken } from '~server/services/auth';
+import { parseAndVerifyIdTokenForMFA } from '~server/utils/parseAndVerifyIdTokenForMFA';
+
+export type VerifyRemovalRequestData = {
+ authenticationResponse: AuthenticationResponseJSON;
+ passkeyId: string;
+};
+
+export type VerifyRemovalResponseData =
+ | {
+ /**
+ * Removed passkey.
+ */
+ passkey: Passkey;
+ mfa: true;
+ }
+ | {
+ /**
+ * Removed passkey.
+ */
+ passkey: Passkey;
+
+ /**
+ * Once all passkeys have been removed, a new token is generated with `mfa_enabled: false` flag.
+ */
+ customToken: string;
+ mfa: false;
+ };
+
+/**
+ * Verify the user's identity before removing the passkey.
+ */
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const idTokenResult = await parseAndVerifyIdTokenForMFA(req.headers.authorization);
+
+ if (!idTokenResult) {
+ return res.status(401).end('User not authenticated.');
+ }
+
+ const challengeSession = await retrieveAndInvalidateChallengeSession(req, res, 'assertion');
+
+ if (!challengeSession || challengeSession.type !== 'assertion') {
+ return res.status(401).end('Challenge session is not active. Please start the login process again.');
+ }
+
+ const { authenticationResponse, passkeyId } = req.body as VerifyRemovalRequestData;
+
+ const passkeyForAuthentication = await getPasskeyBy('credentialId', authenticationResponse.id);
+
+ if (!passkeyForAuthentication) {
+ return res.status(400).end('Passkey for authenticaiton not found.');
+ }
+
+ const { transports, credentialId, credentialPublicKey, credentialCounter } = passkeyForAuthentication;
+
+ const result = await verifyAuthenticationResponse({
+ response: authenticationResponse,
+ expectedRPID: challengeSession.rpId,
+ expectedOrigin: challengeSession.origin,
+ expectedChallenge: challengeSession.challenge,
+ credential: {
+ publicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
+ counter: credentialCounter,
+ transports,
+ id: credentialId,
+ },
+ requireUserVerification: true,
+ });
+
+ logger.debug('verifyAuthenticationResponse:', result);
+
+ if (!result.verified) {
+ return res.status(401).end('User not verified.');
+ }
+
+ const passkeyForRemoval = await getPasskey(passkeyId);
+
+ if (!passkeyForRemoval) {
+ return res.status(400).end('Passkey for removal not found.');
+ }
+
+ const userId = passkeyForRemoval.userId;
+
+ await removeUserPasskey(userId, passkeyForRemoval.id);
+
+ const userPasskeys = await getPasskeys(userId);
+
+ if (userPasskeys.length === 0) {
+ const customToken = await revokenAndCreateCustomUserToken(userId, { [tokenClaims.MFA_ENABLED]: false });
+
+ res.status(200).json({
+ mfa: false,
+ customToken,
+ passkey: passkeyForRemoval,
+ });
+ } else {
+ res.status(200).json({
+ mfa: true,
+ passkey: passkeyForRemoval,
+ });
+ }
+ } catch (error) {
+ logger.error(error);
+
+ res.status(500).end(
+ error instanceof Error && env.NEXT_PUBLIC_NODE_ENV !== 'production'
+ ? error.message
+ : 'Internal Server Error',
+ );
+ }
+}
diff --git a/examples/webauthn-upgrade/src/pages/index.tsx b/examples/webauthn-upgrade/src/pages/index.tsx
new file mode 100644
index 0000000..efcfa1c
--- /dev/null
+++ b/examples/webauthn-upgrade/src/pages/index.tsx
@@ -0,0 +1,3 @@
+import { WebAuthnUpgradeExamplePage } from '~components/WebAuthnUpgradeExamplePage';
+
+export default WebAuthnUpgradeExamplePage;
diff --git a/examples/webauthn-upgrade/src/server/constans/tokenClaims.ts b/examples/webauthn-upgrade/src/server/constans/tokenClaims.ts
new file mode 100644
index 0000000..0de96e5
--- /dev/null
+++ b/examples/webauthn-upgrade/src/server/constans/tokenClaims.ts
@@ -0,0 +1,3 @@
+export const tokenClaims = {
+ MFA_ENABLED: 'mfa_enabled',
+} as const;
diff --git a/examples/webauthn-upgrade/src/server/services/auth/index.ts b/examples/webauthn-upgrade/src/server/services/auth/index.ts
new file mode 100644
index 0000000..a2d49fb
--- /dev/null
+++ b/examples/webauthn-upgrade/src/server/services/auth/index.ts
@@ -0,0 +1,9 @@
+import { auth } from '@workspace/common/server/config/firebase';
+
+export async function revokenAndCreateCustomUserToken(userId: string, claims: TokenClaims) {
+ await auth().revokeRefreshTokens(userId);
+
+ const customToken = await auth().createCustomToken(userId, claims);
+
+ return customToken;
+}
diff --git a/examples/webauthn-upgrade/src/server/utils/parseAndVerifyIdTokenForMFA.ts b/examples/webauthn-upgrade/src/server/utils/parseAndVerifyIdTokenForMFA.ts
new file mode 100644
index 0000000..447a5e5
--- /dev/null
+++ b/examples/webauthn-upgrade/src/server/utils/parseAndVerifyIdTokenForMFA.ts
@@ -0,0 +1,28 @@
+import type { DecodedIdToken } from 'firebase-admin/auth';
+
+import { getPasskeys } from '@workspace/common/server/services/passkeys';
+import { parseAndVerifyIdToken } from '@workspace/common/server/utils';
+
+import { tokenClaims } from '~server/constans/tokenClaims';
+
+export type DecodedIdTokenForMFA = DecodedIdToken & {
+ mfa_enabled: boolean;
+};
+
+export async function parseAndVerifyIdTokenForMFA(authorizationHeader: string | undefined) {
+ const decodedIdToken = await parseAndVerifyIdToken(authorizationHeader);
+
+ if (!decodedIdToken || !decodedIdToken.email_verified) {
+ return null;
+ }
+
+ const userPasskeys = await getPasskeys(decodedIdToken.uid);
+
+ const mfaEnabled = decodedIdToken[tokenClaims.MFA_ENABLED];
+
+ if ((mfaEnabled && userPasskeys.length > 0) || (!mfaEnabled && userPasskeys.length === 0)) {
+ return decodedIdToken as DecodedIdTokenForMFA;
+ }
+
+ return null;
+}
diff --git a/examples/webauthn-upgrade/tsconfig.json b/examples/webauthn-upgrade/tsconfig.json
new file mode 100755
index 0000000..c944d8b
--- /dev/null
+++ b/examples/webauthn-upgrade/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "@tooling/typescript/nextjs",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "~*": [
+ "./src/*",
+ ],
+ "~public/*": [
+ "./public/*"
+ ],
+ "@workspace/common/*": [
+ "../../packages/common/dist/*"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ]
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 0562cad..23b2293 100644
--- a/package.json
+++ b/package.json
@@ -1,22 +1,22 @@
{
"name": "with-webauthn",
"description": "A project with full-stack WebAuthn API examples.",
- "packageManager": "yarn@4.5.0",
+ "packageManager": "yarn@4.5.3+sha512.3003a14012e2987072d244c720506549c1aab73ee728208f1b2580a9fd67b92d61ba6b08fe93f6dce68fd771e3af1e59a0afa28dd242dd0940d73b95fedd4e90",
"private": true,
- "type": "commonjs",
+ "type": "module",
"engines": {
- "node": "20"
+ "node": "22"
},
"scripts": {
"postinstall": "turbo telemetry disable",
- "build": "turbo run build",
- "dev": "turbo run dev --concurrency 100%",
+ "build": "yarn workspace @workspace/common build && turbo run build --parallel",
+ "dev": "yarn workspace @workspace/common build && turbo run dev --concurrency 100%",
"cir-dep": "turbo run cir-dep --parallel",
"test:ci": "turbo run test:ci",
"lint": "turbo run lint --parallel",
"lint:fix": "turbo run lint --parallel -- --fix",
"format": "turbo run format --parallel",
- "audit": "yarn npm audit --severity moderate --environment production --all",
+ "audit": "yarn npm audit --severity moderate --all",
"pre-commit": "prettier-format --staged --log-level=log && turbo run lint --parallel --force -- --staged --fix",
"prepare": "husky"
},
@@ -30,8 +30,8 @@
"@tooling/prettier": "workspace:*",
"@tooling/typescript": "workspace:*",
"dotenv": "16.4.5",
- "husky": "9.1.6",
- "turbo": "2.2.3"
+ "husky": "9.1.7",
+ "turbo": "2.3.3"
},
"prettier": "@tooling/prettier/config",
"license": "GPL-3.0-only",
diff --git a/packages/common/package.json b/packages/common/package.json
index 788156e..224b8c0 100644
--- a/packages/common/package.json
+++ b/packages/common/package.json
@@ -8,38 +8,42 @@
"lint:fix": "yarn lint --fix",
"format": "prettier-format",
"cir-dep": "check-cir-deps .",
- "build": "tsc && tsc-alias"
+ "build": "tsc && tsc-alias",
+ "clean": "rm -rf dist .cache",
+ "dev": "tsc -w & tsc-alias -w"
},
"dependencies": {
"@ackee/antonio-core": "5.0.0",
- "@emotion/react": "11.13.3",
+ "@emotion/react": "11.13.5",
"@emotion/server": "11.11.0",
- "@emotion/styled": "11.13.0",
+ "@emotion/styled": "11.13.5",
"@hookform/resolvers": "3.9.1",
- "@mui/icons-material": "6.1.6",
+ "@mui/icons-material": "6.1.8",
"@mui/lab": "6.0.0-beta.14",
- "@mui/material": "6.1.6",
- "@sentry/nextjs": "8.37.1",
+ "@mui/material": "6.1.8",
+ "@mui/system": "6.1.8",
+ "@sentry/nextjs": "8.41.0",
"@simplewebauthn/browser": "11.0.0",
"@simplewebauthn/server": "11.0.0",
"@t3-oss/env-nextjs": "0.11.1",
- "@tanstack/react-query": "5.59.20",
- "@tanstack/react-query-devtools": "5.59.20",
+ "@tanstack/react-query": "5.61.5",
+ "@tanstack/react-query-devtools": "5.61.5",
"@workspace/logger": "workspace:*",
- "cookie": "1.0.1",
+ "cookie": "1.0.2",
"core-js": "3.39.0",
- "firebase": "11.0.1",
- "firebase-admin": "12.7.0",
+ "firebase": "11.0.2",
+ "firebase-admin": "13.0.1",
"next": "15.0.3",
"normalize.css": "8.0.1",
+ "nuqs": "2.2.3",
"radash": "12.1.0",
"react": "18.3.1",
"react-dom": "18.3.1",
- "react-hook-form": "7.53.1",
- "react-intl": "6.8.7",
+ "react-hook-form": "7.53.2",
+ "react-intl": "7.0.1",
"react-toastify": "10.0.6",
"reset.css": "2.0.2",
- "typescript": "5.6.3",
+ "typescript": "5.7.2",
"zod": "3.23.8"
},
"devDependencies": {
diff --git a/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.styles.ts b/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.styles.ts
new file mode 100644
index 0000000..00a4e3b
--- /dev/null
+++ b/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.styles.ts
@@ -0,0 +1,9 @@
+import { styled } from '@mui/material';
+
+export const Empty = styled('section')(({ theme }) => ({
+ padding: theme.spacing(2),
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: theme.spacing(3),
+}));
diff --git a/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.tsx b/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.tsx
new file mode 100644
index 0000000..7fb3f0e
--- /dev/null
+++ b/packages/common/src/client/api/components/QueryEmpty/QueryEmpty.tsx
@@ -0,0 +1,71 @@
+import type { ReactNode } from 'react';
+import type { RequestResult } from '@ackee/antonio-core';
+import { Refresh } from '@mui/icons-material';
+import type { UseQueryResult } from '@tanstack/react-query';
+
+import { Button, Icon, Words, type AlertProps } from '~client/ui-kit';
+
+import { isAnyResultLoading } from '../QueryLoader';
+import { Empty } from './QueryEmpty.styles';
+
+type Result = UseQueryResult>;
+
+export interface QueryEmptyProps extends Omit {
+ children: React.ReactNode;
+ result: T;
+ message: ReactNode;
+ isEmpty?: (result: T) => boolean;
+ retry?: boolean;
+}
+
+const isResultEmpty = (result: Result) => {
+ const isEmptyArray = Array.isArray(result.data) && result.data.length === 0;
+ const isEmptyObject = !Array.isArray(result.data) && !result.data;
+
+ return result.status === 'success' && result.data && (isEmptyArray || isEmptyObject);
+};
+
+const isAnyEmpty = (results: Result | Result[]) => {
+ return Array.isArray(results) ? results.some(isResultEmpty) : isResultEmpty(results);
+};
+
+export const QueryEmpty = ({
+ children,
+ result,
+ message,
+ isEmpty = isAnyEmpty,
+ retry = false,
+ ...rest
+}: QueryEmptyProps) => {
+ if (isEmpty(result)) {
+ return (
+
+ ({ color: theme.palette.text.secondary })}>
+ {message}
+
+ {retry && (
+
+ )}
+
+ );
+ }
+
+ return <>{children}>;
+};
diff --git a/packages/common/src/client/api/components/QueryEmpty/index.ts b/packages/common/src/client/api/components/QueryEmpty/index.ts
new file mode 100644
index 0000000..d009366
--- /dev/null
+++ b/packages/common/src/client/api/components/QueryEmpty/index.ts
@@ -0,0 +1 @@
+export * from './QueryEmpty';
diff --git a/packages/common/src/client/api/components/QueryError/QueryError.tsx b/packages/common/src/client/api/components/QueryError/QueryError.tsx
index fb6126b..07da1a0 100644
--- a/packages/common/src/client/api/components/QueryError/QueryError.tsx
+++ b/packages/common/src/client/api/components/QueryError/QueryError.tsx
@@ -11,16 +11,16 @@ export interface QueryErrorProps extends Omit
message: string;
}
-function getError(result: QueryErrorProps['result']) {
+function hasError(result: QueryErrorProps['result']) {
if (Array.isArray(result)) {
- return result.find(r => r.error) ?? null;
+ return result.some(r => r.status === 'error');
}
- return result.error ?? null;
+ return result.status === 'error';
}
export const QueryError = ({ result, children = null, ...rest }: QueryErrorProps) => {
- if (getError(result)) {
+ if (hasError(result)) {
return (
} {...rest}>
{rest.message}
diff --git a/packages/common/src/client/api/components/index.ts b/packages/common/src/client/api/components/index.ts
index f6d1d99..4543ee7 100644
--- a/packages/common/src/client/api/components/index.ts
+++ b/packages/common/src/client/api/components/index.ts
@@ -1,3 +1,4 @@
export * from './AppQueryProvider';
+export * from './QueryEmpty';
export * from './QueryError';
export * from './QueryLoader';
diff --git a/packages/common/src/client/clipboard/hooks/useCopyTextToClipboard.ts b/packages/common/src/client/clipboard/hooks/useCopyTextToClipboard.ts
index de837a9..fd7c47c 100644
--- a/packages/common/src/client/clipboard/hooks/useCopyTextToClipboard.ts
+++ b/packages/common/src/client/clipboard/hooks/useCopyTextToClipboard.ts
@@ -1,7 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { useSnack } from '~client/snackbar/hooks';
-import { logger } from '~logger';
export function useCopyTextToClipboard() {
const snack = useSnack();
@@ -16,9 +15,6 @@ export function useCopyTextToClipboard() {
await navigator.clipboard.write([clipboardItem]);
},
- onError(error) {
- logger.error(error);
- },
onSuccess: () => {
snack('success', 'Copied to clipboard');
},
diff --git a/packages/common/src/client/core/components/App.tsx b/packages/common/src/client/core/components/App.tsx
index cac2f63..e1c71c5 100644
--- a/packages/common/src/client/core/components/App.tsx
+++ b/packages/common/src/client/core/components/App.tsx
@@ -3,6 +3,7 @@ import 'reset.css';
import type { AppProps } from 'next/app';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import { NuqsAdapter } from 'nuqs/adapters/next/pages';
import { IntlProvider } from 'react-intl';
import { AppQueryProvider } from '~client/api/components';
@@ -22,7 +23,9 @@ export function App({ Component, pageProps: { dehydratedState, ...pageProps }, e
-
+
+
+
diff --git a/packages/common/src/client/core/components/Document.tsx b/packages/common/src/client/core/components/Document.tsx
index e72bfad..37f4cb5 100644
--- a/packages/common/src/client/core/components/Document.tsx
+++ b/packages/common/src/client/core/components/Document.tsx
@@ -17,10 +17,8 @@ function MyDocument({ emotionStyleTags }: MyDocumentProps) {
name='description'
content='A full-stack WebAuthn example of creating a passkey (attestation ceremony) and then retrieving it (assertation ceremony) with Firebase Firestore integration to store the passkey and Firebase Auth for issuing a JWT token.'
/>
- {/* TODO: */}
{/* */}
- {/* TODO: */}
{/* */}
{/* */}
{/* */}
diff --git a/packages/common/src/client/env/env.mjs b/packages/common/src/client/env/env.mjs
index 8858de8..92cac5e 100644
--- a/packages/common/src/client/env/env.mjs
+++ b/packages/common/src/client/env/env.mjs
@@ -21,7 +21,10 @@ export const env = createEnv({
NEXT_PUBLIC_FIREBASE_APP_ID: z.string(),
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: z.string(),
- NEXT_PUBLIC_FIRBEASE_DB_ID: z.string().optional(),
+ NEXT_PUBLIC_FIREBASE_DB_ID: z.string().optional(),
+
+ NEXT_PUBLIC_DEFAULT_EXAMPLE_ORIGIN: z.string().url(),
+ NEXT_PUBLIC_UPGRADE_EXAMPLE_ORIGIN: z.string().url(),
},
/**
@@ -41,12 +44,16 @@ export const env = createEnv({
NEXT_PUBLIC_FIREBASE_APP_ID: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
- NEXT_PUBLIC_FIRBEASE_DB_ID: process.env.NEXT_PUBLIC_FIRBEASE_DB_ID,
+ NEXT_PUBLIC_FIREBASE_DB_ID: process.env.NEXT_PUBLIC_FIREBASE_DB_ID,
// Dev
NEXT_PUBLIC_DEV_RETRY_QUERIES: process.env.NEXT_PUBLIC_DEV_RETRY_QUERIES,
NEXT_PUBLIC_DEV_SENTRY_DISABLED: process.env.NEXT_PUBLIC_DEV_SENTRY_DISABLED,
+
+ // Example origins
+ NEXT_PUBLIC_DEFAULT_EXAMPLE_ORIGIN: process.env.NEXT_PUBLIC_DEFAULT_EXAMPLE_ORIGIN,
+ NEXT_PUBLIC_UPGRADE_EXAMPLE_ORIGIN: process.env.NEXT_PUBLIC_UPGRADE_EXAMPLE_ORIGIN,
},
- skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION || process.env.npm_lifecycle_event === 'lint',
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION || process.env.npm_lifecycle_event === 'lint',
});
diff --git a/packages/common/src/client/webauthn/utils/parseWebAuthnError.ts b/packages/common/src/client/errors/index.ts
similarity index 71%
rename from packages/common/src/client/webauthn/utils/parseWebAuthnError.ts
rename to packages/common/src/client/errors/index.ts
index a6c5e9c..3113b30 100644
--- a/packages/common/src/client/webauthn/utils/parseWebAuthnError.ts
+++ b/packages/common/src/client/errors/index.ts
@@ -1,6 +1,8 @@
import { isAntonioError } from '@ackee/antonio-core';
import { WebAuthnError } from '@simplewebauthn/browser';
+import { parseCustomFirebaseErrorMessage } from '~client/firebase/errors';
+
function isAbortError(error: unknown): error is {
name: 'AbortError';
message: string;
@@ -9,7 +11,16 @@ function isAbortError(error: unknown): error is {
return error instanceof Error && error.name === 'AbortError' && 'message' in error && 'stack' in error;
}
-export async function parseWebAuthnError(error: unknown) {
+export async function parseUnknownError(error: unknown) {
+ const firebaseError = parseCustomFirebaseErrorMessage(error);
+
+ if (firebaseError) {
+ return {
+ type: 'FIREBASE',
+ message: firebaseError,
+ } as const;
+ }
+
if (isAntonioError(error)) {
const errroData = error.response.bodyUsed ? error.data : await error.response.text();
@@ -35,6 +46,6 @@ export async function parseWebAuthnError(error: unknown) {
return {
type: 'UNKNOWN',
- message: 'An unknown error occurred',
+ message: 'An unknown error occurred. Please try it again later.',
} as const;
}
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthLoader/AuthLoader.tsx b/packages/common/src/client/example/components/ExampleAuth/AuthLoader/AuthLoader.tsx
deleted file mode 100644
index 112cf34..0000000
--- a/packages/common/src/client/example/components/ExampleAuth/AuthLoader/AuthLoader.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Loader, LoaderContainer } from '~client/ui-kit';
-
-export interface AuthLoaderProps {}
-
-export const AuthLoader = ({}: AuthLoaderProps) => {
- return (
-
-
-
- );
-};
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthLoader/index.ts b/packages/common/src/client/example/components/ExampleAuth/AuthLoader/index.ts
deleted file mode 100644
index a7d942c..0000000
--- a/packages/common/src/client/example/components/ExampleAuth/AuthLoader/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AuthLoader';
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/AuthProvider.tsx b/packages/common/src/client/example/components/ExampleAuth/AuthProvider/AuthProvider.tsx
deleted file mode 100644
index e6d93bd..0000000
--- a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/AuthProvider.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-import { onAuthStateChanged } from 'firebase/auth';
-
-import { api } from '~client/api/fetcher';
-import { auth } from '~client/firebase/config';
-
-import { AuthContext, type AuthSession } from './contexts';
-
-export interface AuthProviderProps {
- children: React.ReactNode;
- Loader?: React.ComponentType;
-}
-
-export const AuthProvider = ({ children, Loader = () => null }: AuthProviderProps) => {
- const [session, setSession] = useState({
- state: 'loading',
- authUser: null,
- });
-
- const interceptorId = useRef(null);
-
- useEffect(() => {
- return onAuthStateChanged(auth(), async user => {
- if (user) {
- interceptorId.current = api().interceptors.request.use(undefined, async request => {
- const idToken = await user.getIdToken();
-
- request.headers.set('Authorization', `Bearer ${idToken}`);
-
- return request;
- });
-
- setSession({
- authUser: user,
- state: 'authenticated',
- });
- } else {
- if (interceptorId.current) {
- api().interceptors.request.eject(interceptorId.current);
- }
-
- setSession({
- authUser: null,
- state: 'unauthenticated',
- });
- }
- });
- }, []);
-
- if (!session || session.state === 'loading') {
- return ;
- }
-
- return {children};
-};
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/index.ts b/packages/common/src/client/example/components/ExampleAuth/AuthProvider/index.ts
deleted file mode 100644
index 971096f..0000000
--- a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AuthProvider';
diff --git a/packages/common/src/client/example/components/ExampleAuth/ExampleAuth.tsx b/packages/common/src/client/example/components/ExampleAuth/ExampleAuth.tsx
index 9b76972..c53d0c2 100644
--- a/packages/common/src/client/example/components/ExampleAuth/ExampleAuth.tsx
+++ b/packages/common/src/client/example/components/ExampleAuth/ExampleAuth.tsx
@@ -1,12 +1,68 @@
-import type { ReactNode } from 'react';
+import { useEffect, useRef, useState, type ReactNode } from 'react';
+import { onAuthStateChanged, type User } from 'firebase/auth';
-import { AuthLoader } from './AuthLoader';
-import { AuthProvider } from './AuthProvider';
+import { api } from '~client/api/fetcher';
+import { auth } from '~client/firebase/config';
+import { Loader, LoaderContainer } from '~client/ui-kit';
+
+import { AuthContext, type AuthSession } from './contexts';
export interface ExampleAuthProps {
children: ReactNode;
}
+const hasOnlyEmailProviderWithUnVerifiedEmail = (user: User) => {
+ return user.providerData.length === 1 && user.providerData[0].providerId === 'password' && !user.emailVerified;
+};
+
export const ExampleAuth = ({ children }: ExampleAuthProps) => {
- return {children};
+ const [session, setSession] = useState({
+ state: 'loading',
+ authUser: null,
+ });
+
+ const interceptorId = useRef(null);
+
+ useEffect(() => {
+ return onAuthStateChanged(auth(), async user => {
+ if (!user || hasOnlyEmailProviderWithUnVerifiedEmail(user)) {
+ if (interceptorId.current) {
+ api().interceptors.request.eject(interceptorId.current);
+ }
+
+ setSession({
+ authUser: null,
+ state: 'unauthenticated',
+ });
+
+ return;
+ }
+
+ interceptorId.current = api().interceptors.request.use(undefined, async request => {
+ const idToken = await user.getIdToken();
+
+ request.headers.set('Authorization', `Bearer ${idToken}`);
+
+ return request;
+ });
+
+ const idTokenResult = await user.getIdTokenResult();
+
+ setSession({
+ authUser: user,
+ tokenClaims: idTokenResult.claims,
+ state: 'authenticated',
+ });
+ });
+ }, []);
+
+ if (!session || session.state === 'loading') {
+ return (
+
+
+
+ );
+ }
+
+ return {children};
};
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/contexts/index.ts b/packages/common/src/client/example/components/ExampleAuth/contexts/index.ts
similarity index 83%
rename from packages/common/src/client/example/components/ExampleAuth/AuthProvider/contexts/index.ts
rename to packages/common/src/client/example/components/ExampleAuth/contexts/index.ts
index 1484d1f..cd760f5 100644
--- a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/contexts/index.ts
+++ b/packages/common/src/client/example/components/ExampleAuth/contexts/index.ts
@@ -1,5 +1,5 @@
import { createContext, type Dispatch, type SetStateAction } from 'react';
-import type { User } from 'firebase/auth';
+import type { ParsedToken, User } from 'firebase/auth';
export type AuthSession =
| {
@@ -9,6 +9,7 @@ export type AuthSession =
| {
state: 'authenticated';
authUser: User;
+ tokenClaims: ParsedToken;
};
export type AuthContextType = Readonly<{
diff --git a/packages/common/src/client/example/components/ExampleAuth/hooks/useAuthTokenClaims.ts b/packages/common/src/client/example/components/ExampleAuth/hooks/useAuthTokenClaims.ts
new file mode 100644
index 0000000..99ef9ec
--- /dev/null
+++ b/packages/common/src/client/example/components/ExampleAuth/hooks/useAuthTokenClaims.ts
@@ -0,0 +1,7 @@
+import { useExampleAuthSession } from './useExampleAuthSession';
+
+export function useAuthTokenClaims() {
+ const { session } = useExampleAuthSession();
+
+ return session.state === 'authenticated' ? session.tokenClaims : null;
+}
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/hooks/useAuthUser.ts b/packages/common/src/client/example/components/ExampleAuth/hooks/useAuthUser.ts
similarity index 100%
rename from packages/common/src/client/example/components/ExampleAuth/AuthProvider/hooks/useAuthUser.ts
rename to packages/common/src/client/example/components/ExampleAuth/hooks/useAuthUser.ts
diff --git a/packages/common/src/client/example/components/ExampleAuth/AuthProvider/hooks/useExampleAuthSession.ts b/packages/common/src/client/example/components/ExampleAuth/hooks/useExampleAuthSession.ts
similarity index 100%
rename from packages/common/src/client/example/components/ExampleAuth/AuthProvider/hooks/useExampleAuthSession.ts
rename to packages/common/src/client/example/components/ExampleAuth/hooks/useExampleAuthSession.ts
diff --git a/packages/common/src/client/example/components/ExampleAuth/index.ts b/packages/common/src/client/example/components/ExampleAuth/index.ts
index c243689..dd3abf5 100644
--- a/packages/common/src/client/example/components/ExampleAuth/index.ts
+++ b/packages/common/src/client/example/components/ExampleAuth/index.ts
@@ -1,3 +1,4 @@
-export * from './AuthProvider/hooks/useAuthUser';
-export * from './AuthProvider/hooks/useExampleAuthSession';
export * from './ExampleAuth';
+export * from './hooks/useAuthTokenClaims';
+export * from './hooks/useAuthUser';
+export * from './hooks/useExampleAuthSession';
diff --git a/packages/common/src/client/example/components/ExampleFrame/ExampleFrame.styles.ts b/packages/common/src/client/example/components/ExampleFrame/ExampleFrame.styles.ts
index 50189dd..ca1c121 100644
--- a/packages/common/src/client/example/components/ExampleFrame/ExampleFrame.styles.ts
+++ b/packages/common/src/client/example/components/ExampleFrame/ExampleFrame.styles.ts
@@ -3,7 +3,7 @@ import { Box, styled } from '@mui/material';
export const Container = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
- marginTop: theme.spacing(6),
+ marginTop: theme.spacing(4),
}));
export const Frame = styled(Box, {
@@ -23,4 +23,6 @@ export const Frame = styled(Box, {
display: 'grid',
gridTemplateRows: 'auto 1fr',
boxShadow: '0px 8px 25px rgba(0, 0, 0, 0.1)',
+ overflow: 'hidden',
+ position: 'relative',
}));
diff --git a/packages/common/src/client/example/components/ExampleHeader/ExampleHeader.tsx b/packages/common/src/client/example/components/ExampleHeader/ExampleHeader.tsx
index 254d451..171519e 100644
--- a/packages/common/src/client/example/components/ExampleHeader/ExampleHeader.tsx
+++ b/packages/common/src/client/example/components/ExampleHeader/ExampleHeader.tsx
@@ -1,14 +1,19 @@
import type { ReactNode } from 'react';
+import Link from 'next/link';
+import { GitHub } from '@mui/icons-material';
import { Box } from '@mui/material';
+import { Button } from '~client/ui-kit';
+
import { Words } from '../../../ui-kit/components/Words';
export interface ExampleHeaderProps {
title: ReactNode;
description: ReactNode;
+ githubUrl: string;
}
-export const ExampleHeader = ({ title, description }: ExampleHeaderProps) => {
+export const ExampleHeader = ({ title, description, githubUrl }: ExampleHeaderProps) => {
return (
<>
@@ -22,6 +27,19 @@ export const ExampleHeader = ({ title, description }: ExampleHeaderProps) => {
>
{description}
+
+ }
+ variant='text'
+ sx={theme => ({ color: theme.palette.text.primary })}
+ >
+ View on Github
+
+
>
);
};
diff --git a/packages/common/src/client/example/components/ExampleRouter/createExampleRouter.tsx b/packages/common/src/client/example/components/ExampleRouter/createExampleRouter.tsx
index 43a5849..4c73e2c 100644
--- a/packages/common/src/client/example/components/ExampleRouter/createExampleRouter.tsx
+++ b/packages/common/src/client/example/components/ExampleRouter/createExampleRouter.tsx
@@ -1,28 +1,27 @@
import { createContext, createElement, useContext, useState, type ReactElement, type ReactNode } from 'react';
+import { Loader, LoaderContainer } from '~client/ui-kit';
+
export type Pathname = string;
-export type RenderRoute = () => ReactElement;
-export type UnknownRoutes = Readonly>;
+export type RenderRouteComponent = () => ReactElement;
+export type UnknownRoutes = Readonly>;
export type ExampleRouterContextValue = {
routes: Routes;
- currentRoute: keyof Routes;
+ currentRoute: keyof Routes | null;
redirect: (route: keyof Routes) => void;
};
export interface ExampleRouterProps {
children: ReactNode;
- initialRoute: keyof Routes;
routes: Routes;
}
export function createExampleRouter() {
- type Route = keyof Routes;
-
const ExampleRouterContext = createContext | undefined>(undefined);
- const ExampleRouter = ({ children, initialRoute, routes }: ExampleRouterProps) => {
- const [currentRoute, setCurrentRoute] = useState(initialRoute);
+ const ExampleRouter = ({ children, routes }: ExampleRouterProps) => {
+ const [currentRoute, setCurrentRoute] = useState(null);
return (
@@ -44,6 +43,14 @@ export function createExampleRouter() {
function CurrentExampleRoute() {
const { routes, currentRoute } = useExampleRouter();
+ if (!currentRoute) {
+ return (
+
+
+
+ );
+ }
+
return createElement(routes[currentRoute]);
}
diff --git a/packages/common/src/client/firebase/config/index.ts b/packages/common/src/client/firebase/config/index.ts
index 7dfb6e2..e7c62e1 100644
--- a/packages/common/src/client/firebase/config/index.ts
+++ b/packages/common/src/client/firebase/config/index.ts
@@ -8,6 +8,7 @@ import { env } from '~client/env';
let app: ReturnType;
export const getFirebaseApp = () => {
+ // @ts-ignore
if (app) {
return app;
}
@@ -28,11 +29,17 @@ export const getFirebaseApp = () => {
);
};
-export const auth = () => getAuth(getFirebaseApp());
+export const auth = () => {
+ const auth = getAuth(getFirebaseApp());
+
+ auth.useDeviceLanguage();
+
+ return auth;
+};
export const analytics = () => getAnalytics(getFirebaseApp());
export const db = () => {
const app = getFirebaseApp();
- const databaseId = env.NEXT_PUBLIC_FIRBEASE_DB_ID;
+ const databaseId = env.NEXT_PUBLIC_FIREBASE_DB_ID;
return databaseId ? getFirestore(app, databaseId) : getFirestore(app);
};
diff --git a/packages/common/src/client/firebase/constants/errors.ts b/packages/common/src/client/firebase/constants/errors.ts
new file mode 100644
index 0000000..9a26130
--- /dev/null
+++ b/packages/common/src/client/firebase/constants/errors.ts
@@ -0,0 +1,9 @@
+import type { FirebaseAuthErrorCodeKey } from '../errors';
+
+export const customFirebaseErrors = {
+ 'auth/email-already-in-use': 'The email address is already in use by another account.',
+ 'auth/app-deleted': 'The user corresponding to the provided email has been deleted.',
+ 'auth/user-disabled': 'The user corresponding to the provided email has been disabled.',
+} as const satisfies Partial>;
+
+export type CustomFirebaseErrors = typeof customFirebaseErrors;
diff --git a/packages/common/src/client/firebase/constants/index.ts b/packages/common/src/client/firebase/constants/index.ts
new file mode 100644
index 0000000..f72bc43
--- /dev/null
+++ b/packages/common/src/client/firebase/constants/index.ts
@@ -0,0 +1 @@
+export * from './errors';
diff --git a/packages/common/src/client/firebase/errors/index.ts b/packages/common/src/client/firebase/errors/index.ts
new file mode 100644
index 0000000..b4cc798
--- /dev/null
+++ b/packages/common/src/client/firebase/errors/index.ts
@@ -0,0 +1,38 @@
+import { FirebaseError } from 'firebase/app';
+import { AuthErrorCodes } from 'firebase/auth';
+
+import { customFirebaseErrors, type CustomFirebaseErrors } from '../constants';
+
+const isFirebaseError = (error: unknown): error is FirebaseError => error instanceof FirebaseError;
+
+export type FirebaseAuthErrorCodes = typeof AuthErrorCodes;
+
+export type FirebaseAuthErrorCodeKey = FirebaseAuthErrorCodes[keyof FirebaseAuthErrorCodes];
+
+const errorCodes = new Set(Object.keys(AuthErrorCodes)) as Set;
+
+/**
+ * @url https://firebase.google.com/docs/reference/js/v8/firebase.auth.Error
+ */
+export const isFirebaseErrorWithCodes = (
+ error: unknown,
+ codes: Codes,
+): error is FirebaseError & { code: FirebaseAuthErrorCodes[(typeof codes)[number]] } => {
+ return (
+ isFirebaseError(error) &&
+ codes.every(code => errorCodes.has(code)) &&
+ Object.values(AuthErrorCodes).includes(error.code)
+ );
+};
+
+export const parseCustomFirebaseErrorMessage = (error: unknown) => {
+ const codes = Object.keys(customFirebaseErrors) as (keyof CustomFirebaseErrors)[];
+
+ if (isFirebaseError(error) && codes.includes(error.code)) {
+ const code = error.code as keyof CustomFirebaseErrors;
+
+ return customFirebaseErrors[code];
+ }
+
+ return null;
+};
diff --git a/packages/common/src/client/form/components/Form/Form.tsx b/packages/common/src/client/form/components/Form/Form.tsx
index 4478052..dfd96ee 100644
--- a/packages/common/src/client/form/components/Form/Form.tsx
+++ b/packages/common/src/client/form/components/Form/Form.tsx
@@ -9,6 +9,7 @@ export interface FormProps) => Promise | void;
defaultValues?: UseFormProps['defaultValues'];
+ mode?: UseFormProps['mode'];
}
export const Form = ({
@@ -16,12 +17,13 @@ export const Form = ) => {
const resolver = useLocalizedResolver(schema);
const form = useForm({
resolver,
defaultValues,
- mode: 'onSubmit',
+ mode,
});
const submit = useMemo(() => form.handleSubmit(values => onSubmit(values, form)), [form, onSubmit]);
diff --git a/packages/common/src/client/form/errors/index.ts b/packages/common/src/client/form/errors/index.ts
index c43d53e..a3f45e2 100644
--- a/packages/common/src/client/form/errors/index.ts
+++ b/packages/common/src/client/form/errors/index.ts
@@ -1,4 +1,10 @@
export const formError = {
required: 'Required',
email: 'Please enter valid e-mail address.',
+ password: {
+ length: 'Inserted password must be at least 10 chars of length.',
+ casing: 'Inserted password must contain at least one capital letter.',
+ special: 'Inserted password must contain at least 1 special character.',
+ numerical: 'Inserted password must contain at least 1 number.',
+ },
} as const;
diff --git a/packages/common/src/client/form/validators/password.ts b/packages/common/src/client/form/validators/password.ts
index 26c13c9..7be0c51 100644
--- a/packages/common/src/client/form/validators/password.ts
+++ b/packages/common/src/client/form/validators/password.ts
@@ -1,5 +1,25 @@
import { z } from 'zod';
+import { formError } from '../errors';
import { required } from './general';
-export const newPassword = required.pipe(z.string().min(10));
+const includesOneDigits = /(?:\D*\d){1}/;
+const includesOneUpperCase = /(?=.*[A-Z])/;
+const includesOneSpecialChar = /[A-Za-z0-9 ]*[^A-Za-z0-9 ][A-Za-z0-9 ]*/;
+
+export const newPassword = required.pipe(
+ z
+ .string()
+ .min(10, formError.password.length)
+ .refine(value => includesOneDigits.test(value), {
+ message: formError.password.numerical,
+ })
+ .refine(value => includesOneUpperCase.test(value), {
+ message: formError.password.casing,
+ })
+ .refine(value => includesOneSpecialChar.test(value), {
+ message: formError.password.special,
+ }),
+);
+
+export const password = required;
diff --git a/packages/common/src/client/layout/components/MainHeader/MainHeader.tsx b/packages/common/src/client/layout/components/MainHeader/MainHeader.tsx
deleted file mode 100644
index a4b9097..0000000
--- a/packages/common/src/client/layout/components/MainHeader/MainHeader.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import Head from 'next/head';
-
-import { PageHeader } from '~client/ui-kit';
-
-export interface MainHeaderProps {
- pageTitle: string;
-}
-
-export const MainHeader = ({ pageTitle }: MainHeaderProps) => {
- return (
- <>
-
- {`${pageTitle} | With WebAuthn`}
-
- With WebAuthn demo
- >
- );
-};
diff --git a/packages/common/src/client/layout/components/MainHeader/index.ts b/packages/common/src/client/layout/components/MainHeader/index.ts
deleted file mode 100644
index 3d8a192..0000000
--- a/packages/common/src/client/layout/components/MainHeader/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './MainHeader';
diff --git a/packages/common/src/client/layout/components/PageDrawer/PageDrawer.styles.ts b/packages/common/src/client/layout/components/PageDrawer/PageDrawer.styles.ts
new file mode 100644
index 0000000..cc2f28f
--- /dev/null
+++ b/packages/common/src/client/layout/components/PageDrawer/PageDrawer.styles.ts
@@ -0,0 +1,5 @@
+import { Box, styled } from '~client/ui-kit';
+
+export const DrawerContent = styled(Box)(({ theme }) => ({
+ width: 375,
+}));
diff --git a/packages/common/src/client/layout/components/PageDrawer/PageDrawer.tsx b/packages/common/src/client/layout/components/PageDrawer/PageDrawer.tsx
new file mode 100644
index 0000000..8781b98
--- /dev/null
+++ b/packages/common/src/client/layout/components/PageDrawer/PageDrawer.tsx
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import Link from 'next/link';
+import { Close, Menu } from '@mui/icons-material';
+import { Divider, Drawer, IconButton, List, ListItem, ListItemButton, Stack } from '@mui/material';
+
+import { env } from '~client/env';
+import { Words } from '~client/ui-kit';
+
+import { DrawerContent } from './PageDrawer.styles';
+
+export const PageDrawer = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(!open)} size='large' aria-label='menu'>
+
+
+ setOpen(false)}>
+
+
+
+ WebAuthn Demos
+
+
+ setOpen(false)} size='large' aria-label='close'>
+
+
+
+
+
+
+ {
+ setOpen(false);
+ }}
+ >
+ Authenticate with passkeys
+
+
+
+ {
+ setOpen(false);
+ }}
+ >
+ Upgrade to passkeys
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/common/src/client/layout/components/PageDrawer/index.ts b/packages/common/src/client/layout/components/PageDrawer/index.ts
new file mode 100644
index 0000000..031897d
--- /dev/null
+++ b/packages/common/src/client/layout/components/PageDrawer/index.ts
@@ -0,0 +1 @@
+export * from './PageDrawer';
diff --git a/packages/common/src/client/layout/components/PageHeader/PageHeader.styles.ts b/packages/common/src/client/layout/components/PageHeader/PageHeader.styles.ts
new file mode 100644
index 0000000..a92af4d
--- /dev/null
+++ b/packages/common/src/client/layout/components/PageHeader/PageHeader.styles.ts
@@ -0,0 +1,32 @@
+import { Container, Icon, styled, Words } from '~client/ui-kit';
+
+export const StyledContainer = styled(Container)(({ theme }) => ({
+ paddingTop: theme.spacing(3),
+ paddingBottom: theme.spacing(3),
+ paddingLeft: `${theme.spacing(1)} !important`,
+ paddingRight: `${theme.spacing(1)} !important`,
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+}));
+
+export const LeftSide = styled('div')(({ theme }) => ({
+ display: 'grid',
+ gridTemplateColumns: 'auto auto',
+ gap: theme.spacing(1),
+ alignItems: 'center',
+}));
+
+export const PageTitle = styled(Words)(({ theme }) => ({
+ fontSize: '1.75rem',
+
+ [theme.breakpoints.down('sm')]: {
+ fontSize: theme.typography.h3.fontSize,
+ },
+}));
+
+export const IconGithub = styled(Icon)(({ theme }) => ({
+ color: '#000',
+ height: 32,
+ width: 32,
+}));
diff --git a/packages/common/src/client/layout/components/PageHeader/PageHeader.tsx b/packages/common/src/client/layout/components/PageHeader/PageHeader.tsx
new file mode 100644
index 0000000..e36a516
--- /dev/null
+++ b/packages/common/src/client/layout/components/PageHeader/PageHeader.tsx
@@ -0,0 +1,37 @@
+import Head from 'next/head';
+import Link from 'next/link';
+import { GitHub } from '@mui/icons-material';
+
+import { PageDrawer } from '../PageDrawer';
+import { IconGithub, LeftSide, PageTitle, StyledContainer } from './PageHeader.styles';
+
+export interface PageHeaderProps {
+ pageTitle: string;
+}
+
+export const PageHeader = ({ pageTitle }: PageHeaderProps) => {
+ return (
+ <>
+
+ {`${pageTitle} | With WebAuthn`}
+
+
+
+
+ With WebAuthn demos
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/common/src/client/ui-kit/components/PageHeader/index.ts b/packages/common/src/client/layout/components/PageHeader/index.ts
similarity index 100%
rename from packages/common/src/client/ui-kit/components/PageHeader/index.ts
rename to packages/common/src/client/layout/components/PageHeader/index.ts
diff --git a/packages/common/src/client/layout/components/index.ts b/packages/common/src/client/layout/components/index.ts
index 3d8a192..ba434ed 100644
--- a/packages/common/src/client/layout/components/index.ts
+++ b/packages/common/src/client/layout/components/index.ts
@@ -1 +1 @@
-export * from './MainHeader';
+export * from './PageHeader';
diff --git a/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.styles.ts b/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.styles.ts
new file mode 100644
index 0000000..4bce513
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.styles.ts
@@ -0,0 +1,10 @@
+import { styled } from '~client/ui-kit';
+
+export const Header = styled('header')(({ theme }) => ({
+ marginBottom: theme.spacing(2),
+ marginTop: theme.spacing(2),
+ display: 'grid',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ gridTemplateColumns: 'auto auto',
+}));
diff --git a/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.tsx b/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.tsx
new file mode 100644
index 0000000..d763a7c
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysHeader/PasskeysHeader.tsx
@@ -0,0 +1,28 @@
+import { Fingerprint } from '@mui/icons-material';
+import type { UseMutationResult } from '@tanstack/react-query';
+
+import { Button, Words } from '~client/ui-kit';
+
+import { Header } from './PasskeysHeader.styles';
+
+export interface PasskeysHeaderProps {
+ addPasskey: UseMutationResult;
+}
+
+export const PasskeysHeader = ({ addPasskey }: PasskeysHeaderProps) => {
+ return (
+
+ Passkeys
+
+
+ );
+};
diff --git a/packages/common/src/client/passkeys/components/PasskeysHeader/index.ts b/packages/common/src/client/passkeys/components/PasskeysHeader/index.ts
new file mode 100644
index 0000000..cde5059
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysHeader/index.ts
@@ -0,0 +1 @@
+export * from './PasskeysHeader';
diff --git a/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.styles.ts b/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.styles.ts
new file mode 100644
index 0000000..63062d7
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.styles.ts
@@ -0,0 +1,6 @@
+import { styled } from '@mui/material';
+
+export const List = styled('section')(({ theme }) => ({
+ display: 'grid',
+ rowGap: theme.spacing(3.5),
+}));
diff --git a/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.tsx b/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.tsx
new file mode 100644
index 0000000..c8152b6
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysList/PasskeysList.tsx
@@ -0,0 +1,36 @@
+import { QueryEmpty, QueryError, QueryLoader } from '~client/api/components';
+
+import { Passkey, type PasskeyProps } from '../Passkey/Passkey';
+import { useFetchPasskeys } from './hooks/useFetchPasskeys';
+import { List } from './PasskeysList.styles';
+
+export interface PasskeysListProps {
+ removePasskey: PasskeyProps['removePasskey'];
+ isPasskeyRemovable?: (index: number) => boolean;
+}
+
+export const PasskeysList = ({ removePasskey, isPasskeyRemovable = index => index > 0 }: PasskeysListProps) => {
+ const passkeysResult = useFetchPasskeys();
+
+ return (
+
+
+
+
+ {passkeysResult.data.map((passkey, index) => (
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/packages/common/src/client/passkeys/components/PasskeysList/hooks/useFetchPasskeys.ts b/packages/common/src/client/passkeys/components/PasskeysList/hooks/useFetchPasskeys.ts
new file mode 100644
index 0000000..778593d
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysList/hooks/useFetchPasskeys.ts
@@ -0,0 +1,16 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { useAuthUser } from '~client/example/components';
+import { fetchUserPasskeys } from '~client/firebase/services/passkeys';
+
+export function useFetchPasskeys() {
+ const authUser = useAuthUser();
+ const uid = authUser?.uid;
+
+ return useQuery({
+ queryKey: ['passkeys', uid],
+ queryFn: () => fetchUserPasskeys(uid!),
+ initialData: [],
+ enabled: Boolean(uid),
+ });
+}
diff --git a/packages/common/src/client/passkeys/components/PasskeysList/index.ts b/packages/common/src/client/passkeys/components/PasskeysList/index.ts
new file mode 100644
index 0000000..9419dfb
--- /dev/null
+++ b/packages/common/src/client/passkeys/components/PasskeysList/index.ts
@@ -0,0 +1 @@
+export * from './PasskeysList';
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.styles.ts b/packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.styles.ts
similarity index 82%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.styles.ts
rename to packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.styles.ts
index 14403f3..5f34d73 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.styles.ts
+++ b/packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.styles.ts
@@ -1,4 +1,4 @@
-import { styled } from '@workspace/common/client/ui-kit';
+import { styled } from '~client/ui-kit';
export const PasskeyDetails = styled('div')(({ theme }) => ({
display: 'grid',
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.tsx b/packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.tsx
similarity index 84%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.tsx
rename to packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.tsx
index 61e658a..453773b 100644
--- a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/PostRemovalDialog.tsx
+++ b/packages/common/src/client/passkeys/components/PostRemovalDialog/PostRemovalDialog.tsx
@@ -1,9 +1,10 @@
-import { Caption } from '@workspace/common/client/passkeys/components/Passkey/Caption';
-import { ProviderProfile } from '@workspace/common/client/passkeys/components/Passkey/ProviderProfile';
-import { Box, Button, Dialog, DialogActions, Icon, Stack, Words } from '@workspace/common/client/ui-kit';
-import { Warning } from '@workspace/common/client/ui-kit/icons';
-import type { Passkey } from '@workspace/common/types';
+import { Warning } from '@mui/icons-material';
+import { Box, Button, Dialog, DialogActions, Icon, Stack, Words } from '~client/ui-kit';
+import type { Passkey } from '~types';
+
+import { Caption } from '../Passkey/Caption';
+import { ProviderProfile } from '../Passkey/ProviderProfile';
import { PasskeyDetails } from './PostRemovalDialog.styles';
export interface PostRemovalDialogProps {
diff --git a/examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/index.ts b/packages/common/src/client/passkeys/components/PostRemovalDialog/index.ts
similarity index 100%
rename from examples/webauthn-default/src/components/WebAuthnDefaultExamplePage/DefaultExample/Passkeys/PostRemovalDialog/index.ts
rename to packages/common/src/client/passkeys/components/PostRemovalDialog/index.ts
diff --git a/packages/common/src/client/passkeys/components/index.ts b/packages/common/src/client/passkeys/components/index.ts
index 94c8d58..f1482f1 100644
--- a/packages/common/src/client/passkeys/components/index.ts
+++ b/packages/common/src/client/passkeys/components/index.ts
@@ -1 +1,4 @@
export * from './Passkey';
+export * from './PasskeysHeader';
+export * from './PasskeysList';
+export * from './PostRemovalDialog';
diff --git a/packages/common/src/client/ui-kit/components/Button/Button.styles.ts b/packages/common/src/client/ui-kit/components/Button/Button.styles.ts
new file mode 100644
index 0000000..eecc210
--- /dev/null
+++ b/packages/common/src/client/ui-kit/components/Button/Button.styles.ts
@@ -0,0 +1,26 @@
+import { LoadingButton } from '@mui/lab';
+import { styled } from '@mui/material';
+
+export const StyledButton = styled(LoadingButton)(({ theme, color, variant, size }) => ({
+ textTransform: 'none',
+ boxShadow: 'none',
+ fontSize: '1rem',
+ fontWeight: '500',
+
+ ...(size === 'large' && {
+ height: 48,
+ }),
+
+ ...(color === 'secondary' &&
+ variant === 'outlined' && {
+ color: theme.palette.text.secondary,
+ backgroundColor: theme.palette.common.white,
+ borderColor: theme.palette.text.secondary,
+
+ '&:hover': {
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.common.white,
+ borderColor: theme.palette.text.primary,
+ },
+ }),
+}));
diff --git a/packages/common/src/client/ui-kit/components/Button/Button.tsx b/packages/common/src/client/ui-kit/components/Button/Button.tsx
index 45af374..900b83a 100644
--- a/packages/common/src/client/ui-kit/components/Button/Button.tsx
+++ b/packages/common/src/client/ui-kit/components/Button/Button.tsx
@@ -1,23 +1,13 @@
-import { LoadingButton, type LoadingButtonProps } from '@mui/lab';
+import type { LoadingButtonProps } from '@mui/lab';
+
+import { StyledButton } from './Button.styles';
export interface ButtonProps extends LoadingButtonProps {}
export const Button = ({ children, ...props }: ButtonProps) => {
return (
-
+
{children}
-
+
);
};
diff --git a/packages/common/src/client/ui-kit/components/PageHeader/PageHeader.tsx b/packages/common/src/client/ui-kit/components/PageHeader/PageHeader.tsx
deleted file mode 100644
index 532e9b9..0000000
--- a/packages/common/src/client/ui-kit/components/PageHeader/PageHeader.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import Link from 'next/link';
-import { GitHub } from '@mui/icons-material';
-import { Container } from '@mui/material';
-
-import { Icon } from '../Icon';
-import { Words } from '../Words';
-
-export interface PageHeaderProps {
- children?: React.ReactNode;
-}
-
-export const PageHeader = ({ children }: PageHeaderProps) => {
- return (
- ({
- padding: theme.spacing(3, 1.5),
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- })}
- component='header'
- >
- ({
- [theme.breakpoints.down('sm')]: {
- fontSize: theme.typography.h3.fontSize,
- },
- })}
- >
- {children}
-
-
-
-
-
- );
-};
diff --git a/packages/common/src/client/ui-kit/components/Words/Words.tsx b/packages/common/src/client/ui-kit/components/Words/Words.tsx
index c963908..c92358c 100644
--- a/packages/common/src/client/ui-kit/components/Words/Words.tsx
+++ b/packages/common/src/client/ui-kit/components/Words/Words.tsx
@@ -1,11 +1,9 @@
-import type { ComponentProps, ElementType } from 'react';
+import type { ElementType } from 'react';
import { Typography, type TypographyProps } from '@mui/material';
type WordOwnProps = {};
-export type WordsProps = WordOwnProps &
- TypographyProps &
- Omit['component']>, keyof WordOwnProps>;
+export type WordsProps = WordOwnProps & TypographyProps;
export const Words = ({ children, ...props }: WordsProps) => {
return {children};
diff --git a/packages/common/src/client/ui-kit/components/index.ts b/packages/common/src/client/ui-kit/components/index.ts
index 027bdae..47d2746 100644
--- a/packages/common/src/client/ui-kit/components/index.ts
+++ b/packages/common/src/client/ui-kit/components/index.ts
@@ -7,5 +7,4 @@ export * from './Icon';
export * from './InfoTooltip';
export * from './Loader';
export * from './MenuItem';
-export * from './PageHeader';
export * from './Words';
diff --git a/packages/common/src/client/webauthn/utils/index.ts b/packages/common/src/client/webauthn/utils/index.ts
deleted file mode 100644
index 9e181e1..0000000
--- a/packages/common/src/client/webauthn/utils/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './parseWebAuthnError';
diff --git a/packages/common/src/server/services/auth/index.ts b/packages/common/src/server/services/auth/index.ts
deleted file mode 100644
index c3b280b..0000000
--- a/packages/common/src/server/services/auth/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Auth } from 'firebase-admin/auth';
-
-import { auth } from '~server/config/firebase';
-
-export async function createAuthUser(props: Parameters[0]) {
- await auth().createUser(props);
-}
-
-/**
- * @param userId
- * @docs https://firebase.google.com/docs/auth/admin/create-custom-tokens
- */
-export async function createCustomToken(userId: string) {
- return auth().createCustomToken(userId);
-}
diff --git a/packages/common/src/server/services/challenge-session/index.ts b/packages/common/src/server/services/challenge-session/index.ts
index 8295c32..2851104 100644
--- a/packages/common/src/server/services/challenge-session/index.ts
+++ b/packages/common/src/server/services/challenge-session/index.ts
@@ -10,7 +10,7 @@ import { deleteChallengeSession, getChallengeSession, setChallengeSession } from
export type InitializeChallengeSessionProps = {
timeout: number;
- challenge: ArrayBuffer;
+ challenge: Uint8Array;
} & (
| {
type: 'attestation';
@@ -39,6 +39,7 @@ export async function initializeChallengeSession(
await setChallengeSession({
...restProps,
id: sessionId,
+ // @ts-ignore
challenge: bufferToBase64URLString(challenge),
expiresAt: expiresAt.toISOString(),
origin: env.NEXT_PUBLIC_CLIENT_ORIGIN,
diff --git a/packages/common/src/server/services/users/index.ts b/packages/common/src/server/services/users/index.ts
index 8967b3c..16ca388 100644
--- a/packages/common/src/server/services/users/index.ts
+++ b/packages/common/src/server/services/users/index.ts
@@ -1,12 +1,11 @@
import { bufferToBase64URLString } from '@simplewebauthn/browser';
import type { VerifiedRegistrationResponse } from '@simplewebauthn/server';
-import { db } from '~server/config/firebase';
+import { auth, db } from '~server/config/firebase';
import { collections } from '~server/constants/collections';
import type { Passkey, User } from '~types';
import { getPasskeyProvider } from '../aaguid';
-import { createAuthUser } from '../auth';
import { addPasskey, getPasskeys, removePasskey, type AddPasskeyProps } from '../passkeys';
export async function findUserByUsername(username: User['username']) {
@@ -40,6 +39,17 @@ export async function addUser(
return user;
}
+export async function createUserWithNoPasskeys(uid: string, email: string) {
+ await db()
+ .collection(collections.Users)
+ .doc(uid)
+ .set({
+ id: uid,
+ username: email,
+ passkeyIds: [],
+ } satisfies User);
+}
+
export async function updateUser(userId: string, data: Partial) {
await db().collection(collections.Users).doc(userId).update(data);
}
@@ -60,6 +70,7 @@ function mapPropsToPasskey(userId: string, verifiedRegistrationInfo: VerifiedReg
return {
credentialId: credential.id,
credentialBackedUp,
+ // @ts-ignore
credentialPublicKey: bufferToBase64URLString(credential.publicKey),
credentialDeviceType,
credentialCounter: credential.counter,
@@ -87,7 +98,7 @@ export async function createUserPasskey(
});
// Creates a new user in Firebase Authentication.
- await createAuthUser({
+ await auth().createUser({
uid: userId,
email: username,
});
diff --git a/packages/common/src/server/utils/parseAndVerifyIdToken.ts b/packages/common/src/server/utils/parseAndVerifyIdToken.ts
index 7ea99a3..2df1da6 100644
--- a/packages/common/src/server/utils/parseAndVerifyIdToken.ts
+++ b/packages/common/src/server/utils/parseAndVerifyIdToken.ts
@@ -1,18 +1,15 @@
-import { logger } from '~logger';
-import { auth } from '~server/config/firebase';
-
-export async function parseAndVerifyIdToken(authorizationHeader: string | undefined) {
- try {
- const idToken = authorizationHeader?.split('Bearer ')[1];
+import type { DecodedIdToken } from 'firebase-admin/auth';
- if (!idToken) {
- return null;
- }
+import { auth } from '~server/config/firebase';
- return await auth().verifyIdToken(idToken, true);
- } catch (error) {
- logger.error(error);
+export async function parseAndVerifyIdToken(
+ authorizationHeader: string | undefined,
+): Promise<(DecodedIdToken & CustomClaims) | null> {
+ const idToken = authorizationHeader?.split('Bearer ')[1];
+ if (!idToken) {
return null;
}
+
+ return (await auth().verifyIdToken(idToken, true)) as DecodedIdToken & CustomClaims;
}
diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json
index 1c268a9..6122c65 100644
--- a/packages/common/tsconfig.json
+++ b/packages/common/tsconfig.json
@@ -1,10 +1,10 @@
{
"extends": "@tooling/typescript/nextjs",
"compilerOptions": {
- "baseUrl": "./src",
+ "baseUrl": ".",
"paths": {
"~*": [
- "./*"
+ "./src/*"
],
},
"outDir": "dist",
@@ -12,13 +12,14 @@
"declarationMap": true,
"declarationDir": "dist",
"noEmit": false,
+ "incremental": true,
+ "tsBuildInfoFile": ".cache/.tsbuildinfo"
},
"include": [
- "./src/**/*.ts",
- "./src/**/*.tsx",
+ "./src/**/*",
],
"exclude": [
"dist",
"node_modules"
- ],
+ ]
}
\ No newline at end of file
diff --git a/packages/logger/index.ts b/packages/logger/index.ts
index 232e72f..4d1c117 100644
--- a/packages/logger/index.ts
+++ b/packages/logger/index.ts
@@ -1,4 +1,4 @@
-import { captureException, setExtras } from '@sentry/browser';
+import { captureException, setExtras } from '@sentry/nextjs';
import type { Extras } from '@sentry/types';
import { getLogger } from 'loglevel';
diff --git a/packages/logger/package.json b/packages/logger/package.json
index 400c757..c06fd14 100644
--- a/packages/logger/package.json
+++ b/packages/logger/package.json
@@ -3,11 +3,11 @@
"version": "0.0.1",
"type": "module",
"peerDependencies": {
+ "@sentry/nextjs": "8.x",
"next": "15.x",
"react": "18.x"
},
"dependencies": {
- "@sentry/browser": "8.37.1",
"loglevel": "1.9.2"
},
"devDependencies": {
diff --git a/packages/sentry/client.ts b/packages/sentry/client.ts
new file mode 100644
index 0000000..d1e1b6b
--- /dev/null
+++ b/packages/sentry/client.ts
@@ -0,0 +1,33 @@
+// This file configures the initialization of Sentry on the client.
+// The config you add here will be used whenever a users loads a page in their browser.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { feedbackIntegration, init, replayIntegration } from '@sentry/nextjs';
+
+export function initSentryForClient(dsn: string) {
+ init({
+ dsn,
+
+ // Add optional integrations for additional features
+ integrations: [
+ replayIntegration(),
+ feedbackIntegration({
+ colorScheme: 'system',
+ }),
+ ],
+
+ // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
+ tracesSampleRate: 1,
+
+ // Define how likely Replay events are sampled.
+ // This sets the sample rate to be 10%. You may want this to be 100% while
+ // in development and sample at a lower rate in production
+ replaysSessionSampleRate: 0.1,
+
+ // Define how likely Replay events are sampled when an error occurs.
+ replaysOnErrorSampleRate: 1.0,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+ });
+}
diff --git a/packages/sentry/edge.ts b/packages/sentry/edge.ts
new file mode 100644
index 0000000..1847c2e
--- /dev/null
+++ b/packages/sentry/edge.ts
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever the server handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { init } from '@sentry/nextjs';
+
+export function initSentryForEdge(dsn: string) {
+ init({
+ dsn,
+
+ // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
+ tracesSampleRate: 1,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+ });
+}
diff --git a/packages/sentry/eslint.config.mjs b/packages/sentry/eslint.config.mjs
new file mode 100644
index 0000000..5355fa5
--- /dev/null
+++ b/packages/sentry/eslint.config.mjs
@@ -0,0 +1,3 @@
+import { nextjsConfig } from '@tooling/eslint/config';
+
+export default nextjsConfig;
diff --git a/packages/sentry/next-config.ts b/packages/sentry/next-config.ts
new file mode 100644
index 0000000..eb02c5a
--- /dev/null
+++ b/packages/sentry/next-config.ts
@@ -0,0 +1,51 @@
+/* eslint-disable no-console */
+import { withSentryConfig } from '@sentry/nextjs';
+
+export function withDefinedSentryConfig(nextConfig: C) {
+ console.log('Sentry is enabled');
+ console.log('Sentry organization:', process.env.SENTRY_ORG);
+ console.log('Sentry project:', process.env.SENTRY_PROJECT);
+
+ return withSentryConfig(nextConfig, {
+ // For all available options, see:
+ // https://github.com/getsentry/sentry-webpack-plugin#options
+
+ org: process.env.SENTRY_ORG,
+ project: process.env.SENTRY_PROJECT,
+ authToken: process.env.SENTRY_AUTH_TOKEN,
+
+ // Only print logs for uploading source maps in CI
+ silent: !process.env.CI,
+
+ // For all available options, see:
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
+
+ // Upload a larger set of source maps for prettier stack traces (increases build time)
+ widenClientFileUpload: true,
+
+ // Automatically annotate React components to show their full name in breadcrumbs and session replay
+ reactComponentAnnotation: {
+ enabled: true,
+ },
+
+ // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
+ // This can increase your server load as well as your hosting bill.
+ // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
+ // side errors will fail.
+ // tunnelRoute: "/monitoring",
+
+ // Hides source maps from generated client bundles
+ hideSourceMaps: true,
+
+ // Automatically tree-shake Sentry logger statements to reduce bundle size
+ disableLogger: true,
+
+ // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
+ // See the following for more information:
+ // https://docs.sentry.io/product/crons/
+ // https://vercel.com/docs/cron-jobs
+ automaticVercelMonitors: true,
+
+ telemetry: false,
+ });
+}
diff --git a/packages/sentry/package.json b/packages/sentry/package.json
new file mode 100644
index 0000000..10a886b
--- /dev/null
+++ b/packages/sentry/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@workspace/sentry",
+ "version": "0.0.1",
+ "type": "module",
+ "dependencies": {
+ "@sentry/nextjs": "8.41.0",
+ "next": "15.0.3",
+ "react": "18.3.1",
+ "react-dom": "18.3.1"
+ },
+ "devDependencies": {
+ "@tooling/eslint": "workspace:*",
+ "@tooling/prettier": "workspace:*",
+ "@tooling/typescript": "workspace:*"
+ },
+ "prettier": "@tooling/prettier/config",
+ "scripts": {
+ "format": "prettier-format --write ./*.ts",
+ "lint": "eslint-lint --config eslint.config.mjs ./**/*.ts"
+ }
+}
diff --git a/packages/sentry/server.ts b/packages/sentry/server.ts
new file mode 100644
index 0000000..52f8e6d
--- /dev/null
+++ b/packages/sentry/server.ts
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever the server handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import { init } from '@sentry/nextjs';
+
+export function initSentryForServer(dsn: string) {
+ init({
+ dsn,
+
+ // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
+ tracesSampleRate: 1,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+ });
+}
diff --git a/packages/sentry/tsconfig.json b/packages/sentry/tsconfig.json
new file mode 100644
index 0000000..80d5d09
--- /dev/null
+++ b/packages/sentry/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@tooling/typescript/nextjs",
+ "include": [
+ "**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/tooling/eslint/package.json b/tooling/eslint/package.json
index f530f4c..9f6be7b 100644
--- a/tooling/eslint/package.json
+++ b/tooling/eslint/package.json
@@ -12,24 +12,24 @@
"format": "prettier-format --write ./**/*.js"
},
"dependencies": {
- "@eslint/eslintrc": "3.1.0",
- "@eslint/js": "9.14.0",
- "@tanstack/eslint-plugin-query": "5.59.20",
+ "@eslint/eslintrc": "3.2.0",
+ "@eslint/js": "9.15.0",
+ "@tanstack/eslint-plugin-query": "5.61.4",
"@tooling/typescript": "workspace:*",
"@types/eslint__eslintrc": "2.1.2",
"@types/eslint__js": "8.42.3",
- "eslint": "9.14.0",
+ "eslint": "9.15.0",
"eslint-config-next": "15.0.3",
"eslint-config-prettier": "9.1.0",
- "eslint-plugin-turbo": "2.2.3",
+ "eslint-plugin-turbo": "2.3.3",
"next": "15.0.3",
"react": "18.3.1",
"react-dom": "18.3.1",
- "typescript": "5.6.3",
- "typescript-eslint": "8.13.0"
+ "typescript": "5.7.2",
+ "typescript-eslint": "8.16.0"
},
"devDependencies": {
- "@eslint/compat": "1.2.2",
+ "@eslint/compat": "1.2.3",
"@tooling/prettier": "workspace:*",
"@tooling/staged": "workspace:^",
"@types/eslint": "9.6.1"
diff --git a/tooling/prettier/package.json b/tooling/prettier/package.json
index 69e96cc..45bca07 100644
--- a/tooling/prettier/package.json
+++ b/tooling/prettier/package.json
@@ -13,7 +13,7 @@
},
"dependencies": {
"@ianvs/prettier-plugin-sort-imports": "4.4.0",
- "prettier": "3.3.3"
+ "prettier": "3.4.1"
},
"devDependencies": {
"@tooling/staged": "workspace:^",
diff --git a/tooling/typescript/package.json b/tooling/typescript/package.json
index 33ea1f3..8f459b7 100644
--- a/tooling/typescript/package.json
+++ b/tooling/typescript/package.json
@@ -8,8 +8,8 @@
},
"dependencies": {
"@total-typescript/ts-reset": "0.6.1",
- "@types/node": "20.17.6",
+ "@types/node": "22.10.1",
"network-information-types": "0.1.1",
- "typescript": "5.6.3"
+ "typescript": "5.7.2"
}
}
diff --git a/turbo.json b/turbo.json
index 82254d6..79a1ddd 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,13 +1,15 @@
{
"$schema": "https://turbo.build/schema.json",
+ "cacheDir": ".cache/turbo",
+ "ui": "stream",
+ "globalDependencies": [".env.local", ".env", ".env.server", ".env.sentry-build-plugin"],
"tasks": {
"test:ci": {
"dependsOn": ["//#audit", "build"]
},
"build": {
- "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
- "dependsOn": ["cir-dep", "format", "lint", "^build"],
- "inputs": ["$TURBO_DEFAULT$", ".env.local", ".env", ".env.server"]
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**", ".cache/.tsbuildinfo"],
+ "dependsOn": ["cir-dep", "format", "lint", "^build"]
},
"lint": {
"cache": true,
@@ -18,6 +20,7 @@
"outputLogs": "errors-only"
},
"dev": {
+ "outputs": ["dist/**"],
"cache": false,
"persistent": true,
"dependsOn": ["cir-dep", "format"]
diff --git a/yarn.lock b/yarn.lock
index 12dcfba..45b928c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -223,26 +223,26 @@ __metadata:
languageName: node
linkType: hard
-"@emotion/babel-plugin@npm:^11.12.0":
- version: 11.12.0
- resolution: "@emotion/babel-plugin@npm:11.12.0"
+"@emotion/babel-plugin@npm:^11.13.5":
+ version: 11.13.5
+ resolution: "@emotion/babel-plugin@npm:11.13.5"
dependencies:
"@babel/helper-module-imports": "npm:^7.16.7"
"@babel/runtime": "npm:^7.18.3"
"@emotion/hash": "npm:^0.9.2"
"@emotion/memoize": "npm:^0.9.0"
- "@emotion/serialize": "npm:^1.2.0"
+ "@emotion/serialize": "npm:^1.3.3"
babel-plugin-macros: "npm:^3.1.0"
convert-source-map: "npm:^1.5.0"
escape-string-regexp: "npm:^4.0.0"
find-root: "npm:^1.1.0"
source-map: "npm:^0.5.7"
stylis: "npm:4.2.0"
- checksum: 10c0/930ff6f8768b0c24d05896ad696be20e1c65f32ed61fb5c1488f571120a947ef0a2cf69187b17114cc76e7886f771fac150876ed7b5341324fec2377185d6573
+ checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb
languageName: node
linkType: hard
-"@emotion/cache@npm:^11.13.0, @emotion/cache@npm:^11.13.1":
+"@emotion/cache@npm:^11.13.1":
version: 11.13.1
resolution: "@emotion/cache@npm:11.13.1"
dependencies:
@@ -255,6 +255,19 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/cache@npm:^11.13.5":
+ version: 11.13.5
+ resolution: "@emotion/cache@npm:11.13.5"
+ dependencies:
+ "@emotion/memoize": "npm:^0.9.0"
+ "@emotion/sheet": "npm:^1.4.0"
+ "@emotion/utils": "npm:^1.4.2"
+ "@emotion/weak-memoize": "npm:^0.4.0"
+ stylis: "npm:4.2.0"
+ checksum: 10c0/fc669bf2add27ddff7b1f341b54e7124379156285095f0b38fb846efe90c64c70d2826f73bc734358a4fce04578229774a38ff4de2599d286461bfca57ba7d23
+ languageName: node
+ linkType: hard
+
"@emotion/hash@npm:^0.9.2":
version: 0.9.2
resolution: "@emotion/hash@npm:0.9.2"
@@ -278,16 +291,16 @@ __metadata:
languageName: node
linkType: hard
-"@emotion/react@npm:11.13.3":
- version: 11.13.3
- resolution: "@emotion/react@npm:11.13.3"
+"@emotion/react@npm:11.13.5":
+ version: 11.13.5
+ resolution: "@emotion/react@npm:11.13.5"
dependencies:
"@babel/runtime": "npm:^7.18.3"
- "@emotion/babel-plugin": "npm:^11.12.0"
- "@emotion/cache": "npm:^11.13.0"
- "@emotion/serialize": "npm:^1.3.1"
+ "@emotion/babel-plugin": "npm:^11.13.5"
+ "@emotion/cache": "npm:^11.13.5"
+ "@emotion/serialize": "npm:^1.3.3"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0"
- "@emotion/utils": "npm:^1.4.0"
+ "@emotion/utils": "npm:^1.4.2"
"@emotion/weak-memoize": "npm:^0.4.0"
hoist-non-react-statics: "npm:^3.3.1"
peerDependencies:
@@ -295,11 +308,11 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
- checksum: 10c0/a55e770b9ea35de5d35db05a7ad40a4a3f442809fa8e4fabaf56da63ac9444f09aaf691c4e75a1455dc388991ab0c0ab4e253ce67c5836f27513e45ebd01b673
+ checksum: 10c0/16b4810bc68c619cb25145e543880e905fc99332bacc1c39b20c913b2e6130289d9acd909abba55820fa796c5cca3cade6fe79a26b3ab7e4e2d040c61ee14a6e
languageName: node
linkType: hard
-"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1, @emotion/serialize@npm:^1.3.2":
+"@emotion/serialize@npm:^1.3.2":
version: 1.3.2
resolution: "@emotion/serialize@npm:1.3.2"
dependencies:
@@ -312,6 +325,19 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/serialize@npm:^1.3.3":
+ version: 1.3.3
+ resolution: "@emotion/serialize@npm:1.3.3"
+ dependencies:
+ "@emotion/hash": "npm:^0.9.2"
+ "@emotion/memoize": "npm:^0.9.0"
+ "@emotion/unitless": "npm:^0.10.0"
+ "@emotion/utils": "npm:^1.4.2"
+ csstype: "npm:^3.0.2"
+ checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4
+ languageName: node
+ linkType: hard
+
"@emotion/server@npm:11.11.0":
version: 11.11.0
resolution: "@emotion/server@npm:11.11.0"
@@ -336,23 +362,23 @@ __metadata:
languageName: node
linkType: hard
-"@emotion/styled@npm:11.13.0":
- version: 11.13.0
- resolution: "@emotion/styled@npm:11.13.0"
+"@emotion/styled@npm:11.13.5":
+ version: 11.13.5
+ resolution: "@emotion/styled@npm:11.13.5"
dependencies:
"@babel/runtime": "npm:^7.18.3"
- "@emotion/babel-plugin": "npm:^11.12.0"
+ "@emotion/babel-plugin": "npm:^11.13.5"
"@emotion/is-prop-valid": "npm:^1.3.0"
- "@emotion/serialize": "npm:^1.3.0"
+ "@emotion/serialize": "npm:^1.3.3"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0"
- "@emotion/utils": "npm:^1.4.0"
+ "@emotion/utils": "npm:^1.4.2"
peerDependencies:
"@emotion/react": ^11.0.0-rc.0
react: ">=16.8.0"
peerDependenciesMeta:
"@types/react":
optional: true
- checksum: 10c0/5e2cc85c8a2f6e7bd012731cf0b6da3aef5906225e87e8d4a5c19da50572e24d9aaf92615aa36aa863f0fe6b62a121033356e1cad62617c48bfdaa2c3cf0d8a4
+ checksum: 10c0/18d3e38482f92c93446fbfe46e3ca2b182f228f3317ca23f9bd69ddc313bacabf8ecf4d7e720e9aa492bd651cb0b8f87196547bd136666ef50287c414cd36936
languageName: node
linkType: hard
@@ -379,6 +405,13 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/utils@npm:^1.4.2":
+ version: 1.4.2
+ resolution: "@emotion/utils@npm:1.4.2"
+ checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a
+ languageName: node
+ linkType: hard
+
"@emotion/weak-memoize@npm:^0.4.0":
version: 0.4.0
resolution: "@emotion/weak-memoize@npm:0.4.0"
@@ -404,39 +437,39 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/compat@npm:1.2.2":
- version: 1.2.2
- resolution: "@eslint/compat@npm:1.2.2"
+"@eslint/compat@npm:1.2.3":
+ version: 1.2.3
+ resolution: "@eslint/compat@npm:1.2.3"
peerDependencies:
eslint: ^9.10.0
peerDependenciesMeta:
eslint:
optional: true
- checksum: 10c0/c19e1765673520daf6f08bb82f957c6b42079389725ceda99a4387c403fccd5f9a99d142feec43ed032cb240038ea67db9748b17bf8de4ceb8b2fba382089780
+ checksum: 10c0/b7439e62f73b9a05abea3b54ad8edc171e299171fc4673fc5a2c84d97a584bb9487a7f0bee397342f6574bd53597819a8abe52f1ca72184378cf387275b84e32
languageName: node
linkType: hard
-"@eslint/config-array@npm:^0.18.0":
- version: 0.18.0
- resolution: "@eslint/config-array@npm:0.18.0"
+"@eslint/config-array@npm:^0.19.0":
+ version: 0.19.0
+ resolution: "@eslint/config-array@npm:0.19.0"
dependencies:
"@eslint/object-schema": "npm:^2.1.4"
debug: "npm:^4.3.1"
minimatch: "npm:^3.1.2"
- checksum: 10c0/0234aeb3e6b052ad2402a647d0b4f8a6aa71524bafe1adad0b8db1dfe94d7f5f26d67c80f79bb37ac61361a1d4b14bb8fb475efe501de37263cf55eabb79868f
+ checksum: 10c0/def23c6c67a8f98dc88f1b87e17a5668e5028f5ab9459661aabfe08e08f2acd557474bbaf9ba227be0921ae4db232c62773dbb7739815f8415678eb8f592dbf5
languageName: node
linkType: hard
-"@eslint/core@npm:^0.7.0":
- version: 0.7.0
- resolution: "@eslint/core@npm:0.7.0"
- checksum: 10c0/3cdee8bc6cbb96ac6103d3ead42e59830019435839583c9eb352b94ed558bd78e7ffad5286dc710df21ec1e7bd8f52aa6574c62457a4dd0f01f3736fa4a7d87a
+"@eslint/core@npm:^0.9.0":
+ version: 0.9.0
+ resolution: "@eslint/core@npm:0.9.0"
+ checksum: 10c0/6d8e8e0991cef12314c49425d8d2d9394f5fb1a36753ff82df7c03185a4646cb7c8736cf26638a4a714782cedf4b23cfc17667d282d3e5965b3920a0e7ce20d4
languageName: node
linkType: hard
-"@eslint/eslintrc@npm:3.1.0, @eslint/eslintrc@npm:^3.1.0":
- version: 3.1.0
- resolution: "@eslint/eslintrc@npm:3.1.0"
+"@eslint/eslintrc@npm:3.2.0, @eslint/eslintrc@npm:^3.2.0":
+ version: 3.2.0
+ resolution: "@eslint/eslintrc@npm:3.2.0"
dependencies:
ajv: "npm:^6.12.4"
debug: "npm:^4.3.2"
@@ -447,14 +480,14 @@ __metadata:
js-yaml: "npm:^4.1.0"
minimatch: "npm:^3.1.2"
strip-json-comments: "npm:^3.1.1"
- checksum: 10c0/5b7332ed781edcfc98caa8dedbbb843abfb9bda2e86538529c843473f580e40c69eb894410eddc6702f487e9ee8f8cfa8df83213d43a8fdb549f23ce06699167
+ checksum: 10c0/43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b
languageName: node
linkType: hard
-"@eslint/js@npm:9.14.0":
- version: 9.14.0
- resolution: "@eslint/js@npm:9.14.0"
- checksum: 10c0/a423dd435e10aa3b461599aa02f6cbadd4b5128cb122467ee4e2c798e7ca4f9bb1fce4dcea003b29b983090238cf120899c1af657cf86300b399e4f996b83ddc
+"@eslint/js@npm:9.15.0":
+ version: 9.15.0
+ resolution: "@eslint/js@npm:9.15.0"
+ checksum: 10c0/56552966ab1aa95332f70d0e006db5746b511c5f8b5e0c6a9b2d6764ff6d964e0b2622731877cbc4e3f0e74c5b39191290d5f48147be19175292575130d499ab
languageName: node
linkType: hard
@@ -465,12 +498,12 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/plugin-kit@npm:^0.2.0":
- version: 0.2.2
- resolution: "@eslint/plugin-kit@npm:0.2.2"
+"@eslint/plugin-kit@npm:^0.2.3":
+ version: 0.2.3
+ resolution: "@eslint/plugin-kit@npm:0.2.3"
dependencies:
levn: "npm:^0.4.1"
- checksum: 10c0/ec533ccc99f2ab003d6f64495cff853730fb7d8bc0eaf031ffccc68de7c34c74a2eda50dfa759cacfb409f014c98d306714c995348d5383c9d3140f9f80a5895
+ checksum: 10c0/89a8035976bb1780e3fa8ffe682df013bd25f7d102d991cecd3b7c297f4ce8c1a1b6805e76dd16465b5353455b670b545eff2b4ec3133e0eab81a5f9e99bd90f
languageName: node
linkType: hard
@@ -481,159 +514,159 @@ __metadata:
languageName: node
linkType: hard
-"@firebase/analytics-compat@npm:0.2.15":
- version: 0.2.15
- resolution: "@firebase/analytics-compat@npm:0.2.15"
+"@firebase/analytics-compat@npm:0.2.16":
+ version: 0.2.16
+ resolution: "@firebase/analytics-compat@npm:0.2.16"
dependencies:
- "@firebase/analytics": "npm:0.10.9"
- "@firebase/analytics-types": "npm:0.8.2"
- "@firebase/component": "npm:0.6.10"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/analytics": "npm:0.10.10"
+ "@firebase/analytics-types": "npm:0.8.3"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/03447371ef63efed1f3f7de2687c3103c16b28ac61e217effe63ec0f4c9de6743f0a991f1e5557cb2b24da56f07bd62d71a6a9164db9073f8d053ceec976d5ba
+ checksum: 10c0/c4a91732827cb16c91bb2f19a77d85c274a0a1de20fd2ae2fcb92a55d6fe5cd60fe66ef687f80467f75aeaa49c3a2c68d485616b98a5f5c7f3a3f7960eb9b2a6
languageName: node
linkType: hard
-"@firebase/analytics-types@npm:0.8.2":
- version: 0.8.2
- resolution: "@firebase/analytics-types@npm:0.8.2"
- checksum: 10c0/0345beed0e36637c3e3f5c0638478fbd0d165d197a0374dd848c4bb772298b1eb3f3bccfea1f4501e32ee9a4ae8ac1c30bf399645f60037b2b08f4b5e252ec78
+"@firebase/analytics-types@npm:0.8.3":
+ version: 0.8.3
+ resolution: "@firebase/analytics-types@npm:0.8.3"
+ checksum: 10c0/2cbc5fe8425bc01c7ba03579cdc5ca6b23de51b08edb62927be610a33bbc961bae97aa48ee12dcdb039b752c158d095f234ed20f1f4d2bd7a5c39f44d82cdf22
languageName: node
linkType: hard
-"@firebase/analytics@npm:0.10.9":
- version: 0.10.9
- resolution: "@firebase/analytics@npm:0.10.9"
+"@firebase/analytics@npm:0.10.10":
+ version: 0.10.10
+ resolution: "@firebase/analytics@npm:0.10.10"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/154d9d8b6cf7fc7062314d8431cb7029518c6932681ecd27e4d3e8440b3596f356ca8020efc41c3cbccef57dd8c66c60a04d8628b98edc1f3868d3d3c90c9980
+ checksum: 10c0/909f191e1ff8046088387a6fca901834fb0378b4e75314d27a605011559a9d06cd0bbb04826e552907ecce459d158c56c982e032b5383f1dabe8d8c906ce9f01
languageName: node
linkType: hard
-"@firebase/app-check-compat@npm:0.3.16":
- version: 0.3.16
- resolution: "@firebase/app-check-compat@npm:0.3.16"
+"@firebase/app-check-compat@npm:0.3.17":
+ version: 0.3.17
+ resolution: "@firebase/app-check-compat@npm:0.3.17"
dependencies:
- "@firebase/app-check": "npm:0.8.9"
- "@firebase/app-check-types": "npm:0.5.2"
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/app-check": "npm:0.8.10"
+ "@firebase/app-check-types": "npm:0.5.3"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/ef861a521ada31f28ae7aa245441a38786cd23103077f57658e553ab53cbb190acacb87be107ef082ae9122352f48b9a4615bc982690eaa93a5ae5b0c1124097
+ checksum: 10c0/3e89e78d044c66c1d036f6f078eb27e6738c0a017cd1e71edb9e3b40efdd61fe36a2c6c4de755ff0cef15574f70c178c8874f95f709ace68013934f6c160a235
languageName: node
linkType: hard
-"@firebase/app-check-interop-types@npm:0.3.2":
- version: 0.3.2
- resolution: "@firebase/app-check-interop-types@npm:0.3.2"
- checksum: 10c0/7f1d25bc6cef3e4a209e6db096f6088b132b80f59947026af269406bdfbf140f391aeb94e68ecb4f524b4382b7217cc500cc068eeaf834e9665b7793177cc3f8
+"@firebase/app-check-interop-types@npm:0.3.3":
+ version: 0.3.3
+ resolution: "@firebase/app-check-interop-types@npm:0.3.3"
+ checksum: 10c0/4a887ef5e30ee1a407b569603c433a9f21244d50a19d97a5f1f17d8f5caea83096852b39e67d599f3238f1f7e2a369b02d184a184986a649ed1f8fed12fbd6be
languageName: node
linkType: hard
-"@firebase/app-check-types@npm:0.5.2":
- version: 0.5.2
- resolution: "@firebase/app-check-types@npm:0.5.2"
- checksum: 10c0/0e1e3c89da6591c608647faefd49add3aed8a3d5af061c6f4d192fa52cd48a9c511df3dfda96eac5cf18fde2661361bb26a18c9c346b300f71ffa743a85aeb68
+"@firebase/app-check-types@npm:0.5.3":
+ version: 0.5.3
+ resolution: "@firebase/app-check-types@npm:0.5.3"
+ checksum: 10c0/59af0ae698ff2172e84f504e3b5e778c2cc78fefdcceb917eb899a204ad130ad5497011ab94459f6f9dd0a9062a0455bbd745ad3e488b39dae4625c3fb0d0145
languageName: node
linkType: hard
-"@firebase/app-check@npm:0.8.9":
- version: 0.8.9
- resolution: "@firebase/app-check@npm:0.8.9"
+"@firebase/app-check@npm:0.8.10":
+ version: 0.8.10
+ resolution: "@firebase/app-check@npm:0.8.10"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/80215ef760370b162bdd0853cad076b1b7b2903b7a53cbe33706e0d52743cf3d87abe6369ba0e13ce506659525ca1e576e05ae4192f1611d4de0f9dd3c52cd0e
+ checksum: 10c0/0e189362413b7592f13dcd0dc471cad2d94b3927272d6b0e839c7020c3427ae22d92f57246b49e88d2d952c6651cb1bd4c1c7fb0b9b5134eb7928dcc3aa02468
languageName: node
linkType: hard
-"@firebase/app-compat@npm:0.2.45":
- version: 0.2.45
- resolution: "@firebase/app-compat@npm:0.2.45"
+"@firebase/app-compat@npm:0.2.46":
+ version: 0.2.46
+ resolution: "@firebase/app-compat@npm:0.2.46"
dependencies:
- "@firebase/app": "npm:0.10.15"
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/app": "npm:0.10.16"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
- checksum: 10c0/f5a5a56d31118685c9bbbb4c38546b3c29c418c30ecba5c597274eba75f8145708478027ca65b9538e5af94ca5db0996c9c8ca9046e3157c15c94014b56fd254
+ checksum: 10c0/20033c024ecf1650a4f9018ac7f614a91f836a05e77eb4f692225e25c68f982c38634c4bff2c417500980678f14afde426fa766b8eb79f2021807f6909cd792d
languageName: node
linkType: hard
-"@firebase/app-types@npm:0.9.2":
- version: 0.9.2
- resolution: "@firebase/app-types@npm:0.9.2"
- checksum: 10c0/6bc78395ecadbf4958f1300ce9eb1d80522f05531acbacd88220fb77f4b924355bc920afe7f09c29acc40f374380e36539647604e1dab2fea045622b24988441
+"@firebase/app-types@npm:0.9.3":
+ version: 0.9.3
+ resolution: "@firebase/app-types@npm:0.9.3"
+ checksum: 10c0/02ec9a26c10b9bbb2a1e5b9ae0552b5325b40066e3c23be089ceae53414a1505f2ab716ae1098652a0a0c9992ba322c05371a9b2a837cccfae309788372a72e0
languageName: node
linkType: hard
-"@firebase/app@npm:0.10.15":
- version: 0.10.15
- resolution: "@firebase/app@npm:0.10.15"
+"@firebase/app@npm:0.10.16":
+ version: 0.10.16
+ resolution: "@firebase/app@npm:0.10.16"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
idb: "npm:7.1.1"
tslib: "npm:^2.1.0"
- checksum: 10c0/b5bc80a82e29723361b194a56d405d9573f84cecb23df6c6b341e3ba27fc4ff7c641c08a4bcb4a3a349d566e4948bca403f8c79575f2d50944ecba34035acb32
+ checksum: 10c0/a56d0ece4bc22ff7630561388afc9800a4ba6bf3f0f139d5fe41744e67e51c7c6ac727ddaf1d30f5b05aef0e693c4d717a901d05e9ba47e72dbdd1bbf4bc4503
languageName: node
linkType: hard
-"@firebase/auth-compat@npm:0.5.15":
- version: 0.5.15
- resolution: "@firebase/auth-compat@npm:0.5.15"
+"@firebase/auth-compat@npm:0.5.16":
+ version: 0.5.16
+ resolution: "@firebase/auth-compat@npm:0.5.16"
dependencies:
- "@firebase/auth": "npm:1.8.0"
- "@firebase/auth-types": "npm:0.12.2"
- "@firebase/component": "npm:0.6.10"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/auth": "npm:1.8.1"
+ "@firebase/auth-types": "npm:0.12.3"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/bcc6e2b3905f1244615cfbcfcad1f4252066a0b933735eeb9e4111d803a68b96b7c29de2c8639d5a07ac41cc60612d2a14905771748335c2408ea2d857bf6a2e
+ checksum: 10c0/01ebf2251c5d995f2b7d87523875da10a813f76af4d13fef2fb4be3dcd407252e65f8c48af9c9b2d8cc0cdda8294de27674489e21b431f8b82a8b2c38d82067b
languageName: node
linkType: hard
-"@firebase/auth-interop-types@npm:0.2.3":
- version: 0.2.3
- resolution: "@firebase/auth-interop-types@npm:0.2.3"
- checksum: 10c0/a3e72134a5ba177c87e2a35064f88ec6e9272f582c0754664edaabf23e2dcc1e8f9b70f78521c128d20c8ed060e857f333a9c6d5b463e6612bddef01b070da06
+"@firebase/auth-interop-types@npm:0.2.4":
+ version: 0.2.4
+ resolution: "@firebase/auth-interop-types@npm:0.2.4"
+ checksum: 10c0/ff833bcbb472992c6061847309e338dac736c616522c5fd808526d6dc13b9788458a8c9677d91c33c1288ee38f42896c2b4b8fe10ee74f1569d11f3f3c4f53b5
languageName: node
linkType: hard
-"@firebase/auth-types@npm:0.12.2":
- version: 0.12.2
- resolution: "@firebase/auth-types@npm:0.12.2"
+"@firebase/auth-types@npm:0.12.3":
+ version: 0.12.3
+ resolution: "@firebase/auth-types@npm:0.12.3"
peerDependencies:
"@firebase/app-types": 0.x
"@firebase/util": 1.x
- checksum: 10c0/daf3d785cf7c3bb0fde7a92781f11419f7543980e28ad24eebba61ee448ca9858cdd7cbab91d9c4dcc0b7c21708b72dca45fef49f45af715f7ddfe8d545fafbd
+ checksum: 10c0/8666c6b7dda15965ad0300c18c742eb10e5f3a49fa255e169fd8af2b5b2088e65db24f66eaa7889ef92626c6a3de0b7f1a05960c4e9645f4d1111601121cb148
languageName: node
linkType: hard
-"@firebase/auth@npm:1.8.0":
- version: 1.8.0
- resolution: "@firebase/auth@npm:1.8.0"
+"@firebase/auth@npm:1.8.1":
+ version: 1.8.1
+ resolution: "@firebase/auth@npm:1.8.1"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
@@ -641,448 +674,381 @@ __metadata:
peerDependenciesMeta:
"@react-native-async-storage/async-storage":
optional: true
- checksum: 10c0/e768a93cfb134c1df4511a4d6f63aef09eb18a7aaf6459cdd3554770e4be8ad787dab104bf9be275ca1f2326f7bdf57abb2e894666ed2aebeab0fb7f68d49d82
- languageName: node
- linkType: hard
-
-"@firebase/component@npm:0.6.10":
- version: 0.6.10
- resolution: "@firebase/component@npm:0.6.10"
- dependencies:
- "@firebase/util": "npm:1.10.1"
- tslib: "npm:^2.1.0"
- checksum: 10c0/cbcb9c575d0eccccc161c1c02d87d23e184f8d2bf7b45f0cd966f7f4668816d0a90a29795b379cbaebbe278f98d2cc94c6f405bc4c4694ab37beaa333c85fb39
+ checksum: 10c0/01755c08fda1fea7b50ba22a5cb0e3663be62c9096d0d48201e54ad5d96c4a24259b45117163a150a20cecdf606133b14b939cb5219c28b0c4bd4f003db978e4
languageName: node
linkType: hard
-"@firebase/component@npm:0.6.9":
- version: 0.6.9
- resolution: "@firebase/component@npm:0.6.9"
+"@firebase/component@npm:0.6.11":
+ version: 0.6.11
+ resolution: "@firebase/component@npm:0.6.11"
dependencies:
- "@firebase/util": "npm:1.10.0"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
- checksum: 10c0/609dd193000dd9bdd12d820fbf2653d693e9aa2f768aa7817573e4f349b83ae4aa3b80ccd13b5cde4fb6bdf924a283a33ba0b608896bf6112db9265607202d28
+ checksum: 10c0/788d66a0acb506507042173d1906edaf533ca68405f84aed16f33d8f2a130a8796e2f5c2d80177fc6c1826b74ea510da4541df9c381f6bf0f2b5417d3527797c
languageName: node
linkType: hard
-"@firebase/data-connect@npm:0.1.1":
- version: 0.1.1
- resolution: "@firebase/data-connect@npm:0.1.1"
+"@firebase/data-connect@npm:0.1.2":
+ version: 0.1.2
+ resolution: "@firebase/data-connect@npm:0.1.2"
dependencies:
- "@firebase/auth-interop-types": "npm:0.2.3"
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/auth-interop-types": "npm:0.2.4"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/043003fcced645541e12e1c929a854a41d4cb8186595e2c9b0db9e8a5fba1d14547075c6faa4da01dbe77221f3ebbad4a9f03d81f12e006b8912a43a9afaa842
- languageName: node
- linkType: hard
-
-"@firebase/database-compat@npm:1.0.8":
- version: 1.0.8
- resolution: "@firebase/database-compat@npm:1.0.8"
- dependencies:
- "@firebase/component": "npm:0.6.9"
- "@firebase/database": "npm:1.0.8"
- "@firebase/database-types": "npm:1.0.5"
- "@firebase/logger": "npm:0.4.2"
- "@firebase/util": "npm:1.10.0"
- tslib: "npm:^2.1.0"
- checksum: 10c0/34456da205dc0376601cef43ac1eb22b9bddac0555ccde14d759e0737b041bad6b996335f824543e4d782e9440893ae9c09e28be2c26c6afc6dbbfedd2c3eb84
+ checksum: 10c0/b66de47e63251c4239c6d123dedc6043f0988802f899b7c2efcf33c6dc4f349e55fb4e54de43e581083589105fb2844d3f720e84efb3d82c13907a1bddc980c4
languageName: node
linkType: hard
-"@firebase/database-compat@npm:2.0.0":
- version: 2.0.0
- resolution: "@firebase/database-compat@npm:2.0.0"
+"@firebase/database-compat@npm:2.0.1, @firebase/database-compat@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "@firebase/database-compat@npm:2.0.1"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/database": "npm:1.0.9"
- "@firebase/database-types": "npm:1.0.6"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/database": "npm:1.0.10"
+ "@firebase/database-types": "npm:1.0.7"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
- checksum: 10c0/dd1ffeab13f46b9f2218e56573c77d211757e8aa3650494f1e9d51d875cac2b6f5f0eb800f2d8f48ccdebe9ce5ea8d81a77ae87836de2d1344599534aa4811a4
+ checksum: 10c0/e63c3f432d49c0cebc7f36da97d497ece86fa7d1d68bc59020395f96a3e10a16acf299d6299127a4ef8b8abd5f08ea257c5de3e9af44640f4517021a21495a4f
languageName: node
linkType: hard
-"@firebase/database-types@npm:1.0.5":
- version: 1.0.5
- resolution: "@firebase/database-types@npm:1.0.5"
- dependencies:
- "@firebase/app-types": "npm:0.9.2"
- "@firebase/util": "npm:1.10.0"
- checksum: 10c0/64067fd5f11117898ec499bd63b04e13e0a3ef08c82d10873c112ef86be503152d0848f996d6f3f178392a141f20206d7cadb8e3163fd7ffaf7221c132d0f7a2
- languageName: node
- linkType: hard
-
-"@firebase/database-types@npm:1.0.6":
- version: 1.0.6
- resolution: "@firebase/database-types@npm:1.0.6"
+"@firebase/database-types@npm:1.0.7, @firebase/database-types@npm:^1.0.6":
+ version: 1.0.7
+ resolution: "@firebase/database-types@npm:1.0.7"
dependencies:
- "@firebase/app-types": "npm:0.9.2"
- "@firebase/util": "npm:1.10.1"
- checksum: 10c0/a7f11f9947e9653dd9912d86b99025f01a9f9e80cd0f0ad02fc0fa4848baeba7f1041865e4516575ddc573d64d750b455db1325edd56db6c8fb03fa4b85d7919
+ "@firebase/app-types": "npm:0.9.3"
+ "@firebase/util": "npm:1.10.2"
+ checksum: 10c0/12c1f6b489d662f1191b65c1cd08cea1c60591f24867241d8861cf5c21e0b6402f7af2e832e35bc43cdc94dd00658da0d124009d4b3ab036f188299fbb8561d8
languageName: node
linkType: hard
-"@firebase/database@npm:1.0.8":
- version: 1.0.8
- resolution: "@firebase/database@npm:1.0.8"
- dependencies:
- "@firebase/app-check-interop-types": "npm:0.3.2"
- "@firebase/auth-interop-types": "npm:0.2.3"
- "@firebase/component": "npm:0.6.9"
- "@firebase/logger": "npm:0.4.2"
- "@firebase/util": "npm:1.10.0"
- faye-websocket: "npm:0.11.4"
- tslib: "npm:^2.1.0"
- checksum: 10c0/dac0f0d1836cdd1ccc4785bdf35a1cc35a00d35c5c3d21dd87afccd1873f10ed56a606c72de07dbc93600115cd5a94686fbcf169e34ee9ae19a184469c110810
- languageName: node
- linkType: hard
-
-"@firebase/database@npm:1.0.9":
- version: 1.0.9
- resolution: "@firebase/database@npm:1.0.9"
+"@firebase/database@npm:1.0.10":
+ version: 1.0.10
+ resolution: "@firebase/database@npm:1.0.10"
dependencies:
- "@firebase/app-check-interop-types": "npm:0.3.2"
- "@firebase/auth-interop-types": "npm:0.2.3"
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/app-check-interop-types": "npm:0.3.3"
+ "@firebase/auth-interop-types": "npm:0.2.4"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
faye-websocket: "npm:0.11.4"
tslib: "npm:^2.1.0"
- checksum: 10c0/1d448540dc47f28127a0ad72daf112072437486b7b5ce07967d1a8a683137c8877b3b377e047cb2cc0392d7a72d874528b8cb058c339cc7cc1ea31899f3370d9
+ checksum: 10c0/c159b14f91824ce37c59630ac8333befb63e223289a0fbed4f8a6551a39090dc9893a5a34b89034888d18fa80a1831567688273a07d08f4a101bb206a02daf9a
languageName: node
linkType: hard
-"@firebase/firestore-compat@npm:0.3.39":
- version: 0.3.39
- resolution: "@firebase/firestore-compat@npm:0.3.39"
+"@firebase/firestore-compat@npm:0.3.40":
+ version: 0.3.40
+ resolution: "@firebase/firestore-compat@npm:0.3.40"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/firestore": "npm:4.7.4"
- "@firebase/firestore-types": "npm:3.0.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/firestore": "npm:4.7.5"
+ "@firebase/firestore-types": "npm:3.0.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/ac5671ba0b3b8ace41d6ed4c7b5291551d53b52f534eba7a799d014eee12d842e5545791d6dff5d251e208bd362645968e852cc4d5e9cfb96538bd93817987bd
+ checksum: 10c0/3cdaf8789e860d7460d36265516bb5516d252307c0cb58b00272c51f856973c347c13e119c258c5b2eb39071409c16fa84f583b8396d39248668cec0a164991d
languageName: node
linkType: hard
-"@firebase/firestore-types@npm:3.0.2":
- version: 3.0.2
- resolution: "@firebase/firestore-types@npm:3.0.2"
+"@firebase/firestore-types@npm:3.0.3":
+ version: 3.0.3
+ resolution: "@firebase/firestore-types@npm:3.0.3"
peerDependencies:
"@firebase/app-types": 0.x
"@firebase/util": 1.x
- checksum: 10c0/3f8d97894d6bbef7a15ec5a33b241ddbb6ee90c3316c13f2a38fe5b8333e6b842197b498ec7d597ecd52ba4d5253ee96fcc6c889e9b394156200950577bbbded
+ checksum: 10c0/8196168a2de68bd60e0a9053a670d14d2917bf8e30829a4a2f8435fa2aceaaf97ce7438cd9525786a9bf8c5d6104ced3086acd792439371fea7b35497a53bdfa
languageName: node
linkType: hard
-"@firebase/firestore@npm:4.7.4":
- version: 4.7.4
- resolution: "@firebase/firestore@npm:4.7.4"
+"@firebase/firestore@npm:4.7.5":
+ version: 4.7.5
+ resolution: "@firebase/firestore@npm:4.7.5"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
- "@firebase/webchannel-wrapper": "npm:1.0.2"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
+ "@firebase/webchannel-wrapper": "npm:1.0.3"
"@grpc/grpc-js": "npm:~1.9.0"
"@grpc/proto-loader": "npm:^0.7.8"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/6d1e64385e497288f0e0f78fc29667708c640301fc6d370701ab18887c6a4cc1054bc933054c1eb695aebf91a007bd968e2b8a974bfadd74fac5f5625b43a33b
+ checksum: 10c0/e176609c492b39231514f31c1f68c42cc7093a781d05124c68cf6aaf29d82bf9129a0ac6d02325d5408d34a092368128bd7e073fe490f93c72c9f6c3bf4851aa
languageName: node
linkType: hard
-"@firebase/functions-compat@npm:0.3.15":
- version: 0.3.15
- resolution: "@firebase/functions-compat@npm:0.3.15"
+"@firebase/functions-compat@npm:0.3.16":
+ version: 0.3.16
+ resolution: "@firebase/functions-compat@npm:0.3.16"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/functions": "npm:0.11.9"
- "@firebase/functions-types": "npm:0.6.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/functions": "npm:0.11.10"
+ "@firebase/functions-types": "npm:0.6.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/1db82b79f17f7cefa805ac2a603229d7092812df99c3035e7ab187775951b485efb4b9603c1a87f9901cb138d756a794530b72528ec4a4ca4448705bda17a75a
+ checksum: 10c0/840e579344734004234c0ddf6a42f4a094d8a958e7dc6555aac7e172b427a2df9fa0a77a765a6c456a3afe33af9918cf87bbb61cfdc771d04b4ebc8802353a9e
languageName: node
linkType: hard
-"@firebase/functions-types@npm:0.6.2":
- version: 0.6.2
- resolution: "@firebase/functions-types@npm:0.6.2"
- checksum: 10c0/36ea0b30f4cd8d28fc574870780439642048d25bbed289f37f2567f7d93bac80dc19d03e5e7131e879f1f354f6ad7f6cf70188edaf6dbe005b98403e50224054
+"@firebase/functions-types@npm:0.6.3":
+ version: 0.6.3
+ resolution: "@firebase/functions-types@npm:0.6.3"
+ checksum: 10c0/aabd7bdd8c479323a419bba9ad275d96cd44229bd2213c87be08a9978af5ff0c1306279229a358c77280ce54fa6f42c91a6fd6c947808b1103174db0261b86e1
languageName: node
linkType: hard
-"@firebase/functions@npm:0.11.9":
- version: 0.11.9
- resolution: "@firebase/functions@npm:0.11.9"
+"@firebase/functions@npm:0.11.10":
+ version: 0.11.10
+ resolution: "@firebase/functions@npm:0.11.10"
dependencies:
- "@firebase/app-check-interop-types": "npm:0.3.2"
- "@firebase/auth-interop-types": "npm:0.2.3"
- "@firebase/component": "npm:0.6.10"
- "@firebase/messaging-interop-types": "npm:0.2.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/app-check-interop-types": "npm:0.3.3"
+ "@firebase/auth-interop-types": "npm:0.2.4"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/messaging-interop-types": "npm:0.2.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/93dd419bb08a20f7e2f2d1a599dcdb94b525f7d179df034c60fc60660a9b66ae2b5d564479c4b3044c4c346d2c585ceed64fa119b0bd0e91f18ccc0301983b80
+ checksum: 10c0/6087dbdf7141a38cda35be995df5eb12b28b05e69c9df17496ec9a2046144195feb512bafea7d85b537e57d1bdccccf1e13dbabca29483ecca36b700e0fa5b4e
languageName: node
linkType: hard
-"@firebase/installations-compat@npm:0.2.10":
- version: 0.2.10
- resolution: "@firebase/installations-compat@npm:0.2.10"
+"@firebase/installations-compat@npm:0.2.11":
+ version: 0.2.11
+ resolution: "@firebase/installations-compat@npm:0.2.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/installations-types": "npm:0.5.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/installations-types": "npm:0.5.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/a7c32dcc2859977b75641e242c0910eba4387660ed68b58f33cf7e49b1c705d2afcb8cb2f9defa51f9a46cebdc7becd6ca8b2ad70151f6cf9710fa154d7d6743
+ checksum: 10c0/3cab30c2c9c8db37e34d9b6b79022145fe2ae5ad71edff6ca880e6dbe020bb06c03757f5a69642b73c25242d2d1b92d14f65f8a2ab10b6f29c20602fff7faa4e
languageName: node
linkType: hard
-"@firebase/installations-types@npm:0.5.2":
- version: 0.5.2
- resolution: "@firebase/installations-types@npm:0.5.2"
+"@firebase/installations-types@npm:0.5.3":
+ version: 0.5.3
+ resolution: "@firebase/installations-types@npm:0.5.3"
peerDependencies:
"@firebase/app-types": 0.x
- checksum: 10c0/f0a80b57fbeea6a079bfa564a8e5490aeb4a11e0d8e6ea73e548e3ccee637554eed30abc2c7c639d4fcc13c56f440f3aac1ff1588886cbaf552da0cbbd349545
+ checksum: 10c0/f8af07a17e9c0cd1738009b880579b57d112f991ac99e4a17f327d89ad9f8f633fd50757bfd97f470edcc62045526dc59432fb7fcb1f76daa3c72c975519af62
languageName: node
linkType: hard
-"@firebase/installations@npm:0.6.10":
- version: 0.6.10
- resolution: "@firebase/installations@npm:0.6.10"
+"@firebase/installations@npm:0.6.11":
+ version: 0.6.11
+ resolution: "@firebase/installations@npm:0.6.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/util": "npm:1.10.2"
idb: "npm:7.1.1"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/d08af6a1a037f11da19f36d2fd687d33d6e1df7ec8ca5418e7ba5bb4382a9b320460dda6b3236143909ec0c01f2547b45eb08930e66d3f887f2f3acc52b13405
+ checksum: 10c0/4af7d5d7d9c4a0792a3ecfef510410b426beec085ae8cc6ae71b79fec47c68976239744c004d0239f5c759005f32cd5fb35d80c0f4725d8b47b4970ec5745ce0
languageName: node
linkType: hard
-"@firebase/logger@npm:0.4.2":
- version: 0.4.2
- resolution: "@firebase/logger@npm:0.4.2"
+"@firebase/logger@npm:0.4.4":
+ version: 0.4.4
+ resolution: "@firebase/logger@npm:0.4.4"
dependencies:
tslib: "npm:^2.1.0"
- checksum: 10c0/bec040b451ac10fa2dbec54e262093eedab7a684d2f2c80f2549e918db6c4b2091ff7fc1f70f6cd1ec65564dc3b8f9b9d1b4dbfb9708b7ae2b9fd856ee764b3a
+ checksum: 10c0/0493468960c1243bad71ff932fbf89c17870b07cd3cb25b9565661689e52e93948e43cbd423f9903bdd80c40b98c28e4b2d85698e9ef09d4c59e23beb9140bda
languageName: node
linkType: hard
-"@firebase/logger@npm:0.4.3":
- version: 0.4.3
- resolution: "@firebase/logger@npm:0.4.3"
+"@firebase/messaging-compat@npm:0.2.14":
+ version: 0.2.14
+ resolution: "@firebase/messaging-compat@npm:0.2.14"
dependencies:
- tslib: "npm:^2.1.0"
- checksum: 10c0/da4ff7a385fc2f12d02c156c00ffe504b8af9d3299ccde488055dbc11fa1c7283135f95583fdee2b5a3287e8a7ab0312d0f5f708619947edbcd5f9c15bac149a
- languageName: node
- linkType: hard
-
-"@firebase/messaging-compat@npm:0.2.13":
- version: 0.2.13
- resolution: "@firebase/messaging-compat@npm:0.2.13"
- dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/messaging": "npm:0.12.13"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/messaging": "npm:0.12.14"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/0986e1ac07afe8698cc5effa487ce735092b81f1c0838c8d00c094f63fcba48f8511a6916e4ca23be0c14f5d81003bf4ba4bd640ebe5052421b48fe3cbbf3ac0
+ checksum: 10c0/3abb4d47ef35e4458c3b1f38b063c90fd8b1eecb47fdcb0c3eab858a65db0f146ac11bc690f0623234ed5a3dd5479a4a8d78c33603cddbcff70f00148561b086
languageName: node
linkType: hard
-"@firebase/messaging-interop-types@npm:0.2.2":
- version: 0.2.2
- resolution: "@firebase/messaging-interop-types@npm:0.2.2"
- checksum: 10c0/c2ecebd2c1762869adc5a8dffc8881cb96ed4da8532291d6d5aca5302201546a19cd9a369561de29d253deb82d53be05e3d6fbdabd66ef1ba7c2e162ac5bf0f5
+"@firebase/messaging-interop-types@npm:0.2.3":
+ version: 0.2.3
+ resolution: "@firebase/messaging-interop-types@npm:0.2.3"
+ checksum: 10c0/a6fb8f02db6a93f277cb5bd530934509e49465f775f2b5ed159116d9ce30b6255213781639b98984ff8b424a8fc36a8e5779e0cc3f0cf5e1bdbd41ae938d6c39
languageName: node
linkType: hard
-"@firebase/messaging@npm:0.12.13":
- version: 0.12.13
- resolution: "@firebase/messaging@npm:0.12.13"
+"@firebase/messaging@npm:0.12.14":
+ version: 0.12.14
+ resolution: "@firebase/messaging@npm:0.12.14"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/messaging-interop-types": "npm:0.2.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/messaging-interop-types": "npm:0.2.3"
+ "@firebase/util": "npm:1.10.2"
idb: "npm:7.1.1"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/3a913b2772bc3d5a2495fd53abb31b8b6c88790ef4635362c6413663099504126d3319cd19854b3107a2956735c4a40e5bff265012e8068daa7aa633ab9af891
+ checksum: 10c0/1ba76fb898e9a93f05540363c89da405d6b5e929b6e431daec98bb5116347f9aef59955496d4c4e3f9831c6ee651bbb11d6e43102199f29b6e3363b76299fb19
languageName: node
linkType: hard
-"@firebase/performance-compat@npm:0.2.10":
- version: 0.2.10
- resolution: "@firebase/performance-compat@npm:0.2.10"
+"@firebase/performance-compat@npm:0.2.11":
+ version: 0.2.11
+ resolution: "@firebase/performance-compat@npm:0.2.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/performance": "npm:0.6.10"
- "@firebase/performance-types": "npm:0.2.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/performance": "npm:0.6.11"
+ "@firebase/performance-types": "npm:0.2.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/644c0746f3b4080c3ee23e3fb6ac17920de33c6211091537ad601725592e0a27f93c567513f6499eff7a2c0f67412298afc1219b05e859bcc01ce8a1f9cd403c
+ checksum: 10c0/6a84e73d6a92cd892b0d6f6feaf0f65198f2660c8c0774d1d5202680563858ba44a4467d0d8ad688e9cbba77e58b9a731cb5756efccf3db6622a2b42265cde1a
languageName: node
linkType: hard
-"@firebase/performance-types@npm:0.2.2":
- version: 0.2.2
- resolution: "@firebase/performance-types@npm:0.2.2"
- checksum: 10c0/4187b2d8c49fa7b51bb8811fc25b31500d7e90b43ad48977a57eb77e461be963d4c102468b81471b04c30125270ea48399a4976f1ceb2ddabfe6e1ab901541d1
+"@firebase/performance-types@npm:0.2.3":
+ version: 0.2.3
+ resolution: "@firebase/performance-types@npm:0.2.3"
+ checksum: 10c0/971d6bff448481dd5e8ff9d643e14b364ed4d619aca1d8d64105555c7f4566c9c05bca3cd0c027b3f879cccf8c7bc0e31579f7f0d7b8b1de182af804572b2374
languageName: node
linkType: hard
-"@firebase/performance@npm:0.6.10":
- version: 0.6.10
- resolution: "@firebase/performance@npm:0.6.10"
+"@firebase/performance@npm:0.6.11":
+ version: 0.6.11
+ resolution: "@firebase/performance@npm:0.6.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/baf7ffd9d5967908a9a4dc0237b92e9b5e727df61d8d77756db230a0994238c04f62d107019cbc766b0ccd4f90926557881ca69a06358cbcfe5a9c78b7effd8e
+ checksum: 10c0/4a4788d212e0cd7cdd2fe5623d71b7feac177fb4567f750ed23f0994ea960f77f8beb7051721e77fc44b6f40194aca33567c6c2139aa576736df36ea4934a608
languageName: node
linkType: hard
-"@firebase/remote-config-compat@npm:0.2.10":
- version: 0.2.10
- resolution: "@firebase/remote-config-compat@npm:0.2.10"
+"@firebase/remote-config-compat@npm:0.2.11":
+ version: 0.2.11
+ resolution: "@firebase/remote-config-compat@npm:0.2.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/remote-config": "npm:0.4.10"
- "@firebase/remote-config-types": "npm:0.3.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/remote-config": "npm:0.4.11"
+ "@firebase/remote-config-types": "npm:0.3.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/7ac6abd814f8eb199da1ee33a80296137cfe113e0814faeeb2ea5358b2726ea0e56668ff5b5e9ced49ce66f7b183a2a20dd68c17d950b4588e8b064a3947bae9
+ checksum: 10c0/49e8ee380c7d20b98b5ea533fddb8125dbdb0f123ea40ceb17ad5a7148f432effeb2ac613c6f4ac2ec966eb6f00501da0d4c26594d414274774a99d60e9c733e
languageName: node
linkType: hard
-"@firebase/remote-config-types@npm:0.3.2":
- version: 0.3.2
- resolution: "@firebase/remote-config-types@npm:0.3.2"
- checksum: 10c0/eab1a2c046ed77a9072e73f9cb0a21ce8e93f79a726d6be06ff2338c608f4f3c98a10315ca151b6d88635da5c6301e2a6c8026db1828430a467259497380eb9b
+"@firebase/remote-config-types@npm:0.3.3":
+ version: 0.3.3
+ resolution: "@firebase/remote-config-types@npm:0.3.3"
+ checksum: 10c0/936ee3a5b673e424142d00e7a22788c3c6b28d068cc4fa690b203019f3f7586d1c5fe3cd520ea07744bf9ab93f25df44d0283efdb69611f6b8e02f102cdfd3eb
languageName: node
linkType: hard
-"@firebase/remote-config@npm:0.4.10":
- version: 0.4.10
- resolution: "@firebase/remote-config@npm:0.4.10"
+"@firebase/remote-config@npm:0.4.11":
+ version: 0.4.11
+ resolution: "@firebase/remote-config@npm:0.4.11"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/c85febab2ee943484706bd37eaa343253a39b9099039de9f4610f2a4f03f4ec4d1c1e2424fad0e75f6803e06451cb5d13f9e098b7d1eac10c9ca09c9c65b099f
+ checksum: 10c0/6115001a7f5bd22aa1f8bb2e8c18321c53acccd7d7808555c0b414d44e26d99014a9d6f33d38e23d9949edcb6fd8bb23787326ba7fdb92b7a146841867629ed8
languageName: node
linkType: hard
-"@firebase/storage-compat@npm:0.3.13":
- version: 0.3.13
- resolution: "@firebase/storage-compat@npm:0.3.13"
+"@firebase/storage-compat@npm:0.3.14":
+ version: 0.3.14
+ resolution: "@firebase/storage-compat@npm:0.3.14"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/storage": "npm:0.13.3"
- "@firebase/storage-types": "npm:0.8.2"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/storage": "npm:0.13.4"
+ "@firebase/storage-types": "npm:0.8.3"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app-compat": 0.x
- checksum: 10c0/4655c927156910b6d9ce2811e205b3d441b20917cb979ab51ae5c6ccbf751e0a188e628b1a8743f70182f05e030d0bdcb925cf1996f767978576c7a4e69bb5fb
+ checksum: 10c0/e48b147e886ae7985f16c819756306664204b9dfcff7f08545ee7446bc1490d65935b11739c125a968e8462a8302b55d2f3189afc48ef0bb3b2d49256fe6df6e
languageName: node
linkType: hard
-"@firebase/storage-types@npm:0.8.2":
- version: 0.8.2
- resolution: "@firebase/storage-types@npm:0.8.2"
+"@firebase/storage-types@npm:0.8.3":
+ version: 0.8.3
+ resolution: "@firebase/storage-types@npm:0.8.3"
peerDependencies:
"@firebase/app-types": 0.x
"@firebase/util": 1.x
- checksum: 10c0/8319975f6ee1585d52670fc75eaaf668ba9d4ae75c766dd1b33e609de68b191865a7125beeca5df6232636a7fd3a1cdc412848a1fc196b5410503f096de99daf
+ checksum: 10c0/4b34edca4fcbf75ba6575b02d823f5f5b0680977488a2e8101116313903d75973623cf4440f1e0f8048158e0804d0f5a7730f15bbe5af4ceb35fae6ff532a696
languageName: node
linkType: hard
-"@firebase/storage@npm:0.13.3":
- version: 0.13.3
- resolution: "@firebase/storage@npm:0.13.3"
+"@firebase/storage@npm:0.13.4":
+ version: 0.13.4
+ resolution: "@firebase/storage@npm:0.13.4"
dependencies:
- "@firebase/component": "npm:0.6.10"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
- checksum: 10c0/ec44f04197723056d1fba188a5ad29c77c7aafc11340153506c6943e338f15dcb61c8ab68ed7963735771cbcfec77e3db1c32cf86c13fd17cece653f9d9722e5
- languageName: node
- linkType: hard
-
-"@firebase/util@npm:1.10.0":
- version: 1.10.0
- resolution: "@firebase/util@npm:1.10.0"
- dependencies:
- tslib: "npm:^2.1.0"
- checksum: 10c0/fc152a2cbdd06323f57f66c90cd388369e48e8910d589127f2ea76ca415c43c1c59b5b7b240307ae18f7f4c9cf0f97c71cb06e5ed8cba770b70958903ec52571
+ checksum: 10c0/65d9286867a878f60271a5a51f8d6fa54a72672d741b7cb5b3263226b963c94986e976e0bd5b8aa82d9c5fe7d9a751e6f793a58a1f46130ab2d3d613379af9a8
languageName: node
linkType: hard
-"@firebase/util@npm:1.10.1":
- version: 1.10.1
- resolution: "@firebase/util@npm:1.10.1"
+"@firebase/util@npm:1.10.2":
+ version: 1.10.2
+ resolution: "@firebase/util@npm:1.10.2"
dependencies:
tslib: "npm:^2.1.0"
- checksum: 10c0/f128bcc97e31876f08e221ffc78d21fd5e4f6640e226d16a7247c850011d5a48fec4e9e8a44c0ab7fb2897b2c47e1d029a58723f24b0725c1fca0b8d5da96725
+ checksum: 10c0/d6abb471948517cc9c560ebbb44e9e135716829c3abcd248a1af8aa111e48311ab410b693adc8f3bfe3b564896da7000dd7e26e34ecf59326f3b204a6a8b123c
languageName: node
linkType: hard
-"@firebase/vertexai@npm:1.0.0":
- version: 1.0.0
- resolution: "@firebase/vertexai@npm:1.0.0"
+"@firebase/vertexai@npm:1.0.1":
+ version: 1.0.1
+ resolution: "@firebase/vertexai@npm:1.0.1"
dependencies:
- "@firebase/app-check-interop-types": "npm:0.3.2"
- "@firebase/component": "npm:0.6.10"
- "@firebase/logger": "npm:0.4.3"
- "@firebase/util": "npm:1.10.1"
+ "@firebase/app-check-interop-types": "npm:0.3.3"
+ "@firebase/component": "npm:0.6.11"
+ "@firebase/logger": "npm:0.4.4"
+ "@firebase/util": "npm:1.10.2"
tslib: "npm:^2.1.0"
peerDependencies:
"@firebase/app": 0.x
"@firebase/app-types": 0.x
- checksum: 10c0/c7982f553e503c69679f67b1f8e7f8da5bad912a723790c4f2e809afcbe83860d84e41417e5095619b020e9ff06e87a22155f3eefbb0c1d1d43d24f25cdd13d8
+ checksum: 10c0/3a56bb78500d05808cb8727cea2bdd4470c22d346dcb291d2772b48761d550f6d30939c0de36f40786b05790e3f348fdd037b4a8209b7b0bf00fc8f3962211e6
languageName: node
linkType: hard
-"@firebase/webchannel-wrapper@npm:1.0.2":
- version: 1.0.2
- resolution: "@firebase/webchannel-wrapper@npm:1.0.2"
- checksum: 10c0/b566bb131f10aed501e12b639810fd2a10577b4f0585034f224122e8becae771f0600eb6e017322bf09b5eb1916f8dbeabcf8831b9bdcb7a9671f0a00f3c345d
+"@firebase/webchannel-wrapper@npm:1.0.3":
+ version: 1.0.3
+ resolution: "@firebase/webchannel-wrapper@npm:1.0.3"
+ checksum: 10c0/faa1e53ea82ab6bda0b9dcc5f525101a301c74d1cffb924269de947a46511a633662dd6ee8ca571470e06642b35a596625228c766f37cc2d657321edfc560d28
languageName: node
linkType: hard
@@ -1124,14 +1090,14 @@ __metadata:
languageName: node
linkType: hard
-"@formatjs/ecma402-abstract@npm:2.2.3":
- version: 2.2.3
- resolution: "@formatjs/ecma402-abstract@npm:2.2.3"
+"@formatjs/ecma402-abstract@npm:2.2.4":
+ version: 2.2.4
+ resolution: "@formatjs/ecma402-abstract@npm:2.2.4"
dependencies:
"@formatjs/fast-memoize": "npm:2.2.3"
- "@formatjs/intl-localematcher": "npm:0.5.7"
+ "@formatjs/intl-localematcher": "npm:0.5.8"
tslib: "npm:2"
- checksum: 10c0/611d12bf320fc5c5b85cb2b57e3dcebe8490a51c6a0459c857c7a3560656cd2bdba5b117e9dd7cf174f5aa120c11eaad7a65a6783637b816caa59a1bc5c727f6
+ checksum: 10c0/3f262533fa704ea7a1a7a8107deee2609774a242c621f8cb5dd4bf4c97abf2fc12f5aeda3f4ce85be18147c484a0ca87303dca6abef53290717e685c55eabd2d
languageName: node
linkType: hard
@@ -1144,79 +1110,54 @@ __metadata:
languageName: node
linkType: hard
-"@formatjs/icu-messageformat-parser@npm:2.9.3":
- version: 2.9.3
- resolution: "@formatjs/icu-messageformat-parser@npm:2.9.3"
- dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
- "@formatjs/icu-skeleton-parser": "npm:1.8.7"
- tslib: "npm:2"
- checksum: 10c0/519b59f7b4cf90681315c5382f7fcd105eb1974486f0d62d9227b6d0498895114ccc818792c208baae1ef79571d93b0edb9914c676e5ab76924dddb7fd6c28a0
- languageName: node
- linkType: hard
-
-"@formatjs/icu-skeleton-parser@npm:1.8.7":
- version: 1.8.7
- resolution: "@formatjs/icu-skeleton-parser@npm:1.8.7"
+"@formatjs/icu-messageformat-parser@npm:2.9.4":
+ version: 2.9.4
+ resolution: "@formatjs/icu-messageformat-parser@npm:2.9.4"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
+ "@formatjs/ecma402-abstract": "npm:2.2.4"
+ "@formatjs/icu-skeleton-parser": "npm:1.8.8"
tslib: "npm:2"
- checksum: 10c0/e29eb4151580f2d324e6591509dc4543e2326266fc209a08580c94d502acab14acc3560d98b3aaf9ffbd5ff8e2683601ff08c65b32886f22da015c31ca35c5d0
+ checksum: 10c0/f1ed14ece7ef0abc9fb62e323b78c994fc772d346801ad5aaa9555e1a7d5c0fda791345f4f2e53a3223f0b82c1a4eaf9a83544c1c20cb39349d1a39bedcf1648
languageName: node
linkType: hard
-"@formatjs/intl-displaynames@npm:6.8.4":
- version: 6.8.4
- resolution: "@formatjs/intl-displaynames@npm:6.8.4"
+"@formatjs/icu-skeleton-parser@npm:1.8.8":
+ version: 1.8.8
+ resolution: "@formatjs/icu-skeleton-parser@npm:1.8.8"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
- "@formatjs/intl-localematcher": "npm:0.5.7"
+ "@formatjs/ecma402-abstract": "npm:2.2.4"
tslib: "npm:2"
- checksum: 10c0/a19531ff138ecde23e85f15e1287135a860cd8fe5a721fabceab688bd1001cb2d6256c87bb8b103930aa6245a93a36e594efb3d1d298287003cf8eac783d088a
+ checksum: 10c0/5ad78a5682e83b973e6fed4fca68660b944c41d1e941f0c84d69ff3d10ae835330062dc0a2cf0d237d2675ad3463405061a3963c14c2b9d8d1c1911f892b1a8d
languageName: node
linkType: hard
-"@formatjs/intl-listformat@npm:7.7.4":
- version: 7.7.4
- resolution: "@formatjs/intl-listformat@npm:7.7.4"
- dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
- "@formatjs/intl-localematcher": "npm:0.5.7"
- tslib: "npm:2"
- checksum: 10c0/aedee92b8adcf9c84bc729bfeb6719b11532a5227719299926f8660a1896e02aa3672491cb3c04ba51401ab620ba878f93bd7931c91689a0a37a55501ba6f940
- languageName: node
- linkType: hard
-
-"@formatjs/intl-localematcher@npm:0.5.7":
- version: 0.5.7
- resolution: "@formatjs/intl-localematcher@npm:0.5.7"
+"@formatjs/intl-localematcher@npm:0.5.8":
+ version: 0.5.8
+ resolution: "@formatjs/intl-localematcher@npm:0.5.8"
dependencies:
tslib: "npm:2"
- checksum: 10c0/1ae374ca146a0d7457794926eed808c99971628e594f704a42ae2540b1f38928b26cbf942a7bbcc2796cc9fe8d9d7a603ac422bd9b89b714d2f91b506da40792
+ checksum: 10c0/7a660263986326b662d4cb537e8386331c34fda61fb830b105e6c62d49be58ace40728dae614883b27a41cec7b1df8b44f72f79e16e6028bfca65d398dc04f3b
languageName: node
linkType: hard
-"@formatjs/intl@npm:2.10.14":
- version: 2.10.14
- resolution: "@formatjs/intl@npm:2.10.14"
+"@formatjs/intl@npm:3.0.1":
+ version: 3.0.1
+ resolution: "@formatjs/intl@npm:3.0.1"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/fast-memoize": "npm:2.2.3"
- "@formatjs/icu-messageformat-parser": "npm:2.9.3"
- "@formatjs/intl-displaynames": "npm:6.8.4"
- "@formatjs/intl-listformat": "npm:7.7.4"
- intl-messageformat: "npm:10.7.6"
+ "@formatjs/icu-messageformat-parser": "npm:2.9.4"
+ intl-messageformat: "npm:10.7.7"
tslib: "npm:2"
peerDependencies:
- typescript: ^4.7 || 5
+ typescript: 5
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10c0/e0bb71c53206e20176b509956d78f3ff0df5ce19bde6b25e7717289c10aa12f1e7203df139510060571e6d335786cf03d30b9096dae02a9759398314c90f6e2e
+ checksum: 10c0/0a109656c1c6eada6fe36e281d9f126b07f36e111ed8e468422e90582dd8b4d70b7aee971e6c4b6ee91c5826eeebeb386a9503df574bd4eb1fee1420a6911fd3
languageName: node
linkType: hard
-"@google-cloud/firestore@npm:^7.7.0":
+"@google-cloud/firestore@npm:^7.10.0":
version: 7.10.0
resolution: "@google-cloud/firestore@npm:7.10.0"
dependencies:
@@ -1253,7 +1194,7 @@ __metadata:
languageName: node
linkType: hard
-"@google-cloud/storage@npm:^7.7.0":
+"@google-cloud/storage@npm:^7.14.0":
version: 7.14.0
resolution: "@google-cloud/storage@npm:7.14.0"
dependencies:
@@ -1357,7 +1298,7 @@ __metadata:
languageName: node
linkType: hard
-"@humanwhocodes/retry@npm:^0.4.0":
+"@humanwhocodes/retry@npm:^0.4.1":
version: 0.4.1
resolution: "@humanwhocodes/retry@npm:0.4.1"
checksum: 10c0/be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b
@@ -1650,26 +1591,26 @@ __metadata:
languageName: node
linkType: hard
-"@mui/core-downloads-tracker@npm:^6.1.6":
- version: 6.1.6
- resolution: "@mui/core-downloads-tracker@npm:6.1.6"
- checksum: 10c0/538c561dc46e040ebc5ea884428dccc427fdddbd3747890d96ae52648eed5f7dec4dc8294927b58ff4b7481c0a813dcb16b9d7b9b08cc43871d2d55ebd1a8002
+"@mui/core-downloads-tracker@npm:^6.1.8":
+ version: 6.1.8
+ resolution: "@mui/core-downloads-tracker@npm:6.1.8"
+ checksum: 10c0/a77ac4849c8a0f3bb0eecfae758f277fbdef46ff269314f495719a87f34f54b860d45a4648e456abac33d98b8070649478dc5918d92379728e2ff90e2cc798e1
languageName: node
linkType: hard
-"@mui/icons-material@npm:6.1.6":
- version: 6.1.6
- resolution: "@mui/icons-material@npm:6.1.6"
+"@mui/icons-material@npm:6.1.8":
+ version: 6.1.8
+ resolution: "@mui/icons-material@npm:6.1.8"
dependencies:
"@babel/runtime": "npm:^7.26.0"
peerDependencies:
- "@mui/material": ^6.1.6
+ "@mui/material": ^6.1.8
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
- checksum: 10c0/a6eb10be3cc356fd404febf29a3b26fa63b6b09d3148736fb05279954905186e9804869ff18220840ae92dbdeddfd407c2d0c72b9e165e01fd6bbc620b6b39d7
+ checksum: 10c0/730dd2581e3bdfabb4085ed4675fd3fd49d0f03c22291f79d0f51b1fd4f23b4edccb8b16c0b424b5f81dd6398742f6c9d52cb1fd075927826669732c4a8a0a8c
languageName: node
linkType: hard
@@ -1705,15 +1646,15 @@ __metadata:
languageName: node
linkType: hard
-"@mui/material@npm:6.1.6":
- version: 6.1.6
- resolution: "@mui/material@npm:6.1.6"
+"@mui/material@npm:6.1.8":
+ version: 6.1.8
+ resolution: "@mui/material@npm:6.1.8"
dependencies:
"@babel/runtime": "npm:^7.26.0"
- "@mui/core-downloads-tracker": "npm:^6.1.6"
- "@mui/system": "npm:^6.1.6"
+ "@mui/core-downloads-tracker": "npm:^6.1.8"
+ "@mui/system": "npm:^6.1.8"
"@mui/types": "npm:^7.2.19"
- "@mui/utils": "npm:^6.1.6"
+ "@mui/utils": "npm:^6.1.8"
"@popperjs/core": "npm:^2.11.8"
"@types/react-transition-group": "npm:^4.4.11"
clsx: "npm:^2.1.1"
@@ -1724,7 +1665,7 @@ __metadata:
peerDependencies:
"@emotion/react": ^11.5.0
"@emotion/styled": ^11.3.0
- "@mui/material-pigment-css": ^6.1.6
+ "@mui/material-pigment-css": ^6.1.8
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1737,7 +1678,7 @@ __metadata:
optional: true
"@types/react":
optional: true
- checksum: 10c0/b54c0b01f33f63a700ec7b13d615dd3a109497ee48a1af0f750f780112a7034fbecfcecf29ad67aa62ec12047d465fbcb243052d8680ce681240096fef1f8d63
+ checksum: 10c0/c4515ae5df41538d0eada15d899d70e1c7be83f16ee3a5c582e099d750584351e4220fab47fbeb267cd90e87bb40de9931414f23d9e66577b8235d442794720b
languageName: node
linkType: hard
@@ -1758,6 +1699,23 @@ __metadata:
languageName: node
linkType: hard
+"@mui/private-theming@npm:^6.1.8":
+ version: 6.1.8
+ resolution: "@mui/private-theming@npm:6.1.8"
+ dependencies:
+ "@babel/runtime": "npm:^7.26.0"
+ "@mui/utils": "npm:^6.1.8"
+ prop-types: "npm:^15.8.1"
+ peerDependencies:
+ "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/16425a9001d3038531036dc47f031a4f1175d99b07b788983ce9a5e0c3c063132c6d508af31d3d13c3e44bedb4aa8b2f0111c5eb609ca8e0a652f87237ec1f38
+ languageName: node
+ linkType: hard
+
"@mui/styled-engine@npm:^6.1.6":
version: 6.1.6
resolution: "@mui/styled-engine@npm:6.1.6"
@@ -1781,6 +1739,57 @@ __metadata:
languageName: node
linkType: hard
+"@mui/styled-engine@npm:^6.1.8":
+ version: 6.1.8
+ resolution: "@mui/styled-engine@npm:6.1.8"
+ dependencies:
+ "@babel/runtime": "npm:^7.26.0"
+ "@emotion/cache": "npm:^11.13.1"
+ "@emotion/serialize": "npm:^1.3.2"
+ "@emotion/sheet": "npm:^1.4.0"
+ csstype: "npm:^3.1.3"
+ prop-types: "npm:^15.8.1"
+ peerDependencies:
+ "@emotion/react": ^11.4.1
+ "@emotion/styled": ^11.3.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ "@emotion/react":
+ optional: true
+ "@emotion/styled":
+ optional: true
+ checksum: 10c0/4da513a6bc72a2875fc0d4a097db5141849b69a2c62b867a1ac45d3fe112c2c18abb835f0bdfbe4ffbe626bff2f0490f014ccd3a7db72ada6e3b0cca87af63de
+ languageName: node
+ linkType: hard
+
+"@mui/system@npm:6.1.8, @mui/system@npm:^6.1.8":
+ version: 6.1.8
+ resolution: "@mui/system@npm:6.1.8"
+ dependencies:
+ "@babel/runtime": "npm:^7.26.0"
+ "@mui/private-theming": "npm:^6.1.8"
+ "@mui/styled-engine": "npm:^6.1.8"
+ "@mui/types": "npm:^7.2.19"
+ "@mui/utils": "npm:^6.1.8"
+ clsx: "npm:^2.1.1"
+ csstype: "npm:^3.1.3"
+ prop-types: "npm:^15.8.1"
+ peerDependencies:
+ "@emotion/react": ^11.5.0
+ "@emotion/styled": ^11.3.0
+ "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ "@emotion/react":
+ optional: true
+ "@emotion/styled":
+ optional: true
+ "@types/react":
+ optional: true
+ checksum: 10c0/af7902a1a6664055b4f764020746403749148f88c050edf509f557fd9f0b1d4d86ee9478d78e6c0356129f09b4101a93a345b05b5aa00125d3c164b148275faf
+ languageName: node
+ linkType: hard
+
"@mui/system@npm:^6.1.6":
version: 6.1.6
resolution: "@mui/system@npm:6.1.6"
@@ -1841,6 +1850,26 @@ __metadata:
languageName: node
linkType: hard
+"@mui/utils@npm:^6.1.8":
+ version: 6.1.8
+ resolution: "@mui/utils@npm:6.1.8"
+ dependencies:
+ "@babel/runtime": "npm:^7.26.0"
+ "@mui/types": "npm:^7.2.19"
+ "@types/prop-types": "npm:^15.7.13"
+ clsx: "npm:^2.1.1"
+ prop-types: "npm:^15.8.1"
+ react-is: "npm:^18.3.1"
+ peerDependencies:
+ "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/7d2bfa4863456a5223ddf6a93d56cc4c64e9de0ebc947953a4c23e83f8c9257d02a572da7d8c2dd93dcea5db0d321b7c8bb1e154b26fa5f22663eb6a262726ab
+ languageName: node
+ linkType: hard
+
"@next/env@npm:15.0.3":
version: 15.0.3
resolution: "@next/env@npm:15.0.3"
@@ -2034,16 +2063,16 @@ __metadata:
languageName: node
linkType: hard
-"@opentelemetry/instrumentation-amqplib@npm:^0.42.0":
- version: 0.42.0
- resolution: "@opentelemetry/instrumentation-amqplib@npm:0.42.0"
+"@opentelemetry/instrumentation-amqplib@npm:^0.43.0":
+ version: 0.43.0
+ resolution: "@opentelemetry/instrumentation-amqplib@npm:0.43.0"
dependencies:
"@opentelemetry/core": "npm:^1.8.0"
- "@opentelemetry/instrumentation": "npm:^0.53.0"
+ "@opentelemetry/instrumentation": "npm:^0.54.0"
"@opentelemetry/semantic-conventions": "npm:^1.27.0"
peerDependencies:
"@opentelemetry/api": ^1.3.0
- checksum: 10c0/bdbcce51161f026ed7d57ae8694738655d1c1f1c1308570d28d85c6d42ed1cffedf2dac8dac0ab00a3fa820908525171086a265d5c366cd4a372517d87fbdff5
+ checksum: 10c0/76d0e22d2d2ac06e98474e5cc14dca4f35117bed14bf4f584a1f8a2bf3a91a448d5fd0c6ebebeb5102dba4962a3293b785e410429ccf952391e4f00f3602c5d1
languageName: node
linkType: hard
@@ -2184,6 +2213,18 @@ __metadata:
languageName: node
linkType: hard
+"@opentelemetry/instrumentation-knex@npm:0.41.0":
+ version: 0.41.0
+ resolution: "@opentelemetry/instrumentation-knex@npm:0.41.0"
+ dependencies:
+ "@opentelemetry/instrumentation": "npm:^0.54.0"
+ "@opentelemetry/semantic-conventions": "npm:^1.27.0"
+ peerDependencies:
+ "@opentelemetry/api": ^1.3.0
+ checksum: 10c0/f9d1acdbbe83c428d4929dee468ed19ac758d86e99f6cf1481ee6f04cc4012c60a36f8d75875571c1e10dc486e995d26f2431ec70c37ed5effd78bec8b53ced1
+ languageName: node
+ linkType: hard
+
"@opentelemetry/instrumentation-koa@npm:0.43.0":
version: 0.43.0
resolution: "@opentelemetry/instrumentation-koa@npm:0.43.0"
@@ -2299,6 +2340,19 @@ __metadata:
languageName: node
linkType: hard
+"@opentelemetry/instrumentation-tedious@npm:0.15.0":
+ version: 0.15.0
+ resolution: "@opentelemetry/instrumentation-tedious@npm:0.15.0"
+ dependencies:
+ "@opentelemetry/instrumentation": "npm:^0.54.0"
+ "@opentelemetry/semantic-conventions": "npm:^1.27.0"
+ "@types/tedious": "npm:^4.0.14"
+ peerDependencies:
+ "@opentelemetry/api": ^1.3.0
+ checksum: 10c0/2e1e86497a09511dd1271968ca5610008a985901076d17252e3c27875a85253394bea9b24387a913babfca15c54523b00ddcde2d252c7a5cc8d5952676c82ce7
+ languageName: node
+ linkType: hard
+
"@opentelemetry/instrumentation-undici@npm:0.6.0":
version: 0.6.0
resolution: "@opentelemetry/instrumentation-undici@npm:0.6.0"
@@ -2566,22 +2620,23 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/plugin-commonjs@npm:26.0.1":
- version: 26.0.1
- resolution: "@rollup/plugin-commonjs@npm:26.0.1"
+"@rollup/plugin-commonjs@npm:28.0.1":
+ version: 28.0.1
+ resolution: "@rollup/plugin-commonjs@npm:28.0.1"
dependencies:
"@rollup/pluginutils": "npm:^5.0.1"
commondir: "npm:^1.0.1"
estree-walker: "npm:^2.0.2"
- glob: "npm:^10.4.1"
+ fdir: "npm:^6.2.0"
is-reference: "npm:1.2.1"
magic-string: "npm:^0.30.3"
+ picomatch: "npm:^4.0.2"
peerDependencies:
rollup: ^2.68.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
- checksum: 10c0/483290d327bdb4147584c37d73e47df2c717735f1902cd2f66ebc83c7b40ae10e5a8d5e626f24b76ad4ac489eab4a8c13869410aad663810848b0abc89a630cf
+ checksum: 10c0/15d73306f539763a4b0d5723a0be9099b56d07118ff12b4c7f4c04b26e762076706e9f88a45f131d639ed9b7bd52e51facf93f2ca265b994172677b48ca705fe
languageName: node
linkType: hard
@@ -2615,49 +2670,45 @@ __metadata:
languageName: node
linkType: hard
-"@sentry-internal/browser-utils@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry-internal/browser-utils@npm:8.37.1"
+"@sentry-internal/browser-utils@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry-internal/browser-utils@npm:8.41.0"
dependencies:
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/1202346f296db00ef1833d7bf6c8ccf728e5017d0fa352da72d631b822d9a1adc29be30e5c3dac2f5962c349c507b6777a98d9535c806694139fa5e08d4b43a4
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/287f5a900093ee6a41f6dd574761222db3cbf94591957b8b9146ee206ff461fb00647602656b78d2132c6b3362699c751c4f500ade0ccc16e2faea2cf7235b13
languageName: node
linkType: hard
-"@sentry-internal/feedback@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry-internal/feedback@npm:8.37.1"
+"@sentry-internal/feedback@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry-internal/feedback@npm:8.41.0"
dependencies:
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/a3fca317b47d74ee1d34cf8f9fb6765a777833c56432b1db99841811c556bd801f25c63fde3a47dfbe7100a4b45a65224eef1a7d12d3c7480483e8b38918d908
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/a6476b205fb5ce25dedcb19bf39273ed76d0026e17be9e4a1df43d3a91e395f76a0e47d8302ab3ecd176e7c9ed99893ffb2c759e57e59e326c9f4330bddda812
languageName: node
linkType: hard
-"@sentry-internal/replay-canvas@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry-internal/replay-canvas@npm:8.37.1"
+"@sentry-internal/replay-canvas@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry-internal/replay-canvas@npm:8.41.0"
dependencies:
- "@sentry-internal/replay": "npm:8.37.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/8109f1b4aad8eedf0731f8d7221b7d51fccb6d4737cb43b103df5fa9b7fbc82b321443ab082491f3ee72a7c453681c4de128041298a8948d7d61e3b68f3467a1
+ "@sentry-internal/replay": "npm:8.41.0"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/2ec23d499d81a465e8055705d177308d2ad02428b7b8c0b1aadb1a288e4e559270998d57ea1dd78e2c670cd347fa0dc9c6561c2bbf0c786a31d1cc9b02a2d1e8
languageName: node
linkType: hard
-"@sentry-internal/replay@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry-internal/replay@npm:8.37.1"
+"@sentry-internal/replay@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry-internal/replay@npm:8.41.0"
dependencies:
- "@sentry-internal/browser-utils": "npm:8.37.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/88ee4e32874f20623efaff71531cbb451f5201cbd78faed5c09c6651d463c9970b5432e5deb0b02933131dc5d8c71b73104b7fa225a565a98b8436b752816359
+ "@sentry-internal/browser-utils": "npm:8.41.0"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/0d7e0bb9f94157790d5137b274cd08e01c5b6f97890941e51502bc98896f8a26cbe32a4e25658f79ac594922c3c4d0bcb62e46c52f7dc361e1eb502ed2e769b8
languageName: node
linkType: hard
@@ -2668,18 +2719,17 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/browser@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/browser@npm:8.37.1"
+"@sentry/browser@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/browser@npm:8.41.0"
dependencies:
- "@sentry-internal/browser-utils": "npm:8.37.1"
- "@sentry-internal/feedback": "npm:8.37.1"
- "@sentry-internal/replay": "npm:8.37.1"
- "@sentry-internal/replay-canvas": "npm:8.37.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/1cf0844ac9d7731a639efbf0a48abb111a6d91250010154cf83b5219a01a0bc48cc2dba3fe7cbd2f21fb4d5aa67db4cc5c1eac8cd8160e20ed91d1b9df3d0af5
+ "@sentry-internal/browser-utils": "npm:8.41.0"
+ "@sentry-internal/feedback": "npm:8.41.0"
+ "@sentry-internal/replay": "npm:8.41.0"
+ "@sentry-internal/replay-canvas": "npm:8.41.0"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/a0a8f263d6b2c0343af005f0f9415b5229e1024281e30f88c510ff802f9efc3052bd60ff010bf5645cd3ed67f00cbd5bb29191de493837c96bd33d7fd27efe8e
languageName: node
linkType: hard
@@ -2785,32 +2835,30 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/core@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/core@npm:8.37.1"
+"@sentry/core@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/core@npm:8.41.0"
dependencies:
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/29f2f8c3aa029366888a3d24b86bc75046ac25f6d45270be5eda78ab9c84cd8a08d9af896c3232fad1975a3c58563f94a75f6bea76cf24879f7a5b96ef8a8a29
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/915fc2b64deef6d54053830d86dbe46e00eb445c7e82da7d31915241aeb7cc4919430e91ef5e9f6c270d102911a692af1138d318fd4660fc65692fec92365100
languageName: node
linkType: hard
-"@sentry/nextjs@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/nextjs@npm:8.37.1"
+"@sentry/nextjs@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/nextjs@npm:8.41.0"
dependencies:
"@opentelemetry/api": "npm:^1.9.0"
"@opentelemetry/instrumentation-http": "npm:0.53.0"
"@opentelemetry/semantic-conventions": "npm:^1.27.0"
- "@rollup/plugin-commonjs": "npm:26.0.1"
- "@sentry-internal/browser-utils": "npm:8.37.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/node": "npm:8.37.1"
- "@sentry/opentelemetry": "npm:8.37.1"
- "@sentry/react": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- "@sentry/vercel-edge": "npm:8.37.1"
+ "@rollup/plugin-commonjs": "npm:28.0.1"
+ "@sentry-internal/browser-utils": "npm:8.41.0"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/node": "npm:8.41.0"
+ "@sentry/opentelemetry": "npm:8.41.0"
+ "@sentry/react": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ "@sentry/vercel-edge": "npm:8.41.0"
"@sentry/webpack-plugin": "npm:2.22.6"
chalk: "npm:3.0.0"
resolve: "npm:1.22.8"
@@ -2818,19 +2866,19 @@ __metadata:
stacktrace-parser: "npm:^0.1.10"
peerDependencies:
next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0
- checksum: 10c0/cb9ffc22956ec006bc56458f174b87eb7a91fa1c41f10430b52ec6e16bc36898cf22984dc06b9c421ac562982eaf2159db2a5be4b9416ef6110a48c23cff50bb
+ checksum: 10c0/0366521ea6cedbc0b71198e97c44d4cb172ff00b9c7dab69b3513efe7e5bb1b5def927256bf02edfa4b0b65891e6b7a96042af1d6a9438850cc3be989047c3de
languageName: node
linkType: hard
-"@sentry/node@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/node@npm:8.37.1"
+"@sentry/node@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/node@npm:8.41.0"
dependencies:
"@opentelemetry/api": "npm:^1.9.0"
"@opentelemetry/context-async-hooks": "npm:^1.25.1"
"@opentelemetry/core": "npm:^1.25.1"
"@opentelemetry/instrumentation": "npm:^0.54.0"
- "@opentelemetry/instrumentation-amqplib": "npm:^0.42.0"
+ "@opentelemetry/instrumentation-amqplib": "npm:^0.43.0"
"@opentelemetry/instrumentation-connect": "npm:0.40.0"
"@opentelemetry/instrumentation-dataloader": "npm:0.12.0"
"@opentelemetry/instrumentation-express": "npm:0.44.0"
@@ -2842,6 +2890,7 @@ __metadata:
"@opentelemetry/instrumentation-http": "npm:0.53.0"
"@opentelemetry/instrumentation-ioredis": "npm:0.43.0"
"@opentelemetry/instrumentation-kafkajs": "npm:0.4.0"
+ "@opentelemetry/instrumentation-knex": "npm:0.41.0"
"@opentelemetry/instrumentation-koa": "npm:0.43.0"
"@opentelemetry/instrumentation-lru-memoizer": "npm:0.40.0"
"@opentelemetry/instrumentation-mongodb": "npm:0.48.0"
@@ -2851,77 +2900,65 @@ __metadata:
"@opentelemetry/instrumentation-nestjs-core": "npm:0.40.0"
"@opentelemetry/instrumentation-pg": "npm:0.44.0"
"@opentelemetry/instrumentation-redis-4": "npm:0.42.0"
+ "@opentelemetry/instrumentation-tedious": "npm:0.15.0"
"@opentelemetry/instrumentation-undici": "npm:0.6.0"
"@opentelemetry/resources": "npm:^1.26.0"
"@opentelemetry/sdk-trace-base": "npm:^1.26.0"
"@opentelemetry/semantic-conventions": "npm:^1.27.0"
"@prisma/instrumentation": "npm:5.19.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/opentelemetry": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/opentelemetry": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
import-in-the-middle: "npm:^1.11.2"
- checksum: 10c0/8961ebb4e4bc29a8549769b7f83432ceb48106dc24155eb15d65d064a97a57902b6d61224408348b30e141d20a674fdd294d6831e970d08c3d3d425e1610efb7
+ checksum: 10c0/df5e1b17eb03e8b60f3f35ce63da8d236e7bab0dd0482710e936ca0995caaa328e0d2da513c8442f60ac06b754c2bfbc5871fe1e0492f6cc12eb0e8163aaae31
languageName: node
linkType: hard
-"@sentry/opentelemetry@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/opentelemetry@npm:8.37.1"
+"@sentry/opentelemetry@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/opentelemetry@npm:8.41.0"
dependencies:
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
peerDependencies:
"@opentelemetry/api": ^1.9.0
"@opentelemetry/core": ^1.25.1
"@opentelemetry/instrumentation": ^0.54.0
"@opentelemetry/sdk-trace-base": ^1.26.0
"@opentelemetry/semantic-conventions": ^1.27.0
- checksum: 10c0/b233e9665b56dc578fefbd22edd715ea0a6f274013740d773cfb23dc57ece75bf6abcc71e4f39803b35fc56eec0e7ed5639fd26c76adccd864cb67a0c3a8c11d
+ checksum: 10c0/d817dd977e4730d4a5fe7330f43bf8fae1c73e9879158ec97cd03c7e9eddafc4ac775c755f62002e8795812563ee7acb2a4ef3a9bee53613a1ca489df7562904
languageName: node
linkType: hard
-"@sentry/react@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/react@npm:8.37.1"
+"@sentry/react@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/react@npm:8.41.0"
dependencies:
- "@sentry/browser": "npm:8.37.1"
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
+ "@sentry/browser": "npm:8.41.0"
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
hoist-non-react-statics: "npm:^3.3.2"
peerDependencies:
react: ^16.14.0 || 17.x || 18.x || 19.x
- checksum: 10c0/2930403dfb853340b7594fcb514599e512ecd2b0b6c8cfc42bb9ebaf9fff540f2391fafd7d960e53aa2130dd3875a902a8eed7b4f0ec0b72d1dbd1f438e78ae0
+ checksum: 10c0/8db67897f68c75b7ec03579bda06f7a418a617a5e29bc110a9ee9e6675d8c91395a8c381aab595753928c9f80fd44e19a0397613bc285f9fedd19956726bd279
languageName: node
linkType: hard
-"@sentry/types@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/types@npm:8.37.1"
- checksum: 10c0/f3cbd0c928e80f98002bda76b48b29dd089eb24eea33d3f0ddda1ffae1615fc16da6218fce43cc5ba637577c3d73bb34b7e9ce4c3545deb6a9d2929008a2b83d
+"@sentry/types@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/types@npm:8.41.0"
+ checksum: 10c0/dd8643f63811802c8816fa9d6fbb495b646a4e271f66740dd95496a1f99278639298eb0c76dd555d7f6055516f9d7b4ad6aec447357748345a9b31373c87ce9f
languageName: node
linkType: hard
-"@sentry/utils@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/utils@npm:8.37.1"
- dependencies:
- "@sentry/types": "npm:8.37.1"
- checksum: 10c0/04d6ece0713d686028c8ab9fe9c082cf611365620010b2777f4dd2b41da7cbc9bcede5e9581e6569062bf5165f8a3f7602dfefd72ea73185bd57f16aeca6642a
- languageName: node
- linkType: hard
-
-"@sentry/vercel-edge@npm:8.37.1":
- version: 8.37.1
- resolution: "@sentry/vercel-edge@npm:8.37.1"
+"@sentry/vercel-edge@npm:8.41.0":
+ version: 8.41.0
+ resolution: "@sentry/vercel-edge@npm:8.41.0"
dependencies:
"@opentelemetry/api": "npm:^1.9.0"
- "@sentry/core": "npm:8.37.1"
- "@sentry/types": "npm:8.37.1"
- "@sentry/utils": "npm:8.37.1"
- checksum: 10c0/9e06c9481a7ddbfc8a4d91b16ae37b061a50ef93ce1381f5bd6691ccae2d12c5119e011a8f49af1a8c09313c64241371f607a6e004e1ba7884c3f7ccbab53608
+ "@sentry/core": "npm:8.41.0"
+ "@sentry/types": "npm:8.41.0"
+ checksum: 10c0/c88d3e3dc493c302fef853070f5a4792193265eb43f11a7cf7f60a305528ca75a71d8b33bc74d69296541d5426469d1343708a381a677bc0ee1c02e030a624c3
languageName: node
linkType: hard
@@ -3015,51 +3052,51 @@ __metadata:
languageName: node
linkType: hard
-"@tanstack/eslint-plugin-query@npm:5.59.20":
- version: 5.59.20
- resolution: "@tanstack/eslint-plugin-query@npm:5.59.20"
+"@tanstack/eslint-plugin-query@npm:5.61.4":
+ version: 5.61.4
+ resolution: "@tanstack/eslint-plugin-query@npm:5.61.4"
dependencies:
- "@typescript-eslint/utils": "npm:^8.3.0"
+ "@typescript-eslint/utils": "npm:^8.15.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
- checksum: 10c0/95ded76098059b59ba7c4d47841c84a233b75e10b68261d01a5bce92afe1a70873e2fe91ef7650f9703aeb2d5f56fca29a56e7ee8d55c24f5b7437efdc2c80ea
+ checksum: 10c0/1cd38fb6f534d277baffebb4893ddc246a2fb041d97a4bfc3be465313ad3d92e31d683e0ad0ccc16858b167b330b0d72bb1743ba5fb92babd584d7b011d77ae7
languageName: node
linkType: hard
-"@tanstack/query-core@npm:5.59.20":
- version: 5.59.20
- resolution: "@tanstack/query-core@npm:5.59.20"
- checksum: 10c0/c93a8d41e21db532e92c1c90916bec578729d32d1f39d655603b9e81a9d5aebc8588a4d6a75928e04d3ddc90e71a1f8dc066a61d0ba24108eaf60cc2ce024a2f
+"@tanstack/query-core@npm:5.61.5":
+ version: 5.61.5
+ resolution: "@tanstack/query-core@npm:5.61.5"
+ checksum: 10c0/597af37641eb7e4123259f2f4244de977de98b1cab245a246f892b73bceac78d02e49f3368304e74691ab5ed74aa2b203b0a17045406f0303bbf962696856db9
languageName: node
linkType: hard
-"@tanstack/query-devtools@npm:5.59.20":
- version: 5.59.20
- resolution: "@tanstack/query-devtools@npm:5.59.20"
- checksum: 10c0/e90008af9c5754bacb19b78b54bbbb60b4ef4aeae8ff46349cbf8596f50a49b7a66db5b9c5fc4e5d9eba6d1fe4991f0178c1c4eacdc3bee5b8f4b48f50997174
+"@tanstack/query-devtools@npm:5.61.4":
+ version: 5.61.4
+ resolution: "@tanstack/query-devtools@npm:5.61.4"
+ checksum: 10c0/44886dbf92849d17729bf779a184a3fd304711d8ce8061c1deccc089a65d168ed8f0cf0da43b59a9a22a9afb7b25fba99de8af47fe44479ac3082d852dcaaf53
languageName: node
linkType: hard
-"@tanstack/react-query-devtools@npm:5.59.20":
- version: 5.59.20
- resolution: "@tanstack/react-query-devtools@npm:5.59.20"
+"@tanstack/react-query-devtools@npm:5.61.5":
+ version: 5.61.5
+ resolution: "@tanstack/react-query-devtools@npm:5.61.5"
dependencies:
- "@tanstack/query-devtools": "npm:5.59.20"
+ "@tanstack/query-devtools": "npm:5.61.4"
peerDependencies:
- "@tanstack/react-query": ^5.59.20
+ "@tanstack/react-query": ^5.61.5
react: ^18 || ^19
- checksum: 10c0/bd49b0b66840dcf3e6939f78da9707f84d5c1da27a1e51e3ea1e389485d07829807b8b4ec11e11d4d30f709f7e82d732b121fa95715e809f312ef517c49cce7b
+ checksum: 10c0/62c7d105e6d5bc635cd0545654ebf7b6d95856d51423a3db686b86abc1c102b0bbe4d9d83f2a7b5111dc8f07a83b32b821cc37562ee9c3e45d74d5d1b198a06c
languageName: node
linkType: hard
-"@tanstack/react-query@npm:5.59.20":
- version: 5.59.20
- resolution: "@tanstack/react-query@npm:5.59.20"
+"@tanstack/react-query@npm:5.61.5":
+ version: 5.61.5
+ resolution: "@tanstack/react-query@npm:5.61.5"
dependencies:
- "@tanstack/query-core": "npm:5.59.20"
+ "@tanstack/query-core": "npm:5.61.5"
peerDependencies:
react: ^18 || ^19
- checksum: 10c0/fc3342c3a26c51c866d54082d14f86b2f644847ea8f9051000e529a012ea5437e18e35f999fcc453b2eecb0faaf29bd1aaec0956587ade63ba02262d6737800a
+ checksum: 10c0/1c535836025622a13f7a53947bc715147a34a5f98bcda32c0afd56f605073f21788b19f3e47e21876cad2cff7c9cd3b25cadd12eba5ddcc4c7e37f75d0742c32
languageName: node
linkType: hard
@@ -3067,25 +3104,25 @@ __metadata:
version: 0.0.0-use.local
resolution: "@tooling/eslint@workspace:tooling/eslint"
dependencies:
- "@eslint/compat": "npm:1.2.2"
- "@eslint/eslintrc": "npm:3.1.0"
- "@eslint/js": "npm:9.14.0"
- "@tanstack/eslint-plugin-query": "npm:5.59.20"
+ "@eslint/compat": "npm:1.2.3"
+ "@eslint/eslintrc": "npm:3.2.0"
+ "@eslint/js": "npm:9.15.0"
+ "@tanstack/eslint-plugin-query": "npm:5.61.4"
"@tooling/prettier": "workspace:*"
"@tooling/staged": "workspace:^"
"@tooling/typescript": "workspace:*"
"@types/eslint": "npm:9.6.1"
"@types/eslint__eslintrc": "npm:2.1.2"
"@types/eslint__js": "npm:8.42.3"
- eslint: "npm:9.14.0"
+ eslint: "npm:9.15.0"
eslint-config-next: "npm:15.0.3"
eslint-config-prettier: "npm:9.1.0"
- eslint-plugin-turbo: "npm:2.2.3"
+ eslint-plugin-turbo: "npm:2.3.3"
next: "npm:15.0.3"
react: "npm:18.3.1"
react-dom: "npm:18.3.1"
- typescript: "npm:5.6.3"
- typescript-eslint: "npm:8.13.0"
+ typescript: "npm:5.7.2"
+ typescript-eslint: "npm:8.16.0"
bin:
eslint-lint: ./bin/cli.js
languageName: unknown
@@ -3109,7 +3146,7 @@ __metadata:
"@ianvs/prettier-plugin-sort-imports": "npm:4.4.0"
"@tooling/staged": "workspace:^"
"@tooling/typescript": "workspace:*"
- prettier: "npm:3.3.3"
+ prettier: "npm:3.4.1"
bin:
prettier-format: ./bin/cli.js
languageName: unknown
@@ -3128,9 +3165,9 @@ __metadata:
resolution: "@tooling/typescript@workspace:tooling/typescript"
dependencies:
"@total-typescript/ts-reset": "npm:0.6.1"
- "@types/node": "npm:20.17.6"
+ "@types/node": "npm:22.10.1"
network-information-types: "npm:0.1.1"
- typescript: "npm:5.6.3"
+ typescript: "npm:5.7.2"
languageName: unknown
linkType: soft
@@ -3340,7 +3377,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0, @types/node@npm:^22.0.1":
+"@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0":
version: 22.9.0
resolution: "@types/node@npm:22.9.0"
dependencies:
@@ -3349,12 +3386,21 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:20.17.6":
- version: 20.17.6
- resolution: "@types/node@npm:20.17.6"
+"@types/node@npm:22.10.1":
+ version: 22.10.1
+ resolution: "@types/node@npm:22.10.1"
dependencies:
- undici-types: "npm:~6.19.2"
- checksum: 10c0/5918c7ff8368bbe6d06d5e739c8ae41a9db41628f28760c60cda797be7d233406f07c4d0e6fdd960a0a342ec4173c2217eb6624e06bece21c1f1dd1b92805c15
+ undici-types: "npm:~6.20.0"
+ checksum: 10c0/0fbb6d29fa35d807f0223a4db709c598ac08d66820240a2cd6a8a69b8f0bc921d65b339d850a666b43b4e779f967e6ed6cf6f0fca3575e08241e6b900364c234
+ languageName: node
+ linkType: hard
+
+"@types/node@npm:^22.8.7":
+ version: 22.9.1
+ resolution: "@types/node@npm:22.9.1"
+ dependencies:
+ undici-types: "npm:~6.19.8"
+ checksum: 10c0/ea489ae603aa8874e4e88980aab6f2dad09c755da779c88dd142983bfe9609803c89415ca7781f723072934066f63daf2b3339ef084a8ad1a8079cf3958be243
languageName: node
linkType: hard
@@ -3485,6 +3531,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/tedious@npm:^4.0.14":
+ version: 4.0.14
+ resolution: "@types/tedious@npm:4.0.14"
+ dependencies:
+ "@types/node": "npm:*"
+ checksum: 10c0/d2914f8e9b5b998e4275ec5f0130cba1c2fb47e75616b5c125a65ef6c1db2f1dc3f978c7900693856a15d72bbb4f4e94f805537a4ecb6dc126c64415d31c0590
+ languageName: node
+ linkType: hard
+
"@types/tough-cookie@npm:*":
version: 4.0.5
resolution: "@types/tough-cookie@npm:4.0.5"
@@ -3492,7 +3547,30 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/eslint-plugin@npm:8.13.0, @typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
+"@typescript-eslint/eslint-plugin@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/eslint-plugin@npm:8.16.0"
+ dependencies:
+ "@eslint-community/regexpp": "npm:^4.10.0"
+ "@typescript-eslint/scope-manager": "npm:8.16.0"
+ "@typescript-eslint/type-utils": "npm:8.16.0"
+ "@typescript-eslint/utils": "npm:8.16.0"
+ "@typescript-eslint/visitor-keys": "npm:8.16.0"
+ graphemer: "npm:^1.4.0"
+ ignore: "npm:^5.3.1"
+ natural-compare: "npm:^1.4.0"
+ ts-api-utils: "npm:^1.3.0"
+ peerDependencies:
+ "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/b03612b726ee5aff631cd50e05ceeb06a522e64465e4efdc134e3a27a09406b959ef7a05ec4acef1956b3674dc4fedb6d3a62ce69382f9e30c227bd4093003e5
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version: 8.13.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.13.0"
dependencies:
@@ -3515,7 +3593,25 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/parser@npm:8.13.0, @typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
+"@typescript-eslint/parser@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/parser@npm:8.16.0"
+ dependencies:
+ "@typescript-eslint/scope-manager": "npm:8.16.0"
+ "@typescript-eslint/types": "npm:8.16.0"
+ "@typescript-eslint/typescript-estree": "npm:8.16.0"
+ "@typescript-eslint/visitor-keys": "npm:8.16.0"
+ debug: "npm:^4.3.4"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/e49c6640a7a863a16baecfbc5b99392a4731e9c7e9c9aaae4efbc354e305485fe0f39a28bf0acfae85bc01ce37fe0cc140fd315fdaca8b18f9b5e0addff8ceae
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version: 8.13.0
resolution: "@typescript-eslint/parser@npm:8.13.0"
dependencies:
@@ -3543,6 +3639,16 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/scope-manager@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/scope-manager@npm:8.16.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.16.0"
+ "@typescript-eslint/visitor-keys": "npm:8.16.0"
+ checksum: 10c0/23b7c738b83f381c6419a36e6ca951944187e3e00abb8e012bce8041880410fe498303e28bdeb0e619023a69b14cf32a5ec1f9427c5382807788cd8e52a46a6e
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/type-utils@npm:8.13.0":
version: 8.13.0
resolution: "@typescript-eslint/type-utils@npm:8.13.0"
@@ -3558,6 +3664,23 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/type-utils@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/type-utils@npm:8.16.0"
+ dependencies:
+ "@typescript-eslint/typescript-estree": "npm:8.16.0"
+ "@typescript-eslint/utils": "npm:8.16.0"
+ debug: "npm:^4.3.4"
+ ts-api-utils: "npm:^1.3.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/24c0e815c8bdf99bf488c7528bd6a7c790e8b3b674cb7fb075663afc2ee26b48e6f4cf7c0d14bb21e2376ca62bd8525cbcb5688f36135b00b62b1d353d7235b9
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/types@npm:7.18.0":
version: 7.18.0
resolution: "@typescript-eslint/types@npm:7.18.0"
@@ -3572,6 +3695,13 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/types@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/types@npm:8.16.0"
+ checksum: 10c0/141e257ab4060a9c0e2e14334ca14ab6be713659bfa38acd13be70a699fb5f36932a2584376b063063ab3d723b24bc703dbfb1ce57d61d7cfd7ec5bd8a975129
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/typescript-estree@npm:8.13.0":
version: 8.13.0
resolution: "@typescript-eslint/typescript-estree@npm:8.13.0"
@@ -3591,6 +3721,25 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/typescript-estree@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/typescript-estree@npm:8.16.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.16.0"
+ "@typescript-eslint/visitor-keys": "npm:8.16.0"
+ debug: "npm:^4.3.4"
+ fast-glob: "npm:^3.3.2"
+ is-glob: "npm:^4.0.3"
+ minimatch: "npm:^9.0.4"
+ semver: "npm:^7.6.0"
+ ts-api-utils: "npm:^1.3.0"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/f28fea5af4798a718b6735d1758b791a331af17386b83cb2856d89934a5d1693f7cb805e73c3b33f29140884ac8ead9931b1d7c3de10176fa18ca7a346fe10d0
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/typescript-estree@npm:^7.6.0":
version: 7.18.0
resolution: "@typescript-eslint/typescript-estree@npm:7.18.0"
@@ -3610,7 +3759,7 @@ __metadata:
languageName: node
linkType: hard
-"@typescript-eslint/utils@npm:8.13.0, @typescript-eslint/utils@npm:^8.3.0":
+"@typescript-eslint/utils@npm:8.13.0":
version: 8.13.0
resolution: "@typescript-eslint/utils@npm:8.13.0"
dependencies:
@@ -3624,6 +3773,23 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/utils@npm:8.16.0, @typescript-eslint/utils@npm:^8.15.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/utils@npm:8.16.0"
+ dependencies:
+ "@eslint-community/eslint-utils": "npm:^4.4.0"
+ "@typescript-eslint/scope-manager": "npm:8.16.0"
+ "@typescript-eslint/types": "npm:8.16.0"
+ "@typescript-eslint/typescript-estree": "npm:8.16.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10c0/1e61187eef3da1ab1486d2a977d8f3b1cb8ef7fa26338500a17eb875ca42a8942ef3f2241f509eef74cf7b5620c109483afc7d83d5b0ab79b1e15920f5a49818
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/visitor-keys@npm:7.18.0":
version: 7.18.0
resolution: "@typescript-eslint/visitor-keys@npm:7.18.0"
@@ -3644,6 +3810,16 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/visitor-keys@npm:8.16.0":
+ version: 8.16.0
+ resolution: "@typescript-eslint/visitor-keys@npm:8.16.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.16.0"
+ eslint-visitor-keys: "npm:^4.2.0"
+ checksum: 10c0/537df37801831aa8d91082b2adbffafd40305ed4518f0e7d3cbb17cc466d8b9ac95ac91fa232e7fe585d7c522d1564489ec80052ebb2a6ab9bbf89ef9dd9b7bc
+ languageName: node
+ linkType: hard
+
"@vue/compiler-core@npm:3.5.12":
version: 3.5.12
resolution: "@vue/compiler-core@npm:3.5.12"
@@ -3706,19 +3882,20 @@ __metadata:
resolution: "@workspace/common@workspace:packages/common"
dependencies:
"@ackee/antonio-core": "npm:5.0.0"
- "@emotion/react": "npm:11.13.3"
+ "@emotion/react": "npm:11.13.5"
"@emotion/server": "npm:11.11.0"
- "@emotion/styled": "npm:11.13.0"
+ "@emotion/styled": "npm:11.13.5"
"@hookform/resolvers": "npm:3.9.1"
- "@mui/icons-material": "npm:6.1.6"
+ "@mui/icons-material": "npm:6.1.8"
"@mui/lab": "npm:6.0.0-beta.14"
- "@mui/material": "npm:6.1.6"
- "@sentry/nextjs": "npm:8.37.1"
+ "@mui/material": "npm:6.1.8"
+ "@mui/system": "npm:6.1.8"
+ "@sentry/nextjs": "npm:8.41.0"
"@simplewebauthn/browser": "npm:11.0.0"
"@simplewebauthn/server": "npm:11.0.0"
"@t3-oss/env-nextjs": "npm:0.11.1"
- "@tanstack/react-query": "npm:5.59.20"
- "@tanstack/react-query-devtools": "npm:5.59.20"
+ "@tanstack/react-query": "npm:5.61.5"
+ "@tanstack/react-query-devtools": "npm:5.61.5"
"@tooling/eslint": "workspace:*"
"@tooling/madge": "workspace:*"
"@tooling/prettier": "workspace:*"
@@ -3726,21 +3903,22 @@ __metadata:
"@types/react": "npm:18.3.12"
"@types/react-dom": "npm:18.3.1"
"@workspace/logger": "workspace:*"
- cookie: "npm:1.0.1"
+ cookie: "npm:1.0.2"
core-js: "npm:3.39.0"
- firebase: "npm:11.0.1"
- firebase-admin: "npm:12.7.0"
+ firebase: "npm:11.0.2"
+ firebase-admin: "npm:13.0.1"
next: "npm:15.0.3"
normalize.css: "npm:8.0.1"
+ nuqs: "npm:2.2.3"
radash: "npm:12.1.0"
react: "npm:18.3.1"
react-dom: "npm:18.3.1"
- react-hook-form: "npm:7.53.1"
- react-intl: "npm:6.8.7"
+ react-hook-form: "npm:7.53.2"
+ react-intl: "npm:7.0.1"
react-toastify: "npm:10.0.6"
reset.css: "npm:2.0.2"
tsc-alias: "npm:1.8.10"
- typescript: "npm:5.6.3"
+ typescript: "npm:5.7.2"
zod: "npm:3.23.8"
languageName: unknown
linkType: soft
@@ -3749,17 +3927,31 @@ __metadata:
version: 0.0.0-use.local
resolution: "@workspace/logger@workspace:packages/logger"
dependencies:
- "@sentry/browser": "npm:8.37.1"
"@tooling/eslint": "workspace:*"
"@tooling/prettier": "workspace:*"
"@tooling/typescript": "workspace:*"
loglevel: "npm:1.9.2"
peerDependencies:
+ "@sentry/nextjs": 8.x
next: 15.x
react: 18.x
languageName: unknown
linkType: soft
+"@workspace/sentry@workspace:*, @workspace/sentry@workspace:packages/sentry":
+ version: 0.0.0-use.local
+ resolution: "@workspace/sentry@workspace:packages/sentry"
+ dependencies:
+ "@sentry/nextjs": "npm:8.41.0"
+ "@tooling/eslint": "workspace:*"
+ "@tooling/prettier": "workspace:*"
+ "@tooling/typescript": "workspace:*"
+ next: "npm:15.0.3"
+ react: "npm:18.3.1"
+ react-dom: "npm:18.3.1"
+ languageName: unknown
+ linkType: soft
+
"abbrev@npm:^2.0.0":
version: 2.0.0
resolution: "abbrev@npm:2.0.0"
@@ -4474,10 +4666,10 @@ __metadata:
languageName: node
linkType: hard
-"cookie@npm:1.0.1":
- version: 1.0.1
- resolution: "cookie@npm:1.0.1"
- checksum: 10c0/80afdcad7fe9cab7a0ea1802629f6f4cf9ff957e9489daa7a813e3ac4ca842b0e5ab3f8e6a6ddc1f3f5c771b81c229afd6f0f3c083025d68c48d214ea8fb1097
+"cookie@npm:1.0.2":
+ version: 1.0.2
+ resolution: "cookie@npm:1.0.2"
+ checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b
languageName: node
linkType: hard
@@ -4517,14 +4709,14 @@ __metadata:
languageName: node
linkType: hard
-"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2":
- version: 7.0.4
- resolution: "cross-spawn@npm:7.0.4"
+"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.5":
+ version: 7.0.6
+ resolution: "cross-spawn@npm:7.0.6"
dependencies:
path-key: "npm:^3.1.0"
shebang-command: "npm:^2.0.0"
which: "npm:^2.0.1"
- checksum: 10c0/04f6c70dcbdd156f53073f13730f71160dabb91c8dfbdb24a873f4580ad7ca4b73c062ddfaaa2ba46d0dac433856d0cc0a07ff7173cd5404fefde952e87c9dbf
+ checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1
languageName: node
linkType: hard
@@ -5268,14 +5460,14 @@ __metadata:
languageName: node
linkType: hard
-"eslint-plugin-turbo@npm:2.2.3":
- version: 2.2.3
- resolution: "eslint-plugin-turbo@npm:2.2.3"
+"eslint-plugin-turbo@npm:2.3.3":
+ version: 2.3.3
+ resolution: "eslint-plugin-turbo@npm:2.3.3"
dependencies:
dotenv: "npm:16.0.3"
peerDependencies:
eslint: ">6.6.0"
- checksum: 10c0/37b88dc810e53cdd0b28cf381d89777db64828f00c4fd3ed72d9000624923f2388eabc1db1ace3414e9edec335f75e133a6fa60d80d35004487ddd1baa10fe5e
+ checksum: 10c0/dcad30a72cfb391b44a2f14f9d75b613330a315e4a1037a0cb16e36941dc8d4af1a86caa5c1d7ff8a9bbeea84fb1a5998c18690df0ada9afc416754410b4f18f
languageName: node
linkType: hard
@@ -5303,25 +5495,25 @@ __metadata:
languageName: node
linkType: hard
-"eslint@npm:9.14.0":
- version: 9.14.0
- resolution: "eslint@npm:9.14.0"
+"eslint@npm:9.15.0":
+ version: 9.15.0
+ resolution: "eslint@npm:9.15.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.12.1"
- "@eslint/config-array": "npm:^0.18.0"
- "@eslint/core": "npm:^0.7.0"
- "@eslint/eslintrc": "npm:^3.1.0"
- "@eslint/js": "npm:9.14.0"
- "@eslint/plugin-kit": "npm:^0.2.0"
+ "@eslint/config-array": "npm:^0.19.0"
+ "@eslint/core": "npm:^0.9.0"
+ "@eslint/eslintrc": "npm:^3.2.0"
+ "@eslint/js": "npm:9.15.0"
+ "@eslint/plugin-kit": "npm:^0.2.3"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
- "@humanwhocodes/retry": "npm:^0.4.0"
+ "@humanwhocodes/retry": "npm:^0.4.1"
"@types/estree": "npm:^1.0.6"
"@types/json-schema": "npm:^7.0.15"
ajv: "npm:^6.12.4"
chalk: "npm:^4.0.0"
- cross-spawn: "npm:^7.0.2"
+ cross-spawn: "npm:^7.0.5"
debug: "npm:^4.3.2"
escape-string-regexp: "npm:^4.0.0"
eslint-scope: "npm:^8.2.0"
@@ -5341,7 +5533,6 @@ __metadata:
minimatch: "npm:^3.1.2"
natural-compare: "npm:^1.4.0"
optionator: "npm:^0.9.3"
- text-table: "npm:^0.2.0"
peerDependencies:
jiti: "*"
peerDependenciesMeta:
@@ -5349,7 +5540,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
- checksum: 10c0/e1cbf571b75519ad0b24c27e66a6575e57cab2671ef5296e7b345d9ac3adc1a549118dcc74a05b651a7a13a5e61ebb680be6a3e04a80e1f22eba1931921b5187
+ checksum: 10c0/d0d7606f36bfcccb1c3703d0a24df32067b207a616f17efe5fb1765a91d13f085afffc4fc97ecde4ab9c9f4edd64d9b4ce750e13ff7937a25074b24bee15b20f
languageName: node
linkType: hard
@@ -5517,6 +5708,18 @@ __metadata:
languageName: node
linkType: hard
+"fdir@npm:^6.2.0":
+ version: 6.4.2
+ resolution: "fdir@npm:6.4.2"
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ checksum: 10c0/34829886f34a3ca4170eca7c7180ec4de51a3abb4d380344063c0ae2e289b11d2ba8b724afee974598c83027fea363ff598caf2b51bc4e6b1e0d8b80cc530573
+ languageName: node
+ linkType: hard
+
"fetch-headers@npm:^2.0.0":
version: 2.0.0
resolution: "fetch-headers@npm:2.0.0"
@@ -5580,63 +5783,64 @@ __metadata:
languageName: node
linkType: hard
-"firebase-admin@npm:12.7.0":
- version: 12.7.0
- resolution: "firebase-admin@npm:12.7.0"
+"firebase-admin@npm:13.0.1":
+ version: 13.0.1
+ resolution: "firebase-admin@npm:13.0.1"
dependencies:
"@fastify/busboy": "npm:^3.0.0"
- "@firebase/database-compat": "npm:1.0.8"
- "@firebase/database-types": "npm:1.0.5"
- "@google-cloud/firestore": "npm:^7.7.0"
- "@google-cloud/storage": "npm:^7.7.0"
- "@types/node": "npm:^22.0.1"
+ "@firebase/database-compat": "npm:^2.0.0"
+ "@firebase/database-types": "npm:^1.0.6"
+ "@google-cloud/firestore": "npm:^7.10.0"
+ "@google-cloud/storage": "npm:^7.14.0"
+ "@types/node": "npm:^22.8.7"
farmhash-modern: "npm:^1.1.0"
+ google-auth-library: "npm:^9.14.2"
jsonwebtoken: "npm:^9.0.0"
jwks-rsa: "npm:^3.1.0"
node-forge: "npm:^1.3.1"
- uuid: "npm:^10.0.0"
+ uuid: "npm:^11.0.2"
dependenciesMeta:
"@google-cloud/firestore":
optional: true
"@google-cloud/storage":
optional: true
- checksum: 10c0/5a6645b004adbc13bce4d9876e8d62135408bdcf3537c32493832472bd219d543830a01da1773aa4183e298f96a476095613e6c8b1d334721e10313b6272da34
- languageName: node
- linkType: hard
-
-"firebase@npm:11.0.1":
- version: 11.0.1
- resolution: "firebase@npm:11.0.1"
- dependencies:
- "@firebase/analytics": "npm:0.10.9"
- "@firebase/analytics-compat": "npm:0.2.15"
- "@firebase/app": "npm:0.10.15"
- "@firebase/app-check": "npm:0.8.9"
- "@firebase/app-check-compat": "npm:0.3.16"
- "@firebase/app-compat": "npm:0.2.45"
- "@firebase/app-types": "npm:0.9.2"
- "@firebase/auth": "npm:1.8.0"
- "@firebase/auth-compat": "npm:0.5.15"
- "@firebase/data-connect": "npm:0.1.1"
- "@firebase/database": "npm:1.0.9"
- "@firebase/database-compat": "npm:2.0.0"
- "@firebase/firestore": "npm:4.7.4"
- "@firebase/firestore-compat": "npm:0.3.39"
- "@firebase/functions": "npm:0.11.9"
- "@firebase/functions-compat": "npm:0.3.15"
- "@firebase/installations": "npm:0.6.10"
- "@firebase/installations-compat": "npm:0.2.10"
- "@firebase/messaging": "npm:0.12.13"
- "@firebase/messaging-compat": "npm:0.2.13"
- "@firebase/performance": "npm:0.6.10"
- "@firebase/performance-compat": "npm:0.2.10"
- "@firebase/remote-config": "npm:0.4.10"
- "@firebase/remote-config-compat": "npm:0.2.10"
- "@firebase/storage": "npm:0.13.3"
- "@firebase/storage-compat": "npm:0.3.13"
- "@firebase/util": "npm:1.10.1"
- "@firebase/vertexai": "npm:1.0.0"
- checksum: 10c0/84320423426bed293d466d1c0f8ff808479d50081a7ff93965b4c926245f0e0f654b3157d4cbf3acc73094296b338575a086760a300a7a46652d252059a8bca7
+ checksum: 10c0/0ecf6201141bdd765b0c47800f94bb2ece53d70db9e613127649cc370c4e6054305cde1b5eb573df41d13656eeccb3137392b23041e13e690626b7d37619c41d
+ languageName: node
+ linkType: hard
+
+"firebase@npm:11.0.2":
+ version: 11.0.2
+ resolution: "firebase@npm:11.0.2"
+ dependencies:
+ "@firebase/analytics": "npm:0.10.10"
+ "@firebase/analytics-compat": "npm:0.2.16"
+ "@firebase/app": "npm:0.10.16"
+ "@firebase/app-check": "npm:0.8.10"
+ "@firebase/app-check-compat": "npm:0.3.17"
+ "@firebase/app-compat": "npm:0.2.46"
+ "@firebase/app-types": "npm:0.9.3"
+ "@firebase/auth": "npm:1.8.1"
+ "@firebase/auth-compat": "npm:0.5.16"
+ "@firebase/data-connect": "npm:0.1.2"
+ "@firebase/database": "npm:1.0.10"
+ "@firebase/database-compat": "npm:2.0.1"
+ "@firebase/firestore": "npm:4.7.5"
+ "@firebase/firestore-compat": "npm:0.3.40"
+ "@firebase/functions": "npm:0.11.10"
+ "@firebase/functions-compat": "npm:0.3.16"
+ "@firebase/installations": "npm:0.6.11"
+ "@firebase/installations-compat": "npm:0.2.11"
+ "@firebase/messaging": "npm:0.12.14"
+ "@firebase/messaging-compat": "npm:0.2.14"
+ "@firebase/performance": "npm:0.6.11"
+ "@firebase/performance-compat": "npm:0.2.11"
+ "@firebase/remote-config": "npm:0.4.11"
+ "@firebase/remote-config-compat": "npm:0.2.11"
+ "@firebase/storage": "npm:0.13.4"
+ "@firebase/storage-compat": "npm:0.3.14"
+ "@firebase/util": "npm:1.10.2"
+ "@firebase/vertexai": "npm:1.0.1"
+ checksum: 10c0/65a934e552d461b367e970fed0351b6c603d1d89fc9cd46e6e09521b5a23d6ff210b51af9ac160fff191cab7f87ae184319bc7becbf05e6355e72a60b4aeaf14
languageName: node
linkType: hard
@@ -5870,7 +6074,7 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1":
+"glob@npm:^10.2.2, glob@npm:^10.3.10":
version: 10.4.5
resolution: "glob@npm:10.4.5"
dependencies:
@@ -5961,6 +6165,20 @@ __metadata:
languageName: node
linkType: hard
+"google-auth-library@npm:^9.14.2":
+ version: 9.15.0
+ resolution: "google-auth-library@npm:9.15.0"
+ dependencies:
+ base64-js: "npm:^1.3.0"
+ ecdsa-sig-formatter: "npm:^1.0.11"
+ gaxios: "npm:^6.1.1"
+ gcp-metadata: "npm:^6.1.0"
+ gtoken: "npm:^7.0.0"
+ jws: "npm:^4.0.0"
+ checksum: 10c0/f5a9a46e939147b181bac9b254f11dd8c2d05c15a65c9d3f2180252bef21c12af37d9893bc3caacafd226d6531a960535dbb5222ef869143f393c6a97639cc06
+ languageName: node
+ linkType: hard
+
"google-auth-library@npm:^9.3.0, google-auth-library@npm:^9.6.3":
version: 9.14.2
resolution: "google-auth-library@npm:9.14.2"
@@ -6169,12 +6387,12 @@ __metadata:
languageName: node
linkType: hard
-"husky@npm:9.1.6":
- version: 9.1.6
- resolution: "husky@npm:9.1.6"
+"husky@npm:9.1.7":
+ version: 9.1.7
+ resolution: "husky@npm:9.1.7"
bin:
husky: bin.js
- checksum: 10c0/705673db4a247c1febd9c5df5f6a3519106cf0335845027bb50a15fba9b1f542cb2610932ede96fd08008f6d9f49db0f15560509861808b0031cdc0e7c798bac
+ checksum: 10c0/35bb110a71086c48906aa7cd3ed4913fb913823715359d65e32e0b964cb1e255593b0ae8014a5005c66a68e6fa66c38dcfa8056dbbdfb8b0187c0ffe7ee3a58f
languageName: node
linkType: hard
@@ -6279,15 +6497,15 @@ __metadata:
languageName: node
linkType: hard
-"intl-messageformat@npm:10.7.6":
- version: 10.7.6
- resolution: "intl-messageformat@npm:10.7.6"
+"intl-messageformat@npm:10.7.7":
+ version: 10.7.7
+ resolution: "intl-messageformat@npm:10.7.7"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
+ "@formatjs/ecma402-abstract": "npm:2.2.4"
"@formatjs/fast-memoize": "npm:2.2.3"
- "@formatjs/icu-messageformat-parser": "npm:2.9.3"
+ "@formatjs/icu-messageformat-parser": "npm:2.9.4"
tslib: "npm:2"
- checksum: 10c0/5e1309ed97523eafaf1bfb690b56441d4cb3ea9e62acdd7d7b5be56288b14752ce8570ce6e8238f275c846e3eaba6af23e537d0b85499a158592d512f21a0774
+ checksum: 10c0/691895fb6a73a2feb2569658706e0d452861441de184dd1c9201e458a39fb80fc80080dd40d3d370400a52663f87de7a6d5a263c94245492f7265dd760441a95
languageName: node
linkType: hard
@@ -7309,6 +7527,13 @@ __metadata:
languageName: node
linkType: hard
+"mitt@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "mitt@npm:3.0.1"
+ checksum: 10c0/3ab4fdecf3be8c5255536faa07064d05caa3dd332bd318ff02e04621f7b3069ca1de9106cfe8e7ced675abfc2bec2ce4c4ef321c4a1bb1fb29df8ae090741913
+ languageName: node
+ linkType: hard
+
"mkdirp@npm:^1.0.3":
version: 1.0.4
resolution: "mkdirp@npm:1.0.4"
@@ -7550,6 +7775,27 @@ __metadata:
languageName: node
linkType: hard
+"nuqs@npm:2.2.3":
+ version: 2.2.3
+ resolution: "nuqs@npm:2.2.3"
+ dependencies:
+ mitt: "npm:^3.0.1"
+ peerDependencies:
+ "@remix-run/react": ">=2"
+ next: ">=14.2.0"
+ react: ">=18.2.0 || ^19.0.0-0"
+ react-router-dom: ">=6"
+ peerDependenciesMeta:
+ "@remix-run/react":
+ optional: true
+ next:
+ optional: true
+ react-router-dom:
+ optional: true
+ checksum: 10c0/c240d5fb48d01832d747411da6137b5a42b9aceb2ac92042d6863133ca93d50726ebcafaaf993b995ea7bf4068329fe332a65ba61baa536c68cba05a6fc5f364
+ languageName: node
+ linkType: hard
+
"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1":
version: 4.1.1
resolution: "object-assign@npm:4.1.1"
@@ -8032,12 +8278,12 @@ __metadata:
languageName: node
linkType: hard
-"prettier@npm:3.3.3":
- version: 3.3.3
- resolution: "prettier@npm:3.3.3"
+"prettier@npm:3.4.1":
+ version: 3.4.1
+ resolution: "prettier@npm:3.4.1"
bin:
prettier: bin/prettier.cjs
- checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
+ checksum: 10c0/2d6cc3101ad9de72b49c59339480b0983e6ff6742143da0c43f476bf3b5ef88ede42ebd9956d7a0a8fa59f7a5990e8ef03c9ad4c37f7e4c9e5db43ee0853156c
languageName: node
linkType: hard
@@ -8205,36 +8451,33 @@ __metadata:
languageName: node
linkType: hard
-"react-hook-form@npm:7.53.1":
- version: 7.53.1
- resolution: "react-hook-form@npm:7.53.1"
+"react-hook-form@npm:7.53.2":
+ version: 7.53.2
+ resolution: "react-hook-form@npm:7.53.2"
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
- checksum: 10c0/dd2466359a633f873755b366d367d51ab17100566b687fb3b098f704232bc6ab1c79d29f879151e492880ca5eeac35e9425fbe5a309e2a55f7a4b5baf7826e8d
+ checksum: 10c0/18336d8e8798a70dcd0af703a0becca2d5dbf82a7b7a3ca334ae0e1f26410490bc3ef2ea51adcf790bb1e7006ed7a763fd00d664e398f71225b23529a7ccf0bf
languageName: node
linkType: hard
-"react-intl@npm:6.8.7":
- version: 6.8.7
- resolution: "react-intl@npm:6.8.7"
+"react-intl@npm:7.0.1":
+ version: 7.0.1
+ resolution: "react-intl@npm:7.0.1"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.2.3"
- "@formatjs/icu-messageformat-parser": "npm:2.9.3"
- "@formatjs/intl": "npm:2.10.14"
- "@formatjs/intl-displaynames": "npm:6.8.4"
- "@formatjs/intl-listformat": "npm:7.7.4"
+ "@formatjs/icu-messageformat-parser": "npm:2.9.4"
+ "@formatjs/intl": "npm:3.0.1"
"@types/hoist-non-react-statics": "npm:3"
"@types/react": "npm:16 || 17 || 18"
hoist-non-react-statics: "npm:3"
- intl-messageformat: "npm:10.7.6"
+ intl-messageformat: "npm:10.7.7"
tslib: "npm:2"
peerDependencies:
react: ^16.6.0 || 17 || 18
- typescript: ^4.7 || 5
+ typescript: 5
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10c0/16cf57177e792bf27584c012389273d80642efecd0dbc2071906c824b58f99955c00970ab3e9bda25467fdc29b973eeeaf516887977d98ec72ae2cd1fc78d956
+ checksum: 10c0/b2a6e7d566c75100fd0806ba3fb92c15c87f2e9d6ef662b7ed4052f088f1d4002fb942a6079d72b1df582b48567df5efa7444cbd2173c7c52a11da4f1588d2b5
languageName: node
linkType: hard
@@ -9170,13 +9413,6 @@ __metadata:
languageName: node
linkType: hard
-"text-table@npm:^0.2.0":
- version: 0.2.0
- resolution: "text-table@npm:0.2.0"
- checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c
- languageName: node
- linkType: hard
-
"through2@npm:~0.4.1":
version: 0.4.2
resolution: "through2@npm:0.4.2"
@@ -9277,58 +9513,58 @@ __metadata:
languageName: node
linkType: hard
-"turbo-darwin-64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-darwin-64@npm:2.2.3"
+"turbo-darwin-64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-darwin-64@npm:2.3.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"turbo-darwin-arm64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-darwin-arm64@npm:2.2.3"
+"turbo-darwin-arm64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-darwin-arm64@npm:2.3.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"turbo-linux-64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-linux-64@npm:2.2.3"
+"turbo-linux-64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-linux-64@npm:2.3.3"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
-"turbo-linux-arm64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-linux-arm64@npm:2.2.3"
+"turbo-linux-arm64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-linux-arm64@npm:2.3.3"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
-"turbo-windows-64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-windows-64@npm:2.2.3"
+"turbo-windows-64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-windows-64@npm:2.3.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"turbo-windows-arm64@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo-windows-arm64@npm:2.2.3"
+"turbo-windows-arm64@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo-windows-arm64@npm:2.3.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"turbo@npm:2.2.3":
- version: 2.2.3
- resolution: "turbo@npm:2.2.3"
- dependencies:
- turbo-darwin-64: "npm:2.2.3"
- turbo-darwin-arm64: "npm:2.2.3"
- turbo-linux-64: "npm:2.2.3"
- turbo-linux-arm64: "npm:2.2.3"
- turbo-windows-64: "npm:2.2.3"
- turbo-windows-arm64: "npm:2.2.3"
+"turbo@npm:2.3.3":
+ version: 2.3.3
+ resolution: "turbo@npm:2.3.3"
+ dependencies:
+ turbo-darwin-64: "npm:2.3.3"
+ turbo-darwin-arm64: "npm:2.3.3"
+ turbo-linux-64: "npm:2.3.3"
+ turbo-linux-arm64: "npm:2.3.3"
+ turbo-windows-64: "npm:2.3.3"
+ turbo-windows-arm64: "npm:2.3.3"
dependenciesMeta:
turbo-darwin-64:
optional: true
@@ -9344,7 +9580,7 @@ __metadata:
optional: true
bin:
turbo: bin/turbo
- checksum: 10c0/5896cc1eb4b333ad11016f3b8a4ef5f13d3848c3b0abf4027edc53a0d10f3349bda5a7b880cc57ab1312b8c6371602f0ab932bc1c3902c65c674aef9c341af15
+ checksum: 10c0/9aab52fb868a2b6246f41fe2343dec56c70252a9cb7729a3ea183458cfa728c1445d1b98882dd43542f0ffd46524e8ba7776fe0925e31a86904b113222778fa5
languageName: node
linkType: hard
@@ -9416,21 +9652,33 @@ __metadata:
languageName: node
linkType: hard
-"typescript-eslint@npm:8.13.0":
- version: 8.13.0
- resolution: "typescript-eslint@npm:8.13.0"
+"typescript-eslint@npm:8.16.0":
+ version: 8.16.0
+ resolution: "typescript-eslint@npm:8.16.0"
dependencies:
- "@typescript-eslint/eslint-plugin": "npm:8.13.0"
- "@typescript-eslint/parser": "npm:8.13.0"
- "@typescript-eslint/utils": "npm:8.13.0"
+ "@typescript-eslint/eslint-plugin": "npm:8.16.0"
+ "@typescript-eslint/parser": "npm:8.16.0"
+ "@typescript-eslint/utils": "npm:8.16.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10c0/a84958e7602360c4cb2e6227fd9aae19dd18cdf1a2cfd9ece2a81d54098f80454b5707e861e98547d0b2e5dae552b136aa6733b74f0dd743ca7bfe178083c441
+ checksum: 10c0/3da9401d6c2416b9d95c96a41a9423a5379d233a120cd3304e2c03f191d350ce91cf0c7e60017f7b10c93b4cc1190592702735735b771c1ce1bf68f71a9f1647
+ languageName: node
+ linkType: hard
+
+"typescript@npm:5.7.2":
+ version: 5.7.2
+ resolution: "typescript@npm:5.7.2"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/a873118b5201b2ef332127ef5c63fb9d9c155e6fdbe211cbd9d8e65877283797cca76546bad742eea36ed7efbe3424a30376818f79c7318512064e8625d61622
languageName: node
linkType: hard
-"typescript@npm:5.6.3, typescript@npm:^5.4.4, typescript@npm:^5.4.5, typescript@npm:^5.5.4":
+"typescript@npm:^5.4.4, typescript@npm:^5.4.5, typescript@npm:^5.5.4":
version: 5.6.3
resolution: "typescript@npm:5.6.3"
bin:
@@ -9440,7 +9688,17 @@ __metadata:
languageName: node
linkType: hard
-"typescript@patch:typescript@npm%3A5.6.3#optional!builtin, typescript@patch:typescript@npm%3A^5.4.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.5#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin":
+"typescript@patch:typescript@npm%3A5.7.2#optional!builtin":
+ version: 5.7.2
+ resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=5786d5"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/f3b8082c9d1d1629a215245c9087df56cb784f9fb6f27b5d55577a20e68afe2a889c040aacff6d27e35be165ecf9dca66e694c42eb9a50b3b2c451b36b5675cb
+ languageName: node
+ linkType: hard
+
+"typescript@patch:typescript@npm%3A^5.4.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.5#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin":
version: 5.6.3
resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40"
bin:
@@ -9462,13 +9720,20 @@ __metadata:
languageName: node
linkType: hard
-"undici-types@npm:~6.19.2, undici-types@npm:~6.19.8":
+"undici-types@npm:~6.19.8":
version: 6.19.8
resolution: "undici-types@npm:6.19.8"
checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344
languageName: node
linkType: hard
+"undici-types@npm:~6.20.0":
+ version: 6.20.0
+ resolution: "undici-types@npm:6.20.0"
+ checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf
+ languageName: node
+ linkType: hard
+
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
@@ -9529,12 +9794,12 @@ __metadata:
languageName: node
linkType: hard
-"uuid@npm:^10.0.0":
- version: 10.0.0
- resolution: "uuid@npm:10.0.0"
+"uuid@npm:^11.0.2":
+ version: 11.0.3
+ resolution: "uuid@npm:11.0.3"
bin:
- uuid: dist/bin/uuid
- checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe
+ uuid: dist/esm/bin/uuid
+ checksum: 10c0/cee762fc76d949a2ff9205770334699e0043d52bb766472593a25f150077c9deed821230251ea3d6ab3943a5ea137d2826678797f1d5f6754c7ce5ce27e9f7a6
languageName: node
linkType: hard
@@ -9581,10 +9846,30 @@ __metadata:
"@tooling/prettier": "workspace:*"
"@tooling/typescript": "workspace:*"
"@workspace/common": "workspace:*"
+ "@workspace/sentry": "workspace:*"
+ browserslist-config-custom: "workspace:*"
+ next: "npm:15.0.3"
+ react: "npm:18.3.1"
+ react-dom: "npm:18.3.1"
+ typescript: "npm:5.7.2"
+ languageName: unknown
+ linkType: soft
+
+"webauthn-upgrade-example@workspace:examples/webauthn-upgrade":
+ version: 0.0.0-use.local
+ resolution: "webauthn-upgrade-example@workspace:examples/webauthn-upgrade"
+ dependencies:
+ "@tooling/eslint": "workspace:*"
+ "@tooling/madge": "workspace:*"
+ "@tooling/prettier": "workspace:*"
+ "@tooling/typescript": "workspace:*"
+ "@workspace/common": "workspace:*"
+ "@workspace/sentry": "workspace:*"
browserslist-config-custom: "workspace:*"
next: "npm:15.0.3"
react: "npm:18.3.1"
react-dom: "npm:18.3.1"
+ typescript: "npm:5.7.2"
languageName: unknown
linkType: soft
@@ -9725,8 +10010,8 @@ __metadata:
"@tooling/prettier": "workspace:*"
"@tooling/typescript": "workspace:*"
dotenv: "npm:16.4.5"
- husky: "npm:9.1.6"
- turbo: "npm:2.2.3"
+ husky: "npm:9.1.7"
+ turbo: "npm:2.3.3"
languageName: unknown
linkType: soft