diff --git a/drama-queen/.env b/drama-queen/.env index 21693dee..20bd6111 100644 --- a/drama-queen/.env +++ b/drama-queen/.env @@ -1,7 +1,6 @@ VITE_QUEEN_URL=http://localhost:5001 VITE_QUEEN_V2_URL=http://localhost:5002 VITE_QUEEN_API_URL= -VITE_AUTH_TYPE= -VITE_KEYCLOAK_URL= -VITE_KEYCLOAK_CLIENT_ID= -VITE_KEYCLOAK_REALM= +# If no environment variables for OIDC are provided, the application will use a mock OIDC. +VITE_OIDC_ISSUER= +VITE_OIDC_CLIENT_ID= diff --git a/drama-queen/package.json b/drama-queen/package.json index 741489da..43f3e875 100644 --- a/drama-queen/package.json +++ b/drama-queen/package.json @@ -24,6 +24,7 @@ "jwt-decode": "^3.1.2", "keycloak-js": "^21.1.2", "memoizee": "^0.4.15", + "oidc-spa": "^2.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.14.1", @@ -33,7 +34,7 @@ "zod": "^3.21.4" }, "devDependencies": { - "@inseefr/lunatic": "^2.4.10", + "@inseefr/lunatic": "^2.7.4", "@types/node": "^20.3.3", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", diff --git a/drama-queen/src/App.tsx b/drama-queen/src/App.tsx new file mode 100644 index 00000000..e35bf5cf --- /dev/null +++ b/drama-queen/src/App.tsx @@ -0,0 +1,33 @@ +import { createCoreProvider } from "core"; +import { RequiresAuthentication } from "ui/auth"; +import { LoadingData } from "ui/pages/LoadingData"; + + +const { CoreProvider } = createCoreProvider({ + "apiUrl": import.meta.env.VITE_API_URL, + "publicUrl": import.meta.env.BASE_URL, + "oidcParams": { + "issuerUri": import.meta.env.VITE_OIDC_ISSUER, + "clientId": import.meta.env.VITE_OIDC_CLIENT_ID, + }, +}); + +export default function App() { + console.log("public", import.meta.env.BASE_URL) + return ( + /* + + + + + + + + + */ + Loading} > + + + + ) +} \ No newline at end of file diff --git a/drama-queen/src/bootstrap.tsx b/drama-queen/src/bootstrap.tsx index 888a7c4d..59d1016f 100644 --- a/drama-queen/src/bootstrap.tsx +++ b/drama-queen/src/bootstrap.tsx @@ -1,39 +1,8 @@ -import { useState, useEffect } from "react"; -import { createCoreProvider } from "core"; import { createRoot } from "react-dom/client"; -import { RouterProvider } from "react-router-dom"; import { type RoutingStrategy, createRouter } from "ui/routing/createRouter"; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { injectLegacyEntryQueens } from "core/injectLegacyQueens"; -import { createAuthProvider } from "ui/auth"; -import { createQueenApiProvider } from "ui/queenApi"; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' -import { LoadingData } from "ui/pages/LoadingData"; +import App from "App"; -const queryClient = new QueryClient({}); - -const { AuthProvider } = createAuthProvider({ - isMock: import.meta.env.VITE_AUTH_TYPE !== "OIDC", - keycloakUrl: import.meta.env.VITE_KEYCLOAK_URL, - clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID, - realm: import.meta.env.VITE_KEYCLOAK_REALM, - origin: window.location.origin + import.meta.env.BASE_URL -}); - -const { QueenApiProvider } = createQueenApiProvider({ - apiUrl: import.meta.env.VITE_QUEEN_API_URL -}); - -const { CoreProvider } = createCoreProvider({ - "apiUrl": import.meta.env.VITE_API_URL, - "keycloakParams": { - "url": import.meta.env.VITE_KEYCLOAK_URL, - "clientId": import.meta.env.VITE_KEYCLOAK_CLIENT_ID, - "realm": import.meta.env.VITE_KEYCLOAK_REALM, - "origin": window.location.origin + import.meta.env.BASE_URL - }, - "redirectUrl": import.meta.env.VITE_REDIRECT_URL, -}); const mount = ({ mountPoint, @@ -51,19 +20,7 @@ const mount = ({ const router = createRouter({ strategy: routingStrategy, initialPathname }); const root = createRoot(mountPoint); root.render( - /* - - - - - - - - - */ - Loading} > - - + ); return () => queueMicrotask(() => root.unmount()); diff --git a/drama-queen/src/core/adapters/oidc/default.ts b/drama-queen/src/core/adapters/oidc/default.ts new file mode 100644 index 00000000..4d6b7d0c --- /dev/null +++ b/drama-queen/src/core/adapters/oidc/default.ts @@ -0,0 +1,16 @@ +import { Oidc } from "core/ports/Oidc"; +import { createOidc as createOidcSpa } from "oidc-spa"; + +export async function createOidc(params: { + issuerUri: string; + clientId: string; + publicUrl: string; +}): Promise { + const { issuerUri, clientId, publicUrl } = params; + + return createOidcSpa({ + issuerUri, + clientId, + publicUrl, + }); +} diff --git a/drama-queen/src/core/adapters/oidc/mock.ts b/drama-queen/src/core/adapters/oidc/mock.ts new file mode 100644 index 00000000..14429da7 --- /dev/null +++ b/drama-queen/src/core/adapters/oidc/mock.ts @@ -0,0 +1,33 @@ +import { Oidc } from "core/ports/Oidc"; +import { id } from "tsafe/id"; + +export function createOidc(params: { isUserLoggedIn: boolean }): Oidc { + const common: Oidc.Common = { + params: { + issuerUri: "", + clientId: "", + }, + }; + + if (!params.isUserLoggedIn) { + return id({ + ...common, + isUserLoggedIn: false, + login: async (params: { doesCurrentHrefRequiresAuth: boolean }) => { + return new Promise(() => {}); + }, + }); + } + + return id({ + ...common, + isUserLoggedIn: true, + getTokens: () => ({ + accessToken: "", + idToken: "", + refreshToken: "", + refreshTokenExpirationTime: Infinity, + }), + renewTokens: () => Promise.reject("Not implemented"), + }); +} diff --git a/drama-queen/src/core/queenApi/createApiClient.ts b/drama-queen/src/core/adapters/queenApi/default.ts similarity index 87% rename from drama-queen/src/core/queenApi/createApiClient.ts rename to drama-queen/src/core/adapters/queenApi/default.ts index 34c3e93e..54206427 100644 --- a/drama-queen/src/core/queenApi/createApiClient.ts +++ b/drama-queen/src/core/adapters/queenApi/default.ts @@ -3,25 +3,25 @@ import { IdAndQuestionnaireIdSchema, SurveyUnitSchema, type SurveyUnitWithId, -} from "core/model/surveyUnit"; +} from "core/ports/QueenApi/SurveyUnit"; import axios from "axios"; import memoize from "memoizee"; -import type { SurveyUnitData } from "core/model/surveyUnitData"; -import type { QueenApi } from "./QueenApi"; -import { type Campaign, CampaignSchema } from "core/model/campaing"; -import { type APIReturnedListOfSurvey } from "core/model/survey"; +import type { SurveyUnitData } from "core/ports/QueenApi/SurveyUnitData"; +import type { QueenApi } from "core/ports/QueenApi/QueenApi"; +import { type Campaign, CampaignSchema } from "core/ports/QueenApi/Campaing"; +import { type APIReturnedListOfSurvey } from "core/ports/QueenApi/Questionnaire"; import { type Nomenclature, NomenclatureSchema, type RequiredNomenclatures, RequiredNomenclaturesSchema, -} from "core/model/nomenclature"; -import type { Paradata } from "core/model/paradata"; +} from "core/ports/QueenApi/Nomenclature"; +import type { Paradata } from "core/ports/QueenApi/Paradata"; export function createApiClient(params: { apiUrl: string; - getAccessToken: () => string | null; -}) { + getAccessToken: () => string | undefined; +}): QueenApi { const { apiUrl, getAccessToken } = params; const { axiosInstance } = (() => { @@ -121,5 +121,5 @@ export function createApiClient(params: { ), postParadata: (paradata) => axiosInstance.post(`/api/paradata`, paradata), - } satisfies QueenApi; + }; } diff --git a/drama-queen/src/core/queenApi/createMockApiClient.ts b/drama-queen/src/core/adapters/queenApi/mock.ts similarity index 93% rename from drama-queen/src/core/queenApi/createMockApiClient.ts rename to drama-queen/src/core/adapters/queenApi/mock.ts index 7925faff..70f82bc4 100644 --- a/drama-queen/src/core/queenApi/createMockApiClient.ts +++ b/drama-queen/src/core/adapters/queenApi/mock.ts @@ -1,7 +1,7 @@ -import type { QueenApi } from "./QueenApi"; +import type { QueenApi } from "core/ports/QueenApi/QueenApi"; import { surveySample } from "./mockData/surveySample"; -export function createMockApiClient() { +export function createApiClient(): QueenApi { return { getSurveyUnitsIdsAndQuestionnaireIdsByCampaign: (_idCampaign) => Promise.resolve([{ id: "id", questionnaireId: "questionnaireId" }]), @@ -25,7 +25,7 @@ export function createMockApiClient() { getNomenclature: (idNomenclature) => Promise.resolve([{ id: `${idNomenclature}`, label: "label" }]), postParadata: (paradata) => console.log("postParadata", paradata), - } satisfies QueenApi; + }; } function createSUMocked(props: { idSu?: string; idCampaign?: string }) { diff --git a/drama-queen/src/core/queenApi/mockData/surveySample.ts b/drama-queen/src/core/adapters/queenApi/mockData/surveySample.ts similarity index 97% rename from drama-queen/src/core/queenApi/mockData/surveySample.ts rename to drama-queen/src/core/adapters/queenApi/mockData/surveySample.ts index 32d3dc64..0daf7651 100644 --- a/drama-queen/src/core/queenApi/mockData/surveySample.ts +++ b/drama-queen/src/core/adapters/queenApi/mockData/surveySample.ts @@ -1,4 +1,4 @@ -import type { Questionnaire } from "core/model/survey"; +import type { Questionnaire } from "core/ports/QueenApi/Questionnaire"; export const surveySample = { id: "lk9s32o5", diff --git a/drama-queen/src/core/checkLunaticVersion/isQueenV2Survey.ts b/drama-queen/src/core/checkLunaticVersion/isQueenV2Survey.ts index aff3507e..b6e23483 100644 --- a/drama-queen/src/core/checkLunaticVersion/isQueenV2Survey.ts +++ b/drama-queen/src/core/checkLunaticVersion/isQueenV2Survey.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { Questionnaire } from "core/model/survey"; +import { Questionnaire } from "core/ports/QueenApi/Questionnaire"; const lunaticModelVersionBreaking = "2.2.10"; diff --git a/drama-queen/src/core/indexedDb/tables/paradata.ts b/drama-queen/src/core/indexedDb/tables/paradata.ts index 6649d397..e2550fe3 100644 --- a/drama-queen/src/core/indexedDb/tables/paradata.ts +++ b/drama-queen/src/core/indexedDb/tables/paradata.ts @@ -1,4 +1,4 @@ -import type { Paradata } from "core/model/paradata"; +import type { Paradata } from "core/ports/QueenApi/Paradata"; import type { Table } from "dexie"; export type ParadataTable = { diff --git a/drama-queen/src/core/indexedDb/tables/surveyUnit.ts b/drama-queen/src/core/indexedDb/tables/surveyUnit.ts index 8603132b..fd3e79ed 100644 --- a/drama-queen/src/core/indexedDb/tables/surveyUnit.ts +++ b/drama-queen/src/core/indexedDb/tables/surveyUnit.ts @@ -1,4 +1,4 @@ -import type { SurveyUnitWithId } from "core/model/surveyUnit"; +import type { SurveyUnitWithId } from "core/ports/QueenApi/SurveyUnit"; import type { Table } from "dexie"; export type SurveyUnitTable = { diff --git a/drama-queen/src/core/keycloakClient/Oidc.ts b/drama-queen/src/core/keycloakClient/Oidc.ts deleted file mode 100644 index a7e9dc11..00000000 --- a/drama-queen/src/core/keycloakClient/Oidc.ts +++ /dev/null @@ -1,22 +0,0 @@ -export declare type Oidc = Oidc.LoggedIn | Oidc.NotLoggedIn; - -export declare namespace Oidc { - export type NotLoggedIn = { - isUserLoggedIn: false; - login: () => Promise; - }; - - export type LoggedIn = { - isUserLoggedIn: true; - renewToken(): Promise; - getAccessToken: () => string; - }; -} - -export type KeycloakParams = { - url: string; - realm: string; - origin?: string; - clientId: string; - log?: typeof console.log; -}; diff --git a/drama-queen/src/core/keycloakClient/createKeycloakClient.ts b/drama-queen/src/core/keycloakClient/createKeycloakClient.ts deleted file mode 100644 index 13313eca..00000000 --- a/drama-queen/src/core/keycloakClient/createKeycloakClient.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { KeycloakParams, Oidc } from "./Oidc"; -import Keycloak from "keycloak-js"; -import { assert } from "tsafe/assert"; -import { decodeJwt } from "core/tools/jwt"; -import { listenActivity } from "core/tools/listenActivity"; - -export async function createKeycloakClient(params: KeycloakParams) { - const { - url, - realm, - clientId, - origin, - log, - } = params; - - const keycloakInstance = new Keycloak({ url, realm, clientId }); - - const isAuthenticated = await keycloakInstance.init({ - onLoad: "check-sso", - silentCheckSsoRedirectUri: `${origin}/silent-sso.html`, - checkLoginIframe: false, - }); - - const login = async () => { - await keycloakInstance.login({ redirectUri: window.location.href }); - return new Promise(() => {}); - }; - - if (!isAuthenticated) { - return { - isUserLoggedIn: false, - login, - } satisfies Oidc; - } - - assert(keycloakInstance.token !== undefined); - - let currentAccessToken = keycloakInstance.token; - - const oidc = { - isUserLoggedIn: true, - getAccessToken: () => currentAccessToken, - renewToken: async () => { - await keycloakInstance.updateToken(-1); - - assert(keycloakInstance.token !== undefined); - - currentAccessToken = keycloakInstance.token; - }, - } satisfies Oidc; - - (function callee() { - const msBeforeExpiration = - getAccessTokenExpirationTime(currentAccessToken) - Date.now(); - - setTimeout(async () => { - log?.( - `OIDC access token will expire in ${minValiditySecond} seconds, waiting for user activity before renewing` - ); - - await listenActivity(); - - log?.("User activity detected. Refreshing access token now"); - - const error = await keycloakInstance.updateToken(-1).then( - () => undefined, - (error: Error) => error - ); - - if (error) { - log?.("Can't refresh OIDC access token, getting a new one"); - //NOTE: Never resolves - await login(); - } - - assert(keycloakInstance.token !== undefined); - - currentAccessToken = keycloakInstance.token; - - callee(); - }, msBeforeExpiration - minValiditySecond * 1000); - })(); - - return oidc; -} - -const minValiditySecond = 25; - -function getAccessTokenExpirationTime(accessToken: string): number { - return decodeJwt<{ exp: number }>(accessToken)["exp"] * 1000; -} diff --git a/drama-queen/src/core/keycloakClient/dummyOidcClient.ts b/drama-queen/src/core/keycloakClient/dummyOidcClient.ts deleted file mode 100644 index d313aa48..00000000 --- a/drama-queen/src/core/keycloakClient/dummyOidcClient.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Oidc } from "./Oidc"; - -export const dummyOidcClient: Oidc.LoggedIn = { - isUserLoggedIn: true, - getAccessToken: () => - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", - renewToken: () => Promise.reject("Not implemented"), -}; diff --git a/drama-queen/src/core/model/type-source.ts b/drama-queen/src/core/model/type-source.ts deleted file mode 100644 index 7ae51ec8..00000000 --- a/drama-queen/src/core/model/type-source.ts +++ /dev/null @@ -1,347 +0,0 @@ -//TODO DELETE This file and use type from @inseefr/lunatic -export type LabelType = { value: string; type: "VTL" | "VTL|MD" }; - -export type ComponentTypeEnum = - | "Sequence" - | "Subsequence" - | "RosterForLoop" - | "Loop" - | "Table" - | "Input" - | "InputNumber" - | "Datepicker" - | "CheckboxGroup" - | "CheckboxOne" - | "CheckboxBoolean" - | "Radio" - | "Dropdown" - | "Textarea" - | "FilterDescription" - | "PairwiseLinks" - | "Suggester" - | "ComponentSet"; - -export type ValuesType = { - PREVIOUS: T | null; - COLLECTED: T | null; - FORCED: T | null; - EDITED: T | null; - INPUTED: T | null; -}; - -export type ValuesTypeArray = { - PREVIOUS: T[] | [null]; - COLLECTED: T[] | [null]; - FORCED: T[] | [null]; - EDITED: T[] | [null]; - INPUTED: T[] | [null]; -}; - -export type DeclarationType = { - id: string; - declarationType: - | "INSTRUCTION" - | "COMMENT" - | "HELP" - | "CODECARD" - | "WARNING" - | "STATEMENT"; - position: - | "AFTER_QUESTION_TEXT" - | "AFTER_RESPONSE" - | "BEFORE_QUESTION_TEXT" - | "DETACHABLE"; - label: LabelType; -}; - -export type ConditionFilterType = LabelType & { - bindingDependencies?: string[]; -}; - -export enum Criticality { - INFO = "INFO", - WARN = "WARN", - ERROR = "ERROR", -} - -export enum TypeOfControl { - FORMAT = "FORMAT", - CONSISTENCY = "CONSISTENCY", -} - -export enum ControlTypeEnum { - roundabout = "roundabout", - simple = "simple", -} - -export type ControlType = { - id: string; - criticality: Criticality; - typeOfControl: TypeOfControl; - control: LabelType; - errorMessage: LabelType; - bindingDependencies: string[]; - type: ControlTypeEnum; - iterations?: number; -}; - -export type ResponseType = { name: string }; - -export type SequenceDescription = { - label: LabelType; - id: string; - page: string; -}; - -export type Hierarchy = { - sequence: SequenceDescription; - subSequence?: SequenceDescription; -}; - -export type ComponentTypeBase = { - label: LabelType; - declarations?: DeclarationType[]; - conditionFilter: ConditionFilterType; - controls?: ControlType[]; - id: string; - bindingDependencies?: string[]; - hierarchy: Hierarchy; - mandatory?: boolean; - page: string; -}; -export type ComponentType = - | (ComponentTypeBase & ComponentSequenceType) - | (ComponentTypeBase & ComponentSubSequenceType) - | (ComponentTypeBase & ComponentRosterForLoopType) - | (ComponentTypeBase & ComponentLoopType) - | (ComponentTypeBase & ComponentTableType) - | (ComponentTypeBase & ComponentNumberType) - | (ComponentTypeBase & ComponentDatePickerType) - | (ComponentTypeBase & ComponentCheckboxGroupType) - | (ComponentTypeBase & ComponentCheckboxBooleanType) - | (ComponentTypeBase & ComponentRadioType) - | (ComponentTypeBase & ComponentFilterDescriptionType) - | (ComponentTypeBase & ComponentDropdownType) - | (ComponentTypeBase & ComponentPairWiseLinksType) - | (ComponentTypeBase & ComponentRoundaboutType) - | (ComponentTypeBase & ComponentSuggesterType) - | (ComponentTypeBase & ComponentInputOrTextareaType) - | (ComponentTypeBase & { - componentType: "CheckboxOne"; - }) - | (ComponentTypeBase & ComponentComponentSetType); - -export type ComponentInputOrTextareaType = { - componentType: "Input" | "Textarea"; - maxLength: number; - missingResponse?: ResponseType; - response: ResponseType; -}; -export type ComponentSequenceType = { - componentType: "Sequence"; -}; - -export type ComponentSubSequenceType = { - componentType: "Subsequence"; - gotoPage: string; -}; - -export type ComponentRoundaboutType = { - componentType: "Roundabout"; - components: ComponentType[]; - iterations: LabelType; - locked: boolean; - expressions: Record; -}; - -export type ComponentRosterForLoopType = { - componentType: "RosterForLoop"; - components: ComponentType[]; - lines: { min: LabelType; max: LabelType }; - header: { - value: string; - label: LabelType | string; - options: { value: string; label: LabelType }[]; - colspan?: number; - rowspan?: number; - }[]; - body: { - label?: LabelType; - value?: string; - format?: string; - dateFormat?: string; - unit?: string; - options: { value: string; label: LabelType }[]; - response: ResponseType; - bindingDependencies: string[]; - componentType?: ComponentTypeEnum; - maxLength?: number; - min?: number; - max?: number; - decimals?: number; - colspan?: number; - rowspan?: number; - id?: string; - }[]; - positioning: "HORIZONTAL"; -}; - -export type ComponentLoopType = { - componentType: "Loop"; - loopDependencies: string[]; - lines: { min: LabelType; max: LabelType }; - components: ComponentType[]; - iterations: LabelType; - maxPage: string; - depth: number; - paginatedLoop: boolean; -}; - -export type ComponentTableType = { - componentType: "Table"; - lines: ComponentRosterForLoopType["lines"]; - header: ComponentRosterForLoopType["header"]; - body: ComponentRosterForLoopType["body"]; - positioning: ComponentRosterForLoopType["positioning"]; -}; - -export type ComponentNumberType = { - componentType: "InputNumber"; - unit: string; - response: ResponseType; - min?: number; - max?: number; - decimals?: number; -}; - -export type ComponentDatePickerType = { - componentType: "Datepicker"; - dateFormat: string; - response: ResponseType; - min?: string; - max?: string; -}; - -export type ComponentCheckboxGroupType = { - componentType: "CheckboxGroup"; - responses: Array<{ - label: LabelType; - response: ResponseType; - id: string; - }>; -}; - -export type ComponentCheckboxBooleanType = { - componentType: "CheckboxBoolean"; - response: ResponseType; - missingResponse?: ResponseType; -}; - -export type ComponentRadioType = { - componentType: "Radio"; - options: { value: string; label: LabelType }[]; - response: ResponseType; - missingResponse?: ResponseType; -}; - -export type ComponentDropdownType = { - componentType: "Dropdown"; - options: { value: string; label: LabelType }[]; - response: ResponseType; - missingResponse?: ResponseType; -}; - -export type ComponentFilterDescriptionType = { - componentType: "FilterDescription"; - filterDescription: boolean; -}; - -export type ComponentPairWiseLinksType = { - componentType: "PairwiseLinks"; - xAxisIterations: LabelType; - yAxisIterations: LabelType; - symLinks: { - [variableName: string]: Record; - }; -}; - -export type ComponentComponentSetType = { - componentType: "ComponentSet"; - components: ComponentType[]; -}; - -export type ComponentSuggesterType = { - componentType: "Suggester"; - storeName: string; -}; - -export type SuggesterType = { - name: string; - fields: { - name: string; - min?: number; - rules?: string[]; - language?: string; - stemmer?: boolean; - synonyms: { source: string; target: string[] }[]; - }[]; - max: number; - stopWords?: string; - order?: { field: string; type: string }; - queryParser: { - type: string; - params: { language: string; pattern: string; min?: number }; - }; - url?: string; - version: number; - meloto?: boolean; -}; - -export type Variable = - | { - variableType: "EXTERNAL"; - name: string; - value: unknown; - } - | { - variableType: "COLLECTED"; - name: string; - values: ValuesType | ValuesTypeArray; - } - | { - variableType: "CALCULATED"; - name: string; - expression: LabelType; - bindingDependencies: string[]; - inFilter: string; - shapeFrom?: string; - }; - -export type LunaticSource = { - id: string; - modele?: string; - enoCoreVersion?: string; - lunaticModelVersion?: string; - generatingDate?: string; - missing?: boolean; - pagination?: "question" | "sequence" | "subsequence"; - maxPage: string; - label: LabelType; - components: ComponentType[]; - variables: Variable[]; - suggesters?: SuggesterType[]; - cleaning: { - [variableName: string]: { - [variableName: string]: string; - }; - }; - missingBlock: { - [variableName: string]: string[]; - }; - resizing: { - [variableName: string]: { - size: string; // VTL Expression - variables: string[]; - }; - }; -}; diff --git a/drama-queen/src/core/ports/Oidc.ts b/drama-queen/src/core/ports/Oidc.ts new file mode 100644 index 00000000..c6cbe4b8 --- /dev/null +++ b/drama-queen/src/core/ports/Oidc.ts @@ -0,0 +1,28 @@ +export declare type Oidc = Oidc.LoggedIn | Oidc.NotLoggedIn; + +export declare namespace Oidc { + export type Common = { + params: { + issuerUri: string; + clientId: string; + }; + }; + + export type NotLoggedIn = Common & { + isUserLoggedIn: false; + login: (params: { doesCurrentHrefRequiresAuth: boolean }) => Promise; + }; + + export type LoggedIn = Common & { + isUserLoggedIn: true; + renewTokens(): Promise; + getTokens: () => Tokens; + }; + + export type Tokens = { + accessToken: string; + idToken: string; + refreshToken: string; + refreshTokenExpirationTime: number; + }; +} diff --git a/drama-queen/src/core/model/campaing.ts b/drama-queen/src/core/ports/QueenApi/Campaing.ts similarity index 100% rename from drama-queen/src/core/model/campaing.ts rename to drama-queen/src/core/ports/QueenApi/Campaing.ts diff --git a/drama-queen/src/core/model/nomenclature.ts b/drama-queen/src/core/ports/QueenApi/Nomenclature.ts similarity index 100% rename from drama-queen/src/core/model/nomenclature.ts rename to drama-queen/src/core/ports/QueenApi/Nomenclature.ts diff --git a/drama-queen/src/core/model/paradata.ts b/drama-queen/src/core/ports/QueenApi/Paradata.ts similarity index 100% rename from drama-queen/src/core/model/paradata.ts rename to drama-queen/src/core/ports/QueenApi/Paradata.ts diff --git a/drama-queen/src/core/queenApi/QueenApi.ts b/drama-queen/src/core/ports/QueenApi/QueenApi.ts similarity index 79% rename from drama-queen/src/core/queenApi/QueenApi.ts rename to drama-queen/src/core/ports/QueenApi/QueenApi.ts index c2937b26..8ddcbe77 100644 --- a/drama-queen/src/core/queenApi/QueenApi.ts +++ b/drama-queen/src/core/ports/QueenApi/QueenApi.ts @@ -1,14 +1,14 @@ -import type { Campaign } from "core/model/campaing"; +import type { Campaign } from "core/ports/QueenApi/Campaing"; import type { Nomenclature, RequiredNomenclatures, -} from "core/model/nomenclature"; -import type { Paradata } from "core/model/paradata"; -import type { Questionnaire } from "core/model/survey"; +} from "core/ports/QueenApi/Nomenclature"; +import type { Paradata } from "core/ports/QueenApi/Paradata"; +import type { Questionnaire } from "core/ports/QueenApi/Questionnaire"; import type { IdAndQuestionnaireId, SurveyUnitWithId, -} from "core/model/surveyUnit"; +} from "core/ports/QueenApi/SurveyUnit"; export type QueenApi = { getSurveyUnitsIdsAndQuestionnaireIdsByCampaign: ( diff --git a/drama-queen/src/core/model/survey.ts b/drama-queen/src/core/ports/QueenApi/Questionnaire.ts similarity index 65% rename from drama-queen/src/core/model/survey.ts rename to drama-queen/src/core/ports/QueenApi/Questionnaire.ts index 08d1dbe9..2d73f04d 100644 --- a/drama-queen/src/core/model/survey.ts +++ b/drama-queen/src/core/ports/QueenApi/Questionnaire.ts @@ -1,6 +1,6 @@ -//import type { LunaticSource } from "@inseefr/lunatic/lib/src/use-lunatic/type-source"; -import { LunaticSource } from "./type-source"; +import { LunaticSource } from "@inseefr/lunatic"; + /** * We dont provide zod schema for this type because Survey are very large @@ -10,6 +10,6 @@ export type Questionnaire = LunaticSource; /** * Utility type because API does not return Survey directly - * Cause by /api/campaign/ + * Caused by /api/campaign/ */ export type APIReturnedListOfSurvey = { value: LunaticSource }; diff --git a/drama-queen/src/core/model/surveyUnit.ts b/drama-queen/src/core/ports/QueenApi/SurveyUnit.ts similarity index 93% rename from drama-queen/src/core/model/surveyUnit.ts rename to drama-queen/src/core/ports/QueenApi/SurveyUnit.ts index 1dff7592..0e41c60f 100644 --- a/drama-queen/src/core/model/surveyUnit.ts +++ b/drama-queen/src/core/ports/QueenApi/SurveyUnit.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { SurveyUnitDataSchema } from "./surveyUnitData"; +import { SurveyUnitDataSchema } from "./SurveyUnitData"; export const IdAndQuestionnaireIdSchema = z.object({ id: z.string(), diff --git a/drama-queen/src/core/model/surveyUnitData.ts b/drama-queen/src/core/ports/QueenApi/SurveyUnitData.ts similarity index 100% rename from drama-queen/src/core/model/surveyUnitData.ts rename to drama-queen/src/core/ports/QueenApi/SurveyUnitData.ts diff --git a/drama-queen/src/core/setup.ts b/drama-queen/src/core/setup.ts index 8b61bbbf..55d79a3c 100644 --- a/drama-queen/src/core/setup.ts +++ b/drama-queen/src/core/setup.ts @@ -1,44 +1,68 @@ - import { createCoreFromUsecases } from "redux-clean-architecture"; import type { GenericCreateEvt, GenericThunks } from "redux-clean-architecture"; -import { createApiClient } from "./queenApi/createApiClient"; -import { createKeycloakClient } from "./keycloakClient/createKeycloakClient"; import { usecases } from "./usecases"; type CoreParams = { - apiUrl: string; - keycloakParams: { - url: string; + apiUrl: string; + publicUrl: string; + oidcParams: + | { + issuerUri: string; clientId: string; - realm: string; - origin?: string; - }; - redirectUrl: string; + } + | undefined; }; export async function createCore(params: CoreParams) { + const { apiUrl, publicUrl, oidcParams } = params; - const { apiUrl, keycloakParams } = params; + const oidc = await (async () => { + if (oidcParams === undefined) { + const { createOidc } = await import("core/adapters/oidc/mock"); + return createOidc({ isUserLoggedIn: false }); + } + const { createOidc } = await import("core/adapters/oidc/default"); + return createOidc({ + issuerUri: oidcParams.issuerUri, + clientId: oidcParams.clientId, + publicUrl: publicUrl, + }); + })(); - const oidc = await createKeycloakClient(keycloakParams); + const queenApi = await (async () => { + if (apiUrl === "") { + // When no apiUrl is provided, we use the mock + const { createApiClient } = await import("core/adapters/queenApi/mock"); + return createApiClient(); + } - const queenApi = createApiClient({ - apiUrl, - "getAccessToken": !oidc.isUserLoggedIn ? - (() => null) : - (() => oidc.getAccessToken()) - }); + const { createApiClient } = await import("core/adapters/queenApi/default"); - const core = createCoreFromUsecases({ - "thunksExtraArgument": { - "coreParams": params, - oidc, - queenApi - }, - usecases + return createApiClient({ + apiUrl, + getAccessToken: () => { + if (oidc === undefined) { + return undefined; + } + + if (!oidc.isUserLoggedIn) { + return undefined; + } + return oidc.getTokens().accessToken; + }, }); + })(); + + const core = createCoreFromUsecases({ + thunksExtraArgument: { + coreParams: params, + oidc, + queenApi, + }, + usecases, + }); - return core; + return core; } type Core = Awaited>; @@ -47,4 +71,4 @@ export type State = ReturnType; export type Thunks = GenericThunks; -export type CreateEvt = GenericCreateEvt; \ No newline at end of file +export type CreateEvt = GenericCreateEvt; diff --git a/drama-queen/src/core/usecases/index.ts b/drama-queen/src/core/usecases/index.ts index 11f1ed30..c92478a9 100644 --- a/drama-queen/src/core/usecases/index.ts +++ b/drama-queen/src/core/usecases/index.ts @@ -1,4 +1,3 @@ - import * as loadingData from "./loadingData"; - -export const usecases = { loadingData }; \ No newline at end of file +import * as userAuthentication from "./userAuthentication"; +export const usecases = { loadingData, userAuthentication }; diff --git a/drama-queen/src/core/usecases/loadingData.ts b/drama-queen/src/core/usecases/loadingData.ts index 9ca8ff0a..9d9622a0 100644 --- a/drama-queen/src/core/usecases/loadingData.ts +++ b/drama-queen/src/core/usecases/loadingData.ts @@ -4,175 +4,154 @@ import type { PayloadAction } from "@reduxjs/toolkit"; import type { State as RootState } from "../setup"; import { id } from "tsafe/id"; import { createSelector } from "@reduxjs/toolkit"; -import { Evt } from "evt"; +import { Evt } from "evt"; export type State = State.NotRunning | State.Running; export namespace State { - export type NotRunning = { - stateDescription: "not running"; - }; - export type Running = { - stateDescription: "running"; - surveyUnitProgress: number; - nomenclatureProgress: number; - surveyProgress: number; - }; - + export type NotRunning = { + stateDescription: "not running"; + }; + export type Running = { + stateDescription: "running"; + surveyUnitProgress: number; + nomenclatureProgress: number; + surveyProgress: number; + }; } export const name = "loadingData"; export const { reducer, actions } = createSlice({ - name, - "initialState": id({ - "stateDescription": "not running" - }), - "reducers": { - "progressUpdated": (state, { payload }: PayloadAction<{ - surveyUnitProgress: number; - nomenclatureProgress: number; - surveyProgress: number; - }>) => { - const { - nomenclatureProgress, - surveyProgress, - surveyUnitProgress - } = payload; - - return { - "stateDescription": "running", - nomenclatureProgress, - surveyProgress, - surveyUnitProgress - }; - }, - "completed": (state, { payload }: PayloadAction<{ - redirectUrl: string; - }>) => { - return { "stateDescription": "not running" } - }, - } + name, + initialState: id({ + stateDescription: "not running", + }), + reducers: { + progressUpdated: ( + state, + { + payload, + }: PayloadAction<{ + surveyUnitProgress: number; + nomenclatureProgress: number; + surveyProgress: number; + }> + ) => { + const { nomenclatureProgress, surveyProgress, surveyUnitProgress } = + payload; + + return { + stateDescription: "running", + nomenclatureProgress, + surveyProgress, + surveyUnitProgress, + }; + }, + completed: (state) => { + return { stateDescription: "not running" }; + }, + }, }); export const thunks = { - "start": - () => - async (...args) => { - const [dispatch, getState, { coreParams }] = args; - - { - const state = getState()[name]; - - if (state.stateDescription === "running") { - return; - } - - } - - dispatch( - actions.progressUpdated({ - "nomenclatureProgress": 0, - "surveyProgress": 0, - "surveyUnitProgress": 0 - }) - ); - - for (const progress of [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - - dispatch( - actions.progressUpdated({ - "nomenclatureProgress": progress, - "surveyProgress": progress, - "surveyUnitProgress": progress - }) - ); - - } - - dispatch( - actions.completed({ - "redirectUrl": coreParams.redirectUrl - }) - ); - - } -} satisfies Thunks; + start: + () => + async (...args) => { + const [dispatch, getState] = args; -export const selectors = (() => { - const runningState = (rootState: RootState) => { - const state = rootState[name]; + { + const state = getState()[name]; - if (state.stateDescription === "not running") { - return undefined; - } - return state; - - }; - - const isRunning = createSelector( - runningState, - state => state !== undefined - ); - - const surveyUnitProgress = createSelector( - runningState, - state => { - if (state === undefined) { - return undefined; - } - return state.surveyUnitProgress; + if (state.stateDescription === "running") { + return; } - ); - - const nomenclatureProgress = createSelector( - runningState, - state => { - if (state === undefined) { - return undefined; - } - return state.nomenclatureProgress; - } - ); - - const surveyProgress = createSelector( - runningState, - state => { - if (state === undefined) { - return undefined; - } - return state.surveyProgress; - } - ); - - return { - isRunning, - surveyUnitProgress, - nomenclatureProgress, - surveyProgress - }; - -})(); + } + + dispatch( + actions.progressUpdated({ + nomenclatureProgress: 0, + surveyProgress: 0, + surveyUnitProgress: 0, + }) + ); + + for (const progress of [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + + dispatch( + actions.progressUpdated({ + nomenclatureProgress: progress, + surveyProgress: progress, + surveyUnitProgress: progress, + }) + ); + } + + dispatch(actions.completed()); + }, +} satisfies Thunks; +export const selectors = (() => { + const runningState = (rootState: RootState) => { + const state = rootState[name]; -export const createEvt = (({ evtAction }) =>{ + if (state.stateDescription === "not running") { + return undefined; + } + return state; + }; - const evt = Evt.create<{ - action: "redirect"; - url: string; - }>(); + const isRunning = createSelector( + runningState, + (state) => state !== undefined + ); - evtAction - .pipe(data => data.sliceName === name && data.actionName === "completed" ? [data.payload.redirectUrl] : null) - .attach((redirectUrl) => { + const surveyUnitProgress = createSelector(runningState, (state) => { + if (state === undefined) { + return undefined; + } + return state.surveyUnitProgress; + }); - evt.post({ - "action": "redirect", - "url": redirectUrl - }); - }); + const nomenclatureProgress = createSelector(runningState, (state) => { + if (state === undefined) { + return undefined; + } + return state.nomenclatureProgress; + }); - return evt; + const surveyProgress = createSelector(runningState, (state) => { + if (state === undefined) { + return undefined; + } + return state.surveyProgress; + }); + + return { + isRunning, + surveyUnitProgress, + nomenclatureProgress, + surveyProgress, + }; +})(); -}) satisfies CreateEvt; \ No newline at end of file +export const createEvt = (({ evtAction }) => { + const evt = Evt.create<{ + action: "redirect"; + }>(); + + evtAction + .pipe((action) => + action.sliceName === name && action.actionName === "completed" + ? [action] + : null + ) + .attach(() => { + evt.post({ + action: "redirect", + }); + }); + + return evt; +}) satisfies CreateEvt; diff --git a/drama-queen/src/core/usecases/userAuthentication.ts b/drama-queen/src/core/usecases/userAuthentication.ts new file mode 100644 index 00000000..76461aa9 --- /dev/null +++ b/drama-queen/src/core/usecases/userAuthentication.ts @@ -0,0 +1,24 @@ +import { assert } from "tsafe/assert"; +import { Thunks } from "core/setup"; + +export const name = "userAuthentication"; + +export const reducer = null; + +export const thunks = { + getIsUserLoggedIn: + () => + (...args): boolean => { + const [, , { oidc }] = args; + return oidc.isUserLoggedIn; + }, + login: + () => + (...args): Promise => { + const [, , { oidc }] = args; + + assert(!oidc.isUserLoggedIn); + + return oidc.login({ doesCurrentHrefRequiresAuth: true }); + }, +} satisfies Thunks; diff --git a/drama-queen/src/ui/auth.tsx b/drama-queen/src/ui/auth.tsx index e2826e9a..cdac10a1 100644 --- a/drama-queen/src/ui/auth.tsx +++ b/drama-queen/src/ui/auth.tsx @@ -1,72 +1,17 @@ -import { ReactNode, createContext, useContext, useEffect, useState } from "react"; -import { Oidc } from "core/keycloakClient/Oidc"; -import { createKeycloakClient } from "core/keycloakClient/createKeycloakClient"; -import { dummyOidcClient } from "core/keycloakClient/dummyOidcClient"; -import { useQuery } from "@tanstack/react-query" +import { ReactNode } from "react"; +import { useCoreFunctions, useCoreState } from "core"; -const context = createContext(undefined); - -export function useOidc() { - const value = useContext(context); - if (value === undefined) throw new Error("You must wrap your component inside AuthProvider"); - return value; -} - -export function useLoggedInOidc(): Oidc.LoggedIn { - const oidc = useOidc(); - if (!oidc.isUserLoggedIn) throw new Error("You wrap your component inside RequiresAuthentication to use this hook") - return oidc; - -} export function RequiresAuthentication(props: { children: ReactNode }) { const { children } = props - const oidc = useOidc(); + const { userAuthentication } = useCoreFunctions(); - if (!oidc.isUserLoggedIn) { - oidc.login(); + if (!userAuthentication.getIsUserLoggedIn()) { + userAuthentication.login(); return null; } return <>{children} -} - - -export function createAuthProvider(params: { - isMock: true; -} | { - isMock: false; - keycloakUrl: string; - clientId: string; - realm: string; - origin: string; -} -) { - - const prOidc = params.isMock ? - Promise.resolve(dummyOidcClient) : - createKeycloakClient({ - url: params.keycloakUrl, - clientId: params.clientId, - realm: params.realm, - origin: params.origin - }); - - function AuthProvider(props: { fallback?: ReactNode; children: ReactNode; }) { - const { fallback = null, children } = props; - - const { data: oidc, isLoading } = useQuery({ - queryKey: ["keycloak-client"], - queryFn: () => prOidc - }); - - if (isLoading) return fallback; - return {children} - - } - - return { AuthProvider }; - } \ No newline at end of file diff --git a/drama-queen/src/ui/pages/LoadingData.tsx b/drama-queen/src/ui/pages/LoadingData.tsx index 93e2a0fc..22c51137 100644 --- a/drama-queen/src/ui/pages/LoadingData.tsx +++ b/drama-queen/src/ui/pages/LoadingData.tsx @@ -1,21 +1,11 @@ -import { useEffect, useReducer } from "react"; -import * as loadingData from "core/usecases/loadingData"; +import { useEffect } from "react"; import CircularProgress from "@mui/material/CircularProgress" import LinearProgress from '@mui/material/LinearProgress'; -import { selectors, useCoreState, useCoreFunctions, useCoreEvts } from "core"; -import { assert } from "tsafe/assert" +import { useCoreState, useCoreFunctions, useCoreEvts } from "core"; import { useEvt } from "evt/hooks" export function LoadingData() { - - /* - const { isRunning } = useCoreState(selectors.loadingData.isRunning); - const { nomenclatureProgress } = useCoreState(selectors.loadingData.nomenclatureProgress); - const { surveyProgress } = useCoreState(selectors.loadingData.surveyProgress); - const { surveyUnitProgress } = useCoreState(selectors.loadingData.surveyUnitProgress); - */ - - const loadingDataState = useCoreState(state=> state.loadingData); + const loadingDataState = useCoreState(state => state.loadingData); const { loadingData } = useCoreFunctions(); @@ -29,31 +19,19 @@ export function LoadingData() { const { evtLoadingData } = useCoreEvts(); useEvt( - ctx=> { - + ctx => { evtLoadingData.$attach( - data => data.action === "redirect" ? [data.url] : null, + data => data.action === "redirect" ? [data] : null, ctx, - url => { - alert("redirect to " + url) + () => { + alert("redirect to " + window.location.href) } ); - }, [] ); - /* - if (!isRunning) { - return null; - } - - assert(nomenclatureProgress !== undefined); - assert(surveyProgress !== undefined); - assert(surveyUnitProgress !== undefined); - */ - - if( loadingDataState.stateDescription !== "running"){ + if (loadingDataState.stateDescription !== "running") { return null; } diff --git a/drama-queen/src/ui/pages/synchronize/SynchronizePage.tsx b/drama-queen/src/ui/pages/synchronize/SynchronizePage.tsx index cddd992b..8a241b19 100644 --- a/drama-queen/src/ui/pages/synchronize/SynchronizePage.tsx +++ b/drama-queen/src/ui/pages/synchronize/SynchronizePage.tsx @@ -6,7 +6,7 @@ import preloader from 'ui/assets/preloader.svg'; import { tss } from "tss-react/mui"; import { Fragment, useEffect, useState } from "react"; import { type PullData, usePullData } from 'hooks/usePullData'; -import type { SurveyUnitWithId } from "core/model/surveyUnit"; +import type { SurveyUnitWithId } from "core/ports/QueenApi/SurveyUnit"; import { SyncError } from "hooks/queries/SyncError"; import { storeSyncProgress } from "./storeSyncProgress"; import { db } from 'core/indexedDb'; diff --git a/drama-queen/src/ui/queenApi.tsx b/drama-queen/src/ui/queenApi.tsx index dfb1b6a3..def760dd 100644 --- a/drama-queen/src/ui/queenApi.tsx +++ b/drama-queen/src/ui/queenApi.tsx @@ -1,10 +1,6 @@ -import { Oidc } from "core/keycloakClient/Oidc"; -import { QueenApi } from "core/queenApi/QueenApi"; -import { createApiClient } from "core/queenApi/createApiClient"; -import { createMockApiClient } from "core/queenApi/createMockApiClient"; import { ReactNode, createContext, useContext, useMemo, useRef } from "react"; -import { useOidc } from "ui/auth"; import { useGuaranteedMemo } from "hooks/tools/useGuaranteedMemo"; +import { QueenApi } from "core/ports/QueenApi/QueenApi"; const context = createContext(undefined); @@ -28,17 +24,7 @@ export function createQueenApiProvider( }) { const { children } = props; - const oidc = useOidc() - - const apiClient = useGuaranteedMemo(() => - apiUrl - ? createApiClient({ - apiUrl: apiUrl, - getAccessToken: () => oidc.isUserLoggedIn ? oidc.getAccessToken() : null - }) - : createMockApiClient(), - [] - ); + const apiClient = undefined; return {children}; } diff --git a/drama-queen/src/vite-env.d.ts b/drama-queen/src/vite-env.d.ts index cb067d8f..cbd85044 100644 --- a/drama-queen/src/vite-env.d.ts +++ b/drama-queen/src/vite-env.d.ts @@ -1,16 +1,13 @@ /// /// -import { KeycloakParams } from "core/keycloakClient/Oidc"; - +import { Oidc } from "core/ports/Oidc"; interface ImportMetaEnv { readonly VITE_QUEEN_URL: string; readonly VITE_QUEEN_V2_URL: string; - readonly VITE_AUTH_TYPE?: "OIDC"; readonly VITE_QUEEN_API_URL: string; - readonly VITE_KEYCLOAK_URL: KeycloakParams["url"]; - readonly VITE_KEYCLOAK_CLIENT_ID: KeycloakParams["clientId"]; - readonly VITE_KEYCLOAK_REALM: KeycloakParams["realm"]; + readonly VITE_OIDC_ISSUER: Oidc.Common["params"]["issuerUri"]; + readonly VITE_OIDC_CLIENT_ID: Oidc.Common["params"]["clientId"]; // more env variables... } diff --git a/yarn.lock b/yarn.lock index f7632cf2..eb3d5a25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1601,7 +1601,7 @@ remove-accents "^0.4.2" sass "^1.32.2" -"@inseefr/lunatic@^2.4.10", "@inseefr/lunatic@^2.4.7": +"@inseefr/lunatic@^2.4.7": version "2.4.10" resolved "https://registry.yarnpkg.com/@inseefr/lunatic/-/lunatic-2.4.10.tgz#a86c14ecbe5cf32706eef84c6f24ff6df99a194e" integrity sha512-9QuWMbaIz7X1twZtwspEqf2rhwSYgkhKi8Ksyx4wqXHs66nLEgMK0M721B5NDWH8TexMx6ymxuIqVay8+nz2Cg== @@ -1625,7 +1625,31 @@ snowball "^0.3.1" string-tokenizer "^0.0.8" -"@inseefr/trevas@^0.1.14", "@inseefr/trevas@^0.1.16", "@inseefr/trevas@^0.1.17": +"@inseefr/lunatic@^2.7.4": + version "2.7.4" + resolved "https://registry.yarnpkg.com/@inseefr/lunatic/-/lunatic-2.7.4.tgz#1effadafd95d9333f73a84a3a0da0ac6b2c192c2" + integrity sha512-82+PRRChVK3DeyrhAVBCTYxVi9jTmqUbelWnfjHu4ktDqeRPk6Wm6LubcF3nn29Kzw46Vf7RsVmwi1RDj0+I6A== + dependencies: + "@inseefr/trevas" "^0.1.20" + "@inseefr/vtl-2.0-antlr-tools" "^0.1.0-bundle" + antlr4 "4.11.0" + classnames "^2.3.1" + date-fns "^2.25.0" + lodash.camelcase "^4.3.0" + lodash.debounce "^4.0.8" + lodash.isequal "^4.5.0" + object-hash "^2.2.0" + prop-types "^15.7.2" + react-keyboard-event-handler "^1.5.4" + react-markdown "^8.0.3" + react-number-format "^5.1.3" + react-tooltip "^4.2.15" + remove-accents "^0.4.2" + sass "^1.58.3" + snowball "^0.3.1" + string-tokenizer "^0.0.8" + +"@inseefr/trevas@^0.1.14", "@inseefr/trevas@^0.1.16", "@inseefr/trevas@^0.1.17", "@inseefr/trevas@^0.1.20": version "0.1.20" resolved "https://registry.yarnpkg.com/@inseefr/trevas/-/trevas-0.1.20.tgz#a1d68cf3d22a4da1ef99c1c8560470c8dda407d7" integrity sha512-x1UotSHqHfGhut0JgPCLu+TXZtDpcYSrFQtlIl/L1kav6zYBa888AHhkADWB2hpJM/XAG3lQEfZrqgnsgbQK1g== @@ -5657,6 +5681,11 @@ crypto-js@^4.0.0: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -12113,6 +12142,14 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +oidc-client-ts@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz#764c8a33de542026e2798de9849ce8049047d7e5" + integrity sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w== + dependencies: + crypto-js "^4.2.0" + jwt-decode "^3.1.2" + oidc-client@^1.11.5: version "1.11.5" resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.11.5.tgz#020aa193d68a3e1f87a24fcbf50073b738de92bb" @@ -12124,6 +12161,15 @@ oidc-client@^1.11.5: crypto-js "^4.0.0" serialize-javascript "^4.0.0" +oidc-spa@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-2.0.3.tgz#387a6c92b2fbd097d75ca9f3fa4b10f7f5213cdc" + integrity sha512-pZJgjpdVr2CRGKhqQGj6z7qu1INo9/xP6HkkqIata3zXEmFrIK/zv6SCpDdFMfeIVfqQWV946FVbccUqBrUlcA== + dependencies: + jwt-decode "^3.1.2" + oidc-client-ts "^2.3.0" + tsafe "^1.6.5" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -16138,6 +16184,11 @@ tsafe@^1.6.4: resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.4.tgz#048a114761714538c72f16abd25bb247d4e3780e" integrity sha512-l4Z54QFGHO8GF0gBpb3yPGHjkIkIirl8rwW+lMBmtEMzOJeRs8BdjkDEx6nU8Ak9PQVp/KNDtECxTja8MMIDoA== +tsafe@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.5.tgz#74943b69190069168a53d2accd6d07891cf1cce8" + integrity sha512-895zss8xqqHKTc28sHGIfZKnt3C5jrstB1DyPr/h3/flK0zojsZUMQL1/W4ytdDW6KI4Oth62nb9rrxmA3s3Iw== + tsconfck@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-2.1.2.tgz#f667035874fa41d908c1fe4d765345fcb1df6e35"