├── tsconfig.build.json ├── jest.config.js ├── tslint.json ├── tsconfig.json ├── examples ├── __tests__ │ ├── basic.tsx │ ├── using-cookies.tsx │ ├── using-profile-only.tsx │ ├── using-token.tsx │ ├── using-string-token.tsx │ ├── using-string-token-with-profileFn.tsx │ ├── using-jwt-expiration.tsx │ ├── using-default-expiration.tsx │ ├── using-expiration-option.tsx │ └── using-refreshFn.tsx ├── basic.tsx ├── using-profile-only.tsx ├── using-cookies.tsx ├── using-string-token.tsx ├── using-string-token-with-profileFn.tsx ├── using-token.tsx ├── using-default-expiration.tsx ├── using-jwt-expiration.tsx ├── using-expiration-option.tsx └── using-refreshFn.tsx ├── src ├── index.ts ├── utils │ ├── useInterval.ts │ ├── useGlobalEvents.ts │ └── useSessionTimers.ts ├── interfaces.d.ts ├── storage │ └── cookies.ts ├── reducer.ts └── context.tsx ├── utils.ts ├── LICENSE ├── .gitignore ├── .semaphore └── semaphore.yml ├── package.json ├── CHANGELOG.md └── README.md /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["examples", "utils.ts", "src/__tests__"] 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | 4 | testPathIgnorePatterns: ["utils", "dist"], 5 | 6 | collectCoverage: true, 7 | collectCoverageFrom: ["src/**/*"] 8 | }; 9 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "interface-name": [true, "never-prefix"] 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": ["esNext", "dom"], 6 | "jsx": "react", 7 | 8 | "outDir": "dist", 9 | 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | "strict": true, 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/__tests__/basic.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import React from "react"; 3 | import { render } from "react-testing-library"; 4 | import { cleanup } from "../../utils"; 5 | 6 | import Component from "../basic"; 7 | 8 | afterEach(cleanup); 9 | 10 | test("basic", () => { 11 | const { getByText } = render(); 12 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: Unknown"); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/__tests__/using-cookies.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import React from "react"; 3 | import { render } from "react-testing-library"; 4 | import { cleanup } from "../../utils"; 5 | 6 | import Component from "../using-cookies"; 7 | 8 | afterEach(cleanup); 9 | 10 | test("using-cookies", () => { 11 | const { getByText } = render(); 12 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 13 | }); 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { UseSessionContext } from "./context"; 3 | import { Profile, UseSession } from "./interfaces"; 4 | 5 | export * from "./interfaces"; 6 | export { default as cookies } from "./storage/cookies"; 7 | export { UseSessionProvider } from "./context"; 8 | 9 | export default () => { 10 | return useContext(UseSessionContext as React.Context>); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/__tests__/using-profile-only.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import React from "react"; 3 | import { render } from "react-testing-library"; 4 | import { cleanup } from "../../utils"; 5 | 6 | import Component from "../using-profile-only"; 7 | 8 | afterEach(cleanup); 9 | 10 | test("using-profile-only", () => { 11 | const { getByText } = render(); 12 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/__tests__/using-token.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-token"; 9 | 10 | afterEach(cleanup); 11 | 12 | test("using-token", () => { 13 | const { getByText } = render(); 14 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/__tests__/using-string-token.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-string-token"; 9 | 10 | afterEach(cleanup); 11 | 12 | test("using-string-token", () => { 13 | const { getByText } = render(); 14 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/__tests__/using-string-token-with-profileFn.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { render, waitForElement } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-string-token-with-profileFn"; 9 | 10 | afterEach(cleanup); 11 | 12 | test("using-string-token-with-profileFn", async () => { 13 | const { getByText } = render(); 14 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/useInterval.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | function useInterval(callback: () => void, delay: number | null) { 4 | const savedCallback = useRef<() => void>(callback); 5 | 6 | // Remember the latest callback. 7 | useEffect(() => { 8 | savedCallback.current = callback; 9 | }); 10 | 11 | // Set up the interval. 12 | useEffect(() => { 13 | function tick() { 14 | savedCallback.current(); 15 | } 16 | if (delay !== null) { 17 | const id = setInterval(tick, delay); 18 | return () => clearInterval(id); 19 | } 20 | }, [delay]); 21 | } 22 | 23 | export default useInterval; 24 | -------------------------------------------------------------------------------- /examples/basic.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const Component = () => { 7 | const session = useSession<{ name: string }>(); 8 | 9 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 10 | // You can also use session.isAuthenticated === true 11 | if (session.isAuthenticatedGuard()) { 12 | return
My Name Is: {session.profile.name}
; 13 | } else { 14 | return
My Name Is: Unknown
; 15 | } 16 | }; 17 | 18 | export default () => ( 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /examples/__tests__/using-jwt-expiration.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { act, render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-jwt-expiration"; 9 | 10 | afterEach(cleanup); 11 | 12 | jest.useFakeTimers(); 13 | 14 | test("using-jwt-expiration", () => { 15 | const { getByText } = render(); 16 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 17 | 18 | act(() => jest.runOnlyPendingTimers()); 19 | 20 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: Unknown"); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/__tests__/using-default-expiration.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { act, render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-default-expiration"; 9 | 10 | afterEach(cleanup); 11 | 12 | jest.useFakeTimers(); 13 | 14 | test("using-jwt-expiration", () => { 15 | const { getByText } = render(); 16 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 17 | 18 | act(() => jest.runOnlyPendingTimers()); 19 | 20 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: Unknown"); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/__tests__/using-expiration-option.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { act, render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-expiration-option"; 9 | 10 | afterEach(cleanup); 11 | 12 | jest.useFakeTimers(); 13 | 14 | test("using-expiration-option", () => { 15 | const { getByText } = render(); 16 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 17 | 18 | act(() => jest.runOnlyPendingTimers()); 19 | 20 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: Unknown"); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/using-profile-only.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const Component = () => { 7 | const session = useSession<{ name: string }>(); 8 | 9 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 10 | // You can also use session.isAuthenticated === true 11 | if (session.isAuthenticatedGuard()) { 12 | return
My Name Is: {session.profile.name}
; 13 | } else { 14 | return
My Name Is: Unknown
; 15 | } 16 | }; 17 | 18 | export default () => ( 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { cleanup as rtlCleanup } from "react-testing-library"; 3 | 4 | import { cookies } from "./src"; 5 | 6 | export const cleanup = () => { 7 | cookies.remove(); 8 | rtlCleanup(); 9 | }; 10 | 11 | export const hideConsoleErrors = () => { 12 | // when the error's thrown a bunch of console.errors are called even though 13 | // the error boundary handles the error. This makes the test output noisy, 14 | // so we'll mock out console.error 15 | jest.spyOn(console, "error").mockImplementation(() => undefined); 16 | }; 17 | 18 | export const token = jwt.sign( 19 | { 20 | exp: (Date.now() + 1 * 60 * 1000) / 1000, // 1 minute 21 | name: "John Smith4" 22 | }, 23 | "secret" 24 | ); 25 | -------------------------------------------------------------------------------- /examples/__tests__/using-refreshFn.tsx: -------------------------------------------------------------------------------- 1 | import "jest-dom/extend-expect"; 2 | import jwt from "jsonwebtoken"; 3 | import React from "react"; 4 | 5 | import { act, render } from "react-testing-library"; 6 | import { cleanup } from "../../utils"; 7 | 8 | import Component from "../using-refreshFn"; 9 | 10 | afterEach(cleanup); 11 | 12 | jest.useFakeTimers(); 13 | 14 | test("using-refreshFn", () => { 15 | const { getByText } = render(); 16 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 17 | 18 | act(() => jest.runOnlyPendingTimers()); 19 | 20 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: Jane Smith"); 21 | 22 | act(() => jest.runOnlyPendingTimers()); 23 | 24 | expect(getByText(/^My Name Is:/)).toHaveTextContent("My Name Is: John Smith"); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/using-cookies.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { cookies, UseSessionProvider } from "../src"; 5 | 6 | const token = jwt.sign( 7 | { 8 | name: "John Smith" 9 | }, 10 | "secret" 11 | ); 12 | 13 | cookies.set({ token }); 14 | 15 | const Component = () => { 16 | const session = useSession<{ name: string }>(); 17 | 18 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 19 | // You can also use session.isAuthenticated === true 20 | if (session.isAuthenticatedGuard()) { 21 | return
My Name Is: {session.profile.name}
; 22 | } else { 23 | return
My Name Is: Unknown
; 24 | } 25 | }; 26 | 27 | export default () => ( 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /examples/using-string-token.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const profile = { 7 | name: "John Smith" 8 | }; 9 | 10 | const token = JSON.stringify(profile); 11 | 12 | const Component = () => { 13 | const session = useSession<{ name: string }>(); 14 | 15 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 16 | // You can also use session.isAuthenticated === true 17 | if (session.isAuthenticatedGuard()) { 18 | return
My Name Is: {session.profile.name}
; 19 | } else { 20 | return
My Name Is: Unknown
; 21 | } 22 | }; 23 | 24 | export default () => ( 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /examples/using-string-token-with-profileFn.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const token = JSON.stringify({ 7 | name: "John Smith" 8 | }); 9 | 10 | const Component = () => { 11 | const session = useSession<{ name: string }>(); 12 | 13 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 14 | // You can also use session.isAuthenticated === true 15 | if (session.isAuthenticatedGuard()) { 16 | return
My Name Is: {session.profile.name}
; 17 | } else { 18 | return
My Name Is: Unknown
; 19 | } 20 | }; 21 | 22 | export default () => ( 23 | JSON.parse(idToken)} 27 | > 28 | 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /examples/using-token.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const token = jwt.sign( 7 | { 8 | name: "John Smith" 9 | }, 10 | "secret" 11 | ); 12 | 13 | // After 1 minute, the text will change from 14 | // 'My Name Is: John Smith' to 'My Name Is: Unknown' 15 | const Component = () => { 16 | const session = useSession<{ name: string }>(); 17 | 18 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 19 | // You can also use session.isAuthenticated === true 20 | if (session.isAuthenticatedGuard()) { 21 | return
My Name Is: {session.profile.name}
; 22 | } else { 23 | return
My Name Is: Unknown
; 24 | } 25 | }; 26 | 27 | export default () => ( 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /examples/using-default-expiration.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const token = jwt.sign( 7 | { 8 | name: "John Smith" 9 | }, 10 | "secret" 11 | ); 12 | 13 | // After 10 hours, the text will change from 14 | // 'My Name Is: John Smith' to 'My Name Is: Unknown' 15 | const Component = () => { 16 | const session = useSession<{ name: string }>(); 17 | 18 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 19 | // You can also use session.isAuthenticated === true 20 | if (session.isAuthenticatedGuard()) { 21 | return
My Name Is: {session.profile.name}
; 22 | } else { 23 | return
My Name Is: Unknown
; 24 | } 25 | }; 26 | 27 | export default () => ( 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /src/utils/useGlobalEvents.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | import { UseSession } from "../interfaces"; 4 | 5 | export default ({ 6 | globalLogin, 7 | globalLogout, 8 | setSession, 9 | removeSession, 10 | storage 11 | }: UseSession) => { 12 | /** 13 | * Global logout/login 14 | */ 15 | useEffect(() => { 16 | const logoutEvent = (event: StorageEvent) => { 17 | if (globalLogout && event.key === "logout") { 18 | removeSession(); 19 | } 20 | 21 | if (globalLogin && event.key === "login") { 22 | setSession(storage.get()); 23 | } 24 | }; 25 | 26 | window.addEventListener("storage", logoutEvent); 27 | 28 | return () => { 29 | window.localStorage.removeItem("logout"); 30 | window.localStorage.removeItem("login"); 31 | window.removeEventListener("storage", logoutEvent); 32 | }; 33 | }, [globalLogout, globalLogin]); 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/useSessionTimers.ts: -------------------------------------------------------------------------------- 1 | import useInterval from "./useInterval"; 2 | 3 | import { UseSession } from "../interfaces"; 4 | 5 | export default (session: UseSession) => { 6 | const { 7 | expiration, 8 | isAuthenticated, 9 | removeSession, 10 | refreshFn, 11 | refreshInterval, 12 | setSession 13 | } = session; 14 | 15 | /*** 16 | * Remove Session Timer 17 | */ 18 | const sessionExpiresIn = 19 | expiration && isAuthenticated ? expiration.valueOf() - Date.now() : null; 20 | 21 | useInterval(() => removeSession(), sessionExpiresIn); 22 | 23 | /*** 24 | * RefreshFn timer 25 | */ 26 | let refreshExpiresIn: number | null = null; 27 | 28 | if (refreshFn && refreshInterval) { 29 | refreshExpiresIn = Math.min(refreshInterval, sessionExpiresIn || Infinity); 30 | } 31 | 32 | useInterval(() => { 33 | setSession(refreshFn!(session)); 34 | }, refreshExpiresIn); 35 | }; 36 | -------------------------------------------------------------------------------- /examples/using-jwt-expiration.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const token = jwt.sign( 7 | { 8 | exp: (Date.now() + 1 * 60 * 1000) / 1000, // 1 minute 9 | name: "John Smith" 10 | }, 11 | "secret" 12 | ); 13 | 14 | // After 1 minute, the text will change from 15 | // 'My Name Is: John Smith' to 'My Name Is: Unknown' 16 | const Component = () => { 17 | const session = useSession<{ name: string }>(); 18 | 19 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 20 | // You can also use session.isAuthenticated === true 21 | if (session.isAuthenticatedGuard()) { 22 | return
My Name Is: {session.profile.name}
; 23 | } else { 24 | return
My Name Is: Unknown
; 25 | } 26 | }; 27 | 28 | export default () => ( 29 | 30 | 31 | 32 | ); 33 | -------------------------------------------------------------------------------- /examples/using-expiration-option.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const token = jwt.sign( 7 | { 8 | name: "John Smith" 9 | }, 10 | "secret" 11 | ); 12 | 13 | // After 1 minute, the text will change from 14 | // 'My Name Is: John Smith' to 'My Name Is: Unknown' 15 | const Component = () => { 16 | const session = useSession<{ name: string }>(); 17 | 18 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 19 | // You can also use session.isAuthenticated === true 20 | if (session.isAuthenticatedGuard()) { 21 | return
My Name Is: {session.profile.name}
; 22 | } else { 23 | return
My Name Is: Unknown
; 24 | } 25 | }; 26 | 27 | export default () => ( 28 | 32 | 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mark Lawlor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/using-refreshFn.tsx: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import React from "react"; 3 | 4 | import useSession, { UseSessionProvider } from "../src"; 5 | 6 | const firstToken = jwt.sign( 7 | { 8 | name: "John Smith" 9 | }, 10 | "secret" 11 | ); 12 | 13 | const secondToken = jwt.sign( 14 | { 15 | name: "Jane Smith" 16 | }, 17 | "secret" 18 | ); 19 | 20 | const refreshFn = ({ token }: any) => { 21 | if (token === firstToken) { 22 | return { 23 | token: secondToken 24 | }; 25 | } else { 26 | return { 27 | token: firstToken 28 | }; 29 | } 30 | }; 31 | 32 | // Have a refreshFn that cycles between two tokens 33 | const Component = () => { 34 | const session = useSession<{ name: string }>(); 35 | 36 | // Typescript projects can use session.isAuthenticatedGuard() as a typeguard. 37 | // You can also use session.isAuthenticated === true 38 | if (session.isAuthenticatedGuard()) { 39 | return
My Name Is: {session.profile.name}
; 40 | } else { 41 | return
My Name Is: Unknown
; 42 | } 43 | }; 44 | 45 | export default () => ( 46 | 51 | 52 | 53 | ); 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | 82 | # Typescript outdir 83 | dist 84 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: react-session-hook 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu1804 7 | 8 | blocks: 9 | - name: Install dependencies 10 | task: 11 | jobs: 12 | - name: yarn install and cache 13 | commands: 14 | - checkout 15 | - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-master 16 | - yarn install 17 | - cache store node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock) node_modules 18 | - name: "Tests" 19 | task: 20 | secrets: 21 | - name: codecov-token 22 | jobs: 23 | - name: Jest 24 | commands: 25 | - checkout 26 | - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-master 27 | - yarn test 28 | - npx codecov 29 | 30 | - name: Lint 31 | commands: 32 | - checkout 33 | - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-master 34 | - yarn lint 35 | 36 | - name: Check security 37 | commands: 38 | - checkout 39 | - yarn audit 40 | 41 | - name: "Releases" 42 | task: 43 | secrets: 44 | - name: service-tokens 45 | jobs: 46 | - name: Semantic Release 47 | commands: 48 | - checkout 49 | - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH 50 | - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-master 51 | - yarn package 52 | - if [ $SEMAPHORE_GIT_BRANCH == "master" ]; then yarn semantic-release; fi 53 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-session-hook", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "description": "ReactJS hook to manage session state and storage", 7 | "typings": "index.d.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/marklawlor/react-session-hook" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "hook", 15 | "react-hooks", 16 | "session", 17 | "tokens" 18 | ], 19 | "scripts": { 20 | "test": "jest", 21 | "lint": "tslint -p tsconfig.json", 22 | "postpublish": "cp package.json ../", 23 | "package": "tsc -p tsconfig.build.json && cp -t dist/ src/interfaces.d.ts README.md package.json LICENSE" 24 | }, 25 | "devDependencies": { 26 | "@semantic-release/changelog": "^3.0.2", 27 | "@semantic-release/git": "^7.0.8", 28 | "@semantic-release/github": "^5.2.9", 29 | "@types/jest": "^24.0.3", 30 | "@types/jest-diff": "^20.0.0", 31 | "@types/jsonwebtoken": "^8.3.0", 32 | "@types/jwt-decode": "^2.2.1", 33 | "@types/node": "^11.9.0", 34 | "@types/react": "^16.8.2", 35 | "jest": "^24.1.0", 36 | "jest-dom": "^3.1.0", 37 | "jsonwebtoken": "^8.4.0", 38 | "prettier": "^1.16.4", 39 | "react": "^16.8.1", 40 | "react-dom": "^16.8.1", 41 | "react-testing-library": "^6.0.0", 42 | "semantic-release": "^15.13.3", 43 | "ts-jest": "^24.0.0", 44 | "tslint": "^5.12.1", 45 | "tslint-config-prettier": "^1.18.0", 46 | "typescript": "^3.3.3" 47 | }, 48 | "peerDependencies": { 49 | "react": "^16.8.1" 50 | }, 51 | "dependencies": { 52 | "jwt-decode": "^2.2.0", 53 | "universal-cookie": "^3.0.7" 54 | }, 55 | "release": { 56 | "plugins": [ 57 | "@semantic-release/commit-analyzer", 58 | "@semantic-release/release-notes-generator", 59 | "@semantic-release/changelog", 60 | [ 61 | "@semantic-release/npm", 62 | { 63 | "pkgRoot": "dist" 64 | } 65 | ], 66 | "@semantic-release/github", 67 | "@semantic-release/git" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export type Profile = Record; 2 | 3 | export interface DispatchAction { 4 | type: string; 5 | value?: any; 6 | } 7 | 8 | export interface Tokens { 9 | accessToken?: string; 10 | idToken?: string; 11 | refreshToken?: string; 12 | token?: string; 13 | } 14 | 15 | export interface UseSessionOptions { 16 | globalLogin: boolean; 17 | globalLogout: boolean; 18 | jwt: boolean; 19 | refreshFn?: any; 20 | refreshInterval?: number | null; 21 | profileFn?: (token: string) => TProfile; 22 | storage: any; 23 | expiration?: Date | null; 24 | } 25 | 26 | export interface UseSessionProviderProps 27 | extends UseSessionOptions { 28 | initialAccessToken: string; 29 | initialIdToken: string; 30 | initialRefreshToken: string; 31 | initialToken: string; 32 | 33 | initialProfile: TProfile; 34 | } 35 | 36 | export interface UseSession extends UseSessionOptions { 37 | accessToken?: string; 38 | idToken?: string; 39 | refreshToken?: string; 40 | token?: string; 41 | 42 | errorMessage?: string; 43 | 44 | profile?: TProfile; 45 | 46 | expiration?: Date | null; 47 | 48 | isAuthenticated: boolean; 49 | 50 | setSession: (session: any) => void; 51 | removeSession: () => void; 52 | setErrorMessage: (message?: string) => void; 53 | clearErrorMessage: () => void; 54 | 55 | dispatch: React.Dispatch<{ type: string; value?: any }>; 56 | 57 | isAuthenticatedGuard: ( 58 | this: UseSession 59 | ) => this is AuthenticatedSession; 60 | } 61 | 62 | export interface AuthenticatedSession extends UseSession { 63 | profile: TProfile; 64 | isAuthenticated: true; 65 | refreshInterval?: number; 66 | } 67 | 68 | export interface UnAuthenticatedSession extends UseSession { 69 | profile: undefined; 70 | isAuthenticated: false; 71 | refreshInterval?: null; 72 | } 73 | 74 | export interface HttpReq { 75 | headers: { 76 | cookie?: string; 77 | }; 78 | } 79 | 80 | // export interface Storage { 81 | // set: (tokens: Tokens, expires?: any, req?: HttpReq) => void; 82 | // get: (req?: HttpReq) => Tokens; 83 | // remove: () => void; 84 | // } 85 | -------------------------------------------------------------------------------- /src/storage/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from "universal-cookie"; 2 | 3 | import { HttpReq, Tokens } from "../interfaces"; 4 | 5 | interface CookieStorageOptions { 6 | req?: { 7 | headers: { 8 | cookie?: string; 9 | }; 10 | }; 11 | storageAliases: { 12 | accessToken: string; 13 | idToken: string; 14 | refreshToken: string; 15 | token: string; 16 | }; 17 | } 18 | 19 | const defaultOptions: CookieStorageOptions = { 20 | storageAliases: { 21 | accessToken: "accessToken", 22 | idToken: "idToken", 23 | refreshToken: "refreshToken", 24 | token: "token" 25 | } 26 | }; 27 | 28 | const cookieStorage = (options: Partial = {}) => { 29 | const { req, storageAliases } = { ...defaultOptions, ...options }; 30 | 31 | const cookies = new Cookies(req ? req.headers.cookie : undefined); 32 | 33 | return { 34 | get: (): Tokens => { 35 | const allCookies = cookies.getAll(); 36 | return { 37 | accessToken: allCookies[storageAliases.accessToken], 38 | idToken: allCookies[storageAliases.idToken], 39 | refreshToken: allCookies[storageAliases.refreshToken], 40 | token: allCookies[storageAliases.token] 41 | }; 42 | }, 43 | 44 | remove: () => { 45 | cookies.remove(storageAliases.accessToken); 46 | cookies.remove(storageAliases.idToken); 47 | cookies.remove(storageAliases.refreshToken); 48 | cookies.remove(storageAliases.token); 49 | }, 50 | 51 | set: ( 52 | { accessToken, idToken, refreshToken, token }: Tokens, 53 | expires?: Date 54 | ) => { 55 | if (accessToken) { 56 | cookies.set(storageAliases.accessToken, accessToken, { expires }); 57 | } 58 | 59 | if (idToken) { 60 | cookies.set(storageAliases.idToken, idToken, { expires }); 61 | } 62 | 63 | if (refreshToken) { 64 | cookies.set(storageAliases.refreshToken, refreshToken, { expires }); 65 | } 66 | 67 | if (token) { 68 | cookies.set(storageAliases.token, token, { expires }); 69 | } 70 | } 71 | }; 72 | }; 73 | 74 | cookieStorage.get = (options: Partial = {}) => { 75 | return cookieStorage(options).get(); 76 | }; 77 | 78 | cookieStorage.set = (tokens: Tokens, expires?: Date) => { 79 | return cookieStorage().set(tokens, expires); 80 | }; 81 | 82 | cookieStorage.remove = () => { 83 | return cookieStorage().remove(); 84 | }; 85 | 86 | export default cookieStorage; 87 | -------------------------------------------------------------------------------- /src/reducer.ts: -------------------------------------------------------------------------------- 1 | import jwtDecode from "jwt-decode"; 2 | 3 | import { DispatchAction, UseSession } from "./interfaces"; 4 | 5 | export const reducer = ( 6 | state: UseSession, 7 | action: DispatchAction 8 | ): UseSession => { 9 | if (action.type === "setSession") { 10 | state.storage.set(action.value); 11 | return getState({ ...state, ...action.value }); 12 | } 13 | 14 | if (action.type === "removeSession") { 15 | window.localStorage.setItem("logout", Date.now().toString()); 16 | state.storage.remove(); 17 | 18 | return getState({ 19 | ...state, 20 | expiration: null, 21 | isAuthenticated: false, 22 | profile: undefined, 23 | 24 | accessToken: undefined, 25 | idToken: undefined, 26 | refreshToken: undefined, 27 | token: undefined 28 | }); 29 | } 30 | 31 | if (action.type === "setErrorMessage") { 32 | return getState({ ...state, errorMessage: action.value }); 33 | } 34 | 35 | throw new Error(); 36 | }; 37 | 38 | export const getState = ( 39 | state: UseSession 40 | ): UseSession => { 41 | const profile = getProfile({ ...state }); 42 | const expiration = getExpiration({ ...state, profile }); 43 | const isAuthenticated = getIsAuthenticated({ ...state, expiration, profile }); 44 | 45 | return { 46 | ...state, 47 | 48 | expiration, 49 | isAuthenticated, 50 | profile 51 | }; 52 | }; 53 | 54 | export const getProfile = ({ 55 | accessToken, 56 | idToken, 57 | jwt, 58 | profile, 59 | profileFn, 60 | token 61 | }: any): TProfile | undefined => { 62 | if (profileFn) { 63 | if (accessToken && idToken) { 64 | profile = profileFn(idToken); 65 | } else if (token) { 66 | profile = profileFn(token); 67 | } 68 | } else if (jwt) { 69 | if (accessToken && idToken) { 70 | profile = jwtDecode(idToken); 71 | } else if (token) { 72 | profile = jwtDecode(token); 73 | } 74 | } 75 | 76 | return profile; 77 | }; 78 | 79 | export const getExpiration = ({ expiration, profile }: any) => { 80 | if (expiration || expiration === null) { 81 | return expiration; 82 | } 83 | 84 | if (profile && profile.exp) { 85 | return new Date(profile.exp * 1000); 86 | } 87 | 88 | return new Date(Date.now() + 10 * 60 * 60 * 1000); // 10 hours 89 | }; 90 | 91 | export const getIsAuthenticated = ({ expiration, profile }: any) => { 92 | if (expiration === null && profile) { 93 | return true; 94 | } 95 | 96 | if (expiration && profile) { 97 | return Date.now() < expiration.valueOf(); 98 | } 99 | 100 | return false; 101 | }; 102 | -------------------------------------------------------------------------------- /src/context.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | DispatchAction, 5 | Profile, 6 | UseSession, 7 | UseSessionProviderProps 8 | } from "./interfaces"; 9 | 10 | import cookies from "./storage/cookies"; 11 | 12 | import { getState, reducer } from "./reducer"; 13 | import useGlobalEvents from "./utils/useGlobalEvents"; 14 | import useSessionTimers from "./utils/useSessionTimers"; 15 | 16 | const defaults: UseSession = { 17 | globalLogin: true, 18 | globalLogout: true, 19 | jwt: true, 20 | refreshInterval: 15 * 60 * 1000, 21 | storage: cookies, 22 | 23 | isAuthenticated: false, 24 | 25 | removeSession: () => undefined, 26 | setSession(value: any) { 27 | this.dispatch({ type: "setSession", value }); 28 | }, 29 | 30 | clearErrorMessage: () => undefined, 31 | setErrorMessage: () => undefined, 32 | 33 | dispatch: () => undefined, 34 | 35 | isAuthenticatedGuard: () => false 36 | }; 37 | 38 | export const UseSessionContext = React.createContext(defaults); 39 | 40 | export const UseSessionProvider = ( 41 | props: Partial> & { 42 | children?: React.ReactNode; 43 | } 44 | ) => { 45 | const { 46 | children, 47 | initialAccessToken, 48 | initialIdToken, 49 | initialProfile, 50 | initialRefreshToken, 51 | initialToken, 52 | ...options 53 | } = { ...defaults, ...props }; 54 | 55 | const { accessToken, idToken, refreshToken, token } = options.storage.get(); 56 | 57 | const initialState: UseSession = { 58 | accessToken: initialAccessToken || accessToken, 59 | idToken: initialIdToken || idToken, 60 | refreshToken: initialRefreshToken || refreshToken, 61 | token: initialToken || token, 62 | 63 | profile: initialProfile, 64 | 65 | isAuthenticated: false, 66 | 67 | ...options 68 | }; 69 | 70 | const [state, dispatch] = React.useReducer< 71 | React.Reducer, DispatchAction>, 72 | UseSession 73 | >(reducer, initialState, getState); 74 | 75 | const session: UseSession = { 76 | ...state, 77 | 78 | dispatch, 79 | 80 | setSession: (value: any) => dispatch({ type: "setSession", value }), 81 | 82 | removeSession: () => dispatch({ type: "removeSession" }), 83 | 84 | clearErrorMessage: () => dispatch({ type: "setErrorMessage" }), 85 | 86 | setErrorMessage: (value?: string) => { 87 | dispatch({ type: "setErrorMessage", value }); 88 | }, 89 | 90 | isAuthenticatedGuard() { 91 | return state.isAuthenticated; 92 | } 93 | }; 94 | 95 | useGlobalEvents(session); 96 | useSessionTimers(session); 97 | 98 | return ( 99 | 100 | {children} 101 | 102 | ); 103 | }; 104 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.1](https://github.com/marklawlor/react-session-hook/compare/v2.0.0...v2.0.1) (2019-02-18) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add missing interfaces.d.ts to npm package ([fa42e71](https://github.com/marklawlor/react-session-hook/commit/fa42e71)) 7 | 8 | # [2.0.0](https://github.com/marklawlor/react-session-hook/compare/v1.2.0...v2.0.0) (2019-02-14) 9 | 10 | 11 | ### Features 12 | 13 | * added UseSessionProvider ([#9](https://github.com/marklawlor/react-session-hook/issues/9)) ([dca5744](https://github.com/marklawlor/react-session-hook/commit/dca5744)) 14 | 15 | 16 | ### BREAKING CHANGES 17 | 18 | * All useSession options have been moved to the new UseSessionProvider. 19 | Your application will need to be wrapped in a component. 20 | 21 | See README and examples for more information 22 | 23 | # [1.2.0](https://github.com/marklawlor/react-session-hook/compare/v1.1.1...v1.2.0) (2019-02-13) 24 | 25 | 26 | ### Features 27 | 28 | * fix globalLogout and globalLogin false ([#8](https://github.com/marklawlor/react-session-hook/issues/8)) ([c14fc86](https://github.com/marklawlor/react-session-hook/commit/c14fc86)) 29 | 30 | ## [1.1.1](https://github.com/marklawlor/react-session-hook/compare/v1.1.0...v1.1.1) (2019-02-12) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * fix dependencies ([ab97e6a](https://github.com/marklawlor/react-session-hook/commit/ab97e6a)) 36 | 37 | # [1.1.0](https://github.com/marklawlor/react-session-hook/compare/v1.0.6...v1.1.0) (2019-02-11) 38 | 39 | 40 | ### Features 41 | 42 | * add global .config() function ([7224f1e](https://github.com/marklawlor/react-session-hook/commit/7224f1e)) 43 | 44 | ## [1.0.6](https://github.com/marklawlor/react-session-hook/compare/v1.0.5...v1.0.6) (2019-02-11) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * indentation on readme ([dd72009](https://github.com/marklawlor/react-session-hook/commit/dd72009)) 50 | 51 | ## [1.0.5](https://github.com/marklawlor/react-session-hook/compare/v1.0.4...v1.0.5) (2019-02-11) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * update readme ([82f4547](https://github.com/marklawlor/react-session-hook/commit/82f4547)) 57 | 58 | ## [1.0.4](https://github.com/marklawlor/react-session-hook/compare/v1.0.3...v1.0.4) (2019-02-11) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * spelling errors in readme ([7549697](https://github.com/marklawlor/react-session-hook/commit/7549697)) 64 | 65 | ## [1.0.3](https://github.com/marklawlor/react-session-hook/compare/v1.0.2...v1.0.3) (2019-02-11) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * update readme ([a3b038f](https://github.com/marklawlor/react-session-hook/commit/a3b038f)) 71 | 72 | ## [1.0.2](https://github.com/marklawlor/react-session-hook/compare/v1.0.1...v1.0.2) (2019-02-11) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * update readme ([715635d](https://github.com/marklawlor/react-session-hook/commit/715635d)) 78 | 79 | ## [1.0.1](https://github.com/marklawlor/react-session-hook/compare/v1.0.0...v1.0.1) (2019-02-11) 80 | 81 | 82 | ### Bug Fixes 83 | 84 | * updated readme ([5e200eb](https://github.com/marklawlor/react-session-hook/commit/5e200eb)) 85 | 86 | # 1.0.0 (2019-02-10) 87 | 88 | 89 | ### Features 90 | 91 | * initial commit ([99038a6](https://github.com/marklawlor/react-session-hook/commit/99038a6)) 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

👤

3 |

react-session-hook

4 |

Stateful sessions made easy

5 |

6 | 7 | ## Overview 8 | 9 | [![Greenkeeper badge](https://badges.greenkeeper.io/marklawlor/react-session-hook.svg)](https://greenkeeper.io/) 10 | 11 | 12 | ✔️ Simple API
13 | ✔️ Supports JWTs, string tokens and custom profiles
14 | ✔️ Supports handling multiple tokens (access-token, id-token, refresh-token)
15 | ✔️ Automatic logout and periodic refresh functions
16 | ✔️ Customisable persistent storage
17 | ✔️ Global login and logout events across tabs
18 | ✔️ Typescript typings and helper functions
19 |
20 | 21 | 22 | Coming soon 23 | 24 | 25 | 📋Server-side rendering
26 |
27 | 28 | 29 | 30 | ## Getting Started 31 | 32 | Install react-session-hook using [`yarn`](https://yarnpkg.com): 33 | 34 | ```bash 35 | yarn add react-session-hook 36 | ``` 37 | 38 | Or [`npm`](https://www.npmjs.com/): 39 | 40 | ```bash 41 | npm install --save react-session-hook 42 | ``` 43 | 44 | And within your react component: 45 | 46 | ```javascript 47 | import useSession, { UseSessionProvider } from 'react-session-hook'; 48 | 49 | const Component = () => { 50 | const session = useSession(); 51 | 52 | const handleLogin = () => session.setSession({ token: newToken }) 53 | const handleLogout = () => session.removeSession() 54 | 55 | if (session.isAuthenticated) { 56 | return ( 57 |
58 |
Logged in as: {session.profile.name}
59 | 60 |
61 | ) 62 | } else { 63 | return ( 64 |
65 |
You are logged out
66 | 67 |
68 | ) 69 | } 70 | } 71 | 72 | export const App = () => ( 73 | 74 | 75 | 76 | ) 77 | ``` 78 | 79 | ## Examples 80 | 81 | See the [examples folder](https://github.com/marklawlor/react-session-hook/tree/master/examples) 82 | 83 | ## UseSessionProvider Options 84 | 85 | ```javascript 86 | { 87 | // The initial value for a token. 88 | // If undefined, they will default to the value in storage 89 | initialAccessToken: String (optional) 90 | initialIdToken: String (optional) 91 | initialRefreshToken String (optional) 92 | initialToken: String (optional) 93 | 94 | // The session profile. 95 | // If undefined, they will follow this logic 96 | // 97 | // If profileFn = True 98 | // Set as the return value of profileFn 99 | // Else if jwt = True 100 | // This will be set as the payload of the id Token or token 101 | initialProfile: Object (optional) 102 | 103 | // Attempt to parse profile and expiration date from the tokens 104 | jwt: Boolean (True) 105 | 106 | // Date object for the session expiration 107 | // If jwt = True 108 | // Uses the 'exp' field in the accessToken or token body 109 | // If there is no 'exp' field, this value is used 110 | // If no expiration is set the session will expire after 10 hours 111 | // Set value to null to explicitly set a session that never expires 112 | expiration: Date (optional) 113 | 114 | // Returned object passed to setSession 115 | // Notes: 116 | // - will not be fired if isAuthenticated = false 117 | // - will be fired before the refreshInterval 118 | // if session expires before refresh period 119 | refreshFn: async (session) => Session (optional) 120 | 121 | // How often the refreshFn is called. 122 | refreshInterval: Number (1 * 60 * 60 * 1000) // 1 hours 123 | 124 | // Provide your own profile parsing logic. Useful for string tokens 125 | profileFn: (String) => Object | void 126 | 127 | // Defaults to cookie storage. See Storage for more information 128 | storage: Storage (optional) 129 | 130 | // See: Server-Side Rendering for more information 131 | req: Request (optional) 132 | 133 | // See: Global login/logout for more information 134 | globalLogin: Boolean (True) 135 | globalLogout: Boolean (True) 136 | } 137 | ``` 138 | 139 | ### Returned values 140 | 141 | ```javascript 142 | { 143 | // The session tokens 144 | token: String | void 145 | accessToken: String | void 146 | idToken: String | void 147 | refreshToken: String | void 148 | 149 | // Manually update the session and storage 150 | setSession: Function (options) => void 151 | 152 | // Manually remove the session (will clear storage) 153 | removeSession: Function () => void 154 | 155 | profile: Object | void 156 | 157 | expiration: Date | null 158 | 159 | // If an accessToken or token exists and has not expired 160 | isAuthenticated: Boolean 161 | 162 | // See: Typescript section for more information 163 | isAuthenticatedGuard: () => Boolean 164 | 165 | // You can manually invoke the refreshFn 166 | refreshFn: Function (options) => options 167 | 168 | // If null, the refreshFn is paused. 169 | refreshInterval: Number | null 170 | } 171 | ``` 172 | 173 | ## Features 174 | 175 | ### Global login/logout 176 | 177 | By default, browser tabs are kept in sync. If `removeSession` is called in one tab, it will be called in all tabs 178 | 179 | If you wish to disable this behaviour set `globalLogout` and `globalLogin` to false in the options 180 | 181 | ### Server Side Rendering 182 | 183 | 184 | 📋This feature is coming soon
185 |
186 | 187 | If the `req` option is used, the package assumes you are performing Server Side Rendering. If you are using the default cookie storage, it will switch to using the request headers and an in-memory store. 188 | 189 | If you are using a custom storage, the request will be passed to your store. 190 | 191 | ### Typescript 192 | 193 | `react-session-hook` was written in Typescript and includes typings out of the box. 194 | 195 | It also includes the `isAuthenticatedGuard` function, which acts as a typeguard between an 196 | authenticated and authenticated session 197 | 198 | ```javascript 199 | import useSession from 'react-session-hook'; 200 | 201 | interface Profile { 202 | name: string 203 | } 204 | 205 | export default () => { 206 | // use the JWT values in storage 207 | const session = useSession(); 208 | 209 | if (session.isAuthenticatedGuard()) { 210 | // Typed as session.profile = Profile 211 | return
Logged in as: {session.profile.name}
212 | } else { 213 | // Typed as session.profile = null 214 | return
You are logged out
215 | } 216 | } 217 | ``` 218 | 219 | ## Storage 220 | 221 | By defaut, your session tokens will be stored as seperate cookies. You can overwrite this by providing custom storage functions in the useSession options 222 | 223 | [See the cookies storage functions for more information](https://github.com/marklawlor/react-session-hook/blob/master/src/storage/cookies.ts) 224 | 225 | ## Misc 226 | 227 | The `setSesson` function can also be used to update some of the options. You can update the `refreshFn` with `setSession({ refreshFn })` or disable the refresh with `setSession({ refreshInterval: null })` 228 | 229 | --------------------------------------------------------------------------------