1010use Reqxide \Contract \TransportInterface ;
1111use Reqxide \Cookie \CookieJar ;
1212use Reqxide \Emulation \Browser ;
13+ use Reqxide \Emulation \Platform ;
1314use Reqxide \Emulation \Profile ;
1415use Reqxide \Middleware \CompressionMiddleware ;
1516use Reqxide \Middleware \CookieMiddleware ;
@@ -27,6 +28,8 @@ final class ClientBuilder
2728
2829 private ?Profile $ profile = null ;
2930
31+ private ?Platform $ platform = null ;
32+
3033 private ?TransportInterface $ transport = null ;
3134
3235 private ?Proxy $ proxy = null ;
@@ -64,6 +67,13 @@ public function profile(Profile $profile): self
6467 return $ this ;
6568 }
6669
70+ public function platform (Platform $ platform ): self
71+ {
72+ $ this ->platform = $ platform ;
73+
74+ return $ this ;
75+ }
76+
6777 public function transport (TransportInterface $ transport ): self
6878 {
6979 $ this ->transport = $ transport ;
@@ -145,6 +155,11 @@ public function build(): Client
145155 // Resolve profile
146156 $ profile = $ this ->profile ?? $ this ->browser ?->profile() ?? new Profile ;
147157
158+ // Apply platform overrides to profile headers
159+ if ($ this ->platform instanceof Platform) {
160+ $ profile = $ this ->applyPlatform ($ profile , $ this ->platform );
161+ }
162+
148163 // Resolve transport
149164 $ transport = $ this ->transport ?? TransportFactory::create ();
150165
@@ -172,6 +187,62 @@ public function build(): Client
172187 );
173188 }
174189
190+ private function applyPlatform (Profile $ profile , Platform $ platform ): Profile
191+ {
192+ $ headers = $ profile ->defaultHeaders ;
193+
194+ if (isset ($ headers ['User-Agent ' ])) {
195+ $ headers ['User-Agent ' ] = $ this ->rewriteUserAgent ($ headers ['User-Agent ' ], $ platform );
196+ }
197+
198+ if (isset ($ headers ['sec-ch-ua-platform ' ])) {
199+ $ headers ['sec-ch-ua-platform ' ] = $ platform ->secChUaPlatform ();
200+ }
201+
202+ if (isset ($ headers ['sec-ch-ua-mobile ' ])) {
203+ $ headers ['sec-ch-ua-mobile ' ] = $ platform ->isMobile () ? '?1 ' : '?0 ' ;
204+ }
205+
206+ return new Profile (
207+ tlsOptions: $ profile ->tlsOptions ,
208+ http2Options: $ profile ->http2Options ,
209+ http1Options: $ profile ->http1Options ,
210+ defaultHeaders: $ headers ,
211+ originalHeaderMap: $ profile ->originalHeaderMap ,
212+ connectionGroup: $ profile ->connectionGroup ,
213+ impersonateTarget: $ profile ->impersonateTarget ,
214+ );
215+ }
216+
217+ private function rewriteUserAgent (string $ ua , Platform $ platform ): string
218+ {
219+ $ platformString = $ platform ->userAgentPlatform ();
220+
221+ // Chrome/Edge: Mozilla/5.0 ({platform}) AppleWebKit/...
222+ if (preg_match ('#^(Mozilla/5\.0 \()([^)]+)(\) AppleWebKit/537\.36.+)$# ' , $ ua , $ m )) {
223+ if ($ platform === Platform::IOS ) {
224+ $ suffix = preg_replace ('#Chrome/[\d.]+# ' , 'CriOS/$0 ' , $ m [3 ]) ?? $ m [3 ];
225+ $ suffix = str_replace ('CriOS/CriOS/ ' , 'CriOS/ ' , $ suffix );
226+
227+ return $ m [1 ].$ platformString .$ suffix ;
228+ }
229+
230+ return $ m [1 ].$ platformString .$ m [3 ];
231+ }
232+
233+ // Firefox: Mozilla/5.0 ({platform}; rv:{ver}) Gecko/...
234+ if (preg_match ('#^(Mozilla/5\.0 \()([^;]+(?:;[^)]*)?)(; rv:[\d.]+\) Gecko/.+)$# ' , $ ua , $ m )) {
235+ return $ m [1 ].$ platformString .$ m [3 ];
236+ }
237+
238+ // Safari: Mozilla/5.0 ({platform}) AppleWebKit/605...
239+ if (preg_match ('#^(Mozilla/5\.0 \()([^)]+)(\) AppleWebKit/605.+)$# ' , $ ua , $ m )) {
240+ return $ m [1 ].$ platformString .$ m [3 ];
241+ }
242+
243+ return $ ua ;
244+ }
245+
175246 /** @return list<MiddlewareInterface> */
176247 private function buildMiddlewares (): array
177248 {
0 commit comments