Skip to content

feat(all): SDKS-5019 code quality and pre-1.0 consolidation#49

Merged
pingidentity-gaurav merged 9 commits into
mainfrom
SDKS-5019
Jun 12, 2026
Merged

feat(all): SDKS-5019 code quality and pre-1.0 consolidation#49
pingidentity-gaurav merged 9 commits into
mainfrom
SDKS-5019

Conversation

@pingidentity-gaurav

@pingidentity-gaurav pingidentity-gaurav commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Pre-1.0 code quality consolidation across all SDK packages. Resolves 25+ identified issues from a full codebase audit.

SDK changes:

  • Gradle/native SDK versions aligned across all packages; Ping SDK deps bumped 2.0.0→2.0.1
  • 12 union/discriminated types widened with | (string & {}) for semver safety
  • noopLogger extracted to @ping-identity/rn-types, removed from 11 packages
  • OidcClient/OidcWebClient — add dispose() to deregister from CoreRuntime registries (iOS + Android)
  • journeyCallbackType merged constant added to @ping-identity/rn-types
  • useJourneyForm — add JourneyFormOptions.handledCallbackTypes so canSubmit works correctly for integration-required nodes
  • All 13 NativeRNPing*.ts modules — __DEV__ guard on module list + module-level caching
  • Browser iOS — wire JS logger through to BrowserLauncher.launch() (matches Android)
  • Push — rn-storage fully removed as a dependency; use PushStorageHandle from rn-types
  • LICENSE files added to all 14 packages

Release automation:

  • release.yml — auto-create GitHub Release with changelog notes on tag push

Sample app:

  • Module-level logger constants (no more useMemo(() => logger(...), []))
  • JourneyContinuePanelsubmitDisabled = loading || !form.canSubmit; blocking state derived from form.issues
  • DeviceOf type alignment, <></>null fix

Documentation:

  • All package READMEs: consistent ## License footer, npm install steps confirmed
  • Storage README: document all configure* functions
  • Integration package READMEs (fido, binding, external-idp, device-profile): correct useJourneyForm + handledCallbackTypes examples
  • Core README: stripped to consumer-facing content only
  • CONTRIBUTING.md: document yarn typecheck and yarn lint scripts

Test plan

  • yarn workspaces foreach --all --parallel run typecheck — 30/30 pass
  • yarn packages:build — 15/15 pass
  • iOS sample app builds and runs
  • Binding flow (DeviceBindingCallback + DeviceSigningVerifierCallback) works end-to-end
  • OIDC authorize flow works
  • Browser logger routes correctly on iOS

Summary by CodeRabbit

  • New Features

    • OIDC clients/web clients can be explicitly disposed; automated GitHub release creation added; journey forms accept options to mark integrations as handled.
  • Bug Fixes

    • More reliable native module resolution and clearer errors; stricter browser redirect handling.
  • Documentation

    • Added LICENSE files and updated READMEs; contributor guide now documents repo-level scripts.
  • Refactor

    • Type definitions refined for device/error extensibility; consolidated shared no-op logger.
  • Chores

    • Bumped Kotlin/SDK/serialization versions; removed obsolete TODOs.

- Gradle: align Kotlin/serialization/coroutines versions; bump all native SDK deps 2.0.0→2.0.1
- Union types: widen 12 error code / result types with | (string & {}) across 8 packages
- Push: make rn-storage fully optional — use PushStorageHandle from rn-types, no rn-storage dep
- noopLogger: extract to rn-types, remove local copies from 11 packages
- OidcClient/OidcWebClient: add dispose() — deregisters from CoreRuntime registries (iOS + Android)
- journeyCallbackType: add merged constant to rn-types (callbackType + nativeExtensionCallbackType)
- useJourneyForm: add JourneyFormOptions.handledCallbackTypes so canSubmit works for integration nodes
- getNativeModule: add __DEV__ guard + module-level caching across all 13 NativeRNPing*.ts files
- Browser iOS logger: wire JS logger through to BrowserLauncher.launch() — matches Android behaviour
- release.yml: auto-create GitHub release with changelog notes on tag push
- READMEs: add LICENSE files, standardise footers, document all configure* storage APIs
- Sample app: module-level logger constants, fix <></> → null, align DeviceOf types
- JourneyContinuePanel: derive blocking state from form.issues; submitDisabled = loading || !form.canSubmit
- TODO audit: tag blocked items, remove stale comments, update CONTRIBUTING.md scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • .yarn/install-state.gz is excluded by !**/.yarn/**, !**/*.gz
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 480fb20c-59f3-45c2-9539-9530cd1db2cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR consolidates SDK maintenance across 90+ files: exporting shared logger/callback type primitives from @ping-identity/rn-types, standardizing native bridge caching, aligning device-client and Journey typing, implementing OIDC disposal lifecycle, adding browser logger propagation on iOS, broadening type contracts for extensibility, and synchronizing package licensing, documentation, and Android dependencies.

Changes

SDK-wide bridge hardening, typing alignment, and lifecycle updates

