Skip to content

Commit f517d79

Browse files
crynoboneStyleCIBottaylorotwell
authored
[4.x] Replace jenssegers/agent and use latest mobiledetect/mobiledetectlib (#1399)
* wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Apply fixes from StyleCI * Update Agent.php --------- Signed-off-by: Mior Muhammad Zaki <[email protected]> Co-authored-by: StyleCI Bot <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent e91da52 commit f517d79

File tree

5 files changed

+279
-12
lines changed

5 files changed

+279
-12
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
"ext-json": "*",
1919
"illuminate/console": "^10.17",
2020
"illuminate/support": "^10.17",
21-
"jenssegers/agent": "^2.6",
22-
"laravel/fortify": "^1.15"
21+
"laravel/fortify": "^1.15",
22+
"mobiledetect/mobiledetectlib": "^4.8"
2323
},
2424
"require-dev": {
2525
"inertiajs/inertia-laravel": "^0.6.5",

src/Agent.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
namespace Laravel\Jetstream;
4+
5+
use Closure;
6+
use Detection\Cache\CacheException;
7+
use Detection\Exception\MobileDetectException;
8+
use Detection\MobileDetect;
9+
10+
/**
11+
* @copyright Originally created by Jens Segers: https://github.com/jenssegers/agent
12+
*/
13+
class Agent extends MobileDetect
14+
{
15+
/**
16+
* List of additional operating systems.
17+
*
18+
* @var array<string, string>
19+
*/
20+
protected static $additionalOperatingSystems = [
21+
'Windows' => 'Windows',
22+
'Windows NT' => 'Windows NT',
23+
'OS X' => 'Mac OS X',
24+
'Debian' => 'Debian',
25+
'Ubuntu' => 'Ubuntu',
26+
'Macintosh' => 'PPC',
27+
'OpenBSD' => 'OpenBSD',
28+
'Linux' => 'Linux',
29+
'ChromeOS' => 'CrOS',
30+
];
31+
32+
/**
33+
* List of additional browsers.
34+
*
35+
* @var array<string, string>
36+
*/
37+
protected static $additionalBrowsers = [
38+
'Opera Mini' => 'Opera Mini',
39+
'Opera' => 'Opera|OPR',
40+
'Edge' => 'Edge|Edg',
41+
'Coc Coc' => 'coc_coc_browser',
42+
'UCBrowser' => 'UCBrowser',
43+
'Vivaldi' => 'Vivaldi',
44+
'Chrome' => 'Chrome',
45+
'Firefox' => 'Firefox',
46+
'Safari' => 'Safari',
47+
'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+',
48+
'Netscape' => 'Netscape',
49+
'Mozilla' => 'Mozilla',
50+
'WeChat' => 'MicroMessenger',
51+
];
52+
53+
/**
54+
* Get the platform name from the User Agent.
55+
*
56+
* @return string|null
57+
*/
58+
public function platform()
59+
{
60+
return $this->retrieveUsingCacheOrResolve('jetstream.platform', function () {
61+
return $this->findDetectionRulesAgainstUserAgent(
62+
$this->mergeRules(MobileDetect::getOperatingSystems(), static::$additionalOperatingSystems)
63+
);
64+
});
65+
}
66+
67+
/**
68+
* Get the browser name from the User Agent.
69+
*
70+
* @return string|null
71+
*/
72+
public function browser()
73+
{
74+
return $this->retrieveUsingCacheOrResolve('jetstream.browser', function () {
75+
return $this->findDetectionRulesAgainstUserAgent(
76+
$this->mergeRules(static::$additionalBrowsers, MobileDetect::getBrowsers())
77+
);
78+
});
79+
}
80+
81+
/**
82+
* Determine if the device is a desktop computer.
83+
*
84+
* @return bool
85+
*/
86+
public function isDesktop()
87+
{
88+
return $this->retrieveUsingCacheOrResolve('jetstream.desktop', function () {
89+
// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
90+
if (
91+
$this->getUserAgent() === static::$cloudFrontUA
92+
&& $this->getHttpHeader('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER') === 'true'
93+
) {
94+
return true;
95+
}
96+
97+
return ! $this->isMobile() && ! $this->isTablet();
98+
});
99+
}
100+
101+
/**
102+
* Match a detection rule and return the matched key.
103+
*
104+
* @return string|null
105+
*/
106+
protected function findDetectionRulesAgainstUserAgent(array $rules)
107+
{
108+
$userAgent = $this->getUserAgent();
109+
110+
foreach ($rules as $key => $regex) {
111+
if (empty($regex)) {
112+
continue;
113+
}
114+
115+
if ($this->match($regex, $userAgent)) {
116+
return $key ?: reset($this->matchesArray);
117+
}
118+
}
119+
120+
return null;
121+
}
122+
123+
/**
124+
* Retrieve from the given key from the cache or resolve the value.
125+
*
126+
* @param string $key
127+
* @param \Closure():mixed $callback
128+
* @return mixed
129+
*
130+
* @throws \Detection\Exception\MobileDetectException
131+
*/
132+
protected function retrieveUsingCacheOrResolve(string $key, Closure $callback)
133+
{
134+
try {
135+
$cacheKey = $this->createCacheKey($key);
136+
137+
if (! is_null($cacheItem = $this->cache->get($cacheKey))) {
138+
return $cacheItem->get();
139+
}
140+
141+
return tap(call_user_func($callback), function ($result) use ($cacheKey) {
142+
$this->cache->set($cacheKey, $result);
143+
});
144+
} catch (CacheException $e) {
145+
throw new MobileDetectException("Cache problem in for {$key}: {$e->getMessage()}");
146+
}
147+
}
148+
149+
/**
150+
* Merge multiple rules into one array.
151+
*
152+
* @param array $all
153+
* @return array<string, string>
154+
*/
155+
protected function mergeRules(...$all)
156+
{
157+
$merged = [];
158+
159+
foreach ($all as $rules) {
160+
foreach ($rules as $key => $value) {
161+
if (empty($merged[$key])) {
162+
$merged[$key] = $value;
163+
} elseif (is_array($merged[$key])) {
164+
$merged[$key][] = $value;
165+
} else {
166+
$merged[$key] .= '|'.$value;
167+
}
168+
}
169+
}
170+
171+
return $merged;
172+
}
173+
}

src/Http/Controllers/Inertia/UserProfileController.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use Illuminate\Routing\Controller;
77
use Illuminate\Support\Carbon;
88
use Illuminate\Support\Facades\DB;
9-
use Jenssegers\Agent\Agent;
109
use Laravel\Fortify\Features;
10+
use Laravel\Jetstream\Agent;
1111
use Laravel\Jetstream\Jetstream;
1212

1313
class UserProfileController extends Controller
@@ -67,12 +67,10 @@ public function sessions(Request $request)
6767
* Create a new agent instance from the given session.
6868
*
6969
* @param mixed $session
70-
* @return \Jenssegers\Agent\Agent
70+
* @return \Laravel\Jetstream\Agent
7171
*/
7272
protected function createAgent($session)
7373
{
74-
return tap(new Agent, function ($agent) use ($session) {
75-
$agent->setUserAgent($session->user_agent);
76-
});
74+
return tap(new Agent(), fn ($agent) => $agent->setUserAgent($session->user_agent));
7775
}
7876
}

