Skip to content

feat(mobile): attach device info to every PostHog event - #4885

Merged
iscekic merged 2 commits into
mainfrom
mobile-device-analytics-7e5f
Jul 30, 2026
Merged

feat(mobile): attach device info to every PostHog event#4885
iscekic merged 2 commits into
mainfrom
mobile-device-analytics-7e5f

Conversation

@iscekic

@iscekic iscekic commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Every PostHog event from the mobile app now carries device metadata automatically — no call-site changes, no duplicated properties.

What: installs expo-device (~57.0.1) and adds one device_form_factor property via PostHog's customAppProperties constructor option.

Why: product analytics needs manufacturer, model, OS name/version, device type, and emulator-vs-device segmentation on mobile events.

How: posthog-react-native 4.59.0 ships an optional expo-device integration (dist/optional/OptionalExpoDevice.js, a try/catch require). Verified against the installed SDK source (dist/native-deps.js getAppProperties()): installing expo-device alone turns on auto-capture of $device_manufacturer, $device_name (marketing model name), $os_name, $os_version, $is_emulator on every event. Already captured before this change: $device_type (always the string 'Mobile' — no phone/tablet granularity), $app_name/$app_version/$app_build/$app_namespace, $locale/$timezone, $screen_width/$screen_height.

The one real gap is form factor, so this PR adds device_form_factor ('phone' | 'tablet' | 'desktop' | 'tv' | 'unknown' — a total mapping of Device.DeviceType + null, always present).

Decisions:

  • customAppProperties, not client.register — registered super properties are cleared by client.reset() on logout and memoized init never re-registers; _appProperties (where customAppProperties merges) survive reset and land on every event including the first, synchronously.
  • No $device_type override — it's reserved SDK vocabulary; we add a plainly-named property instead.
  • No brand propertyDevice.brand is a near-duplicate of $device_manufacturer with no extra segmentation value (YAGNI).
  • No person properties — the SDK's setDefaultPersonProperties already feeds flag evaluation; form-factor flag targeting is not a stated need.

Pre-existing observation (not fixed here, out of scope): the existing client.register({ platform: 'mobile' }) super property has the same reset gap — after resetAnalyticsUser() on logout, platform is gone until the app restarts. That behavior predates this PR and is unchanged.

Verification

No manual runtime testing — E2E is skipped as inert: analytics is disabled in every locally buildable configuration (disabled: __DEV__ in initPostHog), so dev builds send no events and there is no observable runtime surface on simulator/emulator. Verification is unit tests plus the pinned SDK source contract.

  • Unit tests (apps/mobile/src/lib/analytics/posthog.test.ts): total device_form_factor enum mapping over every DeviceType + null; auto-captured properties preserved (spread, not replaced); platform: 'mobile' registration unchanged; init memoization unchanged
  • Peer-resolution check: expo-device@57.0.1 resolvable from posthog-react-native's package context (the SDK's optional require('expo-device') resolves; expo-device is a declared optional peer >= 4.0.0)
  • pnpm format, pnpm typecheck, pnpm lint, pnpm check:unused, pnpm test from apps/mobile/ — all pass (288 files / 2466 tests)
  • pnpx expo-doctor before/after — byte-identical output; the 13 version mismatches below are pre-existing on main and intentionally not fixed here
expo-doctor output (before = after, pre-existing on main)
✖ Check that packages match versions required by installed Expo SDK

🔧 Patch version mismatches
package                  expected  found
expo                     ~57.0.9   57.0.8
expo-build-properties    ~57.0.8   57.0.7
expo-constants           ~57.0.8   57.0.7
expo-dev-client          ~57.0.10  57.0.9
expo-image-picker        ~57.0.7   57.0.6
expo-insights            ~57.0.7   57.0.6
expo-location            ~57.0.7   57.0.6
expo-notifications       ~57.0.8   57.0.7
expo-router              ~57.0.9   57.0.8
expo-sharing             ~57.0.8   57.0.7
react-native             0.86.2    0.86.0
react-native-reanimated  4.5.1     4.5.0
react-native-worklets    0.10.1    0.10.0

13 packages out of date.
1 check failed, indicating possible issues with the project.

Note: expo-device is a native module — a new EAS dev-client build is required before the app runs with it.

Visual Changes

N/A

Reviewer Notes

  • Two commits: install+lockfile separate from source+test.
  • The payload rule in the module header ("stable enum strings only") binds our own payloads; the SDK's stock auto-captured device fields are an intentional exception — still no PII beyond stock device metadata.
  • "Model" in analytics is $device_name (marketing name like "iPhone 12"); $device_model is not populated on the expo-device path.

@iscekic iscekic self-assigned this Jul 30, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the expo-device install and the new device_form_factor property wired through PostHog's customAppProperties; the mapping is total, the reserved $device_type is left untouched, auto-captured properties are preserved by the spread, and the added test project glob only picks up the new suite — no correctness, security, or leak issues found in changed code.

Notes and assumptions

  • node_modules was not installed in the review sandbox and external package downloads were blocked, so the posthog-react-native@4.59.0 contract (customAppProperties accepting a function, $device_name sourcing from modelName rather than the user-assigned device name) and the existence of a synchronous Device.deviceType were taken from the PR description plus the author's passing pnpm typecheck, not independently verified.
  • pnpm-lock.yaml contains churn unrelated to this change (@types/node 24.12.4 -> 25.5.2 in the jest/vitest resolution keys, and @cloudflare/vitest-pool-workers entries losing their @cloudflare/workers-types peer key across several services). Lockfiles are out of review scope per REVIEW.md, so this is reported as an observation only, not a finding.
Files Reviewed (5 files)
  • apps/mobile/src/lib/analytics/posthog.ts
  • apps/mobile/src/lib/analytics/posthog.test.ts
  • apps/mobile/vitest.pure.config.ts
  • apps/mobile/package.json
  • pnpm-lock.yaml (generated; skipped per review rules)

Reviewed by claude-opus-5 · Input: 34 · Output: 12.9K · Cached: 896.7K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Jul 30, 2026
@iscekic
iscekic enabled auto-merge (squash) July 30, 2026 12:13
@iscekic
iscekic merged commit 1cca7d6 into main Jul 30, 2026
71 checks passed
@iscekic
iscekic deleted the mobile-device-analytics-7e5f branch July 30, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants