Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #152 +/- ##
=============================================
+ Coverage 43.98% 44.16% +0.17%
- Complexity 1231 1252 +21
=============================================
Files 306 308 +2
Lines 9255 9367 +112
Branches 1326 1375 +49
=============================================
+ Hits 4071 4137 +66
- Misses 4638 4650 +12
- Partials 546 580 +34
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
vibhorgoswami
left a comment
There was a problem hiding this comment.
One comment related to bitmap extraction.
f1ebb03 to
20d807d
Compare
79e1f3c to
8adc3d7
Compare
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 45 minutes and 9 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis PR introduces two new DaVinci collectors— Changes
Sequence Diagram(s)sequenceDiagram
participant Client as PollingCollector
participant Delay as Async Delay
participant Server as HTTP Server
participant Flow as Flow<PollingStatus>
Client->>Delay: Wait pollInterval ms
Delay-->>Client: Delay complete
Client->>Server: POST challenge status (if pollChallengeStatus)
Server-->>Client: Response (200 or error)
alt Non-200 Response
Client->>Flow: emit Expired
Client->>Client: Stop polling
else 200 OK, isChallengeComplete=true
Client->>Flow: emit Complete(status)
Client->>Client: Stop polling
else 200 OK, isChallengeComplete=false
Client->>Flow: emit Continue(retryCount, maxRetries)
Client->>Client: Decrement retriesAllowed
alt Retries Exhausted
Client->>Flow: emit TimedOut
Client->>Client: Stop polling
else Retries Remaining
Client->>Delay: Wait pollInterval ms (loop)
end
else Exception
Client->>Flow: emit Error(exception)
Client->>Client: Stop polling
end
sequenceDiagram
participant Client as Client
participant Handler as Transform
participant Context as FlowContext
participant Factory as CollectorFactory
participant Node as ContinueNode
Client->>Handler: POST (rewindStateToLastRenderedUI)
Handler->>Context: Retrieve existing ContinueNode via CONTINUE_NODE key
alt ContinueNode Exists
Handler->>Handler: Extract context, daVinci, input, collectors from existing node
Handler->>Handler: Create new Connector(reused fields)
Handler->>Factory: inject(Connector, new collectors)
Factory-->>Handler: Connector ready
Handler-->>Client: Return new ContinueNode
else ContinueNode Missing
Handler-->>Client: Fail with IllegalStateException
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (4)
davinci/src/main/kotlin/com/pingidentity/davinci/collector/QRCodeCollector.kt (1)
1-6:⚠️ Potential issue | 🟡 MinorCopyright header should cover 2025–2026.
As per coding guidelines ("Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation").
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@davinci/src/main/kotlin/com/pingidentity/davinci/collector/QRCodeCollector.kt` around lines 1 - 6, Update the file header in QRCodeCollector.kt so the MIT license copyright line covers "2025–2026 Ping Identity Corporation" instead of just 2026; edit the top comment block (the file header comment) to list the years 2025–2026 to comply with the project guideline that every new source file includes the 2025–2026 header.samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/QRCode.kt (1)
1-6:⚠️ Potential issue | 🟡 MinorCopyright header should cover 2025–2026.
Same header concern as the
samples/appcopy. As per coding guidelines ("Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation"), use2025 - 2026.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/QRCode.kt` around lines 1 - 6, Update the source file header in QRCode.kt to use the required copyright range "2025 - 2026" instead of the current year; locate the file/class QRCode.kt and replace the existing copyright line in the MIT license header so it reads "Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved." ensuring the rest of the license text remains unchanged.samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Polling.kt (1)
1-6:⚠️ Potential issue | 🟡 MinorCopyright header should cover 2025–2026.
Same as other new files in this PR.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Polling.kt` around lines 1 - 6, Update the file header in Polling.kt to use the correct copyright range 2025–2026 instead of just 2026; edit the top comment block (the file-level header comment) so the line that currently reads "Copyright (c) 2026 Ping Identity Corporation." becomes "Copyright (c) 2025–2026 Ping Identity Corporation." to match other new files in the PR.samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt (1)
1-6:⚠️ Potential issue | 🟡 MinorCopyright header should cover 2025–2026.
As per coding guidelines ("Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation").
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt` around lines 1 - 6, Update the copyright header in Polling.kt to cover the 2025–2026 range: replace the current "2026" year in the top comment block with "2025–2026" (use an en dash) so the MIT license header matches the project's required range.
🧹 Nitpick comments (4)
davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt (1)
38-38: Prefer JUnit 4's@Beforeoverkotlin.test.BeforeTestin JUnit-based tests.Coding guidelines specify JUnit 4 for unit tests.
kotlin.test.BeforeTesthappens to resolve to@org.junit.Beforeon the JVM target, but directly usingorg.junit.Beforeis clearer and consistent with the rest of the JUnit 4 imports (org.junit.Test,org.junit.Assert.*) used in this file.♻️ Suggested change
-import kotlin.test.BeforeTest +import org.junit.Before @@ - `@BeforeTest` + `@Before` fun setUp() {As per coding guidelines: "Use JUnit 4, MockK (io.mockk:mockk), and Robolectric for Android-specific classes in unit tests".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt` at line 38, Replace the kotlin.test.BeforeTest import with JUnit 4's org.junit.Before in the PollingCollectorTest; update any setup method annotated with `@BeforeTest` (e.g., setUp in class PollingCollectorTest) to use `@Before` so the file consistently uses JUnit 4 annotations alongside existing org.junit.Test and Assert imports.davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt (1)
214-226:pollRetries.toInt()can throw duringinit().
initalready tolerates missing fields with string defaults, but if the server ever supplies a non-numericpollRetriesvalue,toInt()throwsNumberFormatExceptionduring initialization — unlike thepoll()body which usestoIntOrNull()/toLongOrNull()with fallback defaults. Consider using the same defensive conversion here for consistency.♻️ Suggested change
- retriesAllowed = pollRetries.toInt() + retriesAllowed = pollRetries.toIntOrNull() ?: 60🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt` around lines 214 - 226, The init method currently sets retriesAllowed = pollRetries.toInt() which can throw NumberFormatException for non-numeric input; change the conversion to a defensive form (use pollRetries.toIntOrNull() with a fallback like 60) and assign retriesAllowed from that safe value so initialization never throws. Update the PollingCollector.init function to parse pollRetries defensively (mirror the poll() behavior) before assigning retriesAllowed, keeping pollRetries as the original string and using the safe parsed integer as the counter.samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt (1)
44-83: Redundant coroutine +Jobtracking;LaunchedEffectalready handles cancellation.
LaunchedEffect(field)runs the block in a scope that is cancelled automatically when the composable leaves composition or whenfieldchanges, so the innerlaunch { … }wrapper and the mirroringDisposableEffect(field) { onDispose { pollingJob?.cancel() } }do not add cancellation guarantees — they add an extra coroutine layer and redundant state. Runningfield.poll().collect { … }directly inside theLaunchedEffectis simpler and has identical lifecycle semantics. Note also thatstatusMessageassignments inside the terminal branches are never observable because the wholeColumnis hidden onceisPolling = falseandonNext()navigates away.Proposed simplification
- var isPolling by remember { mutableStateOf(true) } - var statusMessage by remember { mutableStateOf("Polling...") } - var pollingJob: Job? by remember { mutableStateOf(null) } - - LaunchedEffect(field) { - pollingJob = launch { - field.poll().collect { status -> - when (status) { - is PollingStatus.Continue -> { - statusMessage = "Polling... (${status.retryCount}/${status.maxRetries})" - } - is PollingStatus.Complete -> { - isPolling = false - onNext() - } - is PollingStatus.TimedOut -> { - isPolling = false - statusMessage = "Polling timedOut" - onNext() - } - is PollingStatus.Error -> { - isPolling = false - statusMessage = "Error: ${status.exception.message}" - onNext() - } - - is PollingStatus.Expired -> { - isPolling = false - statusMessage = "Polling Expired" - onNext() - } - - } - } - } - } - - // Cancel polling when the composable is disposed - DisposableEffect(field) { - onDispose { - pollingJob?.cancel() - isPolling = false - } - } + var isPolling by remember { mutableStateOf(true) } + var statusMessage by remember { mutableStateOf("Polling...") } + + LaunchedEffect(field) { + field.poll().collect { status -> + when (status) { + is PollingStatus.Continue -> + statusMessage = "Polling... (${status.retryCount}/${status.maxRetries})" + is PollingStatus.Complete, + is PollingStatus.TimedOut, + is PollingStatus.Error, + is PollingStatus.Expired -> { + isPolling = false + onNext() + } + } + } + }Imports for
Job,launch, andDisposableEffectcan then be dropped.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt` around lines 44 - 83, Remove the redundant inner coroutine and manual Job tracking: inside the LaunchedEffect(field) block call field.poll().collect { ... } directly instead of launching a new coroutine and assigning pollingJob; drop the pollingJob variable and the DisposableEffect(field) cleanup since LaunchedEffect already cancels on disposal or key changes. Keep the same handling for PollingStatus branches (Continue, Complete, TimedOut, Error, Expired) but remove the external isPolling/pollingJob cancellation logic and any imports only used for Job/launch/DisposableEffect; leave onNext(), statusMessage and isPolling assignments as-is where needed.davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt (1)
14-17: Switch to JUnit 4 and Robolectric for proper bitmap decoding tests.This file uses
kotlin.testbut should use JUnit 4 with@RunWith(RobolectricTestRunner::class)per coding guidelines. More critically,QRCodeCollector.bitmap()callsandroid.graphics.BitmapFactory.decodeByteArray(). On a plain JVM, this unmocked Android stub throwsRuntimeException("Stub!"), which the try-catch inbitmap()swallows and returnsnull. This causes the "valid Base64 but invalid image data" and "only prefix" test cases to pass silently without actually exercising the real decode path.Migrate to Robolectric to shadow
BitmapFactoryand make these assertions meaningful. Consider adding a positive test that decodes a real PNG byte sequence and asserts a non-nullBitmapis returned.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt` around lines 14 - 17, Replace kotlin.test usage with JUnit4 + Robolectric: annotate the test class with `@RunWith`(RobolectricTestRunner::class) and use org.junit.Test and org.junit.Assert methods instead of kotlin.test; this ensures android.graphics.BitmapFactory is shadowed. Update imports and test runner for QRCodeCollectorTest so QRCodeCollector.bitmap() calls hit Robolectric's BitmapFactory rather than the JVM stub. Add a positive test that supplies a valid PNG byte array (real image bytes encoded as Base64) to QRCodeCollector.bitmap() and assert the returned Bitmap is non-null using Assert.assertNotNull, and keep the existing negative tests but ensure they now exercise the real decode path. Ensure unique references: QRCodeCollector.bitmap(), QRCodeCollectorTest class, and the test method names are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt`:
- Around line 1-6: Update the MIT license header year range in newly added
source files (e.g., PollingCollector.kt and PollingCollectorTest.kt) from "2026"
to "2025 - 2026"; find the top-of-file comment block and replace the single year
with the range "2025 - 2026" so every new source file follows the required
copyright header format.
- Around line 351-356: The catch block in PollingCollector.kt currently swallows
CancellationException (catch (e: Exception)) which prevents cooperative
coroutine cancellation; update the try/catch inside the polling loop (the block
around emit(PollingStatus.Error(e)) in class PollingCollector / function
handling the poll) to either rethrow when the caught exception is a
kotlinx.coroutines.CancellationException (if (e is CancellationException) throw
e) before logging/emitting, or narrow the catch to non-cancellation exception
types (e.g., IOException, JsonParseException); also add the import
kotlinx.coroutines.CancellationException if you choose the explicit rethrow
approach.
- Around line 303-307: The current PollingCollector code builds pollingUrl by
deriving baseUrl with selfHref.substringBefore("/davinci/connections"), which is
brittle and can return the full href when the delimiter is missing; replace this
with robust URL parsing (e.g., use java.net.URL) to extract the origin/authority
from selfHref (or explicitly validate that substringBefore found the delimiter
before using it) and then construct pollingUrl as
"<origin>/davinci/user/credentials/challenge/$challenge/status"; update
references to selfHref, baseUrl and pollingUrl in the PollingCollector code to
use the parsed origin or throw/log a clear error when selfHref is invalid.
In `@davinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.kt`:
- Around line 121-135: Replace the unchecked cast on the retrieved ContinueNode
with a safe cast to Connector: change how you obtain existing (use as? Connector
with a null check returning FailureNode) so ClassCastException cannot be thrown;
update the Connector construction to either use the current active context
(context) if rewind should not reuse the old FlowContext, or keep
existing.context but add an explicit comment above the creation explaining the
intentional rewind semantics (preserving old cookies/headers/tokens) so the
behavior is clear; target symbols: CONTINUE_NODE, ContinueNode, Connector,
existing.context and CollectorFactory.inject.
In `@davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt`:
- Around line 1-6: Update the file header to use the correct copyright year
range "2025-2026" instead of "2026"; replace the first comment block in
davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt so the
MIT license header begins with "Copyright (c) 2025-2026 Ping Identity
Corporation. All rights reserved."; ensure the rest of the header text (license
reference lines) remains unchanged.
In
`@samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/QRCode.kt`:
- Around line 1-6: Update the file header in QRCode.kt to use the required
copyright year range "2025–2026" instead of "2026"; specifically change the
top-of-file MIT license header comment in QRCode.kt so the copyright line reads
"Copyright (c) 2025–2026 Ping Identity Corporation. All rights reserved." while
keeping the rest of the license text unchanged.
In `@samples/app/src/main/java/com/pingidentity/samples/app/davinci/DaVinci.kt`:
- Around line 117-122: Replace the direct android.util.Log usage inside the
key(node) composition block in DaVinci.kt with the project's Logger interface
from foundation/logger: find the Log.e("DaVinci", ...) call and swap it for the
Logger instance (use its error method and preserve the same message/context),
ensuring imports are updated and no android.util.Log is referenced; also update
the file header copyright range string "2024-2026" to "2025-2026" to match the
new source file guideline.
---
Duplicate comments:
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/QRCodeCollector.kt`:
- Around line 1-6: Update the file header in QRCodeCollector.kt so the MIT
license copyright line covers "2025–2026 Ping Identity Corporation" instead of
just 2026; edit the top comment block (the file header comment) to list the
years 2025–2026 to comply with the project guideline that every new source file
includes the 2025–2026 header.
In
`@samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Polling.kt`:
- Around line 1-6: Update the file header in Polling.kt to use the correct
copyright range 2025–2026 instead of just 2026; edit the top comment block (the
file-level header comment) so the line that currently reads "Copyright (c) 2026
Ping Identity Corporation." becomes "Copyright (c) 2025–2026 Ping Identity
Corporation." to match other new files in the PR.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt`:
- Around line 1-6: Update the copyright header in Polling.kt to cover the
2025–2026 range: replace the current "2026" year in the top comment block with
"2025–2026" (use an en dash) so the MIT license header matches the project's
required range.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/QRCode.kt`:
- Around line 1-6: Update the source file header in QRCode.kt to use the
required copyright range "2025 - 2026" instead of the current year; locate the
file/class QRCode.kt and replace the existing copyright line in the MIT license
header so it reads "Copyright (c) 2025 - 2026 Ping Identity Corporation. All
rights reserved." ensuring the rest of the license text remains unchanged.
---
Nitpick comments:
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt`:
- Around line 214-226: The init method currently sets retriesAllowed =
pollRetries.toInt() which can throw NumberFormatException for non-numeric input;
change the conversion to a defensive form (use pollRetries.toIntOrNull() with a
fallback like 60) and assign retriesAllowed from that safe value so
initialization never throws. Update the PollingCollector.init function to parse
pollRetries defensively (mirror the poll() behavior) before assigning
retriesAllowed, keeping pollRetries as the original string and using the safe
parsed integer as the counter.
In `@davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt`:
- Line 38: Replace the kotlin.test.BeforeTest import with JUnit 4's
org.junit.Before in the PollingCollectorTest; update any setup method annotated
with `@BeforeTest` (e.g., setUp in class PollingCollectorTest) to use `@Before` so
the file consistently uses JUnit 4 annotations alongside existing org.junit.Test
and Assert imports.
In `@davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt`:
- Around line 14-17: Replace kotlin.test usage with JUnit4 + Robolectric:
annotate the test class with `@RunWith`(RobolectricTestRunner::class) and use
org.junit.Test and org.junit.Assert methods instead of kotlin.test; this ensures
android.graphics.BitmapFactory is shadowed. Update imports and test runner for
QRCodeCollectorTest so QRCodeCollector.bitmap() calls hit Robolectric's
BitmapFactory rather than the JVM stub. Add a positive test that supplies a
valid PNG byte array (real image bytes encoded as Base64) to
QRCodeCollector.bitmap() and assert the returned Bitmap is non-null using
Assert.assertNotNull, and keep the existing negative tests but ensure they now
exercise the real decode path. Ensure unique references:
QRCodeCollector.bitmap(), QRCodeCollectorTest class, and the test method names
are updated accordingly.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.kt`:
- Around line 44-83: Remove the redundant inner coroutine and manual Job
tracking: inside the LaunchedEffect(field) block call field.poll().collect { ...
} directly instead of launching a new coroutine and assigning pollingJob; drop
the pollingJob variable and the DisposableEffect(field) cleanup since
LaunchedEffect already cancels on disposal or key changes. Keep the same
handling for PollingStatus branches (Continue, Complete, TimedOut, Error,
Expired) but remove the external isPolling/pollingJob cancellation logic and any
imports only used for Job/launch/DisposableEffect; leave onNext(), statusMessage
and isPolling assignments as-is where needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3dde2656-fa58-4e9a-ad32-ac809a6ff41f
📒 Files selected for processing (17)
davinci/src/main/kotlin/com/pingidentity/davinci/CollectorRegistry.ktdavinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.ktdavinci/src/main/kotlin/com/pingidentity/davinci/collector/QRCodeCollector.ktdavinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.ktdavinci/src/test/kotlin/com/pingidentity/davinci/CollectorRegistryTest.ktdavinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.Response.ktdavinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.ktdavinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.ktdavinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.ktsamples/app/src/main/java/com/pingidentity/samples/app/davinci/DaVinci.ktsamples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/ContinueNode.ktsamples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Polling.ktsamples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/QRCode.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/DaVinci.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/DaVinciContinueNode.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/Polling.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/QRCode.kt
| /* | ||
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
License header year range should be 2025 - 2026.
As per coding guidelines: "Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation". The same issue applies to the new PollingCollectorTest.kt and any other newly added files.
📝 Proposed header fix
/*
- * Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+ * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* | |
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ | |
| /* | |
| * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt`
around lines 1 - 6, Update the MIT license header year range in newly added
source files (e.g., PollingCollector.kt and PollingCollectorTest.kt) from "2026"
to "2025 - 2026"; find the top-of-file comment block and replace the single year
with the range "2025 - 2026" so every new source file follows the required
copyright header format.
| if (selfHref != null && interactionId != null) { | ||
| // Construct the polling endpoint URL | ||
| // Example: https://auth.pingone.ca/env-id/davinci/user/credentials/challenge/abc123/status | ||
| val baseUrl = selfHref.substringBefore("/davinci/connections") | ||
| val pollingUrl = "$baseUrl/davinci/user/credentials/challenge/$challenge/status" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for any server-provided polling/status link in sample responses and docs
rg -nP -C2 '"(poll|status|challenge)"\s*:\s*\{'
rg -nP -C2 'davinci/user/credentials/challenge'Repository: ForgeRock/ping-android-sdk
Length of output: 5348
🏁 Script executed:
#!/bin/bash
# Read the PollingCollector.kt to understand the context around lines 303-307
echo "=== PollingCollector.kt context (lines 280-320) ==="
sed -n '280,320p' davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt | cat -nRepository: ForgeRock/ping-android-sdk
Length of output: 2531
🏁 Script executed:
#!/bin/bash
# Read the test file to see what selfHref values are being tested
echo "=== PollingCollectorTest.kt ==="
cat -n davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt | head -200Repository: ForgeRock/ping-android-sdk
Length of output: 8588
🏁 Script executed:
#!/bin/bash
# Search for where selfHref comes from and how it's obtained
echo "=== Search for selfHref assignment/origin ==="
rg -n "selfHref\s*=" davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt -B5 -A2Repository: ForgeRock/ping-android-sdk
Length of output: 612
🏁 Script executed:
#!/bin/bash
# Search for other test cases and selfHref patterns
echo "=== Other test cases with selfHref ==="
rg -n "href" davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt -B2 -A2Repository: ForgeRock/ping-android-sdk
Length of output: 1606
🏁 Script executed:
#!/bin/bash
# Check full PollingCollectorTest to see all test cases
echo "=== Full PollingCollectorTest.kt ==="
wc -l davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt
tail -150 davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.ktRepository: ForgeRock/ping-android-sdk
Length of output: 5717
🏁 Script executed:
#!/bin/bash
# Check for any URL parsing utilities or helpers in the codebase
echo "=== Look for URL parsing utilities ==="
rg -n "substringBefore|substringAfter|URL\.parse|URI|toURL" davinci/src/main/kotlin/ --type kt | head -20Repository: ForgeRock/ping-android-sdk
Length of output: 152
🏁 Script executed:
#!/bin/bash
# Search for URL parsing utilities without the problematic type flag
echo "=== Look for URL parsing utilities ==="
rg -n "substringBefore|substringAfter|URL|URI" davinci/src/main/kotlin/com/pingidentity/davinci/ | head -30Repository: ForgeRock/ping-android-sdk
Length of output: 2184
🏁 Script executed:
#!/bin/bash
# Check if there are any other selfHref patterns in actual API responses or docs
echo "=== Check for API response examples or documentation ==="
fd -e json -e md davinci/src/ | xargs grep -l "davinci/connections\|davinci/user/credentials" | head -10Repository: ForgeRock/ping-android-sdk
Length of output: 497
🏁 Script executed:
#!/bin/bash
# Read the actual test to understand what URL the test expects after parsing
echo "=== Full test case showing URL construction ==="
sed -n '135,193p' davinci/src/test/kotlin/com/pingidentity/davinci/PollingCollectorTest.kt | cat -nRepository: ForgeRock/ping-android-sdk
Length of output: 2869
Brittle base-URL extraction via substringBefore.
The selfHref.substringBefore("/davinci/connections") approach silently fails if the URL structure changes. If /davinci/connections is absent, substringBefore returns the entire href unchanged, producing a malformed polling URL. Use proper URL parsing (e.g., java.net.URL) to extract the origin/authority, or validate that the substring operation succeeded before constructing the polling endpoint.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt`
around lines 303 - 307, The current PollingCollector code builds pollingUrl by
deriving baseUrl with selfHref.substringBefore("/davinci/connections"), which is
brittle and can return the full href when the delimiter is missing; replace this
with robust URL parsing (e.g., use java.net.URL) to extract the origin/authority
from selfHref (or explicitly validate that substringBefore found the delimiter
before using it) and then construct pollingUrl as
"<origin>/davinci/user/credentials/challenge/$challenge/status"; update
references to selfHref, baseUrl and pollingUrl in the PollingCollector code to
use the parsed origin or throw/log a clear error when selfHref is invalid.
| } catch (e: Exception) { | ||
| // Handle network errors, JSON parsing errors, or other exceptions | ||
| davinci.config.logger.w("Error polling challenge status", e) | ||
| emit(PollingStatus.Error(e)) | ||
| shouldContinuePolling = false // Exit the polling loop on exception | ||
| } |
There was a problem hiding this comment.
catch (Exception) swallows CancellationException and breaks coroutine cancellation.
kotlinx.coroutines.CancellationException extends Exception, so this catch block will intercept cancellation, emit PollingStatus.Error, and suppress cooperative cancellation of the surrounding viewModelScope/lifecycleScope. If the user navigates away mid-poll, the flow will continue to leak an error emission instead of cancelling cleanly.
Either rethrow CancellationException explicitly or narrow the catch.
🔒 Proposed fix
- } catch (e: Exception) {
+ } catch (e: CancellationException) {
+ throw e
+ } catch (e: Exception) {
// Handle network errors, JSON parsing errors, or other exceptions
davinci.config.logger.w("Error polling challenge status", e)
emit(PollingStatus.Error(e))
shouldContinuePolling = false // Exit the polling loop on exception
}Remember to import kotlinx.coroutines.CancellationException.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (e: Exception) { | |
| // Handle network errors, JSON parsing errors, or other exceptions | |
| davinci.config.logger.w("Error polling challenge status", e) | |
| emit(PollingStatus.Error(e)) | |
| shouldContinuePolling = false // Exit the polling loop on exception | |
| } | |
| } catch (e: CancellationException) { | |
| throw e | |
| } catch (e: Exception) { | |
| // Handle network errors, JSON parsing errors, or other exceptions | |
| davinci.config.logger.w("Error polling challenge status", e) | |
| emit(PollingStatus.Error(e)) | |
| shouldContinuePolling = false // Exit the polling loop on exception | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt`
around lines 351 - 356, The catch block in PollingCollector.kt currently
swallows CancellationException (catch (e: Exception)) which prevents cooperative
coroutine cancellation; update the try/catch inside the polling loop (the block
around emit(PollingStatus.Error(e)) in class PollingCollector / function
handling the poll) to either rethrow when the caught exception is a
kotlinx.coroutines.CancellationException (if (e is CancellationException) throw
e) before logging/emitting, or narrow the catch to non-cancellation exception
types (e.g., IOException, JsonParseException); also add the import
kotlinx.coroutines.CancellationException if you choose the explicit rethrow
approach.
| val eventName = json["eventName"]?.jsonPrimitive?.content | ||
| if (eventName == "rewindStateToLastRenderedUI" || eventName == "rewindStateToSpecificRenderedUI") { | ||
| val existing = context.flowContext.getValue<ContinueNode>(CONTINUE_NODE) | ||
| ?: return FailureNode(IllegalStateException("Rewind state to last rendered UI failed.")) | ||
| // Create a new Connector instance with the same so that Jetpack Compose | ||
| // sees a different object reference and triggers recomposition and its collectors. | ||
| return Connector( | ||
| existing.context, | ||
| (existing as Connector).daVinci, | ||
| existing.input, | ||
| existing.collectors, | ||
| ).apply { | ||
| CollectorFactory.inject(this) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how CONTINUE_NODE is written and read, and whether any non-Connector ContinueNode is ever stored.
rg -nP --type=kotlin -C3 '\bCONTINUE_NODE\b'
rg -nP --type=kotlin -C3 'flowContext\[CONTINUE_NODE\]|getValue<ContinueNode>'
# Find all subclasses/producers of ContinueNode in the davinci module
ast-grep --pattern $'class $_ : ContinueNode($$$)'
ast-grep --pattern $'class $_($$$) : ContinueNode($$$)'Repository: ForgeRock/ping-android-sdk
Length of output: 4933
🏁 Script executed:
# Find ContinueNode definition and all implementations
rg -nP --type=kotlin 'interface ContinueNode|class.*ContinueNode|sealed.*ContinueNode'
# Find Connector class definition and constructor
rg -nP --type=kotlin -B5 -A10 'class Connector'
# Find where daVinci is sourced in Transform.kt
rg -nP --type=kotlin -B10 -A10 'Transform.kt.*daVinci'
# Look at the Connector constructor specifically
fd -e kt -x grep -l "class Connector" {} \;Repository: ForgeRock/ping-android-sdk
Length of output: 3307
🏁 Script executed:
# Read the Connector.kt node hook where CONTINUE_NODE is stored
cat -n davinci/src/main/kotlin/com/pingidentity/davinci/module/Connector.kt | sed -n '135,150p'
# Read the full Transform.kt context around the rewind logic
cat -n davinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.kt | sed -n '110,140p'
# Find the Transform function signature to see where daVinci/workflow comes from
rg -nP --type=kotlin -B5 'fun transform' davinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.kt | head -40Repository: ForgeRock/ping-android-sdk
Length of output: 2022
🏁 Script executed:
# Get full Transform function signature
cat -n davinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.kt | sed -n '104,115p'
# Check ContinueNode definition in orchestrate
cat -n foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/Node.kt | sed -n '32,50p'
# Search for all ContinueNode subclasses across the entire codebase
rg -nP --type=kotlin ': ContinueNode\(' --glob='!**/test/**'Repository: ForgeRock/ping-android-sdk
Length of output: 1562
🏁 Script executed:
# Check if journey module defines its own CONTINUE_NODE
rg -nP --type=kotlin 'CONTINUE_NODE' journey/
# Verify that the CONTINUE_NODE key is davinci-specific
grep -r "CONTINUE_NODE" davinci/src/main/kotlin/ | grep -E 'const|val'Repository: ForgeRock/ping-android-sdk
Length of output: 364
🏁 Script executed:
# Check how Connector.context is used internally
rg -nP --type=kotlin -A5 'this.context|existing.context' davinci/src/main/kotlin/com/pingidentity/davinci/module/Connector.kt
# Look at how ContinueNode.next() calls workflow.next() with its context
cat -n foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/Node.kt | sed -n '38,60p'
# Check what happens after a node is returned from transform — how is it used
rg -nP --type=kotlin -B3 -A3 'transform(context|Transform.transform' davinci/src/main/kotlin/com/pingidentity/davinci/module/ | head -50Repository: ForgeRock/ping-android-sdk
Length of output: 877
🏁 Script executed:
# Find where transform() is called and how its return value is used
rg -nP --type=kotlin 'transform\(' davinci/src/main/kotlin/com/pingidentity/davinci/module/
# Check if there's any documentation or tests showing the rewind context intent
fd -e kt davinci/src/test -exec grep -l "rewind\|Rewind" {} \;Repository: ForgeRock/ping-android-sdk
Length of output: 581
🏁 Script executed:
# Find where transform() is called and understand the flow
rg -nP --type=kotlin -B5 -A5 'return@transform transform' davinci/src/main/kotlin/
# Search for rewind in davinci test files
find davinci/src/test -name "*.kt" -type f -exec grep -l "rewind\|Rewind" {} \;
# Check what workflow.next() does when passed the node's context
rg -nP --type=kotlin 'fun next\(' foundation/orchestrate/ --glob='!**/test/**'Repository: ForgeRock/ping-android-sdk
Length of output: 2018
🏁 Script executed:
# Check the test files for rewind behavior
cat davinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.kt | head -100
# Also check the Response test file
cat davinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.Response.kt | head -100Repository: ForgeRock/ping-android-sdk
Length of output: 8223
🏁 Script executed:
# Verify the context usage: when a Connector calls next(), what context does it use?
rg -nP --type=kotlin -A10 'suspend fun next' foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/Node.kt
# Check how workflow.next() uses the context parameter
cat -n foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/Workflow.kt | sed -n '145,160p'
# Look for any tests involving rewind to understand intent
rg -nP --type=kotlin 'rewindState' davinci/src/test/Repository: ForgeRock/ping-android-sdk
Length of output: 2713
🏁 Script executed:
# Read the rewind test to understand the expected behavior
cat -n davinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.kt | sed -n '557,600p'
# Also read the second rewind test
cat -n davinci/src/test/kotlin/com/pingidentity/davinci/DaVinciTest.kt | sed -n '601,640p'Repository: ForgeRock/ping-android-sdk
Length of output: 4514
Use safe cast for Connector instead of unchecked cast.
The existing as Connector cast will throw ClassCastException if CONTINUE_NODE ever holds a ContinueNode that isn't a Connector. While only Connector is currently stored in the davinci module, this is not enforced at compile time. Adopt the safe cast pattern already used in ErrorNode.kt (line 32) for consistency and defensiveness:
val existing = context.flowContext.getValue<ContinueNode>(CONTINUE_NODE) as? Connector
?: return FailureNode(IllegalStateException("Rewind state to last rendered UI failed."))Additionally, confirm that reusing existing.context (the old FlowContext snapshot) is the intended behavior for rewind semantics. The new Connector is constructed with the previous context, which means subsequent next() calls will operate against that captured context state rather than the current active context. This intentionally preserves the old state (including cookies, headers, PAR tokens) at the cost of divergence. If this is by design, add an explicit comment explaining the rewind semantics; if not, pass context (the current active context) to the new Connector.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@davinci/src/main/kotlin/com/pingidentity/davinci/module/Transform.kt` around
lines 121 - 135, Replace the unchecked cast on the retrieved ContinueNode with a
safe cast to Connector: change how you obtain existing (use as? Connector with a
null check returning FailureNode) so ClassCastException cannot be thrown; update
the Connector construction to either use the current active context (context) if
rewind should not reuse the old FlowContext, or keep existing.context but add an
explicit comment above the creation explaining the intentional rewind semantics
(preserving old cookies/headers/tokens) so the behavior is clear; target
symbols: CONTINUE_NODE, ContinueNode, Connector, existing.context and
CollectorFactory.inject.
| /* | ||
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Copyright header year range should be 2025-2026.
As per coding guidelines, "Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation".
📝 Proposed fix
-/*
- * Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+/*
+ * Copyright (c) 2025-2026 Ping Identity Corporation. All rights reserved.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* | |
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ | |
| /* | |
| * Copyright (c) 2025-2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt`
around lines 1 - 6, Update the file header to use the correct copyright year
range "2025-2026" instead of "2026"; replace the first comment block in
davinci/src/test/kotlin/com/pingidentity/davinci/QRCodeCollectorTest.kt so the
MIT license header begins with "Copyright (c) 2025-2026 Ping Identity
Corporation. All rights reserved."; ensure the rest of the header text (license
reference lines) remains unchanged.
| /* | ||
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Copyright header should cover 2025–2026.
As per coding guidelines ("Every new source file must include the MIT license header with copyright 2025-2026 Ping Identity Corporation"), new files should use the 2025 - 2026 range.
Proposed fix
-/*
- * Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+/*
+ * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* | |
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ | |
| /* | |
| * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/QRCode.kt`
around lines 1 - 6, Update the file header in QRCode.kt to use the required
copyright year range "2025–2026" instead of "2026"; specifically change the
top-of-file MIT license header comment in QRCode.kt so the copyright line reads
"Copyright (c) 2025–2026 Ping Identity Corporation. All rights reserved." while
keeping the rest of the license text unchanged.
| key(node) { | ||
| Render(node = node, onNodeUpdated, onStart) { | ||
| onNext(node) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm rewind reuses the original ContinueNode instance rather than building a new one
fd -t f 'Transform.kt' davinci --exec cat {}Repository: ForgeRock/ping-android-sdk
Length of output: 6144
🏁 Script executed:
fd -t f -p 'samples/app' 'DaVinci.kt'Repository: ForgeRock/ping-android-sdk
Length of output: 167
🏁 Script executed:
# Also check the structure to find the correct path
git ls-files | grep -i 'davinci.*\.kt' | head -20Repository: ForgeRock/ping-android-sdk
Length of output: 1502
🏁 Script executed:
git ls-files | grep 'samples/app.*[Dd]a[Vv]inci'Repository: ForgeRock/ping-android-sdk
Length of output: 2259
🏁 Script executed:
# Let's search more broadly for the samples app
git ls-files | grep 'samples.*\.kt$' | head -30Repository: ForgeRock/ping-android-sdk
Length of output: 2443
🏁 Script executed:
cat -n samples/app/src/main/java/com/pingidentity/samples/app/davinci/DaVinci.kt | sed -n '110,130p'Repository: ForgeRock/ping-android-sdk
Length of output: 858
🏁 Script executed:
# Also check the copyright header
head -10 samples/app/src/main/java/com/pingidentity/samples/app/davinci/DaVinci.ktRepository: ForgeRock/ping-android-sdk
Length of output: 349
🏁 Script executed:
# Check the Connector class to understand its relationship to ContinueNode
cat samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/ContinueNode.ktRepository: ForgeRock/ping-android-sdk
Length of output: 5265
Address logging violation and copyright year issue before approving.
The key(node) approach is correct for forcing recomposition on rewind. However, there are two issues:
-
Line 125: Replace
Log.e("DaVinci", ...)with the Logger interface fromfoundation/loggerper guidelines (do not useandroid.util.Logdirectly). -
Copyright header: Update to "2025-2026" instead of "2024-2026" per coding guidelines for new source files.
The keying works because the rewind logic in Transform.kt deliberately creates a new ContinueNode instance (the comment states: "Create a new Connector instance... so that Jetpack Compose sees a different object reference and triggers recomposition"), so the key detects the changed reference and resets composition state as intended.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@samples/app/src/main/java/com/pingidentity/samples/app/davinci/DaVinci.kt`
around lines 117 - 122, Replace the direct android.util.Log usage inside the
key(node) composition block in DaVinci.kt with the project's Logger interface
from foundation/logger: find the Log.e("DaVinci", ...) call and swap it for the
Logger instance (use its error method and preserve the same message/context),
ensuring imports are updated and no android.util.Log is referenced; also update
the file header copyright range string "2024-2026" to "2025-2026" to match the
new source file guideline.
… flows - Implement `PollingCollector` to handle asynchronous operations like push notifications and OOB authentication, supporting both simple delays and active challenge status polling. - Implement `QRCodeCollector` to handle Base64-encoded QR code images for device pairing and MFA setup. - Add Compose UI components (`Polling.kt`, `QRCode.kt`) and integrate them into `ContinueNode`. - Register new collectors in `CollectorRegistry`. - Add comprehensive unit tests for `PollingCollector` and `QRCodeCollector` logic.
JIRA Ticket
SDKS-4679
SDKS-4681
Description
PollingCollectorto handle asynchronous operations like push notifications and OOB authentication, supporting both simple delays and active challenge status polling.QRCodeCollectorto handle Base64-encoded QR code images for device pairing and MFA setup.Polling.kt,QRCode.kt) and integrate them intoContinueNode.CollectorRegistry.PollingCollectorandQRCodeCollectorlogic.Summary by CodeRabbit
Release Notes