Skip to content

fix(KlaviyoForms): load IAF WebView same-origin to fix 401 on /api/profiles/ (MAGE-787)#614

Open
ab1470 wants to merge 3 commits into
feat/jwts-for-personalized-formsfrom
ab/MAGE-787/fix-profile-fetch-auth
Open

fix(KlaviyoForms): load IAF WebView same-origin to fix 401 on /api/profiles/ (MAGE-787)#614
ab1470 wants to merge 3 commits into
feat/jwts-for-personalized-formsfrom
ab/MAGE-787/fix-profile-fetch-auth

Conversation

@ab1470

@ab1470 ab1470 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes a 401 on the In-App Forms authenticated /api/profiles/ fetch on iOS by loading the form template same-origin with the Klaviyo API host, eliminating the Origin: null that a file:// load produced.

Due Diligence

  • I have tested this on a simulator or a physical device.
  • I have added sufficient unit/integration tests of my changes.
  • I have adjusted or added new test cases to team test docs, if applicable.
  • I am confident these changes are compatible with all iOS and XCode versions the SDK currently supports.

Release/Versioning Considerations

  • Patch Contains internal changes or backwards-compatible bug fixes.
  • Minor Contains changes to the public API.
  • Major Contains breaking changes.
  • Contains readme or migration guide changes.
  • This is planned work for an upcoming release.
    • Part of the JWT personalized In-App Forms feature; merges to feat/jwts-for-personalized-forms.

Changelog / Code Overview

Root cause: The IAF WKWebView loaded the template from a file:// URL, which has an opaque origin. Onsite's authenticated GET /api/profiles/ (cross-origin, with authorization/revision headers) therefore triggered a CORS preflight carrying Origin: null, which the API rejects — surfacing as a 401. The token was present in the DOM and onsite was attaching it correctly; the null origin was the only blocker.

Fix: Load the template's HTML same-origin, anchored to the API host, so the profile fetch is same-origin (no preflight, no Origin: null).

  • KlaviyoWebViewModeling: new baseURL: URL? protocol member (default nil, so other webviews are unaffected).
  • IAFWebViewModel.baseURL returns environment.apiURL().url.
  • KlaviyoWebViewController.loadUrl() uses loadHTMLString(_:baseURL:) when a base URL is supplied; falls back to the direct URLRequest load otherwise.
  • decidePolicyFor now keys external-link handling off navigationAction.navigationType == .linkActivated instead of the template filename — required because same-origin loading changes the document URL to the API host, which previously misclassified the initial load as an external link and bounced it to Safari.

Test Plan

  1. Build & unit tests: xcodebuild test -scheme klaviyo-swift-sdk-Package -destination "platform=iOS Simulator,name=iPhone 17 Pro" -only-testing:KlaviyoFormsTests — all green (incl. testBaseURLMatchesAPIOriginForSameOriginRequests).
  2. On device with a JWT-personalized form configured:
    • Trigger the in-app form; confirm it displays in-place (no Safari bounce).
    • Via Safari Web Inspector → Network: confirm GET /api/profiles/ is same-origin (no OPTIONS preflight) and returns 200.
    • Tap a CTA link in the form; confirm it still opens in the system browser.

Related Issues/Tickets

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved authenticated web form loading by using an optional matching base URL so /api/profiles/ requests behave same-origin and avoid related CORS issues.
    • Updated outbound link handling to open external links in the system browser only when directly activated by the user.
  • Tests

    • Added an async test to confirm the web form base URL matches the authenticated API origin.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 4591795e-0e9b-4992-a9e9-4ddb427eced5

📥 Commits

Reviewing files that changed from the base of the PR and between 24fdd02 and 039c86a.

📒 Files selected for processing (1)
  • Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift

📝 Walkthrough

Walkthrough

Adds a baseURL property to the KlaviyoWebViewModeling protocol (defaulting to nil) and implements it in IAFWebViewModel as environment.apiURL().url. KlaviyoWebViewController.loadUrl() now calls loadHTMLString(_:baseURL:) when baseURL is set, and the navigation delegate switches from filename-based to link-activation-based external URL handling. A new test verifies baseURL matches the configured API origin.

Changes

Same-origin baseURL for in-app form WebView

Layer / File(s) Summary
baseURL protocol contract and default implementation
Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewModeling.swift
var baseURL: URL? { get } added to KlaviyoWebViewModeling with a default nil extension.
IAFWebViewModel baseURL implementation
Sources/KlaviyoForms/InAppForms/IAFWebViewModel.swift
IAFWebViewModel overrides baseURL to return environment.apiURL().url.
WebViewController HTML loading, navigation delegate, and test
Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift, Tests/KlaviyoFormsTests/IAFWebViewModelTests.swift
loadUrl() branches on viewModel.baseURL to call loadHTMLString(_:baseURL:) vs. URLRequest; decidePolicyFor replaced template-filename exclusion with navigationType == .linkActivated; new test asserts baseURL equals the configured HTTPS API origin.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐇 Hopping past the file:// bog,
Same-origin clears the CORS fog.
A baseURL set just right,
Keeps authenticated calls in sight.
No more preflight headaches at night! 🌙

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title clearly and concisely summarizes the primary fix: loading IAF WebView same-origin to resolve 401 errors on /api/profiles/ requests.
Description check ✅ Passed The PR description comprehensively covers all required template sections with detailed explanations of the root cause, fix, testing methodology, and release classification.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ab/MAGE-787/fix-profile-fetch-auth

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

