├── .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 | [![license](https://img.shields.io/github/license/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [![Build & Test](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml/badge.svg)](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [![Github Issues](https://img.shields.io/github/issues/forwardsoftware/react-auth.svg)](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 | 32 | 33 | 39 | 40 | 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 | 93 | 94 | 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 | 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 | 56 | 57 | 63 | 64 | 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 | [![license](https://img.shields.io/github/license/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [![Build & Test](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml/badge.svg)](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [![Github Issues](https://img.shields.io/github/issues/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/issues) 6 | 7 | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth)](https://npmjs.com/package/@forward-software/react-auth) [![NPM downloads](https://img.shields.io/npm/dm/@forward-software/react-auth.svg)](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 | } --------------------------------------------------------------------------------