[feat] [2.0 beta] allow the configuration for the ip address for frontend dev server as well for ios/android
Describe the problem
currently it is hard to debug because my android/ios devices always looking for an internal ip address.
as a mobile dev with 10+ year experience, I haven't found a way to debug tauri 2.0 apps on mobile devices
Describe the solution you'd like
on android, we can use adb reverse to forward the port.
on ios, it would be better to expose an env var to configure this port
Alternatives considered
No response
Additional context
No response
sorry just noticed
pnpm tauri android dev --force-ip-prompt 127.0.0.1
is there a way to make the param 127.0.0.1 work?
any new progress here? I encountered the same problem...
I got it work. but needs a bit, too much code...
This code lookup all local IP addresses and replaces the one that is accessible through WiFi connection instead of the devUrl inside tauri.conf.json .
- You may want to modify it to use other network adapter such as ethernet if your device is sharing internet using USB
- It does not modify the
tauri.conf.json, it shares the ip in rust process and updates the context during runtime. - specifing
devUrlin tauri.conf.json is still necessary since this code just replaces the hostname with the IP and reuses the same port. - When running tauri command, it still logs the internal IP. but the correct IP will be replaced during runtime.
tauri.conf.json
"build": {
"devUrl": "http://localhost:1420",
...
},
build.rs
use if_addrs::get_if_addrs;
use serde_json::Value;
use std::fs;
use url::Url;
/// Detects the first 192.168.x.x IPv4 address on a Wi-Fi interface.
fn detect_wifi_ipv4() -> Option<String> {
for iface in get_if_addrs().ok()? {
let name = iface.name.to_lowercase();
if !(name.contains("wlan")
|| name.contains("wifi")
|| name == "en0"
|| name.contains("wi-fi"))
{
continue;
}
if let std::net::IpAddr::V4(v4) = iface.ip() {
let oct = v4.octets();
if oct[0] == 192 && oct[1] == 168 {
return Some(v4.to_string());
}
}
}
None
}
fn apply_dev_url() {
let config_path = "tauri.conf.json";
let config_str = fs::read_to_string(config_path).expect("Failed to read tauri.conf.json");
let json: Value = serde_json::from_str(&config_str).expect("Invalid JSON in tauri.conf.json");
// Extract the existing devUrl URL
let dev_path = json["build"]["devUrl"]
.as_str()
.expect("build.devUrl must be a string");
// Parse URL and replace host with detected IP (fallback to localhost)
let mut url = Url::parse(dev_path).expect("devUrl is not a valid URL");
let new_ip = detect_wifi_ipv4().unwrap_or_else(|| "127.0.0.1".into());
url.set_host(Some(&new_ip)).expect("Failed to set host");
// Preserve scheme, port, and path. Expose via env var for use in Rust.
println!("cargo:rustc-env=TAURI_DEV_URL={}", url.as_str());
}
fn main() {
apply_dev_url();
...
}
src/lib.rs
use tauri::Url;
use tauri_plugin_log::{Target, TargetKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let mut context = tauri::generate_context!();
let custom_dev_url = env!("TAURI_DEV_URL");
context.config_mut().build.dev_url = Some(Url::parse(custom_dev_url).unwrap());
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![])
.run(context)
.expect("error while running tauri application");
}
Cargo.toml
Add These dependencies to build-deps
[build-dependencies]
serde_json = "1.0"
if-addrs = "0.13.4"
url = "2.5.4"
vite.config.mts (or any other frontend server/bundler)
For frontend, it is not required to do anything. But I lookup for WiFi IPV4 as same as the rust code to set websocket's IP, since vite is not smart enough to use the same IPV4 (or I may be wrong, but this is how it worked)
import os from "os";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
const host = getWifiIPv4();
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [react(), tailwindcss()],
clearScreen: false,
server: {
port: 1420,
strictPort: true,
host: "0.0.0.0",
hmr: {
protocol: "ws",
port: 1421,
host,
},
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
}));
function getWifiIPv4() {
const nets = os.networkInterfaces();
// Adjust these name checks for your platform if needed
const isWifiInterface = (name: string) => {
const lname = name.toLowerCase();
return (
lname.includes("wlan") || // common Linux
lname.includes("wifi") || // Windows & Linux
lname === "en0" || // macOS default Wi‑Fi
lname.includes("wi-fi") // Windows
);
};
for (const [name, addrs] of Object.entries(nets)) {
if (!isWifiInterface(name) || !addrs) continue;
for (const { family, address, internal } of addrs) {
if (family === "IPv4" && !internal && address.startsWith("192.168.")) {
return address;
}
}
}
return "127.0.0.1";
}
I generated these codes with the help of AI. They may have some issues that I am not aware of.
Edit: If you are using this code, give me your feedback. if the code becomes stable and reliable, I can create a library out of it for easier integration and customization.
the IP address is determined by Tauri but your frontend needs to listen on that same address; you can either configure it to listen on 0.0.0.0 (or host: true on some frameworks) or use the TAURI_DEV_HOST environment variable. See https://tauri.app/develop/#development-server
no need to go with build scripts and manually updating the config, the Tauri CLI handles that for you.
the IP address is determined by Tauri but your frontend needs to listen on that same address; you can either configure it to listen on 0.0.0.0 (or
host: trueon some frameworks) or use the TAURI_DEV_HOST environment variable. See https://tauri.app/develop/#development-server no need to go with build scripts and manually updating the config, the Tauri CLI handles that for you.
As I remember TAURI_DEV_HOST is not settable and will be set by tauri and points to the internal IP which is not accessible by mobile (Is not in 192.168.X.X range). Currently I cannot test it but I think I tested that already when I got that problem. If you are sure about it and got it work, make a ROCKET reaction and I will delete this current comment.