@ab1470 ab1470 marked this pull request as ready for review June 16, 2026 03:24
@ab1470 ab1470 requested a review from a team as a code owner June 16, 2026 03:24
@klaviyoit klaviyoit requested a review from ajaysubra June 16, 2026 03:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift`:
- Around line 127-135: The try? operator silently suppresses errors when reading
the HTML content from viewModel.url, making it difficult to debug if the read
unexpectedly fails. Replace the try? pattern with a do-try-catch block to
explicitly capture and log any error that occurs during the
String(contentsOf:encoding:) call, so that if HTML reading fails, the actual
error information is logged before falling back to webView.load with the
URLRequest. This will aid troubleshooting if the local template file read
operation encounters unexpected issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e0a87a07-06c6-435c-833b-07c47182914d

📥 Commits

Reviewing files that changed from the base of the PR and between ca25de3 and d8c79a1.

📒 Files selected for processing (4)
  • Sources/KlaviyoForms/InAppForms/IAFWebViewModel.swift
  • Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift
  • Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewModeling.swift
  • Tests/KlaviyoFormsTests/IAFWebViewModelTests.swift

Comment thread Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift
…irect load

The same-origin load path silently fell back to a direct URLRequest load if
the template contents couldn't be read — which reintroduces the `Origin: null`
the base URL exists to avoid. Capture and warn on the read failure so this
degradation is diagnosable. Addresses CodeRabbit review on PR #614.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift (1)

249-262: ⚠️ Potential issue | 🟠 Major

Cancel link-activated navigations after opening them externally and fix line length violations.

Line 251 opens the tapped URL in the system browser, but Line 262 still returns .allow, so the WKWebView can navigate away from the form as well. Return .cancel for handled links so external links do not also replace the in-app content.

Additionally, lines 255 and 257 exceed the 110-character line length limit. Break these long Logger calls into multiple lines.

Suggested fix
         if navigationAction.navigationType == .linkActivated,
            let url = navigationAction.request.url {
             let didOpenURL = await UIApplication.shared.open(url)
 
             if `#available`(iOS 14.0, *) {
                 if didOpenURL {
-                    Logger.webViewLogger.info("'UIApplication.shared.open(_:)' successfully opened URL '\(url.absoluteString)'")
+                    Logger.webViewLogger.info(
+                        "'UIApplication.shared.open(_:)' successfully opened URL "
+                        + "'\(url.absoluteString)'"
+                    )
                 } else {
-                    Logger.webViewLogger.info("'UIApplication.shared.open(_:)' did not open the URL '\(url.absoluteString)'")
+                    Logger.webViewLogger.info(
+                        "'UIApplication.shared.open(_:)' did not open the URL "
+                        + "'\(url.absoluteString)'"
+                    )
                 }
             }
+
+            return .cancel
         }
 
         return .allow
🤖 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 `@Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift` around
lines 249 - 262, The method currently opens external links via
UIApplication.shared.open(url) but then returns .allow, causing the WKWebView to
also navigate away from the form. Change the return statement from .allow to
.cancel within the linkActivated navigation check to prevent duplicate
navigation after opening the URL externally. Additionally, the
Logger.webViewLogger.info calls on lines 255 and 257 exceed the 110-character
line length limit. Refactor both Logger.webViewLogger.info calls to break the
long string concatenations across multiple lines, keeping the log messages
readable while complying with the line length constraint.
🤖 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 `@Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift`:
- Line 139: The Logger.webViewLogger.warning call in the
KlaviyoWebViewController exceeds the 110-character Swift line-length limit.
Refactor this line by breaking the string argument across multiple lines, either
by concatenating string segments or by moving the interpolated values to
intermediate variables, ensuring each resulting line stays within the
110-character limit while maintaining the same logging output and functionality.

---

Outside diff comments:
In `@Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift`:
- Around line 249-262: The method currently opens external links via
UIApplication.shared.open(url) but then returns .allow, causing the WKWebView to
also navigate away from the form. Change the return statement from .allow to
.cancel within the linkActivated navigation check to prevent duplicate
navigation after opening the URL externally. Additionally, the
Logger.webViewLogger.info calls on lines 255 and 257 exceed the 110-character
line length limit. Refactor both Logger.webViewLogger.info calls to break the
long string concatenations across multiple lines, keeping the log messages
readable while complying with the line length constraint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 34a02f48-a2ea-4d75-ad51-fe757e7bbc02

📥 Commits

Reviewing files that changed from the base of the PR and between d8c79a1 and 24fdd02.

📒 Files selected for processing (1)
  • Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift

Comment thread Sources/KlaviyoForms/KlaviyoWebView/KlaviyoWebViewController.swift Outdated
…ar limit

Addresses CodeRabbit review on PR #614.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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