Skip to content

Commit fa3d434

Browse files
authored
Merge pull request #430 from ForgeRock/journey-test-app
chore(journey-client): refactored client and built test app
2 parents 06f2776 + 5050288 commit fa3d434

File tree

135 files changed

+1814
-1034
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+1814
-1034
lines changed

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ packages/**/coverage/
1919
node_modules/
2020
tests/**/app/index.js*
2121
tests/**/ie11/ie-bundle.js*
22+
2223
# Out-TSC directories (TypeScript output)
2324
**/out-tsc/*
2425
out-tsc/
@@ -32,6 +33,7 @@ out-tsc/
3233

3334
# Test code
3435
tests/**/app/index.js*
36+
e2e/**/.env.e2e
3537
test-results
3638

3739
# Environment files
@@ -53,7 +55,7 @@ vite.config.*.timestamp*
5355
.netrc
5456

5557
# ignore this in CI please.
56-
# Nx stuff we get in CI that we dont want to commit
58+
# Nx stuff we get in CI that we don't want to commit
5759
code
5860
terminalOutput
5961
outputs/*
@@ -82,4 +84,4 @@ test-output
8284

8385
# Gemini local knowledge base files
8486
GEMINI.md
85-
**/GEMINI.md
87+
**/GEMINI.md

e2e/davinci-suites/.env.e2e

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { PasswordCallback } from '@forgerock/journey-client/types';
8+
9+
export default function passwordComponent(
10+
journeyEl: HTMLDivElement,
11+
callback: PasswordCallback,
12+
idx: number,
13+
) {
14+
const collectorKey = callback?.payload?.input?.[0].name || `collector-${idx}`;
15+
const label = document.createElement('label');
16+
const input = document.createElement('input');
17+
18+
label.htmlFor = collectorKey;
19+
label.innerText = callback.getPrompt();
20+
input.type = 'password';
21+
input.id = collectorKey;
22+
input.name = collectorKey;
23+
24+
journeyEl?.appendChild(label);
25+
journeyEl?.appendChild(input);
26+
27+
journeyEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
28+
callback.setPassword((event.target as HTMLInputElement).value);
29+
});
30+
}

e2e/journey-app/components/text.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { NameCallback } from '@forgerock/journey-client/types';
8+
9+
export default function textComponent(
10+
journeyEl: HTMLDivElement,
11+
callback: NameCallback,
12+
idx: number,
13+
) {
14+
const collectorKey = callback?.payload?.input?.[0].name || `collector-${idx}`;
15+
const label = document.createElement('label');
16+
const input = document.createElement('input');
17+
18+
label.htmlFor = collectorKey;
19+
label.innerText = callback.getPrompt();
20+
input.type = 'text';
21+
input.id = collectorKey;
22+
input.name = collectorKey;
23+
24+
journeyEl?.appendChild(label);
25+
journeyEl?.appendChild(input);
26+
27+
journeyEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
28+
callback.setName((event.target as HTMLInputElement).value);
29+
});
30+
}

e2e/journey-app/eslint.config.mjs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import baseConfig from '../../eslint.config.mjs';
2+
3+
export default [
4+
{
5+
ignores: [
6+
'node_modules',
7+
'*.md',
8+
'LICENSE',
9+
'.babelrc',
10+
'.env*',
11+
'.bin',
12+
'dist',
13+
'.eslintignore',
14+
'*.html',
15+
'*.svg',
16+
'*.css',
17+
'public',
18+
'*.json',
19+
'*.d.ts',
20+
],
21+
},
22+
...baseConfig,
23+
{
24+
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
25+
rules: {},
26+
},
27+
{
28+
files: ['*.ts', '*.tsx'],
29+
rules: {},
30+
},
31+
{
32+
files: ['*.js', '*.jsx'],
33+
rules: {},
34+
},
35+
];

e2e/journey-app/helper.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
export function dotToCamelCase(str: string) {
8+
return str
9+
.split('.')
10+
.map((part: string, index: number) =>
11+
index === 0 ? part.toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(),
12+
)
13+
.join('');
14+
}

e2e/journey-app/index.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + TS</title>
8+
</head>
9+
<body>
10+
<div id="app">
11+
<div id="section">
12+
<a href="https://vitejs.dev" target="_blank">
13+
<img src="./public/vite.svg" class="logo" alt="Vite logo" />
14+
</a>
15+
<a href="https://www.typescriptlang.org/" target="_blank">
16+
<img src="./public/typescript.svg" class="logo vanilla" alt="TypeScript logo" />
17+
</a>
18+
<form id="form">
19+
<div id="error"></div>
20+
<div id="journey"></div>
21+
</form>
22+
</div>
23+
</div>
24+
<script type="module" src="main.ts"></script>
25+
</body>
26+
</html>

