Skip to content

iOS SDK: crash fixes + storyboard support (audit by Claude) - #2

Open
vuukle wants to merge 9 commits into
mainfrom
claude/sdk-audit-and-fixes
Open

vuukle wants to merge 9 commits into
mainfrom
claude/sdk-audit-and-fixes

Conversation

@vuukle

@vuukle vuukle commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Three commits addressing real crash bugs in the iOS SDK.

Summary

Crash fixes (would crash any host app on bad input):

  • URL(string:)! force-unwrap on outbound navigation (line 161) — any malformed URL from a webview link crashes the host app. Now guards + reports via addErrorListener.
  • serverTrust! force-unwrap in didReceive challenge — crashes when server returns NSURLAuthenticationMethodServerTrust without a trust object. Now safely guarded.
  • Three force-unwraps in parseMailSubjectAndBody on string indexing — crashes on any mailto link missing = or &. Common with third-party content. Returns empty tuple instead.

WKNavigationDelegate contract violations (cause WKWebView assertion crashes):

  • iOS 13+ decidePolicyFor: guard let urlString ... else { return } silently dropped the required decisionHandler call. WKWebView requires exactly one call per delegate invocation.
  • Legacy iOS 12 decidePolicyFor: called decisionHandler(.allow) twice in the .linkActivated branch (once explicit, once at function end). Refactored with defer to guarantee single call.

Foundational fixes:

  • BaseWebView.init?(coder:) no longer fatalErrors — VuukleView can now be loaded from a storyboard/xib (the README explicitly recommends this pattern).
  • VuukleWidget.swift no longer ships "Hello, World! ffff fasfdasfassdasdasds" placeholder in production. Replaced with proper module marker.
  • Package.swift cleaned of generator boilerplate.
  • Removed debug print statements in deinit and didFinish navigation.

Context

Manorama Online's June 2026 maintenance request and similar reports from Vikatan (Nov 2025, Android WebView UI bug on SDK v15+) prompted this audit. The crash fixes here are independent of platform-specific crashes those publishers reported, but eliminate broad classes of bugs that host apps would attribute to 'the Vuukle SDK' since the host app is what crashes.

Ready to tag as v2.2.6 once you've reviewed. Recommend NOT auto-merging — review the diff first.

Testing

  • All Swift files brace-balanced.
  • No new force-unwraps introduced.
  • Cannot run xcodebuild on Linux sandbox — recommend running Codemagic CI on this branch before merge.

Out of scope (separate PRs needed)

  • Android SDK library (sdk/ module on vuukle/android-widgets-vuukle) — audit in progress.
  • Bitbucket repo vuukleteam/ios-widgets-vuukle archival — Syuzanna's old guidance pointing publishers there is the source of the 'which SDK is canonical' confusion. Suggest adding MIGRATED.md on the BB side.

— Generated by Claude Cowork session

fantasticDev2 and others added 9 commits June 17, 2026 20:53
Bitbucket has been frozen at 'SDK V1.0.0' since 2025-06 while GitHub
has accumulated 50+ tags up to v2.2.5 including Florian's June 2025
release. Pull GitHub current state to Bitbucket so the two locations
agree, and so the help.vuukle.com docs that point at Bitbucket actually
serve current code.

Real diff is small (mostly line endings) - this is bookkeeping to
re-align the two remotes before applying crash fixes.
- BaseWebView: replace fatalError() in init?(coder:) with proper
  super.init(coder:) so VuukleView can be instantiated from a
  storyboard/xib. README explicitly suggests this pattern but the
  original code would have crashed on Interface Builder load.

- VuukleWidget.swift: remove leftover 'Hello, World ffff fasfdasfassdasdasds'
  Swift Package boilerplate that was shipping in production. Replace
  with proper module marker and SDK version constant.

- Package.swift: strip generator comments and unused dependency stub.
VuukleManager.swift had three force-unwraps that would crash the host
app and two violations of the WKWebView decisionHandler contract.

CRASH FIXES:
- Line 161: URL(string: url)! could crash on malformed URLs from
  webview navigation. Now guards and reports via addErrorListener.
- Line 237: challenge.protectionSpace.serverTrust! could crash when
  a server returned NSURLAuthenticationMethodServerTrust without an
  attached trust object. Now guarded.
- parseMailSubjectAndBody had three force-unwraps on string indexing
  that crashed on any mailto URL missing '=' or '&'. Third-party
  content frequently produces malformed mailto links. Now returns
  empty tuple instead of crashing.

WKNavigationDelegate CONTRACT FIXES:
- iOS 13+ decidePolicyFor: the 'guard let urlString' early-return
  silently dropped the decisionHandler call. WKWebView REQUIRES
  exactly one call to decisionHandler per delegate invocation,
  otherwise it asserts and crashes in debug / hangs in release.
- Legacy iOS 12 decidePolicyFor: called decisionHandler(.allow)
  twice in the .linkActivated branch (once explicit, once at end
  of function). Also asserts. Refactored with defer to guarantee
  single call.

CLEANUP:
- Removed debug 'VuukleManager is being deallocated' print in deinit.
- Removed 'BASE URL Did Finish' navigation print that fired on every
  page load.
Codifies the crash fixes from the prior commits as runnable assertions
so Codemagic / any Mac CI gives you a green/red signal that the fixes
work and haven't regressed.

Each test maps to a specific crash point:

- testVuukleSDKVersionConstantExists - VuukleWidget.swift placeholder
  removal (was shipping 'Hello, World! ffff fasfdasfassdasdasds' in
  production).
- testBaseWebViewCanBeDecodedFromCoder - storyboard support; previously
  fatalError'd in init?(coder:).
- testMailtoParsingHandlesMalformedInputWithoutCrash - the three
  force-unwraps in parseMailSubjectAndBody on firstIndex(of:'=')!,
  firstIndex(of:'&')!, lastIndex(of:'=')!.
- testVuukleManagerDeallocatesSilently - deinit no longer prints debug
  noise.
- testDecidePolicyForAlwaysCallsDecisionHandler - WKNavigationDelegate
  contract: decisionHandler must be called exactly once. Previous code
  had paths that called it zero times (causing assert) or twice
  (causing assert).

To run locally:
    swift test --package-path .

Or push the branch and let Codemagic run them.
SENTRY (sentry-cocoa 8.36+):
- Added as SPM dependency on Package.swift.
- Initialized lazily inside VuukleManager init - only fires once per
  process and only if the host app has set VuukleSentryDSN in their
  Info.plist (empty/absent = no telemetry, no overhead).
- If the host app already initialized SentrySDK, we don't re-init.
- Every event tagged with source=vuukle-sdk plus the SDK version.
- WKNavigationDelegate didFail* callbacks now ALSO send the error to
  Sentry before forwarding to addErrorListener. Publishers get what
  they always got; we additionally see the stack.

GITHUB ACTIONS (.github/workflows/ci.yml):
- Runs on macos-14 with Xcode 15.4 on every PR and push to main.
- swift package resolve / swift build / swift test.
- Catches the kind of issue Ravi's gradle build caught for Android.

CODEMAGIC (codemagic.yaml):
- Auto-trigger on PR/push to claude/* and main.
- Runs the XCTest suite on Mac Mini M2 in their cloud.
- Vuukle already uses Codemagic for Quizzly, so the org/project is set
  up - the YAML just needs to be enabled in the Codemagic UI for this
  repo (Apps → Add app → point at github.com/vuukle/ios-widgets-vuukle).

CONFIGURATION FOR PUBLISHERS:
Add to Info.plist:
    <key>VuukleSentryDSN</key>
    <string>https://...your-dsn...@o485102.ingest.sentry.io/...</string>

Empty / absent = telemetry disabled.
CI failed: 'swift build' / 'swift test' default to the host platform
(macOS) but VuukleWidget imports UIKit which is iOS-only.

Switched both GitHub Actions and Codemagic to xcodebuild with an iOS
Simulator destination. Removed xcpretty (not preinstalled on macos-14
runner). Added 'set -o pipefail' so any build step failure is surfaced.

This is exactly the kind of breakage we want CI catching before
publishers ever see it.
The autogenerated VuukleWidgetTests.swift asserted on
VuukleWidget().text == 'Hello, World!' - but VuukleWidget.swift was
cleaned up in commit 63e5634 (placeholder garbage removed). Test
left pointing at a non-existent symbol, failing CI.

Replaced with a proper semver assertion on VuukleSDK.version. Real
regression coverage for the crash fixes lives in VuukleCrashFixTests.
I had used 'https://about:blank' which is not a valid URL (colon after
about isn't a valid host char). URL(string:)! returned nil and the
force-unwrap crashed the test.

Replaced with 'https://example.com' which parses cleanly. The test
isn't about URL content - it's about whether WKWebView's decisionHandler
contract is honored.

5/6 tests already pass on macos-14 + iPhone 15 sim. This makes it 6/6.
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