Skip to content

feat(rn-davinci): pooling and QRCode collectors (SDKS-5130) - #62

Open
tsdamas wants to merge 5 commits into
mainfrom
SDKS-5130
Open

feat(rn-davinci): pooling and QRCode collectors (SDKS-5130)#62
tsdamas wants to merge 5 commits into
mainfrom
SDKS-5130

Conversation

@tsdamas

@tsdamas tsdamas commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for two new DaVinci collector types that drive out-of-band authentication flows: the async PollingCollector (push approval, QR scan, email verification) and the display-only QRCodeCollector. Includes full native bridge implementations on Android and iOS, TypeScript client API, sample app UI, and E2E test coverage.

What's new

  • PollingCollector + pollStatus — New collector type on DaVinciCollector. DaVinciClient.pollStatus() streams PollingStatus events (continue | complete | timedOut | expired | error) mirroring iOS's AsyncStream<PollingStatus> and Android's Flow<PollingStatus>. Terminal statuses do not auto-advance the flow — callers must call next() explicitly to progress.
  • QRCodeCollector — Display-only collector (no label/required, doesn't extend BaseCollector, matching native Collector<Nothing>/Collector). Exposes content (data URI), fallbackText, and raw server field JSON.
  • New bridge methods: pollDaVinci (returns a native subscription id) and supporting event plumbing (RNPingDavinciEvents, iOS RNPingDavinciEventEmitterGate).
  • Sample app: DaVinciPollingField and DaVinciQrCodeField molecules, wired into DaVinciFieldRenderer/DaVinciContinueNodePanel, plus controller hook updates.
  • PingTestRunner: new DaVinci UI E2E tests (iOS DaVinciUITests.swift), BaseTestCase/TestEnvironment helpers, and a new davinci-polling integration test suit

Testing

  • Unit tests: RNPingDavinciCommonTest.kt, DaVinciNodeMapperTest.kt (Android); RNPingDavinciCommonTests.swift, DaVinciNodeMapperTests.swift (iOS); collectorHelpers.test.ts, createDaVinciClient.test.ts, davinciMethods.test.ts, useDavinci.test.tsx (TS).
  • Integration: davinci-polling.test.ts in PingTestRunner/__tests__/integration/.
  • E2E: DaVinciUITests.swift covering the new polling/QR scenario in PingTestRunner/scenarios/DaVinciScenario.tsx.

Docs

  • packages/davinci/README.md updated with usage for pollStatus, PollingCollector, and QRCodeCollector.

Summary by CodeRabbit

  • New Features

    • Added QR code collector support with image display and fallback text.
    • Added live polling status updates, including progress, loading, completion, and error states.
    • Added polling controls, collector selection, and unsubscribe support.
    • Polling automatically advances the flow when the step completes.
  • Bug Fixes

    • Improved handling and reporting of polling failures.
  • Documentation

    • Documented QR code rendering, polling behavior, limits, and status handling.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 471f6f28-153d-4392-bcf7-7a7584ae6b1d

📥 Commits

Reviewing files that changed from the base of the PR and between e86d780 and 85e9bf4.

📒 Files selected for processing (3)
  • packages/browser/android/build.gradle
  • packages/oidc/android/build.gradle
  • packages/oidc/ios/RNPingOidcCommon.swift

📝 Walkthrough

Walkthrough

This change adds POLLING and QR_CODE collector support across public types, JavaScript APIs, Android and iOS bridges, sample rendering, documentation, and tests. Native polling emits status events, while QR collectors provide image content or fallback text.

Changes

DaVinci polling and QR collectors

Layer / File(s) Summary
Public collector and polling API
packages/davinci/src/types/*, packages/davinci/src/davinci.ts, packages/davinci/src/davinciMethods.ts, packages/davinci/src/useDavinci.tsx, packages/davinci/src/NativeRNPingDavinci.ts, packages/davinci/src/__tests__/*
Adds collector types, field kinds, polling statuses, polling options, native methods, event constants, error handling, hook actions, subscription filtering, terminal cleanup, and API tests.
Android collector mapping and polling bridge
packages/davinci/android/src/main/*, packages/davinci/android/src/newarch/*, packages/davinci/android/src/oldarch/*, packages/davinci/android/src/test/*
Maps polling and QR collectors, starts tracked polling jobs, emits status events, cancels jobs during cleanup, and tests validation, event payloads, and lifecycle behavior.
iOS collector mapping and polling bridge
packages/davinci/ios/Mapper/*, packages/davinci/ios/RNPingDavinciCommon.swift, packages/davinci/ios/RNPingDavinci*.mm, packages/davinci/ios/RNPingDavinciEvents.swift, packages/davinci/ios/Tests/*
Maps collectors, tracks and cancels polling tasks, forwards native notifications, coordinates event-emitter ownership, and tests polling lifecycle and payloads.
Sample application polling and QR rendering
PingSampleApp/src/styles/*, PingSampleApp/ui/davinci/components/*, PingSampleApp/ui/davinci/hooks/*, PingTestRunner/scenarios/*
Adds QR image rendering, polling status rendering, collector dispatch, callback propagation, and flow advancement after terminal polling statuses.
Documentation, integration validation, and maintenance wiring
packages/davinci/README.md, PingTestRunner/__tests__/integration/*, PingTestRunner/ios/PingTestRunnerUITests/*, packages/davinci/RNPingDavinci.podspec, packages/davinci/android/build.gradle, packages/external-idp/android/build.gradle, .gitignore, */android/app/build.gradle
Documents collector behavior, adds integration and UI coverage, includes the iOS event-emitter source, adds Android test dependencies, documents a compile-only dependency, and updates formatting and ignore rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Mergeability Score: ⚪ Minimal · up to 85e9b

The PR adds DaVinci polling and QR-code collector support with native, TypeScript, sample-app, and test changes; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant DaVinciClient
  participant NativeBridge
  participant DeviceEventEmitter
  participant PollingRenderer
  DaVinciClient->>NativeBridge: start pollDaVinci(davinciId, options)
  NativeBridge-->>DaVinciClient: return subscription ID
  NativeBridge->>DeviceEventEmitter: emit polling status
  DeviceEventEmitter-->>DaVinciClient: deliver matching status
  DaVinciClient->>PollingRenderer: update polling display
Loading

Possibly related PRs

Suggested reviewers: pingidentity-gaurav

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the DaVinci collector feature, including QRCode support and the intended polling support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SDKS-5130

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.77393% with 106 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.88%. Comparing base (7b9dd1e) to head (85e9bf4).

Files with missing lines Patch % Lines
.../com/pingidentity/rndavinci/RNPingDavinciCommon.kt 0.00% 66 Missing ⚠️
...s/davinci/ios/Tests/RNPingDavinciCommonTests.swift 97.18% 12 Missing ⚠️
packages/oidc/ios/RNPingOidcCommon.swift 0.00% 11 Missing ⚠️
...pingidentity/rndavinci/mapper/DaVinciNodeMapper.kt 69.23% 6 Missing and 2 partials ⚠️
packages/davinci/ios/RNPingDavinciImpl.swift 0.00% 3 Missing ⚠️
.../com/pingidentity/rndavinci/RNPingDavinciModule.kt 0.00% 2 Missing ⚠️
packages/davinci/ios/RNPingDavinciCommon.swift 98.18% 2 Missing ⚠️
packages/davinci/src/NativeRNPingDavinci.ts 0.00% 1 Missing ⚠️
packages/davinci/src/useDavinci.tsx 66.66% 1 Missing ⚠️

❌ Your project check has failed because the head coverage (71.88%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main      #62      +/-   ##
============================================
+ Coverage     70.81%   71.88%   +1.06%     
- Complexity      187      194       +7     
============================================
  Files           193      167      -26     
  Lines         20297    19951     -346     
  Branches        887      715     -172     
============================================
- Hits          14374    14342      -32     
+ Misses         5796     5534     -262     
+ Partials        127       75      -52     
Flag Coverage Δ
android 17.14% <19.14%> (+0.07%) ⬆️
ios 81.06% <96.15%> (+0.70%) ⬆️
javascript 74.06% <95.55%> (+5.79%) ⬆️
Components Coverage Δ
Bridge glue 52.15% <87.09%> (+1.33%) ⬆️
Pure logic 81.52% <86.20%> (+0.13%) ⬆️
Files with missing lines Coverage Δ
...ackages/davinci/ios/Mapper/DaVinciNodeMapper.swift 94.89% <100.00%> (+0.62%) ⬆️
...ges/davinci/ios/Tests/DaVinciNodeMapperTests.swift 99.58% <100.00%> (+0.10%) ⬆️
packages/davinci/src/collectorHelpers.ts 93.12% <100.00%> (+0.32%) ⬆️
packages/davinci/src/davinci.ts 96.64% <100.00%> (+0.70%) ⬆️
packages/davinci/src/davinciMethods.ts 84.61% <100.00%> (+2.79%) ⬆️
packages/davinci/src/NativeRNPingDavinci.ts 95.83% <0.00%> (-4.17%) ⬇️
packages/davinci/src/useDavinci.tsx 82.27% <66.66%> (+3.33%) ⬆️
.../com/pingidentity/rndavinci/RNPingDavinciModule.kt 0.00% <0.00%> (ø)
packages/davinci/ios/RNPingDavinciCommon.swift 77.52% <98.18%> (+7.08%) ⬆️
packages/davinci/ios/RNPingDavinciImpl.swift 50.00% <0.00%> (-4.55%) ⬇️
... and 4 more

... and 26 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7b9dd1e...85e9bf4. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://ForgeRock.github.io/ping-react-native-sdk/docs-preview/pr-62/

Built to branch gh-pages at 2026-08-13 18:19 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (3)
packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciEvents.kt (1)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add KDoc for POLLING_STATUS.

POLLING_STATUS is a public declaration. Add KDoc directly above the constant.

As per coding guidelines: "Use KDoc /** */ on all public and internal declarations."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciEvents.kt`
at line 13, Add a KDoc comment directly above the public POLLING_STATUS constant
describing the polling status event, using the required /** */ syntax.

Source: Coding guidelines

packages/davinci/ios/Tests/RNPingDavinciCommonTests.swift (1)

1094-1115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Store the observation token to actually remove the block-based observer.

addObserver(forName:object:queue:using:) returns an opaque token. removeObserver(self) does not remove that registration, so each EventObserver leaves a live observation for the process lifetime. The weak self capture keeps behavior correct, but registrations accumulate across tests and every later emission invokes all of them.

Keep the returned token and remove it in deinit.

♻️ Proposed refactor
 private final class EventObserver: `@unchecked` Sendable {
   private let lock = NSLock()
   private var _events: [[String: Any]] = []
+  private var token: NSObjectProtocol?
   var onEvent: (([String: Any]) -> Void)?
@@
   init() {
-    NotificationCenter.default.addObserver(
+    token = NotificationCenter.default.addObserver(
       forName: .pingDavinciNativeEmit,
       object: nil,
       queue: nil
     ) { [weak self] notification in
@@
   deinit {
-    NotificationCenter.default.removeObserver(self)
+    if let token {
+      NotificationCenter.default.removeObserver(token)
+    }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/davinci/ios/Tests/RNPingDavinciCommonTests.swift` around lines 1094
- 1115, Update EventObserver’s init to store the token returned by
NotificationCenter.addObserver(forName:object:queue:using:) in a property, then
remove that stored token in deinit instead of self. Preserve the existing weak
capture and event-handling behavior.
packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciCommon.kt (1)

554-564: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the nullable if/else with ?.let { } ?:.

The coding guidelines forbid if (x != null) ... else ... for nullable handling in Android Kotlin sources. Lines 560-564 use that form on requestedKey. The option read on lines 554-558 can also use takeIf/let for consistency.

As per coding guidelines: "Never use if (x != null) ... else ... — use x?.let { } ?: instead".

♻️ Proposed refactor
-        val requestedKey = if (options.hasKey("key") && !options.isNull("key")) {
-            options.getString("key")
-        } else {
-            null
-        }
+        val requestedKey = options
+            .takeIf { it.hasKey("key") && !it.isNull("key") }
+            ?.getString("key")
         val collectors = node.actions.filterIsInstance<PollingCollector>()
-        val collector = (if (requestedKey != null) {
-            collectors.firstOrNull { it.id() == requestedKey }
-        } else {
-            collectors.firstOrNull()
-        })
+        val collector = requestedKey
+            ?.let { key -> collectors.firstOrNull { it.id() == key } }
+            ?: collectors.firstOrNull()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciCommon.kt`
around lines 554 - 564, Refactor the nullable handling in the collector
selection around requestedKey and the option read: replace the explicit
null-check if/else with takeIf/?.let and the Elvis fallback, while preserving
the existing behavior of selecting the requested collector or first collector.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/mapper/DaVinciNodeMapper.kt`:
- Around line 431-438: The polling mapper mapPollingCollector must always emit
numeric pollInterval and pollRetries values. Replace each toIntOrNull fallback
with the deterministic numeric fallback requested and call logWarning when
coercion fails; update DaVinciNodeMapperTest.kt lines 742-765 to assert the
numeric fallback and rename the test to describe numeric behavior.

In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciCommon.kt`:
- Around line 606-633: Update emitPollingStatus to be a suspend function and
replace its scope.launch wrapper with a suspend context switch to
Dispatchers.Main before emitting. Ensure callers invoke it from the existing
poll Job so cancellation propagates and queued events are dropped during dispose
or cleanup.

In `@packages/davinci/ios/RNPingDavinciCommon.swift`:
- Around line 508-522: Update the poll bookkeeping around the task creation flow
to key registrations by subscriptionId instead of a Task reference: remove
taskRef, register the task before starting its execution, and have completion
remove using davinciId and subscriptionId. Update PollJobStore’s storage and
register/remove/cancelAll/removeAll methods to manage inner
subscriptionId-to-task entries, with register overwriting defensively so an
early completion cannot reinsert a finished task.

In `@packages/davinci/ios/RNPingDavinciEventEmitterGate.mm`:
- Around line 33-45: Make event-emitter ownership bridge-scoped: update
RNPingDavinciEventEmitterGate.mm so RNPingDavinciClaimEventEmitterOwnership
tracks the active owner and provides synchronized release or handoff, declare
and document that API in RNPingDavinciEventEmitterGate.h, and call it when
removing observers in RNPingDavinci.mm and RNPingDavinciClassic.mm; ensure the
active bridge module can reclaim ownership after teardown or reload. Affected
sites: packages/davinci/ios/RNPingDavinciEventEmitterGate.mm lines 33-45,
packages/davinci/ios/RNPingDavinciEventEmitterGate.h lines 12-20,
packages/davinci/ios/RNPingDavinci.mm lines 35-53, and
packages/davinci/ios/RNPingDavinciClassic.mm lines 47-65.

In `@packages/davinci/ios/RNPingDavinciEvents.swift`:
- Around line 13-21: Document the public constants pollingStatus and
pingDavinciNativeEmit with concise triple-slash comments directly above each
declaration, describing the notification they represent; retain the existing
type and extension comments.

In `@packages/davinci/ios/Tests/RNPingDavinciCommonTests.swift`:
- Around line 785-798: Prevent repeated fulfillment in the event observers used
by the affected tests, including testCleanupCancelsAllOutstandingPollTasks.
Configure each first-event expectation with assertForOverFulfill disabled, or
clear observer.onEvent immediately after the first callback, while preserving
the existing expectation and polling behavior.

In `@packages/davinci/README.md`:
- Around line 336-353: Update the pollStatus README example and its accompanying
guidance to consistently document terminal-status handling: either call next()
for every listed terminal status or state that it applies only to complete. Keep
the example’s switch cases and unsubscribe behavior aligned with the documented
choice.
- Line 334: Update the README example’s client.next call to handle its returned
Promise, ensuring rejected advances are caught and surfaced rather than becoming
unhandled rejections. Preserve the explicit advancement behavior and use the
example’s existing error-handling approach if available.

In `@packages/davinci/src/index.tsx`:
- Around line 42-45: Remove the DaVinciEvents export from the package index so
this internal event-name constant is not exposed as public API. Leave the
internal events module and pollStatus listener usage unchanged.

In `@packages/davinci/src/useDavinci.tsx`:
- Around line 84-97: Update the pollStatus documentation to state that the
returned unsubscribe function only removes the local status listener and does
not cancel the active native poll. Keep the existing promise return type and
active PollingCollector error description unchanged.

In `@PingTestRunner/__tests__/integration/davinci-polling.test.ts`:
- Around line 159-190: Update client.pollStatus and its polling startup flow to
register DeviceEventEmitter listeners before awaiting or initiating pollDaVinci,
ensuring early complete, error, and status events reach onStatus. Modify the
makeMock setup in the “resolves subscriptionId before any tick is delivered”
test so pollDaVinci emits a status before its promise resolves, and assert that
event is received to make the race detectable.

In `@PingTestRunner/ios/PingTestRunnerUITests/DaVinciUITests.swift`:
- Around line 81-87: Update testUserinfoReturnsPayloadContainingSub so the
assertion failure message does not interpolate or otherwise log the full
userinfo payload; retain the check that the payload contains "sub" while using a
non-sensitive failure message.

---

Nitpick comments:
In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciCommon.kt`:
- Around line 554-564: Refactor the nullable handling in the collector selection
around requestedKey and the option read: replace the explicit null-check if/else
with takeIf/?.let and the Elvis fallback, while preserving the existing behavior
of selecting the requested collector or first collector.

In
`@packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciEvents.kt`:
- Line 13: Add a KDoc comment directly above the public POLLING_STATUS constant
describing the polling status event, using the required /** */ syntax.

In `@packages/davinci/ios/Tests/RNPingDavinciCommonTests.swift`:
- Around line 1094-1115: Update EventObserver’s init to store the token returned
by NotificationCenter.addObserver(forName:object:queue:using:) in a property,
then remove that stored token in deinit instead of self. Preserve the existing
weak capture and event-handling behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d80f3ca3-8e9b-480b-882f-1cce72c8b164

📥 Commits

Reviewing files that changed from the base of the PR and between 7b9dd1e and b6f32da.

⛔ Files ignored due to path filters (2)
  • PingSampleApp/ios/Podfile.lock is excluded by !**/*.lock
  • PingTestRunner/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (67)
  • PingSampleApp/android/app/build.gradle
  • PingSampleApp/src/styles/davinciStyles.ts
  • PingSampleApp/ui/davinci/components/molecules/DaVinciFieldRenderer.tsx
  • PingSampleApp/ui/davinci/components/molecules/DaVinciPollingField.tsx
  • PingSampleApp/ui/davinci/components/molecules/DaVinciQrCodeField.tsx
  • PingSampleApp/ui/davinci/components/molecules/types.ts
  • PingSampleApp/ui/davinci/components/organisms/DaVinciClientPanel.tsx
  • PingSampleApp/ui/davinci/components/organisms/DaVinciContinueNodePanel.tsx
  • PingSampleApp/ui/davinci/hooks/useDaVinciClientPanelController.ts
  • PingTestRunner/__tests__/integration/davinci-polling.test.ts
  • PingTestRunner/android/app/build.gradle
  • PingTestRunner/ios/PingTestRunnerUITests/BaseTestCase.swift
  • PingTestRunner/ios/PingTestRunnerUITests/DaVinciUITests.swift
  • PingTestRunner/ios/PingTestRunnerUITests/TestEnvironment.swift
  • PingTestRunner/scenarios/DaVinciScenario.tsx
  • packages/binding/android/build.gradle
  • packages/browser/android/build.gradle
  • packages/core/android/build.gradle
  • packages/davinci/README.md
  • packages/davinci/RNPingDavinci.podspec
  • packages/davinci/android/build.gradle
  • packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciCommon.kt
  • packages/davinci/android/src/main/java/com/pingidentity/rndavinci/RNPingDavinciEvents.kt
  • packages/davinci/android/src/main/java/com/pingidentity/rndavinci/error/DaVinciErrorCodes.kt
  • packages/davinci/android/src/main/java/com/pingidentity/rndavinci/mapper/DaVinciNodeMapper.kt
  • packages/davinci/android/src/newarch/java/com/pingidentity/rndavinci/RNPingDavinciModule.kt
  • packages/davinci/android/src/oldarch/java/com/pingidentity/rndavinci/RNPingDavinciClassicModule.kt
  • packages/davinci/android/src/test/java/com/pingidentity/rndavinci/RNPingDavinciCommonTest.kt
  • packages/davinci/android/src/test/java/com/pingidentity/rndavinci/mapper/DaVinciNodeMapperTest.kt
  • packages/davinci/ios/Error/DaVinciErrorCodes.swift
  • packages/davinci/ios/Mapper/DaVinciNodeMapper.swift
  • packages/davinci/ios/RNPingDavinci.mm
  • packages/davinci/ios/RNPingDavinciClassic.mm
  • packages/davinci/ios/RNPingDavinciCommon.swift
  • packages/davinci/ios/RNPingDavinciEventEmitterGate.h
  • packages/davinci/ios/RNPingDavinciEventEmitterGate.mm
  • packages/davinci/ios/RNPingDavinciEvents.swift
  • packages/davinci/ios/RNPingDavinciImpl.swift
  • packages/davinci/ios/Tests/DaVinciNodeMapperTests.swift
  • packages/davinci/ios/Tests/RNPingDavinciCommonTests.swift
  • packages/davinci/src/NativeRNPingDavinci.ts
  • packages/davinci/src/__tests__/collectorHelpers.test.ts
  • packages/davinci/src/__tests__/createDaVinciClient.test.ts
  • packages/davinci/src/__tests__/davinciMethods.test.ts
  • packages/davinci/src/__tests__/useDavinci.test.tsx
  • packages/davinci/src/collectorHelpers.ts
  • packages/davinci/src/davinci.ts
  • packages/davinci/src/davinciMethods.ts
  • packages/davinci/src/events.ts
  • packages/davinci/src/index.tsx
  • packages/davinci/src/types/client.types.ts
  • packages/davinci/src/types/error.types.ts
  • packages/davinci/src/types/form.types.ts
  • packages/davinci/src/types/node.types.ts
  • packages/davinci/src/useDavinci.tsx
  • packages/device-client/android/build.gradle
  • packages/device-id/android/build.gradle
  • packages/device-profile/android/build.gradle
  • packages/external-idp/RNPingExternalIdp.podspec
  • packages/external-idp/android/build.gradle
  • packages/fido/android/build.gradle
  • packages/journey/android/build.gradle
  • packages/logger/android/build.gradle
  • packages/oath/android/build.gradle
  • packages/oidc/android/build.gradle
  • packages/push/android/build.gradle
  • packages/storage/android/build.gradle
💤 Files with no reviewable changes (2)
  • PingSampleApp/android/app/build.gradle
  • PingTestRunner/android/app/build.gradle

Comment thread packages/davinci/ios/RNPingDavinciCommon.swift
Comment thread packages/davinci/ios/RNPingDavinciEventEmitterGate.mm
Comment thread packages/davinci/ios/RNPingDavinciEvents.swift
Comment thread packages/davinci/README.md
Comment thread packages/davinci/src/index.tsx Outdated
Comment thread packages/davinci/src/useDavinci.tsx
Comment on lines +159 to +190
it('resolves subscriptionId before any tick is delivered, then streams continue ticks', async () => {
const mock = makeMock();
const { mod, emitter } = await loadDaVinci(mock);
const client = mod.createDaVinciClient(VALID_CONFIG);
await client.start();

const onStatus = jest.fn();
await client.pollStatus(onStatus);

expect(mock.pollDaVinci).toHaveBeenCalledWith('davinci-id-mock', {});

emitter.emit(POLLING_STATUS_EVENT, {
subscriptionId: 'sub-1',
status: 'continue',
retryCount: 1,
maxRetries: 60,
});
emitter.emit(POLLING_STATUS_EVENT, {
subscriptionId: 'sub-1',
status: 'continue',
retryCount: 2,
maxRetries: 60,
});

expect(onStatus).toHaveBeenCalledTimes(2);
expect(onStatus).toHaveBeenNthCalledWith(1, {
subscriptionId: 'sub-1',
status: 'continue',
retryCount: 1,
maxRetries: 60,
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Prevent loss of the first polling status.

client.pollStatus() waits for pollDaVinci() before it installs DeviceEventEmitter listeners. The iOS bridge starts the polling task before it resolves subscriptionId. A fast complete or error event can occur in that interval and never reach onStatus.

Use a listener-before-start handshake, or buffer native events until JavaScript has subscribed. Update this test so pollDaVinci emits a status before its promise resolves. The current mock cannot detect the race.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PingTestRunner/__tests__/integration/davinci-polling.test.ts` around lines
159 - 190, Update client.pollStatus and its polling startup flow to register
DeviceEventEmitter listeners before awaiting or initiating pollDaVinci, ensuring
early complete, error, and status events reach onStatus. Modify the makeMock
setup in the “resolves subscriptionId before any tick is delivered” test so
pollDaVinci emits a status before its promise resolves, and assert that event is
received to make the race detectable.

Comment thread PingTestRunner/ios/PingTestRunnerUITests/DaVinciUITests.swift

@rodrigoareis rodrigoareis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes looks good to me, left just some minor comments

pollJobsByDaVinciId[davinciId]?.remove(job)
}
}
pollJobsByDaVinciId.getOrPut(davinciId) { ConcurrentHashMap.newKeySet() }.add(job)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pollJobsByDaVinciId.getOrPut(davinciId) { ConcurrentHashMap.newKeySet() }.add(job)
pollJobsByDaVinciId.computeIfAbsent(davinciId) { ConcurrentHashMap.newKeySet() }.add(job)

pollJobsByDaVinciId[davinciId]?.remove(job)
}
}
pollJobsByDaVinciId.getOrPut(davinciId) { ConcurrentHashMap.newKeySet() }.add(job)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small concurrency nit: getOrPut on a ConcurrentHashMap isn't atomic

*/
private fun mapPollingCollector(collector: PollingCollector): Map<String, Any?> {
val map = baseCollectorMap(collector)
map["pollInterval"] = collector.pollInterval.toIntOrNull() ?: collector.pollInterval

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: when pollInterval/pollRetries fails to parse as Int, we fall back to the raw String. That silently breaks the documented "always a number" contract with the TS side and gives no signal that something's off. Since this mapper already logs warnings elsewhere for unsupported fields (see logWarning usage above), could we do the same here, e.g. logWarning(logger, TAG, "Non-numeric pollInterval from server: $it"), so this doesn't fail silently in production?

…5, SDKS-5296). Add NOTE comments above the affected bridge code so we remember to revisit once upstream ships fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants