• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mimmi20 / browser-detector / 21621132528

03 Feb 2026 05:25AM UTC coverage: 95.958% (+0.7%) from 95.301%
21621132528

push

github

web-flow
Merge pull request #1030 from mimmi20/updates

add new clients and devices

693 of 698 new or added lines in 20 files covered. (99.28%)

42 existing lines in 4 files now uncovered.

12298 of 12816 relevant lines covered (95.96%)

60.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.18
/src/Parser/Header/UseragentDeviceCode.php
1
<?php
2

3
/**
4
 * This file is part of the browser-detector package.
5
 *
6
 * Copyright (c) 2012-2026, Thomas Mueller <mimmi20@live.de>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types = 1);
13

14
namespace BrowserDetector\Parser\Header;
15

16
use BrowserDetector\Parser\Helper\DeviceInterface;
17
use Override;
18
use UaNormalizer\Normalizer\Exception\Exception;
19
use UaNormalizer\Normalizer\NormalizerInterface;
20
use UaParser\DeviceCodeInterface;
21
use UaParser\DeviceParserInterface;
22

23
use function array_filter;
24
use function array_key_exists;
25
use function array_map;
26
use function mb_strtolower;
27
use function preg_match;
28
use function reset;
29

30
final readonly class UseragentDeviceCode implements DeviceCodeInterface
31
{
32
    /** @throws void */
33
    public function __construct(
27✔
34
        private DeviceParserInterface $deviceParser,
35
        private NormalizerInterface $normalizer,
36
        private DeviceInterface $deviceCodeHelper,
37
    ) {
38
        // nothing to do
39
    }
27✔
40

41
    /**
42
     * @throws void
43
     *
44
     * @phpcs:disable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
45
     */
46
    #[Override]
27✔
47
    public function hasDeviceCode(string $value): bool
48
    {
49
        return true;
27✔
50
    }
51

52
    /**
53
     * @return non-empty-string|null
54
     *
55
     * @throws void
56
     */
57
    #[Override]
27✔
58
    public function getDeviceCode(string $value): string | null
59
    {
60
        if (preg_match('/Android \d+; [A-Za-z0-9]{10}; U; [^;)]*\) AppleWebKit\/.+Chrome\//', $value)) {
27✔
61
            return 'unknown=general mobile phone';
1✔
62
        }
63

64
        try {
65
            $normalizedValue = $this->normalizer->normalize($value);
26✔
66
        } catch (Exception) {
×
UNCOV
67
            return null;
×
68
        }
69

70
        if ($normalizedValue === '' || $normalizedValue === null) {
26✔
UNCOV
71
            return null;
×
72
        }
73

74
        $matches = [];
26✔
75

76
        if (
77
            preg_match('/^WhatsApp\/[0-9.]+ (?P<code>[AW])$/', $normalizedValue, $matches)
26✔
78
            && array_key_exists('code', $matches)
26✔
79
        ) {
80
            return match ($matches['code']) {
2✔
81
                'W' => 'unknown=windows desktop',
2✔
82
                default => 'unknown=general mobile phone',
2✔
83
            };
2✔
84
        }
85

86
        $regexes = [
24✔
87
            '/^mozilla\/[\d.]+ \((?:andr[o0]id|tizen) [\d.]+;(?: arm(?:_64)?;| harmonyos;)? (?P<devicecode>[^);\/]+)(?:(?:\/[^ ]+)? +(?:build|hmscore))[^)]+\)/i',
24✔
88
            '/^mozilla\/[\d.]+ \((?:andr[o0]id|tizen) [\d.]+;(?: arm(?:_64)?;| harmonyos;)? (?P<devicecode>[^);\/]+)[^)]*\)/i',
24✔
89
            '/^mozilla\/[\d.]+ \(linux;(?: arm(?:_64)?;)? (?:andr[o0]id|tizen) [\d.]+;(?: arm(?:_64)?;| harmonyos;)? (?P<devicecode>[^);\/]+)(?:(?:\/[^ ]+)? +(?:build|hmscore))[^)]+\)/i',
24✔
90
            '/^mozilla\/[\d.]+ \(linux;(?: arm(?:_64)?;)? (?:andr[o0]id|tizen) [\d.]+;(?: arm(?:_64)?;| harmonyos;)? (?P<devicecode>[^);\/]+)[^)]*\)/i',
24✔
91
            '/(?:androiddownloadmanager|mozilla|com\.[^\/]+|kodi|androidhttpclient)\/[\d.]+ \(linux; (?:(?:andr[o0]id|tizen) [\d.]+;(?: harmonyos;)?) (?P<devicecode>[^);\/]+)(?:;? +(?:build|hmscore))[^)]+\)/i',
24✔
92
            '/(?:androiddownloadmanager|mozilla|com\.[^\/]+|kodi|androidhttpclient)\/[\d.]+ \(linux; (?:(?:andr[o0]id|tizen) [\d.]+;(?: harmonyos;)?) (?P<devicecode>[^);\/]+)[^)]*\)/i',
24✔
93
            '/dalvik\/[\d.]+ \(linux; (?:andr[o0]id [\d.]+;) (?P<devicecode>[^);\/]+)(?:[);\/]?[^);\/]* +(?:build|hmscore|miui)[^)]+)\)/i',
24✔
94
            '/dalvik\/[\d.]+ \(linux; andr[o0]id [\d.]+\/viber [\d.]+ ; (?P<devicecode>[^);\/]+)[su]p1a/i',
24✔
95
            '/ucweb\/[\d.]+ \((?:midp-2\.0|linux); (?:adr [\d.]+;) (?P<devicecode>[^);\/]+)(?:[^)]+)?\)/i',
24✔
96
            '/;fbdv\/(?P<devicecode>[^);\/]+);/i',
24✔
97
            '/slack\/[\d.]+ \((?P<devicecode>[^);\/]+)(?:;? (?:andr[o0]id|tizen) [\d.]+)(?:[^)]+)?\)/i',
24✔
98
            '/instagram [\d.]+ android \([\d.]+\/[\d.]+; \d+dpi; \d+x\d+; [a-z\/]+; (?P<devicecode>[^);\/]+);/i',
24✔
99
            '/icq_android\/[\d.]+ \(android; \d+; [\d.]+; [^;]+; (?P<devicecode>[^);\/]+)/i',
24✔
100
            '/gg-android\/[\d.]+ \(os;android;\d+\) \([^);\/]+;[^);\/]+;(?P<devicecode>[^);\/]+);[\d.]+/i',
24✔
101
            '/imoandroid\/[\d.]+; \d+; REL; (?P<devicecode>[^);\/]+)/i',
24✔
102
            '/tivimate\/[\d.]+ \((?P<devicecode>[^);\/]+);/i',
24✔
103
            '/; model: (?P<devicecode>[^);\/]+)\)/i',
24✔
104
            '/(lbc|heart)\/[\d.]+ andr[o0]id [\d.]+\/(?P<devicecode>[^);\/]+)/i',
24✔
105
            '/mozilla\/[\d.]+ \(mobile; (?P<devicecode>[^;]+)(?:;android)?; rv:[^)]+\) gecko\/[\d.]+ firefox\/[\d.]+ kaios\/[\d.]+/i',
24✔
106
            '/virgin radio\/[\d.]+ \/ \(linux; andr[o0]id [\d.]+\) exoplayerlib\/[\d.]+ \/ samsung \((?P<devicecode>[^)]+)\)/i',
24✔
107
            '/pugpigbolt [\d.]+ \([^);\/,]+, (android|ios) [\d.]+\) on phone \(model (?P<devicecode>[^)]+)\)/i',
24✔
108
            '/nrc audio\/[\d.]+ \(nl\.nrc\.audio; build:[\d.]+; andr[o0]id [\d.]+; sdk:[\d.]+; manufacturer:samsung; model: (?P<devicecode>[^)]+)\) okhttp\/[\d.]+/i',
24✔
109
            '/luminary\/[\d.]+ \(andr[o0]id [\d.]+; (?P<devicecode>[^);\/]+); /i',
24✔
110
            '/emaudioplayer [\d.]+ \([\d.]+\) \/ andr[o0]id [\d.]+ \/ (?P<devicecode>[^);\/]+)/i',
24✔
111
            '/andr[o0]id [\d.]+; (?P<devicecode>[^);\/]+)\) applewebkit/i',
24✔
112
            '/classic fm\/[\d.]+ andr[o0]id [\d.]+\/(?P<devicecode>[^);\/]+)/i',
24✔
113
            '/mozilla\/[\d.]+ \([\d.]+mb; [\d.]+x[\d.]+; [\d.]+x[\d.]+; [\d.]+x[\d.]+; (?P<devicecode>[^);\/]+); [\d.]+\) applewebkit/i',
24✔
114
            '/kodi\/[\d.]+ \(linux; andr[o0]id [\d.]+; (?P<devicecode>[^);\/]+)(?:[);\/]?[^);\/]* +(?:build|hmscore|miui)[^)]+)\)/i',
24✔
115
            '/androidhttpclient \(linux; (?:(?:andr[o0]id|tizen) [\d.]+;(?: harmonyos;)?) (?P<devicecode>[^);\/]+)(?:;? +(?:build|hmscore))[^)]+\)/i',
24✔
116
        ];
24✔
117

118
        $filtered = array_filter(
24✔
119
            $regexes,
24✔
120
            static fn (string $regex): bool => (bool) preg_match($regex, $normalizedValue),
24✔
121
        );
24✔
122

123
        $results = array_map(
24✔
124
            function (string $regex) use ($normalizedValue): string | null {
24✔
125
                $matches = [];
8✔
126

127
                preg_match($regex, $normalizedValue, $matches);
8✔
128

129
                return $this->deviceCodeHelper->getDeviceCode(
8✔
130
                    mb_strtolower($matches['devicecode'] ?? ''),
8✔
131
                );
8✔
132
            },
24✔
133
            $filtered,
24✔
134
        );
24✔
135

136
        $code = reset($results);
24✔
137

138
        if ($code !== null && $code !== false) {
24✔
139
            return $code;
8✔
140
        }
141

142
        $matches = [];
16✔
143

144
        if (
145
            preg_match(
16✔
146
                '/dv\((?P<devicecode>[^);\/]+)(?:;? +(?:build|hmscore|miui)?[^)]+)?\);/',
16✔
147
                $normalizedValue,
16✔
148
                $matches,
16✔
149
            )
16✔
150
        ) {
151
            $code = $this->deviceCodeHelper->getDeviceCode(mb_strtolower($matches['devicecode']));
10✔
152

153
            if ($code !== null) {
10✔
154
                return $code;
9✔
155
            }
156

157
            $code = $this->deviceParser->parse($matches['devicecode']);
1✔
158

159
            if ($code !== '') {
1✔
160
                return $code;
1✔
161
            }
162
        }
163

164
        $code = $this->deviceParser->parse($normalizedValue);
6✔
165

166
        if ($code === '') {
6✔
UNCOV
167
            return null;
×
168
        }
169

170
        return $code;
6✔
171
    }
172
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc