Skip to content

Commit 52482c9

Browse files
feat(http): Apply overrideUserAgent to requests (#7906)
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
1 parent 4fb12a0 commit 52482c9

File tree

6 files changed

+122
-10
lines changed

6 files changed

+122
-10
lines changed

android/capacitor/src/main/assets/native-bridge.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,17 @@ var nativeBridge = (function (exports) {
486486
method.toLocaleUpperCase() === 'HEAD' ||
487487
method.toLocaleUpperCase() === 'OPTIONS' ||
488488
method.toLocaleUpperCase() === 'TRACE') {
489+
// a workaround for following android webview issue:
490+
// https://issues.chromium.org/issues/40450316
491+
// Sets the user-agent header to a custom value so that its not stripped
492+
// on its way to the native layer
493+
if (platform === 'android' && (options === null || options === void 0 ? void 0 : options.headers)) {
494+
const userAgent = headers.get('User-Agent') || headers.get('user-agent');
495+
if (userAgent !== null) {
496+
headers.set('x-cap-user-agent', userAgent);
497+
options.headers = headers;
498+
}
499+
}
489500
if (typeof resource === 'string') {
490501
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
491502
}
@@ -499,13 +510,22 @@ var nativeBridge = (function (exports) {
499510
try {
500511
const { body } = request;
501512
const optionHeaders = Object.fromEntries(request.headers.entries());
502-
const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']);
513+
const { data: requestData, type, headers: requestHeaders, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']);
514+
const nativeHeaders = Object.assign(Object.assign({}, requestHeaders), optionHeaders);
515+
if (platform === 'android') {
516+
if (headers.has('User-Agent')) {
517+
nativeHeaders['User-Agent'] = headers.get('User-Agent');
518+
}
519+
if (headers.has('user-agent')) {
520+
nativeHeaders['user-agent'] = headers.get('user-agent');
521+
}
522+
}
503523
const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', {
504524
url: request.url,
505525
method: method,
506526
data: requestData,
507527
dataType: type,
508-
headers: Object.assign(Object.assign({}, headers), optionHeaders),
528+
headers: nativeHeaders,
509529
});
510530
const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type'];
511531
let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json'))
@@ -597,6 +617,13 @@ var nativeBridge = (function (exports) {
597617
};
598618
// XHR patch set request header
599619
prototype.setRequestHeader = function (header, value) {
620+
// a workaround for the following android web view issue:
621+
// https://issues.chromium.org/issues/40450316
622+
// Sets the user-agent header to a custom value so that its not stripped
623+
// on its way to the native layer
624+
if (platform === 'android' && (header === 'User-Agent' || header === 'user-agent')) {
625+
header = 'x-cap-user-agent';
626+
}
600627
if (isRelativeOrProxyUrl(this._url)) {
601628
return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value);
602629
}

android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,15 @@ private WebResourceResponse handleCapacitorHttpRequest(WebResourceRequest reques
260260
headers.put(header.getKey(), header.getValue());
261261
}
262262

263+
// a workaround for the following android web view issue:
264+
// https://issues.chromium.org/issues/40450316
265+
// x-cap-user-agent contains the user agent set in JavaScript
266+
String userAgentValue = headers.getString("x-cap-user-agent");
267+
if (userAgentValue != null) {
268+
headers.put("User-Agent", userAgentValue);
269+
}
270+
headers.remove("x-cap-user-agent");
271+
263272
HttpRequestHandler.HttpURLConnectionBuilder connectionBuilder = new HttpRequestHandler.HttpURLConnectionBuilder()
264273
.setUrl(url)
265274
.setMethod(request.getMethod())

android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,19 @@ public static JSObject request(PluginCall call, String httpMethod, Bridge bridge
398398

399399
boolean isHttpMutate = method.equals("DELETE") || method.equals("PATCH") || method.equals("POST") || method.equals("PUT");
400400

401+
// a workaround for the following android web view issue:
402+
// https://issues.chromium.org/issues/40450316
403+
// x-cap-user-agent contains the user agent set in JavaScript
404+
String userAgentValue = headers.getString("x-cap-user-agent");
405+
if (userAgentValue != null) {
406+
headers.put("User-Agent", userAgentValue);
407+
}
408+
headers.remove("x-cap-user-agent");
409+
410+
if (!headers.has("User-Agent") && !headers.has("user-agent")) {
411+
headers.put("User-Agent", bridge.getConfig().getOverriddenUserAgentString());
412+
}
413+
401414
URL url = new URL(urlString);
402415
HttpURLConnectionBuilder connectionBuilder = new HttpURLConnectionBuilder()
403416
.setUrl(url)

core/native-bridge.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,18 @@ const initBridge = (w: any): void => {
513513
method.toLocaleUpperCase() === 'OPTIONS' ||
514514
method.toLocaleUpperCase() === 'TRACE'
515515
) {
516+
// a workaround for following android webview issue:
517+
// https://issues.chromium.org/issues/40450316
518+
// Sets the user-agent header to a custom value so that its not stripped
519+
// on its way to the native layer
520+
if (platform === 'android' && options?.headers) {
521+
const userAgent = headers.get('User-Agent') || headers.get('user-agent');
522+
if (userAgent !== null) {
523+
headers.set('x-cap-user-agent', userAgent);
524+
options.headers = headers;
525+
}
526+
}
527+
516528
if (typeof resource === 'string') {
517529
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
518530
} else if (resource instanceof Request) {
@@ -530,21 +542,33 @@ const initBridge = (w: any): void => {
530542
const {
531543
data: requestData,
532544
type,
533-
headers,
545+
headers: requestHeaders,
534546
} = await convertBody(
535547
options?.body || body || undefined,
536548
optionHeaders['Content-Type'] || optionHeaders['content-type'],
537549
);
538550

551+
const nativeHeaders = {
552+
...requestHeaders,
553+
...optionHeaders,
554+
};
555+
556+
if (platform === 'android') {
557+
if (headers.has('User-Agent')) {
558+
nativeHeaders['User-Agent'] = headers.get('User-Agent');
559+
}
560+
561+
if (headers.has('user-agent')) {
562+
nativeHeaders['user-agent'] = headers.get('user-agent');
563+
}
564+
}
565+
539566
const nativeResponse: HttpResponse = await cap.nativePromise('CapacitorHttp', 'request', {
540567
url: request.url,
541568
method: method,
542569
data: requestData,
543570
dataType: type,
544-
headers: {
545-
...headers,
546-
...optionHeaders,
547-
},
571+
headers: nativeHeaders,
548572
});
549573

550574
const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type'];
@@ -656,6 +680,14 @@ const initBridge = (w: any): void => {
656680

657681
// XHR patch set request header
658682
prototype.setRequestHeader = function (header: string, value: string) {
683+
// a workaround for the following android web view issue:
684+
// https://issues.chromium.org/issues/40450316
685+
// Sets the user-agent header to a custom value so that its not stripped
686+
// on its way to the native layer
687+
if (platform === 'android' && (header === 'User-Agent' || header === 'user-agent')) {
688+
header = 'x-cap-user-agent';
689+
}
690+
659691
if (isRelativeOrProxyUrl(this._url)) {
660692
return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value);
661693
}

ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ open class HttpRequestHandler {
174174
guard var urlString = call.getString("url") else { throw URLError(.badURL) }
175175
let method = httpMethod ?? call.getString("method", "GET")
176176

177-
let headers = (call.getObject("headers") ?? [:]) as [String: Any]
177+
var headers = (call.getObject("headers") ?? [:]) as [String: Any]
178178
let params = (call.getObject("params") ?? [:]) as [String: Any]
179179
let responseType = call.getString("responseType") ?? "text"
180180
let connectTimeout = call.getDouble("connectTimeout")
@@ -194,6 +194,10 @@ open class HttpRequestHandler {
194194
.openConnection()
195195
.build()
196196

197+
if let userAgentString = config?.overridenUserAgentString, headers["User-Agent"] == nil, headers["user-agent"] == nil {
198+
headers["User-Agent"] = userAgentString
199+
}
200+
197201
request.setRequestHeaders(headers)
198202

199203
// Timeouts in iOS are in seconds. So read the value in millis and divide by 1000

ios/Capacitor/Capacitor/assets/native-bridge.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,17 @@ var nativeBridge = (function (exports) {
486486
method.toLocaleUpperCase() === 'HEAD' ||
487487
method.toLocaleUpperCase() === 'OPTIONS' ||
488488
method.toLocaleUpperCase() === 'TRACE') {
489+
// a workaround for following android webview issue:
490+
// https://issues.chromium.org/issues/40450316
491+
// Sets the user-agent header to a custom value so that its not stripped
492+
// on its way to the native layer
493+
if (platform === 'android' && (options === null || options === void 0 ? void 0 : options.headers)) {
494+
const userAgent = headers.get('User-Agent') || headers.get('user-agent');
495+
if (userAgent !== null) {
496+
headers.set('x-cap-user-agent', userAgent);
497+
options.headers = headers;
498+
}
499+
}
489500
if (typeof resource === 'string') {
490501
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
491502
}
@@ -499,13 +510,22 @@ var nativeBridge = (function (exports) {
499510
try {
500511
const { body } = request;
501512
const optionHeaders = Object.fromEntries(request.headers.entries());
502-
const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']);
513+
const { data: requestData, type, headers: requestHeaders, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']);
514+
const nativeHeaders = Object.assign(Object.assign({}, requestHeaders), optionHeaders);
515+
if (platform === 'android') {
516+
if (headers.has('User-Agent')) {
517+
nativeHeaders['User-Agent'] = headers.get('User-Agent');
518+
}
519+
if (headers.has('user-agent')) {
520+
nativeHeaders['user-agent'] = headers.get('user-agent');
521+
}
522+
}
503523
const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', {
504524
url: request.url,
505525
method: method,
506526
data: requestData,
507527
dataType: type,
508-
headers: Object.assign(Object.assign({}, headers), optionHeaders),
528+
headers: nativeHeaders,
509529
});
510530
const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type'];
511531
let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json'))
@@ -597,6 +617,13 @@ var nativeBridge = (function (exports) {
597617
};
598618
// XHR patch set request header
599619
prototype.setRequestHeader = function (header, value) {
620+
// a workaround for the following android web view issue:
621+
// https://issues.chromium.org/issues/40450316
622+
// Sets the user-agent header to a custom value so that its not stripped
623+
// on its way to the native layer
624+
if (platform === 'android' && (header === 'User-Agent' || header === 'user-agent')) {
625+
header = 'x-cap-user-agent';
626+
}
600627
if (isRelativeOrProxyUrl(this._url)) {
601628
return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value);
602629
}

0 commit comments

Comments
 (0)