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' ) ;
@@ -134,6 +123,16 @@ if (searchParams.get('middleware') === 'true') {
134123 renderQRCodeStep ( journeyEl , step ) || renderRecoveryCodesStep ( journeyEl , step ) ;
135124
136125 if ( ! stepRendered ) {
126+ const webAuthnStep = WebAuthn . getWebAuthnStepType ( step ) ;
127+ const isWebAuthn =
128+ webAuthnStep === WebAuthnStepType . Authentication ||
129+ webAuthnStep === WebAuthnStepType . Registration ;
130+ if ( isWebAuthn ) {
131+ await webauthnComponent ( journeyEl , step , 0 ) ;
132+ submitForm ( ) ;
133+ return ; // prevent the rest of the function from running
134+ }
135+
137136 const callbacks = step . callbacks ;
138137 renderCallbacks ( journeyEl , callbacks , submitForm ) ;
139138 }
@@ -145,6 +144,55 @@ if (searchParams.get('middleware') === 'true') {
145144 journeyEl . appendChild ( submitBtn ) ;
146145 }
147146
147+ function renderComplete ( ) {
148+ if ( step ?. type !== 'LoginSuccess' ) {
149+ throw new Error ( 'Expected step to be defined and of type LoginSuccess' ) ;
150+ }
151+
152+ const session = step . getSessionToken ( ) ;
153+
154+ console . log ( `Session Token: ${ session || 'none' } ` ) ;
155+
156+ journeyEl . innerHTML = `
157+ <h2 id="completeHeader">Complete</h2>
158+ <span id="sessionLabel">Session:</span>
159+ <pre id="sessionToken" id="sessionToken">${ session } </pre>
160+ <button type="button" id="logoutButton">Logout</button>
161+ ` ;
162+
163+ const logoutBtn = document . getElementById ( 'logoutButton' ) as HTMLButtonElement ;
164+ const sessionLabelEl = document . getElementById ( 'sessionLabel' ) as HTMLSpanElement ;
165+
166+ renderDeleteDevicesSection (
167+ journeyEl ,
168+ ( ) => storeDevicesBeforeSession ( config ) ,
169+ ( ) => deleteDevicesInSession ( config ) ,
170+ ( ) => deleteAllDevices ( config ) ,
171+ ) ;
172+
173+ const getDevicesButton = document . getElementById ( 'getDevicesButton' ) as HTMLButtonElement ;
174+ const deleteDevicesButton = document . getElementById ( 'deleteDevicesButton' ) as HTMLButtonElement ;
175+ const deleteAllDevicesButton = document . getElementById (
176+ 'deleteAllDevicesButton' ,
177+ ) as HTMLButtonElement ;
178+ const deviceStatus = document . getElementById ( 'deviceStatus' ) as HTMLPreElement ;
179+
180+ journeyEl . insertBefore ( getDevicesButton , sessionLabelEl ) ;
181+ journeyEl . insertBefore ( deleteDevicesButton , sessionLabelEl ) ;
182+ journeyEl . insertBefore ( deleteAllDevicesButton , sessionLabelEl ) ;
183+ journeyEl . insertBefore ( deviceStatus , sessionLabelEl ) ;
184+
185+ logoutBtn . addEventListener ( 'click' , async ( ) => {
186+ await journeyClient . terminate ( ) ;
187+
188+ console . log ( 'Logout successful' ) ;
189+
190+ step = await journeyClient . start ( { journey : journeyName } ) ;
191+
192+ renderForm ( ) ;
193+ } ) ;
194+ }
195+
148196 formEl . addEventListener ( 'submit' , async ( event ) => {
149197 event . preventDefault ( ) ;
150198
0 commit comments