client.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { Dimensions, Platform } from 'react-native';
  2. import Constants from 'expo-constants';
  3. import i18n from 'i18next';
  4. import type { AnalyticsClient } from './analytics.types';
  5. import { cleanUrl } from './privacy';
  6. type ApplicationModule = {
  7. applicationId?: string | null;
  8. nativeApplicationVersion?: string | null;
  9. };
  10. // Guarded: binaries built before expo-application was added have no native module.
  11. let Application: ApplicationModule | null = null;
  12. try {
  13. Application = require('expo-application');
  14. } catch {
  15. Application = null;
  16. }
  17. let client: AnalyticsClient | null = null;
  18. export function isClientAvailable(): boolean {
  19. return true;
  20. }
  21. /** Device language as a tag UNA accepts (`en`, `pt-BR`, `zh-Hans`), or the app language. */
  22. function getLanguage(): string {
  23. let locale = '';
  24. try {
  25. locale = Intl.DateTimeFormat().resolvedOptions().locale || '';
  26. } catch {
  27. locale = '';
  28. }
  29. const match = /^([a-zA-Z]{2,3})(?:[-_]([a-zA-Z0-9]{2,4}))?/.exec(locale || i18n.language || '');
  30. if (!match) return '';
  31. return match[2] ? `${match[1]}-${match[2]}` : (match[1] ?? '');
  32. }
  33. /** App, system and device: the apps have no browser user agent for UNA to read them from. */
  34. export function getClient(): AnalyticsClient {
  35. if (client) return client;
  36. const screen = Dimensions.get('screen');
  37. const ios = Platform.OS === 'ios';
  38. const tablet = Platform.OS === 'ios' ? Platform.isPad : Math.min(screen.width, screen.height) >= 600;
  39. const osVersion = Platform.OS === 'android' ? String(Platform.constants?.Release ?? '') : String(Platform.Version ?? '');
  40. const expoConfig = Constants.expoConfig;
  41. client = {
  42. platform: ios ? 'rn-ios' : 'rn-android',
  43. app: Application?.applicationId || (ios ? expoConfig?.ios?.bundleIdentifier : expoConfig?.android?.package) || expoConfig?.slug || '',
  44. app_version: Application?.nativeApplicationVersion || expoConfig?.version || '',
  45. language: getLanguage(),
  46. screen: `${Math.round(screen.width)}x${Math.round(screen.height)}`,
  47. os: ios ? (tablet ? 'iPadOS' : 'iOS') : 'Android',
  48. os_version: /^[0-9.]+$/.test(osVersion) ? osVersion : '',
  49. device: tablet ? 'tablet' : 'mobile',
  50. };
  51. return client;
  52. }
  53. /** Stack frames of an app have no site origin to drop. */
  54. export function getOwnOrigin(): string {
  55. return '';
  56. }
  57. /** The screen an error or vitals hit belongs to: its UNA page path without the query string. */
  58. export function getHealthPageUrl(viewUrl: string): string {
  59. return cleanUrl(viewUrl) || '/';
  60. }
  61. /** The path events are sent with before the first screen view. */
  62. export function getFallbackUrl(): string {
  63. return '/';
  64. }
  65. export function getInitialReferrer(): string {
  66. return '';
  67. }
  68. /** Apps have no Do Not Track signal. */
  69. export function isTrackingBlocked(_respectDnt: boolean): boolean {
  70. return false;
  71. }