├── 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 | [](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 |
Logout
60 |
61 | )
62 | } else {
63 | return (
64 |
65 |
You are logged out
66 |
Login
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 |
--------------------------------------------------------------------------------