Mapbox Version
default
React Native Version
0.83.6
Platform
Android, iOS
@rnmapbox/maps version
10.3.0
Standalone component to reproduce
import React from 'react';
import { View } from 'react-native';
import Mapbox from '@rnmapbox/maps';
// Substring regex (works on iOS, fails on Android due to strict matching)
const tileUrlRegex = /https:\/\/tiles\.example\.com\/v1\/map/;
class BugReportExample extends React.Component {
componentDidMount() {
// Inject custom header using the partial-match regex
Mapbox.addCustomHeader(
'X-Test-Header',
'test-value-123',
{ urlMatching: tileUrlRegex }
);
}
render() {
return (
<View style={{ flex: 1 }}>
<Mapbox.MapView style={{ flex: 1 }}>
<Mapbox.Camera centerCoordinate={[-74.00597, 40.71427]} zoomLevel={14} />
{/* A RasterSource pointing to the dummy tile server to trigger requests containing extra parameters */}
<Mapbox.RasterSource
id="exampleTileSource"
tileUrlTemplates={[
'https://tiles.example.com/v1/map?style=streets&zoom={z}&x={x}&y={y}&format=png'
]}
tileSize={256}
>
<Mapbox.RasterLayer id="exampleTileLayer" />
</Mapbox.RasterSource>
</Mapbox.MapView>
</View>
);
}
}
export default BugReportExample;
Observed behavior and steps to reproduce
When calling Mapbox.addCustomHeader with a regular expression in urlMatching, the behavior diverges between platforms:
- iOS successfully matches the URL and injects the header if the regex matches any substring of the request URL.
- Android silently fails to inject the header unless the regular expression matches the entire URL string from start to finish.
Steps to Reproduce:
- Call
Mapbox.addCustomHeader("X-Test-Header", "test-value-123", { urlMatching: /https:\/\/tiles\.example\.com\/v1\/map/ }) using a basic substring regex.
- Load a map layer pointing to a target URL that contains trailing parameters, such as:
https://tiles.example.com/v1/map?style=streets&zoom=14&x=4823&y=6122&format=png
- Run the app on an iOS device/simulator. Observe that the network request successfully includes
X-Test-Header: test-value-123.
- Run the exact same code on an Android device/emulator. Observe the network traffic. The custom header is missing because the trailing query and coordinate parameters (
?style=streets&zoom=14...) prevent the regex from achieving a full-string match on the native layer.
Expected behavior
The regular expression evaluation should be unified across both platforms. Both iOS and Android should evaluate urlMatching using a substring/partial match algorithm.
If a pattern matches a portion of the incoming request URL, the header should be successfully appended on both platforms without forcing developers to write cross-platform "hacks" like appending .*$ to their regular expressions.
Notes / preliminary analysis
The bug is caused by a fundamental difference in how the native platforms evaluate regular expressions within their custom network interceptors.
iOS Native Side (Substring Match)
iOS utilizes NSRegularExpression.firstMatch, which checks for the first occurrence of a matching substring in the URL:
if pattern.firstMatch(in: urlString, options: [], range: range) != nil {
headers[key] = entry.headerValue
}
- This acts like JavaScript's
.test() or a substring search. It is highly flexible.
Android Native Side (Strict Full Match)
Android utilizes Kotlin's Regex.matches(), which evaluates whether the entire URL matches the pattern:
if (urlRegexp.matches(destination)) {
headers[entry.key] = entry.value.headerValue
}
- Kotlin's
Regex.matches(input) compiles down to Java's Matcher.matches().
- According to Java specifications, matches() checks if the entire region sequence conforms to the regex (essentially wrapping your pattern in implicit ^...$ anchors). If there are trailing query parameters (which there always are on tile/map servers), the check fails.
Suggested Fix
To align Android with the iOS implementation, the Android native network interceptor should be updated to use containsMatchIn instead of matches:
// Change this line in the Android interceptor to support substring matches:
if (urlRegexp.containsMatchIn(destination)) {
headers[entry.key] = entry.value.headerValue
}
Additional links and references
Mapbox Version
default
React Native Version
0.83.6
Platform
Android, iOS
@rnmapbox/mapsversion10.3.0
Standalone component to reproduce
Observed behavior and steps to reproduce
When calling
Mapbox.addCustomHeaderwith a regular expression inurlMatching, the behavior diverges between platforms:Steps to Reproduce:
Mapbox.addCustomHeader("X-Test-Header", "test-value-123", { urlMatching: /https:\/\/tiles\.example\.com\/v1\/map/ })using a basic substring regex.https://tiles.example.com/v1/map?style=streets&zoom=14&x=4823&y=6122&format=pngX-Test-Header: test-value-123.?style=streets&zoom=14...) prevent the regex from achieving a full-string match on the native layer.Expected behavior
The regular expression evaluation should be unified across both platforms. Both iOS and Android should evaluate
urlMatchingusing a substring/partial match algorithm.If a pattern matches a portion of the incoming request URL, the header should be successfully appended on both platforms without forcing developers to write cross-platform "hacks" like appending
.*$to their regular expressions.Notes / preliminary analysis
The bug is caused by a fundamental difference in how the native platforms evaluate regular expressions within their custom network interceptors.
iOS Native Side (Substring Match)
iOS utilizes
NSRegularExpression.firstMatch, which checks for the first occurrence of a matching substring in the URL:.test()or a substring search. It is highly flexible.Android Native Side (Strict Full Match)
Android utilizes Kotlin's
Regex.matches(), which evaluates whether the entire URL matches the pattern:Regex.matches(input)compiles down to Java'sMatcher.matches().Suggested Fix
To align Android with the iOS implementation, the Android native network interceptor should be updated to use
containsMatchIninstead ofmatches:Additional links and references
Current iOS implementation - The current iOS implementation in @rnmapbox/maps
Current android implementation - The current android implementation in @rnmapbox/maps
Kotlin Standard Library Documentation for Regex.matches() — Explains that the entire input must match the pattern.
Kotlin Standard Library Documentation for Regex.containsMatchIn() — The correct method for substring/partial pattern matching on Android.