├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── general-issue.md ├── pull_request_template.md └── workflows │ ├── lint-and-format.yml │ ├── publish-sdk-angular.yml │ ├── publish-sdk-react.yml │ ├── publish-sdk-vue.yml │ └── run-tests.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── README.md ├── e2e ├── pages │ └── common.page.ts └── tests │ ├── cookies.test.ts │ └── endpoints.test.ts ├── package.json ├── packages ├── core │ ├── package.json │ ├── src │ │ ├── CookieHelpers │ │ │ ├── CookieHelpers.test.ts │ │ │ ├── CookieHelpers.ts │ │ │ └── index.ts │ │ ├── RedirectHelper │ │ │ ├── RedirectHelper.ts │ │ │ └── index.ts │ │ ├── SDKConfig │ │ │ ├── SDKConfig.ts │ │ │ └── index.ts │ │ ├── SDKContext │ │ │ ├── SDKContext.ts │ │ │ └── index.ts │ │ ├── SDKCore │ │ │ ├── SDKCore.test.ts │ │ │ ├── SDKCore.ts │ │ │ └── index.ts │ │ ├── UrlHelper │ │ │ ├── UrlHelper.test.ts │ │ │ ├── UrlHelper.ts │ │ │ ├── UrlHelperTypes.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── testUtils │ │ │ ├── index.ts │ │ │ ├── mockLoggedIn.ts │ │ │ └── mockWindowLocation.ts │ ├── tsconfig.json │ └── vite.config.ts ├── lexicon │ ├── Readme.md │ ├── package.json │ ├── src │ │ ├── GUID │ │ │ ├── GUID.test.ts │ │ │ └── index.ts │ │ ├── Path │ │ │ ├── Path.test.ts │ │ │ └── index.ts │ │ ├── Tools │ │ │ └── index.ts │ │ ├── User │ │ │ └── index.ts │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.ts ├── sdk-angular │ ├── .eslintrc.json │ ├── CHANGES.md │ ├── README.md │ ├── angular.json │ ├── docs │ │ ├── .nojekyll │ │ ├── README.md │ │ ├── classes │ │ │ ├── FusionAuthAccountButtonComponent.md │ │ │ ├── FusionAuthLoginButtonComponent.md │ │ │ ├── FusionAuthLogoutButtonComponent.md │ │ │ ├── FusionAuthModule.md │ │ │ ├── FusionAuthRegisterButtonComponent.md │ │ │ └── FusionAuthService.md │ │ ├── interfaces │ │ │ ├── FusionAuthConfig.md │ │ │ └── UserInfo.md │ │ └── modules.md │ ├── getSDKCore.js │ ├── package.json │ ├── projects │ │ └── fusionauth-angular-sdk │ │ │ ├── README.md │ │ │ ├── ng-package.json │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── lib │ │ │ │ ├── SSRCookieAdapter.ts │ │ │ │ ├── components │ │ │ │ │ ├── fa-button.scss │ │ │ │ │ ├── fusionauth-account.button │ │ │ │ │ │ ├── fusion-auth-account-button.component.html │ │ │ │ │ │ ├── fusion-auth-account-button.component.scss │ │ │ │ │ │ ├── fusion-auth-account-button.component.spec.ts │ │ │ │ │ │ └── fusion-auth-account-button.component.ts │ │ │ │ │ ├── fusionauth-login.button │ │ │ │ │ │ ├── fusion-auth-login-button.component.html │ │ │ │ │ │ ├── fusion-auth-login-button.component.scss │ │ │ │ │ │ ├── fusion-auth-login-button.component.spec.ts │ │ │ │ │ │ └── fusion-auth-login-button.component.ts │ │ │ │ │ ├── fusionauth-logout.button │ │ │ │ │ │ ├── fusion-auth-logout-button.component.html │ │ │ │ │ │ ├── fusion-auth-logout-button.component.scss │ │ │ │ │ │ ├── fusion-auth-logout-button.component.spec.ts │ │ │ │ │ │ └── fusion-auth-logout-button.component.ts │ │ │ │ │ └── fusionauth-register.button │ │ │ │ │ │ ├── fusion-auth-register-button.component.html │ │ │ │ │ │ ├── fusion-auth-register-button.component.scss │ │ │ │ │ │ ├── fusion-auth-register-button.component.spec.ts │ │ │ │ │ │ └── fusion-auth-register-button.component.ts │ │ │ │ ├── fusion-auth.module.ts │ │ │ │ ├── fusion-auth.service.spec.ts │ │ │ │ ├── fusion-auth.service.ts │ │ │ │ ├── injectionToken.ts │ │ │ │ └── types.ts │ │ │ └── public-api.ts │ │ │ ├── tsconfig.lib.json │ │ │ ├── tsconfig.lib.prod.json │ │ │ └── tsconfig.spec.json │ ├── tsconfig.json │ └── yarn.lock ├── sdk-react │ ├── .eslintrc.json │ ├── CHANGES.md │ ├── LICENSE │ ├── README.md │ ├── docs │ │ ├── .nojekyll │ │ ├── README.md │ │ ├── interfaces │ │ │ ├── providers_FusionAuthProviderConfig.FusionAuthProviderConfig.md │ │ │ └── providers_FusionAuthProviderContext.FusionAuthProviderContext.md │ │ ├── modules.md │ │ └── modules │ │ │ ├── providers_FusionAuthProvider.md │ │ │ ├── providers_FusionAuthProviderConfig.md │ │ │ ├── providers_FusionAuthProviderContext.md │ │ │ ├── ui_FusionAuthLoginButton.md │ │ │ ├── ui_FusionAuthLogoutButton.md │ │ │ ├── ui_FusionAuthRegisterButton.md │ │ │ ├── ui_RequireAuth.md │ │ │ └── ui_Unauthenticated.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── providers │ │ │ │ ├── Context.tsx │ │ │ │ ├── FusionAuthProvider.test.tsx │ │ │ │ ├── FusionAuthProvider.tsx │ │ │ │ ├── FusionAuthProviderConfig.ts │ │ │ │ ├── FusionAuthProviderContext.ts │ │ │ │ └── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useCookieAdapter.ts │ │ │ │ │ ├── useRedirecting.ts │ │ │ │ │ ├── useTokenRefresh.ts │ │ │ │ │ └── useUserInfo.ts │ │ │ └── ui │ │ │ │ ├── FusionAuthAccountButton │ │ │ │ ├── FusionAuthAccountButton.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── FusionAuthLoginButton │ │ │ │ ├── FusionAuthLoginButton.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── FusionAuthLogoutButton │ │ │ │ ├── FusionAuthLogoutButton.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── FusionAuthRegisterButton │ │ │ │ ├── FusionAuthRegisterButton.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── RequireAuth │ │ │ │ ├── RequireAuth.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── Unauthenticated │ │ │ │ ├── Unauthenticated.test.tsx │ │ │ │ └── index.tsx │ │ │ │ └── withFusionAuth │ │ │ │ ├── index.tsx │ │ │ │ └── withFusionAuth.test.tsx │ │ ├── index.ts │ │ ├── styles │ │ │ └── button.module.scss │ │ ├── testing-tools │ │ │ ├── mocks │ │ │ │ ├── createContextMock.ts │ │ │ │ ├── mockCrypto.ts │ │ │ │ ├── mockUseFusionAuth.ts │ │ │ │ └── testConfig.ts │ │ │ └── setup.ts │ │ └── vite-env.d.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── vite.config.ts └── sdk-vue │ ├── .eslintrc.json │ ├── CHANGES.md │ ├── LICENSE │ ├── README.md │ ├── docs │ ├── .nojekyll │ ├── README.md │ ├── interfaces │ │ ├── FusionAuth.md │ │ ├── FusionAuthConfig.md │ │ └── UserInfo.md │ ├── modules.md │ └── modules │ │ └── FusionAuthLoginButton.md │ ├── generated │ ├── README.adoc │ └── generate.sh │ ├── package.json │ ├── src │ ├── FusionAuthVuePlugin.ts │ ├── components │ │ ├── FusionAuthAccountButton │ │ │ └── FusionAuthAccountButton.vue │ │ ├── FusionAuthLoginButton │ │ │ ├── FusionAuthLoginButton.test.ts │ │ │ └── FusionAuthLoginButton.vue │ │ ├── FusionAuthLogoutButton │ │ │ ├── FusionAuthLogoutButton.test.ts │ │ │ └── FusionAuthLogoutButton.vue │ │ ├── FusionAuthRegisterButton │ │ │ └── FusionAuthRegisterButton.vue │ │ ├── RequireAnonymous │ │ │ ├── RequireAnonymous.test.ts │ │ │ └── RequireAnonymous.vue │ │ ├── RequireAuth │ │ │ ├── RequireAuth.test.ts │ │ │ └── RequireAuth.vue │ │ └── index.ts │ ├── composables │ │ └── useFusionAuth.ts │ ├── createFusionAuth │ │ ├── NuxtUseCookieAdapter.ts │ │ ├── createFusionAuth.test.ts │ │ ├── createFusionAuth.ts │ │ └── index.ts │ ├── index.ts │ ├── injectionSymbols.ts │ ├── styles │ │ └── button.scss │ ├── types.ts │ ├── utilsForTests │ │ └── index.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── web-types.json ├── playwright.config.ts ├── tsconfig.eslint.json ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "project": ["./tsconfig.json", "./packages/*/tsconfig.json"] 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "ignorePatterns": ["build/**", "node_modules/**", ".eslintrc.json", "dist"], 14 | "rules": { 15 | // Note: disable the base rule as it can report incorrect errors 16 | "no-unused-vars": "off", 17 | "@typescript-eslint/ban-ts-comment": "off", 18 | "@typescript-eslint/ban-ts-ignore": "off", 19 | "@typescript-eslint/interface-name-prefix": "off", 20 | "@typescript-eslint/no-empty-function": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-use-before-define": "off", 23 | "@typescript-eslint/no-unused-vars": [ 24 | "warn", 25 | { 26 | "ignoreRestSiblings": true, 27 | "args": "after-used" 28 | } 29 | ], 30 | "prefer-const": "warn" 31 | }, 32 | "settings": {} 33 | } 34 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a managed file. Manual changes will be overwritten. 2 | # https://github.com/FusionAuth/fusionauth-public-repos/ 3 | 4 | .github/ @fusionauth/owners @fusionauth/platform 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve this SDK 4 | labels: bug 5 | --- 6 | 7 | ## (Put bug title here) 8 | 9 | ### Description 10 | A clear and concise description of what the bug is. 11 | 12 | ### Affects package 13 | The package(s) in which this bug can be observed. 14 | 15 | ### Affects versions 16 | List the version you are running, and any other versions you have attempted a recreated. 17 | 18 | ### Steps to reproduce 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | ### Expected behavior 26 | A clear and concise description of what you expected to happen. 27 | 28 | ### Screenshots 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | ### Platform 32 | (Please complete the following information) 33 | - Device: [e.g. Desktop, iPhone X, Pixel 3, etc] 34 | - OS: [e.g. iOS, macOS, Windows ME] 35 | - Browser + version [e.g. chrome, safari plus a version] 36 | - Database [MySQL | PostgresSQL] & and version 37 | 38 | ### Community guidelines 39 | All issues filed in this repository must abide by the [FusionAuth community guidelines](https://fusionauth.io/community/forum/topic/1000/code-of-conduct). 40 | 41 | ### Additional context 42 | Add any other context about the problem here. 43 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature 4 | labels: enhancement 5 | --- 6 | 7 | ## (Put feature request title here) 8 | 9 | ### Problem 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | ### Solution 13 | A clear and concise description of what you want to happen. 14 | 15 | ### Alternatives/workarounds 16 | A clear and concise description of any alternative solutions or workarounds you've considered. 17 | 18 | ### Additional context 19 | Add any other context or screenshots about the feature request here. 20 | 21 | ### Community guidelines 22 | All issues filed in this repository must abide by the [FusionAuth community guidelines](https://fusionauth.io/community/forum/topic/1000/code-of-conduct). 23 | 24 | ### How to vote 25 | Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General issue 3 | about: Use this for a general comment or issue you've encountered 4 | labels: general issue 5 | --- 6 | 7 | ## (Put issue title here) 8 | 9 | ### Description 10 | Provide a description of the problem you have or just leave us a general comment here. 11 | 12 | Is this a question about how to use FusionAuth? Please consider posting on the [FusionAuth forum](https://fusionauth.io/community/forum/) instead. 13 | 14 | ### Community guidelines 15 | All issues filed in this repository must abide by the [FusionAuth community guidelines](https://fusionauth.io/community/forum/topic/1000/code-of-conduct). 16 | 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## What is this PR and why do we need it? 2 | 3 | _Describe the problem or feature in addition to a reference to the Jira issue._ 4 | 5 | #### Pre-Merge Checklist (if applicable) 6 | 7 | - [ ] Unit and Feature tests have been added/updated for logic changes, or there is a justifiable reason for not doing so. 8 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-format.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Format 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | check: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [20.x] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | cache: 'npm' 20 | - name: Install dependencies 21 | run: yarn install --frozen-lockfile 22 | - name: Check Linter 23 | run: yarn lint:check 24 | - name: Check Formatting 25 | run: yarn format:check 26 | -------------------------------------------------------------------------------- /.github/workflows/publish-sdk-angular.yml: -------------------------------------------------------------------------------- 1 | name: Publish @fusionauth/angular-sdk 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x, 20.x] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | cache: 'npm' 18 | - name: Angular SDK - Install packages 19 | run: yarn install --frozen-lockfile 20 | - name: Copy Core to Angular 21 | working-directory: ./packages/sdk-angular 22 | run: yarn get-sdk-core 23 | - name: Angular SDK - Test 24 | run: yarn test:sdk-angular 25 | 26 | publish-sdk-angular: 27 | needs: test 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Use Node.js 20 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 20 35 | registry-url: https://registry.npmjs.org/ 36 | - run: yarn install --frozen-lockfile 37 | - name: Angular SDK - Build 38 | run: yarn build:sdk-angular 39 | - name: Angular SDK - Publish 40 | working-directory: ./packages/sdk-angular/dist/fusionauth-angular-sdk 41 | run: npm publish 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_ANGULAR_SDK }} 44 | -------------------------------------------------------------------------------- /.github/workflows/publish-sdk-react.yml: -------------------------------------------------------------------------------- 1 | name: Publish @fusionauth/react-sdk 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x, 20.x] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | cache: 'npm' 18 | - name: React SDK - Install packages 19 | run: yarn install --frozen-lockfile 20 | - name: Build core package 21 | run: yarn build:core 22 | - name: React SDK - Test 23 | run: yarn test:sdk-react 24 | 25 | publish-sdk-react: 26 | needs: test 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Use Node.js 20 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: 20 34 | registry-url: https://registry.npmjs.org/ 35 | - run: yarn install --frozen-lockfile 36 | - name: React SDK - Build 37 | run: yarn build:sdk-react 38 | - name: React SDK - Publish 39 | working-directory: ./packages/sdk-react/dist 40 | run: npm publish 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_REACT_SDK }} 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-sdk-vue.yml: -------------------------------------------------------------------------------- 1 | name: Publish @fusionauth/vue-sdk 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [18.x, 20.x] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | cache: 'npm' 18 | - name: Vue SDK - Install packages 19 | run: yarn install --frozen-lockfile 20 | - name: Build core package 21 | run: yarn build:core 22 | - name: Vue SDK - Test 23 | run: yarn test:sdk-vue 24 | 25 | publish-sdk-vue: 26 | needs: test 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Use Node.js 20 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: 20 34 | registry-url: https://registry.npmjs.org/ 35 | - run: yarn install --frozen-lockfile 36 | - name: Vue SDK - Build 37 | run: yarn build:sdk-vue 38 | - name: Vue SDK - Publish 39 | working-directory: ./packages/sdk-vue/dist 40 | run: npm publish 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_VUE_SDK }} 43 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 20.x] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | cache: 'npm' 20 | - name: Install dependencies 21 | run: yarn install --frozen-lockfile 22 | - name: Copy Core to Angular 23 | working-directory: ./packages/sdk-angular 24 | run: yarn get-sdk-core 25 | - name: Build Core 26 | run: yarn build:core 27 | - name: Run Tests 28 | run: yarn test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist 4 | packages/sdk-angular/projects/fusionauth-angular-sdk/src/sdkcore 5 | 6 | yarn-error.log 7 | 8 | .idea/workspace.xml 9 | .idea/usage.statistics.xml 10 | .idea/shelf/* 11 | .idea/**/*.iml 12 | 13 | .vscode 14 | 15 | .eslintcache 16 | packages/sdk-angular/.angular/cache 17 | tsconfig.tsbuildinfo 18 | 19 | test-results 20 | playwright-report 21 | blob-report 22 | playwright/.cache 23 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn test 2 | 3 | npx lint-staged -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.11.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | config 3 | coverage 4 | node_modules 5 | README.md 6 | src/**/*.min.css 7 | .github/ISSUE_TEMPLATE/*.md 8 | packages/sdk-vue/web-types.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "bracketSameLine": false, 4 | "singleQuote": true, 5 | "semi": true, 6 | "trailingComma": "all", 7 | "arrowParens": "avoid", 8 | "endOfLine": "lf", 9 | "printWidth": 80, 10 | "tabWidth": 2, 11 | "overrides": [ 12 | { 13 | "files": ["*.json", "*.yml", "*.css", "*.scss"], 14 | "options": { 15 | "tabWidth": 2 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /e2e/pages/common.page.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page, expect } from '@playwright/test'; 2 | 3 | const Locators = { 4 | logInBtn: 'role=button[name="Login"]', 5 | emailInput: 'role=textbox[name="Email"]', 6 | passwordInput: 'role=textbox[name="Password"]', 7 | submitBtn: 'role=button[name="Submit"]', 8 | createAccountBtn: 'role=button[name="Create a new account."]', 9 | registerBtn: 'role=button[name="Register"]', 10 | logOutBtn: 'text=Logout', 11 | } as const; 12 | 13 | type LocatorsKey = keyof typeof Locators; 14 | 15 | export class quickstartPage { 16 | readonly page: Page; 17 | readonly locators: Record; 18 | 19 | constructor(page: Page) { 20 | this.page = page; 21 | this.locators = Object.keys(Locators).reduce( 22 | (acc, key) => { 23 | acc[key as LocatorsKey] = page.locator(Locators[key as LocatorsKey]); 24 | return acc; 25 | }, 26 | {} as Record, 27 | ); 28 | } 29 | 30 | async navToLogIn() { 31 | await this.locators.logInBtn.nth(0).click(); 32 | await expect(this.locators.emailInput).toBeVisible(); 33 | } 34 | 35 | async authenticate() { 36 | await this.locators.emailInput.click(); 37 | await this.locators.emailInput.clear(); 38 | await this.locators.emailInput.fill('richard@example.com'); 39 | await this.locators.passwordInput.click(); 40 | await this.locators.passwordInput.clear(); 41 | await this.locators.passwordInput.fill('password'); 42 | await this.locators.submitBtn.click(); 43 | } 44 | 45 | async navToRegister() { 46 | await this.locators.createAccountBtn.click(); 47 | await expect(this.locators.registerBtn).toBeVisible(); 48 | } 49 | 50 | async logOut() { 51 | await this.locators.logOutBtn.click(); 52 | await expect(this.locators.logInBtn.nth(0)).toBeVisible(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /e2e/tests/cookies.test.ts: -------------------------------------------------------------------------------- 1 | import { Page, test, BrowserContext, expect, Cookie } from '@playwright/test'; 2 | import { quickstartPage } from '../pages/common.page'; 3 | 4 | test.describe('Login Endpoint Tests', () => { 5 | let page: Page; 6 | let quickstart: quickstartPage; 7 | let browserContext: BrowserContext; 8 | 9 | test.beforeAll(async ({ browser }) => { 10 | browserContext = await browser.newContext(); 11 | page = await browserContext.newPage(); 12 | quickstart = new quickstartPage(page); 13 | await page.goto('/'); 14 | await quickstart.navToLogIn(); 15 | }); 16 | 17 | test.afterAll(async () => { 18 | await page.close(); 19 | await browserContext.close(); 20 | }); 21 | 22 | const checkCookieExistsAndHttpOnly = ( 23 | cookies: Cookie[], 24 | name: string, 25 | httpOnly: boolean, 26 | ) => { 27 | const cookie = cookies.find(cookie => cookie.name === name); 28 | expect(cookie).toBeDefined(); 29 | expect(cookie?.httpOnly).toBe(httpOnly); 30 | }; 31 | 32 | const checkCookieDoesNotExist = (cookies: Cookie[], name: string) => { 33 | const cookie = cookies.find(cookie => cookie.name === name); 34 | expect(cookie).toBeUndefined(); 35 | }; 36 | 37 | test('Unauthenticated Cookies', async () => { 38 | const cookies = await browserContext.cookies(); 39 | checkCookieDoesNotExist(cookies, 'app.at'); 40 | checkCookieDoesNotExist(cookies, 'app.at_exp'); 41 | checkCookieDoesNotExist(cookies, 'app.idt'); 42 | checkCookieDoesNotExist(cookies, 'app.rt'); 43 | }); 44 | 45 | test('Authenticated Cookies', async () => { 46 | await quickstart.authenticate(); 47 | const cookies = await browserContext.cookies(); 48 | checkCookieExistsAndHttpOnly(cookies, 'app.at', true); 49 | checkCookieExistsAndHttpOnly(cookies, 'app.at_exp', false); 50 | checkCookieExistsAndHttpOnly(cookies, 'app.idt', false); 51 | checkCookieExistsAndHttpOnly(cookies, 'app.rt', true); 52 | }); 53 | 54 | test('Post Logout Cookies', async () => { 55 | await quickstart.authenticate(); 56 | await quickstart.logOut(); 57 | const cookies = await browserContext.cookies(); 58 | checkCookieDoesNotExist(cookies, 'app.at'); 59 | checkCookieDoesNotExist(cookies, 'app.at_exp'); 60 | checkCookieDoesNotExist(cookies, 'app.idt'); 61 | checkCookieDoesNotExist(cookies, 'app.rt'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fusionauth-javascript-sdk", 3 | "version": "0.1.0", 4 | "description": "The monorepo containing FusionAuth's React, Angular, and Vue SDKs.", 5 | "main": "index.js", 6 | "repository": "git@github.com:FusionAuth/fusionauth-javascript-sdk.git", 7 | "author": "FusionAuth", 8 | "license": "Apache", 9 | "private": true, 10 | "workspaces": [ 11 | "packages/core", 12 | "packages/lexicon", 13 | "packages/sdk-react", 14 | "packages/sdk-angular", 15 | "packages/sdk-vue" 16 | ], 17 | "devDependencies": { 18 | "@playwright/test": "^1.44.1", 19 | "playwright": "^1.44.1", 20 | "@typescript-eslint/eslint-plugin": "^7.2.0", 21 | "@typescript-eslint/parser": "^7.2.0", 22 | "eslint": "^8.57.0", 23 | "eslint-config-prettier": "^9.1.0", 24 | "eslint-config-standard": "^17.1.0", 25 | "eslint-plugin-import": "^2.29.1", 26 | "eslint-plugin-node": "^11.1.0", 27 | "eslint-plugin-promise": "^6.1.1", 28 | "eslint-plugin-react": "^7.34.0", 29 | "eslint-plugin-react-hooks": "^4.6.0", 30 | "eslint-plugin-react-refresh": "^0.4.5", 31 | "husky": "^9.0.11", 32 | "lint-staged": "^15.2.2", 33 | "prettier": "^3.2.5" 34 | }, 35 | "scripts": { 36 | "prepare": "husky", 37 | "build:core": "yarn workspace @fusionauth-sdk/core build", 38 | "build:lexicon": "yarn workspace @fusionauth-sdk/lexicon build", 39 | "build:sdk-angular": "yarn workspace sdk-angular-workspace build", 40 | "build:sdk-react": "yarn build:lexicon && yarn build:core && yarn workspace @fusionauth/react-sdk build", 41 | "build:sdk-vue": "yarn build:lexicon && yarn build:core && yarn workspace @fusionauth/vue-sdk build", 42 | "yalc-pub:sdk-react": "yarn build:sdk-react && yalc publish packages/sdk-react", 43 | "yalc-pub:sdk-vue": "yarn build:sdk-vue && yalc publish packages/sdk-vue", 44 | "yalc-pub:sdk-angular": "yarn build:sdk-angular && yalc publish packages/sdk-angular/dist/fusionauth-angular-sdk", 45 | "test": "yarn test:lexicon && yarn test:core && yarn test:sdk-react && yarn test:sdk-angular && yarn test:sdk-vue", 46 | "test:core": "yarn workspace @fusionauth-sdk/core test", 47 | "test:lexicon": "yarn workspace @fusionauth-sdk/lexicon test", 48 | "test:sdk-angular": "yarn workspace sdk-angular-workspace test", 49 | "test:sdk-react": "yarn workspace @fusionauth/react-sdk test", 50 | "test:sdk-vue": "yarn workspace @fusionauth/vue-sdk test", 51 | "test:e2e": "yarn playwright test", 52 | "lint:fix": "eslint . --ext .ts,.tsx --fix", 53 | "lint:check": "eslint . --ext .ts,.tsx --max-warnings 0", 54 | "format:fix": "prettier --write .", 55 | "format:check": "prettier --check ." 56 | }, 57 | "lint-staged": { 58 | "*.{ts,tsx}": "eslint --cache --fix", 59 | "*.{js,css,md,ts,tsx,yml}": "prettier --write" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fusionauth-sdk/core", 3 | "private": true, 4 | "description": "The core functionality shared by the FusionAuth React, Angular, and Vue SDKs.", 5 | "version": "0.1.0", 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "module": "./dist/index.js", 9 | "types": "./dist/index.d.ts", 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "vite build", 15 | "dev:watch": "vite", 16 | "test:watch": "vitest", 17 | "test": "vitest --watch=false" 18 | }, 19 | "devDependencies": { 20 | "typescript": "^5.2.2", 21 | "vite": "^5.2.0", 22 | "vite-plugin-dts": "^3.8.0", 23 | "vitest": "^1.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/CookieHelpers/CookieHelpers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, afterEach } from 'vitest'; 2 | 3 | import { CookieAdapter, getAccessTokenExpirationMoment } from '.'; 4 | 5 | describe('getAccessTokenExpirationMoment', () => { 6 | afterEach(() => { 7 | document.cookie = 8 | 'app.at_exp' + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 9 | }); 10 | 11 | it('Should get the "app.at_exp" cookie value in milliseconds', () => { 12 | const exp = Date.now(); 13 | document.cookie = `app.at_exp=${exp}`; 14 | expect(getAccessTokenExpirationMoment()).toBe(exp * 1000); 15 | }); 16 | 17 | it('Should return -1 if the cookie is not set', () => { 18 | expect(getAccessTokenExpirationMoment()).toBe(-1); 19 | }); 20 | 21 | it('Accepts a specific cookieName if one is provided', () => { 22 | const cookieName = 'my-special-cookie'; 23 | const exp = 1200; 24 | document.cookie = `${cookieName}=${exp}`; 25 | 26 | expect(getAccessTokenExpirationMoment(cookieName)).toBe(1200000); 27 | }); 28 | 29 | it('Will get the value from a cookieAdapter if one is passed in', () => { 30 | const value = '500'; 31 | const cookieAdapter: CookieAdapter = { 32 | at_exp() { 33 | return value; 34 | }, 35 | }; 36 | 37 | expect(getAccessTokenExpirationMoment(undefined, cookieAdapter)).toBe( 38 | 500000, 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/core/src/CookieHelpers/CookieHelpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the `app.at_exp` cookie and converts it to milliseconds since epoch. 3 | * Returns -1 if the cookie is not present. 4 | * @param cookieName - defaults to `app.at_exp`. 5 | * @param adapter - SSR frameworks like Nuxt, Next, and angular/ssr will pass in an adapter. 6 | */ 7 | export function getAccessTokenExpirationMoment( 8 | cookieName: string = 'app.at_exp', 9 | adapter?: CookieAdapter, 10 | ): number | -1 { 11 | if (adapter) { 12 | return toMilliseconds(adapter.at_exp(cookieName)); 13 | } 14 | 15 | let cookie; 16 | 17 | try { 18 | // `document` throws a ReferenceError if this runs in a 19 | // non-browser environment such as an SSR framework like Nuxt or Next. 20 | cookie = document.cookie; 21 | } catch { 22 | console.error( 23 | 'Error accessing cookies in fusionauth. If you are using SSR you must configure the SDK with a cookie adapter', 24 | ); 25 | return -1; 26 | } 27 | 28 | const expCookie = cookie 29 | .split('; ') 30 | .map(c => c.split('=')) 31 | .find(([name]) => name === cookieName); 32 | const cookieValue = expCookie?.[1]; 33 | 34 | return toMilliseconds(cookieValue); 35 | } 36 | 37 | export interface CookieAdapter { 38 | /** returns the `app.at_exp` cookie without manipulating the value. */ 39 | at_exp: (cookieName?: string) => number | string | undefined; 40 | } 41 | 42 | function toMilliseconds(seconds?: number | string): number { 43 | if (!seconds) return -1; 44 | else return Number(seconds) * 1000; 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/CookieHelpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CookieHelpers'; 2 | -------------------------------------------------------------------------------- /packages/core/src/RedirectHelper/RedirectHelper.ts: -------------------------------------------------------------------------------- 1 | /** A class responsible for storing a redirect value in localStorage and cleanup afterward. */ 2 | export class RedirectHelper { 3 | private readonly REDIRECT_VALUE = 'fa-sdk-redirect-value'; 4 | private get storage(): Storage { 5 | try { 6 | return localStorage; 7 | } catch { 8 | // fallback for non-browser environments where localStorage is not defined. 9 | return { 10 | /* eslint-disable */ 11 | setItem(_key: string, _value: string) {}, 12 | getItem(_key: string) {}, 13 | removeItem(_key: string) {}, 14 | /* eslint-enable */ 15 | } as Storage; 16 | } 17 | } 18 | 19 | handlePreRedirect(state?: string) { 20 | const valueForStorage = `${this.generateRandomString()}:${state ?? ''}`; 21 | this.storage.setItem(this.REDIRECT_VALUE, valueForStorage); 22 | } 23 | 24 | handlePostRedirect(callback?: (state?: string) => void) { 25 | const didRedirect = Boolean(this.storage.getItem(this.REDIRECT_VALUE)); 26 | if (!didRedirect) { 27 | return; 28 | } 29 | 30 | const state = this.state; 31 | callback?.(state); 32 | this.storage.removeItem(this.REDIRECT_VALUE); 33 | } 34 | 35 | private get state() { 36 | const redirectValue = this.storage.getItem(this.REDIRECT_VALUE); 37 | 38 | if (!redirectValue) { 39 | return; 40 | } 41 | 42 | const [, ...stateValue] = redirectValue.split(':'); 43 | return stateValue.join(':') || undefined; 44 | } 45 | 46 | private generateRandomString() { 47 | const array = new Uint32Array(56 / 2); 48 | window.crypto.getRandomValues(array); 49 | return Array.from(array, (n: number) => 50 | ('0' + n.toString(16)).substring(-2), 51 | ).join(''); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/core/src/RedirectHelper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RedirectHelper'; 2 | -------------------------------------------------------------------------------- /packages/core/src/SDKConfig/SDKConfig.ts: -------------------------------------------------------------------------------- 1 | import { CookieAdapter } from '..'; 2 | 3 | /** 4 | * Config for FusionAuth Web SDKs 5 | */ 6 | export interface SDKConfig { 7 | /** 8 | * The URL of the server that performs the token exchange. 9 | */ 10 | serverUrl: string; 11 | 12 | /** 13 | * The client id of the application. 14 | */ 15 | clientId: string; 16 | 17 | /** 18 | * The redirect URI of the application. 19 | */ 20 | redirectUri: string; 21 | 22 | /** 23 | * The OAuth2 scope parameter passed to the `/oauth2/authorize` endpoint. If not specified fusionauth will default this to `openid offline_access`. 24 | */ 25 | scope?: string; 26 | 27 | /** 28 | * Additional params passed to loginPath typically `/app/login/`, which redirects to `/oauth2/authorize`. Example of this might be loginParams = [{idp_hint:'44449786-3dff-42a6-aac6-1f1ceecb6c46'}] or any params found at https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/endpoints 29 | */ 30 | authParams?: { [key: string]: any }[]; 31 | 32 | /** 33 | * The redirect URI for post-logout. Defaults the provided `redirectUri`. 34 | */ 35 | postLogoutRedirectUri?: string; 36 | 37 | /** 38 | * Enables automatic token refreshing. Defaults to false. 39 | */ 40 | shouldAutoRefresh?: boolean; 41 | 42 | /** 43 | * Enables the SDK to automatically handle fetching user info when logged in. Defaults to false. 44 | */ 45 | shouldAutoFetchUserInfo?: boolean; 46 | 47 | /** 48 | * The number of seconds before the access token expiry when the auto refresh functionality kicks in if enabled. Default is 30. 49 | */ 50 | autoRefreshSecondsBeforeExpiry?: number; 51 | 52 | /** 53 | * Callback function to be invoked with the `state` value upon redirect from login or register. 54 | */ 55 | onRedirect?: (state?: string) => void; 56 | 57 | /** 58 | * The path to the login endpoint. 59 | */ 60 | loginPath?: string; 61 | 62 | /** 63 | * The path to the register endpoint. 64 | */ 65 | registerPath?: string; 66 | 67 | /** 68 | * The path to the logout endpoint. 69 | */ 70 | logoutPath?: string; 71 | 72 | /** 73 | * The path to the token refresh endpoint. 74 | */ 75 | tokenRefreshPath?: string; 76 | 77 | /** 78 | * The path to the me endpoint. 79 | */ 80 | mePath?: string; 81 | 82 | /** 83 | * The name of the access token expiration moment cookie. 84 | * Only set this if you are hosting server that uses a custom name for the 'app.at_exp' cookie. 85 | */ 86 | accessTokenExpireCookieName?: string; 87 | 88 | /** 89 | * Callback to be invoked if a request to refresh the access token fails during autorefresh. 90 | */ 91 | onAutoRefreshFailure?: (error: Error) => void; 92 | 93 | /** 94 | * Adapter pattern for SSR frameworks such as next or nuxt 95 | */ 96 | cookieAdapter?: CookieAdapter; 97 | 98 | /** 99 | * Callback to be invoked at the moment of access token expiration 100 | */ 101 | onTokenExpiration: () => void; 102 | } 103 | -------------------------------------------------------------------------------- /packages/core/src/SDKConfig/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SDKConfig'; 2 | -------------------------------------------------------------------------------- /packages/core/src/SDKContext/SDKContext.ts: -------------------------------------------------------------------------------- 1 | /** The context provided by FusionAuth Web SDKs */ 2 | export interface SDKContext { 3 | /** 4 | * Whether the user is logged in. 5 | */ 6 | isLoggedIn: boolean; 7 | 8 | /** 9 | * Data fetched from the configured 'me' endpoint. 10 | */ 11 | userInfo: UserInfo | null; 12 | 13 | /** 14 | * Fetches user info from the 'me' endpoint. 15 | * This is handled automatically if the SDK is configured with `shouldAutoFetchUserInfo`. 16 | * @returns {Promise} 17 | */ 18 | fetchUserInfo: () => Promise; 19 | 20 | /** 21 | * Indicates that the fetchUserInfo call is unresolved. 22 | */ 23 | isFetchingUserInfo: boolean; 24 | 25 | /** 26 | * Error occurred while fetching userInfo. 27 | */ 28 | error: Error | null; 29 | 30 | /** 31 | * Initiates login flow. 32 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 33 | */ 34 | startLogin: (state?: string) => void; 35 | 36 | /** 37 | * Initiates register flow. 38 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 39 | */ 40 | startRegister: (state?: string) => void; 41 | 42 | /** 43 | * Initiates logout flow. 44 | */ 45 | startLogout: () => void; 46 | 47 | /** 48 | * Refreshes the access token a single time. 49 | * This is handled automatically if the SDK is configured with `shouldAutoRefresh`. 50 | */ 51 | refreshToken: () => Promise; 52 | 53 | /** 54 | * Initializes automatic access token refreshing. 55 | * This is handled automatically if the SDK is configured with `shouldAutoRefresh`. 56 | */ 57 | initAutoRefresh: () => void; 58 | } 59 | 60 | /** 61 | * User information returned from FusionAuth. 62 | */ 63 | export type UserInfo = { 64 | applicationId?: string; 65 | birthdate?: string; 66 | email?: string; 67 | email_verified?: boolean; 68 | family_name?: string; 69 | given_name?: string; 70 | name?: string; 71 | middle_name?: string; 72 | phone_number?: string; 73 | picture?: string; 74 | preferred_username?: string; 75 | roles?: any[]; 76 | sid?: string; 77 | sub?: string; 78 | tid?: string; 79 | }; 80 | -------------------------------------------------------------------------------- /packages/core/src/SDKContext/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SDKContext'; 2 | -------------------------------------------------------------------------------- /packages/core/src/SDKCore/SDKCore.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, describe, it, expect, vi } from 'vitest'; 2 | 3 | import { SDKConfig } from '../SDKConfig'; 4 | import { SDKCore } from '.'; 5 | import { RedirectHelper } from '../RedirectHelper'; 6 | 7 | import { mockIsLoggedIn, mockWindowLocation, removeAt_expCookie } from '..'; 8 | 9 | describe('SDKCore', () => { 10 | afterEach(() => { 11 | vi.restoreAllMocks(); 12 | vi.useRealTimers(); 13 | removeAt_expCookie(); 14 | localStorage.clear(); 15 | }); 16 | 17 | const config: SDKConfig = { 18 | serverUrl: 'http://my-server', 19 | clientId: 'abc123', 20 | redirectUri: 'http://my-client', 21 | onTokenExpiration: vi.fn(), 22 | }; 23 | 24 | it('Knows that the user is logged in when the at_exp is present and in the future', () => { 25 | vi.useFakeTimers(); 26 | mockIsLoggedIn(); 27 | 28 | vi.spyOn(window, 'fetch').mockResolvedValue( 29 | new Response(null, { status: 200 }), 30 | ); 31 | 32 | const core = new SDKCore(config); 33 | 34 | expect(core.isLoggedIn).toBe(true); 35 | 36 | vi.advanceTimersByTime(60 * 59 * 1000); // move time ahead 59 minutes 37 | expect(core.isLoggedIn).toBe(true); 38 | expect(config.onTokenExpiration).not.toHaveBeenCalled(); 39 | 40 | vi.advanceTimersByTime(60 * 1000); // move time ahead 1 minute 41 | expect(core.isLoggedIn).toBe(false); 42 | expect(config.onTokenExpiration).toHaveBeenCalledTimes(1); 43 | }); 44 | 45 | it('Initialize automatic token refresh', async () => { 46 | vi.useFakeTimers(); 47 | mockIsLoggedIn(); 48 | 49 | vi.spyOn(window, 'fetch').mockResolvedValueOnce( 50 | new Response(null, { status: 200 }), 51 | ); 52 | 53 | vi.spyOn(SDKCore.prototype, 'refreshToken'); 54 | 55 | const core = new SDKCore({ ...config, shouldAutoRefresh: true }); 56 | 57 | const timeout = core.initAutoRefresh(); // set autorefresh for 30 seconds before expiration 58 | 59 | vi.advanceTimersByTime(59 * 60 * 1000); // advance time 59 minutes 60 | expect(core.refreshToken).not.toHaveBeenCalled(); 61 | 62 | vi.advanceTimersByTime(49 * 1000); // advance time 50 seconds 63 | expect(core.refreshToken).not.toHaveBeenCalled(); 64 | 65 | vi.advanceTimersByTime(1000); // advance time 1 second 66 | expect(core.refreshToken).toHaveBeenCalledTimes(1); 67 | 68 | clearTimeout(timeout); 69 | }); 70 | 71 | it('Invokes `redirectHelper.handlePreRedirect` before starting login and register', () => { 72 | const handlePreRedirect = vi.spyOn( 73 | RedirectHelper.prototype, 74 | 'handlePreRedirect', 75 | ); 76 | mockWindowLocation(vi); 77 | const core = new SDKCore(config); 78 | 79 | expect(handlePreRedirect).toHaveBeenCalledTimes(0); 80 | 81 | core.startLogin('/login'); 82 | core.startRegister(); 83 | 84 | expect(handlePreRedirect).toHaveBeenNthCalledWith(1, '/login'); 85 | expect(handlePreRedirect).toHaveBeenNthCalledWith(2, undefined); 86 | }); 87 | 88 | it('Stores a redirect value pre-login redirect and cleans up post-login', () => { 89 | mockIsLoggedIn(); 90 | mockWindowLocation(vi); 91 | const redirectIndicator = () => 92 | localStorage.getItem('fa-sdk-redirect-value'); 93 | const onRedirectCallback = vi.fn(); 94 | const core = new SDKCore(config); 95 | 96 | expect(redirectIndicator()).toBeNull(); 97 | 98 | core.startLogin(); 99 | expect(redirectIndicator()).toBeDefined(); 100 | 101 | core.handlePostRedirect(onRedirectCallback); 102 | expect(redirectIndicator()).toBeNull(); 103 | expect(onRedirectCallback).toHaveBeenCalledWith(undefined); 104 | }); 105 | 106 | it('`handlePostRedirect` Does not invoke the callback given if redirect did not happen', () => { 107 | mockIsLoggedIn(); 108 | const core = new SDKCore({ ...config }); 109 | const onRedirect = vi.fn(); 110 | 111 | core.handlePostRedirect(onRedirect); 112 | 113 | expect(onRedirect).not.toHaveBeenCalled(); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /packages/core/src/SDKCore/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SDKCore'; 2 | -------------------------------------------------------------------------------- /packages/core/src/UrlHelper/UrlHelper.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { UrlHelper, UrlHelperConfig } from '.'; 4 | 5 | describe('UrlHelper', () => { 6 | const config: UrlHelperConfig = { 7 | serverUrl: 'http://my-server', 8 | clientId: 'abc123', 9 | redirectUri: 'http://my-client', 10 | scope: 'openid email profile offline_access', 11 | authParams: [{ idp_hint: '44449786-3dff-42a6-aac6-1f1ceecb6c46' }], 12 | postLogoutRedirectUri: 'http://example.com', 13 | }; 14 | 15 | const urlHelper = new UrlHelper(config); 16 | 17 | it('me url', () => { 18 | const meUrl = urlHelper.getMeUrl(); 19 | expect(meUrl.origin).toBe(config.serverUrl); 20 | expect(meUrl.pathname).toBe('/app/me/'); 21 | expect(meUrl.search).toBe(''); 22 | }); 23 | 24 | it('login url', () => { 25 | const stateValue = 'login-state-value'; 26 | const loginUrl = urlHelper.getLoginUrl(stateValue); 27 | expect(loginUrl.origin).toBe(config.serverUrl); 28 | expect(loginUrl.pathname).toBe('/app/login/'); 29 | expect(loginUrl.searchParams.get('client_id')).toBe(config.clientId); 30 | expect(loginUrl.searchParams.get('redirect_uri')).toBe(config.redirectUri); 31 | expect(loginUrl.searchParams.get('scope')).toBe(config.scope); 32 | expect(loginUrl.searchParams.get('state')).toBe(stateValue); 33 | }); 34 | 35 | it('login url authParams', () => { 36 | const stateValue = 'login-state-value'; 37 | const loginUrl = urlHelper.getLoginUrl(stateValue); 38 | expect(loginUrl.origin).toBe(config.serverUrl); 39 | expect(loginUrl.pathname).toBe('/app/login/'); 40 | expect(loginUrl.searchParams.get('idp_hint')).toBe( 41 | config.authParams?.at(0)?.idp_hint, 42 | ); 43 | }); 44 | 45 | it('register url', () => { 46 | const stateValue = 'register-state-value'; 47 | const registerUrl = urlHelper.getRegisterUrl(stateValue); 48 | expect(registerUrl.origin).toBe(config.serverUrl); 49 | expect(registerUrl.pathname).toBe('/app/register/'); 50 | expect(registerUrl.searchParams.get('client_id')).toBe(config.clientId); 51 | expect(registerUrl.searchParams.get('redirect_uri')).toBe( 52 | config.redirectUri, 53 | ); 54 | expect(registerUrl.searchParams.get('scope')).toBe(config.scope); 55 | expect(registerUrl.searchParams.get('state')).toBe(stateValue); 56 | }); 57 | 58 | it('logout url', () => { 59 | const logoutUrl = urlHelper.getLogoutUrl(); 60 | expect(logoutUrl.origin).toBe(config.serverUrl); 61 | expect(logoutUrl.pathname).toBe('/app/logout/'); 62 | expect(logoutUrl.searchParams.get('client_id')).toBe(config.clientId); 63 | expect(logoutUrl.searchParams.get('post_logout_redirect_uri')).toBe( 64 | config.postLogoutRedirectUri, 65 | ); 66 | }); 67 | 68 | it('logout url - default post_logout_redirect_uri', () => { 69 | const configWithoutPostLogoutRedirectUri: UrlHelperConfig = { 70 | serverUrl: 'http://my-server', 71 | clientId: 'abc123', 72 | redirectUri: 'http://my-client', 73 | scope: 'openid email profile offline_access', 74 | }; 75 | 76 | const urlHelperWithoutPostLogoutRedirectUri = new UrlHelper( 77 | configWithoutPostLogoutRedirectUri, 78 | ); 79 | const logoutUrl = urlHelperWithoutPostLogoutRedirectUri.getLogoutUrl(); 80 | expect(logoutUrl.origin).toBe(configWithoutPostLogoutRedirectUri.serverUrl); 81 | expect(logoutUrl.pathname).toBe('/app/logout/'); 82 | expect(logoutUrl.searchParams.get('client_id')).toBe( 83 | configWithoutPostLogoutRedirectUri.clientId, 84 | ); 85 | expect(logoutUrl.searchParams.get('post_logout_redirect_uri')).toBe( 86 | configWithoutPostLogoutRedirectUri.redirectUri, 87 | ); 88 | }); 89 | 90 | it('tokenRefresh url', () => { 91 | const tokenRefreshUrl = urlHelper.getTokenRefreshUrl(); 92 | expect(tokenRefreshUrl.pathname).toBe('/app/refresh/'); 93 | expect(tokenRefreshUrl.searchParams.get('client_id')).toBe(config.clientId); 94 | }); 95 | 96 | it('account management url', () => { 97 | const accountManagementUrl = urlHelper.getAccountManagementUrl(); 98 | expect(accountManagementUrl.pathname).toBe('/account/'); 99 | expect(accountManagementUrl.searchParams.get('client_id')).toBe( 100 | config.clientId, 101 | ); 102 | }); 103 | 104 | it('Should generate urls with a specified path', () => { 105 | const paths = { 106 | mePath: '/my-me', 107 | loginPath: '/my-login', 108 | registerPath: '/my-register', 109 | logoutPath: '/my-logout', 110 | tokenRefreshPath: '/my-refresh', 111 | }; 112 | const { me, login, register, logout, tokenRefresh } = getAllUrls( 113 | new UrlHelper({ 114 | ...config, 115 | ...paths, 116 | }), 117 | ); 118 | 119 | expect(me.pathname).toBe(paths.mePath); 120 | expect(login.pathname).toBe(paths.loginPath); 121 | expect(register.pathname).toBe(paths.registerPath); 122 | expect(logout.pathname).toBe(paths.logoutPath); 123 | expect(tokenRefresh.pathname).toBe(paths.tokenRefreshPath); 124 | }); 125 | }); 126 | 127 | function getAllUrls(urlHelper: UrlHelper) { 128 | return { 129 | me: urlHelper.getMeUrl(), 130 | login: urlHelper.getLoginUrl(), 131 | register: urlHelper.getRegisterUrl(), 132 | logout: urlHelper.getLogoutUrl(), 133 | tokenRefresh: urlHelper.getTokenRefreshUrl(), 134 | }; 135 | } 136 | -------------------------------------------------------------------------------- /packages/core/src/UrlHelper/UrlHelper.ts: -------------------------------------------------------------------------------- 1 | import { UrlHelperConfig, UrlHelperQueryParams } from './UrlHelperTypes'; 2 | 3 | /** A class responsible for generating URLs that FusionAuth SDKs interact with. */ 4 | export class UrlHelper { 5 | serverUrl: string; 6 | clientId: string; 7 | redirectUri: string; 8 | scope?: string; 9 | authParams?: { [key: string]: any }[]; 10 | 11 | mePath: string; 12 | loginPath: string; 13 | registerPath: string; 14 | logoutPath: string; 15 | tokenRefreshPath: string; 16 | postLogoutRedirectUri?: string; 17 | 18 | constructor(config: UrlHelperConfig) { 19 | this.serverUrl = config.serverUrl; 20 | this.clientId = config.clientId; 21 | this.redirectUri = config.redirectUri; 22 | this.scope = config.scope; 23 | this.authParams = config.authParams; 24 | this.postLogoutRedirectUri = config.postLogoutRedirectUri; 25 | 26 | this.mePath = config.mePath ?? '/app/me/'; 27 | this.loginPath = config.loginPath ?? '/app/login/'; 28 | this.registerPath = config.registerPath ?? '/app/register/'; 29 | this.logoutPath = config.logoutPath ?? '/app/logout/'; 30 | this.tokenRefreshPath = config.tokenRefreshPath ?? '/app/refresh/'; 31 | } 32 | 33 | getMeUrl(): URL { 34 | return this.generateUrl(this.mePath); 35 | } 36 | 37 | getLoginUrl(state?: string): URL { 38 | return this.generateUrl(this.loginPath, { 39 | client_id: this.clientId, 40 | redirect_uri: this.redirectUri, 41 | scope: this.scope, 42 | authParams: this.authParams, 43 | state, 44 | }); 45 | } 46 | 47 | getRegisterUrl(state?: string): URL { 48 | return this.generateUrl(this.registerPath, { 49 | client_id: this.clientId, 50 | redirect_uri: this.redirectUri, 51 | scope: this.scope, 52 | authParams: this.authParams, 53 | state, 54 | }); 55 | } 56 | 57 | getLogoutUrl(): URL { 58 | return this.generateUrl(this.logoutPath, { 59 | client_id: this.clientId, 60 | post_logout_redirect_uri: this.postLogoutRedirectUri || this.redirectUri, 61 | }); 62 | } 63 | 64 | getTokenRefreshUrl(): URL { 65 | return this.generateUrl(this.tokenRefreshPath, { 66 | client_id: this.clientId, 67 | }); 68 | } 69 | 70 | getAccountManagementUrl(): URL { 71 | return this.generateUrl('/account/', { 72 | client_id: this.clientId, 73 | }); 74 | } 75 | 76 | private generateUrl(path: string, params?: UrlHelperQueryParams): URL { 77 | const url = new URL(this.serverUrl); 78 | url.pathname = path; 79 | 80 | if (params) { 81 | const urlSearchParams = this.generateURLSearchParams(params); 82 | url.search = urlSearchParams.toString(); 83 | } 84 | 85 | return url; 86 | } 87 | 88 | private generateURLSearchParams( 89 | params: UrlHelperQueryParams, 90 | ): URLSearchParams { 91 | const urlSearchParams = new URLSearchParams(); 92 | this.appendParams(params, urlSearchParams); 93 | return urlSearchParams; 94 | } 95 | 96 | private appendParams( 97 | params: Record, // or a more specific type if known 98 | urlSearchParams: URLSearchParams, 99 | ): void { 100 | Object.entries(params).forEach(([key, value]) => { 101 | if ((value && typeof value === 'object') || Array.isArray(value)) { 102 | // Recursively handle nested objects 103 | this.appendParams(value, urlSearchParams); 104 | } else if (value !== undefined && value !== null) { 105 | // Append primitive values 106 | urlSearchParams.append(key, String(value)); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/core/src/UrlHelper/UrlHelperTypes.ts: -------------------------------------------------------------------------------- 1 | import { SDKConfig } from '../SDKConfig'; 2 | 3 | /** A configuration object for the UrlHelper class. */ 4 | export type UrlHelperConfig = Pick< 5 | SDKConfig, 6 | | 'serverUrl' 7 | | 'clientId' 8 | | 'redirectUri' 9 | | 'mePath' 10 | | 'loginPath' 11 | | 'registerPath' 12 | | 'logoutPath' 13 | | 'tokenRefreshPath' 14 | | 'scope' 15 | | 'authParams' 16 | | 'postLogoutRedirectUri' 17 | >; 18 | 19 | /** The query params associated with URLs generated by the UrlHelper class. */ 20 | export type UrlHelperQueryParams = { 21 | client_id: string; 22 | redirect_uri?: string; 23 | post_logout_redirect_uri?: string; 24 | scope?: string; 25 | authParams?: { [key: string]: any }[]; 26 | state?: string; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/core/src/UrlHelper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UrlHelper'; 2 | export * from './UrlHelperTypes'; 3 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SDKCore'; 2 | export * from './SDKConfig'; 3 | export { type SDKContext } from './SDKContext'; 4 | export * from './testUtils'; 5 | export { type CookieAdapter } from './CookieHelpers'; 6 | -------------------------------------------------------------------------------- /packages/core/src/testUtils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mockLoggedIn'; 2 | export * from './mockWindowLocation'; 3 | -------------------------------------------------------------------------------- /packages/core/src/testUtils/mockLoggedIn.ts: -------------------------------------------------------------------------------- 1 | /** Sets `app.at_exp` moment cookie so the user will be logged in for 1 hour. */ 2 | function mockIsLoggedIn() { 3 | const expirationMoment = new Date(); 4 | expirationMoment.setHours(expirationMoment.getHours() + 1); 5 | const oneHourInTheFutureInMilliseconds = expirationMoment.getTime() / 1000; 6 | document.cookie = `app.at_exp=${oneHourInTheFutureInMilliseconds}`; 7 | } 8 | 9 | /** Removes the `app.at_exp` cookie. */ 10 | function removeAt_expCookie() { 11 | document.cookie = 'app.at_exp' + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; 12 | } 13 | 14 | export { mockIsLoggedIn, removeAt_expCookie }; 15 | -------------------------------------------------------------------------------- /packages/core/src/testUtils/mockWindowLocation.ts: -------------------------------------------------------------------------------- 1 | import { VitestUtils } from 'vitest'; 2 | 3 | function mockWindowLocation(vi: VitestUtils) { 4 | const mockedLocation = { 5 | ...window.location, 6 | assign: vi.fn(), 7 | }; 8 | vi.spyOn(window, 'location', 'get').mockReturnValue(mockedLocation); 9 | return mockedLocation; 10 | } 11 | 12 | export { mockWindowLocation }; 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "outDir": "dist", 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "declaration": true, 17 | "sourceMap": true, 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | import { resolve } from 'path'; 5 | 6 | export default defineConfig({ 7 | test: { 8 | environment: 'jsdom', 9 | }, 10 | build: { 11 | sourcemap: true, 12 | lib: { 13 | entry: resolve(__dirname, 'src/index.ts'), 14 | name: '@fusionauth-sdk/core', 15 | fileName: 'index', 16 | formats: ['es'], 17 | }, 18 | }, 19 | plugins: [ 20 | dts({ 21 | exclude: ['**/*.test.ts'], 22 | }), 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /packages/lexicon/Readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FusionAuth/fusionauth-javascript-sdk/e77ced9e060ace082d0570fe719b7ec8a9802d26/packages/lexicon/Readme.md -------------------------------------------------------------------------------- /packages/lexicon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fusionauth-sdk/lexicon", 3 | "version": "0.1.0", 4 | "description": "Useful types and classes for the FusionAuth SDKs.", 5 | "author": "FusionAuth", 6 | "license": "Apache", 7 | "private": true, 8 | "type": "module", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.js" 12 | } 13 | }, 14 | "main": "./dist/index.js", 15 | "module": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "files": [ 18 | "dist/**/*" 19 | ], 20 | "scripts": { 21 | "build": "tsc && vite build", 22 | "test": "vitest --run", 23 | "test:watch": "vitest", 24 | "test:coverage": "vitest --coverage" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.11.5", 28 | "vite": "^5.0.12", 29 | "vite-plugin-dts": "^3.7.1", 30 | "vitest": "^1.2.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/lexicon/src/GUID/GUID.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { GUID } from '.'; 4 | 5 | const validRawGUID = 'e9fdb985-9173-4e01-9d73-ac2d60d1dc8e'; 6 | const invalidRawGUID = 'Invalid GUID'; 7 | 8 | describe('GUID', () => { 9 | it('should determine if GUID is valid', () => { 10 | expect(GUID.isValid(validRawGUID)).toBe(true); 11 | }); 12 | 13 | it('should determine if GUID is invalid', () => { 14 | expect(GUID.isValid(invalidRawGUID)).toBe(false); 15 | }); 16 | 17 | it('should create a GUID from a string representing avalid GUID', () => { 18 | const testGUID = GUID.from(validRawGUID); 19 | expect(testGUID.isValid()).toBe(true); 20 | }); 21 | 22 | it('should create a GUIDInvalid from a string representing an invalid GUID', () => { 23 | const testGUID = GUID.from(invalidRawGUID); 24 | expect(testGUID.isValid()).toBe(false); 25 | }); 26 | 27 | it('should provide access to value when value is valid', () => { 28 | const testGUID = GUID.from(validRawGUID); 29 | if (testGUID.isValid()) { 30 | expect(testGUID.value).toBe(validRawGUID); 31 | } 32 | }); 33 | 34 | it('should provide access to invalidValue when value is invalid', () => { 35 | const testGUID = GUID.from(invalidRawGUID); 36 | if (testGUID.isValid()) { 37 | // 38 | } else { 39 | expect(testGUID.invalidValue).toBe(invalidRawGUID); 40 | } 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/lexicon/src/GUID/index.ts: -------------------------------------------------------------------------------- 1 | interface Validator { 2 | isValid: () => boolean; 3 | } 4 | 5 | class GUIDInvalid implements Validator { 6 | readonly invalidValue: string; 7 | 8 | constructor(value: string) { 9 | this.invalidValue = value; 10 | } 11 | 12 | isValid(): this is GUID { 13 | return false; 14 | } 15 | } 16 | 17 | export class GUID implements Validator { 18 | readonly value: string; 19 | 20 | private constructor(value: string) { 21 | this.value = value; 22 | } 23 | 24 | isValid(): this is GUID { 25 | return GUID.isValid(this.value); 26 | } 27 | 28 | static isValid(valueToTest: string): boolean { 29 | const pattern = 30 | /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 31 | return pattern.test(valueToTest); 32 | } 33 | 34 | static from(value: string): T { 35 | if (GUID.isValid(value)) { 36 | return new GUID(value) as T; 37 | } else { 38 | return new GUIDInvalid(value) as T; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/lexicon/src/Path/Path.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { 4 | defaultPathValues, 5 | makePaths, 6 | LoginPath, 7 | LogoutPath, 8 | MePath, 9 | RegisterPath, 10 | TokenRefreshPath, 11 | } from '.'; 12 | 13 | const customPathValues = { 14 | loginPath: '/loginPath', 15 | logoutPath: '/logoutPath', 16 | mePath: '/mePath', 17 | registerPath: '/registerPath', 18 | tokenRefreshPath: '/tokenRefreshPath', 19 | }; 20 | 21 | const pathsWithDefaultValues = makePaths(); 22 | const pathsWithCustomValues = makePaths(customPathValues); 23 | 24 | describe('Paths', () => { 25 | it('should create LoginPath', () => { 26 | const { loginPath } = pathsWithDefaultValues; 27 | expect(loginPath).toBeInstanceOf(LoginPath); 28 | }); 29 | 30 | it('should create LogoutPath', () => { 31 | const { logoutPath } = pathsWithDefaultValues; 32 | expect(logoutPath).toBeInstanceOf(LogoutPath); 33 | }); 34 | 35 | it('should create MePath', () => { 36 | const { mePath } = pathsWithDefaultValues; 37 | expect(mePath).toBeInstanceOf(MePath); 38 | }); 39 | 40 | it('should create RegisterPath', () => { 41 | const { registerPath } = pathsWithDefaultValues; 42 | expect(registerPath).toBeInstanceOf(RegisterPath); 43 | }); 44 | 45 | it('should create TokenRefreshPath', () => { 46 | const { tokenRefreshPath } = pathsWithDefaultValues; 47 | expect(tokenRefreshPath).toBeInstanceOf(TokenRefreshPath); 48 | }); 49 | }); 50 | 51 | describe('default Paths', () => { 52 | it('should create LoginPath with default value', () => { 53 | const { loginPath } = pathsWithDefaultValues; 54 | expect(loginPath.getValue()).toBe(defaultPathValues.login); 55 | }); 56 | 57 | it('should create LogoutPath with default value', () => { 58 | const { logoutPath } = pathsWithDefaultValues; 59 | expect(logoutPath.getValue()).toBe(defaultPathValues.logout); 60 | }); 61 | 62 | it('should create MePath with default value', () => { 63 | const { mePath } = pathsWithDefaultValues; 64 | expect(mePath.getValue()).toBe(defaultPathValues.me); 65 | }); 66 | 67 | it('should create RegisterPath with default value', () => { 68 | const { registerPath } = pathsWithDefaultValues; 69 | expect(registerPath.getValue()).toBe(defaultPathValues.register); 70 | }); 71 | 72 | it('should create TokenRefreshPath with default value', () => { 73 | const { tokenRefreshPath } = pathsWithDefaultValues; 74 | expect(tokenRefreshPath.getValue()).toBe(defaultPathValues.tokenRefresh); 75 | }); 76 | }); 77 | 78 | describe('custom Paths', () => { 79 | it('should create LoginPath with default value', () => { 80 | const { loginPath } = pathsWithCustomValues; 81 | expect(loginPath.getValue()).toBe(customPathValues.loginPath); 82 | }); 83 | 84 | it('should create LogoutPath with default value', () => { 85 | const { logoutPath } = pathsWithCustomValues; 86 | expect(logoutPath.getValue()).toBe(customPathValues.logoutPath); 87 | }); 88 | 89 | it('should create MePath with default value', () => { 90 | const { mePath } = pathsWithCustomValues; 91 | expect(mePath.getValue()).toBe(customPathValues.mePath); 92 | }); 93 | 94 | it('should create RegisterPath with default value', () => { 95 | const { registerPath } = pathsWithCustomValues; 96 | expect(registerPath.getValue()).toBe(customPathValues.registerPath); 97 | }); 98 | 99 | it('should create TokenRefreshPath with default value', () => { 100 | const { tokenRefreshPath } = pathsWithCustomValues; 101 | expect(tokenRefreshPath.getValue()).toBe(customPathValues.tokenRefreshPath); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /packages/lexicon/src/Path/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating classes for the "path" concept 3 | * lets us use robust, strict typing. I.e. 4 | * functions can accept and return specific 5 | * paths. This also lets us bundle up the 6 | * fallback values with each path. 7 | */ 8 | 9 | export const defaultPathValues = { 10 | login: '/app/login', 11 | logout: '/app/logout', 12 | me: '/app/me', 13 | register: '/app/register', 14 | tokenRefresh: '/app/refresh', 15 | }; 16 | 17 | export abstract class Path { 18 | private readonly defaultValue: string; 19 | private readonly value?: string; 20 | 21 | constructor(defaultValue: string, value?: string) { 22 | this.defaultValue = defaultValue; 23 | this.value = value; 24 | } 25 | 26 | getValue() { 27 | return this.value || this.defaultValue; 28 | } 29 | } 30 | 31 | export class LoginPath extends Path { 32 | constructor(customValue?: string) { 33 | super(defaultPathValues.login, customValue); 34 | } 35 | } 36 | 37 | export class LogoutPath extends Path { 38 | constructor(customValue?: string) { 39 | super(defaultPathValues.logout, customValue); 40 | } 41 | } 42 | 43 | export class MePath extends Path { 44 | constructor(customValue?: string) { 45 | super(defaultPathValues.me, customValue); 46 | } 47 | } 48 | 49 | export class RegisterPath extends Path { 50 | constructor(customValue?: string) { 51 | super(defaultPathValues.register, customValue); 52 | } 53 | } 54 | 55 | export class TokenRefreshPath extends Path { 56 | constructor(customValue?: string) { 57 | super(defaultPathValues.tokenRefresh, customValue); 58 | } 59 | } 60 | 61 | export const makePaths = (params?: { 62 | loginPath?: string; 63 | logoutPath?: string; 64 | mePath?: string; 65 | registerPath?: string; 66 | tokenRefreshPath?: string; 67 | }): { 68 | loginPath: LoginPath; 69 | logoutPath: LogoutPath; 70 | mePath: MePath; 71 | registerPath: RegisterPath; 72 | tokenRefreshPath: TokenRefreshPath; 73 | } => { 74 | return { 75 | loginPath: new LoginPath(params?.loginPath), 76 | logoutPath: new LogoutPath(params?.logoutPath), 77 | registerPath: new RegisterPath(params?.registerPath), 78 | tokenRefreshPath: new TokenRefreshPath(params?.tokenRefreshPath), 79 | mePath: new MePath(params?.mePath), 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /packages/lexicon/src/Tools/index.ts: -------------------------------------------------------------------------------- 1 | declare const brand: unique symbol; 2 | 3 | export type Brand = T & { 4 | readonly [brand]: Kind; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/lexicon/src/User/index.ts: -------------------------------------------------------------------------------- 1 | import { Brand } from 'src/Tools'; 2 | import { GUID } from 'src/GUID'; 3 | 4 | export type UserInfo = { 5 | applicationId: ApplicationId; 6 | birthdate: Date; 7 | email: Email; 8 | email_verified: boolean; 9 | family_name: string; 10 | given_name: string; 11 | name?: string; 12 | middle_name?: string; 13 | phone_number?: string; 14 | picture?: string; 15 | preferred_username?: string; 16 | roles: string[]; 17 | scope: string; 18 | sid: SId; 19 | sub: Sub; 20 | tid: TId; 21 | }; 22 | 23 | export type ApplicationId = Brand; 24 | export type SId = Brand; 25 | export type Sub = Brand; 26 | export type TId = Brand; 27 | export type Email = Brand; 28 | 29 | // const exampleUser: User = { 30 | // applicationId: GUID.from("e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"), 31 | // birthdate: new Date("1985-11-23"), 32 | // email: "richard@example.com" as Email, 33 | // email_verified: true, 34 | // family_name: "Hendricks", 35 | // given_name: "Richard", 36 | // roles: [], 37 | // scope: "openid offline_access", 38 | // sid: GUID.from("689a2b62-d7e1-480b-bc25-c7fc8f0f3418"), 39 | // sub: GUID.from("00000000-0000-0000-0000-111111111111"), 40 | // tid: GUID.from("d7d09513-a3f5-401c-9685-34ab6c552453"), 41 | // } 42 | -------------------------------------------------------------------------------- /packages/lexicon/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GUID/index.ts'; 2 | export * from './Path/index.ts'; 3 | export * from './Tools/index.ts'; 4 | export * from './User/index.ts'; 5 | -------------------------------------------------------------------------------- /packages/lexicon/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "paths": {} 7 | }, 8 | "include": ["src"], 9 | "exclude": ["**/*.test.ts", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/lexicon/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { resolve } from 'node:path'; 3 | import { defineConfig } from 'vite'; 4 | import dts from 'vite-plugin-dts'; 5 | 6 | export default defineConfig({ 7 | build: { 8 | lib: { 9 | entry: resolve(__dirname, 'src/index.ts'), 10 | formats: ['es'], 11 | name: 'index', 12 | fileName: 'index', 13 | }, 14 | }, 15 | plugins: [dts({ rollupTypes: true })], 16 | test: {}, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/sdk-angular/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": false, 3 | "extends": "../../.eslintrc", 4 | "rules": { 5 | "@typescript-eslint/ban-types": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/sdk-angular/CHANGES.md: -------------------------------------------------------------------------------- 1 | @fusionauth/angular-sdk Changes 2 | 3 | Changes in 1.3.0 4 | 5 | - The `error` passed to [`onAutoRefreshFailure`](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/main/packages/sdk-angular/docs/interfaces/FusionAuthConfig.md#onautorefreshfailure) should now include the response status code. See [issue #151](https://github.com/FusionAuth/fusionauth-javascript-sdk/issues/151). 6 | 7 | Changes in 1.2.0 8 | 9 | - `userInfo` can now be custom typed with an optional generic argument. This may be helpful for SDK users with a non-hosted backend. Below is an example of what it looks like. 10 | 11 | ```typescript 12 | interface MyUserInfo { 13 | specialProperty: string; 14 | // ...etc 15 | } 16 | 17 | @Component({ 18 | template: `

{{ userInfo?.specialProperty }}

`, 19 | }) 20 | class AppComponent implements OnInit { 21 | private fusionauth: FusionAuthService = 22 | inject(FusionAuthService); 23 | userInfo: MyUserInfo | null = null; 24 | 25 | ngOnInit() { 26 | this.subscription = this.fusionauth.getUserInfoObservable().subscribe({ 27 | next: userInfo => (this.userInfo = userInfo), 28 | }); 29 | } 30 | } 31 | ``` 32 | 33 | - `manageAccount` function and button added. [Self service account management](https://fusionauth.io/docs/lifecycle/manage-users/account-management/) is only available in FusionAuth paid plans. 34 | - Some missing optional properties have been added to the [`UserInfo`](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/main/packages/sdk-angular/docs/interfaces/UserInfo.md) type. 35 | 36 | Changes in 1.1.0 37 | 38 | - SDK now supports Angular apps using SSR. No additional configuration is needed. 39 | - Adds `postLogoutRedirectUri` option to [`FusionAuthConfig`](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/main/packages/sdk-angular/docs/interfaces/FusionAuthConfig.md#postLogoutRedirectUri) 40 | - SDK's peer dependencies now specified with `>=`. 41 | - The distribution is fully sourcemapped--sourcemaps were not included for the @fusionauth-sdk/core package in v1.0.2 42 | 43 | Changes in 1.0.2 44 | 45 | - Adds `onAutoRefreshFailure` option to `FusionAuthConfig`. 46 | - _Bug fix_ `isLoggedIn$` observable property is set to `false` after token refresh. [See issue #82](https://github.com/FusionAuth/fusionauth-javascript-sdk/issues/82) 47 | 48 | Changes in 1.0.1 49 | 50 | Several enhancements are availabe on the `FusionAuthService` 51 | 52 | - `getUserInfoObservable` provides an API that Angular components can subscribe to. Provides an api for handling pending state, as well as callbacks for success and error. 53 | - Adds error handling to the get user info request. 54 | - `FusionAuthService` provides observable `isLoggedIn$` property that updates within the Angular change dectection system if the access token expires. 55 | - `shouldAutoRefresh` option for the SDK to automatically handle refreshing the access token. Defaults to `false`. 56 | - `scope` has been added back to the `FusionAuthProviderConfig` to be more compatible with [FusionAuth v1.50](https://fusionauth.io/docs/release-notes/#version-1-50-0) 57 | - Full API documentation with TypeDoc now available (see official README from NPM). 58 | -------------------------------------------------------------------------------- /packages/sdk-angular/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "fusionauth-angular-sdk": { 7 | "projectType": "library", 8 | "root": "projects/fusionauth-angular-sdk", 9 | "sourceRoot": "projects/fusionauth-angular-sdk/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/fusionauth-angular-sdk/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/fusionauth-angular-sdk/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/fusionauth-angular-sdk/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "tsConfig": "projects/fusionauth-angular-sdk/tsconfig.spec.json", 31 | "polyfills": ["zone.js", "zone.js/testing"] 32 | } 33 | } 34 | } 35 | } 36 | }, 37 | "cli": { 38 | "analytics": false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /packages/sdk-angular/docs/README.md: -------------------------------------------------------------------------------- 1 | @fusionauth/angular-sdk / [Exports](modules.md) 2 | 3 | # FusionauthAngularSdk 4 | 5 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.0. 6 | 7 | ## Build 8 | 9 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 10 | 11 | ## Running unit tests 12 | 13 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 14 | 15 | ## Further help 16 | 17 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 18 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/classes/FusionAuthAccountButtonComponent.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](../README.md) / [Exports](../modules.md) / FusionAuthAccountButtonComponent 2 | 3 | # Class: FusionAuthAccountButtonComponent 4 | 5 | ## Table of contents 6 | 7 | ### Constructors 8 | 9 | - [constructor](FusionAuthAccountButtonComponent.md#constructor) 10 | 11 | ### Properties 12 | 13 | - [fusionAuth](FusionAuthAccountButtonComponent.md#fusionauth) 14 | 15 | ### Methods 16 | 17 | - [manageAccount](FusionAuthAccountButtonComponent.md#manageaccount) 18 | 19 | ## Constructors 20 | 21 | ### constructor 22 | 23 | • **new FusionAuthAccountButtonComponent**(`fusionAuth`): [`FusionAuthAccountButtonComponent`](FusionAuthAccountButtonComponent.md) 24 | 25 | #### Parameters 26 | 27 | | Name | Type | 28 | | :----------- | :------------------------------------------------------------------------------------- | 29 | | `fusionAuth` | [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> | 30 | 31 | #### Returns 32 | 33 | [`FusionAuthAccountButtonComponent`](FusionAuthAccountButtonComponent.md) 34 | 35 | #### Defined in 36 | 37 | [lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts#L10) 38 | 39 | ## Properties 40 | 41 | ### fusionAuth 42 | 43 | • `Private` **fusionAuth**: [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> 44 | 45 | #### Defined in 46 | 47 | [lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts#L10) 48 | 49 | ## Methods 50 | 51 | ### manageAccount 52 | 53 | ▸ **manageAccount**(): `void` 54 | 55 | #### Returns 56 | 57 | `void` 58 | 59 | #### Defined in 60 | 61 | [lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts#L12) 62 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/classes/FusionAuthLoginButtonComponent.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](../README.md) / [Exports](../modules.md) / FusionAuthLoginButtonComponent 2 | 3 | # Class: FusionAuthLoginButtonComponent 4 | 5 | ## Table of contents 6 | 7 | ### Constructors 8 | 9 | - [constructor](FusionAuthLoginButtonComponent.md#constructor) 10 | 11 | ### Properties 12 | 13 | - [fusionAuth](FusionAuthLoginButtonComponent.md#fusionauth) 14 | - [state](FusionAuthLoginButtonComponent.md#state) 15 | 16 | ### Methods 17 | 18 | - [login](FusionAuthLoginButtonComponent.md#login) 19 | 20 | ## Constructors 21 | 22 | ### constructor 23 | 24 | • **new FusionAuthLoginButtonComponent**(`fusionAuth`): [`FusionAuthLoginButtonComponent`](FusionAuthLoginButtonComponent.md) 25 | 26 | #### Parameters 27 | 28 | | Name | Type | 29 | | :----------- | :------------------------------------------------------------------------------------- | 30 | | `fusionAuth` | [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> | 31 | 32 | #### Returns 33 | 34 | [`FusionAuthLoginButtonComponent`](FusionAuthLoginButtonComponent.md) 35 | 36 | #### Defined in 37 | 38 | [lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts#L12) 39 | 40 | ## Properties 41 | 42 | ### fusionAuth 43 | 44 | • `Private` **fusionAuth**: [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> 45 | 46 | #### Defined in 47 | 48 | [lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts#L12) 49 | 50 | --- 51 | 52 | ### state 53 | 54 | • **state**: `undefined` \| `string` 55 | 56 | #### Defined in 57 | 58 | [lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts#L10) 59 | 60 | ## Methods 61 | 62 | ### login 63 | 64 | ▸ **login**(): `void` 65 | 66 | #### Returns 67 | 68 | `void` 69 | 70 | #### Defined in 71 | 72 | [lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts:14](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts#L14) 73 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/classes/FusionAuthLogoutButtonComponent.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](../README.md) / [Exports](../modules.md) / FusionAuthLogoutButtonComponent 2 | 3 | # Class: FusionAuthLogoutButtonComponent 4 | 5 | ## Table of contents 6 | 7 | ### Constructors 8 | 9 | - [constructor](FusionAuthLogoutButtonComponent.md#constructor) 10 | 11 | ### Properties 12 | 13 | - [fusionAuth](FusionAuthLogoutButtonComponent.md#fusionauth) 14 | 15 | ### Methods 16 | 17 | - [logout](FusionAuthLogoutButtonComponent.md#logout) 18 | 19 | ## Constructors 20 | 21 | ### constructor 22 | 23 | • **new FusionAuthLogoutButtonComponent**(`fusionAuth`): [`FusionAuthLogoutButtonComponent`](FusionAuthLogoutButtonComponent.md) 24 | 25 | #### Parameters 26 | 27 | | Name | Type | 28 | | :----------- | :------------------------------------------------------------------------------------- | 29 | | `fusionAuth` | [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> | 30 | 31 | #### Returns 32 | 33 | [`FusionAuthLogoutButtonComponent`](FusionAuthLogoutButtonComponent.md) 34 | 35 | #### Defined in 36 | 37 | [lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts#L10) 38 | 39 | ## Properties 40 | 41 | ### fusionAuth 42 | 43 | • `Private` **fusionAuth**: [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> 44 | 45 | #### Defined in 46 | 47 | [lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts#L10) 48 | 49 | ## Methods 50 | 51 | ### logout 52 | 53 | ▸ **logout**(): `void` 54 | 55 | #### Returns 56 | 57 | `void` 58 | 59 | #### Defined in 60 | 61 | [lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts#L12) 62 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/classes/FusionAuthModule.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](../README.md) / [Exports](../modules.md) / FusionAuthModule 2 | 3 | # Class: FusionAuthModule 4 | 5 | ## Table of contents 6 | 7 | ### Constructors 8 | 9 | - [constructor](FusionAuthModule.md#constructor) 10 | 11 | ### Methods 12 | 13 | - [forRoot](FusionAuthModule.md#forroot) 14 | 15 | ## Constructors 16 | 17 | ### constructor 18 | 19 | • **new FusionAuthModule**(): [`FusionAuthModule`](FusionAuthModule.md) 20 | 21 | #### Returns 22 | 23 | [`FusionAuthModule`](FusionAuthModule.md) 24 | 25 | ## Methods 26 | 27 | ### forRoot 28 | 29 | ▸ **forRoot**(`fusionAuthConfig`): `ModuleWithProviders`\<[`FusionAuthModule`](FusionAuthModule.md)\> 30 | 31 | #### Parameters 32 | 33 | | Name | Type | 34 | | :----------------- | :------------------------------------------------------ | 35 | | `fusionAuthConfig` | [`FusionAuthConfig`](../interfaces/FusionAuthConfig.md) | 36 | 37 | #### Returns 38 | 39 | `ModuleWithProviders`\<[`FusionAuthModule`](FusionAuthModule.md)\> 40 | 41 | #### Defined in 42 | 43 | [lib/fusion-auth.module.ts:26](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/fusion-auth.module.ts#L26) 44 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/classes/FusionAuthRegisterButtonComponent.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](../README.md) / [Exports](../modules.md) / FusionAuthRegisterButtonComponent 2 | 3 | # Class: FusionAuthRegisterButtonComponent 4 | 5 | ## Table of contents 6 | 7 | ### Constructors 8 | 9 | - [constructor](FusionAuthRegisterButtonComponent.md#constructor) 10 | 11 | ### Properties 12 | 13 | - [fusionAuth](FusionAuthRegisterButtonComponent.md#fusionauth) 14 | - [state](FusionAuthRegisterButtonComponent.md#state) 15 | 16 | ### Methods 17 | 18 | - [register](FusionAuthRegisterButtonComponent.md#register) 19 | 20 | ## Constructors 21 | 22 | ### constructor 23 | 24 | • **new FusionAuthRegisterButtonComponent**(`fusionAuth`): [`FusionAuthRegisterButtonComponent`](FusionAuthRegisterButtonComponent.md) 25 | 26 | #### Parameters 27 | 28 | | Name | Type | 29 | | :----------- | :------------------------------------------------------------------------------------- | 30 | | `fusionAuth` | [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> | 31 | 32 | #### Returns 33 | 34 | [`FusionAuthRegisterButtonComponent`](FusionAuthRegisterButtonComponent.md) 35 | 36 | #### Defined in 37 | 38 | [lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts#L12) 39 | 40 | ## Properties 41 | 42 | ### fusionAuth 43 | 44 | • `Private` **fusionAuth**: [`FusionAuthService`](FusionAuthService.md)\<[`UserInfo`](../interfaces/UserInfo.md)\> 45 | 46 | #### Defined in 47 | 48 | [lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts:12](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts#L12) 49 | 50 | --- 51 | 52 | ### state 53 | 54 | • **state**: `undefined` \| `string` 55 | 56 | #### Defined in 57 | 58 | [lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts#L10) 59 | 60 | ## Methods 61 | 62 | ### register 63 | 64 | ▸ **register**(): `void` 65 | 66 | #### Returns 67 | 68 | `void` 69 | 70 | #### Defined in 71 | 72 | [lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts:14](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/80c01c9ccb450a2187bc0d2cc65fa8c9c38cfabe/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts#L14) 73 | -------------------------------------------------------------------------------- /packages/sdk-angular/docs/modules.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/angular-sdk](README.md) / Exports 2 | 3 | # @fusionauth/angular-sdk 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [FusionAuthAccountButtonComponent](classes/FusionAuthAccountButtonComponent.md) 10 | - [FusionAuthLoginButtonComponent](classes/FusionAuthLoginButtonComponent.md) 11 | - [FusionAuthLogoutButtonComponent](classes/FusionAuthLogoutButtonComponent.md) 12 | - [FusionAuthModule](classes/FusionAuthModule.md) 13 | - [FusionAuthRegisterButtonComponent](classes/FusionAuthRegisterButtonComponent.md) 14 | - [FusionAuthService](classes/FusionAuthService.md) 15 | 16 | ### Interfaces 17 | 18 | - [FusionAuthConfig](interfaces/FusionAuthConfig.md) 19 | - [UserInfo](interfaces/UserInfo.md) 20 | -------------------------------------------------------------------------------- /packages/sdk-angular/getSDKCore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is responsible for copying @fusionauth-sdk/core into this Angular package without transpiling it. 3 | * This is to allow Angular's library builder to bundle the core code into dist. 4 | * Further details here: https://github.com/FusionAuth/fusionauth-javascript-sdk/issues/84 5 | */ 6 | 7 | const fs = require('fs').promises; 8 | const { existsSync } = require('fs'); 9 | const path = require('path'); 10 | 11 | async function copyDirectory(src, dest) { 12 | await fs.mkdir(dest, { recursive: true }); 13 | 14 | const srcDirContents = await fs.readdir(src, { withFileTypes: true }); 15 | 16 | for (const item of srcDirContents) { 17 | if (item.isFile() && item.name.endsWith('.test.ts')) { 18 | continue; 19 | } 20 | 21 | const itemSrc = path.resolve(src, item.name); 22 | const itemDest = path.resolve(dest, item.name); 23 | 24 | if (item.isDirectory()) { 25 | await copyDirectory(itemSrc, itemDest); 26 | } else if (item.isFile()) { 27 | await fs.copyFile(itemSrc, itemDest); 28 | } 29 | } 30 | } 31 | 32 | async function pruneDirectory(dir) { 33 | if (!existsSync(dir)) { 34 | // check if directory can be accessed before reading from it 35 | return; 36 | } 37 | 38 | try { 39 | const directoryContents = await fs.readdir(dir, { 40 | withFileTypes: true, 41 | }); 42 | 43 | await Promise.all( 44 | directoryContents.map(async item => { 45 | const itemPath = path.resolve(dir, item.name); 46 | 47 | if (item.isDirectory()) { 48 | await pruneDirectory(itemPath); 49 | } else { 50 | await fs.unlink(itemPath); 51 | } 52 | }), 53 | ); 54 | } catch (err) { 55 | throw err; 56 | } 57 | } 58 | 59 | (async () => { 60 | const coreSrc = '../core/src'; 61 | const coreDest = 'projects/fusionauth-angular-sdk/src/sdkcore'; 62 | 63 | await pruneDirectory(coreDest); 64 | await copyDirectory(coreSrc, coreDest) 65 | .then(() => 66 | console.log(`Successfully copied @fusionauth-sdk/core into ${coreDest}`), 67 | ) 68 | .catch(err => 69 | console.error('Error copying core src directory for angular:', err), 70 | ); 71 | })(); 72 | -------------------------------------------------------------------------------- /packages/sdk-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdk-angular-workspace", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "ng": "ng", 6 | "get-sdk-core": "node getSDKCore.js", 7 | "build": "yarn get-sdk-core && ng build && cp README.md dist/fusionauth-angular-sdk/README.md", 8 | "test": "ng test --watch=false --browsers=ChromeHeadless", 9 | "test:watch": "ng test", 10 | "docs": "typedoc --plugin typedoc-plugin-markdown" 11 | }, 12 | "private": false, 13 | "dependencies": { 14 | "@angular/animations": "^17.2.0", 15 | "@angular/common": "^17.2.0", 16 | "@angular/compiler": "^17.2.0", 17 | "@angular/core": "^17.2.0", 18 | "@angular/forms": "^17.2.0", 19 | "@angular/platform-browser": "^17.2.0", 20 | "@angular/platform-browser-dynamic": "^17.2.0", 21 | "@angular/router": "^17.2.0", 22 | "rxjs": "~7.8.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "~0.14.3" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^17.2.3", 28 | "@angular/cli": "^17.2.0", 29 | "@angular/compiler-cli": "^17.2.0", 30 | "typedoc": "^0.25.13", 31 | "typedoc-plugin-markdown": "^3.17.1", 32 | "@types/jasmine": "~5.1.0", 33 | "jasmine-core": "~5.1.0", 34 | "karma": "~6.4.0", 35 | "karma-chrome-launcher": "~3.2.0", 36 | "karma-coverage": "~2.2.0", 37 | "karma-jasmine": "~5.1.0", 38 | "karma-jasmine-html-reporter": "~2.1.0", 39 | "ng-packagr": "^17.2.0", 40 | "typescript": "~5.3.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/README.md: -------------------------------------------------------------------------------- 1 | # FusionauthAngularSdk 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.0. 4 | 5 | ## Build 6 | 7 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 8 | 9 | ## Running unit tests 10 | 11 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 12 | 13 | ## Further help 14 | 15 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/fusionauth-angular-sdk", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fusionauth/angular-sdk", 3 | "version": "1.3.0", 4 | "peerDependencies": { 5 | "@angular/common": ">=17.2.0", 6 | "@angular/core": ">=17.2.0" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.3.0" 10 | }, 11 | "sideEffects": false 12 | } 13 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/SSRCookieAdapter.ts: -------------------------------------------------------------------------------- 1 | import { CookieAdapter } from '../sdkcore'; 2 | 3 | /** An adapter class that supports accessing cookies with SSR */ 4 | export class SSRCookieAdapter implements CookieAdapter { 5 | constructor(private isBrowser: boolean) {} 6 | 7 | at_exp(cookieName: string = 'app.at_exp') { 8 | if (!this.isBrowser) { 9 | return; 10 | } 11 | 12 | try { 13 | const expCookie = document.cookie 14 | .split('; ') 15 | .map(c => c.split('=')) 16 | .find(([name]) => name === cookieName); 17 | return expCookie?.[1]; 18 | } catch (error) { 19 | console.error('Error within the SSRCookieAdapter: ', error); 20 | return -1; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fa-button.scss: -------------------------------------------------------------------------------- 1 | .fa-button { 2 | padding: 16px 16px 13px 16px; 3 | border-radius: 8px; 4 | background-color: #083b94; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 6 | Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 7 | font-size: 18px; 8 | font-weight: 600; 9 | text-align: center; 10 | color: #fff; 11 | 12 | &:hover { 13 | cursor: pointer; 14 | } 15 | 16 | width: 400px; 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.scss: -------------------------------------------------------------------------------- 1 | .fa-button { 2 | padding: 16px 16px 13px 16px; 3 | border-radius: 8px; 4 | background-color: #083b94; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 6 | Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 7 | font-size: 18px; 8 | font-weight: 600; 9 | text-align: center; 10 | color: #fff; 11 | &:hover { 12 | cursor: pointer; 13 | } 14 | width: 400px; 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FusionAuthAccountButtonComponent } from './fusion-auth-account-button.component'; 4 | import { FusionAuthService } from '../../fusion-auth.service'; 5 | 6 | describe('FusionauthAccountButtonComponent', () => { 7 | let component: FusionAuthAccountButtonComponent; 8 | let fixture: ComponentFixture; 9 | const mockService = jasmine.createSpyObj('FusionAuthService', [ 10 | 'manageAccount', 11 | ]); 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [FusionAuthAccountButtonComponent], 16 | providers: [{ provide: FusionAuthService, useValue: mockService }], 17 | }).compileComponents(); 18 | 19 | fixture = TestBed.createComponent(FusionAuthAccountButtonComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should invoke the `manageAccount` method', () => { 25 | expect(component).toBeTruthy(); 26 | component.manageAccount(); 27 | expect(mockService.manageAccount).toHaveBeenCalled(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-account.button/fusion-auth-account-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FusionAuthService } from '../../fusion-auth.service'; 3 | 4 | @Component({ 5 | selector: 'fa-account', 6 | templateUrl: './fusion-auth-account-button.component.html', 7 | styleUrls: ['./fusion-auth-account-button.component.scss'], 8 | }) 9 | export class FusionAuthAccountButtonComponent { 10 | constructor(private fusionAuth: FusionAuthService) {} 11 | 12 | manageAccount() { 13 | this.fusionAuth.manageAccount(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FusionAuth/fusionauth-javascript-sdk/e77ced9e060ace082d0570fe719b7ec8a9802d26/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.scss -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FusionAuthLoginButtonComponent } from './fusion-auth-login-button.component'; 4 | import { FusionAuthService } from '../../fusion-auth.service'; 5 | 6 | describe('FusionauthLoginButtonComponent', () => { 7 | let component: FusionAuthLoginButtonComponent; 8 | let fixture: ComponentFixture; 9 | const mockService = jasmine.createSpyObj('FusionAuthService', ['startLogin']); 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [FusionAuthLoginButtonComponent], 14 | providers: [{ provide: FusionAuthService, useValue: mockService }], 15 | }).compileComponents(); 16 | 17 | fixture = TestBed.createComponent(FusionAuthLoginButtonComponent); 18 | component = fixture.componentInstance; 19 | }); 20 | 21 | it('should invoke the startLogin method', () => { 22 | expect(component).toBeTruthy(); 23 | const stateValue = 'state-value'; 24 | component.state = stateValue; 25 | fixture.detectChanges(); 26 | 27 | component.login(); 28 | expect(mockService.startLogin).toHaveBeenCalledWith(stateValue); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-login.button/fusion-auth-login-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { FusionAuthService } from '../../fusion-auth.service'; 3 | 4 | @Component({ 5 | selector: 'fa-login', 6 | templateUrl: './fusion-auth-login-button.component.html', 7 | styleUrls: ['../fa-button.scss', './fusion-auth-login-button.component.scss'], 8 | }) 9 | export class FusionAuthLoginButtonComponent { 10 | @Input() state: string | undefined; 11 | 12 | constructor(private fusionAuth: FusionAuthService) {} 13 | 14 | login() { 15 | this.fusionAuth.startLogin(this.state); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FusionAuth/fusionauth-javascript-sdk/e77ced9e060ace082d0570fe719b7ec8a9802d26/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.scss -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FusionAuthLogoutButtonComponent } from './fusion-auth-logout-button.component'; 4 | import { FusionAuthService } from '../../fusion-auth.service'; 5 | 6 | describe('FusionauthLogoutButtonComponent', () => { 7 | let component: FusionAuthLogoutButtonComponent; 8 | let fixture: ComponentFixture; 9 | const mockService = jasmine.createSpyObj('FusionAuthService', ['logout']); 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [FusionAuthLogoutButtonComponent], 14 | providers: [{ provide: FusionAuthService, useValue: mockService }], 15 | }).compileComponents(); 16 | 17 | fixture = TestBed.createComponent(FusionAuthLogoutButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should invoke the logout method', () => { 23 | expect(component).toBeTruthy(); 24 | component.logout(); 25 | expect(mockService.logout).toHaveBeenCalled(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-logout.button/fusion-auth-logout-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FusionAuthService } from '../../fusion-auth.service'; 3 | 4 | @Component({ 5 | selector: 'fa-logout', 6 | templateUrl: './fusion-auth-logout-button.component.html', 7 | styleUrls: [ 8 | '../fa-button.scss', 9 | './fusion-auth-logout-button.component.scss', 10 | ], 11 | }) 12 | export class FusionAuthLogoutButtonComponent { 13 | constructor(private fusionAuth: FusionAuthService) {} 14 | 15 | logout() { 16 | this.fusionAuth.logout(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FusionAuth/fusionauth-javascript-sdk/e77ced9e060ace082d0570fe719b7ec8a9802d26/packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.scss -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FusionAuthRegisterButtonComponent } from './fusion-auth-register-button.component'; 4 | import { FusionAuthService } from '../../fusion-auth.service'; 5 | 6 | describe('FusionauthRegisterButtonComponent', () => { 7 | let component: FusionAuthRegisterButtonComponent; 8 | let fixture: ComponentFixture; 9 | const mockService = jasmine.createSpyObj('FusionAuthService', [ 10 | 'startRegistration', 11 | ]); 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [FusionAuthRegisterButtonComponent], 16 | providers: [{ provide: FusionAuthService, useValue: mockService }], 17 | }).compileComponents(); 18 | 19 | fixture = TestBed.createComponent(FusionAuthRegisterButtonComponent); 20 | component = fixture.componentInstance; 21 | }); 22 | 23 | it('should invoke the startRegistration method', () => { 24 | expect(component).toBeTruthy(); 25 | 26 | const stateValue = 'hello-world'; 27 | component.state = stateValue; 28 | fixture.detectChanges(); 29 | 30 | component.register(); 31 | expect(mockService.startRegistration).toHaveBeenCalledWith(stateValue); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/components/fusionauth-register.button/fusion-auth-register-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { FusionAuthService } from '../../fusion-auth.service'; 3 | 4 | @Component({ 5 | selector: 'fa-register', 6 | templateUrl: './fusion-auth-register-button.component.html', 7 | styleUrls: [ 8 | '../fa-button.scss', 9 | './fusion-auth-register-button.component.scss', 10 | ], 11 | }) 12 | export class FusionAuthRegisterButtonComponent { 13 | @Input() state: string | undefined; 14 | 15 | constructor(private fusionAuth: FusionAuthService) {} 16 | 17 | register() { 18 | this.fusionAuth.startRegistration(this.state); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/fusion-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { FusionAuthConfig } from './types'; 3 | import { FusionAuthService } from './fusion-auth.service'; 4 | import { FUSIONAUTH_SERVICE_CONFIG } from './injectionToken'; 5 | import { FusionAuthLoginButtonComponent } from './components/fusionauth-login.button/fusion-auth-login-button.component'; 6 | import { FusionAuthLogoutButtonComponent } from './components/fusionauth-logout.button/fusion-auth-logout-button.component'; 7 | import { FusionAuthRegisterButtonComponent } from './components/fusionauth-register.button/fusion-auth-register-button.component'; 8 | import { FusionAuthAccountButtonComponent } from './components/fusionauth-account.button/fusion-auth-account-button.component'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | FusionAuthLoginButtonComponent, 13 | FusionAuthLogoutButtonComponent, 14 | FusionAuthRegisterButtonComponent, 15 | FusionAuthAccountButtonComponent, 16 | ], 17 | imports: [], 18 | exports: [ 19 | FusionAuthLoginButtonComponent, 20 | FusionAuthLogoutButtonComponent, 21 | FusionAuthRegisterButtonComponent, 22 | FusionAuthAccountButtonComponent, 23 | ], 24 | }) 25 | export class FusionAuthModule { 26 | static forRoot( 27 | fusionAuthConfig: FusionAuthConfig, 28 | ): ModuleWithProviders { 29 | return { 30 | ngModule: FusionAuthModule, 31 | providers: [ 32 | { provide: FUSIONAUTH_SERVICE_CONFIG, useValue: fusionAuthConfig }, 33 | FusionAuthService, 34 | ], 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/fusion-auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { fakeAsync, flush, tick } from '@angular/core/testing'; 3 | import { take } from 'rxjs'; 4 | 5 | import { FusionAuthConfig } from './types'; 6 | import { FusionAuthService } from './fusion-auth.service'; 7 | import { FusionAuthModule } from './fusion-auth.module'; 8 | import { mockIsLoggedIn, removeAt_expCookie } from '../sdkcore'; 9 | 10 | const config: FusionAuthConfig = { 11 | clientId: 'a-client-id', 12 | redirectUri: 'http://my-app.com', 13 | serverUrl: 'http://localhost:9011', 14 | }; 15 | 16 | function configureTestingModule(config: FusionAuthConfig) { 17 | TestBed.configureTestingModule({ 18 | imports: [FusionAuthModule.forRoot(config)], 19 | }); 20 | return TestBed.inject(FusionAuthService); 21 | } 22 | 23 | describe('FusionAuthService', () => { 24 | afterEach(() => { 25 | removeAt_expCookie(); 26 | localStorage.clear(); 27 | }); 28 | 29 | it('Can be configured to automatically handle getting userInfo', (done: DoneFn) => { 30 | mockIsLoggedIn(); 31 | 32 | const user = { 33 | email: 'richard@test.com', 34 | customTrait: 'something special', 35 | }; 36 | spyOn(window, 'fetch').and.resolveTo( 37 | new Response(JSON.stringify(user), { status: 200 }), 38 | ); 39 | 40 | const service: FusionAuthService = 41 | configureTestingModule(config); 42 | 43 | service.getUserInfoObservable().subscribe({ 44 | next: userInfo => { 45 | expect(userInfo.email).toBe('richard@test.com'); 46 | expect(userInfo.customTrait).toBe('something special'); 47 | done(); 48 | }, 49 | }); 50 | }); 51 | 52 | it('Handles a failure to get userInfo', (done: DoneFn) => { 53 | mockIsLoggedIn(); 54 | 55 | const responseStatus = 400; 56 | spyOn(window, 'fetch').and.resolveTo( 57 | new Response(null, { status: responseStatus }), 58 | ); 59 | 60 | const service = configureTestingModule(config); 61 | 62 | service.getUserInfoObservable().subscribe({ 63 | error: error => { 64 | expect(error?.message).toBe( 65 | `Unable to fetch userInfo in fusionauth. Request failed with status code ${responseStatus}`, 66 | ); 67 | done(); 68 | }, 69 | }); 70 | }); 71 | 72 | it("Contains an observable 'isLoggedin$' property that becomes false as the access token expires.", fakeAsync(() => { 73 | mockIsLoggedIn(); // sets `app.at_exp` cookie so user is logged in for 1 hour. 74 | 75 | const service = configureTestingModule(config); 76 | 77 | tick(60 * 59 * 1000); 78 | service.isLoggedIn$.pipe(take(1)).subscribe(isLoggedIn => { 79 | expect(isLoggedIn).toBe(true); 80 | }); 81 | 82 | tick(60 * 1000); // access token expires 83 | service.isLoggedIn$.pipe(take(1)).subscribe(isLoggedIn => { 84 | expect(isLoggedIn).toBe(false); 85 | }); 86 | 87 | flush(); 88 | })); 89 | 90 | it('Can be configured to automatically refresh the access token', () => { 91 | mockIsLoggedIn(); 92 | const spy = spyOn(FusionAuthService.prototype, 'initAutoRefresh'); 93 | 94 | const service = configureTestingModule({ 95 | ...config, 96 | shouldAutoRefresh: true, 97 | }); 98 | 99 | expect(service.isLoggedIn()).toBe(true); 100 | expect(spy).toHaveBeenCalledTimes(1); 101 | }); 102 | 103 | it("Does not invoke 'initAutoRefresh' if the user is not logged in", () => { 104 | const initAutoRefreshSpy = spyOn( 105 | FusionAuthService.prototype, 106 | 'initAutoRefresh', 107 | ); 108 | 109 | const service = configureTestingModule({ 110 | ...config, 111 | shouldAutoRefresh: true, 112 | }); 113 | 114 | expect(service.isLoggedIn()).toBe(false); 115 | expect(initAutoRefreshSpy).not.toHaveBeenCalled(); 116 | }); 117 | 118 | it("Invokes an 'onRedirect' callback", () => { 119 | mockIsLoggedIn(); 120 | 121 | const stateValue = '/welcome-page'; 122 | localStorage.setItem('fa-sdk-redirect-value', `abc123:${stateValue}`); 123 | 124 | const onRedirect = jasmine.createSpy('onRedirectSpy'); 125 | configureTestingModule({ ...config, onRedirect }); 126 | 127 | expect(onRedirect).toHaveBeenCalledWith('/welcome-page'); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/fusion-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, PLATFORM_ID } from '@angular/core'; 2 | import { isPlatformBrowser } from '@angular/common'; 3 | import { Observable, catchError, BehaviorSubject } from 'rxjs'; 4 | 5 | import { SDKCore } from '../sdkcore'; 6 | import { SSRCookieAdapter } from './SSRCookieAdapter'; 7 | import { FusionAuthConfig, UserInfo } from './types'; 8 | import { FUSIONAUTH_SERVICE_CONFIG } from './injectionToken'; 9 | 10 | /** 11 | * Service class to use with FusionAuth backend endpoints. 12 | */ 13 | @Injectable({ 14 | providedIn: 'root', 15 | }) 16 | export class FusionAuthService { 17 | private core: SDKCore; 18 | private autoRefreshTimer?: NodeJS.Timeout; 19 | private isLoggedInSubject: BehaviorSubject; 20 | 21 | constructor( 22 | @Inject(FUSIONAUTH_SERVICE_CONFIG) config: FusionAuthConfig, 23 | @Inject(PLATFORM_ID) platformId: Object, 24 | ) { 25 | this.core = new SDKCore({ 26 | ...config, 27 | onTokenExpiration: () => { 28 | this.isLoggedInSubject.next(false); 29 | }, 30 | cookieAdapter: new SSRCookieAdapter(isPlatformBrowser(platformId)), 31 | }); 32 | 33 | this.isLoggedInSubject = new BehaviorSubject(this.core.isLoggedIn); 34 | this.isLoggedIn$ = this.isLoggedInSubject.asObservable(); 35 | 36 | this.core.handlePostRedirect(config.onRedirect); 37 | 38 | if (config.shouldAutoRefresh && this.core.isLoggedIn) { 39 | this.initAutoRefresh(); 40 | } 41 | } 42 | 43 | /** An observable representing whether the user is logged in. */ 44 | isLoggedIn$: Observable; 45 | 46 | /** A function that returns whether the user is logged in. This returned value is non-observable. */ 47 | isLoggedIn() { 48 | return this.core.isLoggedIn; 49 | } 50 | 51 | /** 52 | * Refreshes the access token a single time. 53 | * Automatic token refreshing can be enabled if the SDK is configured with `shouldAutoRefresh`. 54 | */ 55 | async refreshToken(): Promise { 56 | return await this.core.refreshToken(); 57 | } 58 | 59 | /** 60 | * Initializes automatic access token refreshing. 61 | * This is handled automatically if the SDK is configured with `shouldAutoRefresh`. 62 | */ 63 | initAutoRefresh(): void { 64 | if (this.autoRefreshTimer) { 65 | clearTimeout(this.autoRefreshTimer); 66 | } 67 | 68 | this.autoRefreshTimer = this.core.initAutoRefresh(); 69 | } 70 | 71 | /** 72 | * Returns an observable request that fetches userInfo, and catches error. 73 | */ 74 | getUserInfoObservable(callbacks?: { 75 | onBegin?: () => void; 76 | onDone?: () => void; 77 | }): Observable { 78 | callbacks?.onBegin?.(); 79 | return new Observable(observer => { 80 | this.core 81 | .fetchUserInfo() 82 | .then(userInfo => { 83 | observer.next(userInfo); 84 | }) 85 | .catch(error => { 86 | observer.error(error); 87 | }) 88 | .finally(() => { 89 | callbacks?.onDone?.(); 90 | }); 91 | }).pipe( 92 | catchError(error => { 93 | throw error; 94 | }), 95 | ); 96 | } 97 | 98 | /** 99 | * Fetches userInfo from the 'me' endpoint. 100 | * @throws {Error} - if an error occurred while fetching. 101 | */ 102 | async getUserInfo(): Promise { 103 | return await this.core.fetchUserInfo(); 104 | } 105 | 106 | /** 107 | * Initiates login flow. 108 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 109 | */ 110 | startLogin(state?: string): void { 111 | this.core.startLogin(state); 112 | } 113 | 114 | /** 115 | * Initiates register flow. 116 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 117 | */ 118 | startRegistration(state?: string): void { 119 | this.core.startRegister(state); 120 | } 121 | 122 | /** 123 | * Initiates logout flow. 124 | */ 125 | logout(): void { 126 | this.core.startLogout(); 127 | } 128 | 129 | /** 130 | * Redirects to [self service account management](https://fusionauth.io/docs/lifecycle/manage-users/account-management/) 131 | * Self service account management is only available in FusionAuth paid plans. 132 | */ 133 | manageAccount(): void { 134 | this.core.manageAccount(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/injectionToken.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { FusionAuthConfig } from './types'; 3 | 4 | export const FUSIONAUTH_SERVICE_CONFIG = new InjectionToken( 5 | 'FUSIONAUTH_SERVICE_CONFIG', 6 | ); 7 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config for FusionAuth Angular SDK 3 | */ 4 | export interface FusionAuthConfig { 5 | /** 6 | * The URL of the server that performs the token exchange. 7 | */ 8 | serverUrl: string; 9 | 10 | /** 11 | * The client id of the application. 12 | */ 13 | clientId: string; 14 | 15 | /** 16 | * The redirect URI of the application. 17 | */ 18 | redirectUri: string; 19 | 20 | /** 21 | * The redirect URI for post-logout. Defaults the provided `redirectUri`. 22 | */ 23 | postLogoutRedirectUri?: string; 24 | 25 | /** 26 | * The OAuth2 scope parameter passed to the `/oauth2/authorize` endpoint. If not specified fusionauth will default this to `openid offline_access`. 27 | */ 28 | scope?: string; 29 | 30 | /** 31 | * Enables automatic token refreshing. Defaults to false. 32 | */ 33 | shouldAutoRefresh?: boolean; 34 | 35 | /** 36 | * The number of seconds before the access token expiry when the auto refresh functionality kicks in if enabled. Default is 10. 37 | */ 38 | autoRefreshSecondsBeforeExpiry?: number; 39 | 40 | /** 41 | * Callback function to be invoked with the `state` value upon redirect from login or register. 42 | */ 43 | onRedirect?: (state?: string) => void; 44 | 45 | /** 46 | * Callback to be invoked if a request to refresh the access token fails during autorefresh. 47 | */ 48 | onAutoRefreshFailure?: (error: Error) => void; 49 | 50 | /** 51 | * The path to the login endpoint. 52 | */ 53 | loginPath?: string; 54 | 55 | /** 56 | * The path to the register endpoint. 57 | */ 58 | registerPath?: string; 59 | 60 | /** 61 | * The path to the logout endpoint. 62 | */ 63 | logoutPath?: string; 64 | 65 | /** 66 | * The path to the token refresh endpoint. 67 | */ 68 | tokenRefreshPath?: string; 69 | 70 | /** 71 | * The path to the me endpoint. 72 | */ 73 | mePath?: string; 74 | } 75 | 76 | export interface UserInfo { 77 | applicationId?: string; 78 | birthdate?: string; 79 | email?: string; 80 | email_verified?: boolean; 81 | family_name?: string; 82 | given_name?: string; 83 | middle_name?: string; 84 | name?: string; 85 | phone_number?: string; 86 | picture?: string; 87 | preferred_username?: string; 88 | roles?: any[]; 89 | sid?: string; 90 | sub?: string; 91 | tid?: string; 92 | } 93 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of fusionauth-angular-sdk 3 | */ 4 | 5 | export * from './lib/fusion-auth.service'; 6 | export * from './lib/components/fusionauth-login.button/fusion-auth-login-button.component'; 7 | export * from './lib/components/fusionauth-logout.button/fusion-auth-logout-button.component'; 8 | export * from './lib/components/fusionauth-register.button/fusion-auth-register-button.component'; 9 | export * from './lib/components/fusionauth-account.button/fusion-auth-account-button.component'; 10 | export * from './lib/fusion-auth.module'; 11 | export * from './lib/types'; 12 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": ["node"] 10 | }, 11 | "exclude": ["**/*.spec.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk-angular/projects/fusionauth-angular-sdk/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": ["jasmine", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "paths": { 11 | "fusionauth-angular-sdk": ["./dist/fusionauth-angular-sdk"] 12 | }, 13 | "noPropertyAccessFromIndexSignature": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "skipLibCheck": true, 17 | "esModuleInterop": true, 18 | "sourceMap": true, 19 | "declaration": true, 20 | "experimentalDecorators": true, 21 | "moduleResolution": "node", 22 | "importHelpers": true, 23 | "target": "ES2022", 24 | "useDefineForClassFields": false, 25 | "allowJs": false, 26 | "composite": false, 27 | "allowImportingTsExtensions": false, 28 | "noEmit": false, 29 | "types": ["jasmine"] 30 | }, 31 | "include": ["./**/*.ts"], 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | }, 38 | "typedocOptions": { 39 | "plugin": ["typedoc-plugin-markdown"], 40 | "entryPoints": ["projects/fusionauth-angular-sdk/src/public-api.ts"], 41 | "out": "docs" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/sdk-react/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": false, 3 | "extends": [ 4 | "../../.eslintrc", 5 | "plugin:react-hooks/recommended", 6 | "plugin:react/recommended" 7 | ], 8 | "plugins": ["react"], 9 | "rules": { 10 | "react/react-in-jsx-scope": "off", 11 | "react/prop-types": "off" 12 | }, 13 | "settings": { 14 | "react": { 15 | "version": "detect" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](README.md) / Modules 2 | 3 | # @fusionauth/react-sdk 4 | 5 | ## Table of contents 6 | 7 | ### Modules 8 | 9 | - [providers/FusionAuthProvider](modules/providers_FusionAuthProvider.md) 10 | - [providers/FusionAuthProviderConfig](modules/providers_FusionAuthProviderConfig.md) 11 | - [providers/FusionAuthProviderContext](modules/providers_FusionAuthProviderContext.md) 12 | - [ui/FusionAuthLoginButton](modules/ui_FusionAuthLoginButton.md) 13 | - [ui/FusionAuthLogoutButton](modules/ui_FusionAuthLogoutButton.md) 14 | - [ui/FusionAuthRegisterButton](modules/ui_FusionAuthRegisterButton.md) 15 | - [ui/RequireAuth](modules/ui_RequireAuth.md) 16 | - [ui/Unauthenticated](modules/ui_Unauthenticated.md) 17 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/providers_FusionAuthProvider.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / providers/FusionAuthProvider 2 | 3 | # Module: providers/FusionAuthProvider 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [FusionAuthProvider](providers_FusionAuthProvider.md#fusionauthprovider) 10 | - [useFusionAuth](providers_FusionAuthProvider.md#usefusionauth) 11 | 12 | ## Functions 13 | 14 | ### FusionAuthProvider 15 | 16 | ▸ **FusionAuthProvider**\<`T`\>(`«destructured»`): `Element` 17 | 18 | #### Type parameters 19 | 20 | | Name | Type | 21 | | :--- | :--------- | 22 | | `T` | `UserInfo` | 23 | 24 | #### Parameters 25 | 26 | | Name | Type | 27 | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | 28 | | `«destructured»` | \{ `children?`: `ReactNode` } & [`FusionAuthProviderConfig`](../interfaces/providers_FusionAuthProviderConfig.FusionAuthProviderConfig.md) | 29 | 30 | #### Returns 31 | 32 | `Element` 33 | 34 | #### Defined in 35 | 36 | [packages/sdk-react/src/components/providers/FusionAuthProvider.tsx:15](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx#L15) 37 | 38 | --- 39 | 40 | ### useFusionAuth 41 | 42 | ▸ **useFusionAuth**\<`T`\>(): [`FusionAuthProviderContext`](../interfaces/providers_FusionAuthProviderContext.FusionAuthProviderContext.md)\<`T`\> 43 | 44 | A hook that returns `FusionAuthProviderContext` 45 | 46 | #### Type parameters 47 | 48 | | Name | Type | 49 | | :--- | :--------- | 50 | | `T` | `UserInfo` | 51 | 52 | #### Returns 53 | 54 | [`FusionAuthProviderContext`](../interfaces/providers_FusionAuthProviderContext.FusionAuthProviderContext.md)\<`T`\> 55 | 56 | #### Defined in 57 | 58 | [packages/sdk-react/src/components/providers/FusionAuthProvider.tsx:68](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx#L68) 59 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/providers_FusionAuthProviderConfig.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / providers/FusionAuthProviderConfig 2 | 3 | # Module: providers/FusionAuthProviderConfig 4 | 5 | ## Table of contents 6 | 7 | ### Interfaces 8 | 9 | - [FusionAuthProviderConfig](../interfaces/providers_FusionAuthProviderConfig.FusionAuthProviderConfig.md) 10 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/providers_FusionAuthProviderContext.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / providers/FusionAuthProviderContext 2 | 3 | # Module: providers/FusionAuthProviderContext 4 | 5 | ## Table of contents 6 | 7 | ### Interfaces 8 | 9 | - [FusionAuthProviderContext](../interfaces/providers_FusionAuthProviderContext.FusionAuthProviderContext.md) 10 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/ui_FusionAuthLoginButton.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / ui/FusionAuthLoginButton 2 | 3 | # Module: ui/FusionAuthLoginButton 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [FusionAuthLoginButton](ui_FusionAuthLoginButton.md#fusionauthloginbutton) 10 | 11 | ## Functions 12 | 13 | ### FusionAuthLoginButton 14 | 15 | ▸ **FusionAuthLoginButton**(`props`, `deprecatedLegacyContext?`): `ReactNode` 16 | 17 | Calls the `startLogin` method from `FusionAuthProviderContext`. 18 | 19 | #### Parameters 20 | 21 | | Name | Type | Description | 22 | | :------------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | 23 | | `props` | `FusionAuthLoginButtonProps` | - | 24 | | `deprecatedLegacyContext?` | `any` | **`Deprecated`** **`See`** [React Docs](https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods) | 25 | 26 | #### Returns 27 | 28 | `ReactNode` 29 | 30 | #### Defined in 31 | 32 | [packages/sdk-react/src/components/ui/FusionAuthLoginButton/index.tsx:16](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/ui/FusionAuthLoginButton/index.tsx#L16) 33 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/ui_FusionAuthLogoutButton.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / ui/FusionAuthLogoutButton 2 | 3 | # Module: ui/FusionAuthLogoutButton 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [FusionAuthLogoutButton](ui_FusionAuthLogoutButton.md#fusionauthlogoutbutton) 10 | 11 | ## Functions 12 | 13 | ### FusionAuthLogoutButton 14 | 15 | ▸ **FusionAuthLogoutButton**(`props`, `deprecatedLegacyContext?`): `ReactNode` 16 | 17 | Calls the `startLogout` method from `FusionAuthProviderContext`. 18 | 19 | #### Parameters 20 | 21 | | Name | Type | Description | 22 | | :------------------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | 23 | | `props` | `FusionAuthLogoutButtonProps` | - | 24 | | `deprecatedLegacyContext?` | `any` | **`Deprecated`** **`See`** [React Docs](https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods) | 25 | 26 | #### Returns 27 | 28 | `ReactNode` 29 | 30 | #### Defined in 31 | 32 | [packages/sdk-react/src/components/ui/FusionAuthLogoutButton/index.tsx:13](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/ui/FusionAuthLogoutButton/index.tsx#L13) 33 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/ui_FusionAuthRegisterButton.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / ui/FusionAuthRegisterButton 2 | 3 | # Module: ui/FusionAuthRegisterButton 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [FusionAuthRegisterButton](ui_FusionAuthRegisterButton.md#fusionauthregisterbutton) 10 | 11 | ## Functions 12 | 13 | ### FusionAuthRegisterButton 14 | 15 | ▸ **FusionAuthRegisterButton**(`props`, `deprecatedLegacyContext?`): `ReactNode` 16 | 17 | Calls the `startRegister` method from `FusionAuthProviderContext`. 18 | 19 | #### Parameters 20 | 21 | | Name | Type | Description | 22 | | :------------------------- | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------ | 23 | | `props` | `FusionAuthRegisterButtonProps` | - | 24 | | `deprecatedLegacyContext?` | `any` | **`Deprecated`** **`See`** [React Docs](https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods) | 25 | 26 | #### Returns 27 | 28 | `ReactNode` 29 | 30 | #### Defined in 31 | 32 | [packages/sdk-react/src/components/ui/FusionAuthRegisterButton/index.tsx:15](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/ui/FusionAuthRegisterButton/index.tsx#L15) 33 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/ui_RequireAuth.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / ui/RequireAuth 2 | 3 | # Module: ui/RequireAuth 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [RequireAuth](ui_RequireAuth.md#requireauth) 10 | 11 | ## Functions 12 | 13 | ### RequireAuth 14 | 15 | ▸ **RequireAuth**(`props`, `deprecatedLegacyContext?`): `ReactNode` 16 | 17 | Only renders children when user is authenticated. 18 | 19 | #### Parameters 20 | 21 | | Name | Type | Description | 22 | | :------------------------- | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------ | 23 | | `props` | `RequireAuthProps` | - | 24 | | `deprecatedLegacyContext?` | `any` | **`Deprecated`** **`See`** [React Docs](https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods) | 25 | 26 | #### Returns 27 | 28 | `ReactNode` 29 | 30 | #### Defined in 31 | 32 | [packages/sdk-react/src/components/ui/RequireAuth/index.tsx:10](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/ui/RequireAuth/index.tsx#L10) 33 | -------------------------------------------------------------------------------- /packages/sdk-react/docs/modules/ui_Unauthenticated.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/react-sdk](../README.md) / [Modules](../modules.md) / ui/Unauthenticated 2 | 3 | # Module: ui/Unauthenticated 4 | 5 | ## Table of contents 6 | 7 | ### Functions 8 | 9 | - [Unauthenticated](ui_Unauthenticated.md#unauthenticated) 10 | 11 | ## Functions 12 | 13 | ### Unauthenticated 14 | 15 | ▸ **Unauthenticated**(`props`, `deprecatedLegacyContext?`): `ReactNode` 16 | 17 | Only renders children when user is unauthenticated. 18 | 19 | #### Parameters 20 | 21 | | Name | Type | Description | 22 | | :------------------------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------ | 23 | | `props` | `Object` | - | 24 | | `props.children?` | `ReactNode` | - | 25 | | `deprecatedLegacyContext?` | `any` | **`Deprecated`** **`See`** [React Docs](https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods) | 26 | 27 | #### Returns 28 | 29 | `ReactNode` 30 | 31 | #### Defined in 32 | 33 | [packages/sdk-react/src/components/ui/Unauthenticated/index.tsx:5](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/ce690f3d040e390c8fcdaa133e0c4df7f0050404/packages/sdk-react/src/components/ui/Unauthenticated/index.tsx#L5) 34 | -------------------------------------------------------------------------------- /packages/sdk-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fusionauth/react-sdk", 3 | "version": "2.5.1", 4 | "private": false, 5 | "description": "FusionAuth solves the problem of building essential security without adding risk or distracting from your primary application", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc && vite build", 9 | "docs": "typedoc --plugin typedoc-plugin-markdown", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "test": "vitest --watch=false", 12 | "test:watch": "vitest" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/FusionAuth/fusionauth-javascript-sdk" 17 | }, 18 | "author": "FusionAuth", 19 | "license": "Apache", 20 | "bugs": { 21 | "url": "https://github.com/FusionAuth/fusionauth-javascript-sdk/issues" 22 | }, 23 | "engines": { 24 | "node": ">=18.0.0", 25 | "npm": ">=10.0.0" 26 | }, 27 | "homepage": "https://github.com/FusionAuth/fusionauth-javascript-sdk/tree/main/packages/sdk-react#readme", 28 | "main": "dist/index.js", 29 | "module": "dist/index.js", 30 | "types": "dist/index.d.ts", 31 | "files": [ 32 | "dist/**/*" 33 | ], 34 | "exports": { 35 | ".": "./dist/index.js" 36 | }, 37 | "dependencies": { 38 | "classnames": "^2.3.2" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=18.2.0", 42 | "react-dom": ">=18.2.0" 43 | }, 44 | "devDependencies": { 45 | "@fusionauth-sdk/core": "*", 46 | "@testing-library/jest-dom": "^6.4.2", 47 | "@testing-library/react": "^14.2.1", 48 | "@testing-library/user-event": "^14.5.2", 49 | "@types/js-cookie": "3.0.6", 50 | "@types/node": "^20.11.25", 51 | "@types/react": "^18.2.56", 52 | "@types/react-dom": "^18.2.19", 53 | "@vitejs/plugin-react-swc": "^3.5.0", 54 | "jsdom": "^24.0.0", 55 | "next-client-cookies": "^1.1.1", 56 | "react": "^18.2.0", 57 | "react-dom": "^18.2.0", 58 | "sass": "^1.56.1", 59 | "typedoc": "^0.25.13", 60 | "typedoc-plugin-markdown": "^3.17.1", 61 | "typescript": "^5.2.2", 62 | "vite": "^5.4.17", 63 | "vite-plugin-dts": "^3.7.3", 64 | "vitest": "^1.6.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/Context.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FusionAuthProviderContext } from './FusionAuthProviderContext'; 3 | 4 | export const defaultContext: FusionAuthProviderContext = { 5 | startLogin: () => {}, 6 | startLogout: () => {}, 7 | startRegister: () => {}, 8 | manageAccount: () => {}, 9 | userInfo: null, 10 | fetchUserInfo: () => Promise.resolve({}), 11 | isFetchingUserInfo: false, 12 | error: null, 13 | isLoggedIn: false, 14 | refreshToken: () => Promise.resolve(undefined), 15 | initAutoRefresh: () => {}, 16 | }; 17 | 18 | export type UserInfo = { 19 | applicationId?: string; 20 | birthdate?: string; 21 | email?: string; 22 | email_verified?: boolean; 23 | family_name?: string; 24 | given_name?: string; 25 | name?: string; 26 | middle_name?: string; 27 | phone_number?: string; 28 | picture?: string; 29 | preferred_username?: string; 30 | roles?: any[]; 31 | sid?: string; 32 | sub?: string; 33 | tid?: string; 34 | }; 35 | 36 | export const FusionAuthContext = 37 | React.createContext>( 38 | defaultContext, 39 | ); 40 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/FusionAuthProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | PropsWithChildren, 3 | useContext, 4 | useMemo, 5 | useState, 6 | useRef, 7 | } from 'react'; 8 | 9 | import { SDKConfig, SDKCore } from '@fusionauth-sdk/core'; 10 | 11 | import { FusionAuthProviderConfig } from './FusionAuthProviderConfig'; 12 | import { 13 | useTokenRefresh, 14 | useRedirecting, 15 | useUserInfo, 16 | useCookieAdapter, 17 | } from './hooks'; 18 | import { FusionAuthContext, UserInfo as DefaultUserInfo } from './Context'; 19 | import { FusionAuthProviderContext } from './FusionAuthProviderContext'; 20 | 21 | function FusionAuthProvider( 22 | props: PropsWithChildren & FusionAuthProviderConfig, 23 | ) { 24 | const config: Omit = 25 | useMemo( 26 | () => ({ 27 | serverUrl: props.serverUrl, 28 | clientId: props.clientId, 29 | redirectUri: props.redirectUri, 30 | scope: props.scope, 31 | authParams: props.authParams, 32 | postLogoutRedirectUri: props.postLogoutRedirectUri, 33 | shouldAutoRefresh: props.shouldAutoRefresh, 34 | shouldAutoFetchUserInfo: props.shouldAutoFetchUserInfo, 35 | autoRefreshSecondsBeforeExpiry: props.autoRefreshSecondsBeforeExpiry, 36 | onRedirect: props.onRedirect, 37 | loginPath: props.loginPath, 38 | logoutPath: props.logoutPath, 39 | registerPath: props.registerPath, 40 | tokenRefreshPath: props.tokenRefreshPath, 41 | mePath: props.mePath, 42 | accessTokenExpireCookieName: props.accessTokenExpireCookieName, 43 | onAutoRefreshFailure: props.onAutoRefreshFailure, 44 | }), 45 | [ 46 | props.serverUrl, 47 | props.clientId, 48 | props.redirectUri, 49 | props.scope, 50 | props.authParams, 51 | props.postLogoutRedirectUri, 52 | props.shouldAutoRefresh, 53 | props.shouldAutoFetchUserInfo, 54 | props.autoRefreshSecondsBeforeExpiry, 55 | props.onRedirect, 56 | props.loginPath, 57 | props.logoutPath, 58 | props.registerPath, 59 | props.tokenRefreshPath, 60 | props.mePath, 61 | props.accessTokenExpireCookieName, 62 | props.onAutoRefreshFailure, 63 | ], 64 | ); 65 | 66 | const cookieAdapter = useCookieAdapter(config); 67 | 68 | const coreRef = useRef(); 69 | const core: SDKCore = useMemo(() => { 70 | if (coreRef.current) { 71 | coreRef.current.dispose(); 72 | } 73 | 74 | const newCore = new SDKCore({ 75 | ...config, 76 | cookieAdapter, 77 | onTokenExpiration: () => setIsLoggedIn(false), 78 | }); 79 | coreRef.current = newCore; 80 | return newCore; 81 | }, [config, cookieAdapter]); 82 | 83 | const [isLoggedIn, setIsLoggedIn] = useState(core.isLoggedIn); 84 | 85 | const { manageAccount, startLogin, startLogout, startRegister } = 86 | useRedirecting(core, config.onRedirect); 87 | 88 | const { isFetchingUserInfo, userInfo, fetchUserInfo, error } = useUserInfo( 89 | core, 90 | config.shouldAutoFetchUserInfo ?? false, 91 | ); 92 | 93 | const { refreshToken, initAutoRefresh } = useTokenRefresh( 94 | core, 95 | config.shouldAutoRefresh ?? false, 96 | ); 97 | 98 | const providerValue: FusionAuthProviderContext = { 99 | startLogin, 100 | startRegister, 101 | startLogout, 102 | isLoggedIn, 103 | isFetchingUserInfo, 104 | error, 105 | userInfo, 106 | refreshToken, 107 | initAutoRefresh, 108 | fetchUserInfo, 109 | manageAccount, 110 | }; 111 | 112 | return ( 113 | 114 | {props.children} 115 | 116 | ); 117 | } 118 | 119 | /** 120 | * A hook that returns `FusionAuthProviderContext` 121 | */ 122 | function useFusionAuth() { 123 | return useContext>(FusionAuthContext); 124 | } 125 | 126 | export { FusionAuthProvider, useFusionAuth }; 127 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/FusionAuthProviderConfig.ts: -------------------------------------------------------------------------------- 1 | import { useCookies } from 'next-client-cookies'; 2 | 3 | /** 4 | * Config for FusionAuthProvider 5 | */ 6 | export interface FusionAuthProviderConfig { 7 | /** 8 | * The URL of the server that performs the token exchange. 9 | */ 10 | serverUrl: string; 11 | 12 | /** 13 | * The client id of the application. 14 | */ 15 | clientId: string; 16 | 17 | /** 18 | * The redirect URI of the application. 19 | */ 20 | redirectUri: string; 21 | 22 | /** 23 | * The OAuth2 scope parameter passed to the `/oauth2/authorize` endpoint. If not specified fusionauth will default this to `openid offline_access`. 24 | */ 25 | scope?: string; 26 | 27 | /** 28 | * Additional params passed to loginPath typically `/app/login/`, which redirects to `/oauth2/authorize`. Example of this might be loginParams = [{idp_hint:'44449786-3dff-42a6-aac6-1f1ceecb6c46'}] or any params found at https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/endpoints 29 | */ 30 | authParams?: { [key: string]: any }[]; 31 | 32 | /** 33 | * The redirect URI for post-logout. Defaults the provided `redirectUri`. 34 | */ 35 | postLogoutRedirectUri?: string; 36 | 37 | /** 38 | * Enables automatic token refreshing. Defaults to false. 39 | */ 40 | shouldAutoRefresh?: boolean; 41 | 42 | /** 43 | * Enables the SDK to automatically handle fetching user info when logged in. Defaults to false. 44 | */ 45 | shouldAutoFetchUserInfo?: boolean; 46 | 47 | /** 48 | * The number of seconds before the access token expiry when the auto refresh functionality kicks in if enabled. Default is 10. 49 | */ 50 | autoRefreshSecondsBeforeExpiry?: number; 51 | 52 | /** 53 | * Callback function to be invoked with the `state` value upon redirect from login or register. 54 | */ 55 | onRedirect?: (state?: string) => void; 56 | 57 | /** 58 | * Callback to be invoked if a request to refresh the access token fails during autorefresh. 59 | */ 60 | onAutoRefreshFailure?: (error: Error) => void; 61 | 62 | /** 63 | * The path to the login endpoint. 64 | */ 65 | loginPath?: string; 66 | 67 | /** 68 | * The path to the register endpoint. 69 | */ 70 | registerPath?: string; 71 | 72 | /** 73 | * The path to the logout endpoint. 74 | */ 75 | logoutPath?: string; 76 | 77 | /** 78 | * The path to the token refresh endpoint. 79 | */ 80 | tokenRefreshPath?: string; 81 | 82 | /** 83 | * Pass in `useCookies` from [next-client-cookies](https://github.com/moshest/next-client-cookies). 84 | * This is needed for the React SDK to support Next/SSR. 85 | * See docs for [configuration with nextjs](https://github.com/FusionAuth/fusionauth-javascript-sdk/tree/main/packages/sdk-react#configuration-with-nextjs) for more information. 86 | */ 87 | nextCookieAdapter?: typeof useCookies; 88 | 89 | /** 90 | * The path to the me endpoint. 91 | */ 92 | mePath?: string; 93 | 94 | /** 95 | * The name of the access token expiration moment cookie. 96 | * Only set this if you are hosting server that uses a custom name for the 'app.at_exp' cookie. 97 | */ 98 | accessTokenExpireCookieName?: string; 99 | } 100 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/FusionAuthProviderContext.ts: -------------------------------------------------------------------------------- 1 | import { UserInfo } from './Context'; 2 | 3 | /** The context provided by FusionAuth React SDK */ 4 | export interface FusionAuthProviderContext { 5 | /** 6 | * Whether the user is logged in. 7 | */ 8 | isLoggedIn: boolean; 9 | 10 | /** 11 | * Data fetched from the configured 'me' endpoint. 12 | */ 13 | userInfo: T | null; 14 | 15 | /** 16 | * Fetches user info from the 'me' endpoint. 17 | * This is handled automatically if the SDK is configured with `shouldAutoFetchUserInfo`. 18 | */ 19 | fetchUserInfo: () => Promise; 20 | 21 | /** 22 | * Indicates that the fetchUserInfo call is unresolved. 23 | */ 24 | isFetchingUserInfo: boolean; 25 | 26 | /** 27 | * Error occurred while fetching userInfo. 28 | */ 29 | error: Error | null; 30 | 31 | /** 32 | * Initiates login flow. 33 | * @param {string} state - Optional value to be echoed back to the SDK upon redirect. 34 | */ 35 | startLogin: (state?: string) => void; 36 | 37 | /** 38 | * Initiates register flow. 39 | * @param {string} state - Optional value to be echoed back to the SDK upon redirect. 40 | */ 41 | startRegister: (state?: string) => void; 42 | 43 | /** 44 | * Initiates logout flow. 45 | */ 46 | startLogout: () => void; 47 | 48 | /** 49 | * Redirects to [self service account management](https://fusionauth.io/docs/lifecycle/manage-users/account-management/) 50 | * Self service account management is only available in FusionAuth paid plans. 51 | */ 52 | manageAccount: () => void; 53 | 54 | /** 55 | * Refreshes the access token a single time. 56 | * This is handled automatically if the SDK is configured with `shouldAutoRefresh`. 57 | */ 58 | refreshToken: () => Promise; 59 | 60 | /** 61 | * Initializes automatic access token refreshing. 62 | * This is handled automatically if the SDK is configured with `shouldAutoRefresh`. 63 | */ 64 | initAutoRefresh: () => void; 65 | } 66 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useRedirecting'; 2 | export * from './useTokenRefresh'; 3 | export * from './useUserInfo'; 4 | export * from './useCookieAdapter'; 5 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/hooks/useCookieAdapter.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | import { CookieAdapter } from '@fusionauth-sdk/core'; 4 | 5 | import { FusionAuthProviderConfig } from '../FusionAuthProviderConfig'; 6 | 7 | export function useCookieAdapter(config: FusionAuthProviderConfig) { 8 | const cookies = config.nextCookieAdapter?.(); 9 | const cookieName = config.accessTokenExpireCookieName ?? 'app.at_exp'; 10 | 11 | return useMemo( 12 | () => 13 | cookies 14 | ? { 15 | at_exp() { 16 | return cookies.get(cookieName); 17 | }, 18 | } 19 | : undefined, 20 | [cookies, cookieName], 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/hooks/useRedirecting.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from 'react'; 2 | import { SDKCore } from '@fusionauth-sdk/core'; 3 | 4 | export function useRedirecting( 5 | core: SDKCore, 6 | onRedirect?: (state?: string) => void, 7 | ) { 8 | const manageAccount = useCallback(() => core.manageAccount(), [core]); 9 | const startLogin = useCallback( 10 | (state?: string) => core.startLogin(state), 11 | [core], 12 | ); 13 | const startRegister = useCallback( 14 | (state?: string) => core.startRegister(state), 15 | [core], 16 | ); 17 | const startLogout = useCallback(() => core.startLogout(), [core]); 18 | 19 | useEffect(() => { 20 | core.handlePostRedirect(onRedirect); 21 | }, [core, onRedirect]); 22 | 23 | return { 24 | manageAccount, 25 | startLogin, 26 | startRegister, 27 | startLogout, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/hooks/useTokenRefresh.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useCallback } from 'react'; 2 | import { SDKCore } from '@fusionauth-sdk/core'; 3 | 4 | export function useTokenRefresh(core: SDKCore, shouldAutoRefresh: boolean) { 5 | const refreshToken = useCallback( 6 | async () => await core.refreshToken(), 7 | [core], 8 | ); 9 | 10 | const initAutoRefresh = useCallback(() => { 11 | core.initAutoRefresh(); 12 | }, [core]); 13 | 14 | useEffect(() => { 15 | if (shouldAutoRefresh) { 16 | initAutoRefresh(); 17 | } 18 | }, [initAutoRefresh, shouldAutoRefresh]); 19 | 20 | return { refreshToken, initAutoRefresh }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/providers/hooks/useUserInfo.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useRef, useEffect } from 'react'; 2 | 3 | import { SDKCore } from '@fusionauth-sdk/core'; 4 | 5 | export function useUserInfo( 6 | core: SDKCore, 7 | shouldAutoFetchUserInfo: boolean, 8 | ) { 9 | const [isFetchingUserInfo, setIsFetchingUserInfo] = useState(false); 10 | const [userInfo, setUserInfo] = useState(null); 11 | const [error, setError] = useState(null); 12 | 13 | const fetchUserInfo = useCallback(async () => { 14 | setIsFetchingUserInfo(true); 15 | setError(null); 16 | 17 | try { 18 | const userInfo = await core.fetchUserInfo(); 19 | setUserInfo(userInfo); 20 | return userInfo; 21 | } catch (error) { 22 | setError(error as Error); 23 | } finally { 24 | setIsFetchingUserInfo(false); 25 | } 26 | }, [core]); 27 | 28 | const didAttemptAutoFetch = useRef(false); 29 | 30 | const handleAutoFetch = useCallback(() => { 31 | if (!shouldAutoFetchUserInfo || didAttemptAutoFetch.current) { 32 | return; 33 | } 34 | 35 | // ensures this effect does not run multiple times if we fail to fetch the user 36 | didAttemptAutoFetch.current = true; 37 | 38 | if (core.isLoggedIn) { 39 | fetchUserInfo(); 40 | } 41 | }, [core, fetchUserInfo, shouldAutoFetchUserInfo]); 42 | 43 | useEffect(() => { 44 | handleAutoFetch(); 45 | }, [handleAutoFetch]); 46 | 47 | return { 48 | fetchUserInfo, 49 | isFetchingUserInfo, 50 | userInfo, 51 | error, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthAccountButton/FusionAuthAccountButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen, render, fireEvent } from '@testing-library/react'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | 4 | import { FusionAuthAccountButton } from '.'; 5 | import { FusionAuthProvider } from '../../providers/FusionAuthProvider'; 6 | import { mockUseFusionAuth } from '../../../testing-tools/mocks/mockUseFusionAuth'; 7 | import { TEST_CONFIG } from '../../../testing-tools/mocks/testConfig'; 8 | 9 | describe('FusionAuthAccountButton', () => { 10 | test('Manage Account button will call the useFusionAuth hook', () => { 11 | const manageAccount = vi.fn(); 12 | mockUseFusionAuth({ manageAccount }); 13 | 14 | render( 15 | 16 | 17 | , 18 | ); 19 | 20 | fireEvent.click(screen.getByText('Manage Account')); 21 | expect(manageAccount).toHaveBeenCalled(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthAccountButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 5 | import styles from '#styles/button.module.scss'; 6 | 7 | type FusionAuthAccountButtonProps = { 8 | /** Label displayed by the button. Defaults to "Manage Account". */ 9 | text?: string; 10 | className?: string; 11 | }; 12 | 13 | /** Calls the `manageAccount` method from `FusionAuthProviderContext`. */ 14 | export const FusionAuthAccountButton: FC = ({ 15 | className, 16 | text, 17 | }) => { 18 | const { manageAccount } = useFusionAuth(); 19 | 20 | return ( 21 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthLoginButton/FusionAuthLoginButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen, render, fireEvent } from '@testing-library/react'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | 4 | import { FusionAuthLoginButton } from './'; 5 | import { FusionAuthProvider } from '../../providers/FusionAuthProvider'; 6 | import { mockUseFusionAuth } from '../../../testing-tools/mocks/mockUseFusionAuth'; 7 | import { TEST_CONFIG } from '../../../testing-tools/mocks/testConfig'; 8 | 9 | describe('FusionAuthLoginButton', () => { 10 | test('Login button will call the useFusionAuth hook', () => { 11 | const startLogin = vi.fn(); 12 | mockUseFusionAuth({ startLogin }); 13 | 14 | const stateValue = 'state-value-for-login'; 15 | render( 16 | 17 | 18 | , 19 | ); 20 | 21 | fireEvent.click(screen.getByText('Login')); 22 | expect(startLogin).toHaveBeenCalledWith(stateValue); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthLoginButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 5 | import styles from '#styles/button.module.scss'; 6 | 7 | interface FusionAuthLoginButtonProps { 8 | /** Optional string value that will be passed to `onRedirect` after login. */ 9 | state?: string; 10 | /** Label displayed by the button. Defaults to "Login". */ 11 | text?: string; 12 | className?: string; 13 | } 14 | 15 | /** Calls the `startLogin` method from `FusionAuthProviderContext`. */ 16 | export const FusionAuthLoginButton: FC = ({ 17 | state, 18 | text, 19 | className, 20 | }) => { 21 | const { startLogin } = useFusionAuth(); 22 | 23 | return ( 24 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthLogoutButton/FusionAuthLogoutButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen, render, fireEvent } from '@testing-library/react'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | 4 | import { FusionAuthLogoutButton } from './'; 5 | import { FusionAuthProvider } from '#components/providers/FusionAuthProvider'; 6 | import { mockUseFusionAuth } from '#testing-tools/mocks/mockUseFusionAuth'; 7 | import { TEST_CONFIG } from '#testing-tools/mocks/testConfig'; 8 | 9 | describe('FusionAuthLogoutButton', () => { 10 | test('Logout button will call the useFusionAuth hook', () => { 11 | const startLogout = vi.fn(); 12 | mockUseFusionAuth({ startLogout }); 13 | 14 | render( 15 | 16 | 17 | , 18 | ); 19 | 20 | fireEvent.click(screen.getByText('Logout')); 21 | 22 | expect(startLogout).toHaveBeenCalled(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthLogoutButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 3 | import styles from '#styles/button.module.scss'; 4 | import classNames from 'classnames'; 5 | 6 | interface FusionAuthLogoutButtonProps { 7 | /** Label displayed by the button. Defaults to "Logout". */ 8 | text?: string; 9 | className?: string; 10 | } 11 | 12 | /** Calls the `startLogout` method from `FusionAuthProviderContext`. */ 13 | export const FusionAuthLogoutButton: FC = ({ 14 | text, 15 | className, 16 | }) => { 17 | const { startLogout } = useFusionAuth(); 18 | 19 | return ( 20 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthRegisterButton/FusionAuthRegisterButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen, render, fireEvent } from '@testing-library/react'; 2 | import { afterEach, describe, expect, test, vi } from 'vitest'; 3 | 4 | import { FusionAuthRegisterButton } from '#components/ui/FusionAuthRegisterButton'; 5 | import { FusionAuthProvider } from '#components/providers/FusionAuthProvider'; 6 | import { mockUseFusionAuth } from '#testing-tools/mocks/mockUseFusionAuth'; 7 | import { TEST_CONFIG } from '#testing-tools/mocks/testConfig'; 8 | 9 | describe('FusionAuthRegisterButton', () => { 10 | afterEach(() => { 11 | vi.clearAllMocks(); 12 | }); 13 | 14 | test('Register button will call the useFusionAuth hook', async () => { 15 | const startRegister = vi.fn(); 16 | 17 | mockUseFusionAuth({ startRegister }); 18 | 19 | const stateValue = 'state-value-for-register'; 20 | render( 21 | 22 | 23 | , 24 | ); 25 | 26 | fireEvent.click(screen.getByText('Register Now')); 27 | 28 | expect(startRegister).toHaveBeenCalledWith(stateValue); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/FusionAuthRegisterButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 3 | import styles from '#styles/button.module.scss'; 4 | import classNames from 'classnames'; 5 | 6 | interface FusionAuthRegisterButtonProps { 7 | /** Optional string value that will be passed to `onRedirect` after register. */ 8 | state?: string; 9 | /** Label displayed by the button. Defaults to "Register Now". */ 10 | text?: string; 11 | className?: string; 12 | } 13 | 14 | /** Calls the `startRegister` method from `FusionAuthProviderContext`. */ 15 | export const FusionAuthRegisterButton: FC = ({ 16 | state, 17 | text, 18 | className, 19 | }) => { 20 | const { startRegister } = useFusionAuth(); 21 | 22 | return ( 23 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/RequireAuth/RequireAuth.test.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import { describe, expect, test } from 'vitest'; 4 | 5 | import { RequireAuth } from './'; 6 | import { FusionAuthContext } from '#/components/providers/Context'; 7 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 8 | 9 | import { createContextMock } from '#/testing-tools/mocks/createContextMock'; 10 | 11 | const renderWithMockProvider = ({ 12 | context, 13 | content, 14 | }: { 15 | context: Partial; 16 | content: ReactNode; 17 | }) => { 18 | return render( 19 | 20 | {content} 21 | , 22 | ); 23 | }; 24 | 25 | describe('RequireAuth Component', () => { 26 | test('Will render children when userInfo has the required role', () => { 27 | const protectedContent = 'Admin Content'; 28 | const requiredRole = 'ADMIN'; 29 | 30 | renderWithMockProvider({ 31 | context: { 32 | isLoggedIn: true, 33 | userInfo: { 34 | roles: [requiredRole], 35 | }, 36 | }, 37 | content: ( 38 | 39 |

{protectedContent}

40 |
41 | ), 42 | }); 43 | 44 | expect(screen.queryByText(protectedContent)).toBeInTheDocument(); 45 | }); 46 | 47 | test('Will not render children without the specified role', () => { 48 | const protectedContent = 'Admin Content'; 49 | 50 | renderWithMockProvider({ 51 | context: { 52 | isLoggedIn: true, 53 | userInfo: { 54 | roles: ['USER'], 55 | }, 56 | }, 57 | content: ( 58 | 59 |

{protectedContent}

60 |
61 | ), 62 | }); 63 | 64 | expect(screen.queryByText(protectedContent)).not.toBeInTheDocument(); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/RequireAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 3 | 4 | interface RequireAuthProps extends PropsWithChildren { 5 | /** Children are only rendered for user with a matching role when this prop is specified. */ 6 | withRole?: string | string[]; 7 | } 8 | 9 | /** Only renders children when user is authenticated. */ 10 | export const RequireAuth: FC = ({ withRole, children }) => { 11 | const { userInfo, isLoggedIn } = useFusionAuth(); 12 | 13 | // Check if the user has the required role 14 | // withRole can be a string or an array of strings 15 | const hasRole = withRole 16 | ? ([] as string[]) 17 | .concat(withRole) 18 | .some(role => userInfo?.roles?.includes(role)) 19 | : true; 20 | 21 | const isAuthorized = isLoggedIn && hasRole; 22 | 23 | return <>{isAuthorized && children}; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/Unauthenticated/Unauthenticated.test.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { screen, render } from '@testing-library/react'; 3 | import { afterEach, describe, expect, test, vi } from 'vitest'; 4 | 5 | import { Unauthenticated } from '#components/ui/Unauthenticated'; 6 | import { FusionAuthContext } from '#components/providers/Context'; 7 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 8 | 9 | import { createContextMock } from '#/testing-tools/mocks/createContextMock'; 10 | 11 | const renderWithMockProvider = ({ 12 | context, 13 | content, 14 | }: { 15 | context: Partial; 16 | content: ReactNode; 17 | }) => { 18 | return render( 19 | 20 | {content} 21 | , 22 | ); 23 | }; 24 | 25 | describe('Unauthenticated component', () => { 26 | afterEach(() => { 27 | vi.clearAllMocks(); 28 | }); 29 | 30 | const contentForTheUnauthenticated = 'For unauthenticated eyes only'; 31 | 32 | test('Renders children if unauthenticated', () => { 33 | renderWithMockProvider({ 34 | context: { isLoggedIn: false }, 35 | content: ( 36 | 37 |

{contentForTheUnauthenticated}

38 |
39 | ), 40 | }); 41 | 42 | screen.getByText(contentForTheUnauthenticated); 43 | }); 44 | 45 | test('Does not render children if authenticated', () => { 46 | renderWithMockProvider({ 47 | context: { isLoggedIn: true }, 48 | content: ( 49 | 50 |

{contentForTheUnauthenticated}

51 |
52 | ), 53 | }); 54 | 55 | expect(screen.queryByText(contentForTheUnauthenticated)).toBeNull(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/Unauthenticated/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | import { useFusionAuth } from '#components/providers/FusionAuthProvider'; 3 | 4 | /** Only renders children when user is unauthenticated. */ 5 | export const Unauthenticated: FC = props => { 6 | const { isLoggedIn } = useFusionAuth(); 7 | 8 | return isLoggedIn ? null : <>{props.children}; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/withFusionAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentType } from 'react'; 2 | 3 | import { FusionAuthContext } from '#components/providers/Context'; 4 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 5 | 6 | export interface WithFusionAuthProps { 7 | fusionAuth: FusionAuthProviderContext; 8 | } 9 | 10 | export const withFusionAuth = < 11 | Props extends WithFusionAuthProps = WithFusionAuthProps, 12 | >( 13 | Component: ComponentType, 14 | ) => { 15 | const displayName = Component.displayName; 16 | 17 | const FusionAuthComponent = ( 18 | props: Omit, 19 | ) => ( 20 | 21 | {(fusionAuth: FusionAuthProviderContext) => ( 22 | 23 | )} 24 | 25 | ); 26 | 27 | FusionAuthComponent.displayName = displayName; 28 | 29 | return FusionAuthComponent; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/sdk-react/src/components/ui/withFusionAuth/withFusionAuth.test.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | import { 4 | withFusionAuth, 5 | WithFusionAuthProps, 6 | } from '#components/ui/withFusionAuth'; 7 | import { render } from '@testing-library/react'; 8 | import { FusionAuthContext } from '#components/providers/Context'; 9 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 10 | 11 | import { createContextMock } from '#testing-tools/mocks/createContextMock'; 12 | 13 | describe('withFusionAuth', () => { 14 | test('component wrapped in HOC receives context values', () => { 15 | const startLogout = vi.fn(); 16 | renderWrappedComponent({ startLogout }); 17 | 18 | expect(startLogout).toHaveBeenCalled(); 19 | }); 20 | }); 21 | 22 | class WithoutFusionAuth extends Component { 23 | render() { 24 | return
Test Component
; 25 | } 26 | 27 | componentDidMount() { 28 | this.props.fusionAuth.startLogout(); 29 | } 30 | } 31 | 32 | const WithFusionAuth = withFusionAuth(WithoutFusionAuth); 33 | 34 | const renderWrappedComponent = ( 35 | context: Partial, 36 | ) => { 37 | const contextMock = createContextMock(context); 38 | render( 39 | 40 | 41 | , 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /packages/sdk-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/providers/FusionAuthProvider'; 2 | export * from './components/providers/FusionAuthProviderConfig'; 3 | export * from './components/providers/FusionAuthProviderContext'; 4 | 5 | export * from './components/ui/FusionAuthLoginButton'; 6 | export * from './components/ui/FusionAuthAccountButton'; 7 | export * from './components/ui/FusionAuthLogoutButton'; 8 | export * from './components/ui/FusionAuthRegisterButton'; 9 | export * from './components/ui/RequireAuth'; 10 | export * from './components/ui/Unauthenticated'; 11 | export * from './components/ui/withFusionAuth'; 12 | -------------------------------------------------------------------------------- /packages/sdk-react/src/styles/button.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-stack: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 3 | Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 4 | } 5 | 6 | .fusionAuthButton { 7 | padding: 16px 16px 13px 16px; 8 | border-radius: 8px; 9 | background-color: #083b94; 10 | font-family: var(--font-stack); 11 | font-size: 18px; 12 | font-weight: 600; 13 | text-align: center; 14 | color: #fff; 15 | &:hover { 16 | cursor: pointer; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk-react/src/testing-tools/mocks/createContextMock.ts: -------------------------------------------------------------------------------- 1 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 2 | import { vi } from 'vitest'; 3 | 4 | export const createContextMock = ( 5 | context: Partial, 6 | ): FusionAuthProviderContext => ({ 7 | startLogin: context.startLogin ?? vi.fn(), 8 | startLogout: context.startLogout ?? vi.fn(), 9 | startRegister: context.startRegister ?? vi.fn(), 10 | isLoggedIn: context.isLoggedIn ?? false, 11 | userInfo: context.userInfo ?? null, 12 | refreshToken: context.refreshToken ?? vi.fn(), 13 | initAutoRefresh: context.initAutoRefresh ?? vi.fn(), 14 | manageAccount: context.manageAccount ?? vi.fn(), 15 | isFetchingUserInfo: context.isFetchingUserInfo ?? false, 16 | error: context.error ?? null, 17 | fetchUserInfo: 18 | context.fetchUserInfo ?? 19 | function () { 20 | return Promise.resolve({}); 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /packages/sdk-react/src/testing-tools/mocks/mockCrypto.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'crypto'; 2 | 3 | export const mockCrypto = () => { 4 | Object.defineProperty(global.self, 'crypto', { 5 | value: { 6 | subtle: { 7 | digest: (algorithm: string, data: Uint8Array) => { 8 | return new Promise(resolve => 9 | resolve( 10 | createHash(algorithm.toLowerCase().replace('-', '')) 11 | .update(data) 12 | .digest(), 13 | ), 14 | ); 15 | }, 16 | }, 17 | getRandomValues: (array: number[]): Uint32Array => { 18 | return new Uint32Array(array.length); 19 | }, 20 | }, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/sdk-react/src/testing-tools/mocks/mockUseFusionAuth.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | import * as FusionAuthProvider from '#/components/providers/FusionAuthProvider'; 4 | import { FusionAuthProviderContext } from '#/components/providers/FusionAuthProviderContext'; 5 | 6 | import { createContextMock } from './createContextMock'; 7 | 8 | export const mockUseFusionAuth = ( 9 | context: Partial = {}, 10 | ) => { 11 | const contextMock: FusionAuthProviderContext = createContextMock(context); 12 | return vi 13 | .spyOn(FusionAuthProvider, 'useFusionAuth') 14 | .mockReturnValue(contextMock); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/sdk-react/src/testing-tools/mocks/testConfig.ts: -------------------------------------------------------------------------------- 1 | import { FusionAuthProviderConfig } from '#/components/providers/FusionAuthProviderConfig'; 2 | 3 | export const TEST_CONFIG: FusionAuthProviderConfig = { 4 | clientId: '85a03867-dccf-4882-adde-1a79aeec50df', 5 | serverUrl: 'http://localhost:9000', 6 | redirectUri: 'http://localhost', 7 | scope: 'openid email profile offline_access', 8 | postLogoutRedirectUri: 'http://localhost', 9 | }; 10 | 11 | export const TEST_AUTHPARAM_CONFIG: FusionAuthProviderConfig = { 12 | clientId: '85a03867-dccf-4882-adde-1a79aeec50df', 13 | serverUrl: 'http://localhost:9000', 14 | redirectUri: 'http://localhost', 15 | scope: 'openid email profile offline_access', 16 | authParams: [{ idp_hint: '44449786-3dff-42a6-aac6-1f1ceecb6c46' }], 17 | postLogoutRedirectUri: 'http://localhost', 18 | }; 19 | -------------------------------------------------------------------------------- /packages/sdk-react/src/testing-tools/setup.ts: -------------------------------------------------------------------------------- 1 | import { expect, afterEach } from 'vitest'; 2 | import { cleanup } from '@testing-library/react'; 3 | import '@testing-library/jest-dom'; 4 | import * as matchers from '@testing-library/jest-dom/matchers'; 5 | 6 | // extends Vitest's expect method with methods from react-testing-library 7 | expect.extend(matchers); 8 | 9 | // runs a cleanup after each test case (e.g. clearing jsdom) 10 | afterEach(() => { 11 | cleanup(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/sdk-react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/sdk-react/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/sdk-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "paths": { 7 | "#/*": ["./src/*"], 8 | "#components/*": ["./src/components/*"], 9 | "#styles/*": ["./src/styles/*"], 10 | "#testing-tools/*": ["./src/testing-tools/*"] 11 | }, 12 | "outDir": "dist", 13 | "jsx": "react-jsx", 14 | "sourceMap": true 15 | }, 16 | "include": ["src", "vite.config.ts"], 17 | "typedocOptions": { 18 | "plugin": ["typedoc-plugin-markdown"], 19 | "entryPoints": [ 20 | "src/components/providers/FusionAuthProvider.tsx", 21 | "src/components/providers/FusionAuthProviderConfig.ts", 22 | "src/components/providers/FusionAuthProviderContext.ts", 23 | "src/components/ui/RequireAuth/index.tsx", 24 | "src/components/ui/Unauthenticated/index.tsx", 25 | "src/components/ui/FusionAuthLoginButton/index.tsx", 26 | "src/components/ui/FusionAuthLogoutButton/index.tsx", 27 | "src/components/ui/FusionAuthRegisterButton/index.tsx" 28 | ], 29 | "out": "docs" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/sdk-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | // 2 | import { resolve } from 'node:path'; 3 | import { defineConfig } from 'vite'; 4 | import dts from 'vite-plugin-dts'; 5 | import react from '@vitejs/plugin-react-swc'; 6 | 7 | export default defineConfig({ 8 | build: { 9 | lib: { 10 | entry: resolve(__dirname, 'src/index.ts'), 11 | formats: ['es'], 12 | name: 'index', 13 | fileName: 'index', 14 | }, 15 | rollupOptions: { 16 | external: [ 17 | 'react', 18 | 'react-dom', 19 | 'react/jsx-runtime', 20 | 'next-client-cookies', 21 | ], 22 | }, 23 | sourcemap: true, 24 | }, 25 | plugins: [react(), dts({ rollupTypes: true, insertTypesEntry: true })], 26 | test: { 27 | environment: 'jsdom', 28 | globals: true, 29 | setupFiles: './src/testing-tools/setup.ts', 30 | }, 31 | resolve: { 32 | alias: { 33 | '#': resolve(__dirname, './src'), 34 | '#components': resolve(__dirname, './src/components'), 35 | '#styles': resolve(__dirname, './src/styles'), 36 | '#testing-tools': resolve(__dirname, './src/testing-tools'), 37 | }, 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /packages/sdk-vue/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": false, 3 | "extends": "../../.eslintrc.json" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sdk-vue/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019-2023 FusionAuth 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /packages/sdk-vue/docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /packages/sdk-vue/docs/interfaces/UserInfo.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/vue-sdk](../README.md) / [Exports](../modules.md) / UserInfo 2 | 3 | # Interface: UserInfo 4 | 5 | User information returned from FusionAuth. 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [applicationId](UserInfo.md#applicationid) 12 | - [birthdate](UserInfo.md#birthdate) 13 | - [email](UserInfo.md#email) 14 | - [email_verified](UserInfo.md#email_verified) 15 | - [family_name](UserInfo.md#family_name) 16 | - [given_name](UserInfo.md#given_name) 17 | - [middle_name](UserInfo.md#middle_name) 18 | - [name](UserInfo.md#name) 19 | - [phone_number](UserInfo.md#phone_number) 20 | - [picture](UserInfo.md#picture) 21 | - [preferred_username](UserInfo.md#preferred_username) 22 | - [roles](UserInfo.md#roles) 23 | - [sid](UserInfo.md#sid) 24 | - [sub](UserInfo.md#sub) 25 | - [tid](UserInfo.md#tid) 26 | 27 | ## Properties 28 | 29 | ### applicationId 30 | 31 | • `Optional` **applicationId**: `string` 32 | 33 | #### Defined in 34 | 35 | [src/types.ts:88](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L88) 36 | 37 | --- 38 | 39 | ### birthdate 40 | 41 | • `Optional` **birthdate**: `string` 42 | 43 | #### Defined in 44 | 45 | [src/types.ts:89](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L89) 46 | 47 | --- 48 | 49 | ### email 50 | 51 | • `Optional` **email**: `string` 52 | 53 | #### Defined in 54 | 55 | [src/types.ts:90](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L90) 56 | 57 | --- 58 | 59 | ### email_verified 60 | 61 | • `Optional` **email_verified**: `boolean` 62 | 63 | #### Defined in 64 | 65 | [src/types.ts:91](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L91) 66 | 67 | --- 68 | 69 | ### family_name 70 | 71 | • `Optional` **family_name**: `string` 72 | 73 | #### Defined in 74 | 75 | [src/types.ts:92](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L92) 76 | 77 | --- 78 | 79 | ### given_name 80 | 81 | • `Optional` **given_name**: `string` 82 | 83 | #### Defined in 84 | 85 | [src/types.ts:93](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L93) 86 | 87 | --- 88 | 89 | ### middle_name 90 | 91 | • `Optional` **middle_name**: `string` 92 | 93 | #### Defined in 94 | 95 | [src/types.ts:95](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L95) 96 | 97 | --- 98 | 99 | ### name 100 | 101 | • `Optional` **name**: `string` 102 | 103 | #### Defined in 104 | 105 | [src/types.ts:94](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L94) 106 | 107 | --- 108 | 109 | ### phone_number 110 | 111 | • `Optional` **phone_number**: `string` 112 | 113 | #### Defined in 114 | 115 | [src/types.ts:96](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L96) 116 | 117 | --- 118 | 119 | ### picture 120 | 121 | • `Optional` **picture**: `string` 122 | 123 | #### Defined in 124 | 125 | [src/types.ts:97](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L97) 126 | 127 | --- 128 | 129 | ### preferred_username 130 | 131 | • `Optional` **preferred_username**: `string` 132 | 133 | #### Defined in 134 | 135 | [src/types.ts:98](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L98) 136 | 137 | --- 138 | 139 | ### roles 140 | 141 | • `Optional` **roles**: `any`[] 142 | 143 | #### Defined in 144 | 145 | [src/types.ts:99](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L99) 146 | 147 | --- 148 | 149 | ### sid 150 | 151 | • `Optional` **sid**: `string` 152 | 153 | #### Defined in 154 | 155 | [src/types.ts:100](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L100) 156 | 157 | --- 158 | 159 | ### sub 160 | 161 | • `Optional` **sub**: `string` 162 | 163 | #### Defined in 164 | 165 | [src/types.ts:101](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L101) 166 | 167 | --- 168 | 169 | ### tid 170 | 171 | • `Optional` **tid**: `string` 172 | 173 | #### Defined in 174 | 175 | [src/types.ts:102](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/types.ts#L102) 176 | -------------------------------------------------------------------------------- /packages/sdk-vue/docs/modules.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/vue-sdk](README.md) / Exports 2 | 3 | # @fusionauth/vue-sdk 4 | 5 | ## Table of contents 6 | 7 | ### References 8 | 9 | - [FusionAuthAccountButton](modules.md#fusionauthaccountbutton) 10 | - [FusionAuthLogoutButton](modules.md#fusionauthlogoutbutton) 11 | - [FusionAuthRegisterButton](modules.md#fusionauthregisterbutton) 12 | - [RequireAnonymous](modules.md#requireanonymous) 13 | - [RequireAuth](modules.md#requireauth) 14 | 15 | ### Namespaces 16 | 17 | - [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 18 | 19 | ### Interfaces 20 | 21 | - [FusionAuth](interfaces/FusionAuth.md) 22 | - [FusionAuthConfig](interfaces/FusionAuthConfig.md) 23 | - [UserInfo](interfaces/UserInfo.md) 24 | 25 | ### Variables 26 | 27 | - [default](modules.md#default) 28 | - [fusionAuthKey](modules.md#fusionauthkey) 29 | 30 | ### Functions 31 | 32 | - [createFusionAuth](modules.md#createfusionauth) 33 | - [useFusionAuth](modules.md#usefusionauth) 34 | 35 | ## Other 36 | 37 | ### FusionAuthAccountButton 38 | 39 | Renames and re-exports [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 40 | 41 | --- 42 | 43 | ### FusionAuthLogoutButton 44 | 45 | Renames and re-exports [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 46 | 47 | --- 48 | 49 | ### FusionAuthRegisterButton 50 | 51 | Renames and re-exports [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 52 | 53 | --- 54 | 55 | ### RequireAnonymous 56 | 57 | Renames and re-exports [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 58 | 59 | --- 60 | 61 | ### RequireAuth 62 | 63 | Renames and re-exports [FusionAuthLoginButton](modules/FusionAuthLoginButton.md) 64 | 65 | --- 66 | 67 | ### fusionAuthKey 68 | 69 | • `Const` **fusionAuthKey**: typeof [`fusionAuthKey`](modules.md#fusionauthkey) 70 | 71 | #### Defined in 72 | 73 | [src/injectionSymbols.ts:1](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/injectionSymbols.ts#L1) 74 | 75 | --- 76 | 77 | ### createFusionAuth 78 | 79 | ▸ **createFusionAuth**\<`T`\>(`config`): [`FusionAuth`](interfaces/FusionAuth.md)\<`T`\> 80 | 81 | #### Type parameters 82 | 83 | | Name | Type | 84 | | :--- | :----------------------------------- | 85 | | `T` | [`UserInfo`](interfaces/UserInfo.md) | 86 | 87 | #### Parameters 88 | 89 | | Name | Type | 90 | | :------- | :--------------------------------------------------- | 91 | | `config` | [`FusionAuthConfig`](interfaces/FusionAuthConfig.md) | 92 | 93 | #### Returns 94 | 95 | [`FusionAuth`](interfaces/FusionAuth.md)\<`T`\> 96 | 97 | #### Defined in 98 | 99 | [src/createFusionAuth/createFusionAuth.ts:7](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/createFusionAuth/createFusionAuth.ts#L7) 100 | 101 | --- 102 | 103 | ### useFusionAuth 104 | 105 | ▸ **useFusionAuth**\<`T`\>(): [`FusionAuth`](interfaces/FusionAuth.md)\<`T`\> 106 | 107 | #### Type parameters 108 | 109 | | Name | Type | 110 | | :--- | :----------------------------------- | 111 | | `T` | [`UserInfo`](interfaces/UserInfo.md) | 112 | 113 | #### Returns 114 | 115 | [`FusionAuth`](interfaces/FusionAuth.md)\<`T`\> 116 | 117 | #### Defined in 118 | 119 | [src/composables/useFusionAuth.ts:5](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/composables/useFusionAuth.ts#L5) 120 | 121 | ## Plugin 122 | 123 | ### default 124 | 125 | • `Const` **default**: `Object` 126 | 127 | Installation method for the FusionAuthVuePlugin. 128 | 129 | **`Param`** 130 | 131 | The Vue app instance. 132 | 133 | **`Param`** 134 | 135 | The configuration options for the plugin or an object containing a FusionAuth instance. 136 | 137 | **`Throws`** 138 | 139 | Will throw an error if the required options are missing. 140 | 141 | #### Type declaration 142 | 143 | | Name | Type | 144 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------- | 145 | | `install` | (`app`: `App`\<`any`\>, `options`: [`FusionAuthConfig`](interfaces/FusionAuthConfig.md) \| `FusionAuthInstantiated`) => `void` | 146 | 147 | #### Defined in 148 | 149 | [src/FusionAuthVuePlugin.ts:17](https://github.com/FusionAuth/fusionauth-javascript-sdk/blob/02b46e2174ba0f4804f6b5ef004ac88414902cc3/packages/sdk-vue/src/FusionAuthVuePlugin.ts#L17) 150 | -------------------------------------------------------------------------------- /packages/sdk-vue/docs/modules/FusionAuthLoginButton.md: -------------------------------------------------------------------------------- 1 | [@fusionauth/vue-sdk](../README.md) / [Exports](../modules.md) / FusionAuthLoginButton 2 | 3 | # Namespace: FusionAuthLoginButton 4 | -------------------------------------------------------------------------------- /packages/sdk-vue/generated/generate.sh: -------------------------------------------------------------------------------- 1 | # needs kramdoc installed 2 | # kramdoc handles comments more correctly than pandoc 3 | 4 | # https://github.com/asciidoctor/kramdown-asciidoc 5 | # To install (if you have ruby installed) 6 | # gem install kramdown-asciidoc 7 | 8 | kramdoc ../README.md --heading-offset=1 -o README.adoc 9 | -------------------------------------------------------------------------------- /packages/sdk-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fusionauth/vue-sdk", 3 | "version": "1.2.0", 4 | "description": "FusionAuth solves the problem of building essential security without adding risk or distracting from your primary application", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "docs": "typedoc --plugin typedoc-plugin-markdown", 9 | "build": "vue-docgen-web-types && vue-tsc && vite build", 10 | "preview": "vite preview", 11 | "test": "vitest --environment jsdom --watch=false", 12 | "test:watch": "vitest --environment jsdom" 13 | }, 14 | "repository": "https://github.com/FusionAuth/fusionauth-javascript-sdk", 15 | "author": "FusionAuth", 16 | "license": "Apache", 17 | "bugs": { 18 | "url": "https://github.com/FusionAuth/fusionauth-javascript-sdk/issues" 19 | }, 20 | "homepage": "https://github.com/FusionAuth/fusionauth-javascript-sdk/tree/main/packages/sdk-vue#readme", 21 | "peerDependencies": { 22 | "vue": ">=3.0.0" 23 | }, 24 | "devDependencies": { 25 | "@fusionauth-sdk/core": "*", 26 | "@testing-library/vue": "^8.0.2", 27 | "@types/node": "^18.11.18", 28 | "@vitejs/plugin-vue": "^4.2.3", 29 | "@vue/test-utils": "^2.4.4", 30 | "jsdom": "^24.0.0", 31 | "minimist": "1.2.8", 32 | "nuxt": "^3.12.1", 33 | "sass": "1.65.1", 34 | "typedoc": "^0.25.13", 35 | "typedoc-plugin-markdown": "^3.17.1", 36 | "typescript": "^5.0.2", 37 | "vite": "^4.4.5", 38 | "vite-plugin-dts": "3.5.1", 39 | "vitest": "^1.3.1", 40 | "vue": "^3.0.0", 41 | "vue-docgen-web-types": "0.1.8", 42 | "vue-tsc": "^1.8.5" 43 | }, 44 | "files": [ 45 | "dist/*" 46 | ], 47 | "types": "./dist/index.d.ts", 48 | "main": "./dist/vue-fusionauth.js", 49 | "module": "./dist/vue-fusionauth.js", 50 | "exports": { 51 | ".": { 52 | "types": "./dist/index.d.ts", 53 | "import": "./dist/vue-fusionauth.js" 54 | }, 55 | "./dist/*.css": { 56 | "import": "./dist/*.css", 57 | "require": "./dist/*.css" 58 | } 59 | }, 60 | "web-types": "./web-types.json" 61 | } 62 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/FusionAuthVuePlugin.ts: -------------------------------------------------------------------------------- 1 | import { App, InjectionKey } from 'vue'; 2 | import { FusionAuth, FusionAuthConfig, UserInfo } from './types.ts'; 3 | import * as components from './components/index.ts'; 4 | import { fusionAuthKey } from './injectionSymbols.ts'; 5 | import { createFusionAuth } from './createFusionAuth/index.ts'; 6 | 7 | type FusionAuthInstantiated = { instance: FusionAuth }; 8 | 9 | /** 10 | * Installation method for the FusionAuthVuePlugin. 11 | * 12 | * @category Plugin 13 | * @param {App} app - The Vue app instance. 14 | * @param {FusionAuthConfig | FusionAuthInstantiated} options - The configuration options for the plugin or an object containing a FusionAuth instance. 15 | * @throws {Error} Will throw an error if the required options are missing. 16 | */ 17 | const FusionAuthVuePlugin = { 18 | install(app: App, options: FusionAuthConfig | FusionAuthInstantiated) { 19 | let fusionAuth: FusionAuth; 20 | 21 | if ('instance' in options) { 22 | fusionAuth = options.instance; 23 | } else { 24 | validateConfig(options); 25 | fusionAuth = createFusionAuth(options); 26 | } 27 | 28 | // Register the instance 29 | app.provide(fusionAuthKey as InjectionKey, fusionAuth); 30 | 31 | // Register the components 32 | Object.entries(components).forEach(([key, component]) => { 33 | app.component(key, component); 34 | }); 35 | }, 36 | }; 37 | 38 | function validateConfig(config: FusionAuthConfig) { 39 | if (!config.clientId) { 40 | throw new Error('clientId is required'); 41 | } 42 | 43 | if (!config.serverUrl) { 44 | throw new Error('serverUrl is required'); 45 | } 46 | 47 | if (!config.redirectUri) { 48 | throw new Error('redirectUri is required'); 49 | } 50 | } 51 | 52 | export default FusionAuthVuePlugin; 53 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthAccountButton/FusionAuthAccountButton.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthLoginButton/FusionAuthLoginButton.test.ts: -------------------------------------------------------------------------------- 1 | import FusionAuthLoginButton from './FusionAuthLoginButton.vue'; 2 | import { describe, expect, it } from 'vitest'; 3 | import { shallowMount } from '@vue/test-utils'; 4 | import { getMockFusionAuth } from '../../utilsForTests'; 5 | 6 | const stateValue = 'login-state-value'; 7 | const setup = () => { 8 | const { key, mockedValues } = getMockFusionAuth({}); 9 | return [ 10 | shallowMount(FusionAuthLoginButton, { 11 | props: { state: stateValue }, 12 | global: { 13 | provide: { 14 | [key as symbol]: mockedValues, 15 | }, 16 | }, 17 | slots: { 18 | default: 'Label', 19 | }, 20 | }), 21 | mockedValues, 22 | ] as const; 23 | }; 24 | 25 | describe('FusionAuthLoginButton', () => { 26 | it('renders with label', () => { 27 | // Test 28 | const [Button] = setup(); 29 | 30 | // Assert 31 | expect(Button.text()).toBe('Label'); 32 | }); 33 | 34 | it('can be clicked to run login()', () => { 35 | // Setup 36 | const [Button, { login }] = setup(); 37 | 38 | // Test 39 | Button.trigger('click'); 40 | 41 | // Assert 42 | expect(login).toHaveBeenCalledOnce(); 43 | expect(login).toHaveBeenCalledWith(stateValue); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthLoginButton/FusionAuthLoginButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthLogoutButton/FusionAuthLogoutButton.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { shallowMount } from '@vue/test-utils'; 3 | 4 | import FusionAuthLogoutButton from './FusionAuthLogoutButton.vue'; 5 | import { getMockFusionAuth } from '../../utilsForTests'; 6 | 7 | const label = 'logout button label'; 8 | const setup = () => { 9 | const { key, mockedValues } = getMockFusionAuth({}); 10 | return [ 11 | shallowMount(FusionAuthLogoutButton, { 12 | global: { 13 | provide: { 14 | [key as symbol]: mockedValues, 15 | }, 16 | }, 17 | slots: { 18 | default: label, 19 | }, 20 | }), 21 | mockedValues, 22 | ] as const; 23 | }; 24 | 25 | describe('FusionAuthLogoutButton', () => { 26 | it('renders with label', () => { 27 | const [Button] = setup(); 28 | expect(Button.text()).toBe(label); 29 | }); 30 | 31 | it('can be clicked to run logout()', () => { 32 | const [Button, { logout }] = setup(); 33 | Button.trigger('click'); 34 | expect(logout).toHaveBeenCalledOnce(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthLogoutButton/FusionAuthLogoutButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/FusionAuthRegisterButton/FusionAuthRegisterButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/RequireAnonymous/RequireAnonymous.test.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { describe, expect, it } from 'vitest'; 3 | import { mount } from '@vue/test-utils'; 4 | 5 | import RequireAnonymous from './RequireAnonymous.vue'; 6 | import { getMockFusionAuth } from '../../utilsForTests'; 7 | import { FusionAuth } from '../../types'; 8 | 9 | function setup(params: { 10 | fusionAuthMock: Partial; 11 | content: string; 12 | }) { 13 | const { key, mockedValues } = getMockFusionAuth(params.fusionAuthMock); 14 | return mount(RequireAnonymous, { 15 | global: { provide: { [key as symbol]: mockedValues } }, 16 | slots: { default: params.content }, 17 | }); 18 | } 19 | 20 | describe('RequireAnonymous', () => { 21 | it('Renders the slot if not logged in', () => { 22 | const content = 'There is content here'; 23 | const wrapper = setup({ 24 | fusionAuthMock: { isLoggedIn: ref(false) }, 25 | content, 26 | }); 27 | 28 | expect(wrapper.text()).toBe(content); 29 | }); 30 | 31 | it('Does not render the slot if logged in', () => { 32 | const wrapper = setup({ 33 | fusionAuthMock: { isLoggedIn: ref(true) }, 34 | content: 'For admins only', 35 | }); 36 | 37 | expect(wrapper.text()).toBe(''); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/RequireAnonymous/RequireAnonymous.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/RequireAuth/RequireAuth.test.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { describe, expect, it } from 'vitest'; 3 | import { mount } from '@vue/test-utils'; 4 | 5 | import RequireAuth from './RequireAuth.vue'; 6 | import { getMockFusionAuth } from '../../utilsForTests'; 7 | import { FusionAuth } from '../../types'; 8 | 9 | function setup(params: { 10 | fusionAuthMock: Partial; 11 | content: string; 12 | withRole: string[]; 13 | }) { 14 | const { key, mockedValues } = getMockFusionAuth(params.fusionAuthMock); 15 | 16 | return mount(RequireAuth, { 17 | global: { provide: { [key as symbol]: mockedValues } }, 18 | slots: { default: params.content }, 19 | props: { 20 | withRole: params.withRole, 21 | }, 22 | }); 23 | } 24 | 25 | describe('RequireAuth', () => { 26 | it('Renders the slot if the user is logged in with the specified role', () => { 27 | const content = 'Protected Content'; 28 | const wrapper = setup({ 29 | fusionAuthMock: { 30 | isLoggedIn: ref(true), 31 | userInfo: ref({ roles: ['ADMIN'] }), 32 | }, 33 | content, 34 | withRole: ['USER', 'ADMIN'], 35 | }); 36 | 37 | expect(wrapper.text()).toBe(content); 38 | }); 39 | 40 | it('Does not render the slot if the user is not authorized', () => { 41 | const wrapper = setup({ 42 | fusionAuthMock: { 43 | isLoggedIn: ref(true), 44 | userInfo: ref({ roles: ['USER'] }), 45 | }, 46 | content: 'For admins only', 47 | withRole: ['ADMIN', 'SUPER-ADMIN'], 48 | }); 49 | 50 | expect(wrapper.text()).toBe(''); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/RequireAuth/RequireAuth.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import FusionAuthLoginButton from './FusionAuthLoginButton/FusionAuthLoginButton.vue'; 2 | import FusionAuthLogoutButton from './FusionAuthLogoutButton/FusionAuthLogoutButton.vue'; 3 | import FusionAuthRegisterButton from './FusionAuthRegisterButton/FusionAuthRegisterButton.vue'; 4 | import FusionAuthAccountButton from './FusionAuthAccountButton/FusionAuthAccountButton.vue'; 5 | import RequireAuth from './RequireAuth/RequireAuth.vue'; 6 | import RequireAnonymous from './RequireAnonymous/RequireAnonymous.vue'; 7 | 8 | export { 9 | FusionAuthLoginButton, 10 | FusionAuthLogoutButton, 11 | FusionAuthRegisterButton, 12 | FusionAuthAccountButton, 13 | RequireAuth, 14 | RequireAnonymous, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/composables/useFusionAuth.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, inject } from 'vue'; 2 | import { fusionAuthKey } from '#/injectionSymbols'; 3 | import type { FusionAuth, UserInfo } from '#/types'; 4 | 5 | export const useFusionAuth = (): FusionAuth => { 6 | const fusionAuth = inject(fusionAuthKey as InjectionKey>); 7 | 8 | if (!fusionAuth) { 9 | throw new Error( 10 | 'No FusionAuth instance found. Did you forget to call Vue.use(FusionAuthVuePlugin)?', 11 | ); 12 | } 13 | return fusionAuth; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/createFusionAuth/NuxtUseCookieAdapter.ts: -------------------------------------------------------------------------------- 1 | import { CookieAdapter } from '@fusionauth-sdk/core'; 2 | import { useCookie as useCookieType } from 'nuxt/app'; 3 | 4 | /** 5 | * See docs for more info [useCookie](https://nuxt.com/docs/api/composables/use-cookie). 6 | */ 7 | export class NuxtUseCookieAdapter implements CookieAdapter { 8 | constructor(private useCookie: typeof useCookieType) { 9 | this.useCookie = useCookie; 10 | } 11 | 12 | at_exp(cookieName: string = 'app.at_exp') { 13 | // useCookie must be invoked with the cookie name every time to get the up-to-date value 14 | return this.useCookie(cookieName).value ?? undefined; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/createFusionAuth/createFusionAuth.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref } from 'vue'; 2 | import { SDKCore } from '@fusionauth-sdk/core'; 3 | import { FusionAuth, FusionAuthConfig, UserInfo } from '#/types'; 4 | 5 | import { NuxtUseCookieAdapter } from './NuxtUseCookieAdapter'; 6 | 7 | export const createFusionAuth = ( 8 | config: FusionAuthConfig, 9 | ): FusionAuth => { 10 | let cookieAdapter; 11 | if (config.nuxtUseCookie) { 12 | cookieAdapter = new NuxtUseCookieAdapter(config.nuxtUseCookie); 13 | } 14 | 15 | const core = new SDKCore({ 16 | ...config, 17 | cookieAdapter, 18 | onTokenExpiration: () => { 19 | isLoggedIn.value = false; 20 | }, 21 | }); 22 | 23 | const isLoggedIn = ref(core.isLoggedIn); 24 | const userInfo: Ref = ref(null); 25 | const isGettingUserInfo = ref(false); 26 | const error = ref(null); 27 | 28 | async function getUserInfo() { 29 | isGettingUserInfo.value = true; 30 | error.value = null; 31 | 32 | try { 33 | userInfo.value = await core.fetchUserInfo(); 34 | return userInfo.value; 35 | } catch (e) { 36 | error.value = e as Error; 37 | } finally { 38 | isGettingUserInfo.value = false; 39 | } 40 | } 41 | 42 | async function refreshToken() { 43 | return await core.refreshToken(); 44 | } 45 | 46 | function initAutoRefresh() { 47 | return core.initAutoRefresh(); 48 | } 49 | 50 | function login(state?: string) { 51 | core.startLogin(state); 52 | } 53 | 54 | function register(state?: string) { 55 | core.startRegister(state); 56 | } 57 | 58 | function logout() { 59 | core.startLogout(); 60 | } 61 | 62 | function manageAccount() { 63 | core.manageAccount(); 64 | } 65 | 66 | if (config.shouldAutoFetchUserInfo && core.isLoggedIn === true) { 67 | getUserInfo(); 68 | } 69 | 70 | if (config.shouldAutoRefresh && core.isLoggedIn === true) { 71 | core.initAutoRefresh(); 72 | } 73 | 74 | core.handlePostRedirect(config.onRedirect); 75 | 76 | return { 77 | isLoggedIn, 78 | userInfo, 79 | getUserInfo, 80 | isGettingUserInfo, 81 | error, 82 | login, 83 | register, 84 | logout, 85 | manageAccount, 86 | refreshToken, 87 | initAutoRefresh, 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/createFusionAuth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createFusionAuth'; 2 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './composables/useFusionAuth'; 3 | export * from './types'; 4 | export * from './createFusionAuth/index'; 5 | export * from './injectionSymbols'; 6 | export { default } from './FusionAuthVuePlugin'; 7 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/injectionSymbols.ts: -------------------------------------------------------------------------------- 1 | export const fusionAuthKey = Symbol('fusionAuth'); 2 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/styles/button.scss: -------------------------------------------------------------------------------- 1 | .fusionauth-button { 2 | padding: 16px 16px 13px 16px; 3 | border-radius: 8px; 4 | background-color: var(--fusionauth-button-background-color, #083b94); 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 6 | Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 7 | font-size: 18px; 8 | font-weight: 600; 9 | text-align: center; 10 | color: var(--fusionauth-button-text-color, #fff); 11 | 12 | &:hover { 13 | cursor: pointer; 14 | } 15 | 16 | width: 400px; 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue'; 2 | import { useCookie as useCookieType } from 'nuxt/app'; 3 | 4 | /** 5 | * Config for the FusionAuth Vue SDK 6 | */ 7 | export interface FusionAuthConfig { 8 | /** 9 | * The URL of the FusionAuth server. 10 | */ 11 | serverUrl: string; 12 | /** 13 | * The client id of the application. 14 | */ 15 | clientId: string; 16 | /** 17 | * The redirect URI of the application. 18 | */ 19 | redirectUri: string; 20 | 21 | /** 22 | * The redirect URI for post-logout. Defaults the provided `redirectUri`. 23 | */ 24 | postLogoutRedirectUri?: string; 25 | 26 | /** 27 | * The OAuth2 scope parameter passed to the `/oauth2/authorize` endpoint. If not specified fusionauth will default this to `openid offline_access`. 28 | */ 29 | scope?: string; 30 | 31 | /** 32 | * Enables automatic token refreshing. Defaults to false. 33 | */ 34 | shouldAutoRefresh?: boolean; 35 | 36 | /** 37 | * Enables the SDK to automatically handle fetching user info when logged in. Defaults to false. 38 | */ 39 | shouldAutoFetchUserInfo?: boolean; 40 | 41 | /** 42 | * The number of seconds before the access token expiry when the auto refresh functionality kicks in if enabled. Default is 30. 43 | */ 44 | autoRefreshSecondsBeforeExpiry?: number; 45 | 46 | /** 47 | * Callback function to be invoked with the `state` value upon redirect from login or register. 48 | */ 49 | onRedirect?: (state?: string) => void; 50 | 51 | /** 52 | * Callback to be invoked if a request to refresh the access token fails during autorefresh. 53 | */ 54 | onAutoRefreshFailure?: (error: Error) => void; 55 | 56 | /** 57 | * Pass in `useCookie` from nuxt/app [useCookie](https://nuxt.com/docs/api/composables/use-cookie). 58 | * This is needed for the Vue SDK to support Nuxt/SSR. 59 | */ 60 | nuxtUseCookie?: typeof useCookieType; 61 | 62 | /** 63 | * The path to the login endpoint. 64 | */ 65 | loginPath?: string; 66 | /** 67 | * The path to the logout endpoint. 68 | */ 69 | logoutPath?: string; 70 | /** 71 | * The path to the register endpoint. 72 | */ 73 | registerPath?: string; 74 | /** 75 | * The path to the token refresh endpoint. 76 | */ 77 | tokenRefreshPath?: string; 78 | /** 79 | * The path to the me endpoint. 80 | */ 81 | mePath?: string; 82 | } 83 | 84 | /** 85 | * User information returned from FusionAuth. 86 | */ 87 | export interface UserInfo { 88 | applicationId?: string; 89 | birthdate?: string; 90 | email?: string; 91 | email_verified?: boolean; 92 | family_name?: string; 93 | given_name?: string; 94 | name?: string; 95 | middle_name?: string; 96 | phone_number?: string; 97 | picture?: string; 98 | preferred_username?: string; 99 | roles?: any[]; 100 | sid?: string; 101 | sub?: string; 102 | tid?: string; 103 | } 104 | 105 | /** 106 | * FusionAuth object provided at app-level by FusionAuthVuePlugin 107 | */ 108 | export interface FusionAuth { 109 | /** 110 | * Whether the user is logged in. 111 | */ 112 | isLoggedIn: Ref; 113 | 114 | /** 115 | * This is handled automatically if the SDK is configured with `shouldAutoFetchUserInfo`. 116 | * Internally updates `isFetchingUser` and `userInfo` refs, as well as `error` if the request fails. 117 | * @returns {Promise} 118 | */ 119 | getUserInfo: () => Promise; 120 | 121 | /** 122 | * Data fetched from the configured 'me' endpoint. 123 | */ 124 | userInfo: Ref; 125 | 126 | /** 127 | * Indicates that the getUserInfo call is unresolved. 128 | */ 129 | isGettingUserInfo: Ref; 130 | 131 | /** 132 | * Error occurred within getUserInfo. 133 | */ 134 | error: Ref; 135 | 136 | /** 137 | * Initiates login flow. 138 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 139 | */ 140 | login: (state?: string) => void; 141 | 142 | /** 143 | * Initiates register flow. 144 | * @param {string} [state] - Optional value to be echoed back to the SDK upon redirect. 145 | */ 146 | register: (state?: string) => void; 147 | 148 | /** 149 | * Initiates a logout. 150 | */ 151 | logout: () => void; 152 | 153 | /** 154 | * Redirects to [self service account management](https://fusionauth.io/docs/lifecycle/manage-users/account-management/) 155 | * Self service account management is only available in FusionAuth paid plans. 156 | */ 157 | manageAccount: () => void; 158 | 159 | /** 160 | * Refreshes the access token a single time. 161 | * Token refreshing is handled automatically if configured with `shouldAutoRefresh`. 162 | */ 163 | refreshToken: () => Promise; 164 | 165 | /** 166 | * Initializes automatic refreshing of the access token. 167 | * Refresh is scheduled to happen at the configured `autoRefreshSecondsBeforeExpiry`. 168 | */ 169 | initAutoRefresh: () => NodeJS.Timeout | undefined; 170 | } 171 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/utilsForTests/index.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, ref } from 'vue'; 2 | import { vi } from 'vitest'; 3 | 4 | import type { FusionAuth } from '..'; 5 | 6 | import { fusionAuthKey } from '../injectionSymbols'; 7 | 8 | export const getMockFusionAuth = ({ 9 | login = vi.fn(), 10 | logout = vi.fn(), 11 | register = vi.fn(), 12 | isLoggedIn = ref(false), 13 | userInfo = ref(null), 14 | }: Partial) => { 15 | return { 16 | key: fusionAuthKey as InjectionKey, 17 | mockedValues: { 18 | login, 19 | logout, 20 | register, 21 | isLoggedIn, 22 | userInfo, 23 | }, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/sdk-vue/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue'; 4 | -------------------------------------------------------------------------------- /packages/sdk-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "preserve", 17 | "declaration": true, 18 | "sourceMap": true, 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "paths": { 26 | "#/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 30 | "references": [{ "path": "./tsconfig.node.json" }], 31 | "typedocOptions": { 32 | "plugin": ["typedoc-plugin-markdown"], 33 | "entryPoints": ["src/index.ts"], 34 | "out": "docs" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/sdk-vue/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 | } 11 | -------------------------------------------------------------------------------- /packages/sdk-vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import { resolve } from 'path'; 4 | import dts from 'vite-plugin-dts'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(), dts()], 9 | build: { 10 | sourcemap: true, 11 | lib: { 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | name: 'FusionAuth', 14 | formats: ['es'], 15 | fileName: 'vue-fusionauth', 16 | }, 17 | rollupOptions: { 18 | external: ['vue', 'nuxt'], 19 | output: { 20 | globals: { 21 | vue: 'Vue', 22 | }, 23 | }, 24 | }, 25 | }, 26 | resolve: { 27 | alias: { 28 | '#': resolve(__dirname, './src'), 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /packages/sdk-vue/web-types.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "vue", 3 | "name": "@fusionauth/vue-sdk", 4 | "version": "1.2.0", 5 | "contributions": { 6 | "html": { 7 | "description-markup": "markdown", 8 | "types-syntax": "typescript", 9 | "tags": [ 10 | { 11 | "name": "FusionAuthAccountButton", 12 | "description": "", 13 | "slots": [ 14 | { 15 | "name": "default" 16 | } 17 | ], 18 | "source": { 19 | "module": "./src/components/FusionAuthAccountButton/FusionAuthAccountButton.vue", 20 | "symbol": "default" 21 | } 22 | }, 23 | { 24 | "name": "FusionAuthLoginButton", 25 | "description": "", 26 | "attributes": [ 27 | { 28 | "name": "state", 29 | "required": false, 30 | "value": { 31 | "kind": "expression", 32 | "type": "string" 33 | } 34 | } 35 | ], 36 | "slots": [ 37 | { 38 | "name": "default" 39 | } 40 | ], 41 | "source": { 42 | "module": "./src/components/FusionAuthLoginButton/FusionAuthLoginButton.vue", 43 | "symbol": "default" 44 | } 45 | }, 46 | { 47 | "name": "FusionAuthLogoutButton", 48 | "description": "", 49 | "slots": [ 50 | { 51 | "name": "default" 52 | } 53 | ], 54 | "source": { 55 | "module": "./src/components/FusionAuthLogoutButton/FusionAuthLogoutButton.vue", 56 | "symbol": "default" 57 | } 58 | }, 59 | { 60 | "name": "FusionAuthRegisterButton", 61 | "description": "", 62 | "attributes": [ 63 | { 64 | "name": "state", 65 | "required": false, 66 | "value": { 67 | "kind": "expression", 68 | "type": "string" 69 | } 70 | } 71 | ], 72 | "slots": [ 73 | { 74 | "name": "default" 75 | } 76 | ], 77 | "source": { 78 | "module": "./src/components/FusionAuthRegisterButton/FusionAuthRegisterButton.vue", 79 | "symbol": "default" 80 | } 81 | }, 82 | { 83 | "name": "RequireAnonymous", 84 | "description": "", 85 | "slots": [ 86 | { 87 | "name": "default" 88 | } 89 | ], 90 | "source": { 91 | "module": "./src/components/RequireAnonymous/RequireAnonymous.vue", 92 | "symbol": "default" 93 | } 94 | }, 95 | { 96 | "name": "RequireAuth", 97 | "description": "", 98 | "attributes": [ 99 | { 100 | "name": "withRole", 101 | "required": false, 102 | "value": { 103 | "kind": "expression", 104 | "type": "union" 105 | } 106 | } 107 | ], 108 | "slots": [ 109 | { 110 | "name": "default" 111 | } 112 | ], 113 | "source": { 114 | "module": "./src/components/RequireAuth/RequireAuth.vue", 115 | "symbol": "default" 116 | } 117 | } 118 | ] 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | testDir: './e2e', 5 | /* Run tests in files in parallel */ 6 | fullyParallel: true, 7 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 8 | forbidOnly: !!process.env.CI, 9 | /* Retry on CI only */ 10 | retries: process.env.CI ? 2 : 0, 11 | /* Opt out of parallel tests on CI. */ 12 | workers: 9, 13 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 14 | use: { 15 | /* Base URL to use in actions like `await page.goto('/')`. */ 16 | baseURL: `http://localhost:${process.env.PORT}`, 17 | 18 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 19 | screenshot: 'on', 20 | }, 21 | 22 | /* Configure projects for major browsers */ 23 | projects: [ 24 | { 25 | name: 'chromium', 26 | use: { ...devices['Desktop Chrome'] }, 27 | }, 28 | 29 | { 30 | name: 'firefox', 31 | use: { ...devices['Desktop Firefox'] }, 32 | }, 33 | 34 | { 35 | name: 'webkit', 36 | use: { ...devices['Desktop Safari'] }, 37 | }, 38 | ], 39 | 40 | /* Run your local dev server before starting the tests */ 41 | webServer: { 42 | command: `${process.env.SERVER_COMMAND}`, 43 | url: `http://localhost:${process.env.PORT}`, 44 | reuseExistingServer: !process.env.CI, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["packages"], 3 | "exclude": ["dist"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@fusionauth-sdk/*": ["packages/*/src/index.ts"] 6 | }, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "target": "es2022", 10 | "allowJs": true, 11 | "resolveJsonModule": true, 12 | "moduleDetection": "force", 13 | "isolatedModules": true, 14 | "strict": true, 15 | "noUncheckedIndexedAccess": true, 16 | "moduleResolution": "Bundler", 17 | "module": "ESNext", 18 | "sourceMap": true, 19 | "declaration": true, 20 | "composite": true, 21 | "declarationMap": true, 22 | "lib": ["es2022", "dom", "dom.iterable"], 23 | "allowImportingTsExtensions": true, 24 | "noEmit": true, 25 | "types": ["node"] 26 | } 27 | } 28 | --------------------------------------------------------------------------------