src/Http/Livewire/LogoutOtherBrowserSessionsForm.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Illuminate\Support\Facades\DB;
99
use Illuminate\Support\Facades\Hash;
1010
use Illuminate\Validation\ValidationException;
11-
use Jenssegers\Agent\Agent;
11+
use Laravel\Jetstream\Agent;
1212
use Livewire\Component;
1313

1414
class LogoutOtherBrowserSessionsForm extends Component
@@ -121,13 +121,11 @@ public function getSessionsProperty()
121121
* Create a new agent instance from the given session.
122122
*
123123
* @param mixed $session
124-
* @return \Jenssegers\Agent\Agent
124+
* @return \Laravel\Jetstream\Agent
125125
*/
126126
protected function createAgent($session)
127127
{
128-
return tap(new Agent, function ($agent) use ($session) {
129-
$agent->setUserAgent($session->user_agent);
130-
});
128+
return tap(new Agent(), fn ($agent) => $agent->setUserAgent($session->user_agent));
131129
}
132130

133131
/**

tests/AgentTest.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Laravel\Jetstream\Tests;
4+
5+
use Laravel\Jetstream\Agent;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
use PHPUnit\Framework\TestCase;
8+
9+
/**
10+
* @copyright Originally created by Jens Segers: https://github.com/jenssegers/agent
11+
*/
12+
class AgentTest extends TestCase
13+
{
14+
/**
15+
* @param string $userAgent
16+
* @param string $platform
17+
* @return void
18+
*/
19+
#[DataProvider('operatingSystemsDataProvider')]
20+
public function testOperatingSystems($userAgent, $platform)
21+
{
22+
$agent = new Agent();
23+
$agent->setUserAgent($userAgent);
24+
25+
$this->assertEquals($platform, $agent->platform());
26+
}
27+
28+
public static function operatingSystemsDataProvider()
29+
{
30+
yield ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586', 'Windows'];
31+
yield ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', 'OS X'];
32+
yield ['Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3', 'iOS'];
33+
yield ['Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0', 'Ubuntu'];
34+
yield ['Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+', 'BlackBerryOS'];
35+
yield ['Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', 'AndroidOS'];
36+
yield ['Mozilla/5.0 (X11; CrOS x86_64 6680.78.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.102 Safari/537.36', 'ChromeOS'];
37+
yield ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', 'Windows'];
38+
}
39+
40+
/**
41+
* @param string $userAgent
42+
* @param string $browser
43+
* @return void
44+
*/
45+
#[DataProvider('browsersDataProvider')]
46+
public function testBrowsers($userAgent, $browser)
47+
{
48+
$agent = new Agent();
49+
$agent->setUserAgent($userAgent);
50+
51+
$this->assertEquals($browser, $agent->browser());
52+
}
53+
54+
public static function browsersDataProvider()
55+
{
56+
yield ['Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko', 'IE'];
57+
yield ['Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25', 'Safari'];
58+
yield ['Mozilla/5.0 (Windows; U; Win 9x 4.90; SG; rv:1.9.2.4) Gecko/20101104 Netscape/9.1.0285', 'Netscape'];
59+
yield ['Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0', 'Firefox'];
60+
yield ['Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36', 'Chrome'];
61+
yield ['Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201', 'Mozilla'];
62+
yield ['Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14', 'Opera'];
63+
yield ['Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36 OPR/27.0.1689.76', 'Opera'];
64+
yield ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12', 'Edge'];
65+
yield ['Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25', 'Safari'];
66+
yield ['Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 Vivaldi/1.2.490.43', 'Vivaldi'];
67+
yield ['Mozilla/5.0 (Linux; U; Android 4.0.4; en-US; LT28h Build/6.1.E.3.7) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.2.2.323 U3/0.8.0 Mobile Safari/534.31', 'UCBrowser'];
68+
yield ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063', 'Edge'];
69+
yield ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.29 Safari/537.36 Edg/79.0.309.18', 'Edge'];
70+
yield ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/86.0.180 Chrome/80.0.3987.180 Safari/537.36', 'Coc Coc'];
71+
}
72+
73+
/**
74+
* @param string $userAgent
75+
* @param bool $expected
76+
* @return void
77+
*/
78+
#[DataProvider('devicesDataProvider')]
79+
public function testDesktopDevices($userAgent, $expected)
80+
{
81+
$agent = new Agent();
82+
$agent->setUserAgent($userAgent);
83+
84+
$this->assertSame($expected, $agent->isDesktop());
85+
}
86+
87+
public static function devicesDataProvider()
88+
{
89+
// User Agent, Is Desktop?
90+
yield ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56', true];
91+
yield ['Mozilla/5.0 (iPhone; U; ru; CPU iPhone OS 4_2_1 like Mac OS X; ru) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148a Safari/6533.18.5', false];
92+
yield ['Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25', false];
93+
yield ['Mozilla/5.0 (Linux; U; Android 2.3.4; fr-fr; HTC Desire Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', false];
94+
yield ['Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+', false];
95+
yield ['Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', false];
96+
yield ['Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; ASUS Transformer Pad TF300T Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', false];
97+
}
98+
}

0 commit comments

Comments
 (0)