Skip to content

Commit f401397

Browse files
committed
Add latency and geolocalization provider
1 parent 208ac0d commit f401397

14 files changed

+1507
-11
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 1.4.0
4+
5+
### Added
6+
- Added support for geolocation and latency data collection.
7+
8+
### Fixed
9+
- Logger: Fixed disposing of the logger when the application is closed.
10+
- VideoEncoder: Fix compilation warnings.
11+
312
## 1.3.2
413

514
### Chnages

Documentation.html

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,131 @@ <h3>Integration with Bug Reports</h3>
263263
<strong>Example Scene:</strong> The plugin includes <code>DeviceAuthExample.cs</code> in the Samples folder, demonstrating complete integration between device authentication and bug reporting systems.
264264
</div>
265265

266+
<h2>Optional Components</h2>
267+
268+
<p>The plugin includes two optional components that can enhance bug reports with additional diagnostic information. These components are <strong>not attached to the prefabs by default</strong> and must be manually added to provide enhanced debugging capabilities.</p>
269+
270+
<h3>GeolocationProvider</h3>
271+
272+
<div class="note">
273+
<strong>Purpose:</strong> Automatically collects the user's country and ISP information based on their IP address to help identify location-specific issues and provide geographic context for bug reports.
274+
</div>
275+
276+
<h4>How It Works</h4>
277+
278+
<ol>
279+
<li><strong>Automatic Detection:</strong> Uses BetaHub's ping endpoint to detect the user's country and ISP via IP geolocation</li>
280+
<li><strong>Header Parsing:</strong> Extracts geolocation information from response headers:
281+
<ul>
282+
<li>Country data from headers like <code>X-Viewer-Country</code>, <code>CF-IPCountry</code></li>
283+
<li>ASN (Autonomous System Number) data from <code>X-Viewer-ASN</code> header</li>
284+
</ul>
285+
</li>
286+
<li><strong>ISP Resolution:</strong> ASN data is automatically resolved to ISP/company names on the BetaHub dashboard for easier analysis</li>
287+
<li><strong>Bug Report Integration:</strong> Country and ISP data are automatically included in bug reports as custom fields</li>
288+
</ol>
289+
290+
<h4>Setup Instructions</h4>
291+
292+
<ol>
293+
<li>In your scene, select the GameObject containing your <code>BugReportUI</code> component</li>
294+
<li>Click <strong>Add Component</strong> and search for <code>GeolocationProvider</code>, or drag the script from the project window</li>
295+
<li>Configure the provider in the inspector:
296+
<ul>
297+
<li><strong>Enable Geolocation:</strong> Toggle to enable/disable country code collection</li>
298+
<li><strong>Enable ASN Collection:</strong> Toggle to enable/disable ISP/ASN data collection</li>
299+
<li><strong>Geolocation Endpoint:</strong> API endpoint for geolocation (default: <code>https://ping.betahub.io/ping.txt</code>)</li>
300+
<li><strong>Timeout Seconds:</strong> Request timeout (default: 5 seconds)</li>
301+
</ul>
302+
</li>
303+
<li>The component will automatically link itself to the <code>BugReportUI</code> on the same GameObject</li>
304+
</ol>
305+
306+
<div class="note">
307+
<strong>Automatic Setup:</strong> When <code>GeolocationProvider</code> is attached to the same GameObject as <code>BugReportUI</code>, it will automatically configure itself and display a success message in the console. If placed on a different GameObject, you'll see a warning message with setup instructions.
308+
</div>
309+
310+
<h3>LatencyProvider</h3>
311+
312+
<div class="note">
313+
<strong>Purpose:</strong> Measures network latency to help diagnose network-related issues and provide connection quality context for bug reports.
314+
</div>
315+
316+
<h4>How It Works</h4>
317+
318+
<ol>
319+
<li><strong>Ping Methods:</strong> Supports two measurement methods:
320+
<ul>
321+
<li><strong>ICMP Ping:</strong> Uses Unity's built-in Ping class for traditional network ping (default)</li>
322+
<li><strong>HTTP Ping:</strong> Uses UnityWebRequest for HTTP-based latency measurement</li>
323+
<li><strong>Automatic Fallback:</strong> Automatically switches from ICMP to HTTP if ICMP fails consistently (after 3 consecutive failures per host)</li>
324+
</ul>
325+
</li>
326+
<li><strong>Collection Modes:</strong> Two modes for data collection:
327+
<ul>
328+
<li><strong>OnBugReport:</strong> Collects pings only when bug report window opens (default)</li>
329+
<li><strong>Continuous:</strong> Continuously collects pings in the background at regular intervals</li>
330+
</ul>
331+
</li>
332+
<li><strong>Multiple Host Support:</strong> Can ping multiple ICMP target hosts simultaneously for comprehensive network analysis</li>
333+
<li><strong>Multiple Requests:</strong> Performs up to 10 ping requests per host to calculate accurate statistics</li>
334+
<li><strong>Early Termination:</strong> If user submits the bug report before all pings complete, uses available data</li>
335+
<li><strong>Comprehensive Statistics:</strong> Calculates minimum, maximum, and average latency from successful requests, plus packet loss statistics</li>
336+
<li><strong>Results Format:</strong> Includes detailed data like <code>"min/avg/max = 45ms/67ms/89ms (8/10 requests), Lost: 2/10 (20.0%)"</code> in bug reports</li>
337+
</ol>
338+
339+
<h4>Setup Instructions</h4>
340+
341+
<ol>
342+
<li>In your scene, select the GameObject containing your <code>BugReportUI</code> component</li>
343+
<li>Click <strong>Add Component</strong> and search for <code>LatencyProvider</code>, or drag the script from the project window</li>
344+
<li>Configure the provider in the inspector:
345+
<ul>
346+
<li><strong>Enable Latency:</strong> Toggle to enable/disable latency measurement</li>
347+
<li><strong>Collection Mode:</strong> Choose between OnBugReport (default) or Continuous collection</li>
348+
<li><strong>Ping Method:</strong> Select ICMP (default) or HTTP ping method</li>
349+
<li><strong>ICMP Target Hosts:</strong> List of hosts for ICMP ping (default: ping.betahub.io)</li>
350+
<li><strong>HTTP Endpoint:</strong> API endpoint for HTTP latency testing (default: <code>https://ping.betahub.io/ping.txt</code>)</li>
351+
<li><strong>Min Ping Requests:</strong> Minimum number of ping requests per host (default: 10, range: 1-20)</li>
352+
<li><strong>Timeout Seconds:</strong> Request timeout per ping (default: 5 seconds)</li>
353+
<li><strong>Delay Between Requests:</strong> Delay between consecutive pings (default: 0.1 seconds)</li>
354+
<li><strong>Ping Interval:</strong> Interval between background pings in continuous mode (default: 15 seconds)</li>
355+
</ul>
356+
</li>
357+
<li>The component will automatically link itself to the <code>BugReportUI</code> on the same GameObject</li>
358+
</ol>
359+
360+
<div class="warning">
361+
<strong>Performance Considerations:</strong> Latency testing performs network requests while the bug report UI is open. The default settings (10 requests with 0.1s delays) complete in approximately 1-2 seconds under normal conditions. If users frequently submit reports very quickly, consider reducing <code>Max Ping Requests</code> or increasing <code>Delay Between Requests</code> to balance accuracy with responsiveness.
362+
</div>
363+
364+
<h4>Troubleshooting</h4>
365+
366+
<ul>
367+
<li><strong>"No latency data available":</strong> All ping requests failed. Check network connectivity and endpoint accessibility.</li>
368+
<li><strong>"No country information found":</strong> Geolocation service didn't return expected headers. This may indicate network issues or endpoint changes.</li>
369+
<li><strong>Warning messages in console:</strong> If components are not on the same GameObject as <code>BugReportUI</code>, you'll see warnings with setup instructions.</li>
370+
</ul>
371+
372+
<h4>Data Integration</h4>
373+
374+
<p>When both components are enabled, bug reports will include custom fields like:</p>
375+
376+
<pre><code>Custom Fields:
377+
country=US
378+
asn=Cloudflare, Inc.
379+
latency=ping.betahub.io: min/avg/max = 45ms/67ms/89ms (10/10 requests)</code></pre>
380+
381+
<p>This data appears in the BetaHub dashboard and can be used for:</p>
382+
383+
<ul>
384+
<li><strong>Geographic Analysis:</strong> Identifying region-specific issues and user distribution</li>
385+
<li><strong>ISP Analysis:</strong> Understanding which internet service providers may be experiencing issues</li>
386+
<li><strong>Network Diagnosis:</strong> Correlating bugs with connection quality and packet loss rates</li>
387+
<li><strong>Performance Optimization:</strong> Understanding user network conditions across different providers</li>
388+
<li><strong>Support Prioritization:</strong> Triaging issues based on user location, ISP, and connectivity quality</li>
389+
</ul>
390+
266391
<h3>Configuration</h3>
267392

268393
<p>Adjust these settings in the BugReportingFormCanvas inspector:</p>

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ For continued use and more extensive testing, we strongly recommend creating you
2929
- **Video recording**: Record a video of the bug happening in-game. The video is automatically recorded and attached to the bug report.
3030
- **Log collection**: Collect logs from the game and attach them to the bug report. By default, Unity logs are collected, but you can also add custom logs.
3131
- **Screenshot of the game**: A screenshot of the game is automatically attached to the bug report when the user submits a bug.
32+
- **Geolocation and latency data**: Optional components for collecting user's country information and network latency measurements to help with debugging location-specific and network-related issues.
3233
- **Working Example Scene**: The plugin comes with a working example scene that demonstrates how to use the plugin, serving as a good starting point for your implementation.
3334
- **Customizable**: Customize the bug submission form to ask for more information or to change the look and feel of the form.
3435

@@ -46,6 +47,8 @@ For continued use and more extensive testing, we strongly recommend creating you
4647

4748
The installation and setup documentation is available [here](https://www.betahub.io/docs/integration-guides/).
4849

50+
For detailed information about optional components like geolocation and latency collection, see the included [Documentation.html](Documentation.html) file.
51+
4952
## Device Authentication (Optional)
5053

5154
The plugin supports an optional device authentication flow that provides a seamless user experience for bug reporting.

Runtime/Scripts/BugReportUI.cs

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public enum MediaUploadType
2525
WaitForUpload
2626
}
2727

28+
[RequireComponent(typeof(CustomFieldValidator))]
2829
public class BugReportUI : MonoBehaviour
2930
{
3031
public const string DEMO_PROJECT_ID = "pr-5287510306";
@@ -77,6 +78,17 @@ public class BugReportUI : MonoBehaviour
7778
[Tooltip("Device Authentication (Optional). If set, the user may be authenticated via device auth.")]
7879
[SerializeField] private DeviceAuthManager deviceAuthManager;
7980

81+
// Geolocation provider for collecting location data
82+
[Tooltip("Geolocation Provider (Optional). If set, location data will be included in bug reports.")]
83+
[SerializeField] private GeolocationProvider geolocationProvider;
84+
85+
// Latency provider for collecting network latency data
86+
[Tooltip("Latency Provider (Optional). If set, network latency data will be included in bug reports.")]
87+
[SerializeField] private LatencyProvider latencyProvider;
88+
89+
// Custom field validator for validating provider requirements
90+
private CustomFieldValidator customFieldValidator;
91+
8092
// If set, this email address will be used as the default email address of the reporter.
8193
// This is a hidden field since it's purpose is to be pre-filled programmatically by the developer if the user is somehow already logged in with a specific email address.
8294
[HideInInspector, FormerlySerializedAs("defaultEmailAddress")]
@@ -168,6 +180,9 @@ void Start()
168180
{
169181
_gameRecorder = GetComponent<GameRecorder>();
170182

183+
// Get the required CustomFieldValidator component (automatically added by RequireComponent)
184+
customFieldValidator = GetComponent<CustomFieldValidator>();
185+
171186
BugReportPanel.SetActive(false);
172187
SubmitButton.onClick.AddListener(SubmitBugReport);
173188
CloseButton.onClick.AddListener(() =>
@@ -218,6 +233,9 @@ void Start()
218233
}
219234
}
220235

236+
// Validate custom fields for providers
237+
ValidateProviderCustomFields();
238+
221239
DontDestroyOnLoad(gameObject);
222240
}
223241

@@ -240,6 +258,13 @@ void Update()
240258
if (!_uiWasVisible)
241259
{
242260
OnBugReportWindowShown?.Invoke();
261+
262+
// Start latency testing when bug report window is shown
263+
if (latencyProvider != null && latencyProvider.EnableLatency)
264+
{
265+
latencyProvider.StartLatencyTest();
266+
}
267+
243268
ModifyCursorState();
244269
}
245270
_uiWasVisible = true;
@@ -347,6 +372,11 @@ public void AddLogFile(string path, bool removeAfterUpload)
347372
}
348373

349374
void SubmitBugReport()
375+
{
376+
StartCoroutine(SubmitBugReportCoroutine());
377+
}
378+
379+
private IEnumerator SubmitBugReportCoroutine()
350380
{
351381
string description = DescriptionField.text;
352382
string steps = StepsField.text;
@@ -382,6 +412,76 @@ void SubmitBugReport()
382412
gameRecorder = _gameRecorder;
383413
}
384414

415+
// Get geolocation and latency data if providers are available
416+
var customFieldsData = new System.Collections.Generic.Dictionary<string, string>();
417+
418+
// Get geolocation and ASN data
419+
if (geolocationProvider != null && (geolocationProvider.EnableGeolocation || geolocationProvider.EnableAsnCollection))
420+
{
421+
bool locationReceived = false;
422+
string locationError = null;
423+
424+
yield return geolocationProvider.GetLocationDataAsync(
425+
(locationData) => {
426+
if (locationData != null)
427+
{
428+
if (geolocationProvider.EnableGeolocation && !string.IsNullOrEmpty(locationData.country))
429+
{
430+
customFieldsData["country"] = locationData.country;
431+
Debug.Log("BugReportUI: Including location data in bug report: " + locationData.country);
432+
}
433+
434+
if (geolocationProvider.EnableAsnCollection && !string.IsNullOrEmpty(locationData.asn))
435+
{
436+
customFieldsData["asn"] = locationData.asn;
437+
Debug.Log("BugReportUI: Including ASN data in bug report: " + locationData.asn);
438+
}
439+
}
440+
locationReceived = true;
441+
},
442+
(error) => {
443+
locationError = error;
444+
locationReceived = true;
445+
Debug.LogWarning("BugReportUI: Failed to get location/ASN data: " + error);
446+
}
447+
);
448+
449+
// Wait for location request to complete (success or failure)
450+
while (!locationReceived)
451+
{
452+
yield return null;
453+
}
454+
}
455+
456+
// Get latency data (stop any ongoing test and get current results)
457+
if (latencyProvider != null && latencyProvider.EnableLatency)
458+
{
459+
// Stop the latency test and get whatever results we have
460+
latencyProvider.StopLatencyTest();
461+
var latencyData = latencyProvider.GetCurrentLatencyData();
462+
463+
if (latencyData != null && latencyData.HasSuccessfulResults())
464+
{
465+
customFieldsData["latency"] = latencyData.GetFormattedLatency();
466+
Debug.Log("BugReportUI: Including latency data in bug report: " + latencyData.GetFormattedLatency());
467+
}
468+
else
469+
{
470+
Debug.LogWarning("BugReportUI: No latency data available for bug report.");
471+
}
472+
}
473+
474+
// Log custom fields if any
475+
if (customFieldsData.Count > 0)
476+
{
477+
var fieldsList = new System.Collections.Generic.List<string>();
478+
foreach (var kvp in customFieldsData)
479+
{
480+
fieldsList.Add($"{kvp.Key}={kvp.Value}");
481+
}
482+
Debug.Log("BugReportUI: Final custom fields: " + string.Join(", ", fieldsList.ToArray()));
483+
}
484+
385485
// Create Issue instance and post it with authentication
386486
string effectiveAuthToken = GetEffectiveAuthToken();
387487
Issue issue = new Issue(SubmitEndpoint, ProjectID, effectiveAuthToken, MessagePanelUI, ReportSubmittedUI, gameRecorder);
@@ -447,7 +547,8 @@ void SubmitBugReport()
447547
(error) =>
448548
{
449549
onIssueError(new ErrorMessage { error = error });
450-
}
550+
},
551+
customFieldsData.Count > 0 ? customFieldsData : null
451552
),
452553
(ex) => // done
453554
{
@@ -513,6 +614,16 @@ public void SetDeviceAuthManager(DeviceAuthManager authManager)
513614
deviceAuthManager = authManager;
514615
}
515616

