Replace fabricated user agents with structured device info; add watchOS#8
Replace fabricated user agents with structured device info; add watchOS#8srna wants to merge 10 commits into
Conversation
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>
The SDK previously impersonated Safari with hand-built Mozilla strings and, on iOS, spun up a WKWebView and blocked on a semaphore to read navigator.userAgent — a guaranteed 1s main-thread stall when initialized on the main thread. The backend's UA parser has a sanctioned format for app traffic (Model=<hw>; Manufacturer=<vendor>), which sets device brand/model and prevents the request from being classified as server traffic. Emit OpenPanel/<version> (Model=<hw>; Manufacturer=Apple) on every platform, using sysctl hw.model on macOS and utsname machine elsewhere (with the SIMULATOR_MODEL_IDENTIFIER override under the simulator). This removes the WebKit dependency entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge __os, __osVersion, __device, __brand, and __model into global properties at initialize(). The backend prefers these property overrides to user-agent parsing, and the values follow the ua-parser-js v2 vocabulary (macOS/iOS/tvOS/watchOS; mobile/tablet/desktop/smarttv/wearable) so app traffic lands in the same dashboard buckets as web traffic. Device defaults are seeded beneath user-set globals so setGlobalProperties always wins, and clear() re-seeds them instead of wiping them with the user state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The README has always advertised watchOS, but the package never declared the platform and the source had no watch code paths. Declare watchOS 7 (needed for the WKExtension lifecycle notifications), import WatchKit, and register app_opened/app_closed observers on applicationDidBecomeActive and applicationDidEnterBackground. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The OperationQueue with maxConcurrentOperationCount = 1 never serialized anything: each BlockOperation only spawned an unawaited Task and finished immediately, so requests raced and could arrive out of order (a track overtaking the identify before it). Chain sends instead — each Task awaits the previous one before hitting the network, with the chain tail guarded by the same barrier queue as the rest of the shared state. The profile id is now also resolved at track time rather than at network time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 59 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Replaces all user-agent fabrication with the structured device-info format the backend already understands. SDK-only — no backend changes needed. Every claim below was verified by running the actual backend parser (
packages/common/server/parser-user-agent.ts) and bot detector against the new UAs.What changes
1. One deterministic UA on every platform (
ed1045e)The
Model=…; Manufacturer=…form is the backend's sanctioned app-traffic format (APP_MODEL_REGEX): it sets device model/vendor and defeats theisServerclassification. Verified parse results for iPhone15,2, iPad13,1, MacBookPro18,3, AppleTV14,1, and Watch6,9: correct OS/device/brand/model,isServer: false, and none match the bot list.This deletes the WKWebView
navigator.userAgentevaluation (which blocked the main thread on a semaphore for a guaranteed 1 s when initialized on the main thread — the async block couldn't run until the wait timed out), both hand-built fake Safari UA strings, and the WebKit import. Hardware model comes fromsysctl hw.modelon macOS,utsname.machineelsewhere,SIMULATOR_MODEL_IDENTIFIERunder simulators.2. Structured device properties on every event (
8e44f3c)initialize()seeds__os,__osVersion,__device,__brand,__modelinto global properties — the overrides the backend prefers to UA parsing. Values follow the ua-parser-js v2 vocabulary ("macOS"not"Mac OS";mobile/tablet/desktop/smarttv/wearable) so app traffic shares dashboard buckets with web traffic. Device defaults sit under user globals (setGlobalPropertiesalways wins), andclear()re-seeds them rather than wiping them with user state.This also fixes tvOS, which previously fell to a generic UA that the backend classified as server traffic — no sessions, no OS breakdown.
3. watchOS support (
627498a)The README has always advertised watchOS but the package never declared it. Adds
.watchOS(.v7)(needed for theWKExtensionlifecycle notifications), plusapp_opened/app_closedobservers.4. Real send serialization (
6fdbea9)The
OperationQueuewithmaxConcurrentOperationCount = 1never serialized requests — eachBlockOperationspawned an unawaitedTaskand finished immediately, so events could reach the server out of order (atrackovertaking its precedingidentify). Sends are now a task chain: each awaits the previous one, with the chain tail guarded by the existing barrier queue. Profile id is resolved at track time rather than network time.Behavior changes to release-note
deviceId = sha256(ua:ip:origin:salt), and the UA changes — existing anonymous visitors get a new deviceId after upgrading. Identified profiles are unaffected.Verification
swift build(macOS) andxcodebuildfor iOS, tvOS, and watchOS simulators: BUILD SUCCEEDED, zero warnings on all four platforms.🤖 Generated with Claude Code