Skip to main content

Mountain/Binary/Build/
LocalhostPlugin.rs

1//! # Localhost Plugin Module
2//!
3//! Configures and creates the Tauri localhost plugin with CORS headers for
4//! Service Workers and an OTLP proxy for build-baked telemetry.
5
6use std::{
7	io::{Read, Write},
8	net::TcpStream,
9	time::Duration,
10};
11
12use tauri::plugin::TauriPlugin;
13
14/// OTLP collector host:port. OTELBridge.ts sends to `/v1/traces` (same-origin),
15/// this proxy forwards to the real collector via raw TCP. Zero CORS issues.
16const OTLP_HOST:&str = "127.0.0.1:4318";
17
18/// Forward a JSON body to the OTLP collector via raw HTTP/1.1 POST.
19/// Returns true if the collector accepted (2xx), false otherwise.
20fn ProxyToOTLP(Body:&[u8]) -> bool {
21	let Ok(mut Stream) = TcpStream::connect_timeout(&OTLP_HOST.parse().unwrap(), Duration::from_millis(500)) else {
22		return false;
23	};
24
25	let _ = Stream.set_write_timeout(Some(Duration::from_millis(500)));
26	let _ = Stream.set_read_timeout(Some(Duration::from_millis(500)));
27
28	let Request = format!(
29		"POST /v1/traces HTTP/1.1\r\nHost: {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: \
30		 close\r\n\r\n",
31		OTLP_HOST,
32		Body.len(),
33	);
34
35	if Stream.write_all(Request.as_bytes()).is_err() {
36		return false;
37	}
38	if Stream.write_all(Body).is_err() {
39		return false;
40	}
41
42	let mut ResponseBuffer = [0u8; 32];
43	let _ = Stream.read(&mut ResponseBuffer);
44	// Check for "HTTP/1.1 2" - any 2xx status
45	ResponseBuffer.starts_with(b"HTTP/1.1 2") || ResponseBuffer.starts_with(b"HTTP/1.0 2")
46}
47
48/// Creates and configures the localhost plugin with CORS headers preconfigured.
49///
50/// # CORS Configuration
51///
52/// - Access-Control-Allow-Origin: * (allows all origins)
53/// - Access-Control-Allow-Methods: GET, POST, OPTIONS, HEAD
54/// - Access-Control-Allow-Headers: Content-Type, Authorization, Origin, Accept
55///
56/// # OTLP Proxy
57///
58/// Requests to `/v1/traces` are forwarded to the local OTLP collector
59/// (Jaeger, OTEL Collector, etc.) so OTELBridge.ts can send telemetry
60/// without cross-origin issues. Uses raw TCP - no extra HTTP client dependency.
61pub fn LocalhostPlugin<R:tauri::Runtime>(ServerPort:u16) -> TauriPlugin<R> {
62	tauri_plugin_localhost::Builder::new(ServerPort)
63		.on_request(|Request, Response| {
64			// CORS headers for Service Workers and frontend integration
65			Response.add_header("Access-Control-Allow-Origin", "*");
66			Response.add_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
67			Response.add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept");
68
69			// OTLP proxy: forward /v1/traces to the local collector
70			let Url = Request.url();
71			if Url.contains("/v1/traces") {
72				Response.set_handled(true);
73				let Body = Request.body();
74				if ProxyToOTLP(Body) {
75					Response.set_status(200);
76					Response.add_header("Content-Type", "application/json");
77				} else {
78					// Collector not running - 204 silently.
79					// OTELBridge stops retrying after first failure.
80					Response.set_status(204);
81				}
82			}
83		})
84		.build()
85}