617+
public void SetGeolocationProvider(GeolocationProvider provider)
618+
{
619+
geolocationProvider = provider;
620+
}
621+
622+
public void SetLatencyProvider(LatencyProvider provider)
623+
{
624+
latencyProvider = provider;
625+
}
626+
516627
private bool IsUserAuthenticatedViaDeviceAuth()
517628
{
518629
return deviceAuthManager != null && deviceAuthManager.IsAuthenticated();
@@ -534,6 +645,42 @@ void OnDestroy()
534645
// Cleanup handled automatically since we don't subscribe to events
535646
}
536647

648+
private void ValidateProviderCustomFields()
649+
{
650+
// Skip validation if we don't have the necessary credentials
651+
if (string.IsNullOrEmpty(SubmitEndpoint) || string.IsNullOrEmpty(ProjectID))
652+
{
653+
Debug.LogWarning("BugReportUI: Cannot validate custom fields - missing endpoint or project ID");
654+
return;
655+
}
656+
657+
string effectiveAuthToken = GetEffectiveAuthToken();
658+
if (string.IsNullOrEmpty(effectiveAuthToken))
659+
{
660+
Debug.LogWarning("BugReportUI: Cannot validate custom fields - no auth token available");
661+
return;
662+
}
663+
664+
// Collect required fields from all attached providers
665+
var requiredFields = new List<CustomFieldRequirement>();
666+
667+
if (geolocationProvider != null && geolocationProvider.RequiredCustomFields != null)
668+
{
669+
requiredFields.AddRange(geolocationProvider.RequiredCustomFields);
670+
}
671+
672+
if (latencyProvider != null && latencyProvider.RequiredCustomFields != null)
673+
{
674+
requiredFields.AddRange(latencyProvider.RequiredCustomFields);
675+
}
676+
677+
// Run validation if we have any required fields
678+
if (requiredFields.Count > 0 && customFieldValidator != null)
679+
{
680+
customFieldValidator.ValidateCustomFields(SubmitEndpoint, ProjectID, effectiveAuthToken, requiredFields);
681+
}
682+
}
683+
537684
#endregion
538685
}
539686
}

0 commit comments

Comments
 (0)