@@ -42,6 +42,7 @@ public final class NRVideoConfiguration {
4242 private final boolean memoryOptimized ;
4343 private final boolean debugLoggingEnabled ;
4444 private final boolean isTV ;
45+ private final String collectorAddress ;
4546
4647 // Performance optimization constants
4748 private static final int DEFAULT_HARVEST_CYCLE_SECONDS = 5 * 60 ; // 5 minutes
@@ -78,6 +79,7 @@ private NRVideoConfiguration(Builder builder) {
7879 this .memoryOptimized = builder .memoryOptimized ;
7980 this .debugLoggingEnabled = builder .debugLoggingEnabled ;
8081 this .isTV = builder .isTV ;
82+ this .collectorAddress = builder .collectorAddress ;
8183 }
8284
8385 // Immutable getters
@@ -91,6 +93,7 @@ private NRVideoConfiguration(Builder builder) {
9193 public boolean isMemoryOptimized () { return memoryOptimized ; }
9294 public boolean isDebugLoggingEnabled () { return debugLoggingEnabled ; }
9395 public boolean isTV () { return isTV ; }
96+ public String getCollectorAddress () { return collectorAddress ; }
9497
9598 /**
9699 * Get dead letter retry interval in milliseconds
@@ -107,41 +110,56 @@ public long getDeadLetterRetryInterval() {
107110 }
108111
109112 /**
110- * Enterprise-grade region identification with multiple fallback strategies
111- * Thread-safe and optimized for performance
113+ * Parse region code from application token prefix
114+ * Matches NewRelic iOS Agent pattern: extracts region prefix before 'x'
115+ * Examples: "EUxABCD..." -> "EU", "APxABCD..." -> "AP", "AA..." -> ""
116+ */
117+ private static String parseRegionFromToken (String applicationToken ) {
118+ if (applicationToken == null || applicationToken .length () < 3 ) {
119+ return "" ;
120+ }
121+
122+ // Find the first 'x' in the token
123+ int xIndex = applicationToken .indexOf ('x' );
124+ if (xIndex == -1 ) {
125+ return "" ; // No region prefix found
126+ }
127+
128+ // Extract everything before the first 'x'
129+ String regionCode = applicationToken .substring (0 , xIndex );
130+
131+ // Remove any trailing 'x' characters
132+ while (regionCode .length () > 0 && regionCode .charAt (regionCode .length () - 1 ) == 'x' ) {
133+ regionCode = regionCode .substring (0 , regionCode .length () - 1 );
134+ }
135+
136+ return regionCode ;
137+ }
138+
139+ /**
140+ * Identify region with proper token parsing and fallback logic
141+ * Behavior similar to NewRelic iOS Agent's NRMAAgentConfiguration
112142 */
113143 private static String identifyRegion (String applicationToken ) {
114144 if (applicationToken == null || applicationToken .length () < 10 ) {
115145 return "US" ; // Safe default
116146 }
117147
118- String cleanToken = applicationToken .trim ().toLowerCase ();
148+ // First, try to parse region from token prefix (e.g., "EUx", "APx")
149+ String regionCode = parseRegionFromToken (applicationToken );
119150
120- // Strategy 1: Direct prefix matching (most reliable)
121- for (Map .Entry <String , String > entry : REGION_MAPPINGS .entrySet ()) {
122- String regionKey = entry .getKey ().toLowerCase ();
123- if (cleanToken .startsWith (regionKey ) || cleanToken .contains ("-" + regionKey + "-" )) {
124- return entry .getValue ();
125- }
126- }
151+ if (regionCode != null && regionCode .length () > 0 ) {
152+ // Convert region code to uppercase and validate
153+ String upperRegion = regionCode .toUpperCase ();
127154
128- // Strategy 2: Token structure analysis
129- if (cleanToken .length () >= 40 ) { // Standard NR token length
130- // EU tokens often have specific patterns
131- if (cleanToken .contains ("eu" ) || cleanToken .contains ("europe" )) {
132- return "EU" ;
133- }
134- // AP tokens often have specific patterns
135- if (cleanToken .contains ("ap" ) || cleanToken .contains ("asia" ) || cleanToken .contains ("pacific" )) {
136- return "AP" ;
137- }
138- // Gov tokens have specific patterns
139- if (cleanToken .contains ("gov" ) || cleanToken .contains ("fed" )) {
140- return "GOV" ;
155+ // Map region codes to standard regions
156+ String mappedRegion = REGION_MAPPINGS .get (upperRegion );
157+ if (mappedRegion != null ) {
158+ return mappedRegion ;
141159 }
142160 }
143161
144- // Strategy 3: Default to US for production stability
162+ // Default to US for standard tokens without region prefix
145163 return "US" ;
146164 }
147165
@@ -158,6 +176,7 @@ public static final class Builder {
158176 private boolean memoryOptimized = true ;
159177 private boolean debugLoggingEnabled = false ;
160178 private boolean isTV = false ;
179+ private String collectorAddress = null ;
161180
162181 public Builder (String applicationToken ) {
163182 this .applicationToken = applicationToken ;
@@ -235,6 +254,16 @@ public Builder enableLogging() {
235254 return this ;
236255 }
237256
257+ /**
258+ * Set custom collector domain address for /connect and /data endpoints (optional)
259+ * Example: "staging-mobile-collector.newrelic.com" or "mobile-collector.newrelic.com"
260+ * If not set, will be auto-detected from application token region
261+ */
262+ public Builder withCollectorAddress (String collectorAddress ) {
263+ this .collectorAddress = collectorAddress ;
264+ return this ;
265+ }
266+
238267 private void applyTVOptimizations () {
239268 this .harvestCycleSeconds = TV_HARVEST_CYCLE_SECONDS ;
240269 this .liveHarvestCycleSeconds = TV_LIVE_HARVEST_CYCLE_SECONDS ;
0 commit comments