diff --git a/package-lock.json b/package-lock.json index bd175e4..59a12b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@flare/tuitio-client": "^1.0.0" + "@flare/tuitio-client": "^1.0.2" }, "devDependencies": { "@types/jest": "^29.4.0", + "@types/react": "^18.0.27", "jest": "^29.4.1", "jest-environment-jsdom": "^29.4.2", "prettier": "^2.8.3", @@ -616,9 +617,9 @@ "integrity": "sha512-VgXQHoQEVZ/71B6YQHQP8/Yd/w1smGD+kCCiNvJKZ1xMD3nkN9mjoHxIqbOJMZ2q5PZlV6gXYT7eVol8Wm+D0A==" }, "node_modules/@flare/tuitio-client": { - "version": "1.0.0", - "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.0.0.tgz", - "integrity": "sha512-w3XBzH02qg+PsKOz1nbVk5BQdGoCssKzotYlMq59zttn2kHN6G7QDrg7VqKwSb4n0udyRAUGafzu9aUL8VEiFA==", + "version": "1.0.2", + "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.0.2.tgz", + "integrity": "sha512-USpnUfZ36RiTg2UTVRvW5+FT3c8UuXjTbIe02GLLKImjF4ZV27Rz9nn7UaVtkQLt9YyxNBYokGKekKl+WVRG0Q==", "dependencies": { "@flare/js-utils": "^1.0.3", "axios": "^1.3.2" @@ -1114,6 +1115,29 @@ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.0.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", + "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -1644,6 +1668,12 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -4996,9 +5026,9 @@ "integrity": "sha512-VgXQHoQEVZ/71B6YQHQP8/Yd/w1smGD+kCCiNvJKZ1xMD3nkN9mjoHxIqbOJMZ2q5PZlV6gXYT7eVol8Wm+D0A==" }, "@flare/tuitio-client": { - "version": "1.0.0", - "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.0.0.tgz", - "integrity": "sha512-w3XBzH02qg+PsKOz1nbVk5BQdGoCssKzotYlMq59zttn2kHN6G7QDrg7VqKwSb4n0udyRAUGafzu9aUL8VEiFA==", + "version": "1.0.2", + "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.0.2.tgz", + "integrity": "sha512-USpnUfZ36RiTg2UTVRvW5+FT3c8UuXjTbIe02GLLKImjF4ZV27Rz9nn7UaVtkQLt9YyxNBYokGKekKl+WVRG0Q==", "requires": { "@flare/js-utils": "^1.0.3", "axios": "^1.3.2" @@ -5418,6 +5448,29 @@ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", "dev": true }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "18.0.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", + "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -5821,6 +5874,12 @@ } } }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, "data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", diff --git a/package.json b/package.json index c1fd8c0..3488fe4 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ ], "devDependencies": { "@types/jest": "^29.4.0", + "@types/react": "^18.0.27", "jest": "^29.4.1", "jest-environment-jsdom": "^29.4.2", "prettier": "^2.8.3", @@ -53,7 +54,7 @@ "typescript": "^4.9.5" }, "dependencies": { - "@flare/tuitio-client": "^1.0.0" + "@flare/tuitio-client": "^1.0.2" }, "peerDependencies": { "react": "^16.14.0" diff --git a/src/contexts.ts b/src/contexts.ts new file mode 100644 index 0000000..6ffe48a --- /dev/null +++ b/src/contexts.ts @@ -0,0 +1,5 @@ +import React from "react"; +import { initialState, initialDispatchActions } from "./initialState"; + +export const TuitioContext = React.createContext(initialState); +export const TuitioDispatchContext = React.createContext(initialDispatchActions); diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..4a1b42b --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,69 @@ +import { useContext } from "react"; +import { TuitioContext, TuitioDispatchContext } from "./contexts"; +import { TuitioClient, invalidate } from "@flare/tuitio-client"; +import type { TuitioAuthenticationResult } from "@flare/tuitio-client"; + +export type TuitioClientHookOptions = { + onLoginSuccess: (result: TuitioAuthenticationResult, userName: string) => void; + onLoginFailed: (result: TuitioAuthenticationResult, userName: string) => void; + onLoginError: (error: any) => void; +}; + +const useTuitioClient = (options: TuitioClientHookOptions) => { + const state = useContext(TuitioContext); + const dispatchActions = useContext(TuitioDispatchContext); + + const { onLoginSuccess, onLoginFailed, onLoginError } = options; + + const login = async (userName: string, password: string) => { + try { + const connector = new TuitioClient(state.configuration.tuitioUrl); + const response = await connector.authenticate(userName, password); + if (response.token) { + dispatchActions.onLoginSuccess(response.token, userName); + onLoginSuccess && onLoginSuccess(response, userName); + } else { + onLoginFailed && onLoginFailed(response, userName); + } + return response; + } catch (error) { + if (onLoginError) onLoginError(error); + else throw error; + } + }; + + const logout = (): void => { + invalidate(); + dispatchActions.onLogout(); + }; + + return { login, logout }; +}; + +const useTuitioUser = () => { + const state = useContext(TuitioContext); + const userName = state.userName; + const lastLoginDate = state.token?.validFrom; + return { userName, lastLoginDate }; +}; + +const useTuitioToken = () => { + const state = useContext(TuitioContext); + const token = state.token; + + const validate = (): boolean => { + const token = state.token; + if (!token) { + return false; + } + + const valid = new Date(token.validUntil) >= new Date(); + return valid; + }; + + const valid: boolean = validate(); + + return { token, validate, valid }; +}; + +export { useTuitioClient, useTuitioUser, useTuitioToken }; diff --git a/src/initialState.ts b/src/initialState.ts new file mode 100644 index 0000000..d078456 --- /dev/null +++ b/src/initialState.ts @@ -0,0 +1,15 @@ +import { fetch } from "@flare/tuitio-client"; +import type { TuitioReactState, TuitioDispatchActions } from "./types"; + +const { userName, token } = fetch(); + +export const initialState: TuitioReactState = { + userName, + token, + configuration: { tuitioUrl: null } +}; + +export const initialDispatchActions: TuitioDispatchActions = { + onLoginSuccess: () => {}, + onLogout: () => {} +}; diff --git a/src/reducer.ts b/src/reducer.ts new file mode 100644 index 0000000..627974e --- /dev/null +++ b/src/reducer.ts @@ -0,0 +1,31 @@ +import { initialState } from "./initialState"; +import type { TuitioReactState, TuitioDispatchActions } from "./types"; +import type { TuitioToken } from "@flare/tuitio-client"; +import { Dispatch } from "react"; + +export const reducer = (state: TuitioReactState = initialState, action: any) => { + switch (action.type) { + case "onLoginSuccess": { + return { + ...state, + token: action.token, + userName: action.userName + }; + } + case "onLogout": { + const { configuration } = state; + return { + ...initialState, + configuration + }; + } + default: { + return state; + } + } +}; + +export const dispatchActions = (dispatch: Dispatch): TuitioDispatchActions => ({ + onLoginSuccess: (token: TuitioToken, userName: string): void => dispatch({ type: "onLoginSuccess", token, userName }), + onLogout: (): void => dispatch({ type: "onLogout" }) +}); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..eda78a6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ +import type { TuitioToken } from "@flare/tuitio-client"; + +export type TuitioConfiguration = { tuitioUrl: string | null }; +export type TuitioReactState = { userName: string; token: TuitioToken; configuration: TuitioConfiguration }; +export type TuitioDispatchActions = { + onLoginSuccess: (token: TuitioToken, userName: string) => void; + onLogout: () => void; +};