# All files / src/utils device.ts

0% Statements 0/82
0% Branches 0/62
0% Functions 0/9
0% Lines 0/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132

     /**
     * Browser device fingerprint utilities for stable device registration in the frontend.
     * Stores a stable device_id and a human-friendly device_name in localStorage.
     */

    function safeLocalStorageGet(key: string): string | null {
      try {
        if (typeof window === 'undefined') return null;
        return window.localStorage.getItem(key);
      } catch {
        return null;
      }
    }

    function safeLocalStorageSet(key: string, value: string): void {
      try {
        if (typeof window === 'undefined') return;
        window.localStorage.setItem(key, value);
      } catch {
        /* ignore */
      }
    }

    function detectOS(): string {
      if (typeof navigator === 'undefined') return 'Unknown OS';
      const ua = navigator.userAgent || '';
      const platform = (navigator as any).platform || '';

      if (/android/i.test(ua)) return 'Android';
      if (/iphone|ipad|ipod/i.test(ua)) return 'iOS';
      if (/win/i.test(platform)) return 'Windows';
      if (/mac/i.test(platform)) return 'macOS';
      if (/linux/i.test(platform)) return 'Linux';
      return 'Unknown OS';
    }

    function detectBrowser(): string {
      if (typeof navigator === 'undefined') return 'Browser';
      const ua = navigator.userAgent || '';

      if (/edg/i.test(ua)) return 'Edge';
      if (/chrome|crios/i.test(ua) && !/edg/i.test(ua)) return 'Chrome';
      if (/firefox|fxios/i.test(ua)) return 'Firefox';
      if (/safari/i.test(ua) && !/chrome|crios/i.test(ua)) return 'Safari';
      if (/opr\//i.test(ua)) return 'Opera';
      return 'Browser';
    }

    function defaultDeviceName(): string {
      const os = detectOS();
      const br = detectBrowser();
      return `${os} · ${br}`;
    }

    function generateDeviceId(): string {
      try {
        if (typeof crypto !== 'undefined' && (crypto as any).randomUUID) {
          return (crypto as any).randomUUID();
        }
      } catch {
        // ignore
      }

      // Fallback: pseudo-random plus weak fingerprint
      try {
        const pieces: string[] = [];
        if (typeof navigator !== 'undefined') {
          pieces.push(navigator.userAgent || '');
          pieces.push((navigator as any).platform || '');
          pieces.push((navigator as any).language || '');
        }
        if (typeof screen !== 'undefined') {
          pieces.push(String(screen.width));
          pieces.push(String(screen.height));
          pieces.push(String((screen as any).colorDepth || ''));
        }
        pieces.push(String(Date.now()));
        const raw = pieces.join('|');

        // Simple hash
        let hash = 0;
        for (let i = 0; i < raw.length; i++) {
          hash = (hash << 5) - hash + raw.charCodeAt(i);
          hash |= 0;
        }
        const rand = Math.floor(Math.random() * 1e9).toString(36);
        return `web-${Math.abs(hash).toString(36)}-${rand}`;
      } catch {
        return `web-${Date.now().toString(36)}`;
      }
    }

    const STORAGE_KEYS = {
      DEVICE_ID: 'iptv_device_id',
      DEVICE_NAME: 'iptv_device_name'} as const;

    export function getOrCreateBrowserDevice(): { id: string; name: string } {
      // 1) Try to read existing
      let id = safeLocalStorageGet(STORAGE_KEYS.DEVICE_ID);
      let name = safeLocalStorageGet(STORAGE_KEYS.DEVICE_NAME);

      // 2) Create new if missing
      if (!id) {
        id = generateDeviceId();
        safeLocalStorageSet(STORAGE_KEYS.DEVICE_ID, id);
      }

      if (!name) {
        name = defaultDeviceName();
        safeLocalStorageSet(STORAGE_KEYS.DEVICE_NAME, name);
      }

      return { id, name };
    }

    /**
     * Update the stored device name (user-customizable).
     */
    export function setBrowserDeviceName(newName: string): void {
      const name = String(newName || '').trim() || defaultDeviceName();
      safeLocalStorageSet(STORAGE_KEYS.DEVICE_NAME, name);
    }

    /**
     * Force a new device id (not recommended, but available if needed).
     * Useful for debugging; production flows should keep device ID stable per browser profile.
     */
    export function resetBrowserDeviceId(): string {
      const id = generateDeviceId();
      safeLocalStorageSet(STORAGE_KEYS.DEVICE_ID, id);
      return id;
    }