e2e/journey-app/main.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import './style.css';
8+
9+
import { journey } from '@forgerock/journey-client';
10+
11+
import type {
12+
RequestMiddleware,
13+
NameCallback,
14+
PasswordCallback,
15+
} from '@forgerock/journey-client/types';
16+
17+
import textComponent from './components/text.js';
18+
import passwordComponent from './components/password.js';
19+
import { serverConfigs } from './server-configs.js';
20+
21+
const qs = window.location.search;
22+
const searchParams = new URLSearchParams(qs);
23+
24+
const config = serverConfigs[searchParams.get('clientId') || 'basic'];
25+
26+
const requestMiddleware: RequestMiddleware[] = [
27+
(req, action, next) => {
28+
switch (action.type) {
29+
case 'JOURNEY_START':
30+
if ((action.payload as any).type === 'service') {
31+
console.log('Starting authentication with service');
32+
}
33+
break;
34+
case 'JOURNEY_NEXT':
35+
if (!('type' in (action.payload as any))) {
36+
console.log('Continuing authentication with service');
37+
}
38+
break;
39+
}
40+
next();
41+
},
42+
];
43+
44+
(async () => {
45+
const journeyClient = await journey({ config, requestMiddleware });
46+
47+
const errorEl = document.getElementById('error') as HTMLDivElement;
48+
const formEl = document.getElementById('form') as HTMLFormElement;
49+
const journeyEl = document.getElementById('journey') as HTMLDivElement;
50+
51+
let step = await journeyClient.start();
52+
53+
function renderComplete() {
54+
if (step?.type !== 'LoginSuccess') {
55+
throw new Error('Expected step to be defined and of type LoginSuccess');
56+
}
57+
58+
const session = step.getSessionToken();
59+
60+
journeyEl.innerHTML = `
61+
<h2>Complete</h2>
62+
<span>Session:</span>
63+
<pre data-testid="sessionToken" id="sessionToken">${session}</pre>
64+
<button type="button" id="logoutButton">Logout</button>
65+
`;
66+
67+
const loginBtn = document.getElementById('logoutButton') as HTMLButtonElement;
68+
loginBtn.addEventListener('click', async () => {
69+
await journeyClient.terminate();
70+
71+
console.log('Logout successful');
72+
73+
step = await journeyClient.start();
74+
75+
renderForm();
76+
});
77+
}
78+
79+
function renderError() {
80+
if (step?.type !== 'LoginFailure') {
81+
throw new Error('Expected step to be defined and of type LoginFailure');
82+
}
83+
84+
const error = step.payload.message;
85+
if (errorEl) {
86+
errorEl.innerHTML = `
87+
<pre>${error}</pre>
88+
`;
89+
}
90+
}
91+
92+
// Represents the main render function for app
93+
async function renderForm() {
94+
journeyEl.innerHTML = '';
95+
96+
if (step?.type !== 'Step') {
97+
throw new Error('Expected step to be defined and of type Step');
98+
}
99+
100+
const formName = step.getHeader();
101+
102+
const header = document.createElement('h2');
103+
header.innerText = formName || '';
104+
journeyEl.appendChild(header);
105+
106+
const callbacks = step.callbacks;
107+
108+
callbacks.forEach((callback, idx) => {
109+
if (callback.getType() === 'NameCallback') {
110+
const cb = callback as NameCallback;
111+
textComponent(
112+
journeyEl, // You can ignore this; it's just for rendering
113+
cb, // This callback class
114+
idx,
115+
);
116+
} else if (callback.getType() === 'PasswordCallback') {
117+
const cb = callback as PasswordCallback;
118+
passwordComponent(
119+
journeyEl, // You can ignore this; it's just for rendering
120+
cb, // This callback class
121+
idx,
122+
);
123+
}
124+
});
125+
126+
const submitBtn = document.createElement('button');
127+
submitBtn.type = 'submit';
128+
submitBtn.id = 'submitButton';
129+
submitBtn.innerText = 'Submit';
130+
journeyEl.appendChild(submitBtn);
131+
}
132+
133+
formEl.addEventListener('submit', async (event) => {
134+
event.preventDefault();
135+
136+
if (step?.type !== 'Step') {
137+
throw new Error('Expected step to be defined and of type Step');
138+
}
139+
140+
/**
141+
* We can just call `next` here and not worry about passing any arguments
142+
*/
143+
step = await journeyClient.next(step);
144+
145+
/**
146+
* Recursively render the form with the new state
147+
*/
148+
if (step?.type === 'Step') {
149+
renderForm();
150+
} else if (step?.type === 'LoginSuccess') {
151+
console.log('Basic login successful');
152+
renderComplete();
153+
} else if (step?.type === 'LoginFailure') {
154+
renderForm();
155+
renderError();
156+
} else {
157+
console.error('Unknown node status', step);
158+
}
159+
});
160+
161+
if (step?.type !== 'LoginSuccess') {
162+
renderForm();
163+
} else {
164+
renderComplete();
165+
}
166+
})();

e2e/journey-app/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "@forgerock/journey-app",
3+
"version": "0.0.0",
4+
"description": "Ping Journey Client Test App",
5+
"type": "module",
6+
"private": true,
7+
"nx": {
8+
"tags": ["scope:e2e"]
9+
},
10+
"scripts": {
11+
"build": "pnpm nx nxBuild",
12+
"lint": "pnpm nx nxLint",
13+
"preview": "pnpm nx nxPreview",
14+
"serve": "pnpm nx nxServe"
15+
},
16+
"dependencies": {
17+
"@forgerock/journey-client": "workspace:*",
18+
"@forgerock/oidc-client": "workspace:*",
19+
"@forgerock/sdk-logger": "workspace:*"
20+
}
21+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>OK</p>

0 commit comments

Comments
 (0)