@@ -12,7 +12,7 @@ import { FetchBaseQueryError } from '@reduxjs/toolkit/query/react';
1212import type { logger as loggerFn } from '@forgerock/sdk-logger' ;
1313
1414import type { ClientStore , RootState } from './client.store.utils.js' ;
15- import type { PollingStatus , InternalErrorResponse } from './client.types.js' ;
15+ import type { NodeStates , PollingStatus , InternalErrorResponse } from './client.types.js' ;
1616import type { PollingCollector } from './collector.types.js' ;
1717
1818import { createInternalError , isInternalError } from './client.store.utils.js' ;
@@ -227,9 +227,62 @@ export function interpretChallengeResponse(
227227 return 'timedOut' ;
228228}
229229
230+ /**
231+ * Shape returned from one iteration of continue polling — the latest node and the
232+ * next PollingCollector the server wants us to use (or null if the flow advanced).
233+ */
234+ export interface PollingContinuation {
235+ node : NodeStates ;
236+ nextPollingCollector : PollingCollector | null ;
237+ }
238+
239+ /**
240+ * Pure snapshot of the current node and whether the server still wants polling.
241+ * The caller decides whether to loop based on `nextPollingCollector`.
242+ */
243+ export function evaluatePollingContinuation ( rootState : RootState ) : PollingContinuation {
244+ const node = nodeSlice . selectSlice ( rootState ) ;
245+ const { state : collectors } = nodeSlice . selectors . selectCollectors ( rootState ) ;
246+
247+ let nextPollingCollector : PollingCollector | null = null ;
248+ for ( const c of collectors ?? [ ] ) {
249+ if ( c . type === 'PollingCollector' ) {
250+ nextPollingCollector = c ;
251+ break ;
252+ }
253+ }
254+
255+ return { node, nextPollingCollector } ;
256+ }
257+
258+ /**
259+ * Stamps the PollingCollector's input.value, dispatches `next`, and resolves with
260+ * the resulting NodeStates. The value is what `transformSubmitRequest` inspects to
261+ * set `eventType: 'polling'` on the wire.
262+ */
263+ function advanceFlowµ ( {
264+ store,
265+ collectorId,
266+ pollingValue,
267+ } : {
268+ store : ReturnType < ClientStore > ;
269+ collectorId : string ;
270+ pollingValue : string ;
271+ } ) : Micro . Micro < NodeStates , InternalErrorResponse > {
272+ return Micro . sync ( ( ) =>
273+ store . dispatch ( nodeSlice . actions . update ( { id : collectorId , value : pollingValue } ) ) ,
274+ ) . pipe (
275+ Micro . flatMap ( ( ) =>
276+ Micro . promise ( ( ) => store . dispatch ( davinciApi . endpoints . next . initiate ( undefined ) ) ) ,
277+ ) ,
278+ Micro . map ( ( ) => nodeSlice . selectSlice ( store . getState ( ) ) ) ,
279+ ) ;
280+ }
281+
230282/**
231283 * Builds a Micro effect for the challenge polling branch.
232- * validate → dispatch → repeat → interpret → lift errors
284+ * validate → dispatch poll endpoint → repeat until complete → interpret →
285+ * on terminal status, dispatch next and resolve with the resulting NodeStates.
233286 */
234287function challengePollingµ ( {
235288 collector,
@@ -241,7 +294,7 @@ function challengePollingµ({
241294 challenge : string ;
242295 store : ReturnType < ClientStore > ;
243296 log : ReturnType < typeof loggerFn > ;
244- } ) : Micro . Micro < PollingStatus , InternalErrorResponse > {
297+ } ) : Micro . Micro < NodeStates , InternalErrorResponse > {
245298 const maxRetries = collector . output . config . pollRetries ?? 60 ;
246299 const pollInterval = collector . output . config . pollInterval ?? 2000 ;
247300
@@ -263,24 +316,43 @@ function challengePollingµ({
263316 schedule : Micro . scheduleSpaced ( pollInterval ) ,
264317 } ) ,
265318 Micro . map ( ( response ) => interpretChallengeResponse ( response , log ) ) ,
266- Micro . flatMap ( ( result ) =>
267- isInternalError ( result ) ? Micro . fail ( result ) : Micro . succeed ( result ) ,
268- ) ,
319+ Micro . flatMap ( ( result ) : Micro . Micro < NodeStates , InternalErrorResponse > => {
320+ if ( isInternalError ( result ) ) {
321+ return Micro . fail ( result ) ;
322+ }
323+ if ( result === 'timedOut' || result === 'error' ) {
324+ return Micro . fail ( createInternalError ( `Challenge polling ${ result } ` , 'unknown_error' ) ) ;
325+ }
326+ return advanceFlowµ ( { store, collectorId : collector . id , pollingValue : result } ) ;
327+ } ) ,
269328 ) ;
270329}
271330
272331/**
273332 * Builds a Micro effect for the continue polling branch.
274- * If retries remain, delays by pollInterval then returns 'continue'.
275- * If retries are exhausted, returns 'timedOut' immediately .
333+ * Each iteration: sleep → dispatch next → re-read state. Repeats while the server
334+ * keeps returning a PollingCollector on a 'continue' node; stops once the flow advances .
276335 */
277- function continuePollingµ (
278- mode : Extract < PollingMode , { _tag : 'continue' } > ,
279- ) : Micro . Micro < PollingStatus , InternalErrorResponse > {
280- if ( mode . retriesRemaining <= 0 ) {
281- return Micro . succeed ( 'timedOut' as PollingStatus ) ;
282- }
283- return Micro . sleep ( mode . pollInterval ) . pipe ( Micro . map ( ( ) => 'continue' ) ) ;
336+ function continuePollingµ ( {
337+ collector,
338+ store,
339+ } : {
340+ collector : PollingCollector ;
341+ store : ReturnType < ClientStore > ;
342+ } ) : Micro . Micro < NodeStates , InternalErrorResponse > {
343+ const intervalMs = collector . output . config . pollInterval ?? 2000 ;
344+
345+ return Micro . sleep ( intervalMs ) . pipe (
346+ Micro . flatMap ( ( ) =>
347+ advanceFlowµ ( { store, collectorId : collector . id , pollingValue : 'continue' } ) ,
348+ ) ,
349+ Micro . map ( ( ) => evaluatePollingContinuation ( store . getState ( ) ) ) ,
350+ Micro . repeat ( {
351+ while : ( { node, nextPollingCollector } ) =>
352+ node . status === 'continue' && nextPollingCollector !== null ,
353+ } ) ,
354+ Micro . map ( ( { node } ) => node ) ,
355+ ) ;
284356}
285357
286358/**
@@ -297,13 +369,13 @@ export function pollingµ({
297369 collector : PollingCollector ;
298370 store : ReturnType < ClientStore > ;
299371 log : ReturnType < typeof loggerFn > ;
300- } ) : Micro . Micro < PollingStatus , InternalErrorResponse > {
372+ } ) : Micro . Micro < NodeStates , InternalErrorResponse > {
301373 if ( mode . _tag === 'challenge' ) {
302374 return challengePollingµ ( { collector, challenge : mode . challenge , store, log } ) ;
303375 }
304376
305377 if ( mode . _tag === 'continue' ) {
306- return continuePollingµ ( mode ) ;
378+ return continuePollingµ ( { collector , store } ) ;
307379 }
308380
309381 return Micro . fail (
0 commit comments