Skip to content

SDK-5788: React Native SDK#37

Draft
akashvercetti wants to merge 39 commits into
release/native-display-v1from
SDK-5788-react-native-sdk
Draft

SDK-5788: React Native SDK#37
akashvercetti wants to merge 39 commits into
release/native-display-v1from
SDK-5788-react-native-sdk

Conversation

@akashvercetti

Copy link
Copy Markdown

No description provided.

CTLalit and others added 19 commits May 8, 2026 12:46
Lands the .github/workflows/docs.yml file on the default branch so the
"Run workflow" button appears in the Actions UI. This is a small,
infra-only PR cherry-picked ahead of the full docs PR (#27) so we can
trigger workflow_dispatch against the feat branch and produce a first
gh-pages deploy without waiting for the v1 release stack to merge.

The workflow itself does nothing destructive on main pushes until the
docs PR cascade lands (Dokka + DocC + Docusaurus all live on the docs
branch). The only intended use of this workflow file on main right now
is workflow_dispatch from the feat branch.

Once the docs PR merges into main via the cascade, the workflow file
content here will match the docs PR — no merge conflict.

Jira: https://wizrocket.atlassian.net/browse/SDK-5784

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore(SDK-5784): add docs CI workflow scaffolding (infra-only)
@akashvercetti akashvercetti requested a review from CTLalit May 25, 2026 07:35
akashvercetti and others added 10 commits May 26, 2026 15:20
feat: Native Display SDK — initial release
… action.metadata (#55)

* feat: remove elementID transport marker — attribution fields flow via action metadata

wzrk_element_id (and wzrk_btn_text, wzrk_activity_type, wzrk_data) are now
injected by the BE into each action's metadata field. ActionAttributionExtras
already spreads action.metadata into the extras map, so these keys reach Core
SDK via additionalProperties without a dedicated elementID parameter.

- Remove wzrk_btn_id transport marker from ActionAttributionExtras (Android + iOS)
- Update NativeDisplayBridge reflection probe to 2-arg Core SDK method signature
- Simplify invokeClickedEvent — pass all sanitized extras directly
- Remove sendThreeArgSelector (dead code) from iOS bridge
- Update NativeDisplayRenderer call sites to match new from() signature
- Update all tests to match new API and 2-arg selector

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

* rename wzrk_btn_text→wzrk_c2a and wzrk_activity_type→wzrk_act

* rename wzrk_btn_text→wzrk_c2a and wzrk_activity_type→wzrk_act

* rename wzrk_btn_text→wzrk_c2a and wzrk_activity_type→wzrk_act

* fix: replace string literals with enum values for type-correct compilation

- IndicatorPosition.BOTTOM / IndicatorShape.CIRCLE in sample gallery configs
- TextAlign.CENTER replaces "center" strings in sample and StylePropertiesTest

* fix: add Dokka plugin to :sdk so dokkaHtml CI task resolves

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

* fix(ci): skip docs site job when website/ content is absent

The site job references website/package-lock.json for npm caching, but
that content only lands when the docs/native-ui-kit-site branch is merged.
PRs touching android/sdk/** triggered the job and failed immediately on
the setup-node cache-path resolution step.

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

* fix(ci): gate docs site job via a pre-check job output

hashFiles() is not valid in job-level if conditions. Replace it with a
check-website job that shells out to test for website/package.json and
exports a has_website output; the site job gates on that output via
needs.check-website.outputs.has_website.

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

* feat(SDK-5846): add metadata to OpenUrl action and harden metadata parsing (Android + iOS)

- Action.OpenUrl gains metadata: Map<String, String>? so attribution fields
  (wzrk_element_id, wzrk_c2a, wzrk_act, wzrk_data) reach Core SDK for URL
  button clicks — previously they were silently dropped
- CustomAction now uses CustomActionSerializer (Android) / custom init(from:)
  (iOS) replacing the auto-synthesised Codable that crashed when a metadata
  value was a JSON object; values that are objects/arrays are serialised to
  compact JSON strings so the map stays Map<String,String> without throwing
- ActionAttributionExtras spreads OpenUrl.metadata into attribution extras,
  matching the existing CustomAction.metadata spreading
- OpenUrlSerializer URL resolution hardened to handle plain strings,
  platform objects, and legacy {text,replacements} Ultron format without crashing
- 4 new unit tests: string metadata round-trip, wzrk_data as JSON object for
  CustomAction and OpenUrl, and null value dropping

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

* fix(SDK-5846): fix cache bridge architecture and element-click attribution (Android + iOS)

Android:
- Fix updateDisplayUnits proxy handler — was casting ArrayList<CleverTapDisplayUnit>
  to JSONArray (always null); now extracts JSON via getJsonObject() reflection
- Change onServerUpdate signature from (JSONArray) to (List<String>) to match
- Simplify coreSdkCacheProxy lambda now that list is pre-extracted
- Revert wireListener to early-return when cache is attached; remove activeCache
  field (Core SDK holds the cache strongly — extra reference was dead weight)
- Fix invokeClickedEvent gate: prefer element-clicked method whenever available,
  passing empty map when extras is absent (Core SDK still enriches from cached JSON)
- Add diagnostic logging for element-clicked method resolution

iOS:
- Fix handleServerCacheUpdate: replace wrong [[String:Any]] cast with performSelector
  extraction (json/jsonObject) matching the delegate fallback path
- Remove activeCache field from CleverTapAutoWire — Core SDK holds cache strongly
  via @Property(nonatomic,strong); bridge also holds it; extra reference was redundant
- Move isCacheAttached flag to NativeDisplayBridge; set it in attachCache
- Fix invokeClickedEvent gate: prefer element-clicked path whenever available
- Replace perform(_:with:with:) with class_getMethodImplementation + unsafeBitCast
  for the 2-arg void ObjC call — perform misinterprets the empty return register

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…id (#54)

* Fix BOX hit-testing and video controls tap handling for ios and android
Video controls — tap not registering on AndroidView and tap unreliable on UIViewRepresentable
Bindings — non-string JSON values cause parse failure
VariableEvaluator — numeric booleans not evaluated correctly

* fix(ios): handle Double in asBool and cancel stale video control timers

- Add Double case to asBool(_:) so numeric JSON values like 1.0 are treated as truthy instead of falling through to nil
- Replace untracked asyncAfter dispatches with cancellable DispatchWorkItem in VideoPlayerView and VideoFullscreenView to prevent stale timers hiding controls prematurely

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

---------

Co-authored-by: CTLalit <144685420+CTLalit@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…tation (#56)

* SDK-5847: Fix Android sample ND views disappearing on device rotation (Android)

On rotation the Activity recreates, destroying all Compose `remember`/
`mutableStateOf` state. Bridge callbacks (onNativeDisplaysLoaded) fire
only once — so the received units were never re-delivered, leaving the
canvas blank after rotation.

Fix: move received units and log messages into ViewModels that survive
configuration changes.

- New CleverTapIntegrationViewModel: holds receivedUnits + logMessages
  as StateFlows; CleverTapIntegrationScreen collects via collectAsState()
- New BridgeIntegrationViewModel: holds the NativeDisplayBridge instance
  (so the same bridge survives rotation) plus all UI state flows;
  bridge.clear() deferred to onCleared() instead of DisposableEffect
- XmlFeedViewModel: added receivedUnits StateFlow + setUnits(); Fragment
  collects it in a second repeatOnLifecycle coroutine to restore canvas
  on view recreation without waiting for a new bridge callback
- Added lifecycle-viewmodel-compose dependency (reuses lifecycle 2.8.7)

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

* fix(android): improve ExoPlayer lifecycle and video rendering robustness

- Switch PlayerView surface from SurfaceView to TextureView (via ct_player_view.xml)
  so parent alpha/graphicsLayer transforms are respected; fixes overlapping video
  bleed-through when tabs are hidden with graphicsLayer { alpha = 0f }
- Replace 100ms polling loop with Player.Listener callbacks (onIsPlayingChanged,
  onVolumeChanged, onPlaybackStateChanged) to eliminate unnecessary recompositions
- Add onPlayerError listener to catch async decoder failures (e.g. 4K H.264
  NO_EXCEEDS_CAPABILITIES) and surface them as a visible error state instead of
  silently stalling
- Remove inline AndroidView from composition when fullscreen is active using
  if (!isFullscreen) instead of view.player = null
- Configure AudioAttributes (USAGE_MEDIA + AUDIO_CONTENT_TYPE_MOVIE) with
  handleAudioFocus=true so video correctly pauses on audio interruptions
- Drop manual isMuted = !isMuted toggle from click handlers; onVolumeChanged
  fires synchronously so the manual toggle was immediately inverting the state

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

* refactor(android-sample): decouple navigation and improve MainActivity structure

- Replace NavController params with callback lambdas in BannerDetailScreen,
  BannerShowcaseScreen, JSONViewerScreen, and DemoScreenContainer; screens no
  longer import NavController and are independently testable
- Add Routes object centralising all route strings and builder functions;
  eliminates raw string literals scattered across NavHost declarations
- Add MainTab enum and replace 5 repetitive NavigationBarItem blocks with a
  forEachIndexed loop over MainTab.entries
- Switch selectedTab to rememberSaveable { mutableIntStateOf(0) } so the active
  tab survives screen rotation and process death
- Replace TabContent visibility wrapper with a plain when(selectedTab) expression;
  TabContent function deleted as it no longer had a role after the conditional
  composition fix from the previous commit

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

* SDK-5847: Address CodeRabbit PR #56 review comments

- VideoRenderer: add `import android.util.Log`, replace all qualified calls
- VideoRenderer: `errorMessage` keyed on `videoUrl` (remember → remember(videoUrl))
- VideoRenderer: clear errorMessage on STATE_READY recovery
- VideoRenderer: rebind `view.player = exoPlayer` in AndroidView.update for both
  inline and fullscreen PlayerView instances
- BridgeIntegrationScreen: move bridge.addListener into DisposableEffect keyed on
  (bridge, listenerRegistered) so listener re-attaches after rotation
- CleverTapIntegrationViewModel: atomic log append via _logMessages.update { }
- MainActivity: Uri.encode route params; omit filename query param when null

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update readme

* removes unwanted docs

* Update README.md
* SDK-5859: Support custom URL schemes in open_url action (Android + iOS)

Replace the scheme allowlist (http/https/tel/mailto) with a blocklist
of dangerous schemes (javascript/data/file) in both ActionHandler
implementations. For non-http/https schemes the OS resolves the handler
directly — enabling deep-links into other apps (e.g. myapp://, fb://)
without going through Chrome Custom Tabs / SFSafariViewController.

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

* SDK-5859: Match Core SDK openUrl behavior exactly (Android + iOS)

Replace the previous allowlist/blocklist scheme validation and custom
routing with an implementation identical to Core SDK:

Android: strip \n/\r, extract query params as Bundle extras, use
Intent.ACTION_VIEW, prefer own app's handler (mirrors
InAppActionHandler.openUrl / setPackageNameFromResolveInfoList).

iOS: call UIApplication.shared.open(url, options: [:]) directly,
matching CleverTap.m openURL:forModule: (openURL:options:completionHandler:).

Only the customTabsEnabled flag introduces a branch — Custom Tabs on
Android / SFSafariViewController on iOS. Everything else, including
custom app schemes (myapp://, fb://, spotify://) and http/https, goes
through the same OS-level dispatch as Core SDK.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty imageview removed when failure or no image
Adds explicit clear background to make UI similar to android
…ndroid and iOS (#60)

* feat(SDK-5860): add NDLogger with client-configurable log level for Android and iOS

Replace raw android.util.Log / print() calls across both SDKs with a
structured NDLogger that respects a configurable level. Clients can now
silence or amplify SDK output independently of the Core SDK.

Android:
- New internal NDLogger object (OFF/INFO/DEBUG/VERBOSE, default INFO)
- New public NDLogLevel enum exposed via NativeDisplayBridge.setLogLevel()
- CleverTapAutoWire syncs Core SDK's getDebugLevel() as default on auto-wire
  when the client has not set an explicit level
- Replaced ~60 Log.* calls across handler, renderer, bridge, placement, internal

iOS:
- New internal NDLogger enum with matching levels (default .info)
- CTNDLogLevel typealias + NativeDisplayBridge.setLogLevel(_:) as public API
- CleverTapAutoWire reads debugLevel via KVC on auto-wire as default
- Replaced ~51 print() calls across Bridge, Handlers, Placement

Client call always wins regardless of call order relative to auto-wire.

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

* fix(SDK-5860): address CodeRabbit review — thread safety and cross-platform parity

Android NDLogger:
- Guard setLevel and syncFromCoreSdk under stateLock to eliminate the
  check-then-write race between @volatile fields

iOS NDLogger:
- Add NSLock protecting setLevel and syncFromCoreSdk writes
- Add syncFromCoreSdk(_ level:) that updates _level without setting
  explicitlySet, matching Android's re-sync semantics

iOS CleverTapAutoWire:
- Switch syncLogLevelFromCoreSdk to call NDLogger.syncFromCoreSdk
  so auto-wire does not permanently lock out future Core SDK syncs
- Change unknown-level fallback from .info to .debug (matches Android)
- Clarify comment to reflect actual explicitlySet behavior

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

* fix(SDK-5860): add NDLogger.swift to Xcode project — resolves 'cannot find NDLogger in scope'

The .xcodeproj explicitly enumerates source files and does not auto-discover
new subdirectories. Added Internal/ group and NDLogger.swift PBXFileReference
+ PBXBuildFile so Xcode targets compile the file alongside the rest of the SDK.

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

* Adds oslog for iOS

* update android logs with [NativeDisplay] tag and class name

* Adds fix for italic style in ios

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Sonal Kachare <sonal@clevertap.com>
…ment taps (#61)

* SDK-5862: fire "Notification Clicked" attribution for IMAGE taps (Android + iOS)

IMAGE elements with click actions were executing the action (open URL, etc.)
but silently skipping the "Notification Clicked" system event, so click-through
attribution was never recorded in CleverTap analytics.

Android: extend onSystemClick guard in NativeDisplayRenderer from isButton to
isButton || isImage — one-line change, no impact on shouldApplyClickable.

iOS: add onSystemClick hook to TappableModifier / applyTappable (default nil,
zero impact on containers and non-image elements); RenderNode passes the
system-event closure for IMAGE nodes.

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

* SDK-5862: guard image attribution against missing onClick action

onSystemClick was wired for all IMAGE nodes, so tapping an image that
has a componentListener but no server onClick action would fire
"Notification Clicked" with empty extras.

Android: use a `when` expression so the image branch only produces a
closure when node.actions[ON_CLICK] != null.

iOS: add a `guard let onClick` in the closure — returns early when no
onClick action is defined, preventing a spurious attribution event.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sonal-Kachare and others added 4 commits June 10, 2026 15:02
* feat(SDK-5860): add NDLogger with client-configurable log level for Android and iOS

Replace raw android.util.Log / print() calls across both SDKs with a
structured NDLogger that respects a configurable level. Clients can now
silence or amplify SDK output independently of the Core SDK.

Android:
- New internal NDLogger object (OFF/INFO/DEBUG/VERBOSE, default INFO)
- New public NDLogLevel enum exposed via NativeDisplayBridge.setLogLevel()
- CleverTapAutoWire syncs Core SDK's getDebugLevel() as default on auto-wire
  when the client has not set an explicit level
- Replaced ~60 Log.* calls across handler, renderer, bridge, placement, internal

iOS:
- New internal NDLogger enum with matching levels (default .info)
- CTNDLogLevel typealias + NativeDisplayBridge.setLogLevel(_:) as public API
- CleverTapAutoWire reads debugLevel via KVC on auto-wire as default
- Replaced ~51 print() calls across Bridge, Handlers, Placement

Client call always wins regardless of call order relative to auto-wire.

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

* fix(SDK-5860): address CodeRabbit review — thread safety and cross-platform parity

Android NDLogger:
- Guard setLevel and syncFromCoreSdk under stateLock to eliminate the
  check-then-write race between @volatile fields

iOS NDLogger:
- Add NSLock protecting setLevel and syncFromCoreSdk writes
- Add syncFromCoreSdk(_ level:) that updates _level without setting
  explicitlySet, matching Android's re-sync semantics

iOS CleverTapAutoWire:
- Switch syncLogLevelFromCoreSdk to call NDLogger.syncFromCoreSdk
  so auto-wire does not permanently lock out future Core SDK syncs
- Change unknown-level fallback from .info to .debug (matches Android)
- Clarify comment to reflect actual explicitlySet behavior

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

* fix(SDK-5860): add NDLogger.swift to Xcode project — resolves 'cannot find NDLogger in scope'

The .xcodeproj explicitly enumerates source files and does not auto-discover
new subdirectories. Added Internal/ group and NDLogger.swift PBXFileReference
+ PBXBuildFile so Xcode targets compile the file alongside the rest of the SDK.

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

* Adds oslog for iOS

* update android logs with [NativeDisplay] tag and class name

* Adds fix for italic style in ios

* Adds push support

* Fixes a crash in syncLogLevelFromCoreSdk as performselector breaks for primitive type
Update package.swift path

* update debug mapping comment

---------

Co-authored-by: CTLalit <144685420+CTLalit@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… small fixes (#63)

* SDK-5862: fire "Notification Clicked" attribution for IMAGE taps (Android + iOS)

IMAGE elements with click actions were executing the action (open URL, etc.)
but silently skipping the "Notification Clicked" system event, so click-through
attribution was never recorded in CleverTap analytics.

Android: extend onSystemClick guard in NativeDisplayRenderer from isButton to
isButton || isImage — one-line change, no impact on shouldApplyClickable.

iOS: add onSystemClick hook to TappableModifier / applyTappable (default nil,
zero impact on containers and non-image elements); RenderNode passes the
system-event closure for IMAGE nodes.

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

* SDK-5862: guard image attribution against missing onClick action

onSystemClick was wired for all IMAGE nodes, so tapping an image that
has a componentListener but no server onClick action would fire
"Notification Clicked" with empty extras.

Android: use a `when` expression so the image branch only produces a
closure when node.actions[ON_CLICK] != null.

iOS: add a `guard let onClick` in the closure — returns early when no
onClick action is defined, preventing a spurious attribution event.

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

* feat: rebrand sample apps to "CT Display Lab" with Firebase FCM setup

- Rename app to "CT Display Lab" on Android (strings.xml) and iOS (CFBundleDisplayName)
- Add launcher icons (mdpi–xxxhdpi) with adaptive icon support (back/fore layers + anydpi-v26 XML)
- Update iOS AppIcon.appiconset with new 1024px icon
- Add Firebase Messaging dependency (BOM 33.7.0) and google-services plugin to Android sample
- Add POST_NOTIFICATIONS permission and FCM service to AndroidManifest
- Add google-services.json to .gitignore

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

* fix: address CodeRabbit review comments

- Fix iOS/Android parity: fire onSystemClick unconditionally before
  notifyComponentListener in TappableModifier.swift so "Notification
  Clicked" attribution always fires on click, matching Android behavior
- Replace hardcoded CleverTap credentials with placeholders in
  AndroidManifest.xml and iOS Info.plist
- Remove empty colors.xml (unused after adaptive icon switched to PNG background)

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

* chore: bump sample app versions to 2.0 (build 15) for QA distribution

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#64)

* feat: add ObjC sample app with full SDK integration

- Restructure ObjC sample with 4-tab layout (Events, Slots, UIKit, More)
- Add RootTabBarController, BridgeIntegrationViewController, CleverTapIntegrationViewController, UIKitDemoViewController, SlotDemoViewController
- Remove unused screens (Browser, Arrangements, Animations, Home, TestCases)
- Add NDSlotPlaceholderView in NDDisplayHelper for slot-driven placeholder management
- Make NativeDisplayBridge, NativeDisplaySlotManager, NDLogLevel @objc-compatible
- Fix SlotDemoView AsyncImage frame/clipping in Swift sample
- Clean up NDDisplayHelper: remove unused arrangement-override, font-demo, and no-placeholder slot methods

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

* Update creds in sample app objc

* Adds code review fixes

* Fix coderabbit review comments

* Adds license and updates podspec
Fix: pod lib lint

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ff27e3d-ae76-471e-9ed1-dbf1a26e21eb

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SDK-5788-react-native-sdk

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 and usage tips.

…hey were leaking through and breaking evaluateBoolean.
…tribution survives, and accepted the legacy nested {text} URL shape.
… with every click event so the dashboard can slice by button on RN like it can on native.
…Notification Viewed/Clicked itself, so hosts don't double-report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants