Skip to content

OM SDK intergration for iOS - #95

Closed
phiviet01 wants to merge 6 commits into
duc/custom-internal-webviewfrom
duc/om-sdk
Closed

OM SDK intergration for iOS#95
phiviet01 wants to merge 6 commits into
duc/custom-internal-webviewfrom
duc/om-sdk

Conversation

@phiviet01

Copy link
Copy Markdown
Contributor

No description provided.

@phiviet01 phiviet01 changed the title Duc/om sdk OM SDK intergration for iOS Mar 25, 2026
@phiviet01
phiviet01 requested a review from JakubMrozek March 25, 2026 16:06
@phiviet01
phiviet01 marked this pull request as ready for review March 25, 2026 16:06
@JakubMrozek

Copy link
Copy Markdown
Member

@claude

@claude

claude Bot commented Mar 27, 2026

Copy link
Copy Markdown

Claude finished @JakubMrozek's task in 2m 16s —— View job


Code Review: OM SDK Integration for iOS

  • Read key Swift OM SDK files
  • Read Dart service and model changes
  • Review webview integration
  • Provide comprehensive review feedback

Overall this is a clean, well-structured implementation that follows IAB OM SDK patterns. A few issues are worth addressing before merge.


Issues

1. OMSDKService is dead code

lib/src/services/omsdk_service.dart defines OMSDKService.activate() but it is never called anywhere in the Dart codebase. Activation is handled internally in startOpenMeasurementSessionIfReady() on the Swift side. Either call this from Dart before the first ad renders, or remove it along with OMSDKPlugin.swift's activate method channel handler to avoid shipping unused code.

Fix this →

2. Possible double-injection of omsdk-v1.js

In KontextInAppWebViewPlatformView.init, omsdk-v1.js is injected as a WKUserScript (atDocumentStart, main frame only). Separately, OMIDMegabraincoAdSessionContext(partner:webView:contentUrl:...) may also inject the OM service script into the WKWebView's user content controller. Per IAB OM SDK documentation, the recommended approach for WKWebView is to use one injection path — either OMIDScriptInjector + addUserScript OR rely on the session context's automatic injection — not both. If the service script is injected twice, verification may fail. Recommend verifying against the IAB integration guide or adding a guard to check if the script is already present.

3. OMConstants.integrationVersion is hardcoded

ios/Classes/OMSDK/OMConstants.swift:4 has integrationVersion = "1.0.0" hardcoded. This should be kept in sync with the SDK version (kSdkVersion in lib/src/utils/constants.dart) for proper traceability in OM measurement reports. Consider reading it from a shared source of truth or at least adding a comment noting it must be updated on release.


Observations / Non-blocking

4. OMSession.retire() sends retire-iframe — verify ad JS handles it

