Skip to content

Commit e38d4f8

Browse files
committed
Load the new version of the webinterface when device got updated
Fixes #215
1 parent cb16b3f commit e38d4f8

File tree

6 files changed

+175
-65
lines changed

6 files changed

+175
-65
lines changed

backend/src/api_docs.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async fn main() {
8282
routes::auth::register::register,
8383
routes::auth::verify::verify,
8484
routes::auth::generate_salt::generate_salt,
85-
routes::auth:: jwt_refresh::jwt_refresh,
85+
routes::auth::jwt_refresh::jwt_refresh,
8686
routes::auth::get_login_salt::get_login_salt,
8787
routes::auth::recovery::recovery,
8888
routes::auth::start_recovery::start_recovery,
@@ -108,6 +108,7 @@ async fn main() {
108108
routes::check_expiration::check_expiration,
109109
routes::management::management,
110110
routes::send_chargelog_to_user::send_chargelog,
111+
routes::webinterface::get_webinterface,
111112
),
112113
components(schemas(
113114
routes::auth::login::LoginSchema,

backend/src/routes/static_files.rs

Lines changed: 0 additions & 50 deletions
This file was deleted.

backend/src/routes/webinterface.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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_files::NamedFile;
21+
use actix_web::{get, web, Result};
22+
use utoipa::IntoParams;
23+
use std::path::PathBuf;
24+
25+
use crate::utils::{get_charger_from_db, parse_uuid};
26+
27+
#[cfg(not(debug_assertions))]
28+
const STATIC_SERVE_FROM: &str = "/static/";
29+
30+
fn get_file(filename: String) -> Result<NamedFile> {
31+
let path: PathBuf = filename.parse().unwrap();
32+
33+
#[cfg(debug_assertions)]
34+
let static_serve_from = {
35+
let env =
36+
std::env::var("WARP_CHARGER_GIT_PATH").unwrap_or_else(|_| "warp-charger".to_string());
37+
format!("{env}/firmwares/static_html/")
38+
};
39+
#[cfg(not(debug_assertions))]
40+
let static_serve_from = "/static/";
41+
42+
let base_path = PathBuf::from(static_serve_from);
43+
let full_path = base_path.join(&path);
44+
45+
let file = NamedFile::open(full_path)?;
46+
Ok(file)
47+
}
48+
49+
#[derive(serde::Deserialize, serde::Serialize, IntoParams)]
50+
pub struct StaticFileQuery {
51+
#[param(example = "550e8400-e29b-41d4-a716-446655440000")]
52+
charger: String,
53+
}
54+
55+
#[utoipa::path(
56+
params(
57+
StaticFileQuery
58+
),
59+
responses(
60+
(status = 200, description = "Webinterface HTML file"),
61+
(status = 400, description = "Invalid UUID format"),
62+
(status = 401, description = "Unauthorized"),
63+
(status = 404, description = "File not found"),
64+
(status = 500, description = "Internal server error"),
65+
),
66+
security(
67+
("jwt" = [])
68+
)
69+
)]
70+
#[get("/webinterface")]
71+
pub async fn get_webinterface(
72+
query: web::Query<StaticFileQuery>,
73+
user: crate::models::uuid::Uuid,
74+
state: web::Data<crate::AppState>,
75+
) -> Result<NamedFile> {
76+
let charger = parse_uuid(&query.charger)?;
77+
let user: uuid::Uuid = user.into();
78+
79+
if !crate::routes::charger::user_is_allowed(&state, user, charger).await? {
80+
return Err(actix_web::error::ErrorUnauthorized("Unauthorized"));
81+
}
82+
83+
let charger = get_charger_from_db(charger, &state).await?;
84+
let firmware = charger.firmware_version.replace('+', "_");
85+
let firmware = firmware.replace('.', "_");
86+
let firmware = format!("{firmware}_index.html");
87+
get_file(firmware)
88+
}

frontend/src/pages/Frame.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,11 @@ class VirtualNetworkInterface {
154154
switch (e.data) {
155155
case "ready":
156156
this.setParentState.parentState({connection_state: ConnectionState.LoadingWebinterface});
157-
const firmware_version = this.chargerInfo.firmware_version;
158157
const iframe = document.getElementById("interface") as HTMLIFrameElement;
159-
iframe.src = `/wg-${this.id}/${this.path}?firmware_version=${encodeURIComponent(firmware_version)}`;
158+
const urlParams = new URLSearchParams([
159+
['charger', this.chargerInfo.id],
160+
]);
161+
iframe.src = `/wg-${this.id}/${this.path}?${urlParams.toString()}`;
160162
iframe.addEventListener("load", () => {
161163
clearTimeout(this.timeout);
162164
this.setParentState.parentState({show_spinner: false});

frontend/src/schema.d.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,22 @@ export interface paths {
501501
patch?: never;
502502
trace?: never;
503503
};
504+
"/webinterface": {
505+
parameters: {
506+
query?: never;
507+
header?: never;
508+
path?: never;
509+
cookie?: never;
510+
};
511+
get: operations["get_webinterface"];
512+
put?: never;
513+
post?: never;
514+
delete?: never;
515+
options?: never;
516+
head?: never;
517+
patch?: never;
518+
trace?: never;
519+
};
504520
}
505521
export type webhooks = Record<string, never>;
506522
export interface components {
@@ -1590,4 +1606,53 @@ export interface operations {
15901606
};
15911607
};
15921608
};
1609+
get_webinterface: {
1610+
parameters: {
1611+
query: {
1612+
/** @example 550e8400-e29b-41d4-a716-446655440000 */
1613+
charger: string;
1614+
};
1615+
header?: never;
1616+
path?: never;
1617+
cookie?: never;
1618+
};
1619+
requestBody?: never;
1620+
responses: {
1621+
/** @description Webinterface HTML file */
1622+
200: {
1623+
headers: {
1624+
[name: string]: unknown;
1625+
};
1626+
content?: never;
1627+
};
1628+
/** @description Invalid UUID format */
1629+
400: {
1630+
headers: {
1631+
[name: string]: unknown;
1632+
};
1633+
content?: never;
1634+
};
1635+
/** @description Unauthorized */
1636+
401: {
1637+
headers: {
1638+
[name: string]: unknown;
1639+
};
1640+
content?: never;
1641+
};
1642+
/** @description File not found */
1643+
404: {
1644+
headers: {
1645+
[name: string]: unknown;
1646+
};
1647+
content?: never;
1648+
};
1649+
/** @description Internal server error */
1650+
500: {
1651+
headers: {
1652+
[name: string]: unknown;
1653+
};
1654+
content?: never;
1655+
};
1656+
};
1657+
};
15931658
}

frontend/src/sw.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,31 @@ function handleWGRequest(event: FetchEvent) {
2828
headers1.push([key, val]);
2929
});
3030
if (event.request.headers.has("X-Connection-Id") || url.startsWith("/wg-")) {
31-
let receiver_id = "";
32-
let firmware_version: string | null = null;
31+
let receiverId = "";
32+
let chargerId: string | null = null;
3333
if (url.startsWith("/wg-")) {
3434
url = url.replace("/wg-", "");
3535
const first = url.indexOf("/");
36-
receiver_id = url.substring(0, first);
37-
url = url.replace(receiver_id, "");
36+
receiverId = url.substring(0, first);
37+
url = url.replace(receiverId, "");
3838
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;
39+
chargerId = parsedUrl.searchParams.get("charger");
4240
if (parsedUrl.pathname !== "/") {
43-
firmware_version = null;
4441
url = parsedUrl.pathname;
4542
}
4643
} else {
47-
receiver_id = event.request.headers.get("X-Connection-Id") as string;
44+
receiverId = event.request.headers.get("X-Connection-Id") as string;
4845
}
4946
const promise: Promise<Response> = new Promise(async (resolve) => {
50-
if (firmware_version) {
51-
const response = await fetch(`/api/static/${firmware_version}_index.html`);
47+
if (chargerId) {
48+
const params = new URLSearchParams([
49+
['charger', chargerId]
50+
]);
51+
const req = new Request(`/api/webinterface?${params.toString()}`, {cache: 'no-cache'});
52+
const response = await fetch(req, {
53+
method: 'GET',
54+
headers: new Headers(headers1)
55+
});
5256
if (response.status === 200) {
5357
const ds = new DecompressionStream("gzip");
5458
const stream = response.body?.pipeThrough(ds);
@@ -77,7 +81,7 @@ function handleWGRequest(event: FetchEvent) {
7781
url
7882
};
7983
const msg: Message = {
80-
receiver_id,
84+
receiver_id: receiverId,
8185
id,
8286
type: MessageType.Fetch,
8387
data: message

0 commit comments

Comments
 (0)