├── .devcontainer
└── devcontainer.json
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── build-test.yml
│ └── release.yml
├── .gitignore
├── .release-please-manifest.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples
├── base
│ ├── .gitignore
│ ├── .npmignore
│ ├── index.html
│ ├── index.tsx
│ ├── package.json
│ ├── src
│ │ ├── App.tsx
│ │ ├── Content.tsx
│ │ ├── auth.ts
│ │ └── main.tsx
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── refresh-token
│ ├── .gitignore
│ ├── .npmignore
│ ├── index.html
│ ├── index.tsx
│ ├── package.json
│ ├── src
│ │ ├── App.tsx
│ │ ├── AppContent.tsx
│ │ ├── api
│ │ │ ├── index.ts
│ │ │ └── interceptors.ts
│ │ ├── auth.ts
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
└── reqres
│ ├── .gitignore
│ ├── .npmignore
│ ├── index.html
│ ├── index.tsx
│ ├── package.json
│ ├── src
│ ├── App.tsx
│ ├── Content.tsx
│ ├── auth.ts
│ └── main.tsx
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── lib
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── src
│ ├── auth.tsx
│ ├── index.ts
│ └── utils.ts
├── test
│ ├── authClient.spec.ts
│ ├── context.spec.ts
│ ├── provider.spec.tsx
│ └── test-utils.tsx
├── tsconfig.json
└── vitest.config.ts
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── release-please-config.json
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
3 | {
4 | "name": "React Auth",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
7 |
8 | // Features to add to the dev container. More info: https://containers.dev/features.
9 | "features": {
10 | "ghcr.io/devcontainers/features/common-utils:2": {
11 | "configureZshAsDefaultShell": true,
12 | "username": "node"
13 | },
14 | "ghcr.io/devcontainers-extra/features/zsh-plugins:0": {
15 | "plugins": "git npm",
16 | "omzPlugins": "https://github.com/zsh-users/zsh-autosuggestions",
17 | "username": "node"
18 | },
19 | "ghcr.io/devcontainers/features/git:1": {},
20 | "ghcr.io/devcontainers/features/node:1": {},
21 | "ghcr.io/joshuanianji/devcontainer-features/mount-pnpm-store:1": {}
22 | },
23 |
24 | // Volumes to mount
25 | "mounts": [
26 | "source=${devcontainerId}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
27 | ],
28 |
29 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
30 | // "forwardPorts": [],
31 |
32 | // Use 'postCreateCommand' to run commands after the container is created.
33 | "postCreateCommand": "sudo chown node node_modules; pnpm install",
34 |
35 | // Configure tool-specific properties.
36 | "customizations": {
37 | "vscode": {
38 | "extensions": [
39 | "dbaeumer.vscode-eslint",
40 | "esbenp.prettier-vscode",
41 | "editorconfig.editorconfig",
42 | "GitHub.vscode-github-actions",
43 | "yzhang.markdown-all-in-one",
44 | ]
45 | }
46 | },
47 |
48 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
49 | // "remoteUser": "root"
50 | }
51 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Each line is a file pattern followed by one or more owners.
2 |
3 | # These owners will be the default owners for everything in
4 | # the repo. Unless a later match takes precedence,
5 | # @panz3r will be requested for review when someone
6 | # opens a pull request.
7 | * @panz3r
8 |
9 | # Owners of the examples
10 | /examples/* @panz3r
11 |
12 | # Owners of the main library
13 | /lib @panz3r
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: '[Bug] '
4 | labels: ['bug', 'triage']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: Thanks for taking the time to fill out this bug report! We'll try to help you as soon as possible so please provide as much information as you can.
9 |
10 | - type: textarea
11 | id: what-happened
12 | attributes:
13 | label: What happened?
14 | description: Tell us what you see
15 | validations:
16 | required: true
17 |
18 | - type: textarea
19 | id: what-expected
20 | attributes:
21 | label: What did you expect to happen?
22 | description: Tell us what you wanted to see
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: reproduction-steps
28 | attributes:
29 | label: Steps to reproduce the issue
30 | description: Tell us how we can reproduce this issue. Please be specific and provide sample code if you can
31 | validations:
32 | required: true
33 |
34 | - type: input
35 | id: version
36 | attributes:
37 | label: Version
38 | description: What version of the library are you using?
39 | placeholder: ex. v1.0.0
40 | validations:
41 | required: true
42 |
43 | - type: dropdown
44 | id: environment
45 | attributes:
46 | label: What environment are you seeing the problem on?
47 | multiple: true
48 | options:
49 | - ReactJS (specify Browser below)
50 | - React Native (Android)
51 | - React Native (iOS)
52 | validations:
53 | required: true
54 |
55 | - type: dropdown
56 | id: browsers
57 | attributes:
58 | label: What browsers are you seeing the problem on?
59 | multiple: true
60 | options:
61 | - Chrome
62 | - Firefox
63 | - Microsoft Edge
64 | - Safari
65 | - Others
66 |
67 | - type: textarea
68 | id: logs
69 | attributes:
70 | label: Relevant log output
71 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
72 | render: shell
73 |
74 | - type: checkboxes
75 | id: terms
76 | attributes:
77 | label: Code of Conduct
78 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/forwardsoftware/react-auth/blob/main/CODE_OF_CONDUCT.md)
79 | options:
80 | - label: I agree to follow this project's Code of Conduct
81 | required: true
82 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: General Questions and Inquiries
4 | url: https://github.com/forwardsoftware/react-auth/discussions
5 | about: Please ask general integration/design/architecture questions using GitHub Discussion.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea for this project
3 | title: '[Feature] '
4 | labels: ['enhancement']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: Thanks for taking the time to fill out this feature request! We'll try to help you as soon as possible so please provide as much information as you can.
9 |
10 | - type: textarea
11 | id: related-issue
12 | attributes:
13 | label: Is your feature request related to a problem?
14 | description: |
15 | A clear and concise description of what the problem is.
16 | E.g. I'm always frustrated when [...]
17 |
18 | - type: textarea
19 | id: feature-requested
20 | attributes:
21 | label: Describe the solution you'd like
22 | description: |
23 | A clear and concise description of what you want to happen.
24 | validations:
25 | required: true
26 |
27 | - type: textarea
28 | id: alternatives
29 | attributes:
30 | label: Describe alternatives you've considered
31 | description: |
32 | A clear and concise description of any alternative solutions or features you've considered.
33 |
34 | - type: textarea
35 | id: additional-context
36 | attributes:
37 | label: Additional context
38 | description: |
39 | Add any other context or screenshots about the feature request here.
40 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for more information:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 | # https://containers.dev/guide/dependabot
6 |
7 | version: 2
8 | updates:
9 | - package-ecosystem: "devcontainers"
10 | directory: "/"
11 | schedule:
12 | interval: monthly
13 |
14 | - package-ecosystem: "github-actions"
15 | directory: "/"
16 | schedule:
17 | interval: monthly
18 |
19 | - package-ecosystem: "npm"
20 | directories:
21 | - "/"
22 | - "/lib"
23 | - "/examples/*"
24 | schedule:
25 | interval: weekly
26 | day: tuesday
27 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Related Issue(s)
2 |
3 |
9 |
10 | ## Motivation
11 |
12 |
13 |
14 | ## Description of Changes
15 |
16 |
17 |
18 | *
19 | *
20 | *
21 |
22 | ## How to Test
23 |
24 | 1. **CI Checks:** Verify that all automated tests (`Vitest`) and build steps pass successfully on this PR.
25 | 2. **Local Verification (Optional):**
26 | * Run `pnpm install` (or equivalent).
27 | * Run the development server (`pnpm dev` or equivalent) for the library or examples to ensure Vite starts correctly.
28 | * Run a build (`pnpm build` or equivalent) to ensure it completes successfully.
29 |
30 | ## Checklist
31 |
32 |
33 |
34 | * [ ] My code follows the project's style guidelines
35 | * [ ] I have added or updated tests to cover the changes
36 | * [ ] I have updated relevant documentation
37 | * [ ] All tests are passing locally
38 | * [ ] CI checks are passing
39 | * [ ] I have reviewed my own code and lock file changes
40 | * [ ] I have checked for any potential security implications
41 | * [ ] I have verified the changes work as expected
42 |
43 | ## Notes for Reviewers
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test
2 |
3 | on:
4 | push:
5 | branches-ignore: [main]
6 | workflow_dispatch:
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | test:
13 | name: Test
14 |
15 | strategy:
16 | matrix:
17 | node_version: [lts/-1, lts/*, latest]
18 | fail-fast: false
19 |
20 | runs-on: "ubuntu-latest"
21 |
22 | steps:
23 | - name: Checkout repository
24 | uses: actions/checkout@v4
25 |
26 | - name: Setup pnpm
27 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
28 |
29 | - name: Setup NodeJS
30 | uses: actions/setup-node@v4
31 | with:
32 | node-version: ${{ matrix.node_version }}
33 | cache: pnpm
34 |
35 | - name: Install dependencies
36 | run: pnpm i --frozen-lockfile
37 |
38 | - name: Test package
39 | run: pnpm --filter "@forward-software/react-auth" test
40 |
41 | build:
42 | name: Build
43 |
44 | runs-on: "ubuntu-latest"
45 |
46 | steps:
47 | - name: Checkout repository
48 | uses: actions/checkout@v4
49 |
50 | - name: Setup pnpm
51 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
52 |
53 | - name: Setup NodeJS
54 | uses: actions/setup-node@v4
55 | with:
56 | node-version: "lts/*"
57 | cache: pnpm
58 |
59 | - name: Install dependencies
60 | run: pnpm i --frozen-lockfile
61 |
62 | - name: Build package
63 | run: pnpm --filter "@forward-software/react-auth" build
64 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release-please:
10 | name: Run Release Please
11 |
12 | runs-on: ubuntu-latest
13 |
14 | permissions:
15 | contents: write
16 | issues: write
17 | pull-requests: write
18 |
19 | outputs:
20 | releases_created: ${{ steps.release.outputs.releases_created }}
21 | paths_released: ${{ steps.release.outputs.paths_released }}
22 |
23 | steps:
24 | - name: Run release-please command
25 | id: release
26 | uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445
27 |
28 | build-and-publish:
29 | name: Build & Publish
30 |
31 | runs-on: ubuntu-latest
32 |
33 | needs: [release-please]
34 |
35 | if: needs.release-please.outputs.paths_released != '[]'
36 |
37 | strategy:
38 | matrix:
39 | path: ${{ fromJSON(needs.release-please.outputs.paths_released) }}
40 |
41 | permissions:
42 | contents: read
43 | id-token: write
44 |
45 | steps:
46 | - name: Checkout repository
47 | uses: actions/checkout@v4
48 |
49 | - name: Setup pnpm
50 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
51 |
52 | - name: Setup NodeJS
53 | uses: actions/setup-node@v4
54 | with:
55 | node-version: latest
56 | cache: pnpm
57 | registry-url: "https://registry.npmjs.org"
58 |
59 | - name: Install dependencies
60 | run: pnpm i --frozen-lockfile
61 |
62 | - name: Build package
63 | working-directory: ${{ matrix.path }}
64 | run: pnpm build
65 |
66 | - name: Publish package at ${{ matrix.path }}
67 | run: npm publish --provenance --access public
68 | working-directory: ${{ matrix.path }}
69 | env:
70 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,windows,intellij,visualstudiocode,node
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,intellij,visualstudiocode,node
3 |
4 | ### Intellij ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/**/usage.statistics.xml
12 | .idea/**/dictionaries
13 | .idea/**/shelf
14 |
15 | # AWS User-specific
16 | .idea/**/aws.xml
17 |
18 | # Generated files
19 | .idea/**/contentModel.xml
20 |
21 | # Sensitive or high-churn files
22 | .idea/**/dataSources/
23 | .idea/**/dataSources.ids
24 | .idea/**/dataSources.local.xml
25 | .idea/**/sqlDataSources.xml
26 | .idea/**/dynamic.xml
27 | .idea/**/uiDesigner.xml
28 | .idea/**/dbnavigator.xml
29 |
30 | # Gradle
31 | .idea/**/gradle.xml
32 | .idea/**/libraries
33 |
34 | # Gradle and Maven with auto-import
35 | # When using Gradle or Maven with auto-import, you should exclude module files,
36 | # since they will be recreated, and may cause churn. Uncomment if using
37 | # auto-import.
38 | # .idea/artifacts
39 | # .idea/compiler.xml
40 | # .idea/jarRepositories.xml
41 | # .idea/modules.xml
42 | # .idea/*.iml
43 | # .idea/modules
44 | # *.iml
45 | # *.ipr
46 |
47 | # CMake
48 | cmake-build-*/
49 |
50 | # Mongo Explorer plugin
51 | .idea/**/mongoSettings.xml
52 |
53 | # File-based project format
54 | *.iws
55 |
56 | # IntelliJ
57 | out/
58 |
59 | # mpeltonen/sbt-idea plugin
60 | .idea_modules/
61 |
62 | # JIRA plugin
63 | atlassian-ide-plugin.xml
64 |
65 | # Cursive Clojure plugin
66 | .idea/replstate.xml
67 |
68 | # SonarLint plugin
69 | .idea/sonarlint/
70 |
71 | # Crashlytics plugin (for Android Studio and IntelliJ)
72 | com_crashlytics_export_strings.xml
73 | crashlytics.properties
74 | crashlytics-build.properties
75 | fabric.properties
76 |
77 | # Editor-based Rest Client
78 | .idea/httpRequests
79 |
80 | # Android studio 3.1+ serialized cache file
81 | .idea/caches/build_file_checksums.ser
82 |
83 | ### Intellij Patch ###
84 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
85 |
86 | # *.iml
87 | # modules.xml
88 | # .idea/misc.xml
89 | # *.ipr
90 |
91 | # Sonarlint plugin
92 | # https://plugins.jetbrains.com/plugin/7973-sonarlint
93 | .idea/**/sonarlint/
94 |
95 | # SonarQube Plugin
96 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
97 | .idea/**/sonarIssues.xml
98 |
99 | # Markdown Navigator plugin
100 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
101 | .idea/**/markdown-navigator.xml
102 | .idea/**/markdown-navigator-enh.xml
103 | .idea/**/markdown-navigator/
104 |
105 | # Cache file creation bug
106 | # See https://youtrack.jetbrains.com/issue/JBR-2257
107 | .idea/$CACHE_FILE$
108 |
109 | # CodeStream plugin
110 | # https://plugins.jetbrains.com/plugin/12206-codestream
111 | .idea/codestream.xml
112 |
113 | # Azure Toolkit for IntelliJ plugin
114 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
115 | .idea/**/azureSettings.xml
116 |
117 | ### macOS ###
118 | # General
119 | .DS_Store
120 | .AppleDouble
121 | .LSOverride
122 |
123 | # Icon must end with two \r
124 | Icon
125 |
126 |
127 | # Thumbnails
128 | ._*
129 |
130 | # Files that might appear in the root of a volume
131 | .DocumentRevisions-V100
132 | .fseventsd
133 | .Spotlight-V100
134 | .TemporaryItems
135 | .Trashes
136 | .VolumeIcon.icns
137 | .com.apple.timemachine.donotpresent
138 |
139 | # Directories potentially created on remote AFP share
140 | .AppleDB
141 | .AppleDesktop
142 | Network Trash Folder
143 | Temporary Items
144 | .apdisk
145 |
146 | ### macOS Patch ###
147 | # iCloud generated files
148 | *.icloud
149 |
150 | ### Node ###
151 | # Logs
152 | logs
153 | *.log
154 | npm-debug.log*
155 | yarn-debug.log*
156 | yarn-error.log*
157 | lerna-debug.log*
158 | .pnpm-debug.log*
159 |
160 | # Diagnostic reports (https://nodejs.org/api/report.html)
161 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
162 |
163 | # Runtime data
164 | pids
165 | *.pid
166 | *.seed
167 | *.pid.lock
168 |
169 | # Directory for instrumented libs generated by jscoverage/JSCover
170 | lib-cov
171 |
172 | # Coverage directory used by tools like istanbul
173 | coverage
174 | *.lcov
175 |
176 | # nyc test coverage
177 | .nyc_output
178 |
179 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
180 | .grunt
181 |
182 | # Bower dependency directory (https://bower.io/)
183 | bower_components
184 |
185 | # node-waf configuration
186 | .lock-wscript
187 |
188 | # Compiled binary addons (https://nodejs.org/api/addons.html)
189 | build/Release
190 |
191 | # Dependency directories
192 | node_modules/
193 | jspm_packages/
194 |
195 | # Snowpack dependency directory (https://snowpack.dev/)
196 | web_modules/
197 |
198 | # TypeScript cache
199 | *.tsbuildinfo
200 |
201 | # Optional npm cache directory
202 | .npm
203 |
204 | # Optional eslint cache
205 | .eslintcache
206 |
207 | # Optional stylelint cache
208 | .stylelintcache
209 |
210 | # Microbundle cache
211 | .rpt2_cache/
212 | .rts2_cache_cjs/
213 | .rts2_cache_es/
214 | .rts2_cache_umd/
215 |
216 | # Optional REPL history
217 | .node_repl_history
218 |
219 | # Output of 'npm pack'
220 | *.tgz
221 |
222 | # Yarn Integrity file
223 | .yarn-integrity
224 |
225 | # dotenv environment variable files
226 | .env
227 | .env.development.local
228 | .env.test.local
229 | .env.production.local
230 | .env.local
231 |
232 | # parcel-bundler cache (https://parceljs.org/)
233 | .cache
234 | .parcel-cache
235 |
236 | # Next.js build output
237 | .next
238 | out
239 |
240 | # Nuxt.js build / generate output
241 | .nuxt
242 | dist
243 |
244 | # Gatsby files
245 | .cache/
246 | # Comment in the public line in if your project uses Gatsby and not Next.js
247 | # https://nextjs.org/blog/next-9-1#public-directory-support
248 | # public
249 |
250 | # vuepress build output
251 | .vuepress/dist
252 |
253 | # vuepress v2.x temp and cache directory
254 | .temp
255 |
256 | # Docusaurus cache and generated files
257 | .docusaurus
258 |
259 | # Serverless directories
260 | .serverless/
261 |
262 | # FuseBox cache
263 | .fusebox/
264 |
265 | # DynamoDB Local files
266 | .dynamodb/
267 |
268 | # TernJS port file
269 | .tern-port
270 |
271 | # Stores VSCode versions used for testing VSCode extensions
272 | .vscode-test
273 |
274 | # yarn v2
275 | .yarn/cache
276 | .yarn/unplugged
277 | .yarn/build-state.yml
278 | .yarn/install-state.gz
279 | .pnp.*
280 |
281 | ### Node Patch ###
282 | # Serverless Webpack directories
283 | .webpack/
284 |
285 | # Optional stylelint cache
286 |
287 | # SvelteKit build / generate output
288 | .svelte-kit
289 |
290 | ### VisualStudioCode ###
291 | .vscode/*
292 | !.vscode/settings.json
293 | !.vscode/tasks.json
294 | !.vscode/launch.json
295 | !.vscode/extensions.json
296 | !.vscode/*.code-snippets
297 |
298 | # Local History for Visual Studio Code
299 | .history/
300 |
301 | # Built Visual Studio Code Extensions
302 | *.vsix
303 |
304 | ### VisualStudioCode Patch ###
305 | # Ignore all local history of files
306 | .history
307 | .ionide
308 |
309 | ### Windows ###
310 | # Windows thumbnail cache files
311 | Thumbs.db
312 | Thumbs.db:encryptable
313 | ehthumbs.db
314 | ehthumbs_vista.db
315 |
316 | # Dump file
317 | *.stackdump
318 |
319 | # Folder config file
320 | [Dd]esktop.ini
321 |
322 | # Recycle Bin used on file shares
323 | $RECYCLE.BIN/
324 |
325 | # Windows Installer files
326 | *.cab
327 | *.msi
328 | *.msix
329 | *.msm
330 | *.msp
331 |
332 | # Windows shortcuts
333 | *.lnk
334 |
335 | # End of https://www.toptal.com/developers/gitignore/api/macos,windows,intellij,visualstudiocode,node
336 |
337 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {"lib":"2.0.0"}
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | community@forwardsoftware.solutions.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Welcome to React Auth contributing guide
2 |
3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
4 |
5 | - Reporting a bug
6 | - Discussing the current state of the code
7 | - Submitting a fix
8 | - Proposing new features
9 |
10 | Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
11 |
12 | In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
13 |
14 | > Use the table of contents icon on the top left corner of this document to get to a specific section of this guide quickly.
15 |
16 | ## New contributor guide
17 |
18 | To get an overview of the project, read the [README](README.md).
19 |
20 | Here are some resources to help you get started with open source contributions:
21 |
22 | - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
23 | - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
24 | - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
25 | - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
26 |
27 | ## Getting Started
28 |
29 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
30 |
31 | ### Report bugs using GitHub's [issues](https://github.com/forwardsoftware/react-auth/issues)
32 |
33 | Report a bug by [opening a new issue](https://github.com/forwardsoftware/react-auth/issues/new/choose)
34 |
35 | #### Write bug reports with detail, background, and sample code
36 |
37 | **Great Bug Reports** should contain:
38 |
39 | - A quick summary and/or background
40 | - Steps to reproduce
41 | - Be specific!
42 | - Give sample code if you can, the sample code should allow _anyone_ with a base setup to reproduce your issue
43 | - What you expected would happen
44 | - What actually happens
45 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
46 |
47 | ### We use [GitHub Flow](https://guides.github.com/introduction/flow/index.html), so all code changes happen through Pull Requests
48 |
49 | We actively welcome your pull requests:
50 |
51 | 1. Fork the repo and create your branch from `main`.
52 | 2. If you've added code that should be tested, add tests.
53 | 3. If you've changed APIs, update the documentation.
54 | 4. Ensure the test suite passes.
55 | 5. Make sure your code lints.
56 | 6. Issue your pull request!
57 |
58 | #### Use a consistent Coding Style
59 |
60 | This project uses [ESLint](https://eslint.org/) and [Prettier](https://prettier.io/) to maintain a unified coding style.
61 | Before committing your changes remember to run `yarn lint` and check possible warnings and errors.
62 |
63 | #### Any contributions you make will be under the MIT Software License
64 |
65 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project.
66 | Feel free to contact the maintainers if that's a concern.
67 |
68 | ## License
69 |
70 | By contributing, you agree that your contributions will be licensed under its [MIT License](LICENSE).
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 ForWarD Software (https://forwardsoftware.solutions/)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Auth
2 |
3 | > Simplify your Auth flow when working with React apps
4 |
5 | [](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [](https://github.com/forwardsoftware/react-auth/issues)
6 |
7 |
8 | ## Examples
9 |
10 | The `examples` folder contains some examples of how you can integrate this library in your React app.
11 |
12 |
13 | ## Credits
14 |
15 | This library has been inspired by [`react-keycloak`](https://github.com/react-keycloak/react-keycloak) and similar libraries.
16 |
17 |
18 | ## License
19 |
20 | MIT
21 |
22 | ---
23 |
24 | Made with ✨ & ❤️ by [ForWarD Software](https://github.com/forwardsoftware) and [contributors](https://github.com/forwardsoftware/react-auth/graphs/contributors)
25 |
26 | If you found this project to be helpful, please consider contacting us to develop your React and React Native projects.
27 |
--------------------------------------------------------------------------------
/examples/base/.gitignore:
--------------------------------------------------------------------------------
1 | yarn.lock
2 |
--------------------------------------------------------------------------------
/examples/base/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Base Example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/base/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import { createRoot } from 'react-dom/client';
4 |
5 | import { App } from './src/App';
6 |
7 | const rootElement = document.getElementById('root')
8 | if (rootElement) {
9 | createRoot(rootElement).render( );
10 | }
--------------------------------------------------------------------------------
/examples/base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "base-example",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview --host"
10 | },
11 | "dependencies": {
12 | "@forward-software/react-auth": "workspace:^",
13 | "react": "catalog:",
14 | "react-dom": "catalog:"
15 | },
16 | "devDependencies": {
17 | "@types/react": "catalog:",
18 | "@types/react-dom": "catalog:",
19 | "@vitejs/plugin-react": "catalog:",
20 | "typescript": "catalog:",
21 | "vite": "catalog:"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/base/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { AuthProvider } from './auth';
4 | import { Content } from './Content';
5 |
6 | export const App: React.FC = () => (
7 |
8 |
9 |
10 | );
--------------------------------------------------------------------------------
/examples/base/src/Content.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 |
3 | import { useAuthClient } from './auth';
4 |
5 | export const Content: React.FC = () => {
6 | const authClient = useAuthClient();
7 |
8 | const [doLogin, isLoginLoading] = useAsyncCallback(() => authClient.login(), [
9 | authClient,
10 | ]);
11 | const [doRefresh, isRefreshLoading] = useAsyncCallback(
12 | () => authClient.refresh(),
13 | [authClient]
14 | );
15 | const [doLogout, isLogoutLoading] = useAsyncCallback(
16 | () => authClient.logout(),
17 | [authClient]
18 | );
19 |
20 | return (
21 |
22 |
Auth client ready? {String(authClient.isInitialized)}
23 |
Auth client authenticated? {String(authClient.isAuthenticated)}
24 |
25 |
26 |
30 | Login
31 |
32 |
33 |
37 | Refresh
38 |
39 |
40 |
44 | Logout
45 |
46 |
47 |
48 | {isLoginLoading ?
Login in progress..
: null}
49 | {isRefreshLoading ?
Refresh in progress..
: null}
50 |
51 |
Tokens:
52 |
{JSON.stringify(authClient.tokens, null, 2)}
53 |
54 | );
55 | };
56 |
57 | function useAsyncCallback Promise>(
58 | callback: T,
59 | deps: React.DependencyList
60 | ): [T, boolean] {
61 | const [isLoading, setLoading] = useState(false);
62 | const cb = useCallback(async (...argsx: never[]) => {
63 | setLoading(true);
64 | const res = await callback(...argsx);
65 | setLoading(false);
66 | return res;
67 | }, deps) as T;
68 |
69 | return [cb, isLoading];
70 | }
71 |
--------------------------------------------------------------------------------
/examples/base/src/auth.ts:
--------------------------------------------------------------------------------
1 | import { createAuth } from "@forward-software/react-auth";
2 | import type { AuthClient } from "@forward-software/react-auth";
3 |
4 | type AuthCredentials = {};
5 |
6 | type AuthTokens = {
7 | authToken: string;
8 |
9 | refreshToken: string;
10 | };
11 |
12 | class MyAuthClient implements AuthClient {
13 | onLogin(): Promise {
14 | return new Promise((resolve) => {
15 | setTimeout(
16 | () =>
17 | resolve({
18 | authToken: "auth.token",
19 | refreshToken: "refresh.token",
20 | }),
21 | 2000
22 | );
23 | });
24 | }
25 |
26 | onRefresh(): Promise {
27 | return new Promise((resolve) => {
28 | setTimeout(
29 | () =>
30 | resolve({
31 | authToken: "new.auth.token",
32 | refreshToken: "new.refresh.token",
33 | }),
34 | 2000
35 | );
36 | });
37 | }
38 | }
39 |
40 | const myAuthClient = new MyAuthClient();
41 |
42 | export const { AuthProvider, authClient, useAuthClient } = createAuth(myAuthClient);
43 |
--------------------------------------------------------------------------------
/examples/base/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { App } from './App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 |
9 | )
--------------------------------------------------------------------------------
/examples/base/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "esModuleInterop": true,
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "include": ["src"],
22 | "references": [{ "path": "./tsconfig.node.json" }]
23 | }
--------------------------------------------------------------------------------
/examples/base/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
--------------------------------------------------------------------------------
/examples/base/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | server: {
7 | port: 3000,
8 | open: true
9 | }
10 | })
--------------------------------------------------------------------------------
/examples/refresh-token/.gitignore:
--------------------------------------------------------------------------------
1 | yarn.lock
2 |
--------------------------------------------------------------------------------
/examples/refresh-token/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/refresh-token/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Refresh Token Example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/refresh-token/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import { createRoot } from 'react-dom/client';
4 |
5 | import { App } from './src/App';
6 |
7 | const rootElement = document.getElementById('root')
8 | if (rootElement) {
9 | createRoot(rootElement).render( );
10 | }
--------------------------------------------------------------------------------
/examples/refresh-token/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "refresh-token-example",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview --host"
10 | },
11 | "dependencies": {
12 | "@forward-software/react-auth": "workspace:^",
13 | "axios": "1.9.0",
14 | "jwt-check-expiry": "^1.0.10",
15 | "react": "catalog:",
16 | "react-dom": "catalog:"
17 | },
18 | "devDependencies": {
19 | "@types/react": "catalog:",
20 | "@types/react-dom": "catalog:",
21 | "@vitejs/plugin-react": "catalog:",
22 | "typescript": "catalog:",
23 | "vite": "catalog:"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/App.tsx:
--------------------------------------------------------------------------------
1 | import AppContent from './AppContent';
2 | import { AuthProvider } from './auth';
3 |
4 | export const App = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/AppContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { DependencyList, FC, useCallback, useState } from 'react';
3 | import { useAuthClient } from './auth';
4 |
5 | function useUserCredentials() {
6 | const [username, setUsername] = useState('');
7 | const [password, setPassword] = useState('');
8 |
9 | const updateUsername = useCallback(
10 | (evt: React.ChangeEvent) => {
11 | setUsername(evt.target.value);
12 | },
13 | []
14 | );
15 | const updatePassword = useCallback(
16 | (evt: React.ChangeEvent) => {
17 | setPassword(evt.target.value);
18 | },
19 | []
20 | );
21 |
22 | return {
23 | username,
24 | password,
25 | updateUsername,
26 | updatePassword,
27 | };
28 | }
29 |
30 | function useAsyncCallback Promise>(
31 | callback: T,
32 | deps: DependencyList
33 | ): [T, boolean] {
34 | const [isLoading, setLoading] = useState(false);
35 |
36 | const cb = useCallback(async (...argsx: never[]) => {
37 | setLoading(true);
38 | const res = await callback(...argsx);
39 | setLoading(false);
40 | return res;
41 | }, deps) as T;
42 |
43 | return [cb, isLoading];
44 | }
45 |
46 | const AppContent: FC = () => {
47 | const client = useAuthClient();
48 | const userCredentials = useUserCredentials();
49 |
50 | const [onLogin, isLoginLoading] = useAsyncCallback(
51 | () =>
52 | client.login({
53 | username: userCredentials.username,
54 | password: userCredentials.password,
55 | }),
56 | [client, userCredentials]
57 | );
58 |
59 | const [onLogout, isLogoutLoading] = useAsyncCallback(() => client.logout(), [
60 | client,
61 | ]);
62 |
63 | const [onRefreshTokens, tokenRefreshLoading] = useAsyncCallback(
64 | () => client.refresh(),
65 | [client]
66 | );
67 |
68 | return (
69 |
70 |
Auth client ready? {String(client.isInitialized)}
71 |
Auth client authenticated? {String(client.isAuthenticated)}
72 |
73 |
74 |
79 |
84 |
85 |
86 |
87 |
91 | Login
92 |
93 |
94 |
98 | Logout
99 |
100 |
101 |
102 | {isLoginLoading ?
Login in progress..
: null}
103 |
104 |
Tokens:
105 |
{JSON.stringify(client.tokens ?? {}, null, 2)}
106 |
107 | {/*
108 | use client.refresh() where you implement your API calls logic (eg. redux-saga, ....)
109 | check code in src/api/interceptors.ts and use APIClient to make call to API
110 | */}
111 |
115 | Refresh tokens
116 |
117 |
118 | );
119 | };
120 |
121 | export default AppContent;
122 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | requestSuccessInterceptor,
4 | requestErrorInterceptor,
5 | responseSuccessInterceptor,
6 | responseErrorInterceptor,
7 | } from "./interceptors";
8 |
9 | // AXIOS
10 | // use this APIClient to make service calls
11 | export const APIClient = axios.create({
12 | baseURL: import.meta.env.VITE_BASE_URL,
13 | headers: {
14 | "Content-Type": "application/json",
15 | },
16 | });
17 |
18 | // Set request interceptor to add Auth token
19 | APIClient.interceptors.request.use(requestSuccessInterceptor, requestErrorInterceptor);
20 |
21 | APIClient.interceptors.response.use(responseSuccessInterceptor, responseErrorInterceptor);
22 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/api/interceptors.ts:
--------------------------------------------------------------------------------
1 | import type { AxiosResponse, InternalAxiosRequestConfig } from "axios";
2 |
3 | import { authClient } from "../auth";
4 |
5 | // REQUEST
6 |
7 | export async function requestSuccessInterceptor(axiosRequestCfg: InternalAxiosRequestConfig) {
8 | try {
9 | // Check and acquire a token before the request is sent
10 | const token = await authClient.refresh();
11 |
12 | // Set token inside provided request
13 | if (axiosRequestCfg?.headers) axiosRequestCfg.headers.Authorization = `Bearer ${token}`;
14 | } catch (err) {
15 | // Do something with error of acquiring the token
16 | console.error("getAuthToken:", err);
17 | }
18 |
19 | return axiosRequestCfg;
20 | }
21 |
22 | export function requestErrorInterceptor(error: any) {
23 | // Do nothing in case of request error
24 | return Promise.reject(error);
25 | }
26 |
27 | // RESPONSE
28 |
29 | export function responseSuccessInterceptor(axiosReponse: AxiosResponse) {
30 | // Do nothing with response
31 | return axiosReponse;
32 | }
33 |
34 | export function responseErrorInterceptor({ request, response, message }: any) {
35 | let status = -1;
36 | let errMsg = "";
37 |
38 | if (response) {
39 | // The request was made and the server responded with a status code
40 | // that falls out of the range of 2xx
41 | status = response?.status ?? 500;
42 | errMsg = response?.statusText ?? "Unknow server error";
43 | if (response?.data?.error instanceof Object) {
44 | errMsg = response?.data?.error?.message ?? errMsg;
45 | } else if (response?.data?.error) {
46 | errMsg = response?.data?.error;
47 | } else if (response?.data?.message) {
48 | // errMsg = response?.data?.message; // original: return directly the message
49 | errMsg = response?.data; // return entire BE error
50 | }
51 | } else if (request) {
52 | status = request?.status ?? 400;
53 | errMsg = "Unknow client error";
54 | } else if (!!message) {
55 | // Something happened in setting up the request that triggered an Error
56 | errMsg = message;
57 | }
58 |
59 | return Promise.reject({
60 | status,
61 | message: errMsg,
62 | });
63 | }
64 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/auth.ts:
--------------------------------------------------------------------------------
1 | import { createAuth, type AuthClient } from "@forward-software/react-auth";
2 | import axios, { AxiosInstance } from "axios";
3 | import isJwtTokenExpired from "jwt-check-expiry";
4 |
5 | type Tokens = Partial<{
6 | accessToken: string;
7 | refreshToken: string;
8 | }>;
9 |
10 | type Credentials = {
11 | username: string;
12 | password: string;
13 | };
14 |
15 | class MyAuthClient implements AuthClient {
16 | private axiosAuthClient: AxiosInstance | null = null;
17 |
18 | async onInit() {
19 | this.axiosAuthClient = axios.create({
20 | baseURL: import.meta.env.VITE_BASE_URL,
21 | headers: {
22 | "Content-Type": "application/json",
23 | },
24 | });
25 |
26 | // get tokens from persisted state (localstorage....)
27 | const tokens = localStorage.getItem("tokens");
28 |
29 | if (tokens) {
30 | return JSON.parse(tokens);
31 | }
32 |
33 | return null;
34 | }
35 |
36 | async onLogin(credentials?: Credentials): Promise {
37 | if (!this.axiosAuthClient) {
38 | return Promise.reject("axios client not initialized!");
39 | }
40 |
41 | // Replace auth/login with your url without the domain
42 | const payload = await this.axiosAuthClient.post("auth/login", {
43 | username: credentials?.username,
44 | password: credentials?.password,
45 | });
46 |
47 | localStorage.setItem("tokens", JSON.stringify(payload.data.data));
48 |
49 | return payload.data.data;
50 | }
51 |
52 | async onRefresh(currentTokens: Tokens): Promise {
53 | if (!this.axiosAuthClient) {
54 | return Promise.reject("axios client not initialized!");
55 | }
56 |
57 | if (!!currentTokens.accessToken && !isJwtTokenExpired(currentTokens.accessToken)) {
58 | return currentTokens;
59 | }
60 |
61 | const payload = await this.axiosAuthClient.post(
62 | // Replace jwt/refresh with your url without the domain
63 | "jwt/refresh",
64 | {
65 | refreshToken: currentTokens.refreshToken,
66 | },
67 | {
68 | headers: {
69 | Authorization: `Bearer ${currentTokens.accessToken}`,
70 | },
71 | }
72 | );
73 |
74 | localStorage.setItem("tokens", JSON.stringify(payload.data.data));
75 | return payload.data.data;
76 | }
77 |
78 | onLogout(): Promise {
79 | localStorage.removeItem("tokens");
80 | // If you need to call an API to logout, just use the onLogin code to do your stuff
81 | return Promise.resolve();
82 | }
83 | }
84 |
85 | export const { AuthProvider, authClient, useAuthClient } = createAuth(new MyAuthClient());
86 |
--------------------------------------------------------------------------------
/examples/refresh-token/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { App } from './App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 |
9 | )
--------------------------------------------------------------------------------
/examples/refresh-token/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/refresh-token/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "esModuleInterop": true,
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "include": ["src"],
22 | "references": [{ "path": "./tsconfig.node.json" }]
23 | }
--------------------------------------------------------------------------------
/examples/refresh-token/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
--------------------------------------------------------------------------------
/examples/refresh-token/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | server: {
7 | port: 3002,
8 | open: true
9 | }
10 | })
--------------------------------------------------------------------------------
/examples/reqres/.gitignore:
--------------------------------------------------------------------------------
1 | yarn.lock
2 |
--------------------------------------------------------------------------------
/examples/reqres/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/reqres/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ReqRes Example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/reqres/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import { createRoot } from 'react-dom/client';
4 |
5 | import { App } from './src/App';
6 |
7 | const rootElement = document.getElementById('root')
8 | if (rootElement) {
9 | createRoot(rootElement).render( );
10 | }
--------------------------------------------------------------------------------
/examples/reqres/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reqres-example",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview --host"
10 | },
11 | "dependencies": {
12 | "@forward-software/react-auth": "workspace:^",
13 | "axios": "^1.9.0",
14 | "react": "catalog:",
15 | "react-dom": "catalog:"
16 | },
17 | "devDependencies": {
18 | "@types/react": "catalog:",
19 | "@types/react-dom": "catalog:",
20 | "@vitejs/plugin-react": "catalog:",
21 | "typescript": "catalog:",
22 | "vite": "catalog:"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/reqres/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { AuthProvider } from './auth';
4 | import { Content } from './Content';
5 |
6 | export const App: React.FC = () => (
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/examples/reqres/src/Content.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useCallback, useState } from 'react';
3 | import { DependencyList } from 'react';
4 |
5 | import { useAuthClient } from './auth';
6 |
7 | export const Content: React.FC = () => {
8 | const authClient = useAuthClient();
9 | const userCredentials = useUserCredentials();
10 |
11 | const [doRegister, isRegisterLoading] = useAsyncCallback(
12 | () =>
13 | authClient.register({
14 | email: userCredentials.email,
15 | password: userCredentials.password,
16 | }),
17 | [authClient, userCredentials]
18 | );
19 |
20 |
21 | const [doLogin, isLoginLoading] = useAsyncCallback(
22 | () => authClient.login(userCredentials),
23 | [authClient, userCredentials]
24 | );
25 |
26 | const [doLogout, isLogoutLoading] = useAsyncCallback(
27 | () => authClient.logout(),
28 | [authClient]
29 | );
30 |
31 | return (
32 |
33 |
Auth client ready? {String(authClient.isInitialized)}
34 |
Auth client authenticated? {String(authClient.isAuthenticated)}
35 |
36 |
37 |
42 |
47 |
48 |
49 |
50 |
54 | Register
55 |
56 |
57 |
61 | Login
62 |
63 |
64 |
68 | Logout
69 |
70 |
71 |
72 | {isRegisterLoading ?
Register in progress..
: null}
73 | {isLoginLoading ?
Login in progress..
: null}
74 |
75 |
Tokens:
76 |
{JSON.stringify(authClient.tokens ?? {}, null, 2)}
77 |
78 | );
79 | };
80 |
81 | function useUserCredentials() {
82 | const [email, setEmail] = useState('');
83 | const [password, setPassword] = useState('');
84 |
85 | const updateEmail = useCallback(
86 | (evt: React.ChangeEvent) => {
87 | setEmail(evt.target.value);
88 | },
89 | []
90 | );
91 | const updatePassword = useCallback(
92 | (evt: React.ChangeEvent) => {
93 | setPassword(evt.target.value);
94 | },
95 | []
96 | );
97 |
98 | return {
99 | email,
100 | password,
101 | updateEmail,
102 | updatePassword,
103 | };
104 | }
105 |
106 | function useAsyncCallback Promise>(
107 | callback: T,
108 | deps: DependencyList
109 | ): [T, boolean] {
110 | const [isLoading, setLoading] = useState(false);
111 | const cb = useCallback(async (...argsx: never[]) => {
112 | setLoading(true);
113 | const res = await callback(...argsx);
114 | setLoading(false);
115 | return res;
116 | }, deps) as T;
117 |
118 | return [cb, isLoading];
119 | }
120 |
--------------------------------------------------------------------------------
/examples/reqres/src/auth.ts:
--------------------------------------------------------------------------------
1 | import { createAuth, type AuthClient } from "@forward-software/react-auth";
2 | import axios from "axios";
3 |
4 | type ReqResCredentials = {
5 | email: string;
6 |
7 | password: string;
8 | };
9 |
10 | type ReqResAuthTokens = {
11 | token: string;
12 | };
13 |
14 | class ReqResAuthClient implements AuthClient {
15 | private _apiClient = axios.create({
16 | baseURL: "https://reqres.in",
17 | headers: {
18 | "x-api-key": "reqres-free-v1",
19 | },
20 | });
21 |
22 | async onLogin(credentials: ReqResCredentials): Promise {
23 | if (!credentials) {
24 | throw new Error("Invalid credentials");
25 | }
26 |
27 | const { data } = await this._apiClient.post("api/login", {
28 | email: credentials.email,
29 | password: credentials.password,
30 | });
31 |
32 | return {
33 | token: data.token,
34 | };
35 | }
36 |
37 | public async register(credentials: ReqResCredentials): Promise {
38 | try {
39 | await this._apiClient.post("/api/register", credentials);
40 |
41 | return true;
42 | } catch (err) {
43 | console.error("Register call failed", err);
44 | }
45 |
46 | return false;
47 | }
48 | }
49 |
50 | export const { AuthProvider, authClient, useAuthClient } = createAuth(new ReqResAuthClient());
51 |
--------------------------------------------------------------------------------
/examples/reqres/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { App } from './App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 |
9 | )
--------------------------------------------------------------------------------
/examples/reqres/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "esModuleInterop": true,
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "include": ["src"],
22 | "references": [{ "path": "./tsconfig.node.json" }]
23 | }
--------------------------------------------------------------------------------
/examples/reqres/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
--------------------------------------------------------------------------------
/examples/reqres/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | server: {
7 | port: 3001,
8 | open: true
9 | }
10 | })
--------------------------------------------------------------------------------
/lib/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [2.0.0](https://github.com/forwardsoftware/react-auth/compare/v1.1.0...v2.0.0) (2025-05-13)
4 |
5 |
6 | ### ⚠ BREAKING CHANGES
7 |
8 | * Expose enhanced AuthClient interface and refactor authentication logic ([#13](https://github.com/forwardsoftware/react-auth/issues/13))
9 |
10 | ### Features
11 |
12 | * Expose enhanced AuthClient interface and refactor authentication logic ([#13](https://github.com/forwardsoftware/react-auth/issues/13)) ([b5d3988](https://github.com/forwardsoftware/react-auth/commit/b5d39884fb9d65e472a54a42b1a52b403adf4851))
13 | * modernize project tooling, dependencies, and examples ([#7](https://github.com/forwardsoftware/react-auth/issues/7)) ([ac06376](https://github.com/forwardsoftware/react-auth/commit/ac063768917622e537c57b45da9eff5b50060c75))
14 |
--------------------------------------------------------------------------------
/lib/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 ForWarD Software (https://forwardsoftware.solutions/)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | # React Auth
2 |
3 | > Simplify your Auth flow when working with React apps
4 |
5 | [](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [](https://github.com/forwardsoftware/react-auth/issues)
6 |
7 | [](https://npmjs.com/package/@forward-software/react-auth) [](https://npmjs.com/package/@forward-software/react-auth)
8 |
9 | This React package allows you to streamline the integration of user authentication flows in any React app by providing a single unified interface
10 |
11 | ---
12 |
13 | ## Install
14 |
15 | ```sh
16 | npm install @forward-software/react-auth
17 | ```
18 |
19 | ## Setup
20 |
21 | ### Define an AuthClient
22 |
23 | Create a new object that implements the `AuthClient` interface provided by this library. The interface includes several lifecycle methods, some of which are optional:
24 |
25 | ```ts
26 | import type { AuthClient } from '@forward-software/react-auth';
27 |
28 | // The type for your credentials
29 | type AuthCredentials = {
30 | username: string;
31 | password: string;
32 | };
33 |
34 | // The type for your tokens
35 | type AuthTokens = {
36 | authToken: string;
37 | refreshToken: string;
38 | };
39 |
40 | const authClient: AuthClient = {
41 | // Optional: Called when the AuthClient gets initialized
42 | onInit: async (): Promise => {
43 | // Implement the initialization logic for your client
44 | return null;
45 | },
46 |
47 | // Optional: Called after initialization completes
48 | onPostInit: async (): Promise => {
49 | // Implement any post-initialization logic
50 | },
51 |
52 | // Optional: Called before login starts
53 | onPreLogin: async (): Promise => {
54 | // Implement any pre-login logic
55 | },
56 |
57 | // Required: Called when login is requested
58 | onLogin: async (credentials?: AuthCredentials): Promise => {
59 | // Implement the logic required to exchange the provided credentials for user tokens
60 | return {
61 | authToken: '...',
62 | refreshToken: '...'
63 | };
64 | },
65 |
66 | // Optional: Called after login completes
67 | onPostLogin: async (isSuccess: boolean): Promise => {
68 | // Implement any post-login logic
69 | },
70 |
71 | // Optional: Called before refresh starts
72 | onPreRefresh: async (): Promise => {
73 | // Implement any pre-refresh logic
74 | },
75 |
76 | // Optional: Called when refresh is requested
77 | // The current tokens are passed as the first argument
78 | onRefresh: async (currentTokens: AuthTokens, minValidity?: number): Promise => {
79 | // Implement the logic required to refresh the current user tokens
80 | return {
81 | authToken: '...',
82 | refreshToken: '...'
83 | };
84 | },
85 |
86 | // Optional: Called after refresh completes
87 | onPostRefresh: async (isSuccess: boolean): Promise => {
88 | // Implement any post-refresh logic
89 | },
90 |
91 | // Optional: Called before logout starts
92 | onPreLogout: async (): Promise => {
93 | // Implement any pre-logout logic
94 | },
95 |
96 | // Optional: Called when logout is requested
97 | onLogout: async (): Promise => {
98 | // Implement the logic required to invalidate the current user tokens
99 | },
100 |
101 | // Optional: Called after logout completes
102 | onPostLogout: async (isSuccess: boolean): Promise => {
103 | // Implement any post-logout logic
104 | }
105 | };
106 | ```
107 |
108 | ### Use the AuthClient
109 |
110 | The `AuthClient` instance can be used directly with the `createAuth` function:
111 |
112 | ```ts
113 | import { createAuth } from '@forward-software/react-auth';
114 |
115 | export const { AuthProvider, useAuthClient, authClient: enhancedAuthClient } = createAuth(authClient);
116 | ```
117 |
118 | The `createAuth` function returns:
119 |
120 | - `AuthProvider`, the context Provider component that should wrap your app and provide access to your AuthClient
121 | - `useAuthClient`, the hook to retrieve and interact with your AuthClient
122 | - `authClient`, the enhanced authentication client instance
123 |
124 | #### AuthProvider
125 |
126 | The context Provider component that should wrap your app and provide access to your AuthClient, this component also accepts 2 additional props
127 |
128 | - `ErrorComponent`, displayed when the AuthClient initialization fails
129 | - `LoadingComponent`, displayed while the AuthClient is being initialized
130 |
131 | #### EnhancedAuthClient
132 |
133 | The `createAuth` function wraps your `AuthClient` implementation with an `EnhancedAuthClient` that provides additional functionality:
134 |
135 | ##### Properties
136 | - `isInitialized`, a boolean indicating if the AuthClient has been initialized
137 | - `isAuthenticated`, a boolean indicating if the login process has been successful and the user is authenticated
138 | - `tokens`, the current tokens returned by the `login` or the `refresh` process
139 |
140 | ##### Methods
141 | - `init()`, initialize the AuthClient (**N.B.** this shouldn't be called if using `AuthProvider` - see above)
142 | - `login(credentials)`, start the login process
143 | - `refresh()`, refresh the current tokens
144 | - `logout()`, logout and invalidate the current tokens
145 | - `on(eventName, listenerFn)`, subscribe to `eventName` events emitted by the AuthClient
146 | - `off(eventName, listenerFn)`, unsubscribe from `eventName` events emitted by the AuthClient
147 | - `subscribe(() => { })`, subscribe to AuthClient state changes
148 | - `getSnapshot()`, returns the current state of the AuthClient
149 |
150 | ### React components
151 |
152 | Setup React components to interact with the AuthClient using the `createAuth` function exported by this library
153 |
154 | ```ts
155 | import { createAuth } from '@forward-software/react-auth';
156 |
157 | export const { AuthProvider, useAuthClient } = createAuth(authClient);
158 | ```
159 |
160 | the `createAuth` function returns:
161 |
162 | - `AuthProvider`, the context Provider component that should wrap your app and provide access to your AuthClient
163 | - `useAuthClient`, the hook to retrieve and interact with your AuthClient
164 |
165 | #### AuthProvider
166 |
167 | The context Provider component that should wrap your app and provide access to your AuthClient, this component also accepts 2 additional props
168 |
169 | - `ErrorComponent`, displayed when the AuthClient initialization fails
170 | - `LoadingComponent`, displayed while the AuthClient is being initialized
171 |
172 | ## Examples
173 |
174 | The `examples` folder contains some examples of how you can integrate this library in your React app.
175 |
176 | ## Credits
177 |
178 | This library has been inspired by [`react-keycloak`](https://github.com/react-keycloak/react-keycloak) and similar libraries.
179 |
180 | ## License
181 |
182 | MIT
183 |
184 | ---
185 |
186 | Made with ✨ & ❤️ by [ForWarD Software](https://github.com/forwardsoftware) and [contributors](https://github.com/forwardsoftware/react-auth/graphs/contributors)
187 |
188 | If you found this project to be helpful, please consider contacting us to develop your React and React Native projects.
189 |
--------------------------------------------------------------------------------
/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@forward-software/react-auth",
3 | "description": "Simplify your Auth flow when working with React apps",
4 | "version": "2.0.0",
5 | "author": "ForWarD Software (https://forwardsoftware.solutions/)",
6 | "license": "MIT",
7 | "repository": "https://github.com/forwardsoftware/react-auth",
8 | "homepage": "https://github.com/forwardsoftware/react-auth#readme",
9 | "keywords": [
10 | "react",
11 | "react-native",
12 | "auth",
13 | "authentication"
14 | ],
15 | "main": "dist/index.js",
16 | "types": "dist/index.d.ts",
17 | "react-native": "src/index.tsx",
18 | "source": "src/index.tsx",
19 | "files": [
20 | "dist",
21 | "src"
22 | ],
23 | "scripts": {
24 | "build:code": "tsc --removeComments",
25 | "build:types": "tsc --declaration --emitDeclarationOnly",
26 | "build": "run-p clean build:*",
27 | "lint": "eslint src",
28 | "test": "vitest",
29 | "test:watch": "vitest watch",
30 | "clean": "rimraf dist"
31 | },
32 | "devDependencies": {
33 | "@testing-library/dom": "^10.0.0",
34 | "@testing-library/jest-dom": "^6.6.3",
35 | "@testing-library/react": "^16.3.0",
36 | "@types/node": "^22.15.29",
37 | "@types/react": "catalog:",
38 | "@types/react-dom": "catalog:",
39 | "@types/use-sync-external-store": "^1.5.0",
40 | "@vitejs/plugin-react": "catalog:",
41 | "jsdom": "^26.1.0",
42 | "react": "catalog:",
43 | "react-dom": "catalog:",
44 | "vite": "catalog:",
45 | "vitest": "^3.1.2"
46 | },
47 | "dependencies": {
48 | "use-sync-external-store": "^1.5.0"
49 | },
50 | "peerDependencies": {
51 | "react": ">=16.8"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/auth.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useEffect, useState } from 'react';
2 | import type { PropsWithChildren } from 'react';
3 | import { useSyncExternalStore } from 'use-sync-external-store/shim';
4 |
5 | import { createEventEmitter, Deferred, EventReceiver } from "./utils";
6 | import type { EventKey } from "./utils";
7 |
8 | /**
9 | * Represents authentication tokens used for API authorization
10 | */
11 | type AuthTokens = {};
12 |
13 | /**
14 | * Represents user credentials used for authentication
15 | */
16 | type AuthCredentials = {};
17 |
18 | /**
19 | * Maps authentication events to their corresponding payload types
20 | * @template E - The error type used throughout the authentication flow
21 | */
22 | type AuthEventsMap = {
23 | initSuccess: undefined;
24 |
25 | initFailed: E;
26 |
27 | loginStarted: undefined;
28 |
29 | loginSuccess: undefined;
30 |
31 | loginFailed: E;
32 |
33 | refreshStarted: undefined;
34 |
35 | refreshSuccess: undefined;
36 |
37 | refreshFailed: E;
38 |
39 | logoutStarted: undefined;
40 |
41 | logoutSuccess: undefined;
42 |
43 | logoutFailed: E;
44 | };
45 |
46 | /**
47 | * Function type for subscription callbacks
48 | */
49 | type SubscribeFn = () => void;
50 |
51 | /**
52 | * Function type for unsubscribing from events
53 | * @returns {boolean} - Returns true if the subscription was successfully removed
54 | */
55 | type UnsubscribeFn = () => boolean;
56 |
57 | /**
58 | * Interface defining the core authentication client functionality
59 | * @template T - The type of authentication tokens
60 | * @template C - The type of authentication credentials
61 | */
62 | export interface AuthClient {
63 | /**
64 | * Optional initialization hook called before authentication
65 | * @returns {Promise} - Returns authentication tokens if available
66 | */
67 | onInit?(): Promise;
68 |
69 | /**
70 | * Optional post-initialization hook
71 | */
72 | onPostInit?(): Promise;
73 |
74 | /**
75 | * Optional pre-login hook
76 | */
77 | onPreLogin?(): Promise;
78 |
79 | /**
80 | * Handles the login process
81 | * @param {C} [credentials] - Optional credentials for authentication
82 | * @returns {Promise} - Returns authentication tokens upon successful login
83 | */
84 | onLogin(credentials?: C): Promise;
85 |
86 | /**
87 | * Optional post-login hook
88 | * @param {boolean} isSuccess - Indicates whether the login was successful
89 | */
90 | onPostLogin?(isSuccess: boolean): Promise;
91 |
92 | /**
93 | * Optional pre-refresh hook
94 | */
95 | onPreRefresh?(): Promise;
96 |
97 | /**
98 | * Optional token refresh handler.
99 | * Implement this method to handle token refresh logic.
100 | * @param {T} currentTokens - The current authentication tokens.
101 | * @param {number} [minValidity] - Optional minimum token validity period in seconds.
102 | * @returns {Promise} - A promise that resolves with the refreshed authentication tokens.
103 | */
104 | onRefresh?(currentTokens: T, minValidity?: number): Promise;
105 |
106 | /**
107 | * Optional post-refresh hook
108 | * @param {boolean} isSuccess - Indicates whether the token refresh was successful
109 | */
110 | onPostRefresh?(isSuccess: boolean): Promise;
111 |
112 | /**
113 | * Optional pre-logout hook
114 | */
115 | onPreLogout?(): Promise;
116 |
117 | /**
118 | * Optional logout handler
119 | */
120 | onLogout?(): Promise;
121 |
122 | /**
123 | * Optional post-logout hook
124 | * @param {boolean} isSuccess - Indicates whether the logout was successful
125 | */
126 | onPostLogout?(isSuccess: boolean): Promise;
127 | }
128 |
129 | /**
130 | * Extracts token type from an AuthClient implementation
131 | * @template AC - The AuthClient implementation type
132 | */
133 | type AuthClientTokens = Partial>>;
134 |
135 | /**
136 | * Extracts credentials type from an AuthClient implementation
137 | * @template AC - The AuthClient implementation type
138 | */
139 | type AuthClientCredentials = Parameters;
140 |
141 | /**
142 | * Represents the current state of an AuthClient
143 | * @template AC - The AuthClient implementation type
144 | */
145 | type AuthClientState = {
146 | isAuthenticated: boolean;
147 |
148 | isInitialized: boolean;
149 |
150 | tokens: AuthClientTokens;
151 | };
152 |
153 |
154 | class AuthClientEnhancements {
155 |
156 | private _state: Readonly> = {
157 | isAuthenticated: false,
158 | isInitialized: false,
159 | tokens: {},
160 | };
161 |
162 | // refresh queue - used to avoid concurrency issue during Token refresh
163 | private refreshQ: Array> = [];
164 |
165 | private eventEmitter = createEventEmitter>();
166 |
167 | private subscribers: Set = new Set();
168 |
169 | private _authClient: AC;
170 |
171 | constructor(authClient: AC) {
172 | this._authClient = authClient;
173 | }
174 |
175 | //
176 | // Getters
177 | //
178 |
179 | /**
180 | * Indicates whether the authentication client has been initialized
181 | * @readonly
182 | */
183 | public get isInitialized() {
184 | return this._state.isInitialized;
185 | }
186 |
187 | /**
188 | * Indicates whether the user is currently authenticated
189 | * @readonly
190 | */
191 | public get isAuthenticated() {
192 | return this._state.isAuthenticated;
193 | }
194 |
195 | /**
196 | * Current authentication tokens
197 | * @readonly
198 | */
199 | public get tokens() {
200 | return this._state.tokens;
201 | }
202 |
203 | /**
204 | * Initializes the authentication client
205 | * @returns {Promise} - Returns true if initialization was successful
206 | */
207 | public async init(): Promise {
208 | try {
209 | const prevTokens = await this._authClient.onInit?.();
210 |
211 | this.setState({
212 | isInitialized: true,
213 | isAuthenticated: !!prevTokens,
214 | tokens: prevTokens || {},
215 | });
216 |
217 | this.emit("initSuccess", undefined);
218 | } catch (error) {
219 | this.setState({
220 | isInitialized: false,
221 | });
222 |
223 | this.emit("initFailed", error as E);
224 | }
225 |
226 | await this._authClient.onPostInit?.();
227 |
228 | return this.isInitialized;
229 | }
230 |
231 | /**
232 | * Attempts to authenticate the user with provided credentials
233 | * @param {...AuthClientCredentials} params - Authentication credentials
234 | * @returns {Promise} - Returns true if login was successful
235 | */
236 | public async login(...params: AuthClientCredentials): Promise {
237 | this.emit("loginStarted", undefined);
238 |
239 | await this._authClient.onPreLogin?.();
240 |
241 | let isSuccess: boolean = false;
242 |
243 | try {
244 | const tokens = await this._authClient.onLogin(...params);
245 |
246 | this.setState({
247 | isAuthenticated: !!tokens,
248 | tokens,
249 | });
250 |
251 | this.emit("loginSuccess", undefined);
252 |
253 | isSuccess = true;
254 | } catch (err) {
255 | this.setState({
256 | isAuthenticated: false,
257 | tokens: {},
258 | });
259 |
260 | this.emit("loginFailed", err as E);
261 |
262 | isSuccess = false;
263 | }
264 |
265 | await this._authClient.onPostLogin?.(isSuccess);
266 |
267 | return this.isAuthenticated;
268 | }
269 |
270 | /**
271 | * Refreshes the authentication tokens
272 | * @param {number} [minValidity] - Minimum token validity period in seconds
273 | * @returns {Promise} - Returns true if token refresh was successful
274 | */
275 | public async refresh(minValidity?: number): Promise {
276 | const deferred = new Deferred();
277 |
278 | this.runRefresh(deferred, minValidity);
279 |
280 | return deferred.getPromise();
281 | }
282 |
283 | /**
284 | * Logs out the current user
285 | * @returns {Promise}
286 | */
287 | public async logout(): Promise {
288 | this.emit("logoutStarted", undefined);
289 |
290 | await this._authClient.onPreLogout?.();
291 |
292 | let isSuccess: boolean = false;
293 |
294 | try {
295 | await this._authClient.onLogout?.();
296 |
297 | this.setState({
298 | isAuthenticated: false,
299 | tokens: {},
300 | });
301 |
302 | this.emit("logoutSuccess", undefined);
303 |
304 | isSuccess = true;
305 | } catch (err) {
306 | this.emit("logoutFailed", err as E);
307 |
308 | isSuccess = false;
309 | }
310 |
311 | await this._authClient.onPostLogout?.(isSuccess);
312 | }
313 |
314 | /**
315 | * Registers an event listener for authentication events
316 | * @template K - The event key type
317 | * @param {K} eventName - The name of the event to listen for
318 | * @param {EventReceiver[K]>} listener - The event handler function
319 | */
320 | public on>>(eventName: K, listener: EventReceiver[K]>): void {
321 | this.eventEmitter.on(eventName, listener);
322 | }
323 |
324 | /**
325 | * Removes an event listener for authentication events
326 | * @template K - The event key type
327 | * @param {K} eventName - The name of the event to stop listening for
328 | * @param {EventReceiver[K]>} listener - The event handler function to remove
329 | */
330 | public off>>(eventName: K, listener: EventReceiver[K]>): void {
331 | this.eventEmitter.off(eventName, listener);
332 | }
333 |
334 | /**
335 | * Subscribes to authentication state changes
336 | * @param {SubscribeFn} subscription - The callback function to be called on state changes
337 | * @returns {UnsubscribeFn} - A function to unsubscribe from state changes
338 | */
339 | // Should be declared like this to avoid binding issues when used by useSyncExternalStore
340 | public subscribe = (subscription: SubscribeFn): UnsubscribeFn => {
341 | this.subscribers.add(subscription);
342 |
343 | return () => this.subscribers.delete(subscription);
344 | };
345 |
346 |
347 | /**
348 | * Gets the current authentication state
349 | * @returns {AuthClientState} - The current authentication state
350 | */
351 | // Should be declared like this to avoid binding issues when used by useSyncExternalStore
352 | public getSnapshot = (): AuthClientState => {
353 | return this._state;
354 | };
355 |
356 | //
357 | // Private methods
358 | //
359 |
360 | private setState(stateUpdate: Partial>): void {
361 | this._state = {
362 | ...this._state,
363 | ...stateUpdate,
364 | };
365 |
366 | this.notifySubscribers();
367 | }
368 |
369 | private async runRefresh(deferred: Deferred, minValidity?: number): Promise {
370 | // Add deferred Promise to refresh queue
371 | this.refreshQ.push(deferred);
372 |
373 | // If refresh queue already has promises enqueued do not attempt a new refresh - one is already in progress
374 | if (this.refreshQ.length !== 1) {
375 | return;
376 | }
377 |
378 | this.emit("refreshStarted", undefined);
379 |
380 | await this._authClient.onPreRefresh?.();
381 |
382 | let isAuthenticated: boolean = false;
383 | let tokens: AuthClientTokens = {};
384 |
385 | try {
386 | tokens = (await this._authClient.onRefresh?.(this.tokens, minValidity)) ?? {};
387 | isAuthenticated = true;
388 |
389 | this.emit("refreshSuccess", undefined);
390 | } catch (err) {
391 | isAuthenticated = false;
392 |
393 | this.emit("refreshFailed", err as E);
394 | }
395 |
396 | this.setState({
397 | isAuthenticated,
398 | tokens,
399 | });
400 |
401 | await this._authClient.onPostRefresh?.(isAuthenticated);
402 |
403 | for (let p = this.refreshQ.pop(); p != null; p = this.refreshQ.pop()) {
404 | p.resolve(isAuthenticated);
405 | }
406 | }
407 |
408 | private emit>>(eventName: K, error: AuthEventsMap[K]): void {
409 | this.eventEmitter.emit(eventName, error);
410 | }
411 |
412 | private notifySubscribers() {
413 | this.subscribers.forEach((s) => {
414 | try {
415 | s();
416 | } catch { }
417 | });
418 | }
419 | }
420 |
421 | /**
422 | * Enhanced authentication client with additional functionality and state management
423 | * @template AC - The AuthClient implementation type
424 | * @template E - The error type used throughout the authentication flow
425 | */
426 | export type EnhancedAuthClient = AC & AuthClientEnhancements;
427 |
428 | /**
429 | * Wraps a basic AuthClient implementation with enhanced functionality
430 | * @template AC - The AuthClient implementation type
431 | * @template E - The error type used throughout the authentication flow
432 | * @param {AC} authClient - The base authentication client to enhance
433 | * @returns {EnhancedAuthClient} - An enhanced authentication client with additional features
434 | */
435 | export function wrapAuthClient(authClient: AC): EnhancedAuthClient {
436 | Object.setPrototypeOf(AuthClientEnhancements.prototype, authClient);
437 |
438 | return new AuthClientEnhancements(authClient) as unknown as EnhancedAuthClient;
439 | }
440 |
441 | /**
442 | * Represents the current state of the authentication provider
443 | */
444 | type AuthProviderState = {
445 | isAuthenticated: boolean;
446 | isInitialized: boolean;
447 | };
448 |
449 | /**
450 | * The authentication context containing both the state and the enhanced auth client
451 | * @template AC - The AuthClient implementation type
452 | * @template E - The error type used throughout the authentication flow
453 | */
454 | type AuthContext = AuthProviderState & {
455 | authClient: EnhancedAuthClient;
456 | };
457 |
458 | /**
459 | * Props that can be passed to AuthProvider
460 | */
461 | export type AuthProviderProps = PropsWithChildren<{
462 | /**
463 | * An optional component to display if AuthClient initialization failed.
464 | */
465 | ErrorComponent?: React.ReactNode;
466 |
467 | /**
468 | * An optional component to display while AuthClient instance is being initialized.
469 | */
470 | LoadingComponent?: React.ReactNode;
471 | }>;
472 |
473 | /**
474 | * Creates an authentication context and provider for a React application.
475 | * It wraps the provided `authClient` with enhanced state management and event handling.
476 | *
477 | * @template AC - The type of the base `AuthClient` implementation.
478 | * @template E - The type of error expected during authentication flows. Defaults to `Error`.
479 | * @param {AC} authClient - The base authentication client instance to use.
480 | * @returns An object containing:
481 | * - `AuthProvider`: A React component to wrap the application or parts of it.
482 | * - `authClient`: The enhanced authentication client instance.
483 | * - `useAuthClient`: A hook to access the enhanced `authClient` within the `AuthProvider`.
484 | */
485 | export function createAuth(authClient: AC) {
486 | // Create a React context containing an AuthClient instance.
487 | const authContext = createContext | null>(null);
488 |
489 | const enhancedAuthClient = wrapAuthClient(authClient);
490 |
491 | // Create the React Context Provider for the AuthClient instance.
492 | const AuthProvider: React.FC = ({ children, ErrorComponent, LoadingComponent }) => {
493 | const [isInitFailed, setInitFailed] = useState(false);
494 | const { isAuthenticated, isInitialized } = useSyncExternalStore(enhancedAuthClient.subscribe, enhancedAuthClient.getSnapshot);
495 |
496 | useEffect(() => {
497 | async function initAuthClient() {
498 | // Call init function
499 | const initSuccess = await enhancedAuthClient.init();
500 | setInitFailed(!initSuccess);
501 | }
502 |
503 | // Init AuthClient
504 | initAuthClient();
505 | }, []);
506 |
507 | if (!!ErrorComponent && isInitFailed) {
508 | return ErrorComponent;
509 | }
510 |
511 | if (!!LoadingComponent && !isInitialized) {
512 | return LoadingComponent;
513 | }
514 |
515 | return (
516 |
523 | {children}
524 |
525 | );
526 | };
527 |
528 | /**
529 | * Hook to access the authentication client within the AuthProvider
530 | * @throws Error if used outside of an AuthProvider
531 | */
532 | const useAuthClient = function (): EnhancedAuthClient {
533 | const ctx = useContext(authContext);
534 | if (!ctx) {
535 | throw new Error('useAuthClient hook should be used inside AuthProvider');
536 | }
537 |
538 | return ctx.authClient;
539 | };
540 |
541 | return {
542 | AuthProvider,
543 | authClient: enhancedAuthClient,
544 | useAuthClient,
545 | };
546 | }
--------------------------------------------------------------------------------
/lib/src/index.ts:
--------------------------------------------------------------------------------
1 | export { createAuth } from "./auth";
2 | export type { AuthClient } from "./auth";
3 |
--------------------------------------------------------------------------------
/lib/src/utils.ts:
--------------------------------------------------------------------------------
1 | // DEFERRED
2 |
3 | export class Deferred {
4 | private promise: Promise;
5 |
6 | public resolve!: (value: T | PromiseLike) => void;
7 |
8 | public reject!: (reason?: any) => void;
9 |
10 | constructor() {
11 | this.promise = new Promise((resolve, reject) => {
12 | this.reject = reject;
13 | this.resolve = resolve;
14 | });
15 | }
16 |
17 | public getPromise(): Promise {
18 | return this.promise;
19 | }
20 | }
21 |
22 | // EVENT EMITTER
23 |
24 | type EventsMap = Record;
25 |
26 | export type EventKey = string & keyof T;
27 |
28 | export type EventReceiver = (params: T) => void;
29 |
30 | interface Emitter {
31 | on>(eventName: K, fn: EventReceiver): void;
32 | off>(eventName: K, fn: EventReceiver): void;
33 | emit>(eventName: K, params: T[K]): void;
34 | }
35 |
36 | // TODO: Improve -> `listeners` are unbounded -- don't use this in practice!
37 | export function createEventEmitter(): Emitter {
38 | const listeners: {
39 | [K in keyof EventsMap]?: Array<(p: EventsMap[K]) => void>;
40 | } = {};
41 |
42 | return {
43 | on(key, fn) {
44 | listeners[key] = (listeners[key] || []).concat(fn);
45 | },
46 | off(key, fn) {
47 | listeners[key] = (listeners[key] || []).filter((f) => f !== fn);
48 | },
49 | emit(key, data) {
50 | (listeners[key] || []).forEach(function (fn) {
51 | try {
52 | fn(data);
53 | } catch {}
54 | });
55 | },
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/lib/test/authClient.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, afterEach } from "vitest";
2 | import * as rtl from "@testing-library/react";
3 | import "@testing-library/jest-dom";
4 |
5 | import { wrapAuthClient } from "../src/auth";
6 |
7 | import { createMockAuthClient, createMockAuthClientWithHooks } from "./test-utils";
8 |
9 | afterEach(rtl.cleanup);
10 |
11 | describe("AuthClient", () => {
12 | describe("on Init", () => {
13 | it("should notify success", async () => {
14 | // Arrange
15 |
16 | const initSuccessEventListener = vi.fn();
17 |
18 | const authClientMock = createMockAuthClient();
19 | vi.spyOn(authClientMock, "onInit").mockResolvedValueOnce(null);
20 |
21 | const authClient = wrapAuthClient(authClientMock);
22 |
23 | authClient.on("initSuccess", initSuccessEventListener);
24 |
25 | // Act
26 |
27 | await rtl.act(async () => {
28 | await authClient.init();
29 | });
30 |
31 | // Assert
32 |
33 | expect(initSuccessEventListener).toHaveBeenCalledTimes(1);
34 | });
35 |
36 | it("should notify failure", async () => {
37 | // Arrange
38 |
39 | const authClient = wrapAuthClient(createMockAuthClient());
40 |
41 | const initFailureEventListener = vi.fn();
42 | authClient.on("initFailed", initFailureEventListener);
43 |
44 | // Act
45 |
46 | await rtl.act(async () => {
47 | await authClient.init();
48 | });
49 |
50 | // Assert
51 |
52 | expect(initFailureEventListener).toHaveBeenCalledTimes(1);
53 | });
54 |
55 | it("should invoke postInit hook", async () => {
56 | // Arrange
57 |
58 | const postInitHook = vi.fn();
59 |
60 | const authClientMock = createMockAuthClientWithHooks({ onPostInit: postInitHook });
61 | vi.spyOn(authClientMock, "onInit").mockResolvedValue(null);
62 |
63 | const authClient = wrapAuthClient(authClientMock);
64 |
65 | // Act
66 |
67 | await rtl.act(async () => {
68 | await authClient.init();
69 | });
70 |
71 | // Assert
72 |
73 | expect(postInitHook).toHaveBeenCalledTimes(1);
74 | });
75 | });
76 |
77 | describe("on Login", () => {
78 | it("should notify start", async () => {
79 | // Arrange
80 |
81 | const authClient = wrapAuthClient(createMockAuthClient());
82 |
83 | const loginStartedListener = vi.fn();
84 | authClient.on("loginStarted", loginStartedListener);
85 |
86 | // Act
87 |
88 | await rtl.act(async () => {
89 | await authClient.login();
90 | });
91 |
92 | // Assert
93 |
94 | expect(loginStartedListener).toHaveBeenCalledTimes(1);
95 | });
96 |
97 | it("should notify success", async () => {
98 | // Arrange
99 |
100 | const authClientMock = createMockAuthClient();
101 | vi.spyOn(authClientMock, "onLogin").mockResolvedValue({
102 | authToken: "tkn",
103 | refreshToken: "tkn",
104 | });
105 |
106 | const authClient = wrapAuthClient(authClientMock);
107 |
108 | const loginSuccessEventListener = vi.fn();
109 | authClient.on("loginSuccess", loginSuccessEventListener);
110 |
111 | // Act
112 |
113 | await rtl.act(async () => {
114 | await authClient.login();
115 | });
116 |
117 | // Assert
118 |
119 | expect(loginSuccessEventListener).toHaveBeenCalledTimes(1);
120 | });
121 |
122 | it("should notify failure", async () => {
123 | // Arrange
124 |
125 | const authClient = wrapAuthClient(createMockAuthClient());
126 |
127 | const loginFailureEventListener = vi.fn();
128 | authClient.on("loginFailed", loginFailureEventListener);
129 |
130 | // Act
131 |
132 | await rtl.act(async () => {
133 | await authClient.login();
134 | });
135 |
136 | // Assert
137 |
138 | expect(loginFailureEventListener).toHaveBeenCalledTimes(1);
139 | });
140 |
141 | it("should invoke preLogin and postLogin hooks in case of success", async () => {
142 | // Arrange
143 |
144 | const preLoginHook = vi.fn();
145 | const postLoginHook = vi.fn();
146 |
147 | const authClientMock = createMockAuthClientWithHooks({
148 | onPreLogin: preLoginHook,
149 | onPostLogin: postLoginHook,
150 | });
151 | vi.spyOn(authClientMock, "onLogin").mockResolvedValue({
152 | authToken: "tkn",
153 | refreshToken: "tkn",
154 | });
155 |
156 | const authClient = wrapAuthClient(authClientMock);
157 |
158 | // Act
159 |
160 | await rtl.act(async () => {
161 | await authClient.login();
162 | });
163 |
164 | // Assert
165 |
166 | expect(preLoginHook).toHaveBeenCalledTimes(1);
167 | expect(postLoginHook).toHaveBeenCalledTimes(1);
168 | expect(postLoginHook).toHaveBeenCalledWith(true);
169 | });
170 |
171 | it("should invoke preLogin and postLogin hooks in case of failure", async () => {
172 | // Arrange
173 |
174 | const preLoginHook = vi.fn();
175 | const postLoginHook = vi.fn();
176 |
177 | const authClientMock = createMockAuthClientWithHooks({
178 | onPreLogin: preLoginHook,
179 | onPostLogin: postLoginHook,
180 | });
181 |
182 | const authClient = wrapAuthClient(authClientMock);
183 |
184 | // Act
185 |
186 | await rtl.act(async () => {
187 | await authClient.login();
188 | });
189 |
190 | // Assert
191 |
192 | expect(preLoginHook).toHaveBeenCalledTimes(1);
193 | expect(postLoginHook).toHaveBeenCalledTimes(1);
194 | expect(postLoginHook).toHaveBeenCalledWith(false);
195 | });
196 | });
197 |
198 | describe("on Refresh", () => {
199 | it("should notify start", async () => {
200 | // Arrange
201 |
202 | const authClient = wrapAuthClient(createMockAuthClient());
203 |
204 | const refreshStartedListener = vi.fn();
205 | authClient.on("refreshStarted", refreshStartedListener);
206 |
207 | // Act
208 |
209 | await rtl.act(async () => {
210 | await authClient.refresh();
211 | });
212 |
213 | // Assert
214 |
215 | expect(refreshStartedListener).toHaveBeenCalledTimes(1);
216 | });
217 |
218 | it("should notify success", async () => {
219 | // Arrange
220 |
221 | const authClientMock = createMockAuthClient();
222 | vi.spyOn(authClientMock, "onRefresh").mockResolvedValue({
223 | authToken: "tkn",
224 | refreshToken: "tkn",
225 | });
226 |
227 | const authClient = wrapAuthClient(authClientMock);
228 |
229 | const refreshSuccessEventListener = vi.fn();
230 | authClient.on("refreshSuccess", refreshSuccessEventListener);
231 |
232 | // Act
233 |
234 | await rtl.act(async () => {
235 | await authClient.refresh();
236 | });
237 |
238 | // Assert
239 |
240 | expect(refreshSuccessEventListener).toHaveBeenCalledTimes(1);
241 | });
242 |
243 | it("should notify failure", async () => {
244 | // Arrange
245 |
246 | const authClient = wrapAuthClient(createMockAuthClient());
247 |
248 | const refreshFailureEventListener = vi.fn();
249 | authClient.on("refreshFailed", refreshFailureEventListener);
250 |
251 | // Act
252 |
253 | await rtl.act(async () => {
254 | await authClient.refresh();
255 | });
256 |
257 | // Assert
258 |
259 | expect(refreshFailureEventListener).toHaveBeenCalledTimes(1);
260 | });
261 |
262 | it("should NOT trigger onRefresh twice", async () => {
263 | // Arrange
264 |
265 | const authClientMock = createMockAuthClient();
266 | vi.spyOn(authClientMock, "onRefresh").mockResolvedValue({
267 | authToken: "tkn",
268 | refreshToken: "tkn",
269 | });
270 |
271 | const authClient = wrapAuthClient(authClientMock);
272 |
273 | // Act
274 |
275 | await rtl.act(() => {
276 | authClient.refresh();
277 | authClient.refresh();
278 | });
279 |
280 | // Assert
281 |
282 | expect(authClientMock.onRefresh).toHaveBeenCalledTimes(1);
283 | });
284 |
285 | it("should NOT emit refresh events twice", async () => {
286 | // Arrange
287 |
288 | const authClientMock = createMockAuthClient();
289 | vi.spyOn(authClientMock, "onRefresh").mockResolvedValue({
290 | authToken: "tkn",
291 | refreshToken: "tkn",
292 | });
293 |
294 | const authClient = wrapAuthClient(authClientMock);
295 |
296 | const refreshStartedListener = vi.fn();
297 | authClient.on("refreshStarted", refreshStartedListener);
298 |
299 | const refreshSuccessEventListener = vi.fn();
300 | authClient.on("refreshSuccess", refreshSuccessEventListener);
301 |
302 | const refreshFailureEventListener = vi.fn();
303 | authClient.on("refreshFailed", refreshFailureEventListener);
304 |
305 | // Act
306 |
307 | await rtl.act(() => {
308 | authClient.refresh();
309 | authClient.refresh();
310 | });
311 |
312 | // Assert
313 |
314 | expect(refreshStartedListener).toHaveBeenCalledTimes(1);
315 | expect(refreshSuccessEventListener).toHaveBeenCalledTimes(1);
316 | expect(refreshFailureEventListener).toHaveBeenCalledTimes(0);
317 | });
318 |
319 | it("should invoke preRefresh and postRefresh hooks in case of success", async () => {
320 | // Arrange
321 |
322 | const preRefreshHook = vi.fn();
323 | const postRefreshHook = vi.fn();
324 |
325 | const authClientMock = createMockAuthClientWithHooks({
326 | onPreRefresh: preRefreshHook,
327 | onPostRefresh: postRefreshHook,
328 | });
329 |
330 | vi.spyOn(authClientMock, "onRefresh").mockResolvedValue({
331 | authToken: "tkn",
332 | refreshToken: "tkn",
333 | });
334 |
335 | const authClient = wrapAuthClient(authClientMock);
336 |
337 | // Act
338 |
339 | await rtl.act(async () => {
340 | await authClient.refresh();
341 | });
342 |
343 | // Assert
344 |
345 | expect(preRefreshHook).toHaveBeenCalledTimes(1);
346 | expect(postRefreshHook).toHaveBeenCalledTimes(1);
347 | expect(postRefreshHook).toHaveBeenCalledWith(true);
348 | });
349 |
350 | it("should invoke preRefresh and postRefresh hooks in case of failure", async () => {
351 | // Arrange
352 |
353 | const preRefreshHook = vi.fn();
354 | const postRefreshHook = vi.fn();
355 |
356 | const authClientMock = createMockAuthClientWithHooks({
357 | onPreRefresh: preRefreshHook,
358 | onPostRefresh: postRefreshHook,
359 | });
360 |
361 | const authClient = wrapAuthClient(authClientMock);
362 |
363 | // Act
364 |
365 | await rtl.act(async () => {
366 | await authClient.refresh();
367 | });
368 |
369 | // Assert
370 |
371 | expect(preRefreshHook).toHaveBeenCalledTimes(1);
372 | expect(postRefreshHook).toHaveBeenCalledTimes(1);
373 | expect(postRefreshHook).toHaveBeenCalledWith(false);
374 | });
375 | });
376 |
377 | describe("on logout", () => {
378 | it("should notify start", async () => {
379 | // Arrange
380 |
381 | const authClient = wrapAuthClient(createMockAuthClient());
382 |
383 | const logoutStartedListener = vi.fn();
384 | authClient.on("logoutStarted", logoutStartedListener);
385 |
386 | // Act
387 |
388 | await rtl.act(async () => {
389 | await authClient.logout();
390 | });
391 |
392 | // Assert
393 |
394 | expect(logoutStartedListener).toHaveBeenCalledTimes(1);
395 | });
396 |
397 | it("should notify success", async () => {
398 | // Arrange
399 |
400 | const authClientMock = createMockAuthClient();
401 | vi.spyOn(authClientMock, "onLogout").mockResolvedValue(undefined);
402 |
403 | const authClient = wrapAuthClient(authClientMock);
404 |
405 | const logoutSuccessEventListener = vi.fn();
406 | authClient.on("logoutSuccess", logoutSuccessEventListener);
407 |
408 | // Act
409 |
410 | await rtl.act(async () => {
411 | await authClient.logout();
412 | });
413 |
414 | // Assert
415 |
416 | expect(logoutSuccessEventListener).toHaveBeenCalledTimes(1);
417 | });
418 |
419 | it("should notify failure", async () => {
420 | // Arrange
421 |
422 | const authClientMock = createMockAuthClient();
423 |
424 | const authClient = wrapAuthClient(authClientMock);
425 |
426 | const logoutFailureEventListener = vi.fn();
427 | authClient.on("logoutFailed", logoutFailureEventListener);
428 |
429 | // Act
430 |
431 | await rtl.act(async () => {
432 | await authClient.logout();
433 | });
434 |
435 | // Assert
436 |
437 | expect(logoutFailureEventListener).toHaveBeenCalledTimes(1);
438 | });
439 |
440 | it("should invoke preLogout and postLogout hooks in case of success", async () => {
441 | // Arrange
442 |
443 | const preLogoutHook = vi.fn();
444 | const postLogoutHook = vi.fn();
445 |
446 | const authClientMock = createMockAuthClientWithHooks({
447 | onPreLogout: preLogoutHook,
448 | onPostLogout: postLogoutHook,
449 | });
450 | vi.spyOn(authClientMock, "onLogout").mockResolvedValue(undefined);
451 |
452 | const authClient = wrapAuthClient(authClientMock);
453 |
454 | // Act
455 |
456 | await rtl.act(async () => {
457 | await authClient.logout();
458 | });
459 |
460 | // Assert
461 |
462 | expect(preLogoutHook).toHaveBeenCalledTimes(1);
463 | expect(postLogoutHook).toHaveBeenCalledTimes(1);
464 | expect(postLogoutHook).toHaveBeenCalledWith(true);
465 | });
466 |
467 | it("should invoke preLogout and postLogout hooks in case of failure", async () => {
468 | // Arrange
469 |
470 | const preLogoutHook = vi.fn();
471 | const postLogoutHook = vi.fn();
472 |
473 | const authClientMock = createMockAuthClientWithHooks({
474 | onPreLogout: preLogoutHook,
475 | onPostLogout: postLogoutHook,
476 | });
477 |
478 | const authClient = wrapAuthClient(authClientMock);
479 |
480 | // Act
481 |
482 | await rtl.act(async () => {
483 | await authClient.logout();
484 | });
485 |
486 | // Assert
487 |
488 | expect(preLogoutHook).toHaveBeenCalledTimes(1);
489 | expect(postLogoutHook).toHaveBeenCalledTimes(1);
490 | expect(postLogoutHook).toHaveBeenCalledWith(false);
491 | });
492 | });
493 |
494 | describe("when requested", () => {
495 | it("should return empty tokens by default", async () => {
496 | // Arrange
497 |
498 | const authClient = wrapAuthClient(createMockAuthClient());
499 |
500 | // Assert
501 |
502 | expect(authClient.tokens).toStrictEqual({});
503 | });
504 |
505 | it("should return current tokens after login", async () => {
506 | // Arrange
507 |
508 | const authClientMock = createMockAuthClient();
509 | vi.spyOn(authClientMock, "onLogin").mockResolvedValue({
510 | authToken: "a.fake.tkn",
511 | refreshToken: "a.fake.tkn",
512 | });
513 |
514 | const authClient = wrapAuthClient(authClientMock);
515 |
516 | // Act
517 |
518 | await rtl.act(async () => {
519 | await authClient.login();
520 | });
521 |
522 | // Assert
523 |
524 | expect(authClient.tokens).toStrictEqual({
525 | authToken: "a.fake.tkn",
526 | refreshToken: "a.fake.tkn",
527 | });
528 | });
529 | });
530 |
531 | describe("when event listener is removed", () => {
532 | it("should not crash if no listener is defined", async () => {
533 | // Arrange
534 |
535 | const authClient = wrapAuthClient(createMockAuthClient());
536 |
537 | const initSuccessEventListener = vi.fn();
538 |
539 | // Assert
540 |
541 | expect(() => {
542 | authClient.off("initSuccess", initSuccessEventListener);
543 | }).not.toThrow();
544 |
545 | expect(initSuccessEventListener).not.toBeCalled();
546 | });
547 |
548 | it("should not be invoked on login success", async () => {
549 | // Arrange
550 |
551 | const authClientMock = createMockAuthClient();
552 | vi.spyOn(authClientMock, "onLogin").mockResolvedValue({
553 | authToken: "tkn",
554 | refreshToken: "tkn",
555 | });
556 |
557 | const authClient = wrapAuthClient(authClientMock);
558 |
559 | const loginSuccessEventListener = vi.fn();
560 | authClient.on("loginSuccess", loginSuccessEventListener);
561 |
562 | // Act
563 |
564 | await rtl.act(async () => {
565 | await authClient.login();
566 | });
567 |
568 | authClient.off("loginSuccess", loginSuccessEventListener);
569 |
570 | await rtl.act(async () => {
571 | await authClient.login();
572 | });
573 |
574 | // Assert
575 |
576 | expect(loginSuccessEventListener).toHaveBeenCalledTimes(1);
577 | });
578 |
579 | it("should not be invoked on login failed", async () => {
580 | // Arrange
581 |
582 | const authClient = wrapAuthClient(createMockAuthClient());
583 |
584 | const loginFailureEventListener = vi.fn();
585 | authClient.on("loginFailed", loginFailureEventListener);
586 |
587 | // Act
588 |
589 | await rtl.act(async () => {
590 | await authClient.login();
591 | });
592 |
593 | authClient.off("loginFailed", loginFailureEventListener);
594 |
595 | await rtl.act(async () => {
596 | await authClient.login();
597 | });
598 |
599 | // Assert
600 |
601 | expect(loginFailureEventListener).toHaveBeenCalledTimes(1);
602 | });
603 |
604 | it("should not be invoked on refresh success", async () => {
605 | // Arrange
606 |
607 | const authClientMock = createMockAuthClient();
608 | vi.spyOn(authClientMock, "onRefresh").mockResolvedValue({
609 | authToken: "tkn",
610 | refreshToken: "tkn",
611 | });
612 |
613 | const authClient = wrapAuthClient(authClientMock);
614 |
615 | const refreshStartedListener = vi.fn();
616 | authClient.on("refreshStarted", refreshStartedListener);
617 |
618 | const refreshSuccessEventListener = vi.fn();
619 | authClient.on("refreshSuccess", refreshSuccessEventListener);
620 |
621 | const refreshFailureEventListener = vi.fn();
622 | authClient.on("refreshFailed", refreshFailureEventListener);
623 |
624 | // Act
625 |
626 | await rtl.act(() => {
627 | authClient.refresh();
628 | });
629 |
630 | authClient.off("refreshSuccess", refreshSuccessEventListener);
631 | authClient.off("refreshStarted", refreshStartedListener);
632 | authClient.off("refreshFailed", refreshFailureEventListener);
633 |
634 | await rtl.act(() => {
635 | authClient.refresh();
636 | });
637 |
638 | // Assert
639 |
640 | expect(refreshStartedListener).toHaveBeenCalledTimes(1);
641 | expect(refreshSuccessEventListener).toHaveBeenCalledTimes(1);
642 | expect(refreshFailureEventListener).toHaveBeenCalledTimes(0);
643 | });
644 |
645 | it("should not be invoked on refresh failed", async () => {
646 | // Arrange
647 |
648 | const authClientMock = createMockAuthClient();
649 | vi.spyOn(authClientMock, "onRefresh").mockRejectedValue(null);
650 |
651 | const authClient = wrapAuthClient(authClientMock);
652 |
653 | const refreshStartedListener = vi.fn();
654 | authClient.on("refreshStarted", refreshStartedListener);
655 |
656 | const refreshSuccessEventListener = vi.fn();
657 | authClient.on("refreshSuccess", refreshSuccessEventListener);
658 |
659 | const refreshFailureEventListener = vi.fn();
660 | authClient.on("refreshFailed", refreshFailureEventListener);
661 |
662 | // Act
663 |
664 | await rtl.act(() => {
665 | authClient.refresh();
666 | });
667 |
668 | authClient.off("refreshSuccess", refreshSuccessEventListener);
669 | authClient.off("refreshStarted", refreshStartedListener);
670 | authClient.off("refreshFailed", refreshFailureEventListener);
671 |
672 | await rtl.act(() => {
673 | authClient.refresh();
674 | });
675 |
676 | // Assert
677 |
678 | expect(refreshStartedListener).toHaveBeenCalledTimes(1);
679 | expect(refreshSuccessEventListener).toHaveBeenCalledTimes(0);
680 | expect(refreshFailureEventListener).toHaveBeenCalledTimes(1);
681 | });
682 | });
683 | });
684 |
--------------------------------------------------------------------------------
/lib/test/context.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, afterEach } from 'vitest';
2 | import { renderHook } from '@testing-library/react';
3 |
4 | import { createMockAuthClient } from './test-utils';
5 |
6 | import { createAuth } from '../src';
7 |
8 | afterEach(require('@testing-library/react').cleanup);
9 |
10 | describe('createAuth', () => {
11 | it('should return a new ReactAuth with initialized values', () => {
12 | const rcContext = createAuth(createMockAuthClient());
13 |
14 | expect(rcContext).toBeDefined();
15 | expect(rcContext.AuthProvider).toBeDefined();
16 | expect(rcContext.useAuthClient).toBeDefined();
17 | });
18 |
19 | describe('useAuthClient hook', () => {
20 | it('should throw error if used outside AuthProvider context', async () => {
21 | const consoleErrorFn = vi
22 | .spyOn(console, 'error')
23 | .mockImplementation(() => vi.fn());
24 |
25 | const { useAuthClient } = createAuth(createMockAuthClient());
26 |
27 | expect(() => {
28 | renderHook(() => useAuthClient());
29 | }).toThrow('useAuthClient hook should be used inside AuthProvider');
30 |
31 | consoleErrorFn.mockRestore();
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/lib/test/provider.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, it, expect, vi, afterEach } from 'vitest';
3 | import * as rtl from '@testing-library/react';
4 | import '@testing-library/jest-dom';
5 |
6 | import { createMockAuthClient, createChild, flushPromises } from './test-utils';
7 |
8 | import { createAuth } from '../src';
9 |
10 | afterEach(rtl.cleanup);
11 |
12 | describe('AuthProvider', () => {
13 | describe('on initialization', () => {
14 | it('should init AuthClient instance', async () => {
15 | const authClientStub = createMockAuthClient();
16 | const authClientInitSpy = vi
17 | .spyOn(authClientStub, 'onInit')
18 | .mockResolvedValue(null);
19 |
20 | const { AuthProvider } = createAuth(authClientStub);
21 |
22 | rtl.render(
23 |
24 |
25 |
26 | );
27 |
28 | await rtl.act(() => flushPromises());
29 |
30 | expect(authClientInitSpy).toHaveBeenCalledTimes(1);
31 | });
32 |
33 | it('should handle errors during init', async () => {
34 | const authClientStub = createMockAuthClient();
35 |
36 | const authClientInitSpy = vi
37 | .spyOn(authClientStub, 'onInit')
38 | .mockRejectedValue(new Error('Stub error'));
39 |
40 | const { AuthProvider } = createAuth(authClientStub);
41 |
42 | rtl.render(
43 |
44 |
45 |
46 | );
47 |
48 | await rtl.act(() => flushPromises());
49 |
50 | expect(authClientInitSpy).toHaveBeenCalledTimes(1);
51 | });
52 |
53 | it('should diplay LoadingComponent if provided', async () => {
54 | const authClientStub = createMockAuthClient();
55 |
56 | const { AuthProvider } = createAuth(authClientStub);
57 |
58 | const tester = rtl.render(
59 | Loading...
62 | }
63 | >
64 |
65 |
66 | );
67 |
68 | await rtl.act(() => flushPromises());
69 |
70 | expect(tester.getByTestId('LoadingComponent')).toBeVisible();
71 | expect(tester.getByTestId('LoadingComponent')).toHaveTextContent(
72 | 'Loading...'
73 | );
74 | });
75 |
76 | it('should diplay ErrorComponent if provided', async () => {
77 | const authClientStub = createMockAuthClient();
78 | vi
79 | .spyOn(authClientStub, 'onInit')
80 | .mockRejectedValue(new Error('Stub error'));
81 |
82 | const { AuthProvider } = createAuth(authClientStub);
83 |
84 | const tester = rtl.render(
85 | Error!}
87 | >
88 |
89 |
90 | );
91 |
92 | await rtl.act(() => flushPromises());
93 |
94 | expect(tester.getByTestId('ErrorComponent')).toBeVisible();
95 | expect(tester.getByTestId('ErrorComponent')).toHaveTextContent('Error!');
96 | });
97 | });
98 |
99 | it('should add the authClient instance to context', async () => {
100 | const authClientStub = createMockAuthClient();
101 |
102 | const { AuthProvider, useAuthClient } = createAuth(authClientStub);
103 |
104 | const Child = createChild(useAuthClient);
105 |
106 | const tester = rtl.render(
107 |
108 |
109 |
110 | );
111 |
112 | await rtl.act(() => flushPromises());
113 |
114 | expect(tester.getByTestId('authClient')).toHaveTextContent(
115 | 'authClient: present'
116 | );
117 | });
118 | });
119 |
--------------------------------------------------------------------------------
/lib/test/test-utils.tsx:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | import React from 'react';
4 |
5 | import { createAuth } from '../src';
6 | import type { AuthClient } from '../src';
7 |
8 | type MockTokens = {
9 | authToken: string;
10 | refreshToken: string;
11 | };
12 |
13 | type MockCredentials = {
14 | username: string;
15 | password: string;
16 | };
17 |
18 | class MockAuthClient implements AuthClient {
19 | onInit(): Promise {
20 | throw new Error('Method not implemented.');
21 | }
22 |
23 | onLogin(_credentials?: MockCredentials): Promise {
24 | throw new Error('Method not implemented.');
25 | }
26 |
27 | onRefresh(_minValidity?: number): Promise {
28 | throw new Error('Method not implemented.');
29 | }
30 |
31 | onLogout(): Promise {
32 | throw new Error('Method not implemented.');
33 | }
34 | }
35 |
36 | export const createMockAuthClient = () => {
37 | return new MockAuthClient();
38 | };
39 |
40 | export const createMockAuthClientWithHooks = (hooks: Record) => {
41 | // console.log('hooks', hooks);
42 |
43 | class MockAuthClientWithHooks extends MockAuthClient {
44 | onPostInit(): Promise {
45 | hooks['onPostInit']?.();
46 | return Promise.resolve();
47 | }
48 |
49 | onPreLogin(): Promise {
50 | hooks['onPreLogin']?.();
51 | return Promise.resolve();
52 | }
53 |
54 | onLogin(_credentials?: MockCredentials): Promise {
55 | throw new Error('Method not implemented.');
56 | }
57 |
58 | onPostLogin(isSuccess: boolean): Promise {
59 | hooks['onPostLogin']?.(isSuccess);
60 | return Promise.resolve();
61 | }
62 |
63 | onPreRefresh(): Promise {
64 | hooks['onPreRefresh']?.();
65 | return Promise.resolve();
66 | }
67 |
68 | onRefresh(_minValidity?: number): Promise {
69 | throw new Error('Method not implemented.');
70 | }
71 |
72 | onPostRefresh(isSuccess: boolean): Promise {
73 | hooks['onPostRefresh']?.(isSuccess);
74 | return Promise.resolve();
75 | }
76 |
77 | onPreLogout(): Promise {
78 | hooks['onPreLogout']?.();
79 | return Promise.resolve();
80 | }
81 |
82 | onLogout(): Promise {
83 | throw new Error('Method not implemented.');
84 | }
85 |
86 | onPostLogout(isSuccess: boolean): Promise {
87 | hooks['onPostLogout']?.(isSuccess);
88 | return Promise.resolve();
89 | }
90 | }
91 |
92 | return new MockAuthClientWithHooks();
93 | };
94 |
95 | export const createChild = (useAuthClientHook: ReturnType["useAuthClient"]) => {
96 | return function () {
97 | const authClient = useAuthClientHook();
98 | return (
99 |
100 | authClient: {!!authClient ? 'present' : 'absent'}
101 |
102 | );
103 | };
104 | };
105 |
106 | export const flushPromises = () => new Promise(process.nextTick);
107 |
--------------------------------------------------------------------------------
/lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "target": "ES6",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "allowSyntheticDefaultImports": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "removeComments": false,
12 | "outDir": "./dist",
13 | "rootDir": "./src",
14 | "jsx": "react-jsx"
15 | },
16 | "include": [
17 | "src/**/*"
18 | ],
19 | "exclude": [
20 | "node_modules",
21 | "**/*.test.ts"
22 | ]
23 | }
--------------------------------------------------------------------------------
/lib/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import react from "@vitejs/plugin-react";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: "jsdom",
8 | globals: true,
9 | include: ["**/*.{test,spec}.{js,jsx,ts,tsx}"],
10 | coverage: {
11 | reporter: ["clover", "lcov", "html"],
12 | include: ["src/**/*.{js,jsx,ts,tsx}"],
13 | exclude: ["**/*.d.ts"],
14 | },
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-auth",
3 | "description": "Simplify your Auth flow within React apps",
4 | "author": "ForWarD Software (https://forwardsoftware.solutions/)",
5 | "license": "MIT",
6 | "keywords": [
7 | "react-auth",
8 | "react",
9 | "auth",
10 | "authentication"
11 | ],
12 | "homepage": "https://github.com/forwardsoftware/react-auth#readme",
13 | "bugs": "https://github.com/forwardsoftware/react-auth/issues",
14 | "repository": "https://github.com/forwardsoftware/react-auth",
15 | "scripts": {},
16 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
17 | "devDependencies": {
18 | "npm-run-all2": "8.0.4",
19 | "rimraf": "6.0.1",
20 | "tslib": "2.8.1",
21 | "typescript": "5.8.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | catalogs:
8 | default:
9 | '@types/react':
10 | specifier: ^19.1.3
11 | version: 19.1.6
12 | '@types/react-dom':
13 | specifier: ^19.1.4
14 | version: 19.1.5
15 | '@vitejs/plugin-react':
16 | specifier: ^4.4.1
17 | version: 4.5.1
18 | react:
19 | specifier: ^19.1.0
20 | version: 19.1.0
21 | react-dom:
22 | specifier: ^19.1.0
23 | version: 19.1.0
24 | typescript:
25 | specifier: ^5.8.3
26 | version: 5.8.3
27 | vite:
28 | specifier: ^6.3.5
29 | version: 6.3.5
30 |
31 | importers:
32 |
33 | .:
34 | devDependencies:
35 | npm-run-all2:
36 | specifier: 8.0.4
37 | version: 8.0.4
38 | rimraf:
39 | specifier: 6.0.1
40 | version: 6.0.1
41 | tslib:
42 | specifier: 2.8.1
43 | version: 2.8.1
44 | typescript:
45 | specifier: 5.8.3
46 | version: 5.8.3
47 |
48 | examples/base:
49 | dependencies:
50 | '@forward-software/react-auth':
51 | specifier: workspace:^
52 | version: link:../../lib
53 | react:
54 | specifier: 'catalog:'
55 | version: 19.1.0
56 | react-dom:
57 | specifier: 'catalog:'
58 | version: 19.1.0(react@19.1.0)
59 | devDependencies:
60 | '@types/react':
61 | specifier: 'catalog:'
62 | version: 19.1.6
63 | '@types/react-dom':
64 | specifier: 'catalog:'
65 | version: 19.1.5(@types/react@19.1.6)
66 | '@vitejs/plugin-react':
67 | specifier: 'catalog:'
68 | version: 4.5.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))
69 | typescript:
70 | specifier: 'catalog:'
71 | version: 5.8.3
72 | vite:
73 | specifier: 'catalog:'
74 | version: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
75 |
76 | examples/refresh-token:
77 | dependencies:
78 | '@forward-software/react-auth':
79 | specifier: workspace:^
80 | version: link:../../lib
81 | axios:
82 | specifier: 1.9.0
83 | version: 1.9.0
84 | jwt-check-expiry:
85 | specifier: ^1.0.10
86 | version: 1.0.10
87 | react:
88 | specifier: 'catalog:'
89 | version: 19.1.0
90 | react-dom:
91 | specifier: 'catalog:'
92 | version: 19.1.0(react@19.1.0)
93 | devDependencies:
94 | '@types/react':
95 | specifier: 'catalog:'
96 | version: 19.1.6
97 | '@types/react-dom':
98 | specifier: 'catalog:'
99 | version: 19.1.5(@types/react@19.1.6)
100 | '@vitejs/plugin-react':
101 | specifier: 'catalog:'
102 | version: 4.5.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))
103 | typescript:
104 | specifier: 'catalog:'
105 | version: 5.8.3
106 | vite:
107 | specifier: 'catalog:'
108 | version: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
109 |
110 | examples/reqres:
111 | dependencies:
112 | '@forward-software/react-auth':
113 | specifier: workspace:^
114 | version: link:../../lib
115 | axios:
116 | specifier: ^1.9.0
117 | version: 1.9.0
118 | react:
119 | specifier: 'catalog:'
120 | version: 19.1.0
121 | react-dom:
122 | specifier: 'catalog:'
123 | version: 19.1.0(react@19.1.0)
124 | devDependencies:
125 | '@types/react':
126 | specifier: 'catalog:'
127 | version: 19.1.6
128 | '@types/react-dom':
129 | specifier: 'catalog:'
130 | version: 19.1.5(@types/react@19.1.6)
131 | '@vitejs/plugin-react':
132 | specifier: 'catalog:'
133 | version: 4.5.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))
134 | typescript:
135 | specifier: 'catalog:'
136 | version: 5.8.3
137 | vite:
138 | specifier: 'catalog:'
139 | version: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
140 |
141 | lib:
142 | dependencies:
143 | use-sync-external-store:
144 | specifier: ^1.5.0
145 | version: 1.5.0(react@19.1.0)
146 | devDependencies:
147 | '@testing-library/dom':
148 | specifier: ^10.0.0
149 | version: 10.4.0
150 | '@testing-library/jest-dom':
151 | specifier: ^6.6.3
152 | version: 6.6.3
153 | '@testing-library/react':
154 | specifier: ^16.3.0
155 | version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
156 | '@types/node':
157 | specifier: ^22.15.29
158 | version: 22.15.29
159 | '@types/react':
160 | specifier: 'catalog:'
161 | version: 19.1.6
162 | '@types/react-dom':
163 | specifier: 'catalog:'
164 | version: 19.1.5(@types/react@19.1.6)
165 | '@types/use-sync-external-store':
166 | specifier: ^1.5.0
167 | version: 1.5.0
168 | '@vitejs/plugin-react':
169 | specifier: 'catalog:'
170 | version: 4.5.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))
171 | jsdom:
172 | specifier: ^26.1.0
173 | version: 26.1.0
174 | react:
175 | specifier: 'catalog:'
176 | version: 19.1.0
177 | react-dom:
178 | specifier: 'catalog:'
179 | version: 19.1.0(react@19.1.0)
180 | vite:
181 | specifier: 'catalog:'
182 | version: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
183 | vitest:
184 | specifier: ^3.1.2
185 | version: 3.2.1(@types/node@22.15.29)(jsdom@26.1.0)(lightningcss@1.29.3)(terser@5.39.0)
186 |
187 | packages:
188 |
189 | '@adobe/css-tools@4.4.2':
190 | resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==}
191 |
192 | '@ampproject/remapping@2.3.0':
193 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
194 | engines: {node: '>=6.0.0'}
195 |
196 | '@asamuzakjp/css-color@3.1.4':
197 | resolution: {integrity: sha512-SeuBV4rnjpFNjI8HSgKUwteuFdkHwkboq31HWzznuqgySQir+jSTczoWVVL4jvOjKjuH80fMDG0Fvg1Sb+OJsA==}
198 |
199 | '@babel/code-frame@7.26.2':
200 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
201 | engines: {node: '>=6.9.0'}
202 |
203 | '@babel/compat-data@7.26.8':
204 | resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==}
205 | engines: {node: '>=6.9.0'}
206 |
207 | '@babel/core@7.26.10':
208 | resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==}
209 | engines: {node: '>=6.9.0'}
210 |
211 | '@babel/generator@7.27.0':
212 | resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==}
213 | engines: {node: '>=6.9.0'}
214 |
215 | '@babel/helper-compilation-targets@7.27.0':
216 | resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==}
217 | engines: {node: '>=6.9.0'}
218 |
219 | '@babel/helper-module-imports@7.25.9':
220 | resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
221 | engines: {node: '>=6.9.0'}
222 |
223 | '@babel/helper-module-transforms@7.26.0':
224 | resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
225 | engines: {node: '>=6.9.0'}
226 | peerDependencies:
227 | '@babel/core': ^7.0.0
228 |
229 | '@babel/helper-plugin-utils@7.26.5':
230 | resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
231 | engines: {node: '>=6.9.0'}
232 |
233 | '@babel/helper-string-parser@7.25.9':
234 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
235 | engines: {node: '>=6.9.0'}
236 |
237 | '@babel/helper-validator-identifier@7.25.9':
238 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
239 | engines: {node: '>=6.9.0'}
240 |
241 | '@babel/helper-validator-option@7.25.9':
242 | resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
243 | engines: {node: '>=6.9.0'}
244 |
245 | '@babel/helpers@7.27.0':
246 | resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==}
247 | engines: {node: '>=6.9.0'}
248 |
249 | '@babel/parser@7.27.0':
250 | resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==}
251 | engines: {node: '>=6.0.0'}
252 | hasBin: true
253 |
254 | '@babel/plugin-transform-react-jsx-self@7.25.9':
255 | resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==}
256 | engines: {node: '>=6.9.0'}
257 | peerDependencies:
258 | '@babel/core': ^7.0.0-0
259 |
260 | '@babel/plugin-transform-react-jsx-source@7.25.9':
261 | resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==}
262 | engines: {node: '>=6.9.0'}
263 | peerDependencies:
264 | '@babel/core': ^7.0.0-0
265 |
266 | '@babel/runtime@7.27.0':
267 | resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
268 | engines: {node: '>=6.9.0'}
269 |
270 | '@babel/template@7.27.0':
271 | resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
272 | engines: {node: '>=6.9.0'}
273 |
274 | '@babel/traverse@7.27.0':
275 | resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==}
276 | engines: {node: '>=6.9.0'}
277 |
278 | '@babel/types@7.27.0':
279 | resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
280 | engines: {node: '>=6.9.0'}
281 |
282 | '@csstools/color-helpers@5.0.2':
283 | resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
284 | engines: {node: '>=18'}
285 |
286 | '@csstools/css-calc@2.1.3':
287 | resolution: {integrity: sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==}
288 | engines: {node: '>=18'}
289 | peerDependencies:
290 | '@csstools/css-parser-algorithms': ^3.0.4
291 | '@csstools/css-tokenizer': ^3.0.3
292 |
293 | '@csstools/css-color-parser@3.0.9':
294 | resolution: {integrity: sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==}
295 | engines: {node: '>=18'}
296 | peerDependencies:
297 | '@csstools/css-parser-algorithms': ^3.0.4
298 | '@csstools/css-tokenizer': ^3.0.3
299 |
300 | '@csstools/css-parser-algorithms@3.0.4':
301 | resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
302 | engines: {node: '>=18'}
303 | peerDependencies:
304 | '@csstools/css-tokenizer': ^3.0.3
305 |
306 | '@csstools/css-tokenizer@3.0.3':
307 | resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
308 | engines: {node: '>=18'}
309 |
310 | '@esbuild/aix-ppc64@0.25.3':
311 | resolution: {integrity: sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==}
312 | engines: {node: '>=18'}
313 | cpu: [ppc64]
314 | os: [aix]
315 |
316 | '@esbuild/android-arm64@0.25.3':
317 | resolution: {integrity: sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==}
318 | engines: {node: '>=18'}
319 | cpu: [arm64]
320 | os: [android]
321 |
322 | '@esbuild/android-arm@0.25.3':
323 | resolution: {integrity: sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==}
324 | engines: {node: '>=18'}
325 | cpu: [arm]
326 | os: [android]
327 |
328 | '@esbuild/android-x64@0.25.3':
329 | resolution: {integrity: sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==}
330 | engines: {node: '>=18'}
331 | cpu: [x64]
332 | os: [android]
333 |
334 | '@esbuild/darwin-arm64@0.25.3':
335 | resolution: {integrity: sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==}
336 | engines: {node: '>=18'}
337 | cpu: [arm64]
338 | os: [darwin]
339 |
340 | '@esbuild/darwin-x64@0.25.3':
341 | resolution: {integrity: sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==}
342 | engines: {node: '>=18'}
343 | cpu: [x64]
344 | os: [darwin]
345 |
346 | '@esbuild/freebsd-arm64@0.25.3':
347 | resolution: {integrity: sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==}
348 | engines: {node: '>=18'}
349 | cpu: [arm64]
350 | os: [freebsd]
351 |
352 | '@esbuild/freebsd-x64@0.25.3':
353 | resolution: {integrity: sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==}
354 | engines: {node: '>=18'}
355 | cpu: [x64]
356 | os: [freebsd]
357 |
358 | '@esbuild/linux-arm64@0.25.3':
359 | resolution: {integrity: sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==}
360 | engines: {node: '>=18'}
361 | cpu: [arm64]
362 | os: [linux]
363 |
364 | '@esbuild/linux-arm@0.25.3':
365 | resolution: {integrity: sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==}
366 | engines: {node: '>=18'}
367 | cpu: [arm]
368 | os: [linux]
369 |
370 | '@esbuild/linux-ia32@0.25.3':
371 | resolution: {integrity: sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==}
372 | engines: {node: '>=18'}
373 | cpu: [ia32]
374 | os: [linux]
375 |
376 | '@esbuild/linux-loong64@0.25.3':
377 | resolution: {integrity: sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==}
378 | engines: {node: '>=18'}
379 | cpu: [loong64]
380 | os: [linux]
381 |
382 | '@esbuild/linux-mips64el@0.25.3':
383 | resolution: {integrity: sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==}
384 | engines: {node: '>=18'}
385 | cpu: [mips64el]
386 | os: [linux]
387 |
388 | '@esbuild/linux-ppc64@0.25.3':
389 | resolution: {integrity: sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==}
390 | engines: {node: '>=18'}
391 | cpu: [ppc64]
392 | os: [linux]
393 |
394 | '@esbuild/linux-riscv64@0.25.3':
395 | resolution: {integrity: sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==}
396 | engines: {node: '>=18'}
397 | cpu: [riscv64]
398 | os: [linux]
399 |
400 | '@esbuild/linux-s390x@0.25.3':
401 | resolution: {integrity: sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==}
402 | engines: {node: '>=18'}
403 | cpu: [s390x]
404 | os: [linux]
405 |
406 | '@esbuild/linux-x64@0.25.3':
407 | resolution: {integrity: sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==}
408 | engines: {node: '>=18'}
409 | cpu: [x64]
410 | os: [linux]
411 |
412 | '@esbuild/netbsd-arm64@0.25.3':
413 | resolution: {integrity: sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==}
414 | engines: {node: '>=18'}
415 | cpu: [arm64]
416 | os: [netbsd]
417 |
418 | '@esbuild/netbsd-x64@0.25.3':
419 | resolution: {integrity: sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==}
420 | engines: {node: '>=18'}
421 | cpu: [x64]
422 | os: [netbsd]
423 |
424 | '@esbuild/openbsd-arm64@0.25.3':
425 | resolution: {integrity: sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==}
426 | engines: {node: '>=18'}
427 | cpu: [arm64]
428 | os: [openbsd]
429 |
430 | '@esbuild/openbsd-x64@0.25.3':
431 | resolution: {integrity: sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==}
432 | engines: {node: '>=18'}
433 | cpu: [x64]
434 | os: [openbsd]
435 |
436 | '@esbuild/sunos-x64@0.25.3':
437 | resolution: {integrity: sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==}
438 | engines: {node: '>=18'}
439 | cpu: [x64]
440 | os: [sunos]
441 |
442 | '@esbuild/win32-arm64@0.25.3':
443 | resolution: {integrity: sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==}
444 | engines: {node: '>=18'}
445 | cpu: [arm64]
446 | os: [win32]
447 |
448 | '@esbuild/win32-ia32@0.25.3':
449 | resolution: {integrity: sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==}
450 | engines: {node: '>=18'}
451 | cpu: [ia32]
452 | os: [win32]
453 |
454 | '@esbuild/win32-x64@0.25.3':
455 | resolution: {integrity: sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==}
456 | engines: {node: '>=18'}
457 | cpu: [x64]
458 | os: [win32]
459 |
460 | '@isaacs/cliui@8.0.2':
461 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
462 | engines: {node: '>=12'}
463 |
464 | '@jridgewell/gen-mapping@0.3.8':
465 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
466 | engines: {node: '>=6.0.0'}
467 |
468 | '@jridgewell/resolve-uri@3.1.2':
469 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
470 | engines: {node: '>=6.0.0'}
471 |
472 | '@jridgewell/set-array@1.2.1':
473 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
474 | engines: {node: '>=6.0.0'}
475 |
476 | '@jridgewell/source-map@0.3.6':
477 | resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
478 |
479 | '@jridgewell/sourcemap-codec@1.5.0':
480 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
481 |
482 | '@jridgewell/trace-mapping@0.3.25':
483 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
484 |
485 | '@rolldown/pluginutils@1.0.0-beta.9':
486 | resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==}
487 |
488 | '@rollup/rollup-android-arm-eabi@4.40.0':
489 | resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==}
490 | cpu: [arm]
491 | os: [android]
492 |
493 | '@rollup/rollup-android-arm64@4.40.0':
494 | resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==}
495 | cpu: [arm64]
496 | os: [android]
497 |
498 | '@rollup/rollup-darwin-arm64@4.40.0':
499 | resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==}
500 | cpu: [arm64]
501 | os: [darwin]
502 |
503 | '@rollup/rollup-darwin-x64@4.40.0':
504 | resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==}
505 | cpu: [x64]
506 | os: [darwin]
507 |
508 | '@rollup/rollup-freebsd-arm64@4.40.0':
509 | resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==}
510 | cpu: [arm64]
511 | os: [freebsd]
512 |
513 | '@rollup/rollup-freebsd-x64@4.40.0':
514 | resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==}
515 | cpu: [x64]
516 | os: [freebsd]
517 |
518 | '@rollup/rollup-linux-arm-gnueabihf@4.40.0':
519 | resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==}
520 | cpu: [arm]
521 | os: [linux]
522 |
523 | '@rollup/rollup-linux-arm-musleabihf@4.40.0':
524 | resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==}
525 | cpu: [arm]
526 | os: [linux]
527 |
528 | '@rollup/rollup-linux-arm64-gnu@4.40.0':
529 | resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==}
530 | cpu: [arm64]
531 | os: [linux]
532 |
533 | '@rollup/rollup-linux-arm64-musl@4.40.0':
534 | resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==}
535 | cpu: [arm64]
536 | os: [linux]
537 |
538 | '@rollup/rollup-linux-loongarch64-gnu@4.40.0':
539 | resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==}
540 | cpu: [loong64]
541 | os: [linux]
542 |
543 | '@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
544 | resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==}
545 | cpu: [ppc64]
546 | os: [linux]
547 |
548 | '@rollup/rollup-linux-riscv64-gnu@4.40.0':
549 | resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==}
550 | cpu: [riscv64]
551 | os: [linux]
552 |
553 | '@rollup/rollup-linux-riscv64-musl@4.40.0':
554 | resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==}
555 | cpu: [riscv64]
556 | os: [linux]
557 |
558 | '@rollup/rollup-linux-s390x-gnu@4.40.0':
559 | resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==}
560 | cpu: [s390x]
561 | os: [linux]
562 |
563 | '@rollup/rollup-linux-x64-gnu@4.40.0':
564 | resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==}
565 | cpu: [x64]
566 | os: [linux]
567 |
568 | '@rollup/rollup-linux-x64-musl@4.40.0':
569 | resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==}
570 | cpu: [x64]
571 | os: [linux]
572 |
573 | '@rollup/rollup-win32-arm64-msvc@4.40.0':
574 | resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==}
575 | cpu: [arm64]
576 | os: [win32]
577 |
578 | '@rollup/rollup-win32-ia32-msvc@4.40.0':
579 | resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==}
580 | cpu: [ia32]
581 | os: [win32]
582 |
583 | '@rollup/rollup-win32-x64-msvc@4.40.0':
584 | resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==}
585 | cpu: [x64]
586 | os: [win32]
587 |
588 | '@testing-library/dom@10.4.0':
589 | resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
590 | engines: {node: '>=18'}
591 |
592 | '@testing-library/jest-dom@6.6.3':
593 | resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==}
594 | engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
595 |
596 | '@testing-library/react@16.3.0':
597 | resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==}
598 | engines: {node: '>=18'}
599 | peerDependencies:
600 | '@testing-library/dom': ^10.0.0
601 | '@types/react': ^18.0.0 || ^19.0.0
602 | '@types/react-dom': ^18.0.0 || ^19.0.0
603 | react: ^18.0.0 || ^19.0.0
604 | react-dom: ^18.0.0 || ^19.0.0
605 | peerDependenciesMeta:
606 | '@types/react':
607 | optional: true
608 | '@types/react-dom':
609 | optional: true
610 |
611 | '@types/aria-query@5.0.4':
612 | resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
613 |
614 | '@types/babel__core@7.20.5':
615 | resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
616 |
617 | '@types/babel__generator@7.27.0':
618 | resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
619 |
620 | '@types/babel__template@7.4.4':
621 | resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
622 |
623 | '@types/babel__traverse@7.20.7':
624 | resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
625 |
626 | '@types/chai@5.2.2':
627 | resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
628 |
629 | '@types/deep-eql@4.0.2':
630 | resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
631 |
632 | '@types/estree@1.0.7':
633 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
634 |
635 | '@types/node@22.15.29':
636 | resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
637 |
638 | '@types/react-dom@19.1.5':
639 | resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==}
640 | peerDependencies:
641 | '@types/react': ^19.0.0
642 |
643 | '@types/react@19.1.6':
644 | resolution: {integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==}
645 |
646 | '@types/use-sync-external-store@1.5.0':
647 | resolution: {integrity: sha512-5dyB8nLC/qogMrlCizZnYWQTA4lnb/v+It+sqNl5YnSRAPMlIqY/X0Xn+gZw8vOL+TgTTr28VEbn3uf8fUtAkw==}
648 |
649 | '@vitejs/plugin-react@4.5.1':
650 | resolution: {integrity: sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==}
651 | engines: {node: ^14.18.0 || >=16.0.0}
652 | peerDependencies:
653 | vite: ^4.2.0 || ^5.0.0 || ^6.0.0
654 |
655 | '@vitest/expect@3.2.1':
656 | resolution: {integrity: sha512-FqS/BnDOzV6+IpxrTg5GQRyLOCtcJqkwMwcS8qGCI2IyRVDwPAtutztaf1CjtPHlZlWtl1yUPCd7HM0cNiDOYw==}
657 |
658 | '@vitest/mocker@3.2.1':
659 | resolution: {integrity: sha512-OXxMJnx1lkB+Vl65Re5BrsZEHc90s5NMjD23ZQ9NlU7f7nZiETGoX4NeKZSmsKjseuMq2uOYXdLOeoM0pJU+qw==}
660 | peerDependencies:
661 | msw: ^2.4.9
662 | vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
663 | peerDependenciesMeta:
664 | msw:
665 | optional: true
666 | vite:
667 | optional: true
668 |
669 | '@vitest/pretty-format@3.2.1':
670 | resolution: {integrity: sha512-xBh1X2GPlOGBupp6E1RcUQWIxw0w/hRLd3XyBS6H+dMdKTAqHDNsIR2AnJwPA3yYe9DFy3VUKTe3VRTrAiQ01g==}
671 |
672 | '@vitest/runner@3.2.1':
673 | resolution: {integrity: sha512-kygXhNTu/wkMYbwYpS3z/9tBe0O8qpdBuC3dD/AW9sWa0LE/DAZEjnHtWA9sIad7lpD4nFW1yQ+zN7mEKNH3yA==}
674 |
675 | '@vitest/snapshot@3.2.1':
676 | resolution: {integrity: sha512-5xko/ZpW2Yc65NVK9Gpfg2y4BFvcF+At7yRT5AHUpTg9JvZ4xZoyuRY4ASlmNcBZjMslV08VRLDrBOmUe2YX3g==}
677 |
678 | '@vitest/spy@3.2.1':
679 | resolution: {integrity: sha512-Nbfib34Z2rfcJGSetMxjDCznn4pCYPZOtQYox2kzebIJcgH75yheIKd5QYSFmR8DIZf2M8fwOm66qSDIfRFFfQ==}
680 |
681 | '@vitest/utils@3.2.1':
682 | resolution: {integrity: sha512-KkHlGhePEKZSub5ViknBcN5KEF+u7dSUr9NW8QsVICusUojrgrOnnY3DEWWO877ax2Pyopuk2qHmt+gkNKnBVw==}
683 |
684 | acorn@8.14.1:
685 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
686 | engines: {node: '>=0.4.0'}
687 | hasBin: true
688 |
689 | agent-base@7.1.3:
690 | resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
691 | engines: {node: '>= 14'}
692 |
693 | ansi-regex@5.0.1:
694 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
695 | engines: {node: '>=8'}
696 |
697 | ansi-regex@6.1.0:
698 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
699 | engines: {node: '>=12'}
700 |
701 | ansi-styles@4.3.0:
702 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
703 | engines: {node: '>=8'}
704 |
705 | ansi-styles@5.2.0:
706 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
707 | engines: {node: '>=10'}
708 |
709 | ansi-styles@6.2.1:
710 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
711 | engines: {node: '>=12'}
712 |
713 | aria-query@5.3.0:
714 | resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
715 |
716 | assertion-error@2.0.1:
717 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
718 | engines: {node: '>=12'}
719 |
720 | asynckit@0.4.0:
721 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
722 |
723 | axios@1.9.0:
724 | resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
725 |
726 | balanced-match@1.0.2:
727 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
728 |
729 | brace-expansion@2.0.1:
730 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
731 |
732 | browserslist@4.24.4:
733 | resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
734 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
735 | hasBin: true
736 |
737 | buffer-from@1.1.2:
738 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
739 |
740 | cac@6.7.14:
741 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
742 | engines: {node: '>=8'}
743 |
744 | call-bind-apply-helpers@1.0.2:
745 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
746 | engines: {node: '>= 0.4'}
747 |
748 | caniuse-lite@1.0.30001715:
749 | resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==}
750 |
751 | chai@5.2.0:
752 | resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
753 | engines: {node: '>=12'}
754 |
755 | chalk@3.0.0:
756 | resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
757 | engines: {node: '>=8'}
758 |
759 | chalk@4.1.2:
760 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
761 | engines: {node: '>=10'}
762 |
763 | check-error@2.1.1:
764 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
765 | engines: {node: '>= 16'}
766 |
767 | color-convert@2.0.1:
768 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
769 | engines: {node: '>=7.0.0'}
770 |
771 | color-name@1.1.4:
772 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
773 |
774 | combined-stream@1.0.8:
775 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
776 | engines: {node: '>= 0.8'}
777 |
778 | commander@2.20.3:
779 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
780 |
781 | convert-source-map@2.0.0:
782 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
783 |
784 | cross-spawn@7.0.6:
785 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
786 | engines: {node: '>= 8'}
787 |
788 | css.escape@1.5.1:
789 | resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
790 |
791 | cssstyle@4.3.1:
792 | resolution: {integrity: sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==}
793 | engines: {node: '>=18'}
794 |
795 | csstype@3.1.3:
796 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
797 |
798 | data-urls@5.0.0:
799 | resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
800 | engines: {node: '>=18'}
801 |
802 | debug@4.4.0:
803 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
804 | engines: {node: '>=6.0'}
805 | peerDependencies:
806 | supports-color: '*'
807 | peerDependenciesMeta:
808 | supports-color:
809 | optional: true
810 |
811 | debug@4.4.1:
812 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
813 | engines: {node: '>=6.0'}
814 | peerDependencies:
815 | supports-color: '*'
816 | peerDependenciesMeta:
817 | supports-color:
818 | optional: true
819 |
820 | decimal.js@10.5.0:
821 | resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
822 |
823 | deep-eql@5.0.2:
824 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
825 | engines: {node: '>=6'}
826 |
827 | delayed-stream@1.0.0:
828 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
829 | engines: {node: '>=0.4.0'}
830 |
831 | dequal@2.0.3:
832 | resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
833 | engines: {node: '>=6'}
834 |
835 | detect-libc@2.0.4:
836 | resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
837 | engines: {node: '>=8'}
838 |
839 | dom-accessibility-api@0.5.16:
840 | resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
841 |
842 | dom-accessibility-api@0.6.3:
843 | resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
844 |
845 | dunder-proto@1.0.1:
846 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
847 | engines: {node: '>= 0.4'}
848 |
849 | eastasianwidth@0.2.0:
850 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
851 |
852 | electron-to-chromium@1.5.140:
853 | resolution: {integrity: sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==}
854 |
855 | emoji-regex@8.0.0:
856 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
857 |
858 | emoji-regex@9.2.2:
859 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
860 |
861 | entities@6.0.0:
862 | resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
863 | engines: {node: '>=0.12'}
864 |
865 | es-define-property@1.0.1:
866 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
867 | engines: {node: '>= 0.4'}
868 |
869 | es-errors@1.3.0:
870 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
871 | engines: {node: '>= 0.4'}
872 |
873 | es-module-lexer@1.7.0:
874 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
875 |
876 | es-object-atoms@1.1.1:
877 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
878 | engines: {node: '>= 0.4'}
879 |
880 | es-set-tostringtag@2.1.0:
881 | resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
882 | engines: {node: '>= 0.4'}
883 |
884 | esbuild@0.25.3:
885 | resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==}
886 | engines: {node: '>=18'}
887 | hasBin: true
888 |
889 | escalade@3.2.0:
890 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
891 | engines: {node: '>=6'}
892 |
893 | estree-walker@3.0.3:
894 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
895 |
896 | expect-type@1.2.1:
897 | resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
898 | engines: {node: '>=12.0.0'}
899 |
900 | fdir@6.4.4:
901 | resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
902 | peerDependencies:
903 | picomatch: ^3 || ^4
904 | peerDependenciesMeta:
905 | picomatch:
906 | optional: true
907 |
908 | fdir@6.4.5:
909 | resolution: {integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==}
910 | peerDependencies:
911 | picomatch: ^3 || ^4
912 | peerDependenciesMeta:
913 | picomatch:
914 | optional: true
915 |
916 | follow-redirects@1.15.9:
917 | resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
918 | engines: {node: '>=4.0'}
919 | peerDependencies:
920 | debug: '*'
921 | peerDependenciesMeta:
922 | debug:
923 | optional: true
924 |
925 | foreground-child@3.3.1:
926 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
927 | engines: {node: '>=14'}
928 |
929 | form-data@4.0.2:
930 | resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
931 | engines: {node: '>= 6'}
932 |
933 | fsevents@2.3.3:
934 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
935 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
936 | os: [darwin]
937 |
938 | function-bind@1.1.2:
939 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
940 |
941 | gensync@1.0.0-beta.2:
942 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
943 | engines: {node: '>=6.9.0'}
944 |
945 | get-intrinsic@1.3.0:
946 | resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
947 | engines: {node: '>= 0.4'}
948 |
949 | get-proto@1.0.1:
950 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
951 | engines: {node: '>= 0.4'}
952 |
953 | glob@11.0.2:
954 | resolution: {integrity: sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==}
955 | engines: {node: 20 || >=22}
956 | hasBin: true
957 |
958 | globals@11.12.0:
959 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
960 | engines: {node: '>=4'}
961 |
962 | gopd@1.2.0:
963 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
964 | engines: {node: '>= 0.4'}
965 |
966 | has-flag@4.0.0:
967 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
968 | engines: {node: '>=8'}
969 |
970 | has-symbols@1.1.0:
971 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
972 | engines: {node: '>= 0.4'}
973 |
974 | has-tostringtag@1.0.2:
975 | resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
976 | engines: {node: '>= 0.4'}
977 |
978 | hasown@2.0.2:
979 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
980 | engines: {node: '>= 0.4'}
981 |
982 | html-encoding-sniffer@4.0.0:
983 | resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
984 | engines: {node: '>=18'}
985 |
986 | http-proxy-agent@7.0.2:
987 | resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
988 | engines: {node: '>= 14'}
989 |
990 | https-proxy-agent@7.0.6:
991 | resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
992 | engines: {node: '>= 14'}
993 |
994 | iconv-lite@0.6.3:
995 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
996 | engines: {node: '>=0.10.0'}
997 |
998 | indent-string@4.0.0:
999 | resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
1000 | engines: {node: '>=8'}
1001 |
1002 | is-fullwidth-code-point@3.0.0:
1003 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
1004 | engines: {node: '>=8'}
1005 |
1006 | is-potential-custom-element-name@1.0.1:
1007 | resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
1008 |
1009 | isexe@2.0.0:
1010 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1011 |
1012 | isexe@3.1.1:
1013 | resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
1014 | engines: {node: '>=16'}
1015 |
1016 | jackspeak@4.1.0:
1017 | resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
1018 | engines: {node: 20 || >=22}
1019 |
1020 | js-tokens@4.0.0:
1021 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
1022 |
1023 | jsdom@26.1.0:
1024 | resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
1025 | engines: {node: '>=18'}
1026 | peerDependencies:
1027 | canvas: ^3.0.0
1028 | peerDependenciesMeta:
1029 | canvas:
1030 | optional: true
1031 |
1032 | jsesc@3.1.0:
1033 | resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
1034 | engines: {node: '>=6'}
1035 | hasBin: true
1036 |
1037 | json-parse-even-better-errors@4.0.0:
1038 | resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==}
1039 | engines: {node: ^18.17.0 || >=20.5.0}
1040 |
1041 | json5@2.2.3:
1042 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
1043 | engines: {node: '>=6'}
1044 | hasBin: true
1045 |
1046 | jwt-check-expiry@1.0.10:
1047 | resolution: {integrity: sha512-DaqnmxMu9WSmCnCiL6mblrSxNwG5K7F8xbP9FVXhDlttglbxfluLRIzUqQE8W0mr5SU246HFMJEln/RXjWZkNQ==}
1048 |
1049 | lightningcss-darwin-arm64@1.29.3:
1050 | resolution: {integrity: sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==}
1051 | engines: {node: '>= 12.0.0'}
1052 | cpu: [arm64]
1053 | os: [darwin]
1054 |
1055 | lightningcss-darwin-x64@1.29.3:
1056 | resolution: {integrity: sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==}
1057 | engines: {node: '>= 12.0.0'}
1058 | cpu: [x64]
1059 | os: [darwin]
1060 |
1061 | lightningcss-freebsd-x64@1.29.3:
1062 | resolution: {integrity: sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==}
1063 | engines: {node: '>= 12.0.0'}
1064 | cpu: [x64]
1065 | os: [freebsd]
1066 |
1067 | lightningcss-linux-arm-gnueabihf@1.29.3:
1068 | resolution: {integrity: sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==}
1069 | engines: {node: '>= 12.0.0'}
1070 | cpu: [arm]
1071 | os: [linux]
1072 |
1073 | lightningcss-linux-arm64-gnu@1.29.3:
1074 | resolution: {integrity: sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==}
1075 | engines: {node: '>= 12.0.0'}
1076 | cpu: [arm64]
1077 | os: [linux]
1078 |
1079 | lightningcss-linux-arm64-musl@1.29.3:
1080 | resolution: {integrity: sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==}
1081 | engines: {node: '>= 12.0.0'}
1082 | cpu: [arm64]
1083 | os: [linux]
1084 |
1085 | lightningcss-linux-x64-gnu@1.29.3:
1086 | resolution: {integrity: sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==}
1087 | engines: {node: '>= 12.0.0'}
1088 | cpu: [x64]
1089 | os: [linux]
1090 |
1091 | lightningcss-linux-x64-musl@1.29.3:
1092 | resolution: {integrity: sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==}
1093 | engines: {node: '>= 12.0.0'}
1094 | cpu: [x64]
1095 | os: [linux]
1096 |
1097 | lightningcss-win32-arm64-msvc@1.29.3:
1098 | resolution: {integrity: sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==}
1099 | engines: {node: '>= 12.0.0'}
1100 | cpu: [arm64]
1101 | os: [win32]
1102 |
1103 | lightningcss-win32-x64-msvc@1.29.3:
1104 | resolution: {integrity: sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==}
1105 | engines: {node: '>= 12.0.0'}
1106 | cpu: [x64]
1107 | os: [win32]
1108 |
1109 | lightningcss@1.29.3:
1110 | resolution: {integrity: sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==}
1111 | engines: {node: '>= 12.0.0'}
1112 |
1113 | lodash@4.17.21:
1114 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
1115 |
1116 | loupe@3.1.3:
1117 | resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
1118 |
1119 | lru-cache@10.4.3:
1120 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
1121 |
1122 | lru-cache@11.1.0:
1123 | resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
1124 | engines: {node: 20 || >=22}
1125 |
1126 | lru-cache@5.1.1:
1127 | resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
1128 |
1129 | lz-string@1.5.0:
1130 | resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
1131 | hasBin: true
1132 |
1133 | magic-string@0.30.17:
1134 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
1135 |
1136 | math-intrinsics@1.1.0:
1137 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
1138 | engines: {node: '>= 0.4'}
1139 |
1140 | memorystream@0.3.1:
1141 | resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
1142 | engines: {node: '>= 0.10.0'}
1143 |
1144 | mime-db@1.52.0:
1145 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
1146 | engines: {node: '>= 0.6'}
1147 |
1148 | mime-types@2.1.35:
1149 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
1150 | engines: {node: '>= 0.6'}
1151 |
1152 | min-indent@1.0.1:
1153 | resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
1154 | engines: {node: '>=4'}
1155 |
1156 | minimatch@10.0.1:
1157 | resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
1158 | engines: {node: 20 || >=22}
1159 |
1160 | minipass@7.1.2:
1161 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
1162 | engines: {node: '>=16 || 14 >=14.17'}
1163 |
1164 | ms@2.1.3:
1165 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1166 |
1167 | nanoid@3.3.11:
1168 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
1169 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1170 | hasBin: true
1171 |
1172 | node-releases@2.0.19:
1173 | resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
1174 |
1175 | npm-normalize-package-bin@4.0.0:
1176 | resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==}
1177 | engines: {node: ^18.17.0 || >=20.5.0}
1178 |
1179 | npm-run-all2@8.0.4:
1180 | resolution: {integrity: sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==}
1181 | engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'}
1182 | hasBin: true
1183 |
1184 | nwsapi@2.2.20:
1185 | resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
1186 |
1187 | package-json-from-dist@1.0.1:
1188 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
1189 |
1190 | parse5@7.3.0:
1191 | resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
1192 |
1193 | path-key@3.1.1:
1194 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1195 | engines: {node: '>=8'}
1196 |
1197 | path-scurry@2.0.0:
1198 | resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
1199 | engines: {node: 20 || >=22}
1200 |
1201 | pathe@2.0.3:
1202 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
1203 |
1204 | pathval@2.0.0:
1205 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
1206 | engines: {node: '>= 14.16'}
1207 |
1208 | picocolors@1.1.1:
1209 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1210 |
1211 | picomatch@4.0.2:
1212 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
1213 | engines: {node: '>=12'}
1214 |
1215 | pidtree@0.6.0:
1216 | resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
1217 | engines: {node: '>=0.10'}
1218 | hasBin: true
1219 |
1220 | postcss@8.5.3:
1221 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
1222 | engines: {node: ^10 || ^12 || >=14}
1223 |
1224 | pretty-format@27.5.1:
1225 | resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
1226 | engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
1227 |
1228 | proxy-from-env@1.1.0:
1229 | resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
1230 |
1231 | punycode@2.3.1:
1232 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
1233 | engines: {node: '>=6'}
1234 |
1235 | react-dom@19.1.0:
1236 | resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
1237 | peerDependencies:
1238 | react: ^19.1.0
1239 |
1240 | react-is@17.0.2:
1241 | resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
1242 |
1243 | react-refresh@0.17.0:
1244 | resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
1245 | engines: {node: '>=0.10.0'}
1246 |
1247 | react@19.1.0:
1248 | resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
1249 | engines: {node: '>=0.10.0'}
1250 |
1251 | read-package-json-fast@4.0.0:
1252 | resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
1253 | engines: {node: ^18.17.0 || >=20.5.0}
1254 |
1255 | redent@3.0.0:
1256 | resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
1257 | engines: {node: '>=8'}
1258 |
1259 | regenerator-runtime@0.14.1:
1260 | resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
1261 |
1262 | rimraf@6.0.1:
1263 | resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
1264 | engines: {node: 20 || >=22}
1265 | hasBin: true
1266 |
1267 | rollup@4.40.0:
1268 | resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==}
1269 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1270 | hasBin: true
1271 |
1272 | rrweb-cssom@0.8.0:
1273 | resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
1274 |
1275 | safer-buffer@2.1.2:
1276 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
1277 |
1278 | saxes@6.0.0:
1279 | resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
1280 | engines: {node: '>=v12.22.7'}
1281 |
1282 | scheduler@0.26.0:
1283 | resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
1284 |
1285 | semver@6.3.1:
1286 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
1287 | hasBin: true
1288 |
1289 | shebang-command@2.0.0:
1290 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
1291 | engines: {node: '>=8'}
1292 |
1293 | shebang-regex@3.0.0:
1294 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1295 | engines: {node: '>=8'}
1296 |
1297 | shell-quote@1.8.2:
1298 | resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
1299 | engines: {node: '>= 0.4'}
1300 |
1301 | siginfo@2.0.0:
1302 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
1303 |
1304 | signal-exit@4.1.0:
1305 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
1306 | engines: {node: '>=14'}
1307 |
1308 | source-map-js@1.2.1:
1309 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1310 | engines: {node: '>=0.10.0'}
1311 |
1312 | source-map-support@0.5.21:
1313 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
1314 |
1315 | source-map@0.6.1:
1316 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
1317 | engines: {node: '>=0.10.0'}
1318 |
1319 | stackback@0.0.2:
1320 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
1321 |
1322 | std-env@3.9.0:
1323 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
1324 |
1325 | string-width@4.2.3:
1326 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
1327 | engines: {node: '>=8'}
1328 |
1329 | string-width@5.1.2:
1330 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
1331 | engines: {node: '>=12'}
1332 |
1333 | strip-ansi@6.0.1:
1334 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
1335 | engines: {node: '>=8'}
1336 |
1337 | strip-ansi@7.1.0:
1338 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
1339 | engines: {node: '>=12'}
1340 |
1341 | strip-indent@3.0.0:
1342 | resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
1343 | engines: {node: '>=8'}
1344 |
1345 | supports-color@7.2.0:
1346 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
1347 | engines: {node: '>=8'}
1348 |
1349 | symbol-tree@3.2.4:
1350 | resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
1351 |
1352 | terser@5.39.0:
1353 | resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==}
1354 | engines: {node: '>=10'}
1355 | hasBin: true
1356 |
1357 | tinybench@2.9.0:
1358 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
1359 |
1360 | tinyexec@0.3.2:
1361 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
1362 |
1363 | tinyglobby@0.2.13:
1364 | resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
1365 | engines: {node: '>=12.0.0'}
1366 |
1367 | tinyglobby@0.2.14:
1368 | resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
1369 | engines: {node: '>=12.0.0'}
1370 |
1371 | tinypool@1.1.0:
1372 | resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==}
1373 | engines: {node: ^18.0.0 || >=20.0.0}
1374 |
1375 | tinyrainbow@2.0.0:
1376 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
1377 | engines: {node: '>=14.0.0'}
1378 |
1379 | tinyspy@4.0.3:
1380 | resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
1381 | engines: {node: '>=14.0.0'}
1382 |
1383 | tldts-core@6.1.86:
1384 | resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
1385 |
1386 | tldts@6.1.86:
1387 | resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
1388 | hasBin: true
1389 |
1390 | tough-cookie@5.1.2:
1391 | resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
1392 | engines: {node: '>=16'}
1393 |
1394 | tr46@5.1.1:
1395 | resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
1396 | engines: {node: '>=18'}
1397 |
1398 | tslib@2.8.1:
1399 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1400 |
1401 | typescript@5.8.3:
1402 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
1403 | engines: {node: '>=14.17'}
1404 | hasBin: true
1405 |
1406 | undici-types@6.21.0:
1407 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1408 |
1409 | update-browserslist-db@1.1.3:
1410 | resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
1411 | hasBin: true
1412 | peerDependencies:
1413 | browserslist: '>= 4.21.0'
1414 |
1415 | use-sync-external-store@1.5.0:
1416 | resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
1417 | peerDependencies:
1418 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
1419 |
1420 | vite-node@3.2.1:
1421 | resolution: {integrity: sha512-V4EyKQPxquurNJPtQJRZo8hKOoKNBRIhxcDbQFPFig0JdoWcUhwRgK8yoCXXrfYVPKS6XwirGHPszLnR8FbjCA==}
1422 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1423 | hasBin: true
1424 |
1425 | vite@6.3.5:
1426 | resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
1427 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1428 | hasBin: true
1429 | peerDependencies:
1430 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
1431 | jiti: '>=1.21.0'
1432 | less: '*'
1433 | lightningcss: ^1.21.0
1434 | sass: '*'
1435 | sass-embedded: '*'
1436 | stylus: '*'
1437 | sugarss: '*'
1438 | terser: ^5.16.0
1439 | tsx: ^4.8.1
1440 | yaml: ^2.4.2
1441 | peerDependenciesMeta:
1442 | '@types/node':
1443 | optional: true
1444 | jiti:
1445 | optional: true
1446 | less:
1447 | optional: true
1448 | lightningcss:
1449 | optional: true
1450 | sass:
1451 | optional: true
1452 | sass-embedded:
1453 | optional: true
1454 | stylus:
1455 | optional: true
1456 | sugarss:
1457 | optional: true
1458 | terser:
1459 | optional: true
1460 | tsx:
1461 | optional: true
1462 | yaml:
1463 | optional: true
1464 |
1465 | vitest@3.2.1:
1466 | resolution: {integrity: sha512-VZ40MBnlE1/V5uTgdqY3DmjUgZtIzsYq758JGlyQrv5syIsaYcabkfPkEuWML49Ph0D/SoqpVFd0dyVTr551oA==}
1467 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1468 | hasBin: true
1469 | peerDependencies:
1470 | '@edge-runtime/vm': '*'
1471 | '@types/debug': ^4.1.12
1472 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
1473 | '@vitest/browser': 3.2.1
1474 | '@vitest/ui': 3.2.1
1475 | happy-dom: '*'
1476 | jsdom: '*'
1477 | peerDependenciesMeta:
1478 | '@edge-runtime/vm':
1479 | optional: true
1480 | '@types/debug':
1481 | optional: true
1482 | '@types/node':
1483 | optional: true
1484 | '@vitest/browser':
1485 | optional: true
1486 | '@vitest/ui':
1487 | optional: true
1488 | happy-dom:
1489 | optional: true
1490 | jsdom:
1491 | optional: true
1492 |
1493 | w3c-xmlserializer@5.0.0:
1494 | resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
1495 | engines: {node: '>=18'}
1496 |
1497 | webidl-conversions@7.0.0:
1498 | resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
1499 | engines: {node: '>=12'}
1500 |
1501 | whatwg-encoding@3.1.1:
1502 | resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
1503 | engines: {node: '>=18'}
1504 |
1505 | whatwg-mimetype@4.0.0:
1506 | resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
1507 | engines: {node: '>=18'}
1508 |
1509 | whatwg-url@14.2.0:
1510 | resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
1511 | engines: {node: '>=18'}
1512 |
1513 | which@2.0.2:
1514 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
1515 | engines: {node: '>= 8'}
1516 | hasBin: true
1517 |
1518 | which@5.0.0:
1519 | resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
1520 | engines: {node: ^18.17.0 || >=20.5.0}
1521 | hasBin: true
1522 |
1523 | why-is-node-running@2.3.0:
1524 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
1525 | engines: {node: '>=8'}
1526 | hasBin: true
1527 |
1528 | wrap-ansi@7.0.0:
1529 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
1530 | engines: {node: '>=10'}
1531 |
1532 | wrap-ansi@8.1.0:
1533 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
1534 | engines: {node: '>=12'}
1535 |
1536 | ws@8.18.1:
1537 | resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
1538 | engines: {node: '>=10.0.0'}
1539 | peerDependencies:
1540 | bufferutil: ^4.0.1
1541 | utf-8-validate: '>=5.0.2'
1542 | peerDependenciesMeta:
1543 | bufferutil:
1544 | optional: true
1545 | utf-8-validate:
1546 | optional: true
1547 |
1548 | xml-name-validator@5.0.0:
1549 | resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
1550 | engines: {node: '>=18'}
1551 |
1552 | xmlchars@2.2.0:
1553 | resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
1554 |
1555 | yallist@3.1.1:
1556 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
1557 |
1558 | snapshots:
1559 |
1560 | '@adobe/css-tools@4.4.2': {}
1561 |
1562 | '@ampproject/remapping@2.3.0':
1563 | dependencies:
1564 | '@jridgewell/gen-mapping': 0.3.8
1565 | '@jridgewell/trace-mapping': 0.3.25
1566 |
1567 | '@asamuzakjp/css-color@3.1.4':
1568 | dependencies:
1569 | '@csstools/css-calc': 2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
1570 | '@csstools/css-color-parser': 3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
1571 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
1572 | '@csstools/css-tokenizer': 3.0.3
1573 | lru-cache: 10.4.3
1574 |
1575 | '@babel/code-frame@7.26.2':
1576 | dependencies:
1577 | '@babel/helper-validator-identifier': 7.25.9
1578 | js-tokens: 4.0.0
1579 | picocolors: 1.1.1
1580 |
1581 | '@babel/compat-data@7.26.8': {}
1582 |
1583 | '@babel/core@7.26.10':
1584 | dependencies:
1585 | '@ampproject/remapping': 2.3.0
1586 | '@babel/code-frame': 7.26.2
1587 | '@babel/generator': 7.27.0
1588 | '@babel/helper-compilation-targets': 7.27.0
1589 | '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10)
1590 | '@babel/helpers': 7.27.0
1591 | '@babel/parser': 7.27.0
1592 | '@babel/template': 7.27.0
1593 | '@babel/traverse': 7.27.0
1594 | '@babel/types': 7.27.0
1595 | convert-source-map: 2.0.0
1596 | debug: 4.4.0
1597 | gensync: 1.0.0-beta.2
1598 | json5: 2.2.3
1599 | semver: 6.3.1
1600 | transitivePeerDependencies:
1601 | - supports-color
1602 |
1603 | '@babel/generator@7.27.0':
1604 | dependencies:
1605 | '@babel/parser': 7.27.0
1606 | '@babel/types': 7.27.0
1607 | '@jridgewell/gen-mapping': 0.3.8
1608 | '@jridgewell/trace-mapping': 0.3.25
1609 | jsesc: 3.1.0
1610 |
1611 | '@babel/helper-compilation-targets@7.27.0':
1612 | dependencies:
1613 | '@babel/compat-data': 7.26.8
1614 | '@babel/helper-validator-option': 7.25.9
1615 | browserslist: 4.24.4
1616 | lru-cache: 5.1.1
1617 | semver: 6.3.1
1618 |
1619 | '@babel/helper-module-imports@7.25.9':
1620 | dependencies:
1621 | '@babel/traverse': 7.27.0
1622 | '@babel/types': 7.27.0
1623 | transitivePeerDependencies:
1624 | - supports-color
1625 |
1626 | '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)':
1627 | dependencies:
1628 | '@babel/core': 7.26.10
1629 | '@babel/helper-module-imports': 7.25.9
1630 | '@babel/helper-validator-identifier': 7.25.9
1631 | '@babel/traverse': 7.27.0
1632 | transitivePeerDependencies:
1633 | - supports-color
1634 |
1635 | '@babel/helper-plugin-utils@7.26.5': {}
1636 |
1637 | '@babel/helper-string-parser@7.25.9': {}
1638 |
1639 | '@babel/helper-validator-identifier@7.25.9': {}
1640 |
1641 | '@babel/helper-validator-option@7.25.9': {}
1642 |
1643 | '@babel/helpers@7.27.0':
1644 | dependencies:
1645 | '@babel/template': 7.27.0
1646 | '@babel/types': 7.27.0
1647 |
1648 | '@babel/parser@7.27.0':
1649 | dependencies:
1650 | '@babel/types': 7.27.0
1651 |
1652 | '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)':
1653 | dependencies:
1654 | '@babel/core': 7.26.10
1655 | '@babel/helper-plugin-utils': 7.26.5
1656 |
1657 | '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)':
1658 | dependencies:
1659 | '@babel/core': 7.26.10
1660 | '@babel/helper-plugin-utils': 7.26.5
1661 |
1662 | '@babel/runtime@7.27.0':
1663 | dependencies:
1664 | regenerator-runtime: 0.14.1
1665 |
1666 | '@babel/template@7.27.0':
1667 | dependencies:
1668 | '@babel/code-frame': 7.26.2
1669 | '@babel/parser': 7.27.0
1670 | '@babel/types': 7.27.0
1671 |
1672 | '@babel/traverse@7.27.0':
1673 | dependencies:
1674 | '@babel/code-frame': 7.26.2
1675 | '@babel/generator': 7.27.0
1676 | '@babel/parser': 7.27.0
1677 | '@babel/template': 7.27.0
1678 | '@babel/types': 7.27.0
1679 | debug: 4.4.0
1680 | globals: 11.12.0
1681 | transitivePeerDependencies:
1682 | - supports-color
1683 |
1684 | '@babel/types@7.27.0':
1685 | dependencies:
1686 | '@babel/helper-string-parser': 7.25.9
1687 | '@babel/helper-validator-identifier': 7.25.9
1688 |
1689 | '@csstools/color-helpers@5.0.2': {}
1690 |
1691 | '@csstools/css-calc@2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
1692 | dependencies:
1693 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
1694 | '@csstools/css-tokenizer': 3.0.3
1695 |
1696 | '@csstools/css-color-parser@3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
1697 | dependencies:
1698 | '@csstools/color-helpers': 5.0.2
1699 | '@csstools/css-calc': 2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
1700 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
1701 | '@csstools/css-tokenizer': 3.0.3
1702 |
1703 | '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
1704 | dependencies:
1705 | '@csstools/css-tokenizer': 3.0.3
1706 |
1707 | '@csstools/css-tokenizer@3.0.3': {}
1708 |
1709 | '@esbuild/aix-ppc64@0.25.3':
1710 | optional: true
1711 |
1712 | '@esbuild/android-arm64@0.25.3':
1713 | optional: true
1714 |
1715 | '@esbuild/android-arm@0.25.3':
1716 | optional: true
1717 |
1718 | '@esbuild/android-x64@0.25.3':
1719 | optional: true
1720 |
1721 | '@esbuild/darwin-arm64@0.25.3':
1722 | optional: true
1723 |
1724 | '@esbuild/darwin-x64@0.25.3':
1725 | optional: true
1726 |
1727 | '@esbuild/freebsd-arm64@0.25.3':
1728 | optional: true
1729 |
1730 | '@esbuild/freebsd-x64@0.25.3':
1731 | optional: true
1732 |
1733 | '@esbuild/linux-arm64@0.25.3':
1734 | optional: true
1735 |
1736 | '@esbuild/linux-arm@0.25.3':
1737 | optional: true
1738 |
1739 | '@esbuild/linux-ia32@0.25.3':
1740 | optional: true
1741 |
1742 | '@esbuild/linux-loong64@0.25.3':
1743 | optional: true
1744 |
1745 | '@esbuild/linux-mips64el@0.25.3':
1746 | optional: true
1747 |
1748 | '@esbuild/linux-ppc64@0.25.3':
1749 | optional: true
1750 |
1751 | '@esbuild/linux-riscv64@0.25.3':
1752 | optional: true
1753 |
1754 | '@esbuild/linux-s390x@0.25.3':
1755 | optional: true
1756 |
1757 | '@esbuild/linux-x64@0.25.3':
1758 | optional: true
1759 |
1760 | '@esbuild/netbsd-arm64@0.25.3':
1761 | optional: true
1762 |
1763 | '@esbuild/netbsd-x64@0.25.3':
1764 | optional: true
1765 |
1766 | '@esbuild/openbsd-arm64@0.25.3':
1767 | optional: true
1768 |
1769 | '@esbuild/openbsd-x64@0.25.3':
1770 | optional: true
1771 |
1772 | '@esbuild/sunos-x64@0.25.3':
1773 | optional: true
1774 |
1775 | '@esbuild/win32-arm64@0.25.3':
1776 | optional: true
1777 |
1778 | '@esbuild/win32-ia32@0.25.3':
1779 | optional: true
1780 |
1781 | '@esbuild/win32-x64@0.25.3':
1782 | optional: true
1783 |
1784 | '@isaacs/cliui@8.0.2':
1785 | dependencies:
1786 | string-width: 5.1.2
1787 | string-width-cjs: string-width@4.2.3
1788 | strip-ansi: 7.1.0
1789 | strip-ansi-cjs: strip-ansi@6.0.1
1790 | wrap-ansi: 8.1.0
1791 | wrap-ansi-cjs: wrap-ansi@7.0.0
1792 |
1793 | '@jridgewell/gen-mapping@0.3.8':
1794 | dependencies:
1795 | '@jridgewell/set-array': 1.2.1
1796 | '@jridgewell/sourcemap-codec': 1.5.0
1797 | '@jridgewell/trace-mapping': 0.3.25
1798 |
1799 | '@jridgewell/resolve-uri@3.1.2': {}
1800 |
1801 | '@jridgewell/set-array@1.2.1': {}
1802 |
1803 | '@jridgewell/source-map@0.3.6':
1804 | dependencies:
1805 | '@jridgewell/gen-mapping': 0.3.8
1806 | '@jridgewell/trace-mapping': 0.3.25
1807 | optional: true
1808 |
1809 | '@jridgewell/sourcemap-codec@1.5.0': {}
1810 |
1811 | '@jridgewell/trace-mapping@0.3.25':
1812 | dependencies:
1813 | '@jridgewell/resolve-uri': 3.1.2
1814 | '@jridgewell/sourcemap-codec': 1.5.0
1815 |
1816 | '@rolldown/pluginutils@1.0.0-beta.9': {}
1817 |
1818 | '@rollup/rollup-android-arm-eabi@4.40.0':
1819 | optional: true
1820 |
1821 | '@rollup/rollup-android-arm64@4.40.0':
1822 | optional: true
1823 |
1824 | '@rollup/rollup-darwin-arm64@4.40.0':
1825 | optional: true
1826 |
1827 | '@rollup/rollup-darwin-x64@4.40.0':
1828 | optional: true
1829 |
1830 | '@rollup/rollup-freebsd-arm64@4.40.0':
1831 | optional: true
1832 |
1833 | '@rollup/rollup-freebsd-x64@4.40.0':
1834 | optional: true
1835 |
1836 | '@rollup/rollup-linux-arm-gnueabihf@4.40.0':
1837 | optional: true
1838 |
1839 | '@rollup/rollup-linux-arm-musleabihf@4.40.0':
1840 | optional: true
1841 |
1842 | '@rollup/rollup-linux-arm64-gnu@4.40.0':
1843 | optional: true
1844 |
1845 | '@rollup/rollup-linux-arm64-musl@4.40.0':
1846 | optional: true
1847 |
1848 | '@rollup/rollup-linux-loongarch64-gnu@4.40.0':
1849 | optional: true
1850 |
1851 | '@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
1852 | optional: true
1853 |
1854 | '@rollup/rollup-linux-riscv64-gnu@4.40.0':
1855 | optional: true
1856 |
1857 | '@rollup/rollup-linux-riscv64-musl@4.40.0':
1858 | optional: true
1859 |
1860 | '@rollup/rollup-linux-s390x-gnu@4.40.0':
1861 | optional: true
1862 |
1863 | '@rollup/rollup-linux-x64-gnu@4.40.0':
1864 | optional: true
1865 |
1866 | '@rollup/rollup-linux-x64-musl@4.40.0':
1867 | optional: true
1868 |
1869 | '@rollup/rollup-win32-arm64-msvc@4.40.0':
1870 | optional: true
1871 |
1872 | '@rollup/rollup-win32-ia32-msvc@4.40.0':
1873 | optional: true
1874 |
1875 | '@rollup/rollup-win32-x64-msvc@4.40.0':
1876 | optional: true
1877 |
1878 | '@testing-library/dom@10.4.0':
1879 | dependencies:
1880 | '@babel/code-frame': 7.26.2
1881 | '@babel/runtime': 7.27.0
1882 | '@types/aria-query': 5.0.4
1883 | aria-query: 5.3.0
1884 | chalk: 4.1.2
1885 | dom-accessibility-api: 0.5.16
1886 | lz-string: 1.5.0
1887 | pretty-format: 27.5.1
1888 |
1889 | '@testing-library/jest-dom@6.6.3':
1890 | dependencies:
1891 | '@adobe/css-tools': 4.4.2
1892 | aria-query: 5.3.0
1893 | chalk: 3.0.0
1894 | css.escape: 1.5.1
1895 | dom-accessibility-api: 0.6.3
1896 | lodash: 4.17.21
1897 | redent: 3.0.0
1898 |
1899 | '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
1900 | dependencies:
1901 | '@babel/runtime': 7.27.0
1902 | '@testing-library/dom': 10.4.0
1903 | react: 19.1.0
1904 | react-dom: 19.1.0(react@19.1.0)
1905 | optionalDependencies:
1906 | '@types/react': 19.1.6
1907 | '@types/react-dom': 19.1.5(@types/react@19.1.6)
1908 |
1909 | '@types/aria-query@5.0.4': {}
1910 |
1911 | '@types/babel__core@7.20.5':
1912 | dependencies:
1913 | '@babel/parser': 7.27.0
1914 | '@babel/types': 7.27.0
1915 | '@types/babel__generator': 7.27.0
1916 | '@types/babel__template': 7.4.4
1917 | '@types/babel__traverse': 7.20.7
1918 |
1919 | '@types/babel__generator@7.27.0':
1920 | dependencies:
1921 | '@babel/types': 7.27.0
1922 |
1923 | '@types/babel__template@7.4.4':
1924 | dependencies:
1925 | '@babel/parser': 7.27.0
1926 | '@babel/types': 7.27.0
1927 |
1928 | '@types/babel__traverse@7.20.7':
1929 | dependencies:
1930 | '@babel/types': 7.27.0
1931 |
1932 | '@types/chai@5.2.2':
1933 | dependencies:
1934 | '@types/deep-eql': 4.0.2
1935 |
1936 | '@types/deep-eql@4.0.2': {}
1937 |
1938 | '@types/estree@1.0.7': {}
1939 |
1940 | '@types/node@22.15.29':
1941 | dependencies:
1942 | undici-types: 6.21.0
1943 |
1944 | '@types/react-dom@19.1.5(@types/react@19.1.6)':
1945 | dependencies:
1946 | '@types/react': 19.1.6
1947 |
1948 | '@types/react@19.1.6':
1949 | dependencies:
1950 | csstype: 3.1.3
1951 |
1952 | '@types/use-sync-external-store@1.5.0': {}
1953 |
1954 | '@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))':
1955 | dependencies:
1956 | '@babel/core': 7.26.10
1957 | '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10)
1958 | '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10)
1959 | '@rolldown/pluginutils': 1.0.0-beta.9
1960 | '@types/babel__core': 7.20.5
1961 | react-refresh: 0.17.0
1962 | vite: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
1963 | transitivePeerDependencies:
1964 | - supports-color
1965 |
1966 | '@vitest/expect@3.2.1':
1967 | dependencies:
1968 | '@types/chai': 5.2.2
1969 | '@vitest/spy': 3.2.1
1970 | '@vitest/utils': 3.2.1
1971 | chai: 5.2.0
1972 | tinyrainbow: 2.0.0
1973 |
1974 | '@vitest/mocker@3.2.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))':
1975 | dependencies:
1976 | '@vitest/spy': 3.2.1
1977 | estree-walker: 3.0.3
1978 | magic-string: 0.30.17
1979 | optionalDependencies:
1980 | vite: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
1981 |
1982 | '@vitest/pretty-format@3.2.1':
1983 | dependencies:
1984 | tinyrainbow: 2.0.0
1985 |
1986 | '@vitest/runner@3.2.1':
1987 | dependencies:
1988 | '@vitest/utils': 3.2.1
1989 | pathe: 2.0.3
1990 |
1991 | '@vitest/snapshot@3.2.1':
1992 | dependencies:
1993 | '@vitest/pretty-format': 3.2.1
1994 | magic-string: 0.30.17
1995 | pathe: 2.0.3
1996 |
1997 | '@vitest/spy@3.2.1':
1998 | dependencies:
1999 | tinyspy: 4.0.3
2000 |
2001 | '@vitest/utils@3.2.1':
2002 | dependencies:
2003 | '@vitest/pretty-format': 3.2.1
2004 | loupe: 3.1.3
2005 | tinyrainbow: 2.0.0
2006 |
2007 | acorn@8.14.1:
2008 | optional: true
2009 |
2010 | agent-base@7.1.3: {}
2011 |
2012 | ansi-regex@5.0.1: {}
2013 |
2014 | ansi-regex@6.1.0: {}
2015 |
2016 | ansi-styles@4.3.0:
2017 | dependencies:
2018 | color-convert: 2.0.1
2019 |
2020 | ansi-styles@5.2.0: {}
2021 |
2022 | ansi-styles@6.2.1: {}
2023 |
2024 | aria-query@5.3.0:
2025 | dependencies:
2026 | dequal: 2.0.3
2027 |
2028 | assertion-error@2.0.1: {}
2029 |
2030 | asynckit@0.4.0: {}
2031 |
2032 | axios@1.9.0:
2033 | dependencies:
2034 | follow-redirects: 1.15.9
2035 | form-data: 4.0.2
2036 | proxy-from-env: 1.1.0
2037 | transitivePeerDependencies:
2038 | - debug
2039 |
2040 | balanced-match@1.0.2: {}
2041 |
2042 | brace-expansion@2.0.1:
2043 | dependencies:
2044 | balanced-match: 1.0.2
2045 |
2046 | browserslist@4.24.4:
2047 | dependencies:
2048 | caniuse-lite: 1.0.30001715
2049 | electron-to-chromium: 1.5.140
2050 | node-releases: 2.0.19
2051 | update-browserslist-db: 1.1.3(browserslist@4.24.4)
2052 |
2053 | buffer-from@1.1.2:
2054 | optional: true
2055 |
2056 | cac@6.7.14: {}
2057 |
2058 | call-bind-apply-helpers@1.0.2:
2059 | dependencies:
2060 | es-errors: 1.3.0
2061 | function-bind: 1.1.2
2062 |
2063 | caniuse-lite@1.0.30001715: {}
2064 |
2065 | chai@5.2.0:
2066 | dependencies:
2067 | assertion-error: 2.0.1
2068 | check-error: 2.1.1
2069 | deep-eql: 5.0.2
2070 | loupe: 3.1.3
2071 | pathval: 2.0.0
2072 |
2073 | chalk@3.0.0:
2074 | dependencies:
2075 | ansi-styles: 4.3.0
2076 | supports-color: 7.2.0
2077 |
2078 | chalk@4.1.2:
2079 | dependencies:
2080 | ansi-styles: 4.3.0
2081 | supports-color: 7.2.0
2082 |
2083 | check-error@2.1.1: {}
2084 |
2085 | color-convert@2.0.1:
2086 | dependencies:
2087 | color-name: 1.1.4
2088 |
2089 | color-name@1.1.4: {}
2090 |
2091 | combined-stream@1.0.8:
2092 | dependencies:
2093 | delayed-stream: 1.0.0
2094 |
2095 | commander@2.20.3:
2096 | optional: true
2097 |
2098 | convert-source-map@2.0.0: {}
2099 |
2100 | cross-spawn@7.0.6:
2101 | dependencies:
2102 | path-key: 3.1.1
2103 | shebang-command: 2.0.0
2104 | which: 2.0.2
2105 |
2106 | css.escape@1.5.1: {}
2107 |
2108 | cssstyle@4.3.1:
2109 | dependencies:
2110 | '@asamuzakjp/css-color': 3.1.4
2111 | rrweb-cssom: 0.8.0
2112 |
2113 | csstype@3.1.3: {}
2114 |
2115 | data-urls@5.0.0:
2116 | dependencies:
2117 | whatwg-mimetype: 4.0.0
2118 | whatwg-url: 14.2.0
2119 |
2120 | debug@4.4.0:
2121 | dependencies:
2122 | ms: 2.1.3
2123 |
2124 | debug@4.4.1:
2125 | dependencies:
2126 | ms: 2.1.3
2127 |
2128 | decimal.js@10.5.0: {}
2129 |
2130 | deep-eql@5.0.2: {}
2131 |
2132 | delayed-stream@1.0.0: {}
2133 |
2134 | dequal@2.0.3: {}
2135 |
2136 | detect-libc@2.0.4:
2137 | optional: true
2138 |
2139 | dom-accessibility-api@0.5.16: {}
2140 |
2141 | dom-accessibility-api@0.6.3: {}
2142 |
2143 | dunder-proto@1.0.1:
2144 | dependencies:
2145 | call-bind-apply-helpers: 1.0.2
2146 | es-errors: 1.3.0
2147 | gopd: 1.2.0
2148 |
2149 | eastasianwidth@0.2.0: {}
2150 |
2151 | electron-to-chromium@1.5.140: {}
2152 |
2153 | emoji-regex@8.0.0: {}
2154 |
2155 | emoji-regex@9.2.2: {}
2156 |
2157 | entities@6.0.0: {}
2158 |
2159 | es-define-property@1.0.1: {}
2160 |
2161 | es-errors@1.3.0: {}
2162 |
2163 | es-module-lexer@1.7.0: {}
2164 |
2165 | es-object-atoms@1.1.1:
2166 | dependencies:
2167 | es-errors: 1.3.0
2168 |
2169 | es-set-tostringtag@2.1.0:
2170 | dependencies:
2171 | es-errors: 1.3.0
2172 | get-intrinsic: 1.3.0
2173 | has-tostringtag: 1.0.2
2174 | hasown: 2.0.2
2175 |
2176 | esbuild@0.25.3:
2177 | optionalDependencies:
2178 | '@esbuild/aix-ppc64': 0.25.3
2179 | '@esbuild/android-arm': 0.25.3
2180 | '@esbuild/android-arm64': 0.25.3
2181 | '@esbuild/android-x64': 0.25.3
2182 | '@esbuild/darwin-arm64': 0.25.3
2183 | '@esbuild/darwin-x64': 0.25.3
2184 | '@esbuild/freebsd-arm64': 0.25.3
2185 | '@esbuild/freebsd-x64': 0.25.3
2186 | '@esbuild/linux-arm': 0.25.3
2187 | '@esbuild/linux-arm64': 0.25.3
2188 | '@esbuild/linux-ia32': 0.25.3
2189 | '@esbuild/linux-loong64': 0.25.3
2190 | '@esbuild/linux-mips64el': 0.25.3
2191 | '@esbuild/linux-ppc64': 0.25.3
2192 | '@esbuild/linux-riscv64': 0.25.3
2193 | '@esbuild/linux-s390x': 0.25.3
2194 | '@esbuild/linux-x64': 0.25.3
2195 | '@esbuild/netbsd-arm64': 0.25.3
2196 | '@esbuild/netbsd-x64': 0.25.3
2197 | '@esbuild/openbsd-arm64': 0.25.3
2198 | '@esbuild/openbsd-x64': 0.25.3
2199 | '@esbuild/sunos-x64': 0.25.3
2200 | '@esbuild/win32-arm64': 0.25.3
2201 | '@esbuild/win32-ia32': 0.25.3
2202 | '@esbuild/win32-x64': 0.25.3
2203 |
2204 | escalade@3.2.0: {}
2205 |
2206 | estree-walker@3.0.3:
2207 | dependencies:
2208 | '@types/estree': 1.0.7
2209 |
2210 | expect-type@1.2.1: {}
2211 |
2212 | fdir@6.4.4(picomatch@4.0.2):
2213 | optionalDependencies:
2214 | picomatch: 4.0.2
2215 |
2216 | fdir@6.4.5(picomatch@4.0.2):
2217 | optionalDependencies:
2218 | picomatch: 4.0.2
2219 |
2220 | follow-redirects@1.15.9: {}
2221 |
2222 | foreground-child@3.3.1:
2223 | dependencies:
2224 | cross-spawn: 7.0.6
2225 | signal-exit: 4.1.0
2226 |
2227 | form-data@4.0.2:
2228 | dependencies:
2229 | asynckit: 0.4.0
2230 | combined-stream: 1.0.8
2231 | es-set-tostringtag: 2.1.0
2232 | mime-types: 2.1.35
2233 |
2234 | fsevents@2.3.3:
2235 | optional: true
2236 |
2237 | function-bind@1.1.2: {}
2238 |
2239 | gensync@1.0.0-beta.2: {}
2240 |
2241 | get-intrinsic@1.3.0:
2242 | dependencies:
2243 | call-bind-apply-helpers: 1.0.2
2244 | es-define-property: 1.0.1
2245 | es-errors: 1.3.0
2246 | es-object-atoms: 1.1.1
2247 | function-bind: 1.1.2
2248 | get-proto: 1.0.1
2249 | gopd: 1.2.0
2250 | has-symbols: 1.1.0
2251 | hasown: 2.0.2
2252 | math-intrinsics: 1.1.0
2253 |
2254 | get-proto@1.0.1:
2255 | dependencies:
2256 | dunder-proto: 1.0.1
2257 | es-object-atoms: 1.1.1
2258 |
2259 | glob@11.0.2:
2260 | dependencies:
2261 | foreground-child: 3.3.1
2262 | jackspeak: 4.1.0
2263 | minimatch: 10.0.1
2264 | minipass: 7.1.2
2265 | package-json-from-dist: 1.0.1
2266 | path-scurry: 2.0.0
2267 |
2268 | globals@11.12.0: {}
2269 |
2270 | gopd@1.2.0: {}
2271 |
2272 | has-flag@4.0.0: {}
2273 |
2274 | has-symbols@1.1.0: {}
2275 |
2276 | has-tostringtag@1.0.2:
2277 | dependencies:
2278 | has-symbols: 1.1.0
2279 |
2280 | hasown@2.0.2:
2281 | dependencies:
2282 | function-bind: 1.1.2
2283 |
2284 | html-encoding-sniffer@4.0.0:
2285 | dependencies:
2286 | whatwg-encoding: 3.1.1
2287 |
2288 | http-proxy-agent@7.0.2:
2289 | dependencies:
2290 | agent-base: 7.1.3
2291 | debug: 4.4.0
2292 | transitivePeerDependencies:
2293 | - supports-color
2294 |
2295 | https-proxy-agent@7.0.6:
2296 | dependencies:
2297 | agent-base: 7.1.3
2298 | debug: 4.4.0
2299 | transitivePeerDependencies:
2300 | - supports-color
2301 |
2302 | iconv-lite@0.6.3:
2303 | dependencies:
2304 | safer-buffer: 2.1.2
2305 |
2306 | indent-string@4.0.0: {}
2307 |
2308 | is-fullwidth-code-point@3.0.0: {}
2309 |
2310 | is-potential-custom-element-name@1.0.1: {}
2311 |
2312 | isexe@2.0.0: {}
2313 |
2314 | isexe@3.1.1: {}
2315 |
2316 | jackspeak@4.1.0:
2317 | dependencies:
2318 | '@isaacs/cliui': 8.0.2
2319 |
2320 | js-tokens@4.0.0: {}
2321 |
2322 | jsdom@26.1.0:
2323 | dependencies:
2324 | cssstyle: 4.3.1
2325 | data-urls: 5.0.0
2326 | decimal.js: 10.5.0
2327 | html-encoding-sniffer: 4.0.0
2328 | http-proxy-agent: 7.0.2
2329 | https-proxy-agent: 7.0.6
2330 | is-potential-custom-element-name: 1.0.1
2331 | nwsapi: 2.2.20
2332 | parse5: 7.3.0
2333 | rrweb-cssom: 0.8.0
2334 | saxes: 6.0.0
2335 | symbol-tree: 3.2.4
2336 | tough-cookie: 5.1.2
2337 | w3c-xmlserializer: 5.0.0
2338 | webidl-conversions: 7.0.0
2339 | whatwg-encoding: 3.1.1
2340 | whatwg-mimetype: 4.0.0
2341 | whatwg-url: 14.2.0
2342 | ws: 8.18.1
2343 | xml-name-validator: 5.0.0
2344 | transitivePeerDependencies:
2345 | - bufferutil
2346 | - supports-color
2347 | - utf-8-validate
2348 |
2349 | jsesc@3.1.0: {}
2350 |
2351 | json-parse-even-better-errors@4.0.0: {}
2352 |
2353 | json5@2.2.3: {}
2354 |
2355 | jwt-check-expiry@1.0.10: {}
2356 |
2357 | lightningcss-darwin-arm64@1.29.3:
2358 | optional: true
2359 |
2360 | lightningcss-darwin-x64@1.29.3:
2361 | optional: true
2362 |
2363 | lightningcss-freebsd-x64@1.29.3:
2364 | optional: true
2365 |
2366 | lightningcss-linux-arm-gnueabihf@1.29.3:
2367 | optional: true
2368 |
2369 | lightningcss-linux-arm64-gnu@1.29.3:
2370 | optional: true
2371 |
2372 | lightningcss-linux-arm64-musl@1.29.3:
2373 | optional: true
2374 |
2375 | lightningcss-linux-x64-gnu@1.29.3:
2376 | optional: true
2377 |
2378 | lightningcss-linux-x64-musl@1.29.3:
2379 | optional: true
2380 |
2381 | lightningcss-win32-arm64-msvc@1.29.3:
2382 | optional: true
2383 |
2384 | lightningcss-win32-x64-msvc@1.29.3:
2385 | optional: true
2386 |
2387 | lightningcss@1.29.3:
2388 | dependencies:
2389 | detect-libc: 2.0.4
2390 | optionalDependencies:
2391 | lightningcss-darwin-arm64: 1.29.3
2392 | lightningcss-darwin-x64: 1.29.3
2393 | lightningcss-freebsd-x64: 1.29.3
2394 | lightningcss-linux-arm-gnueabihf: 1.29.3
2395 | lightningcss-linux-arm64-gnu: 1.29.3
2396 | lightningcss-linux-arm64-musl: 1.29.3
2397 | lightningcss-linux-x64-gnu: 1.29.3
2398 | lightningcss-linux-x64-musl: 1.29.3
2399 | lightningcss-win32-arm64-msvc: 1.29.3
2400 | lightningcss-win32-x64-msvc: 1.29.3
2401 | optional: true
2402 |
2403 | lodash@4.17.21: {}
2404 |
2405 | loupe@3.1.3: {}
2406 |
2407 | lru-cache@10.4.3: {}
2408 |
2409 | lru-cache@11.1.0: {}
2410 |
2411 | lru-cache@5.1.1:
2412 | dependencies:
2413 | yallist: 3.1.1
2414 |
2415 | lz-string@1.5.0: {}
2416 |
2417 | magic-string@0.30.17:
2418 | dependencies:
2419 | '@jridgewell/sourcemap-codec': 1.5.0
2420 |
2421 | math-intrinsics@1.1.0: {}
2422 |
2423 | memorystream@0.3.1: {}
2424 |
2425 | mime-db@1.52.0: {}
2426 |
2427 | mime-types@2.1.35:
2428 | dependencies:
2429 | mime-db: 1.52.0
2430 |
2431 | min-indent@1.0.1: {}
2432 |
2433 | minimatch@10.0.1:
2434 | dependencies:
2435 | brace-expansion: 2.0.1
2436 |
2437 | minipass@7.1.2: {}
2438 |
2439 | ms@2.1.3: {}
2440 |
2441 | nanoid@3.3.11: {}
2442 |
2443 | node-releases@2.0.19: {}
2444 |
2445 | npm-normalize-package-bin@4.0.0: {}
2446 |
2447 | npm-run-all2@8.0.4:
2448 | dependencies:
2449 | ansi-styles: 6.2.1
2450 | cross-spawn: 7.0.6
2451 | memorystream: 0.3.1
2452 | picomatch: 4.0.2
2453 | pidtree: 0.6.0
2454 | read-package-json-fast: 4.0.0
2455 | shell-quote: 1.8.2
2456 | which: 5.0.0
2457 |
2458 | nwsapi@2.2.20: {}
2459 |
2460 | package-json-from-dist@1.0.1: {}
2461 |
2462 | parse5@7.3.0:
2463 | dependencies:
2464 | entities: 6.0.0
2465 |
2466 | path-key@3.1.1: {}
2467 |
2468 | path-scurry@2.0.0:
2469 | dependencies:
2470 | lru-cache: 11.1.0
2471 | minipass: 7.1.2
2472 |
2473 | pathe@2.0.3: {}
2474 |
2475 | pathval@2.0.0: {}
2476 |
2477 | picocolors@1.1.1: {}
2478 |
2479 | picomatch@4.0.2: {}
2480 |
2481 | pidtree@0.6.0: {}
2482 |
2483 | postcss@8.5.3:
2484 | dependencies:
2485 | nanoid: 3.3.11
2486 | picocolors: 1.1.1
2487 | source-map-js: 1.2.1
2488 |
2489 | pretty-format@27.5.1:
2490 | dependencies:
2491 | ansi-regex: 5.0.1
2492 | ansi-styles: 5.2.0
2493 | react-is: 17.0.2
2494 |
2495 | proxy-from-env@1.1.0: {}
2496 |
2497 | punycode@2.3.1: {}
2498 |
2499 | react-dom@19.1.0(react@19.1.0):
2500 | dependencies:
2501 | react: 19.1.0
2502 | scheduler: 0.26.0
2503 |
2504 | react-is@17.0.2: {}
2505 |
2506 | react-refresh@0.17.0: {}
2507 |
2508 | react@19.1.0: {}
2509 |
2510 | read-package-json-fast@4.0.0:
2511 | dependencies:
2512 | json-parse-even-better-errors: 4.0.0
2513 | npm-normalize-package-bin: 4.0.0
2514 |
2515 | redent@3.0.0:
2516 | dependencies:
2517 | indent-string: 4.0.0
2518 | strip-indent: 3.0.0
2519 |
2520 | regenerator-runtime@0.14.1: {}
2521 |
2522 | rimraf@6.0.1:
2523 | dependencies:
2524 | glob: 11.0.2
2525 | package-json-from-dist: 1.0.1
2526 |
2527 | rollup@4.40.0:
2528 | dependencies:
2529 | '@types/estree': 1.0.7
2530 | optionalDependencies:
2531 | '@rollup/rollup-android-arm-eabi': 4.40.0
2532 | '@rollup/rollup-android-arm64': 4.40.0
2533 | '@rollup/rollup-darwin-arm64': 4.40.0
2534 | '@rollup/rollup-darwin-x64': 4.40.0
2535 | '@rollup/rollup-freebsd-arm64': 4.40.0
2536 | '@rollup/rollup-freebsd-x64': 4.40.0
2537 | '@rollup/rollup-linux-arm-gnueabihf': 4.40.0
2538 | '@rollup/rollup-linux-arm-musleabihf': 4.40.0
2539 | '@rollup/rollup-linux-arm64-gnu': 4.40.0
2540 | '@rollup/rollup-linux-arm64-musl': 4.40.0
2541 | '@rollup/rollup-linux-loongarch64-gnu': 4.40.0
2542 | '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0
2543 | '@rollup/rollup-linux-riscv64-gnu': 4.40.0
2544 | '@rollup/rollup-linux-riscv64-musl': 4.40.0
2545 | '@rollup/rollup-linux-s390x-gnu': 4.40.0
2546 | '@rollup/rollup-linux-x64-gnu': 4.40.0
2547 | '@rollup/rollup-linux-x64-musl': 4.40.0
2548 | '@rollup/rollup-win32-arm64-msvc': 4.40.0
2549 | '@rollup/rollup-win32-ia32-msvc': 4.40.0
2550 | '@rollup/rollup-win32-x64-msvc': 4.40.0
2551 | fsevents: 2.3.3
2552 |
2553 | rrweb-cssom@0.8.0: {}
2554 |
2555 | safer-buffer@2.1.2: {}
2556 |
2557 | saxes@6.0.0:
2558 | dependencies:
2559 | xmlchars: 2.2.0
2560 |
2561 | scheduler@0.26.0: {}
2562 |
2563 | semver@6.3.1: {}
2564 |
2565 | shebang-command@2.0.0:
2566 | dependencies:
2567 | shebang-regex: 3.0.0
2568 |
2569 | shebang-regex@3.0.0: {}
2570 |
2571 | shell-quote@1.8.2: {}
2572 |
2573 | siginfo@2.0.0: {}
2574 |
2575 | signal-exit@4.1.0: {}
2576 |
2577 | source-map-js@1.2.1: {}
2578 |
2579 | source-map-support@0.5.21:
2580 | dependencies:
2581 | buffer-from: 1.1.2
2582 | source-map: 0.6.1
2583 | optional: true
2584 |
2585 | source-map@0.6.1:
2586 | optional: true
2587 |
2588 | stackback@0.0.2: {}
2589 |
2590 | std-env@3.9.0: {}
2591 |
2592 | string-width@4.2.3:
2593 | dependencies:
2594 | emoji-regex: 8.0.0
2595 | is-fullwidth-code-point: 3.0.0
2596 | strip-ansi: 6.0.1
2597 |
2598 | string-width@5.1.2:
2599 | dependencies:
2600 | eastasianwidth: 0.2.0
2601 | emoji-regex: 9.2.2
2602 | strip-ansi: 7.1.0
2603 |
2604 | strip-ansi@6.0.1:
2605 | dependencies:
2606 | ansi-regex: 5.0.1
2607 |
2608 | strip-ansi@7.1.0:
2609 | dependencies:
2610 | ansi-regex: 6.1.0
2611 |
2612 | strip-indent@3.0.0:
2613 | dependencies:
2614 | min-indent: 1.0.1
2615 |
2616 | supports-color@7.2.0:
2617 | dependencies:
2618 | has-flag: 4.0.0
2619 |
2620 | symbol-tree@3.2.4: {}
2621 |
2622 | terser@5.39.0:
2623 | dependencies:
2624 | '@jridgewell/source-map': 0.3.6
2625 | acorn: 8.14.1
2626 | commander: 2.20.3
2627 | source-map-support: 0.5.21
2628 | optional: true
2629 |
2630 | tinybench@2.9.0: {}
2631 |
2632 | tinyexec@0.3.2: {}
2633 |
2634 | tinyglobby@0.2.13:
2635 | dependencies:
2636 | fdir: 6.4.4(picomatch@4.0.2)
2637 | picomatch: 4.0.2
2638 |
2639 | tinyglobby@0.2.14:
2640 | dependencies:
2641 | fdir: 6.4.5(picomatch@4.0.2)
2642 | picomatch: 4.0.2
2643 |
2644 | tinypool@1.1.0: {}
2645 |
2646 | tinyrainbow@2.0.0: {}
2647 |
2648 | tinyspy@4.0.3: {}
2649 |
2650 | tldts-core@6.1.86: {}
2651 |
2652 | tldts@6.1.86:
2653 | dependencies:
2654 | tldts-core: 6.1.86
2655 |
2656 | tough-cookie@5.1.2:
2657 | dependencies:
2658 | tldts: 6.1.86
2659 |
2660 | tr46@5.1.1:
2661 | dependencies:
2662 | punycode: 2.3.1
2663 |
2664 | tslib@2.8.1: {}
2665 |
2666 | typescript@5.8.3: {}
2667 |
2668 | undici-types@6.21.0: {}
2669 |
2670 | update-browserslist-db@1.1.3(browserslist@4.24.4):
2671 | dependencies:
2672 | browserslist: 4.24.4
2673 | escalade: 3.2.0
2674 | picocolors: 1.1.1
2675 |
2676 | use-sync-external-store@1.5.0(react@19.1.0):
2677 | dependencies:
2678 | react: 19.1.0
2679 |
2680 | vite-node@3.2.1(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0):
2681 | dependencies:
2682 | cac: 6.7.14
2683 | debug: 4.4.1
2684 | es-module-lexer: 1.7.0
2685 | pathe: 2.0.3
2686 | vite: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
2687 | transitivePeerDependencies:
2688 | - '@types/node'
2689 | - jiti
2690 | - less
2691 | - lightningcss
2692 | - sass
2693 | - sass-embedded
2694 | - stylus
2695 | - sugarss
2696 | - supports-color
2697 | - terser
2698 | - tsx
2699 | - yaml
2700 |
2701 | vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0):
2702 | dependencies:
2703 | esbuild: 0.25.3
2704 | fdir: 6.4.4(picomatch@4.0.2)
2705 | picomatch: 4.0.2
2706 | postcss: 8.5.3
2707 | rollup: 4.40.0
2708 | tinyglobby: 0.2.13
2709 | optionalDependencies:
2710 | '@types/node': 22.15.29
2711 | fsevents: 2.3.3
2712 | lightningcss: 1.29.3
2713 | terser: 5.39.0
2714 |
2715 | vitest@3.2.1(@types/node@22.15.29)(jsdom@26.1.0)(lightningcss@1.29.3)(terser@5.39.0):
2716 | dependencies:
2717 | '@types/chai': 5.2.2
2718 | '@vitest/expect': 3.2.1
2719 | '@vitest/mocker': 3.2.1(vite@6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0))
2720 | '@vitest/pretty-format': 3.2.1
2721 | '@vitest/runner': 3.2.1
2722 | '@vitest/snapshot': 3.2.1
2723 | '@vitest/spy': 3.2.1
2724 | '@vitest/utils': 3.2.1
2725 | chai: 5.2.0
2726 | debug: 4.4.1
2727 | expect-type: 1.2.1
2728 | magic-string: 0.30.17
2729 | pathe: 2.0.3
2730 | picomatch: 4.0.2
2731 | std-env: 3.9.0
2732 | tinybench: 2.9.0
2733 | tinyexec: 0.3.2
2734 | tinyglobby: 0.2.14
2735 | tinypool: 1.1.0
2736 | tinyrainbow: 2.0.0
2737 | vite: 6.3.5(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
2738 | vite-node: 3.2.1(@types/node@22.15.29)(lightningcss@1.29.3)(terser@5.39.0)
2739 | why-is-node-running: 2.3.0
2740 | optionalDependencies:
2741 | '@types/node': 22.15.29
2742 | jsdom: 26.1.0
2743 | transitivePeerDependencies:
2744 | - jiti
2745 | - less
2746 | - lightningcss
2747 | - msw
2748 | - sass
2749 | - sass-embedded
2750 | - stylus
2751 | - sugarss
2752 | - supports-color
2753 | - terser
2754 | - tsx
2755 | - yaml
2756 |
2757 | w3c-xmlserializer@5.0.0:
2758 | dependencies:
2759 | xml-name-validator: 5.0.0
2760 |
2761 | webidl-conversions@7.0.0: {}
2762 |
2763 | whatwg-encoding@3.1.1:
2764 | dependencies:
2765 | iconv-lite: 0.6.3
2766 |
2767 | whatwg-mimetype@4.0.0: {}
2768 |
2769 | whatwg-url@14.2.0:
2770 | dependencies:
2771 | tr46: 5.1.1
2772 | webidl-conversions: 7.0.0
2773 |
2774 | which@2.0.2:
2775 | dependencies:
2776 | isexe: 2.0.0
2777 |
2778 | which@5.0.0:
2779 | dependencies:
2780 | isexe: 3.1.1
2781 |
2782 | why-is-node-running@2.3.0:
2783 | dependencies:
2784 | siginfo: 2.0.0
2785 | stackback: 0.0.2
2786 |
2787 | wrap-ansi@7.0.0:
2788 | dependencies:
2789 | ansi-styles: 4.3.0
2790 | string-width: 4.2.3
2791 | strip-ansi: 6.0.1
2792 |
2793 | wrap-ansi@8.1.0:
2794 | dependencies:
2795 | ansi-styles: 6.2.1
2796 | string-width: 5.1.2
2797 | strip-ansi: 7.1.0
2798 |
2799 | ws@8.18.1: {}
2800 |
2801 | xml-name-validator@5.0.0: {}
2802 |
2803 | xmlchars@2.2.0: {}
2804 |
2805 | yallist@3.1.1: {}
2806 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - lib
3 | - examples/*
4 |
5 | catalog:
6 | "@types/react": "^19.1.3"
7 | "@types/react-dom": "^19.1.4"
8 | "@vitejs/plugin-react": "^4.4.1"
9 | "react": "^19.1.0"
10 | "react-dom": "^19.1.0"
11 | "typescript": "^5.8.3"
12 | "vite": "^6.3.5"
13 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3 | "release-type": "node",
4 | "separate-pull-requests": true,
5 | "always-update": true,
6 | "packages": {
7 | "lib": {
8 | "include-component-in-tag": false
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------