ios/Classes/OMSDK/OMSession.swift:21-25 sends a retire-iframe postMessage to the webview before finishing the session. This is the correct IAB pattern (letting the ad's JS clean up OM state before the session ends). Make sure the ad server's JS actually listens for and handles this message type — if it doesn't, the retire step is a no-op.

5. Potential double finishOpenMeasurementSession call

lib/src/widgets/kontext_webview.dart:128-137 has a useEffect cleanup that calls controller.finishOpenMeasurementSession(), and AdFormat.resetIframe() (lib/src/widgets/ad_format.dart:614-615) also calls finishOpenMeasurementSession. The Swift implementation guards against double-finish correctly (guard let activeOMSession else { return }), but it would be cleaner to have a single call site to avoid confusion.

6. bid.om parsing expects an object, not a string

lib/src/models/bid.dart:259-272_parseOmCreativeType expects the server to send "om": {"creativeType": "display"} (an object). If the contract ever changes to "om": "display" (a string), this silently returns null. The contract should be verified against the API spec, or a comment added to document the expected shape.

7. OMIDMegabraincoAdSessionContext uses webView: init — confirms HTML display path

Using the webView: initializer means the OM SDK treats this as an HTML display session (not native), which is correct for a webview-based ad. The JS-side impression and media event ownership (impressionOwner: .javaScriptOwner) is consistent with this.

8. tvOS slices in the xcframework

The committed xcframework includes tvos-arm64 and tvos-arm64_x86_64-simulator slices, but the podspec targets :ios, '13.0'. The tvOS slices won't cause issues but add ~2× binary weight for no benefit. Consider stripping them if the framework vendor provides an iOS-only build.


@JakubMrozek

Copy link
Copy Markdown
Member

@claude

@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown

Claude finished @JakubMrozek's task in 3m 11s —— View job


Code Review: OM SDK Integration for iOS

  • Read key Swift OM SDK files
  • Read Dart service and model changes
  • Review webview integration
  • Provide comprehensive review feedback

Overall this is a clean, well-structured integration that correctly follows the IAB OM SDK HTML display path. The key issues from the previous review are still present; adding some new observations from a deeper read.


Issues

1. OMSDKService is dead code

lib/src/services/omsdk_service.dart defines OMSDKService.activate() but it is never called anywhere in the Dart codebase. Activation is handled internally on the Swift side inside startOpenMeasurementSessionIfReady() (via omService.activate()). The OMSDKPlugin.swift handles the activate channel call, but no Dart code ever invokes it. Either call OMSDKService.activate() early in the SDK lifecycle, or remove both the Dart service and the Swift plugin's activate handler.

Fix this →

2. configureOpenMeasurement is redundantly called in onWebViewCreated

In kontext_webview.dart:152, initialOmCreativeType is already passed as a creation param, which sets omCreativeType in the Swift init on the iOS side. Then onWebViewCreated (line 171–173) calls configureOpenMeasurement(omCreativeType!) again, which sets the same property a second time and fires startOpenMeasurementSessionIfReady() unnecessarily (it's a no-op at that point since pendingOpenMeasurementStart is still false). The configureOpenMeasurement channel call is only needed when creative type changes after creation. Either remove the onWebViewCreated call when initialOmCreativeType is already set, or drop the creation param and always use the method channel.

Fix this →

3. Possible double-injection of omsdk-v1.js

omsdk-v1.js is injected as a WKUserScript at document start (lines 104–112 of KontextInAppWebViewPlugin.swift). The IAB OM SDK's OMIDMegabraincoAdSessionContext(partner:webView:...) initializer (used in OMManager.swift:61) also injects the OM service script into the WKWebView's content controller. This results in the OM service script running twice on each page load. While the script is idempotent, IAB guidelines recommend a single injection path. Consider removing the WKUserScript injection (loadOpenMeasurementJavaScript) and relying solely on the session context's automatic injection, or vice versa.

4. OMConstants.integrationVersion is hardcoded

ios/Classes/OMSDK/OMConstants.swift:5 has integrationVersion = "1.0.0" hardcoded and will drift from the SDK version (kSdkVersion in lib/src/utils/constants.dart). OM measurement reports use this version for traceability. At minimum add a comment that it must be bumped on each release.

5. Magic string "video" in OMSession.logError

ios/Classes/OMSDK/OMSession.swift:32:

let omErrorType: OMIDErrorType = errorType == "video" ? .media : .generic

The string "video" is a magic value with no definition or documentation. If the ad server ever sends a different string (e.g. "media"), errors will silently be misclassified as .generic. Define a constant or map it via the OMCreativeType enum.

Fix this →


Observations / Non-blocking

6. retire() is fire-and-forget before finish()

KontextInAppWebViewPlugin.swift:538-539:

activeOMSession.retire()
activeOMSession.finish()

retire() calls webView.evaluateJavaScript(...) asynchronously; finish() runs immediately after without waiting. The ad JS will almost certainly not have received retire-iframe before the native session ends. This is likely acceptable since finish() is native-side and doesn't depend on JS state — but if the ad JS needs to do OM cleanup before session end, there's a race. Consider adding a short delay or completing finish() in the JS eval's completion handler.

7. Double finishOpenMeasurementSession call path

kontext_webview.dart:135 (useEffect cleanup) and ad_format.dart:615 (inside resetIframe) both call finishOpenMeasurementSession(). The Swift guard let activeOMSession else { return } prevents a double-finish correctly, but two call sites adds confusion about ownership. A comment noting the guard is the safety net would help.

8. OM not started for non-immediate impression triggers

ad_format.dart:195 only calls startOpenMeasurementSession() when bid.impressionTrigger == ImpressionTrigger.immediate. If other trigger types are ever used with OM-enabled bids, no session will start. This appears intentional (commit message references preventing inline OM start for component ads), but it's worth a comment to make the constraint explicit.

9. Upstream webview backend silently no-ops all OM methods

upstream_in_app_webview.dart:155–167 — all four OM methods are empty stubs. OM will silently do nothing when the upstream backend is used. This is intentional for the non-custom-webview path, but should be commented so future maintainers don't debug why OM isn't firing.

10. tvOS slices in xcframework add binary weight for no benefit

The xcframework includes tvos-arm64 and tvos-arm64_x86_64-simulator slices, but the podspec targets :ios, '13.0' only. Consider requesting an iOS-only build from the framework vendor to halve the binary size.


JakubMrozek and others added 2 commits March 30, 2026 23:40
- Replace OMSDK_Megabrainco.xcframework with OMSDK_Kontextso.xcframework
- Update all OMIDMegabrainco* class references to OMIDKontextso*
- Update partnerName to "Kontextso" in OMConstants.swift
- Update omsdk-v1.js to v1.6.4
- Update podspec vendored_frameworks and linker flags

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

Copy link
Copy Markdown
Member

@phiviet01 Merged to v4 (different repo)

@JakubMrozek JakubMrozek closed this May 4, 2026
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.

2 participants