Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
fdb9ae6c40 | |||
3c82ffc0ab | |||
5dd3103aba | |||
84fc81f531 | |||
a20cbc62a5 | |||
e6a93e2838 | |||
3cff54561f | |||
e50a6a7876 | |||
b887ec839b | |||
465daa19a0 | |||
6c2b761d95 | |||
0e8f95ce19 | |||
6a17f343c6 | |||
1a45fb0039 | |||
75032898d6 | |||
88a4c97428 | |||
82e7a7edae | |||
eac28f97b8 | |||
e160882db9 | |||
2bc07e77fd | |||
c9b2db625c | |||
e3b41c9bd1 | |||
4aaee35d9c |
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,3 +1,38 @@
|
|||||||
|
### **2.0.4** (2021-07-03)
|
||||||
|
|
||||||
|
- Fix: #7
|
||||||
|
|
||||||
|
### **2.0.3** (2021-06-30)
|
||||||
|
|
||||||
|
- Escape double quote in ftl to js conversion #15
|
||||||
|
- Update readme
|
||||||
|
|
||||||
|
### **2.0.2** (2021-06-28)
|
||||||
|
|
||||||
|
- Updagte README for implementing non incuded pages
|
||||||
|
|
||||||
|
### **2.0.1** (2021-06-28)
|
||||||
|
|
||||||
|
- Update documentation for v2
|
||||||
|
|
||||||
|
# **2.0.0** (2021-06-28)
|
||||||
|
|
||||||
|
- Fix last bugs before relasing v2
|
||||||
|
- Implement a mechanism to overload kcContext
|
||||||
|
- Give the option in template to pull the default assets or not
|
||||||
|
- Enable possiblity to support custom pages (without forking keycloakify)
|
||||||
|
- Implement a getter for kcContext
|
||||||
|
- Update README.md
|
||||||
|
|
||||||
|
# **2.0.0** (2021-06-28)
|
||||||
|
|
||||||
|
- Fix last bugs before relasing v2
|
||||||
|
- Implement a mechanism to overload kcContext
|
||||||
|
- Give the option in template to pull the default assets or not
|
||||||
|
- Enable possiblity to support custom pages (without forking keycloakify)
|
||||||
|
- Implement a getter for kcContext
|
||||||
|
- Update README.md
|
||||||
|
|
||||||
### **1.2.1** (2021-06-22)
|
### **1.2.1** (2021-06-22)
|
||||||
|
|
||||||
- Remove unessesary log
|
- Remove unessesary log
|
||||||
|
61
README.md
61
README.md
@ -20,6 +20,10 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
**NEW in v2**
|
||||||
|
- It's now possible to implement custom `.ftl` pages.
|
||||||
|
- Support for Keycloak plugins that introduce non standard ftl values.
|
||||||
|
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`).
|
||||||
# Motivations
|
# Motivations
|
||||||
|
|
||||||
Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.
|
Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.
|
||||||
@ -58,7 +62,7 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
|
|||||||
- [How to use](#how-to-use)
|
- [How to use](#how-to-use)
|
||||||
- [Setting up the build tool](#setting-up-the-build-tool)
|
- [Setting up the build tool](#setting-up-the-build-tool)
|
||||||
- [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme)
|
- [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme)
|
||||||
- [Changing the look **and** feel](#changing-the-look-and-feel)
|
- [Advanced pages configuration](#advanced-pages-configuration)
|
||||||
- [Hot reload](#hot-reload)
|
- [Hot reload](#hot-reload)
|
||||||
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
|
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
|
||||||
- [Support for Terms and conditions](#support-for-terms-and-conditions)
|
- [Support for Terms and conditions](#support-for-terms-and-conditions)
|
||||||
@ -78,6 +82,7 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
|
|||||||
Tested with the following Keycloak versions:
|
Tested with the following Keycloak versions:
|
||||||
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
||||||
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
|
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
|
||||||
|
- Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore)
|
||||||
|
|
||||||
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
|
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
|
||||||
(before you customize it) will always be the ones of Keycloak v11.
|
(before you customize it) will always be the ones of Keycloak v11.
|
||||||
@ -129,10 +134,12 @@ import { App } from "./<wherever>/App";
|
|||||||
import {
|
import {
|
||||||
KcApp,
|
KcApp,
|
||||||
defaultKcProps,
|
defaultKcProps,
|
||||||
kcContext
|
getKcContext
|
||||||
} from "keycloakify";
|
} from "keycloakify";
|
||||||
import { css } from "tss-react";
|
import { css } from "tss-react";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
const myClassName = css({ "color": "red" });
|
const myClassName = css({ "color": "red" });
|
||||||
|
|
||||||
reactDom.render(
|
reactDom.render(
|
||||||
@ -156,10 +163,12 @@ import { App } from "./<wherever>/App";
|
|||||||
import {
|
import {
|
||||||
KcApp,
|
KcApp,
|
||||||
defaultKcProps,
|
defaultKcProps,
|
||||||
kcContext
|
getKcContext
|
||||||
} from "keycloakify";
|
} from "keycloakify";
|
||||||
import { css } from "tss-react";
|
import { css } from "tss-react";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
const myClassName = css({ "color": "red" });
|
const myClassName = css({ "color": "red" });
|
||||||
|
|
||||||
reactDom.render(
|
reactDom.render(
|
||||||
@ -194,20 +203,25 @@ and the result you can expect:
|
|||||||
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
|
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Changing the look **and** feel
|
### Advanced pages configuration
|
||||||
|
|
||||||
If you want to really re-implement the pages, the best approach is to
|
If you want to go beyond only customizing the CSS you can re-implement some of the
|
||||||
create your own version of the [`<KcApp />`](https://github.com/garronej/keycloakify/blob/develop/src/lib/components/KcApp.tsx).
|
pages or event add new ones.
|
||||||
Copy/past some of [the components](https://github.com/garronej/keycloakify/tree/develop/src/lib/components) provided by this module and start hacking around.
|
|
||||||
|
|
||||||
You can find an example of such customization [here](https://github.com/InseeFrLab/onyxia-ui/tree/master/src/app/components/KcApp).
|
If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel).
|
||||||
|
If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/app/components/KcApp).
|
||||||
|
The web app is in production [here](https://datalab.sspcloud.fr).
|
||||||
|
|
||||||
And you can test the result in production by trying the login register page of [Onyxia](https://datalab.sspcloud.fr)
|
Main takeaways are:
|
||||||
|
- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22)
|
||||||
Note that you don’t have to re write **all** components, only the ones that you most need customized.
|
- (TS only) You must declare theses page in the type argument of the getter
|
||||||
Look at [here for example](https://github.com/InseeFrLab/onyxia-ui/blob/3bf18aa82b198fc6ba7998c30abf0a9ae54a58b1/src/app/components/KcApp/KcApp.tsx#L112-L120).
|
function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21)
|
||||||
We want to have our very own login and register page, so we wrote customs [Login.tsx](https://github.com/InseeFrLab/onyxia-ui/blob/master/src/app/components/KcApp/Login.tsx) and [Register.txs](https://github.com/InseeFrLab/onyxia-ui/blob/master/src/app/components/KcApp/Register.tsx), we import them [here](https://github.com/InseeFrLab/onyxia-ui/blob/3bf18aa82b198fc6ba7998c30abf0a9ae54a58b1/src/app/components/KcApp/KcApp.tsx#L9-L10) and use them [here](https://github.com/InseeFrLab/onyxia-ui/blob/3bf18aa82b198fc6ba7998c30abf0a9ae54a58b1/src/app/components/KcApp/KcApp.tsx#L113-L114).
|
- (TS only) If you use Keycloak plugins that defines non standard `.ftl` values
|
||||||
We don't want to bother, however, customizing `login-reset-password.ftl`. We are fine using the component from [the default theme](https://github.com/InseeFrLab/onyxia-ui/blob/3bf18aa82b198fc6ba7998c30abf0a9ae54a58b1/src/app/components/KcApp/KcApp.tsx#L13) with just some [CSS customization](https://github.com/InseeFrLab/onyxia-ui/blob/3bf18aa82b198fc6ba7998c30abf0a9ae54a58b1/src/app/components/KcApp/KcApp.tsx#L103-L110).
|
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
|
||||||
|
that define `authorizedMailDomains` in `register.ftl`) you should
|
||||||
|
declare theses value to get the type. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L6-L13)
|
||||||
|
- You should provide sample data for all the non standard value if you want to be able
|
||||||
|
to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43)
|
||||||
|
|
||||||
WARNING: If you chose to go this way use:
|
WARNING: If you chose to go this way use:
|
||||||
```json
|
```json
|
||||||
@ -220,16 +234,19 @@ in your `package.json` instead of `^X.Y.Z`. A minor update of Keycloakify might
|
|||||||
### Hot reload
|
### Hot reload
|
||||||
|
|
||||||
Rebuild the theme each time you make a change to see the result is not practical.
|
Rebuild the theme each time you make a change to see the result is not practical.
|
||||||
If you want to test your login screens outside of Keycloak, in [storybook](https://storybook.js.org/)
|
If you want to test your login screens outside of Keycloak you can mock a given `kcContext`:
|
||||||
for example you can use `kcContextMocks`.
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import {
|
import {
|
||||||
KcApp,
|
KcApp,
|
||||||
defaultKcProps,
|
defaultKcProps,
|
||||||
kcContextMocks
|
getKcContext
|
||||||
} from "keycloakify";
|
} from "keycloakify";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext({
|
||||||
|
"mockPageId": "login.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
reactDom.render(
|
reactDom.render(
|
||||||
<KcApp
|
<KcApp
|
||||||
kcContext={kcContextMocks.kcLoginContext}
|
kcContext={kcContextMocks.kcLoginContext}
|
||||||
@ -271,16 +288,10 @@ Then to load your own therms of services using [like this](https://github.com/ga
|
|||||||
|
|
||||||
# Some pages still have the default theme. Why?
|
# Some pages still have the default theme. Why?
|
||||||
|
|
||||||
**NEW in v1.2 it is now much more easy to add support for custom pages since the
|
This project only support out of the box the most common user facing pages of Keycloak login.
|
||||||
Keycloak context is now automatically converted into a JavaScript object (kcContext).
|
|
||||||
In v2 (coming soon) it won't be required to fork for adding support for custom pages.**
|
|
||||||
|
|
||||||
This project only support the most common user facing pages of Keycloak login.
|
|
||||||
[Here](https://user-images.githubusercontent.com/6702424/116787906-227fe700-aaa7-11eb-92ee-22e7673717c2.png) is the complete list of pages (you get them after running `yarn test`)
|
[Here](https://user-images.githubusercontent.com/6702424/116787906-227fe700-aaa7-11eb-92ee-22e7673717c2.png) is the complete list of pages (you get them after running `yarn test`)
|
||||||
and [here](https://github.com/InseeFrLab/keycloakify/tree/main/src/lib/components) are the pages currently implemented by this module.
|
and [here](https://github.com/InseeFrLab/keycloakify/tree/main/src/lib/components) are the pages currently implemented by this module.
|
||||||
If you need to customize pages that are not supported yet you can submit an issue about it and wait for me get it implemented.
|
If you need to customize pages that are not supported yet or if you need to implement some non standard `.ftl` pages please refer to [Advanced pages configuration](#advanced-pages-configuration).
|
||||||
If you can't wait, PR are welcome! [Here](https://github.com/InseeFrLab/keycloakify/commit/0163459ad6b1ad0afcc34fae5f3cc28dbcf8b4a7) is the commit that adds support
|
|
||||||
for the `login-otp.ftl` page. You can use it as a model for implementing other pages.
|
|
||||||
|
|
||||||
# GitHub Actions
|
# GitHub Actions
|
||||||
|
|
||||||
|
14
package.json
14
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "1.2.1",
|
"version": "2.0.4",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"clean": "rimraf dist/",
|
"clean": "rimraf dist/",
|
||||||
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
|
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
|
||||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||||
"test": "node dist/test",
|
"test": "node dist/test/bin && node dist/test/lib",
|
||||||
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
|
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
|
||||||
"generate-messages": "node dist/bin/generate-i18n-messages.js"
|
"generate-messages": "node dist/bin/generate-i18n-messages.js"
|
||||||
},
|
},
|
||||||
@ -46,16 +46,18 @@
|
|||||||
"properties-parser": "^0.3.1",
|
"properties-parser": "^0.3.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3",
|
||||||
|
"ts-toolbelt": "^9.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"evt": "2.0.0-beta.15",
|
"evt": "2.0.0-beta.20",
|
||||||
"minimal-polyfills": "^2.1.6",
|
"minimal-polyfills": "^2.1.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"powerhooks": "^0.1.0",
|
"powerhooks": "^0.1.6",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"tss-react": "^0.0.12"
|
"tss-react": "^0.0.12",
|
||||||
|
"tsafe": "^0.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import * as child_process from "child_process";
|
|||||||
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
|
|
||||||
|
|
||||||
const reactProjectDirPath = process.cwd();
|
const reactProjectDirPath = process.cwd();
|
||||||
|
|
||||||
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
|
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
|
||||||
@ -19,6 +18,8 @@ export function main() {
|
|||||||
|
|
||||||
console.log("🔏 Building the keycloak theme...⌚");
|
console.log("🔏 Building the keycloak theme...⌚");
|
||||||
|
|
||||||
|
const extraPagesId: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? [];
|
||||||
|
|
||||||
generateKeycloakThemeResources({
|
generateKeycloakThemeResources({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath,
|
||||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
||||||
@ -53,7 +54,8 @@ export function main() {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})()
|
})(),
|
||||||
|
extraPagesId
|
||||||
});
|
});
|
||||||
|
|
||||||
const { jarFilePath } = generateJavaStackFiles({
|
const { jarFilePath } = generateJavaStackFiles({
|
||||||
|
@ -113,7 +113,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<#attempt>
|
<#attempt>
|
||||||
"${object?no_esc}"
|
"${object?replace('"', '\\"')?no_esc}"
|
||||||
<#recover>
|
<#recover>
|
||||||
/* couldn't convert into string non hash, non method, non boolean, non enumerable object */
|
/* couldn't convert into string non hash, non method, non boolean, non enumerable object */
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from "../replaceImportFromStatic";
|
} from "../replaceImportFromStatic";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
import { objectKeys } from "tsafe/objectKeys";
|
||||||
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
|
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
|
||||||
|
|
||||||
export const pageIds = [
|
export const pageIds = [
|
||||||
@ -136,7 +136,7 @@ export function generateFtlFilesCodeFactory(
|
|||||||
|
|
||||||
function generateFtlFilesCode(
|
function generateFtlFilesCode(
|
||||||
params: {
|
params: {
|
||||||
pageId: PageId;
|
pageId: string;
|
||||||
}
|
}
|
||||||
): { ftlCode: string; } {
|
): { ftlCode: string; } {
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
|
|||||||
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
|
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
|
||||||
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
|
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/getKcContext/kcContextMocks/urlResourcesPath";
|
||||||
import { isInside } from "../tools/isInside";
|
import { isInside } from "../tools/isInside";
|
||||||
|
|
||||||
|
|
||||||
@ -22,10 +22,14 @@ export function generateKeycloakThemeResources(
|
|||||||
urlPathname: string;
|
urlPathname: string;
|
||||||
//If urlOrigin is not undefined then it means --externals-assets
|
//If urlOrigin is not undefined then it means --externals-assets
|
||||||
urlOrigin: undefined | string;
|
urlOrigin: undefined | string;
|
||||||
|
extraPagesId: string[];
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, urlPathname, urlOrigin } = params;
|
const {
|
||||||
|
themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath,
|
||||||
|
urlPathname, urlOrigin, extraPagesId
|
||||||
|
} = params;
|
||||||
|
|
||||||
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
|
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ export function generateKeycloakThemeResources(
|
|||||||
urlOrigin
|
urlOrigin
|
||||||
});
|
});
|
||||||
|
|
||||||
pageIds.forEach(pageId => {
|
[...pageIds, ...extraPagesId].forEach(pageId => {
|
||||||
|
|
||||||
const { ftlCode } = generateFtlFilesCode({ pageId });
|
const { ftlCode } = generateFtlFilesCode({ pageId });
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { crawl } from "./crawl";
|
import { crawl } from "./crawl";
|
||||||
import { id } from "evt/tools/typeSafety/id";
|
import { id } from "tsafe/id";
|
||||||
|
|
||||||
type TransformSourceCode =
|
type TransformSourceCode =
|
||||||
(params: {
|
(params: {
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import { assert } from "../tools/assert";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { KcContext } from "../KcContext";
|
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
|
|
||||||
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContext.Error; } & KcProps) => {
|
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error; } & KcProps) => {
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = useKcMessage();
|
||||||
|
|
||||||
assert(kcContext.message !== undefined);
|
|
||||||
|
|
||||||
const { message, client } = kcContext;
|
const { message, client } = kcContext;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("errorTitle")}
|
headerNode={msg("errorTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
|
@ -3,10 +3,10 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import { assert } from "../tools/assert";
|
import { assert } from "../tools/assert";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
|
|
||||||
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContext.Info; } & KcProps) => {
|
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info; } & KcProps) => {
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = useKcMessage();
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ export const Info = memo(({ kcContext, ...props }: { kcContext: KcContext.Info;
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={
|
headerNode={
|
||||||
messageHeader !== undefined ?
|
messageHeader !== undefined ?
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import { Login } from "./Login";
|
import { Login } from "./Login";
|
||||||
import { Register } from "./Register";
|
import { Register } from "./Register";
|
||||||
@ -13,7 +13,7 @@ import { LoginOtp } from "./LoginOtp";
|
|||||||
import { LoginUpdateProfile } from "./LoginUpdateProfile";
|
import { LoginUpdateProfile } from "./LoginUpdateProfile";
|
||||||
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
|
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
|
||||||
|
|
||||||
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContext; } & KcProps) => {
|
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase; } & KcProps) => {
|
||||||
switch (kcContext.pageId) {
|
switch (kcContext.pageId) {
|
||||||
case "login.ftl": return <Login {...{ kcContext, ...props }} />;
|
case "login.ftl": return <Login {...{ kcContext, ...props }} />;
|
||||||
case "register.ftl": return <Register {...{ kcContext, ...props }} />;
|
case "register.ftl": return <Register {...{ kcContext, ...props }} />;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
|
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
|
||||||
import { doExtends } from "evt/tools/typeSafety/doExtends";
|
import { doExtends } from "tsafe/doExtends";
|
||||||
|
|
||||||
/** Class names can be provided as an array or separated by whitespace */
|
/** Class names can be provided as an array or separated by whitespace */
|
||||||
export type KcPropsGeneric<CssClasses extends string> = { [key in CssClasses]: readonly string[] | string | undefined; };
|
export type KcPropsGeneric<CssClasses extends string> = { [key in CssClasses]: readonly string[] | string | undefined; };
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
import { useState, memo } from "react";
|
import { useState, memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
import { useConstCallback } from "powerhooks";
|
import { useConstCallback } from "powerhooks";
|
||||||
|
|
||||||
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContext.Login; } & KcProps) => {
|
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login; } & KcProps) => {
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ export const Login = memo(({ kcContext, ...props }: { kcContext: KcContext.Login
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayInfo={social.displayInfo}
|
displayInfo={social.displayInfo}
|
||||||
displayWide={realm.password && social.providers !== undefined}
|
displayWide={realm.password && social.providers !== undefined}
|
||||||
headerNode={msg("doLogIn")}
|
headerNode={msg("doLogIn")}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginIdpLinkConfirm; } & KcProps) => {
|
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; } & KcProps) => {
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = useKcMessage();
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: K
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
headerNode={msg("confirmLinkIdpTitle")}
|
headerNode={msg("confirmLinkIdpTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<form id="kc-register-form" action={url.loginAction} method="post">
|
<form id="kc-register-form" action={url.loginAction} method="post">
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
import { useEffect, memo } from "react";
|
import { useEffect, memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { appendHead } from "../tools/appendHead";
|
import { appendHead } from "../tools/appendHead";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginOtp; } & KcProps) => {
|
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginOtp; } & KcProps) => {
|
||||||
|
|
||||||
const { otpLogin, url } = kcContext;
|
const { otpLogin, url } = kcContext;
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContext.Lo
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
headerNode={msg("doLogIn")}
|
headerNode={msg("doLogIn")}
|
||||||
formNode={
|
formNode={
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginResetPassword; } & KcProps) => {
|
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword; } & KcProps) => {
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("emailForgotTitle")}
|
headerNode={msg("emailForgotTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginUpdateProfile; } & KcProps) => {
|
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; } & KcProps) => {
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: Kc
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
headerNode={msg("loginProfileTitle")}
|
headerNode={msg("loginProfileTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
|
|
||||||
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginVerifyEmail; } & KcProps) => {
|
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; } & KcProps) => {
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = useKcMessage();
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcCo
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("emailVerifyTitle")}
|
headerNode={msg("emailVerifyTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Register; } & KcProps) => {
|
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register; } & KcProps) => {
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
@ -22,11 +22,12 @@ export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Re
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
headerNode={msg("registerTitle")}
|
headerNode={msg("registerTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('firstName', props.kcFormGroupErrorClass))}>
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label>
|
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { useReducer, useEffect, memo } from "react";
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { assert } from "../tools/assert";
|
import { assert } from "../tools/assert";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
||||||
@ -25,7 +25,11 @@ export type TemplateProps = {
|
|||||||
showUsernameNode?: ReactNode;
|
showUsernameNode?: ReactNode;
|
||||||
formNode: ReactNode;
|
formNode: ReactNode;
|
||||||
infoNode?: ReactNode;
|
infoNode?: ReactNode;
|
||||||
} & { kcContext: KcContext; } & KcTemplateProps;
|
/** If you write your own page you probably want
|
||||||
|
* to avoid pulling the default theme assets.
|
||||||
|
*/
|
||||||
|
doFetchDefaultThemeResources: boolean;
|
||||||
|
} & { kcContext: KcContextBase; } & KcTemplateProps;
|
||||||
|
|
||||||
export const Template = memo((props: TemplateProps) => {
|
export const Template = memo((props: TemplateProps) => {
|
||||||
|
|
||||||
@ -39,7 +43,8 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
showUsernameNode = null,
|
showUsernameNode = null,
|
||||||
formNode,
|
formNode,
|
||||||
infoNode = null,
|
infoNode = null,
|
||||||
kcContext
|
kcContext,
|
||||||
|
doFetchDefaultThemeResources
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
|
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
|
||||||
@ -84,6 +89,11 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!doFetchDefaultThemeResources) {
|
||||||
|
setExtraCssLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let isUnmounted = false;
|
let isUnmounted = false;
|
||||||
const cleanups: (() => void)[] = [];
|
const cleanups: (() => void)[] = [];
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { cx } from "tss-react";
|
import { cx } from "tss-react";
|
||||||
|
|
||||||
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContext.Terms; } & KcProps) => {
|
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms; } & KcProps) => {
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContext.Terms
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, ...props }}
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("termsTitle")}
|
headerNode={msg("termsTitle")}
|
||||||
formNode={
|
formNode={
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/ftlValuesGlobalName";
|
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
|
||||||
import type { PageId } from "../bin/build-keycloak-theme/generateFtl";
|
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
||||||
import { id } from "evt/tools/typeSafety/id";
|
import { doExtends } from "tsafe/doExtends";
|
||||||
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
|
import type { MessageKey } from "../i18n/useKcMessage";
|
||||||
import { doExtends } from "evt/tools/typeSafety/doExtends";
|
import type { LanguageLabel } from "../i18n/KcLanguageTag";
|
||||||
import type { MessageKey } from "./i18n/useKcMessage";
|
|
||||||
import type { LanguageLabel } from "./i18n/KcLanguageTag";
|
|
||||||
|
|
||||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
|
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
|
||||||
StrEnum extends `${Prefix}${infer U}` ? U : never;
|
StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||||
@ -14,13 +12,13 @@ type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
|
|||||||
* Some values might be undefined on some pages.
|
* Some values might be undefined on some pages.
|
||||||
* (ex: url.loginAction is undefined on error.ftl)
|
* (ex: url.loginAction is undefined on error.ftl)
|
||||||
*/
|
*/
|
||||||
export type KcContext =
|
export type KcContextBase =
|
||||||
KcContext.Login | KcContext.Register | KcContext.Info |
|
KcContextBase.Login | KcContextBase.Register | KcContextBase.Info |
|
||||||
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail |
|
KcContextBase.Error | KcContextBase.LoginResetPassword | KcContextBase.LoginVerifyEmail |
|
||||||
KcContext.Terms | KcContext.LoginOtp | KcContext.LoginUpdateProfile |
|
KcContextBase.Terms | KcContextBase.LoginOtp | KcContextBase.LoginUpdateProfile |
|
||||||
KcContext.LoginIdpLinkConfirm;
|
KcContextBase.LoginIdpLinkConfirm;
|
||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContextBase {
|
||||||
|
|
||||||
export type Common = {
|
export type Common = {
|
||||||
url: {
|
url: {
|
||||||
@ -125,11 +123,6 @@ export declare namespace KcContext {
|
|||||||
passwordRequired: boolean;
|
passwordRequired: boolean;
|
||||||
recaptchaRequired: boolean;
|
recaptchaRequired: boolean;
|
||||||
recaptchaSiteKey?: string;
|
recaptchaSiteKey?: string;
|
||||||
/**
|
|
||||||
* Defined when you use the keycloak-mail-whitelisting keycloak plugin
|
|
||||||
* (https://github.com/micedre/keycloak-mail-whitelisting)
|
|
||||||
*/
|
|
||||||
authorizedMailDomains?: string[];
|
|
||||||
social: {
|
social: {
|
||||||
displayInfo: boolean;
|
displayInfo: boolean;
|
||||||
providers?: {
|
providers?: {
|
||||||
@ -157,7 +150,8 @@ export declare namespace KcContext {
|
|||||||
pageId: "error.ftl";
|
pageId: "error.ftl";
|
||||||
client?: {
|
client?: {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
}
|
},
|
||||||
|
message: NonNullable<Common["message"]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginResetPassword = Common & {
|
export type LoginResetPassword = Common & {
|
||||||
@ -207,9 +201,8 @@ export declare namespace KcContext {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doExtends<KcContext["pageId"], PageId>();
|
doExtends<KcContextBase["pageId"], PageId>();
|
||||||
doExtends<PageId, KcContext["pageId"]>();
|
doExtends<PageId, KcContextBase["pageId"]>();
|
||||||
|
|
||||||
export const kcContext = id<KcContext | undefined>((window as any)[ftlValuesGlobalName]);
|
|
||||||
|
|
||||||
|
|
86
src/lib/getKcContext/getKcContext.ts
Normal file
86
src/lib/getKcContext/getKcContext.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
import type { KcContextBase } from "./KcContextBase";
|
||||||
|
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||||
|
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
|
||||||
|
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
|
||||||
|
import type { DeepPartial } from "../tools/DeepPartial";
|
||||||
|
import { deepAssign } from "../tools/deepAssign";
|
||||||
|
|
||||||
|
|
||||||
|
export type ExtendsKcContextBase<
|
||||||
|
KcContextExtended extends { pageId: string; }
|
||||||
|
> =
|
||||||
|
[KcContextExtended] extends [never] ?
|
||||||
|
KcContextBase :
|
||||||
|
AndByDiscriminatingKey<
|
||||||
|
"pageId",
|
||||||
|
KcContextExtended & KcContextBase.Common,
|
||||||
|
KcContextBase
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function getKcContext<KcContextExtended extends { pageId: string; } = never>(
|
||||||
|
params?: {
|
||||||
|
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
||||||
|
mockData?: readonly DeepPartial<ExtendsKcContextBase<KcContextExtended>>[];
|
||||||
|
}
|
||||||
|
): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined; } {
|
||||||
|
|
||||||
|
const {
|
||||||
|
mockPageId,
|
||||||
|
mockData
|
||||||
|
} = params ?? {};
|
||||||
|
|
||||||
|
if (mockPageId !== undefined) {
|
||||||
|
|
||||||
|
//TODO maybe trow if no mock fo custom page
|
||||||
|
|
||||||
|
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
||||||
|
|
||||||
|
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
kcContextDefaultMock === undefined &&
|
||||||
|
partialKcContextCustomMock === undefined
|
||||||
|
) {
|
||||||
|
|
||||||
|
console.warn([
|
||||||
|
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
|
||||||
|
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
|
||||||
|
`Please check the documentation of the getKcContext function`
|
||||||
|
].join("\n"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const kcContext: any = {};
|
||||||
|
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext,
|
||||||
|
"source": kcContextDefaultMock !== undefined ?
|
||||||
|
kcContextDefaultMock :
|
||||||
|
{ "pageId": mockPageId, ...kcContextCommonMock, }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (partialKcContextCustomMock !== undefined) {
|
||||||
|
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext,
|
||||||
|
"source": partialKcContextCustomMock
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return { kcContext };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"kcContext":
|
||||||
|
typeof window === "undefined" ?
|
||||||
|
undefined :
|
||||||
|
(window as any)[ftlValuesGlobalName]
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
2
src/lib/getKcContext/index.ts
Normal file
2
src/lib/getKcContext/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type { KcContextBase } from "./KcContextBase";
|
||||||
|
export { getKcContext } from "./getKcContext";
|
256
src/lib/getKcContext/kcContextMocks/kcContextMocks.ts
Normal file
256
src/lib/getKcContext/kcContextMocks/kcContextMocks.ts
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
|
||||||
|
import type { KcContextBase } from "../KcContextBase";
|
||||||
|
import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag";
|
||||||
|
import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
|
||||||
|
//NOTE: Aside because we want to be able to import them from node
|
||||||
|
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
|
||||||
|
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
||||||
|
|
||||||
|
export const kcContextCommonMock: KcContextBase.Common = {
|
||||||
|
"url": {
|
||||||
|
"loginAction": "#",
|
||||||
|
"resourcesPath": pathJoin(PUBLIC_URL, resourcesPath),
|
||||||
|
"resourcesCommonPath": pathJoin(PUBLIC_URL, resourcesCommonPath),
|
||||||
|
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
|
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
|
},
|
||||||
|
"realm": {
|
||||||
|
"displayName": "myrealm",
|
||||||
|
"displayNameHtml": "myrealm",
|
||||||
|
"internationalizationEnabled": true,
|
||||||
|
"registrationEmailAsUsername": true,
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"supported": [
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||||
|
"languageTag": "de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||||
|
"languageTag": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||||
|
"languageTag": "ru"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||||
|
"languageTag": "sv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||||
|
"languageTag": "pt-BR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||||
|
"languageTag": "lt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||||
|
"languageTag": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||||
|
"languageTag": "it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||||
|
"languageTag": "fr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||||
|
"languageTag": "zh-CN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||||
|
"languageTag": "es"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||||
|
"languageTag": "cs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||||
|
"languageTag": "ja"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||||
|
"languageTag": "sk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||||
|
"languageTag": "pl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||||
|
"languageTag": "ca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||||
|
"languageTag": "nl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||||
|
"languageTag": "tr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
//"current": null as any
|
||||||
|
"current": "English"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"showUsername": false,
|
||||||
|
"showResetCredentials": false,
|
||||||
|
"showTryAnotherWayLink": false
|
||||||
|
},
|
||||||
|
"scripts": [],
|
||||||
|
"message": {
|
||||||
|
"type": "success",
|
||||||
|
"summary": "This is a test message"
|
||||||
|
},
|
||||||
|
"isAppInitiatedAction": false,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperty(
|
||||||
|
kcContextCommonMock.locale!,
|
||||||
|
"current",
|
||||||
|
{
|
||||||
|
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
|
||||||
|
"enumerable": true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const loginUrl = {
|
||||||
|
...kcContextCommonMock.url,
|
||||||
|
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
|
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const kcContextMocks: KcContextBase[] = [
|
||||||
|
id<KcContextBase.Login>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login.ftl",
|
||||||
|
"url": loginUrl,
|
||||||
|
"realm": {
|
||||||
|
...kcContextCommonMock.realm,
|
||||||
|
"loginWithEmailAllowed": true,
|
||||||
|
"rememberMe": true,
|
||||||
|
"password": true,
|
||||||
|
"resetPasswordAllowed": true,
|
||||||
|
"registrationAllowed": true
|
||||||
|
},
|
||||||
|
"auth": kcContextCommonMock.auth!,
|
||||||
|
"social": {
|
||||||
|
"displayInfo": true
|
||||||
|
},
|
||||||
|
"usernameEditDisabled": false,
|
||||||
|
"login": {
|
||||||
|
"rememberMe": false
|
||||||
|
},
|
||||||
|
"registrationDisabled": false,
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.Register>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "register.ftl",
|
||||||
|
"url": {
|
||||||
|
...loginUrl,
|
||||||
|
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
|
||||||
|
},
|
||||||
|
"messagesPerField": {
|
||||||
|
"printIfExists": (...[, x]) => x
|
||||||
|
},
|
||||||
|
"scripts": [],
|
||||||
|
"isAppInitiatedAction": false,
|
||||||
|
"register": {
|
||||||
|
"formData": {}
|
||||||
|
},
|
||||||
|
"passwordRequired": true,
|
||||||
|
"recaptchaRequired": false,
|
||||||
|
"social": {
|
||||||
|
"displayInfo": true
|
||||||
|
},
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.Info>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "info.ftl",
|
||||||
|
"messageHeader": "<Message header>",
|
||||||
|
"requiredActions": undefined,
|
||||||
|
"skipLink": false,
|
||||||
|
"actionUri": "#",
|
||||||
|
"client": {
|
||||||
|
"baseUrl": "#"
|
||||||
|
}
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.Error>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "error.ftl",
|
||||||
|
"client": {
|
||||||
|
"baseUrl": "#"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "error",
|
||||||
|
"summary": "This is the error message"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
id<KcContextBase.LoginResetPassword>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-reset-password.ftl",
|
||||||
|
"realm": {
|
||||||
|
...kcContextCommonMock.realm,
|
||||||
|
"loginWithEmailAllowed": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.LoginVerifyEmail>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-verify-email.ftl"
|
||||||
|
}),
|
||||||
|
id<KcContextBase.Terms>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "terms.ftl"
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.LoginOtp>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-otp.ftl",
|
||||||
|
"otpLogin": {
|
||||||
|
"userOtpCredentials": [
|
||||||
|
{
|
||||||
|
"id": "id1",
|
||||||
|
"userLabel": "label1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "id2",
|
||||||
|
"userLabel": "label2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}),
|
||||||
|
id<KcContextBase.LoginUpdateProfile>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-update-profile.ftl",
|
||||||
|
"user": {
|
||||||
|
"editUsernameAllowed": true,
|
||||||
|
"username": "anUsername",
|
||||||
|
"email": "foo@example.com",
|
||||||
|
"firstName": "aFirstName",
|
||||||
|
"lastName": "aLastName"
|
||||||
|
},
|
||||||
|
"messagesPerField": {
|
||||||
|
"printIfExists": () => undefined
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
id<KcContextBase.LoginIdpLinkConfirm>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-idp-link-confirm.ftl",
|
||||||
|
"idpAlias": "FranceConnect"
|
||||||
|
})
|
||||||
|
];
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
import { objectKeys } from "tsafe/objectKeys";
|
||||||
import { kcMessages } from "./kcMessages/login";
|
import { kcMessages } from "./kcMessages/login";
|
||||||
|
|
||||||
export type KcLanguageTag = keyof typeof kcMessages;
|
export type KcLanguageTag = keyof typeof kcMessages;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { kcMessages } from "../generated_kcMessages/login";
|
import { kcMessages } from "../generated_kcMessages/login";
|
||||||
import { Evt } from "evt";
|
import { Evt } from "evt";
|
||||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
import { objectKeys } from "tsafe/objectKeys";
|
||||||
|
|
||||||
export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
|
export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
|
||||||
|
|
||||||
|
@ -1,21 +1,40 @@
|
|||||||
|
|
||||||
import { createUseGlobalState } from "powerhooks";
|
import { createUseGlobalState } from "powerhooks";
|
||||||
import { kcContext } from "../KcContext";
|
import { getKcContext } from "../getKcContext";
|
||||||
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
|
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
|
||||||
|
import type { StatefulEvt } from "powerhooks";
|
||||||
|
import { KcLanguageTag } from "./KcLanguageTag";
|
||||||
|
|
||||||
|
|
||||||
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
|
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
|
||||||
const wrap = createUseGlobalState(
|
const wrap = createUseGlobalState(
|
||||||
"kcLanguageTag",
|
"kcLanguageTag",
|
||||||
() => getBestMatchAmongKcLanguageTag(
|
() => {
|
||||||
kcContext?.locale?.current ??
|
|
||||||
navigator.language
|
|
||||||
),
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
|
const languageLike =
|
||||||
|
kcContext?.locale?.current ??
|
||||||
|
(
|
||||||
|
typeof navigator === "undefined" ?
|
||||||
|
undefined :
|
||||||
|
navigator.language
|
||||||
|
);
|
||||||
|
|
||||||
|
if (languageLike === undefined) {
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBestMatchAmongKcLanguageTag(languageLike);
|
||||||
|
|
||||||
|
},
|
||||||
{ "persistance": "localStorage" }
|
{ "persistance": "localStorage" }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const { useKcLanguageTag } = wrap;
|
export const { useKcLanguageTag } = wrap;
|
||||||
|
|
||||||
export function getEvtKcLanguage() {
|
export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> {
|
||||||
return wrap.evtKcLanguageTag;
|
return wrap.evtKcLanguageTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export * from "./KcContext";
|
export * from "./getKcContext";
|
||||||
|
|
||||||
export * from "./i18n/KcLanguageTag";
|
export * from "./i18n/KcLanguageTag";
|
||||||
export * from "./i18n/useKcLanguageTag";
|
export * from "./i18n/useKcLanguageTag";
|
||||||
@ -17,5 +17,3 @@ export * from "./keycloakJsAdapter";
|
|||||||
|
|
||||||
export * from "./tools/assert";
|
export * from "./tools/assert";
|
||||||
|
|
||||||
|
|
||||||
export * as kcContextMocks from "./kcContextMocks";
|
|
@ -1,252 +0,0 @@
|
|||||||
|
|
||||||
import type { KcContext } from "../KcContext";
|
|
||||||
import { getEvtKcLanguage } from "../i18n/useKcLanguageTag";
|
|
||||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
|
||||||
//NOTE: Aside because we want to be able to import them from node
|
|
||||||
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
|
||||||
|
|
||||||
const kcCommonContext: KcContext.Common = {
|
|
||||||
"url": {
|
|
||||||
"loginAction": "#",
|
|
||||||
"resourcesPath": `${process.env["PUBLIC_URL"]}/${resourcesPath}`,
|
|
||||||
"resourcesCommonPath": `${process.env["PUBLIC_URL"]}/${resourcesCommonPath}`,
|
|
||||||
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
|
|
||||||
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
|
|
||||||
},
|
|
||||||
"realm": {
|
|
||||||
"displayName": "myrealm",
|
|
||||||
"displayNameHtml": "myrealm",
|
|
||||||
"internationalizationEnabled": true,
|
|
||||||
"registrationEmailAsUsername": true,
|
|
||||||
},
|
|
||||||
"locale": {
|
|
||||||
"supported": [
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
|
||||||
"languageTag": "de"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
|
||||||
"languageTag": "no"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
|
||||||
"languageTag": "ru"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
|
||||||
"languageTag": "sv"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
|
||||||
"languageTag": "pt-BR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
|
||||||
"languageTag": "lt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
|
||||||
"languageTag": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
|
||||||
"languageTag": "it"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
|
||||||
"languageTag": "fr"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
|
||||||
"languageTag": "zh-CN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
|
||||||
"languageTag": "es"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
|
||||||
"languageTag": "cs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
|
||||||
"languageTag": "ja"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
|
||||||
"languageTag": "sk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
|
||||||
"languageTag": "pl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
|
||||||
"languageTag": "ca"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
|
||||||
"languageTag": "nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
|
||||||
"languageTag": "tr"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"current": null as any
|
|
||||||
},
|
|
||||||
"auth": {
|
|
||||||
"showUsername": false,
|
|
||||||
"showResetCredentials": false,
|
|
||||||
"showTryAnotherWayLink": false
|
|
||||||
},
|
|
||||||
"scripts": [],
|
|
||||||
"message": {
|
|
||||||
"type": "success",
|
|
||||||
"summary": "This is a test message"
|
|
||||||
},
|
|
||||||
"isAppInitiatedAction": false,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperty(
|
|
||||||
kcCommonContext.locale!,
|
|
||||||
"current",
|
|
||||||
{
|
|
||||||
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
|
|
||||||
"enumerable": true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const kcLoginContext: KcContext.Login = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login.ftl",
|
|
||||||
"url": {
|
|
||||||
...kcCommonContext.url,
|
|
||||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
|
||||||
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
|
|
||||||
},
|
|
||||||
"realm": {
|
|
||||||
...kcCommonContext.realm,
|
|
||||||
"loginWithEmailAllowed": true,
|
|
||||||
"rememberMe": true,
|
|
||||||
"password": true,
|
|
||||||
"resetPasswordAllowed": true,
|
|
||||||
"registrationAllowed": true
|
|
||||||
},
|
|
||||||
"auth": kcCommonContext.auth!,
|
|
||||||
"social": {
|
|
||||||
"displayInfo": true
|
|
||||||
},
|
|
||||||
"usernameEditDisabled": false,
|
|
||||||
"login": {
|
|
||||||
"rememberMe": false
|
|
||||||
},
|
|
||||||
"registrationDisabled": false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcRegisterContext: KcContext.Register = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"url": {
|
|
||||||
...kcLoginContext.url,
|
|
||||||
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
|
|
||||||
},
|
|
||||||
"messagesPerField": {
|
|
||||||
"printIfExists": (...[, x]) => x
|
|
||||||
},
|
|
||||||
"scripts": [],
|
|
||||||
"isAppInitiatedAction": false,
|
|
||||||
"pageId": "register.ftl",
|
|
||||||
"register": {
|
|
||||||
"formData": {}
|
|
||||||
},
|
|
||||||
"passwordRequired": true,
|
|
||||||
"recaptchaRequired": false,
|
|
||||||
"authorizedMailDomains": [
|
|
||||||
"example.com",
|
|
||||||
"another-example.com",
|
|
||||||
"*.yet-another-example.com",
|
|
||||||
"*.example.com",
|
|
||||||
"hello-world.com"
|
|
||||||
],
|
|
||||||
"social": {
|
|
||||||
"displayInfo": true
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcInfoContext: KcContext.Info = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "info.ftl",
|
|
||||||
"messageHeader": "<Message header>",
|
|
||||||
"requiredActions": undefined,
|
|
||||||
"skipLink": false,
|
|
||||||
"actionUri": "#",
|
|
||||||
"client": {
|
|
||||||
"baseUrl": "#"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcErrorContext: KcContext.Error = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "error.ftl",
|
|
||||||
"client": {
|
|
||||||
"baseUrl": "#"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcLoginResetPasswordContext: KcContext.LoginResetPassword = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login-reset-password.ftl",
|
|
||||||
"realm": {
|
|
||||||
...kcCommonContext.realm,
|
|
||||||
"loginWithEmailAllowed": false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcLoginVerifyEmailContext: KcContext.LoginVerifyEmail = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login-verify-email.ftl"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcTermsContext: KcContext.Terms = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "terms.ftl"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcLoginOtpContext: KcContext.LoginOtp = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login-otp.ftl",
|
|
||||||
"otpLogin": {
|
|
||||||
"userOtpCredentials": [
|
|
||||||
{
|
|
||||||
"id": "id1",
|
|
||||||
"userLabel": "label1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id2",
|
|
||||||
"userLabel": "label2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcLoginUpdateProfileContext: KcContext.LoginUpdateProfile = {
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login-update-profile.ftl",
|
|
||||||
"user": {
|
|
||||||
"editUsernameAllowed": true,
|
|
||||||
"username": "anUsername",
|
|
||||||
"email": "foo@example.com",
|
|
||||||
"firstName": "aFirstName",
|
|
||||||
"lastName": "aLastName"
|
|
||||||
},
|
|
||||||
"messagesPerField": {
|
|
||||||
"printIfExists": () => undefined
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kcLoginIdpLinkConfirmContext: KcContext.LoginIdpLinkConfirm ={
|
|
||||||
...kcCommonContext,
|
|
||||||
"pageId": "login-idp-link-confirm.ftl",
|
|
||||||
"idpAlias": "FranceConnect"
|
|
||||||
};
|
|
35
src/lib/tools/AndByDiscriminatingKey.ts
Normal file
35
src/lib/tools/AndByDiscriminatingKey.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
export type AndByDiscriminatingKey<
|
||||||
|
DiscriminatingKey extends string,
|
||||||
|
U1 extends Record<DiscriminatingKey, string>,
|
||||||
|
U2 extends Record<DiscriminatingKey, string>
|
||||||
|
> =
|
||||||
|
AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
|
||||||
|
|
||||||
|
export declare namespace AndByDiscriminatingKey {
|
||||||
|
|
||||||
|
export type Tf1<
|
||||||
|
DiscriminatingKey extends string,
|
||||||
|
U1,
|
||||||
|
U1Again extends Record<DiscriminatingKey, string>,
|
||||||
|
U2 extends Record<DiscriminatingKey, string>
|
||||||
|
> =
|
||||||
|
U1 extends Pick<U2, DiscriminatingKey> ?
|
||||||
|
Tf2<DiscriminatingKey, U1, U2, U1Again> :
|
||||||
|
U1;
|
||||||
|
|
||||||
|
export type Tf2<
|
||||||
|
DiscriminatingKey extends string,
|
||||||
|
SingletonU1 extends Record<DiscriminatingKey, string>,
|
||||||
|
U2,
|
||||||
|
U1 extends Record<DiscriminatingKey, string>
|
||||||
|
> =
|
||||||
|
U2 extends Pick<SingletonU1, DiscriminatingKey> ?
|
||||||
|
U2 & SingletonU1 :
|
||||||
|
U2 extends Pick<U1, DiscriminatingKey> ?
|
||||||
|
never :
|
||||||
|
U2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
4
src/lib/tools/DeepPartial.ts
Normal file
4
src/lib/tools/DeepPartial.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
|
};
|
@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
export { assert } from "evt/tools/typeSafety/assert";
|
export { assert } from "tsafe/assert";
|
57
src/lib/tools/deepAssign.ts
Normal file
57
src/lib/tools/deepAssign.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { is } from "tsafe/is";
|
||||||
|
|
||||||
|
//Warning: Be mindful that because of array this is not idempotent.
|
||||||
|
export function deepAssign(
|
||||||
|
params: {
|
||||||
|
target: Record<string, unknown>;
|
||||||
|
source: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
const { target, source } = params;
|
||||||
|
|
||||||
|
Object.keys(source).forEach(key => {
|
||||||
|
var dereferencedSource = source[key];
|
||||||
|
|
||||||
|
if (
|
||||||
|
target[key] === undefined ||
|
||||||
|
!(dereferencedSource instanceof Object)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Object.defineProperty(
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
"enumerable": true,
|
||||||
|
"value": dereferencedSource
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dereferencedTarget = target[key];
|
||||||
|
|
||||||
|
if (dereferencedSource instanceof Array) {
|
||||||
|
|
||||||
|
assert(is<unknown[]>(dereferencedTarget));
|
||||||
|
assert(is<unknown[]>(dereferencedSource));
|
||||||
|
|
||||||
|
dereferencedSource.forEach(entry => dereferencedTarget.push(entry));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(is<Record<string, unknown>>(dereferencedTarget));
|
||||||
|
assert(is<Record<string, unknown>>(dereferencedSource));
|
||||||
|
|
||||||
|
deepAssign({
|
||||||
|
"target": dereferencedTarget,
|
||||||
|
"source": dereferencedSource
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
4
src/lib/tools/deepClone.ts
Normal file
4
src/lib/tools/deepClone.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export function deepClone<T>(arg: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(arg));
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
|
import { generateKeycloakThemeResources } from "../../bin/build-keycloak-theme/generateKeycloakThemeResources";
|
||||||
import {
|
import {
|
||||||
setupSampleReactProject,
|
setupSampleReactProject,
|
||||||
sampleReactProjectDirPath
|
sampleReactProjectDirPath
|
||||||
@ -13,6 +13,7 @@ generateKeycloakThemeResources({
|
|||||||
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
||||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
||||||
"urlPathname": "/keycloakify-demo-app/",
|
"urlPathname": "/keycloakify-demo-app/",
|
||||||
"urlOrigin": undefined
|
"urlOrigin": undefined,
|
||||||
|
"extraPagesId": ["my-custom-page.ftl"]
|
||||||
});
|
});
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
|||||||
} from "./setupSampleReactProject";
|
} from "./setupSampleReactProject";
|
||||||
import * as st from "scripting-tools";
|
import * as st from "scripting-tools";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
import { getProjectRoot } from "../../bin/tools/getProjectRoot";
|
||||||
|
|
||||||
setupSampleReactProject();
|
setupSampleReactProject();
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
|||||||
replaceImportsFromStaticInJsCode,
|
replaceImportsFromStaticInJsCode,
|
||||||
replaceImportsInCssCode,
|
replaceImportsInCssCode,
|
||||||
generateCssCodeToDefineGlobals
|
generateCssCodeToDefineGlobals
|
||||||
} from "../bin/build-keycloak-theme/replaceImportFromStatic";
|
} from "../../bin/build-keycloak-theme/replaceImportFromStatic";
|
||||||
|
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||||
"jsCode": `
|
"jsCode": `
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
import { getProjectRoot } from "../../bin/tools/getProjectRoot";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { downloadAndUnzip } from "../bin/tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "../../bin/tools/downloadAndUnzip";
|
||||||
|
|
||||||
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
||||||
|
|
250
src/test/lib/getKcContext.ts
Normal file
250
src/test/lib/getKcContext.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
|
||||||
|
import { getKcContext } from "../../lib/getKcContext";
|
||||||
|
import type { KcContextBase } from "../../lib/getKcContext";
|
||||||
|
import type { ExtendsKcContextBase } from "../../lib/getKcContext/getKcContext";
|
||||||
|
import { same } from "evt/tools/inDepth";
|
||||||
|
import { doExtends } from "tsafe/doExtends";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { kcContextMocks, kcContextCommonMock } from "../../lib/getKcContext/kcContextMocks";
|
||||||
|
import { deepClone } from "../../lib/tools/deepClone";
|
||||||
|
import type { Any } from "ts-toolbelt";
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const authorizedMailDomains = [
|
||||||
|
"example.com",
|
||||||
|
"another-example.com",
|
||||||
|
"*.yet-another-example.com",
|
||||||
|
"*.example.com",
|
||||||
|
"hello-world.com"
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayName = "this is an overwritten common value";
|
||||||
|
|
||||||
|
const aNonStandardValue1 = "a non standard value 1";
|
||||||
|
const aNonStandardValue2 = "a non standard value 2";
|
||||||
|
|
||||||
|
type KcContextExtended = {
|
||||||
|
pageId: "register.ftl";
|
||||||
|
authorizedMailDomains: string[];
|
||||||
|
} | {
|
||||||
|
pageId: "info.ftl";
|
||||||
|
aNonStandardValue1: string;
|
||||||
|
} | {
|
||||||
|
pageId: "my-extra-page-1.ftl";
|
||||||
|
} | {
|
||||||
|
pageId: "my-extra-page-2.ftl";
|
||||||
|
aNonStandardValue2: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKcContextProxy = (
|
||||||
|
params: {
|
||||||
|
mockPageId: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const { mockPageId } = params;
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext<KcContextExtended>({
|
||||||
|
mockPageId,
|
||||||
|
"mockData": [
|
||||||
|
{
|
||||||
|
"pageId": "login.ftl",
|
||||||
|
"realm": { displayName }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pageId": "info.ftl",
|
||||||
|
aNonStandardValue1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pageId": "register.ftl",
|
||||||
|
authorizedMailDomains
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pageId": "my-extra-page-2.ftl",
|
||||||
|
aNonStandardValue2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return { kcContext };
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const pageId = "login.ftl";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
|
|
||||||
|
assert(kcContext?.pageId === pageId);
|
||||||
|
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase.Login>, 1>();
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
//NOTE: deepClone for printIfExists or other functions...
|
||||||
|
deepClone(kcContext),
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!);
|
||||||
|
|
||||||
|
mock.realm.displayName = displayName;
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
|
||||||
|
})()
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log(`PASS ${pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pageId = "info.ftl";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
|
|
||||||
|
assert(kcContext?.pageId === pageId);
|
||||||
|
|
||||||
|
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase.Info & { pageId: typeof pageId; aNonStandardValue1: string; }>, 1>();
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
deepClone(kcContext),
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!);
|
||||||
|
|
||||||
|
Object.assign(mock, { aNonStandardValue1 });
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
|
||||||
|
})()
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log(`PASS ${pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pageId = "register.ftl";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
|
|
||||||
|
assert(kcContext?.pageId === pageId);
|
||||||
|
|
||||||
|
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase.Register & { pageId: typeof pageId; authorizedMailDomains: string[]; }>, 1>();
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
deepClone(kcContext),
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!);
|
||||||
|
|
||||||
|
Object.assign(mock, { authorizedMailDomains });
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
|
||||||
|
})()
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log(`PASS ${pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const pageId = "my-extra-page-2.ftl";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
|
|
||||||
|
assert(kcContext?.pageId === pageId);
|
||||||
|
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase.Common & { pageId: typeof pageId; aNonStandardValue2: string; }>, 1>();
|
||||||
|
|
||||||
|
kcContext.aNonStandardValue2;
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
deepClone(kcContext),
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const mock = deepClone(kcContextCommonMock);
|
||||||
|
|
||||||
|
Object.assign(mock, { pageId, aNonStandardValue2 });
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
|
||||||
|
})()
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log(`PASS ${pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const pageId = "my-extra-page-1.ftl";
|
||||||
|
|
||||||
|
console.log("We expect a warning here =>");
|
||||||
|
|
||||||
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
|
|
||||||
|
|
||||||
|
assert(kcContext?.pageId === pageId);
|
||||||
|
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase.Common & { pageId: typeof pageId; }>, 1>();
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
deepClone(kcContext),
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const mock = deepClone(kcContextCommonMock);
|
||||||
|
|
||||||
|
Object.assign(mock, { pageId });
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
|
||||||
|
})()
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log(`PASS ${pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const pageId = "login.ftl";
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext({
|
||||||
|
"mockPageId": pageId
|
||||||
|
});
|
||||||
|
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase | undefined>, 1>();
|
||||||
|
|
||||||
|
assert(same(
|
||||||
|
deepClone(kcContext),
|
||||||
|
deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!)
|
||||||
|
));
|
||||||
|
|
||||||
|
console.log("PASS no extension");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
|
doExtends<Any.Equals<typeof kcContext, KcContextBase | undefined>, 1>();
|
||||||
|
|
||||||
|
assert(kcContext === undefined);
|
||||||
|
|
||||||
|
console.log("PASS no extension, no mock");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
2
src/test/lib/index.ts
Normal file
2
src/test/lib/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
import "./getKcContext";
|
91
src/test/lib/tools/AndByDiscriminatingKey.type.ts
Normal file
91
src/test/lib/tools/AndByDiscriminatingKey.type.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
import { AndByDiscriminatingKey } from "../../../lib/tools/AndByDiscriminatingKey";
|
||||||
|
import { doExtends } from "tsafe/doExtends";
|
||||||
|
|
||||||
|
type Base =
|
||||||
|
{ pageId: "a"; onlyA: string; } |
|
||||||
|
{ pageId: "b"; onlyB: string; } |
|
||||||
|
{ pageId: "only base"; onlyBase: string; };
|
||||||
|
|
||||||
|
type Extension =
|
||||||
|
{ pageId: "a"; onlyExtA: string; } |
|
||||||
|
{ pageId: "b"; onlyExtB: string; } |
|
||||||
|
{ pageId: "only ext"; onlyExt: string; };
|
||||||
|
|
||||||
|
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
|
||||||
|
|
||||||
|
type Expected =
|
||||||
|
{ pageId: "a"; onlyA: string; onlyExtA: string; } |
|
||||||
|
{ pageId: "b"; onlyB: string; onlyExtB: string; } |
|
||||||
|
{ pageId: "only base"; onlyBase: string; } |
|
||||||
|
{ pageId: "only ext"; onlyExt: string; };
|
||||||
|
|
||||||
|
doExtends<Got, Expected>();
|
||||||
|
doExtends<Expected, Got>();
|
||||||
|
|
||||||
|
const x: Got = null as any;
|
||||||
|
|
||||||
|
if (x.pageId === "a") {
|
||||||
|
|
||||||
|
x.onlyA;
|
||||||
|
x.onlyExtA;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyB;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyBase;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyExt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (x.pageId === "b") {
|
||||||
|
|
||||||
|
x.onlyB;
|
||||||
|
x.onlyExtB;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyA;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyBase;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyExt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.pageId === "only base") {
|
||||||
|
|
||||||
|
x.onlyBase;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyA;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyB;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyExt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.pageId === "only ext") {
|
||||||
|
|
||||||
|
x.onlyExt;
|
||||||
|
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyA;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyB;
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
x.onlyBase;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
34
yarn.lock
34
yarn.lock
@ -514,6 +514,15 @@ evt@2.0.0-beta.15:
|
|||||||
minimal-polyfills "^2.1.5"
|
minimal-polyfills "^2.1.5"
|
||||||
run-exclusive "^2.2.14"
|
run-exclusive "^2.2.14"
|
||||||
|
|
||||||
|
evt@2.0.0-beta.20:
|
||||||
|
version "2.0.0-beta.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.20.tgz#a018ff46a7dcb475c488e6508ac3aec7910e76fb"
|
||||||
|
integrity sha512-aIIqrDkQ5Hm4zzW7v9STZbO9RcwAI2vMQUkjQ7Bw4YkWOv2zoIl6ooJZ6942JU7UCYKWSi1u5BBtyoiXUSsnMA==
|
||||||
|
dependencies:
|
||||||
|
minimal-polyfills "^2.2.1"
|
||||||
|
run-exclusive "^2.2.14"
|
||||||
|
tsafe "^0.2.2"
|
||||||
|
|
||||||
ext@^1.1.2:
|
ext@^1.1.2:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
|
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
|
||||||
@ -770,7 +779,7 @@ micromark@~2.11.0:
|
|||||||
debug "^4.0.0"
|
debug "^4.0.0"
|
||||||
parse-entities "^2.0.0"
|
parse-entities "^2.0.0"
|
||||||
|
|
||||||
minimal-polyfills@^2.1.5, minimal-polyfills@^2.1.6:
|
minimal-polyfills@^2.1.5, minimal-polyfills@^2.1.6, minimal-polyfills@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.1.tgz#7249d7ece666d3b4e1ec1c1b8f949eb9d44e2308"
|
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.1.tgz#7249d7ece666d3b4e1ec1c1b8f949eb9d44e2308"
|
||||||
integrity sha512-WLmHQrsZob4rVYf8yHapZPNJZ3sspGa/sN8abuSD59b0FifDEE7HMfLUi24z7mPZqTpBXy4Svp+iGvAmclCmXg==
|
integrity sha512-WLmHQrsZob4rVYf8yHapZPNJZ3sspGa/sN8abuSD59b0FifDEE7HMfLUi24z7mPZqTpBXy4Svp+iGvAmclCmXg==
|
||||||
@ -893,10 +902,10 @@ path@^0.12.7:
|
|||||||
process "^0.11.1"
|
process "^0.11.1"
|
||||||
util "^0.10.3"
|
util "^0.10.3"
|
||||||
|
|
||||||
powerhooks@^0.1.0:
|
powerhooks@^0.1.6:
|
||||||
version "0.1.4"
|
version "0.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.1.4.tgz#82aae8dae5485a154d3ce4e89342a2d68cb2a413"
|
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.1.6.tgz#23dfb6c781bbfdd20ea37ff5a2a0fd5a9799d591"
|
||||||
integrity sha512-ig47hJIW/b75gCS3l2EtK8NVAduNVd9vem8IvaWkuPuZIP4mbDAy4Rc0/hvjXVUPs9OzK7jc/zIQika3tTacYg==
|
integrity sha512-S1b5awSAimyt9jXZsm+N8/GFBtzLPzmVIUyVLq/IADDe4aAcxFWBgXSWTOFc31PlWE9uz2K/NovsQNDf4Fr52A==
|
||||||
dependencies:
|
dependencies:
|
||||||
evt "2.0.0-beta.15"
|
evt "2.0.0-beta.15"
|
||||||
memoizee "^0.4.15"
|
memoizee "^0.4.15"
|
||||||
@ -1121,11 +1130,26 @@ trough@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||||
|
|
||||||
|
ts-toolbelt@^9.6.0:
|
||||||
|
version "9.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5"
|
||||||
|
integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==
|
||||||
|
|
||||||
tsafe@^0.1.0:
|
tsafe@^0.1.0:
|
||||||
version "0.1.15"
|
version "0.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.1.15.tgz#9e2b6137fb5a49fc7c23cb0bc49ae09bab215f2a"
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.1.15.tgz#9e2b6137fb5a49fc7c23cb0bc49ae09bab215f2a"
|
||||||
integrity sha512-aAWMOACHXMmwE2zRcQpBv+whxYqB4zvAbs+dzwbnGaGK9NAOQ65m0+WyO+jw/41JzCX7orJU/ieFHTmgehTOKA==
|
integrity sha512-aAWMOACHXMmwE2zRcQpBv+whxYqB4zvAbs+dzwbnGaGK9NAOQ65m0+WyO+jw/41JzCX7orJU/ieFHTmgehTOKA==
|
||||||
|
|
||||||
|
tsafe@^0.2.2:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.2.4.tgz#f2cffb9b33e4d20c6f9800aa1c45bee02d917aa1"
|
||||||
|
integrity sha512-oS/0wJKgEv/iDow/6U51xjAascJVtUQfZQMCDdoOS+VnMlWZFdXGMKuoH47JYs6hEWHpO+Z5pC5oAFFHiPfTLg==
|
||||||
|
|
||||||
|
tsafe@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.4.1.tgz#00af1be2db82abb4be531209b90232d7954e1a03"
|
||||||
|
integrity sha512-+OZ0gdgmwcru+MOSheCx+ymAvQz+1/ui+KFJRuaq0t2m8RNrlf7eSzEieptoPQXPY67Mdkqgkdjknn8azoD5sw==
|
||||||
|
|
||||||
tslib@^2.2.0:
|
tslib@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
||||||
|
Reference in New Issue
Block a user