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,42 +73,20 @@ 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 ) ;
6885 errorEl . textContent = message ;
6986 return ;
7087 }
7188 let step = await journeyClient . start ( { journey : journeyName } ) ;
72-
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- }
89+ let webAuthnUsedInThisJourney = false ;
10090
10191 function renderError ( ) {
10292 if ( step ?. type !== 'LoginFailure' ) {
@@ -130,6 +120,18 @@ if (searchParams.get('middleware') === 'true') {
130120
131121 const submitForm = ( ) => formEl . requestSubmit ( ) ;
132122
123+ const webAuthnStep = WebAuthn . getWebAuthnStepType ( step ) ;
124+ const isWebAuthn =
125+ webAuthnStep === WebAuthnStepType . Authentication ||
126+ webAuthnStep === WebAuthnStepType . Registration ;
127+
128+ if ( isWebAuthn ) {
129+ webAuthnUsedInThisJourney = true ;
130+ await webauthnComponent ( journeyEl , step , 0 ) ;
131+ submitForm ( ) ;
132+ return ; // prevent the rest of the function from running
133+ }
134+
133135 const stepRendered =
134136 renderQRCodeStep ( journeyEl , step ) || renderRecoveryCodesStep ( journeyEl , step ) ;
135137
@@ -145,6 +147,60 @@ if (searchParams.get('middleware') === 'true') {
145147 journeyEl . appendChild ( submitBtn ) ;
146148 }
147149
150+ function renderComplete ( ) {
151+ if ( step ?. type !== 'LoginSuccess' ) {
152+ throw new Error ( 'Expected step to be defined and of type LoginSuccess' ) ;
153+ }
154+
155+ const session = step . getSessionToken ( ) ;
156+
157+ console . log ( `Session Token: ${ session || 'none' } ` ) ;
158+
159+ journeyEl . innerHTML = `
160+ <h2 id="completeHeader">Complete</h2>
161+ <span id="sessionLabel">Session:</span>
162+ <pre id="sessionToken" id="sessionToken">${ session } </pre>
163+ <button type="button" id="logoutButton">Logout</button>
164+ ` ;
165+
166+ const logoutBtn = document . getElementById ( 'logoutButton' ) as HTMLButtonElement ;
167+ const sessionLabelEl = document . getElementById ( 'sessionLabel' ) as HTMLSpanElement ;
168+
169+ if ( webAuthnUsedInThisJourney || step . getSessionToken ( ) ) {
170+ renderDeleteDevicesSection (
171+ journeyEl ,
172+ ( ) => storeDevicesBeforeSession ( config ) ,
173+ ( ) => deleteDevicesInSession ( config ) ,
174+ ( ) => deleteAllDevices ( config ) ,
175+ ) ;
176+
177+ const getDevicesButton = document . getElementById ( 'getDevicesButton' ) as HTMLButtonElement ;
178+ const deleteDevicesButton = document . getElementById (
179+ 'deleteDevicesButton' ,
180+ ) as HTMLButtonElement ;
181+ const deleteAllDevicesButton = document . getElementById (
182+ 'deleteAllDevicesButton' ,
183+ ) as HTMLButtonElement ;
184+ const deviceStatus = document . getElementById ( 'deviceStatus' ) as HTMLPreElement ;
185+
186+ journeyEl . insertBefore ( getDevicesButton , sessionLabelEl ) ;
187+ journeyEl . insertBefore ( deleteDevicesButton , sessionLabelEl ) ;
188+ journeyEl . insertBefore ( deleteAllDevicesButton , sessionLabelEl ) ;
189+ journeyEl . insertBefore ( deviceStatus , sessionLabelEl ) ;
190+ }
191+
192+ logoutBtn . addEventListener ( 'click' , async ( ) => {
193+ await journeyClient . terminate ( ) ;
194+
195+ console . log ( 'Logout successful' ) ;
196+
197+ webAuthnUsedInThisJourney = false ;
198+ step = await journeyClient . start ( { journey : journeyName } ) ;
199+
200+ renderForm ( ) ;
201+ } ) ;
202+ }
203+
148204 formEl . addEventListener ( 'submit' , async ( event ) => {
149205 event . preventDefault ( ) ;
150206
0 commit comments