Skip to content

Commit bc0faee

Browse files
committed
serve the webinterfaces statically when available
1 parent e757fd2 commit bc0faee

File tree

10 files changed

+158
-7
lines changed

10 files changed

+158
-7
lines changed

backend/Cargo.lock

Lines changed: 59 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ path = "src/api_docs.rs"
1414
actix-web = "4.10.2"
1515
actix-web-validator = {git = "https://github.com/Tinkerforge/actix-web-validator", rev = "a001926b1a51ff07daa134abcc2d8855455f0604"}
1616
actix = "0.13.3"
17+
actix-files = "0.6"
1718

1819
dotenvy = "0.15.7"
1920
db_connector = { path = "../db_connector" }

backend/src/routes/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod check_expiration;
2323
pub mod management;
2424
pub mod selfdestruct;
2525
pub mod state;
26+
pub mod static_files;
2627
pub mod user;
2728

2829
use actix_web::web::{self, scope};
@@ -33,6 +34,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
3334
cfg.configure(user::configure);
3435
cfg.configure(auth::configure);
3536
cfg.configure(charger::configure);
37+
cfg.configure(static_files::configure);
3638

3739
cfg.service(management::management);
3840
cfg.service(selfdestruct::selfdestruct);

backend/src/routes/static_files.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* esp32-remote-access
2+
* Copyright (C) 2025 Frederic Henrichs <[email protected]>
3+
*
4+
* This library is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2 of the License, or (at your option) any later version.
8+
*
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the
16+
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17+
* Boston, MA 02111-1307, USA.
18+
*/
19+
20+
use actix_web::{web, HttpRequest, Result};
21+
use actix_files::NamedFile;
22+
use std::path::PathBuf;
23+
24+
// Define static file serving path based on build configuration
25+
26+
#[cfg(not(debug_assertions))]
27+
const STATIC_SERVE_FROM: &str = "/static/";
28+
29+
async fn serve_gzip_static(req: HttpRequest) -> Result<NamedFile> {
30+
let path: PathBuf = req.match_info().query("filename").parse().unwrap();
31+
32+
#[cfg(debug_assertions)]
33+
let static_serve_from = {
34+
let env = std::env::var("WARP_CHARGER_GIT_URL").unwrap_or_else(|_| "warp-charger".to_string());
35+
format!("{}/firmwares/static_html/", env)
36+
};
37+
#[cfg(not(debug_assertions))]
38+
let static_serve_from = "/static/";
39+
40+
let base_path = PathBuf::from(static_serve_from);
41+
let full_path = base_path.join(&path);
42+
43+
let file = NamedFile::open(full_path)?;
44+
Ok(file)
45+
}
46+
47+
pub fn configure(cfg: &mut web::ServiceConfig) {
48+
cfg.service(
49+
web::resource("/static/{filename:.*}")
50+
.route(web::get().to(serve_gzip_static))
51+
);
52+
}

docker/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ services:
4343
working_dir: /app
4444
volumes:
4545
- ../backend/target/release:/app
46+
- ../../warp-charger/firmwares/static_html:/static
4647
environment:
4748
- DATABASE_URL=${DATABASE_URL}
4849
- JWT_SECRET=${JWT_SECRET}

frontend/src/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ export function App() {
8686
}, [connected.value, t]);
8787

8888
useEffect(() => {
89+
if (window.parent !== window) {
90+
console.warn("Warp Charger is running in an iframe, closing window");
91+
const errorMessage = t("iframe.error_message");
92+
window.parent.postMessage({ type: "error", message: errorMessage }, "*");
93+
return;
94+
}
8995
refresh_access_token();
9096
startVersionChecking(10);
9197
}, []);

frontend/src/locales/de.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,8 @@ export const de ={
169169
"new_version_available": "Eine neue Version der Anwendung ist verfügbar. Jetzt neu laden, um die neuesten Funktionen und Fehlerbehebungen zu erhalten?",
170170
"already_latest": "Du verwendest bereits die neueste Version.",
171171
"new_version_confirm": "Eine neue Version ist verfügbar. Jetzt neu laden?"
172+
},
173+
"iframe": {
174+
"error_message": "Fernzugriff kann nicht in einem iframe ausgeführt werden"
172175
}
173176
};

frontend/src/locales/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,8 @@ export const en = {
173173
"new_version_available": "A new version of the application is available. Reload now to get the latest features and bug fixes?",
174174
"already_latest": "You are already using the latest version.",
175175
"new_version_confirm": "A new version is available. Reload now?"
176+
},
177+
"iframe": {
178+
"error_message": "Remote Access cannot run in an iframe"
176179
}
177180
};

frontend/src/pages/Frame.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ class VirtualNetworkInterface {
8686
// This handles Messages from the iframe/ Device-Webinterface
8787
iframeMessageHandler(e: MessageEvent) {
8888
const iframe = document.getElementById("interface") as HTMLIFrameElement;
89+
if (typeof e.data !== "string") {
90+
const data = e.data as Message;
91+
this.handleErrorMessage(data);
92+
return;
93+
}
8994
switch (e.data) {
9095
case "initIFrame":
9196
this.worker.postMessage("connect");
@@ -149,8 +154,9 @@ class VirtualNetworkInterface {
149154
switch (e.data) {
150155
case "ready":
151156
this.setParentState.parentState({connection_state: ConnectionState.LoadingWebinterface});
157+
const firmware_version = this.chargerInfo.firmware_version;
152158
const iframe = document.getElementById("interface") as HTMLIFrameElement;
153-
iframe.src = `/wg-${this.id}/${this.path}`;
159+
iframe.src = `/wg-${this.id}/${this.path}?firmware_version=${encodeURIComponent(firmware_version)}`;
154160
iframe.addEventListener("load", () => {
155161
clearTimeout(this.timeout);
156162
this.setParentState.parentState({show_spinner: false});

frontend/src/sw.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,44 @@ function handleWGRequest(event: FetchEvent) {
2929
});
3030
if (event.request.headers.has("X-Connection-Id") || url.startsWith("/wg-")) {
3131
let receiver_id = "";
32+
let firmware_version: string | null = null;
3233
if (url.startsWith("/wg-")) {
3334
url = url.replace("/wg-", "");
3435
const first = url.indexOf("/");
3536
receiver_id = url.substring(0, first);
3637
url = url.replace(receiver_id, "");
38+
const parsedUrl = new URL(url, self.location.origin);
39+
firmware_version = parsedUrl.searchParams.get("firmware_version");
40+
firmware_version = firmware_version?.replace("+", "_") || null;
41+
firmware_version = firmware_version?.replaceAll(".", "_") || null;
3742
} else {
3843
receiver_id = event.request.headers.get("X-Connection-Id") as string;
3944
}
4045
const promise: Promise<Response> = new Promise(async (resolve) => {
46+
if (firmware_version) {
47+
const response = await fetch(`/api/static/${firmware_version}_index.html`);
48+
if (response.status === 200) {
49+
const ds = new DecompressionStream("gzip");
50+
const stream = response.body?.pipeThrough(ds);
51+
if (stream) {
52+
const decompressedResponse = new Response(stream, {
53+
status: response.status,
54+
statusText: response.statusText,
55+
headers: response.headers
56+
});
57+
resolve(decompressedResponse);
58+
return;
59+
}
60+
}
61+
}
62+
4163
const id = crypto.randomUUID();
4264
const body = await event.request.arrayBuffer();
4365
const headers: [string, string][] = [];
4466
event.request.headers.forEach((val, key) => {
4567
headers.push([key, val]);
4668
});
47-
const fetch: FetchMessage = {
69+
const message: FetchMessage = {
4870
method: event.request.method,
4971
headers,
5072
body: body.byteLength === 0 ? undefined : body,
@@ -54,7 +76,7 @@ function handleWGRequest(event: FetchEvent) {
5476
receiver_id,
5577
id,
5678
type: MessageType.Fetch,
57-
data: fetch
79+
data: message
5880
};
5981
self.addEventListener(id, (e: Event) => {
6082
const event = e as CustomEvent;

0 commit comments

Comments
 (0)