@@ -183,8 +183,9 @@ export async function handleChallengePolling({
183183 retriesLeft -- ;
184184 return store . dispatch (
185185 davinciApi . endpoints . poll . initiate ( {
186- challengeEndpoint,
186+ endpoint : challengeEndpoint ,
187187 interactionId,
188+ mode : 'challenge' ,
188189 } ) ,
189190 ) ;
190191 } ) ;
@@ -272,3 +273,190 @@ export async function handleChallengePolling({
272273 } ;
273274 }
274275}
276+
277+ export async function handleContinuePolling ( {
278+ collector,
279+ store,
280+ log,
281+ } : {
282+ collector : PollingCollector ;
283+ store : ReturnType < ClientStore > ;
284+ log : ReturnType < typeof loggerFn > ;
285+ } ) : Promise < ContinueNode | InternalErrorResponse > {
286+ const rootState : RootState = store . getState ( ) ;
287+ const serverSlice = nodeSlice . selectors . selectServer ( rootState ) ;
288+
289+ if ( serverSlice === null ) {
290+ log . error ( 'No server info found for poll operation' ) ;
291+ return {
292+ error : {
293+ message : 'No server info found for poll operation' ,
294+ type : 'state_error' ,
295+ } ,
296+ type : 'internal_error' ,
297+ } ;
298+ }
299+
300+ if ( isGenericError ( serverSlice ) ) {
301+ log . error ( serverSlice . message ?? serverSlice . error ) ;
302+ return {
303+ error : {
304+ message : serverSlice . message ?? 'Failed to retrieve server info for poll operation' ,
305+ type : 'internal_error' ,
306+ } ,
307+ type : 'internal_error' ,
308+ } ;
309+ }
310+
311+ if ( serverSlice . status !== 'continue' ) {
312+ return {
313+ error : {
314+ message : 'Not in a continue node state, must be in a continue node to use poll method' ,
315+ type : 'state_error' ,
316+ } ,
317+ } as InternalErrorResponse ;
318+ }
319+
320+ // Get the continue polling endpoint
321+ const links = serverSlice . _links ;
322+ if ( ! links || ! ( 'next' in links ) || ! ( 'href' in links [ 'next' ] ) || ! links [ 'next' ] . href ) {
323+ return {
324+ error : {
325+ message : 'No next link found in server info for continue polling operation' ,
326+ type : 'internal_error' ,
327+ } ,
328+ } as InternalErrorResponse ;
329+ }
330+
331+ const nextUrl = links [ 'next' ] . href ;
332+
333+ const interactionId = serverSlice . interactionId ;
334+ if ( ! interactionId ) {
335+ return {
336+ error : {
337+ message : 'Missing interactionId in server info for challenge polling' ,
338+ type : 'internal_error' ,
339+ } ,
340+ } as InternalErrorResponse ;
341+ }
342+
343+ // Start continue polling
344+ let retriesLeft = collector . output . config . pollRetries ?? 60 ;
345+ const pollInterval = collector . output . config . pollInterval ?? 2000 ; // miliseconds
346+
347+ const updateµ = Micro . try ( {
348+ try : ( ) => {
349+ // Update the polling collector input value to 'continue' or 'timedOut'. We will call
350+ // continue polling endpoint (_links.next.href) with this data in queryµ
351+ if ( retriesLeft > 0 ) {
352+ return store . dispatch (
353+ nodeSlice . actions . update ( { id : collector . id , value : 'continue' as PollingStatus } ) ,
354+ ) ;
355+ } else {
356+ return store . dispatch (
357+ nodeSlice . actions . update ( { id : collector . id , value : 'timedOut' as PollingStatus } ) ,
358+ ) ;
359+ }
360+ } ,
361+ catch : ( err ) => {
362+ const errorMessage = err instanceof Error ? err . message : String ( err ) ;
363+ return {
364+ type : 'internal_error' ,
365+ error : { message : errorMessage , type : 'internal_error' } ,
366+ } as InternalErrorResponse ;
367+ } ,
368+ } ) ;
369+
370+ const queryµ = Micro . promise ( ( ) => {
371+ retriesLeft -- ;
372+ return store . dispatch (
373+ davinciApi . endpoints . poll . initiate ( {
374+ endpoint : nextUrl ,
375+ interactionId,
376+ mode : 'continue' ,
377+ } ) ,
378+ ) ;
379+ } ) ;
380+
381+ const repeatµ = updateµ . pipe ( Micro . flatMap ( ( ) => queryµ ) ) ;
382+
383+ const continuePollµ = Micro . repeat ( repeatµ , {
384+ while : ( { data, error } ) =>
385+ retriesLeft > 0 &&
386+ ! error &&
387+ [ 'rewindStateToLastRenderedUI' , 'rewindStateToSpecificRenderedUI' ] . includes (
388+ ( data as Record < string , unknown > ) [ 'eventName' ] as string ,
389+ ) ,
390+ schedule : Micro . scheduleSpaced ( pollInterval ) ,
391+ } ) . pipe (
392+ Micro . flatMap ( ( { data, error } ) => {
393+ const pollResponse = data as Record < string , unknown > ;
394+ console . log ( 'Continue poll response' , pollResponse ) ;
395+
396+ if ( error ) {
397+ // SerializedError
398+ let message = 'An unknown error occurred while challenge polling' ;
399+ if ( 'message' in error && error . message ) {
400+ message = error . message ;
401+ return Micro . fail ( {
402+ error : {
403+ message,
404+ type : 'unknown_error' ,
405+ } ,
406+ type : 'internal_error' ,
407+ } as InternalErrorResponse ) ;
408+ }
409+
410+ // FetchBaseQueryError
411+ if ( 'status' in error ) {
412+ return Micro . fail ( {
413+ error : {
414+ message : 'An unknown error occured during continue polling' ,
415+ type : 'unknown_error' ,
416+ } ,
417+ type : 'internal_error' ,
418+ } as InternalErrorResponse ) ;
419+ }
420+ }
421+
422+ if ( retriesLeft <= 0 ) {
423+ // Note: when retries are exhausted, DaVinci currently returns another continue polling response instead of an error
424+ return Micro . fail ( {
425+ error : {
426+ message : 'Continue polling timed out' ,
427+ type : 'internal_error' ,
428+ } ,
429+ type : 'internal_error' ,
430+ } as InternalErrorResponse ) ;
431+ }
432+
433+ // TODO: If there are still retries left but we did not find a rewind event then polling succeeded and the next node was returned
434+ // return the next node here
435+
436+ // Just in case no polling status was determined
437+ return Micro . fail ( {
438+ error : {
439+ message : 'Unknown error occurred during continue polling' ,
440+ type : 'unknown_error' ,
441+ } ,
442+ type : 'internal_error' ,
443+ } as InternalErrorResponse ) ;
444+ } ) ,
445+ ) ;
446+
447+ const result = await Micro . runPromiseExit ( continuePollµ ) ;
448+
449+ if ( exitIsSuccess ( result ) ) {
450+ return result . value ;
451+ } else if ( exitIsFail ( result ) ) {
452+ return result . cause . error ;
453+ } else {
454+ return {
455+ error : {
456+ message : result . cause . message ,
457+ type : 'unknown_error' ,
458+ } ,
459+ type : 'internal_error' ,
460+ } ;
461+ }
462+ }
0 commit comments