11/*
2- * Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
2+ * Copyright (c) 2025-2026 Ping Identity Corporation. All rights reserved.
33 *
44 * This software may be modified and distributed under the terms
55 * of the MIT license. See the LICENSE file for details.
66 */
77import './style.css' ;
88
99import { journey } from '@forgerock/journey-client' ;
10+ import { WebAuthn , WebAuthnStepType } from '@forgerock/journey-client/webauthn' ;
1011
11- import type { JourneyClient , RequestMiddleware } from '@forgerock/journey-client/types' ;
12+ import type {
13+ JourneyClient ,
14+ JourneyClientConfig ,
15+ RequestMiddleware ,
16+ } from '@forgerock/journey-client/types' ;
1217
1318import { renderCallbacks } from './callback-map.js' ;
19+ import { renderDeleteDevicesSection } from './components/webauthn-devices.js' ;
1420import { renderQRCodeStep } from './components/qr-code.js' ;
1521import { renderRecoveryCodesStep } from './components/recovery-codes.js' ;
22+ import {
23+ deleteAllDevices ,
24+ deleteDevicesInSession ,
25+ storeDevicesBeforeSession ,
26+ } from './services/delete-webauthn-devices.js' ;
27+ import { webauthnComponent } from './components/webauthn.js' ;
1628import { serverConfigs } from './server-configs.js' ;
1729
1830const qs = window . location . search ;
@@ -61,7 +73,12 @@ if (searchParams.get('middleware') === 'true') {
6173
6274 let journeyClient : JourneyClient ;
6375 try {
64- journeyClient = await journey ( { config : config , requestMiddleware } ) ;
76+ const journeyConfig : JourneyClientConfig = {
77+ serverConfig : {
78+ wellknown : config . serverConfig . wellknown ,
79+ } ,
80+ } ;
81+ journeyClient = await journey ( { config : journeyConfig , requestMiddleware } ) ;
6582 } catch ( error ) {
6683 const message = error instanceof Error ? error . message : 'Unknown error' ;
6784 console . error ( 'Failed to initialize journey client:' , message ) ;
@@ -70,34 +87,6 @@ if (searchParams.get('middleware') === 'true') {
7087 }
7188 let step = await journeyClient . start ( { journey : journeyName } ) ;
7289
73- function renderComplete ( ) {
74- if ( step ?. type !== 'LoginSuccess' ) {
75- throw new Error ( 'Expected step to be defined and of type LoginSuccess' ) ;
76- }
77-
78- const session = step . getSessionToken ( ) ;
79-
80- console . log ( `Session Token: ${ session || 'none' } ` ) ;
81-
82- journeyEl . innerHTML = `
83- <h2 id="completeHeader">Complete</h2>
84- <span id="sessionLabel">Session:</span>
85- <pre id="sessionToken" id="sessionToken">${ session } </pre>
86- <button type="button" id="logoutButton">Logout</button>
87- ` ;
88-
89- const loginBtn = document . getElementById ( 'logoutButton' ) as HTMLButtonElement ;
90- loginBtn . addEventListener ( 'click' , async ( ) => {
91- await journeyClient . terminate ( ) ;
92-
93- console . log ( 'Logout successful' ) ;
94-
95- step = await journeyClient . start ( { journey : journeyName } ) ;
96-
97- renderForm ( ) ;
98- } ) ;
99- }
100-
10190 function renderError ( ) {
10291 if ( step ?. type !== 'LoginFailure' ) {
10392 throw new Error ( 'Expected step to be defined and of type LoginFailure' ) ;
@@ -130,6 +119,17 @@ if (searchParams.get('middleware') === 'true') {
130119
131120 const submitForm = ( ) => formEl . requestSubmit ( ) ;
132121
122+ const webAuthnStep = WebAuthn . getWebAuthnStepType ( step ) ;
123+ const isWebAuthn =
124+ webAuthnStep === WebAuthnStepType . Authentication ||
125+ webAuthnStep === WebAuthnStepType . Registration ;
126+
127+ if ( isWebAuthn ) {
128+ await webauthnComponent ( journeyEl , step , 0 ) ;
129+ submitForm ( ) ;
130+ return ; // prevent the rest of the function from running
131+ }
132+
133133 const stepRendered =
134134 renderQRCodeStep ( journeyEl , step ) || renderRecoveryCodesStep ( journeyEl , step ) ;
135135
@@ -145,6 +145,55 @@ if (searchParams.get('middleware') === 'true') {
145145 journeyEl . appendChild ( submitBtn ) ;
146146 }
147147
148+ function renderComplete ( ) {
149+ if ( step ?. type !== 'LoginSuccess' ) {
150+ throw new Error ( 'Expected step to be defined and of type LoginSuccess' ) ;
151+ }
152+
153+ const session = step . getSessionToken ( ) ;
154+
155+ console . log ( `Session Token: ${ session || 'none' } ` ) ;
156+
157+ journeyEl . innerHTML = `
158+ <h2 id="completeHeader">Complete</h2>
159+ <span id="sessionLabel">Session:</span>
160+ <pre id="sessionToken" id="sessionToken">${ session } </pre>
161+ <button type="button" id="logoutButton">Logout</button>
162+ ` ;
163+
164+ const logoutBtn = document . getElementById ( 'logoutButton' ) as HTMLButtonElement ;
165+ const sessionLabelEl = document . getElementById ( 'sessionLabel' ) as HTMLSpanElement ;
166+
167+ renderDeleteDevicesSection (
168+ journeyEl ,
169+ ( ) => storeDevicesBeforeSession ( config ) ,
170+ ( ) => deleteDevicesInSession ( config ) ,
171+ ( ) => deleteAllDevices ( config ) ,
172+ ) ;
173+
174+ const getDevicesButton = document . getElementById ( 'getDevicesButton' ) as HTMLButtonElement ;
175+ const deleteDevicesButton = document . getElementById ( 'deleteDevicesButton' ) as HTMLButtonElement ;
176+ const deleteAllDevicesButton = document . getElementById (
177+ 'deleteAllDevicesButton' ,
178+ ) as HTMLButtonElement ;
179+ const deviceStatus = document . getElementById ( 'deviceStatus' ) as HTMLPreElement ;
180+
181+ journeyEl . insertBefore ( getDevicesButton , sessionLabelEl ) ;
182+ journeyEl . insertBefore ( deleteDevicesButton , sessionLabelEl ) ;
183+ journeyEl . insertBefore ( deleteAllDevicesButton , sessionLabelEl ) ;
184+ journeyEl . insertBefore ( deviceStatus , sessionLabelEl ) ;
185+
186+ logoutBtn . addEventListener ( 'click' , async ( ) => {
187+ await journeyClient . terminate ( ) ;
188+
189+ console . log ( 'Logout successful' ) ;
190+
191+ step = await journeyClient . start ( { journey : journeyName } ) ;
192+
193+ renderForm ( ) ;
194+ } ) ;
195+ }
196+
148197 formEl . addEventListener ( 'submit' , async ( event ) => {
149198 event . preventDefault ( ) ;
150199
0 commit comments