Layer / File(s) Summary
Shared type primitives: noopLogger and journeyCallbackType
packages/types/src/index.ts, packages/browser/src/index.tsx, packages/device-profile/src/index.tsx, packages/external-idp/src/externalIdp.ts, packages/fido/src/index.tsx, packages/journey/src/journey.ts, packages/oidc/src/index.tsx, packages/push/src/push.ts, packages/storage/src/index.tsx, packages/device-client/src/createDeviceClient.ts
Exports journeyCallbackType (merged callback type map) and noopLogger constant from @ping-identity/rn-types; replaces local noop logger definitions across packages to consolidate logger defaults.
Native bridge module caching and diagnostics
packages/binding/src/NativeRNPingBinding.ts, packages/browser/src/NativeRNPingBrowser.ts, packages/device-client/src/NativeRNPingDeviceClient.ts, packages/device-id/src/NativeRNPingDeviceId.ts, packages/external-idp/src/NativeRNPingExternalIdp.ts, packages/fido/src/NativeRNPingFido.ts, packages/journey/src/NativeRNPingJourney.ts, packages/oath/src/NativeRNPingOath.ts, packages/push/src/NativeRNPingPush.ts, packages/oidc/src/NativeRNPingOidc.ts, packages/logger/src/NativeRNPingLogger.ts
Standardizes getNativeModule() across packages with module-level caching, test reset helpers where added, and consistent available-module diagnostics in thrown errors.
Device client typing: DeviceByKind map-based constraints
packages/device-client/src/types/device.types.ts, packages/device-client/src/types/error.types.ts, packages/device-client/src/createDeviceClient.ts, PingSampleApp/src/hooks/useDevices.ts, PingSampleApp/ui/DevicesScreen.tsx, PingSampleApp/ui/devices/components/molecules/DeviceRow.tsx, PingSampleApp/ui/devices/components/organisms/DeviceListCard.tsx, PingSampleApp/ui/devices/types.ts
Moves device typing to DeviceByKind-keyed constraints and updates SDK and sample app hooks/components to DeviceOf<keyof DeviceByKind>.
Journey form: handledCallbackTypes configuration and submit planning
packages/journey/src/types/form.types.ts, packages/journey/src/callbackHelpers.ts, packages/journey/src/useJourneyForm.ts, PingSampleApp/ui/journey/hooks/useJourneyClientPanelController.ts
Adds JourneyFormOptions.handledCallbackTypes to useJourneyForm and threads it through buildNextInput to suppress INTEGRATION_REQUIRED issues for known-handled callbacks; sample controller provides INTEGRATION_HANDLED_CALLBACK_TYPES.
Sample Journey panel: issue-driven blocking and browser redirect guards
PingSampleApp/ui/journey/components/organisms/JourneyContinuePanel.tsx
Computes blocking integration state from form.issues filtered by handled callback types; simplifies submit gating to `loading
Browser launch: iOS logger handle resolution and propagation
packages/browser/ios/BrowserLaunching.swift, packages/browser/ios/DefaultBrowserLauncherAdapter.swift, packages/browser/ios/RNPingBrowserCommon.swift, packages/browser/ios/Tests/RNPingBrowserCommonTests.swift
Extends BrowserLaunching protocol and adapter to accept logger: Logger; resolves JS loggerId to native Logger and forwards it into launcher; adds tests verifying forwarding.
OIDC client/web-client lifecycle: dispose across native and JS layers
packages/oidc/android/src/main/java/com/pingidentity/rnoidc/RNPingOidcCommon.kt, packages/oidc/android/src/newarch/java/com/pingidentity/rnoidc/RNPingOidcModule.kt, packages/oidc/android/src/oldarch/java/com/pingidentity/rnoidc/RNPingOidcClassicModule.kt, packages/oidc/ios/RNPingOidcCommon.swift, packages/oidc/ios/RNPingOidcImpl.swift, packages/oidc/src/NativeRNPingOidc.ts, packages/oidc/src/index.tsx, packages/oidc/src/types/oidc.types.ts, packages/oidc/src/useOidc.tsx, PingTestRunner/scenarios/UseOidcScenario.tsx
Adds native disposeClient / disposeWebClient methods, updates TS native spec and public client types to include dispose(), implements JS-side dispose() on created clients that calls native dispose and swallows errors after logging.
Type surface widening: error codes and result extensibility
packages/binding/src/types/binding.types.ts, packages/browser/src/types/browser.types.ts, packages/journey/src/types/error.types.ts, packages/oath/src/types/oath.types.ts, packages/device-profile/src/types/deviceProfile.types.ts, packages/fido/src/types/fido.types.ts
Broadens error-code and result unions with (string & {}) fallback members to permit custom/future codes and result variants.
Storage and push config: handle types and error wrapping
packages/push/src/types/config.types.ts, packages/storage/src/index.tsx, packages/storage/src/NativeRNPingStorage.ts
Changes PushConfig.storage to PushStorageHandle; updates storage configuration helpers to wrap caught errors as StorageError.from(error) and refines JSDoc.
Sample app: OathAccountDetailModal return type nullable
PingSampleApp/ui/oath/components/organisms/OathAccountDetailModal.tsx
Broadened return type to `React.ReactElement
Repository CI and contribution workflow
.github/workflows/release.yml, CONTRIBUTING.md
Adds GitHub Actions step to create releases from version tags with changelog extraction; documents yarn typecheck, yarn lint, and yarn lint --fix in CONTRIBUTING.md.
Sample app iOS and Android build configuration
PingSampleApp/android/settings.gradle, PingSampleApp/ios/PingSampleApp.xcodeproj/project.pbxproj
Removes a TODO line from Android settings; switches Xcode pod build phases to use outputFileListPaths (xcfilelists) and changes OTHER_LDFLAGS to list form.
Package licenses and README standardization
packages/*/LICENSE, packages/*/README.md, README.md
Adds MIT LICENSE files to multiple packages and standardizes README license sections and various docs (useJourneyForm examples, error handling, storage overrides).
Android Gradle: SDK and Kotlin dependency alignment
packages/*/android/build.gradle
Bumps com.pingidentity.sdks:* artifacts from 2.0.02.0.1, upgrades kotlinx-serialization-json to 1.9.0, and updates Kotlin Gradle plugin to 2.2.10 where applicable.
Documentation comments and TODO cleanups
packages/binding/ios/RNPingBindingImpl.swift, packages/core/*, packages/storage/*
Clarifies Journey callback resolver docs, removes stale TODOs, and documents storage logger behavior as JS-side only.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • tsdamas

Poem

🐰 I rewired the bridges and cached every call,
Shared loggers now stand proud in the hall,
Device kinds align, Journey knows what to do,
OIDC says "dispose" — tidy and new,
Licenses, docs, CI — a monorepo tune for all.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SDKS-5019

pingidentity-gaurav and others added 2 commits June 9, 2026 23:45
…ncies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 10, 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-49/

Built to branch gh-pages at 2026-06-12 13:17 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

pingidentity-gaurav and others added 2 commits June 10, 2026 00:19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…clude module list; add PingLogger import to browser iOS test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@george-bafaloukas-forgerock

Copy link
Copy Markdown

Code review

Hi @pingidentity-gaurav — great consolidation work here, the pre-1.0 cleanup is much appreciated! I found 3 issues worth addressing before merge:

1. __DEV__ guard removed from all NativeRNPing*.ts modules

The __DEV__ guard that was deliberately introduced in external-idp (in response to a review comment on PR #38) has been removed here. JSON.stringify(Object.keys(NativeModules)) is now embedded in error messages unconditionally, which means the full list of registered native module names is included in production error telemetry (Crashlytics, Sentry, etc.). The previous intent was to show this only in dev builds. Could you either restore the guard across all 13 modules, or leave a comment explaining why unconditional inclusion is acceptable?

const availableModules =
'\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules));
throw new Error(
'[@ping-identity/rn-external-idp] Native module RNPingExternalIdp not found.\n' +
'Ensure the library is linked correctly and the app has been rebuilt.' +
availableModules,
);

2. Module-level caching not applied consistently

NativeRNPingDeviceProfile.ts and NativeRNPingLogger.ts only received the error-message reformatting — the let _nativeModule: Spec | null = null caching pattern was not applied to them. The PR description says "All 13 NativeRNPing*.ts modules — module-level caching", but these two were skipped (and NativeRNPingStorage.ts wasn't touched at all). Happy to be corrected if there's a reason these were intentionally left out, but it looks like it may have slipped through.

*/
export function getNativeModule(): Spec {
const turbo = TurboModuleRegistry.get<Spec>('RNPingDeviceProfile');
if (turbo) {
return turbo;
}
const classic = NativeModules.RNPingDeviceProfileClassic as Spec | undefined;
if (classic) {
return classic;
}
const availableModules =
'\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules));
throw new Error(
'[@ping-identity/rn-device-profile] Native module RNPingDeviceProfile not found.\n' +
'Ensure the library is linked correctly and the app has been rebuilt.' +
availableModules,

*/
export function getNativeModule(): Spec {
const turbo = TurboModuleRegistry.get<Spec>('Logger');
if (turbo) {
return turbo;
}
const classic = NativeModules.RNPingLoggerClassic as Spec | undefined;
if (classic) {
return classic;
}
const availableModules =
'\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules));
throw new Error(
'[@ping-identity/rn-logger] Native module Logger not found.\n' +
'Ensure the library is linked correctly and the app has been rebuilt.' +

3. DeviceOf<DeviceKind> silently becomes invalid after the widening

DeviceOf's constraint changed from K extends DeviceKind to K extends keyof DeviceByKind, while DeviceKind was simultaneously widened with | (string & {}). These two changes are not compatible: DeviceOf<DeviceKind> — the pattern shown in the existing @example JSDoc — now resolves to never for the open-string branch and fails type-checking. The sample app callsites were correctly migrated to DeviceOf<keyof DeviceByKind>, but any external SDK consumer using the documented DeviceOf<DeviceKind> pattern will hit a silent breaking change on upgrade. It would be worth updating the @typeParam/@example on DeviceOf, and noting this as a breaking change in the CHANGELOG.

https://github.com/ForgeRock/ping-react-native-sdk/blob/a05fa878f173ba15b9fc45395368a8d2952e8c09/packages/device-client/src/types/device.types.ts#L197-L202

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

…t for tests

- RNPingBrowserCommon: use async actor-based logger resolution instead of RegistrySync to avoid deadlock in test context
- RNPingBrowser.podspec: add PingLogger as explicit dependency to main spec and test spec
- NativeRNPingExternalIdp/DeviceId/Fido: export _resetNativeModuleForTesting() so tests can reset module-level cache between runs
- external-idp native-module test: call _resetNativeModuleForTesting() in beforeEach

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/storage/src/NativeRNPingStorage.ts (1)

452-470: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add module-level caching to align with PR description.

The PR description states that module-level caching was added across all NativeRNPing*.ts files, but getNativeModule() in this file lacks the _nativeModule caching pattern present in other modules (e.g., packages/external-idp/src/NativeRNPingExternalIdp.ts). According to PR comment #2, NativeRNPingStorage.ts appears untouched and should either receive the caching pattern or the PR description should be updated to reflect the intentional exclusion.

Without caching, getNativeModule() probes TurboModuleRegistry and NativeModules on every call, which is unnecessary since the native module reference does not change at runtime.

⚡ Proposed fix to add module-level caching
+let _nativeModule: Spec | null = null;
+/** `@internal` — resets the module cache for testing only. */
+export function _resetNativeModuleForTesting(): void {
+  _nativeModule = null;
+}
 export function getNativeModule(): Spec {
+  if (_nativeModule) return _nativeModule;
+
   const turbo = TurboModuleRegistry.get<Spec>('RNPingStorage');
   if (turbo) {
-    return turbo;
+    _nativeModule = turbo;
+    return _nativeModule;
   }
 
   const classic = NativeModules.RNPingStorageClassic as Spec | undefined;
   if (classic) {
-    return classic;
+    _nativeModule = classic;
+    return _nativeModule;
   }
 
   const availableModules = __DEV__
     ? '\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules))
     : '';
   throw new Error(
     '[`@ping-identity/rn-storage`] Native module RNPingStorage not found.\n' +
       'Ensure the library is linked correctly and the app has been rebuilt.' +
       availableModules,
   );
 }
🤖 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/storage/src/NativeRNPingStorage.ts` around lines 452 - 470,
getNativeModule currently probes TurboModuleRegistry/NativeModules on every
call; add a module-level cache (e.g., a variable named _nativeModule: Spec |
undefined | null) and update getNativeModule to return _nativeModule if set,
otherwise resolve the native module from TurboModuleRegistry or NativeModules,
assign it to _nativeModule, and then return it; ensure the thrown Error remains
unchanged if resolution fails. This mirrors the caching pattern used in other
files such as NativeRNPingExternalIdp.ts and prevents repeated lookups.
packages/logger/src/NativeRNPingLogger.ts (1)

57-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Module-level caching was not added.

The PR description states that "All 13 NativeRNPing*.ts modules: added __DEV__ guard on module list and module-level caching (per description)," but this file only received the error message reformatting. The _nativeModule caching pattern implemented in NativeRNPingOath.ts was not applied here.

Per the reviewer's feedback, this inconsistency should either be corrected or explained if intentional.

⚡ Proposed fix to add module-level caching
+let _nativeModule: Spec | null = null;
 export function getNativeModule(): Spec {
+  if (_nativeModule) return _nativeModule;
+
   const turbo = TurboModuleRegistry.get<Spec>('Logger');
   if (turbo) {
-    return turbo;
+    _nativeModule = turbo;
+    return _nativeModule;
   }

   const classic = NativeModules.RNPingLoggerClassic as Spec | undefined;
   if (classic) {
-    return classic;
+    _nativeModule = classic;
+    return _nativeModule;
   }

   const availableModules =
     '\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules));
   throw new Error(
     '[`@ping-identity/rn-logger`] Native module Logger not found.\n' +
       'Ensure the library is linked correctly and the app has been rebuilt.' +
       availableModules,
   );
 }
🤖 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/logger/src/NativeRNPingLogger.ts` around lines 57 - 75,
getNativeModule currently always queries TurboModuleRegistry/NativeModules and
throws if not found; add the same module-level caching used in
NativeRNPingOath.ts by introducing a file-scoped _nativeModule variable and
returning it when set to avoid repeated lookups, and add the __DEV__ guard
around the list of available modules string construction as done in other
NativeRNPing*.ts files; update getNativeModule to set _nativeModule to the found
turbo or classic module before returning and reuse _nativeModule on subsequent
calls.
packages/device-profile/src/NativeRNPingDeviceProfile.ts (1)

52-70: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Module-level caching was not added.

The PR description states that "All 13 NativeRNPing*.ts modules: added __DEV__ guard on module list and module-level caching (per description)," but this file only received the error message reformatting. The _nativeModule caching pattern implemented in NativeRNPingOath.ts was not applied here.

Per the reviewer's feedback, this inconsistency should either be corrected or explained if intentional.

⚡ Proposed fix to add module-level caching
+let _nativeModule: Spec | null = null;
 export function getNativeModule(): Spec {
+  if (_nativeModule) return _nativeModule;
+
   const turbo = TurboModuleRegistry.get<Spec>('RNPingDeviceProfile');
   if (turbo) {
-    return turbo;
+    _nativeModule = turbo;
+    return _nativeModule;
   }

   const classic = NativeModules.RNPingDeviceProfileClassic as Spec | undefined;
   if (classic) {
-    return classic;
+    _nativeModule = classic;
+    return _nativeModule;
   }

   const availableModules =
     '\nAvailable NativeModules: ' + JSON.stringify(Object.keys(NativeModules));
   throw new Error(
     '[`@ping-identity/rn-device-profile`] Native module RNPingDeviceProfile not found.\n' +
       'Ensure the library is linked correctly and the app has been rebuilt.' +
       availableModules,
   );
 }
🤖 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/device-profile/src/NativeRNPingDeviceProfile.ts` around lines 52 -
70, getNativeModule currently builds and throws an error each call but lacks the
module-level cache used elsewhere; add a module-scoped cache (e.g., let
_nativeModule: Spec | null = null) and have getNativeModule return _nativeModule
when set, otherwise locate TurboModuleRegistry or
NativeModules.RNPingDeviceProfileClassic, assign the found module to
_nativeModule and return it; also mirror the other files' behavior by only
including the available NativeModules list in the error when __DEV__ is true so
production calls don't leak that info.
packages/device-client/src/types/device.types.ts (1)

189-199: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

DeviceOf constraint and documentation are inconsistent with DeviceKind widening.

The constraint change from K extends DeviceKind to K extends keyof DeviceByKind combined with widening DeviceKind to include | (string & {}) creates a breaking change:

  • keyof DeviceByKind is the closed union 'oath' | 'push' | 'bound' | 'profile' | 'webAuthn'.
  • DeviceKind now includes (string & {}), which is NOT assignable to keyof DeviceByKind.
  • Any external code using DeviceOf<DeviceKind> (e.g., in generic helper functions) will now fail because the (string & {}) branch cannot satisfy the keyof DeviceByKind constraint.

The @typeParam at line 192 references DeviceKind literals but the constraint is now stricter. The @example shows DeviceOf<'bound'> (which works) but does not demonstrate the correct generic pattern DeviceOf<keyof DeviceByKind> that all sample app callsites now use.

Impact:

  • External consumers following the existing documentation will encounter type errors.
  • The migration pattern (DeviceOf<keyof DeviceByKind>) is not documented here.
📝 Proposed documentation fix

Update the @typeParam and add a generic usage example:

 /**
  * Resolves the concrete device type for a given {`@link` DeviceKind}.
  *
- * `@typeParam` K - One of the supported {`@link` DeviceKind} literals.
+ * `@typeParam` K - A key from {`@link` DeviceByKind}. To accept all known kinds in a generic context, use `keyof DeviceByKind` rather than `DeviceKind` (which includes an extensible string fallback).
  *
  * `@example`
+ * // Specific device kind:
  * ```ts
  * type T = DeviceOf<'bound'>; // BoundDevice
  * ```
+ *
+ * `@example`
+ * // Generic usage accepting any known kind:
+ * ```ts
+ * function processDevice<K extends keyof DeviceByKind>(
+ *   kind: K,
+ *   device: DeviceOf<K>
+ * ) { ... }
+ * ```
  */
 export type DeviceOf<K extends keyof DeviceByKind> = DeviceByKind[K];

Additionally, note this as a breaking change in the CHANGELOG as flagged by reviewer george-bafaloukas-forgerock: code using DeviceOf<DeviceKind> in generic contexts must migrate to DeviceOf<keyof DeviceByKind>.

🤖 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/device-client/src/types/device.types.ts` around lines 189 - 199, The
DeviceOf type's constraint was tightened to K extends keyof DeviceByKind while
DeviceKind was widened to include (string & {}), causing code that used
DeviceOf<DeviceKind> to break; update the DeviceOf documentation and examples to
reflect the new constraint by changing the `@typeParam` text to reference keyof
DeviceByKind, add a new generic example showing usage like function
processDevice<K extends keyof DeviceByKind>(kind: K, device: DeviceOf<K>) { ...
}, and add a note to the CHANGELOG calling out the breaking change for callers
using DeviceOf<DeviceKind> so they can migrate to DeviceOf<keyof DeviceByKind>.
🧹 Nitpick comments (2)
packages/oidc/src/NativeRNPingOidc.ts (1)

121-129: 💤 Low value

Consider adding a test reset helper for consistency.

Other native modules in this PR added _resetNativeModuleForTesting() exports to allow tests to clear the module-level cache. Adding a similar helper here would maintain consistency and improve testability.

♻️ Suggested addition
 let _nativeModule: Spec | null = null;
 export function getNativeModule(): Spec {
   if (_nativeModule) return _nativeModule;
   // ... rest of function
 }
+
+/**
+ * Reset cached native module for testing.
+ * `@internal`
+ */
+export function _resetNativeModuleForTesting(): void {
+  _nativeModule = null;
+}
🤖 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/oidc/src/NativeRNPingOidc.ts` around lines 121 - 129, Add a test
helper that clears the module-level cache by exporting a function named
_resetNativeModuleForTesting which sets the module variable _nativeModule to
null; locate the module-level variable _nativeModule and the getNativeModule
function and implement _resetNativeModuleForTesting() to assign null to
_nativeModule so tests can reset state between runs.
packages/oidc/android/src/main/java/com/pingidentity/rnoidc/RNPingOidcCommon.kt (1)

689-697: 💤 Low value

Consider documenting disposal order expectations.

These disposal methods remove registry entries without checking for dependent web clients. If a client is disposed while web clients derived from it still exist, those web clients may fail when attempting parent-client fallback (e.g., during token operations). While the fallback logic handles missing parents gracefully by returning "No authenticated user" errors, the expected disposal order (web clients first, then parent client) should be documented to prevent confusion.

🤖 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/oidc/android/src/main/java/com/pingidentity/rnoidc/RNPingOidcCommon.kt`
around lines 689 - 697, Add documentation and/or a log comment near the
disposeClient and disposeWebClient functions clarifying the expected disposal
order: web clients (webRegistry entries) should be disposed before their parent
native clients (clientRegistry entries) because web clients may attempt
parent-client fallback for token operations and will surface "No authenticated
user" errors if the parent is gone; update the doc comment for fun
disposeClient(clientId: String, promise: Promise) and fun
disposeWebClient(webClientId: String, promise: Promise) to state this ordering
and the rationale so callers know to call disposeWebClient first or handle
missing parents explicitly.
🤖 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 @.github/workflows/release.yml:
- Around line 93-97: The regex literal in the NOTES node script treats
`${VERSION}` as plain text so the changelog section never matches; update the
log.match call inside the NOTES assignment to build the regex via the RegExp
constructor (or a template string passed to RegExp) so the VERSION variable is
interpolated, and ensure the newline and character-class tokens (like \n and
[\s\S]) are properly escaped in the constructed pattern to preserve the original
intent and fallback behavior.

In `@packages/binding/src/NativeRNPingBinding.ts`:
- Around line 146-151: The error message in NativeRNPingBinding.ts
unconditionally appends JSON.stringify(Object.keys(NativeModules)) (via the
availableModules variable) which leaks module inventory; change the construction
of availableModules in the RNPingBinding error path so it only includes the
module list when __DEV__ is true (or a redacted placeholder in production).
Locate the throw in NativeRNPingBinding (where availableModules is built and
thrown when RNPingBinding is not found) and wrap the module-list generation in
an __DEV__ conditional (or replace with a fixed redaction string) so production
bundles never include the full NativeModules list.

In `@packages/external-idp/src/NativeRNPingExternalIdp.ts`:
- Around line 97-98: The error message currently builds availableModules by
unconditionally calling JSON.stringify(Object.keys(NativeModules)), leaking
native module names to production; wrap that construction in a __DEV__ guard so
availableModules is only populated when __DEV__ is true (otherwise set to an
empty string or a minimal non-sensitive placeholder) and use that guarded value
in the thrown error inside NativeRNPingExternalIdp (the variable
availableModules and the place where the error is thrown should be updated
accordingly).

In `@packages/oath/src/NativeRNPingOath.ts`:
- Around line 154-160: Restore the development-only guard around the
NativeModules diagnostic in NativeRNPingOath.ts: change the construction of
availableModules (used when throwing the Error that references RNPingOath) so
that Object.keys(NativeModules) is only serialized and appended when __DEV__ is
true; in non-dev builds append a generic, non-sensitive hint instead. Locate the
availableModules variable and the Error throw in NativeRNPingOath (the
RNPingOath not found error) and apply the same __DEV__-guarded pattern as in the
other two files.

In `@packages/oidc/src/NativeRNPingOidc.ts`:
- Around line 137-142: The error currently builds and includes availableModules
(JSON.stringify(Object.keys(NativeModules))) unconditionally when throwing the
'RNPingOidc not found' Error, which leaks native module names; change the logic
around the availableModules constant and the thrown Error in NativeRNPingOidc
(the throw new Error(...) block and the availableModules variable) so that the
detailed diagnostic is only generated and appended when __DEV__ is true,
otherwise append a generic hint (e.g. "rebuild/relink the app") without listing
NativeModules; ensure you reference NativeModules and availableModules only
inside the __DEV__ branch so production builds never include the module list.

In `@packages/storage/src/NativeRNPingStorage.ts`:
- Around line 463-464: The error message currently constructs availableModules
by unconditionally serializing Object.keys(NativeModules) (the availableModules
constant), which leaks native module names to production telemetry; wrap the
enumeration in a __DEV__ check so availableModules includes the
JSON.stringify(Object.keys(NativeModules)) only when __DEV__ is true (and use an
empty string or a minimal placeholder when false), updating the code that builds
availableModules and any thrown error that references it (look for the
availableModules constant and throw/Error creation in NativeRNPingStorage) to
prevent exposing module names in production.

---

Outside diff comments:
In `@packages/device-client/src/types/device.types.ts`:
- Around line 189-199: The DeviceOf type's constraint was tightened to K extends
keyof DeviceByKind while DeviceKind was widened to include (string & {}),
causing code that used DeviceOf<DeviceKind> to break; update the DeviceOf
documentation and examples to reflect the new constraint by changing the
`@typeParam` text to reference keyof DeviceByKind, add a new generic example
showing usage like function processDevice<K extends keyof DeviceByKind>(kind: K,
device: DeviceOf<K>) { ... }, and add a note to the CHANGELOG calling out the
breaking change for callers using DeviceOf<DeviceKind> so they can migrate to
DeviceOf<keyof DeviceByKind>.

In `@packages/device-profile/src/NativeRNPingDeviceProfile.ts`:
- Around line 52-70: getNativeModule currently builds and throws an error each
call but lacks the module-level cache used elsewhere; add a module-scoped cache
(e.g., let _nativeModule: Spec | null = null) and have getNativeModule return
_nativeModule when set, otherwise locate TurboModuleRegistry or
NativeModules.RNPingDeviceProfileClassic, assign the found module to
_nativeModule and return it; also mirror the other files' behavior by only
including the available NativeModules list in the error when __DEV__ is true so
production calls don't leak that info.

In `@packages/logger/src/NativeRNPingLogger.ts`:
- Around line 57-75: getNativeModule currently always queries
TurboModuleRegistry/NativeModules and throws if not found; add the same
module-level caching used in NativeRNPingOath.ts by introducing a file-scoped
_nativeModule variable and returning it when set to avoid repeated lookups, and
add the __DEV__ guard around the list of available modules string construction
as done in other NativeRNPing*.ts files; update getNativeModule to set
_nativeModule to the found turbo or classic module before returning and reuse
_nativeModule on subsequent calls.

In `@packages/storage/src/NativeRNPingStorage.ts`:
- Around line 452-470: getNativeModule currently probes
TurboModuleRegistry/NativeModules on every call; add a module-level cache (e.g.,
a variable named _nativeModule: Spec | undefined | null) and update
getNativeModule to return _nativeModule if set, otherwise resolve the native
module from TurboModuleRegistry or NativeModules, assign it to _nativeModule,
and then return it; ensure the thrown Error remains unchanged if resolution
fails. This mirrors the caching pattern used in other files such as
NativeRNPingExternalIdp.ts and prevents repeated lookups.

---

Nitpick comments:
In
`@packages/oidc/android/src/main/java/com/pingidentity/rnoidc/RNPingOidcCommon.kt`:
- Around line 689-697: Add documentation and/or a log comment near the
disposeClient and disposeWebClient functions clarifying the expected disposal
order: web clients (webRegistry entries) should be disposed before their parent
native clients (clientRegistry entries) because web clients may attempt
parent-client fallback for token operations and will surface "No authenticated
user" errors if the parent is gone; update the doc comment for fun
disposeClient(clientId: String, promise: Promise) and fun
disposeWebClient(webClientId: String, promise: Promise) to state this ordering
and the rationale so callers know to call disposeWebClient first or handle
missing parents explicitly.

In `@packages/oidc/src/NativeRNPingOidc.ts`:
- Around line 121-129: Add a test helper that clears the module-level cache by
exporting a function named _resetNativeModuleForTesting which sets the module
variable _nativeModule to null; locate the module-level variable _nativeModule
and the getNativeModule function and implement _resetNativeModuleForTesting() to
assign null to _nativeModule so tests can reset state between runs.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7503375d-8195-4035-92e4-db3cfc28f012

📥 Commits

Reviewing files that changed from the base of the PR and between e2b610d and 60aac85.

⛔ Files ignored due to path filters (3)
  • .yarn/install-state.gz is excluded by !**/.yarn/**, !**/*.gz
  • PingSampleApp/ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (112)
  • .github/workflows/release.yml
  • CONTRIBUTING.md
  • PingSampleApp/android/settings.gradle
  • PingSampleApp/ios/PingSampleApp.xcodeproj/project.pbxproj
  • PingSampleApp/src/hooks/useDevices.ts
  • PingSampleApp/ui/DevicesScreen.tsx
  • PingSampleApp/ui/devices/components/molecules/DeviceRow.tsx
  • PingSampleApp/ui/devices/components/organisms/DeviceListCard.tsx
  • PingSampleApp/ui/devices/types.ts
  • PingSampleApp/ui/journey/components/organisms/JourneyContinuePanel.tsx
  • PingSampleApp/ui/journey/hooks/useJourneyClientPanelController.ts
  • PingSampleApp/ui/oath/components/organisms/OathAccountDetailModal.tsx
  • PingTestRunner/scenarios/UseOidcScenario.tsx
  • README.md
  • packages/binding/LICENSE
  • packages/binding/README.md
  • packages/binding/TODOS.md
  • packages/binding/android/build.gradle
  • packages/binding/ios/RNPingBindingImpl.swift
  • packages/binding/src/NativeRNPingBinding.ts
  • packages/binding/src/types/binding.types.ts
  • packages/browser/LICENSE
  • packages/browser/README.md
  • packages/browser/RNPingBrowser.podspec
  • packages/browser/android/build.gradle
  • packages/browser/ios/BrowserLaunching.swift
  • packages/browser/ios/DefaultBrowserLauncherAdapter.swift
  • packages/browser/ios/RNPingBrowserCommon.swift
  • packages/browser/ios/Tests/RNPingBrowserCommonTests.swift
  • packages/browser/src/NativeRNPingBrowser.ts
  • packages/browser/src/index.tsx
  • packages/browser/src/types/browser.types.ts
  • packages/core/LICENSE
  • packages/core/README.md
  • packages/core/android/build.gradle
  • packages/core/android/src/main/java/com/pingidentity/rncore/CoreRuntime.kt
  • packages/core/ios/CoreRuntime.swift
  • packages/device-client/LICENSE
  • packages/device-client/README.md
  • packages/device-client/android/build.gradle
  • packages/device-client/src/NativeRNPingDeviceClient.ts
  • packages/device-client/src/createDeviceClient.ts
  • packages/device-client/src/types/device.types.ts
  • packages/device-client/src/types/error.types.ts
  • packages/device-id/LICENSE
  • packages/device-id/README.md
  • packages/device-id/android/build.gradle
  • packages/device-id/src/NativeRNPingDeviceId.ts
  • packages/device-profile/LICENSE
  • packages/device-profile/README.md
  • packages/device-profile/android/build.gradle
  • packages/device-profile/src/NativeRNPingDeviceProfile.ts
  • packages/device-profile/src/index.tsx
  • packages/device-profile/src/types/deviceProfile.types.ts
  • packages/external-idp/LICENSE
  • packages/external-idp/README.md
  • packages/external-idp/android/build.gradle
  • packages/external-idp/src/NativeRNPingExternalIdp.ts
  • packages/external-idp/src/__tests__/native-module.test.tsx
  • packages/external-idp/src/externalIdp.ts
  • packages/fido/LICENSE
  • packages/fido/README.md
  • packages/fido/android/build.gradle
  • packages/fido/src/NativeRNPingFido.ts
  • packages/fido/src/index.tsx
  • packages/fido/src/types/fido.types.ts
  • packages/journey/LICENSE
  • packages/journey/README.md
  • packages/journey/android/build.gradle
  • packages/journey/src/NativeRNPingJourney.ts
  • packages/journey/src/callbackHelpers.ts
  • packages/journey/src/journey.ts
  • packages/journey/src/types/error.types.ts
  • packages/journey/src/types/form.types.ts
  • packages/journey/src/useJourneyForm.ts
  • packages/logger/LICENSE
  • packages/logger/README.md
  • packages/logger/android/build.gradle
  • packages/logger/src/NativeRNPingLogger.ts
  • packages/oath/LICENSE
  • packages/oath/README.md
  • packages/oath/android/build.gradle
  • packages/oath/src/NativeRNPingOath.ts
  • packages/oath/src/oath.ts
  • packages/oath/src/types/oath.types.ts
  • packages/oidc/LICENSE
  • packages/oidc/README.md
  • packages/oidc/android/build.gradle
  • packages/oidc/android/src/main/java/com/pingidentity/rnoidc/RNPingOidcCommon.kt
  • packages/oidc/android/src/newarch/java/com/pingidentity/rnoidc/RNPingOidcModule.kt
  • packages/oidc/android/src/oldarch/java/com/pingidentity/rnoidc/RNPingOidcClassicModule.kt
  • packages/oidc/ios/RNPingOidcCommon.swift
  • packages/oidc/ios/RNPingOidcImpl.swift
  • packages/oidc/src/NativeRNPingOidc.ts
  • packages/oidc/src/index.tsx
  • packages/oidc/src/types/oidc.types.ts
  • packages/oidc/src/useOidc.tsx
  • packages/push/LICENSE
  • packages/push/README.md
  • packages/push/android/build.gradle
  • packages/push/package.json
  • packages/push/src/NativeRNPingPush.ts
  • packages/push/src/push.ts
  • packages/push/src/types/config.types.ts
  • packages/storage/LICENSE
  • packages/storage/README.md
  • packages/storage/android/build.gradle
  • packages/storage/android/src/main/java/com/pingidentity/rnstorage/RNPingStorageCommon.kt
  • packages/storage/ios/RNPingStorageCommon.swift
  • packages/storage/src/NativeRNPingStorage.ts
  • packages/storage/src/index.tsx
  • packages/types/src/index.ts
💤 Files with no reviewable changes (5)
  • packages/push/package.json
  • packages/binding/TODOS.md
  • packages/storage/android/src/main/java/com/pingidentity/rnstorage/RNPingStorageCommon.kt
  • PingSampleApp/android/settings.gradle
  • packages/storage/ios/RNPingStorageCommon.swift

Comment thread .github/workflows/release.yml Outdated
Comment thread packages/binding/src/NativeRNPingBinding.ts
Comment thread packages/external-idp/src/NativeRNPingExternalIdp.ts
Comment thread packages/oath/src/NativeRNPingOath.ts
Comment thread packages/oidc/src/NativeRNPingOidc.ts
Comment thread packages/storage/src/NativeRNPingStorage.ts
…e between tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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.

Overall changes looks good. Left some comments

Comment thread packages/journey/src/useJourneyForm.ts
Comment thread packages/types/src/index.ts Outdated
Comment thread packages/oidc/src/index.tsx
Comment thread packages/oidc/src/index.tsx
Comment thread packages/device-profile/src/NativeRNPingDeviceProfile.ts
Comment thread packages/external-idp/README.md Outdated
Comment thread packages/logger/src/NativeRNPingLogger.ts
@pingidentity-gaurav

Copy link
Copy Markdown
Contributor Author

Hey @george-bafaloukas-forgerock — thanks for the thorough review! Addressed all three points:

1. __DEV__ guard removed

We removed the guard to keep all 13 NativeRNPing*.ts files consistent with each other — the guard only existed in external-idp from PR #38 and hadn't been applied to any other module. The immediate blocker was that __DEV__ is not defined in Jest environments, causing test failures across multiple packages when we tried to apply the guard uniformly. Rather than patching every jest config, we removed it uniformly for now. Added a TODO(SDKS-separate-ticket) comment in external-idp to revisit with a proper cross-package solution in a future ticket.

2. Module-level caching not applied to logger, device-profile, storage

Intentional — these three use export default getNativeModule() at module load time (not per-call). The module system caches the export automatically, so a _nativeModule variable would be redundant. The other 10 modules call getNativeModule() inside async operation functions that fire repeatedly — those benefit from the cache. Added @remarks to each of the three to document this. Will update the PR description to say "applied to the 10 per-call modules" to avoid the mismatch you flagged.

3. DeviceOf<DeviceKind> silent breaking change

Good catch. Updated the DeviceOf JSDoc with a @remarks note explaining that the constraint is keyof DeviceByKind (not DeviceKind) and that DeviceOf<DeviceKind> will not compile after the widening. Updated the @example to show DeviceOf<keyof DeviceByKind>. The sample app callsites already use the correct form.

- release.yml: fix VERSION interpolation using env var + split-based changelog extraction
- device-client: update DeviceOf JSDoc — constraint is keyof DeviceByKind not DeviceKind
- external-idp: add TODO(SDKS-separate-ticket) explaining __DEV__ guard removal
- external-idp/README: remove IdPCallback (normalised to IdpCallback by journey bridge)
- oidc: revert noopLogger to local const to fix Jest ESM issue; add dispose tests
- oidc/android: add thread-safety comment on disposeClient/disposeWebClient
- oidc/index: clarify loggerRegistry ownership in OidcWebClient.dispose()
- storage/logger/device-profile NativeRNPing*.ts: add @remarks explaining intentional no-cache
- types/index.ts: move callbackType import to top of file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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.

LGTM

@tsdamas tsdamas 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.

LGTM

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pingidentity-gaurav pingidentity-gaurav merged commit 03f32e1 into main Jun 12, 2026
6 checks passed
@pingidentity-gaurav pingidentity-gaurav deleted the SDKS-5019 branch June 12, 2026 13:14
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.

4 participants