Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/__tests__/deviceType.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { resolveExpoDeviceType } from "../lib/event/EventFactory";

/**
* getDeviceInfo()'s Expo branch runs when EITHER expo-device or
* expo-application is installed. With only expo-application present,
* `ExpoDevice` is null, so `ExpoDevice?.deviceType` and
* `ExpoDevice?.DeviceType?.TABLET` are both undefined — and the original
* `===` comparison reported every such device as a tablet. That flowed into
* context.device_type, flipped the synthesized user agent to iPad/Tablet, and
* showed real phones under Tablet in the dashboard's device breakdown.
*
* expo-device's DeviceType enum: UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4.
*/
describe("resolveExpoDeviceType", () => {
it("returns mobile when expo-device is absent (both args undefined)", () => {
expect(resolveExpoDeviceType(undefined, undefined)).toBe("mobile");
});

it("returns mobile when only the enum is missing", () => {
expect(resolveExpoDeviceType(2, undefined)).toBe("mobile");
});

it("returns mobile when only the device type is missing", () => {
expect(resolveExpoDeviceType(undefined, 2)).toBe("mobile");
});

it("returns tablet for a real tablet", () => {
expect(resolveExpoDeviceType(2, 2)).toBe("tablet");
});

it("returns mobile for a phone", () => {
expect(resolveExpoDeviceType(1, 2)).toBe("mobile");
});

it("returns mobile for DeviceType.UNKNOWN (0), not tablet", () => {
// Guards the null-check being written as a falsy check: 0 is a valid enum
// member and must not be treated as "missing" in a way that changes intent.
expect(resolveExpoDeviceType(0, 2)).toBe("mobile");
});

it("returns mobile for desktop and TV form factors", () => {
expect(resolveExpoDeviceType(3, 2)).toBe("mobile");
expect(resolveExpoDeviceType(4, 2)).toBe("mobile");
});

it("handles null the same as undefined", () => {
expect(resolveExpoDeviceType(null, null)).toBe("mobile");
});
});
25 changes: 23 additions & 2 deletions src/lib/event/EventFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ export function getSessionId(): string {
* containing the tokens the classifier keys off (iphone/ipad/android + version)
* so mobile device and OS resolve correctly. Returns "" for unknown platforms.
*/
/**
* Resolve `device_type` from expo-device's `deviceType` enum.
*
* Extracted and guarded because the Expo branch of getDeviceInfo() runs when
* EITHER expo-device or expo-application is installed, so `deviceType` may be
* undefined. A bare `deviceType === DeviceType.TABLET` is then
* `undefined === undefined` — true — and every device reports as a tablet,
* which also flips the synthesized user agent to iPad/Tablet and corrupts
* device breakdowns downstream. Unknown means "mobile", the safe default for a
* React Native app.
*/
export function resolveExpoDeviceType(
deviceType: number | null | undefined,
tabletEnumValue: number | null | undefined,
): "tablet" | "mobile" {
if (deviceType == null || tabletEnumValue == null) return "mobile";
return deviceType === tabletEnumValue ? "tablet" : "mobile";
}

export function synthesizeUserAgent(info: {
os_name: string;
os_version: string;
Expand Down Expand Up @@ -324,11 +343,13 @@ class EventFactory implements IEventFactory {
// Fall back to Expo modules (Expo Go)
if (ExpoDevice || ExpoApplication) {
try {
const isTablet = ExpoDevice?.deviceType === ExpoDevice?.DeviceType?.TABLET;
const os_name = ExpoDevice?.osName || Platform.OS;
const os_version = ExpoDevice?.osVersion || String(Platform.Version);
const device_model = ExpoDevice?.modelName || "Unknown";
const device_type = isTablet ? "tablet" : "mobile";
const device_type = resolveExpoDeviceType(
ExpoDevice?.deviceType,
ExpoDevice?.DeviceType?.TABLET,
);
return {
os_name,
os_version,
Expand Down