Skip to content

Fix user-agent, identify-merge, and thread-safety bugs; declare tvOS#7

Open
srna wants to merge 6 commits into
Openpanel-dev:mainfrom
srna-lib:bugfix/macos-fixes
Open

Fix user-agent, identify-merge, and thread-safety bugs; declare tvOS#7
srna wants to merge 6 commits into
Openpanel-dev:mainfrom
srna-lib:bugfix/macos-fixes

Conversation

@srna

@srna srna commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Six small, independent fixes — one commit each — found while reviewing the SDK. No public API changes.

Fixes

  1. macOS user agent built from structured operatingSystemVersion instead of splitting the localized operatingSystemVersionString, which Apple documents as display-only and not machine-parseable.
  2. sdkVersion aligned with the release version (0.0.1 → 1.0.1). The string goes into the openpanel-sdk-version header and every user agent; it had never been bumped past 0.0.1 even though the repo is tagged 1.0.0.
  3. iOS user-agent fallback made reachable. The isEmpty check ran after appending OpenPanel/<version>, so the string was never empty and getBasicUserAgent() was dead code — a WKWebView failure/timeout produced a UA of just OpenPanel/0.0.1. The check now runs first.
  4. identify property merge no longer uses AnyObject dynamic lookup. (new as AnyObject).value resolved via the ObjC runtime against a boxed Swift struct, returning nil at runtime and corrupting merged properties (the compiler flagged it with an Any?? coercion warning). Replaced with (new as? AnyCodable)?.value ?? new; the build is now warning-free.
  5. Pending-event queue synchronized. The waitForProfile buffer was appended and flushed from arbitrary threads with no synchronization, despite the README's thread-safety claim. Appends now go through the existing barrier queue and flush() snapshots-and-clears atomically.
  6. tvOS declared in Package.swift (+ UIKit import). The source already had os(tvOS) branches, but without the platform declaration they had never compiled.

Verification

swift build (macOS) with zero warnings; xcodebuild BUILD SUCCEEDED for iOS Simulator and tvOS Simulator.

Note on releasing

Please cut a new tag after merging: SPM users on .upToNextMajor(from: "1.0.0") are still resolving tag 1.0.0 and have never received the macOS compile fix (9b0c524) that is already on main. A new release finally ships it to them, along with these fixes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for tvOS 13 and later.
    • Updated SDK version information to 1.0.1.
  • Bug Fixes

    • Improved device and operating-system identification across iOS and macOS.
    • Fixed handling of queued events when waiting for profile information.
    • Improved property merging during profile identification.
    • Enhanced queue synchronization to help ensure events are captured and sent reliably.

srna and others added 6 commits July 19, 2026 20:29
ProcessInfo.operatingSystemVersionString is localized and human-readable
("Version 14.5 (Build 23F79)"), so splitting on spaces and taking index 1
breaks on non-English locales. Build the OS version token from
operatingSystemVersion's major/minor/patch components instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sdkVersion still reported 0.0.1 while the released tag is 1.0.0. This
string is sent in the openpanel-sdk-version header and embedded in every
user agent, so bump it to the next release version, 1.0.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The OpenPanel/<version> suffix was appended before the isEmpty check, so
when the WKWebView JS evaluation failed or timed out the user agent
became " OpenPanel/x.y.z" instead of falling back to
getBasicUserAgent(). Check for the empty result first, then append the
suffix (getBasicUserAgent already appends it itself).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(new as AnyObject).value resolved through AnyObject dynamic lookup,
producing an Any?? that was implicitly coerced to Any (compiler
warning) and left the merged value double-wrapped. Cast to AnyCodable
and unwrap its underlying value instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
send() appended to queue and flush() read/cleared it from arbitrary
threads with no synchronization. Route both through the existing
globalQueue barrier pattern: barrier-append in send(), atomic
snapshot-and-clear in flush().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The source already guards automatic tracking with #if os(iOS) || os(tvOS),
but the platform was never declared and UIKit was only imported for iOS.
Add .tvOS(.v13) and import UIKit on tvOS so the declared platforms match
the code (WebKit is unavailable on tvOS, so the generic user agent path
is used there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SDK declares tvOS support, updates platform-specific user-agent generation and the SDK version, synchronizes deferred payload queue access, and changes identify property merging to handle AnyCodable values explicitly.

Changes

SDK Runtime Updates

Layer / File(s) Summary
Platform and user-agent support
Package.swift, Sources/OpenPanel/OpenPanel.swift
The package adds tvOS 13 support, UIKit compilation for tvOS, revised iOS/macOS user-agent construction, and updates sdkVersion to 1.0.1.
Synchronized deferred event queue
Sources/OpenPanel/OpenPanel.swift
Deferred enqueueing and flush() queue capture now use barrier operations for synchronized access.
Identify property merge handling
Sources/OpenPanel/OpenPanel.swift
identify extracts values from AnyCodable inputs while preserving other incoming values.
Estimated code review effort: 3 (Moderate) ~20 minutes
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fixes and the new tvOS support without unnecessary noise.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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/OpenPanel/OpenPanel.swift`:
- Around line 48-52: Update the user-agent lookup used by initialize() so it
does not synchronously fetch or construct WKWebView on the caller thread,
especially the main thread. Make the lookup asynchronous or cache its result,
ensuring initialize() remains non-blocking while preserving the fallback user
agent with the OpenPanel SDK suffix.
🪄 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: c95aef42-6985-4f00-91d2-3ec1668671f6

📥 Commits

Reviewing files that changed from the base of the PR and between 3651435 and 85ce8dd.

📒 Files selected for processing (2)
  • Package.swift
  • Sources/OpenPanel/OpenPanel.swift

Comment on lines 48 to +52
if userAgent.isEmpty {
userAgent = getBasicUserAgent()
return getBasicUserAgent()
}
return userAgent

return userAgent + " OpenPanel/\(OpenPanel.sdkVersion)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if getBasicUserAgent already appends the SDK version
rg -A 5 'func getBasicUserAgent' Sources/OpenPanel/OpenPanel.swift

Repository: Openpanel-dev/swift-sdk

Length of output: 381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='Sources/OpenPanel/OpenPanel.swift'

echo '--- outline ---'
ast-grep outline "$file" --view expanded || true

echo '--- numbered excerpt ---'
sed -n '1,220p' "$file" | cat -n

Repository: Openpanel-dev/swift-sdk

Length of output: 12146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' Sources/OpenPanel/OpenPanel.swift | nl -ba

Repository: Openpanel-dev/swift-sdk

Length of output: 201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path('Sources/OpenPanel/OpenPanel.swift').read_text()
for needle in ['getBasicUserAgent', 'sdkVersion', 'semaphore.wait', 'DispatchQueue.main.async', 'WKWebView']:
    print(f'--- {needle} ---')
    idx = text.find(needle)
    if idx == -1:
        print('not found')
        continue
    start = max(0, text.rfind('\n', 0, idx-400))
    end = min(len(text), idx+800)
    snippet = text[start:end]
    print(snippet)
PY

Repository: Openpanel-dev/swift-sdk

Length of output: 6343


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='Sources/OpenPanel/OpenPanel.swift'

echo '--- call sites for DeviceInfo.getUserAgent ---'
rg -n 'DeviceInfo\.getUserAgent|getUserAgent\(' Sources/OpenPanel/OpenPanel.swift

echo '--- OpenPanel.initialize excerpt ---'
sed -n '298,380p' "$file" | cat -n

Repository: Openpanel-dev/swift-sdk

Length of output: 3870


Sources/OpenPanel/OpenPanel.swift:30-52 — Avoid blocking initialize() while fetching the user agent. The fallback already appends OpenPanel/\(OpenPanel.sdkVersion), but this path can still block the caller for up to 1s if initialize() runs on the main thread, and WKWebView is created before the DispatchQueue.main.async hop. Make the lookup async or cache the result instead.

🤖 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/OpenPanel/OpenPanel.swift` around lines 48 - 52, Update the
user-agent lookup used by initialize() so it does not synchronously fetch or
construct WKWebView on the caller thread, especially the main thread. Make the
lookup asynchronous or cache its result, ensuring initialize() remains
non-blocking while preserving the fallback user agent with the OpenPanel SDK
suffix.

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.

1 participant