Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: offline support with service worker caching #141

Merged
merged 37 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
15132ba
feat(server): Serve widgets through server
veryard Oct 30, 2024
08343fb
wip: Linux?
veryard Nov 1, 2024
fd1f089
remove setting of user agent
lars-berger Dec 6, 2024
f7432ae
attach token via header in service worker
lars-berger Dec 6, 2024
36e7419
go to init route first then redirect to target
lars-berger Dec 6, 2024
29aed6c
change to use "asset id" for identifying incoming requests
lars-berger Dec 6, 2024
6fb869b
move server-related functions to new `asset_server` mod
lars-berger Dec 6, 2024
c90e495
move initialization script to its own file
lars-berger Dec 6, 2024
6073d2a
feat: add normalize css in initialization script
lars-berger Dec 6, 2024
3c6fe0b
add empty favicon in initialization script
lars-berger Dec 7, 2024
77ea1ce
remove `cache_assets` from config
lars-berger Dec 7, 2024
aa58b93
change localhost port to `6124`
lars-berger Dec 7, 2024
67c5745
parse with params when creating webview url
lars-berger Dec 7, 2024
7d21a74
share cache id for widgets in the same top-level dir
lars-berger Dec 7, 2024
3c620c9
simplify retrieval of widget assets from rocket route
lars-berger Dec 7, 2024
f10668b
simplify `NotFound` errors by returning an `Option`
lars-berger Dec 7, 2024
3d173db
allow service worker to be served from subroute
lars-berger Dec 7, 2024
ad72515
handle opaque responses in sw
lars-berger Dec 8, 2024
a6e2262
prevent directory traversal when serving assets
lars-berger Dec 8, 2024
91e269a
add config options and create form component
lars-berger Dec 8, 2024
9e2d05f
wip custom field for cache duration
lars-berger Dec 8, 2024
8e3a008
working cache duration field
lars-berger Dec 8, 2024
d484cea
form styling
lars-berger Dec 8, 2024
dbbece6
add state route for the sw to fetch from
lars-berger Dec 9, 2024
7d3b109
bump @glzr/components
lars-berger Dec 9, 2024
e18c986
wip passing config to sw
lars-berger Dec 9, 2024
1ffa1c7
successfully set config from initialization script
lars-berger Dec 9, 2024
eda8728
update widget config schema
lars-berger Dec 9, 2024
4776009
wip implementation of cache duration + rules
lars-berger Dec 9, 2024
b9f4f82
store metadata in separate cache
lars-berger Dec 9, 2024
688a54b
fix cache duration
lars-berger Dec 9, 2024
8b016f3
clear sw cache from system tray
lars-berger Dec 9, 2024
0523027
inline script for clearing sw cache
lars-berger Dec 9, 2024
f0badde
share token for widgets in the same directory
lars-berger Dec 10, 2024
fe26e6b
implement `PathExt` for `Path`
lars-berger Dec 10, 2024
9e7202b
move `create_init_url` to `asset_server` mod
lars-berger Dec 10, 2024
c15d322
move out asset server token map to `asset_server` mod
lars-berger Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
move out asset server token map to asset_server mod
  • Loading branch information
lars-berger committed Dec 10, 2024
commit c15d322197ab3e1110b41e63724849081c0cfe6b
84 changes: 52 additions & 32 deletions packages/desktop/src/asset_server.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
use std::{
collections::HashMap,
io::Cursor,
path::{Path, PathBuf},
sync::Arc,
sync::LazyLock,
};

use rocket::{
fs::NamedFile,
http::{ContentType, Cookie, CookieJar, Header, SameSite, Status},
request::{FromRequest, Outcome},
response::{self, Redirect, Responder, Response},
tokio::task,
Request, State,
Request,
};
use tokio::{sync::Mutex, task};
use uuid::Uuid;

use crate::{
common::PathExt, config::Config, widget_factory::WidgetFactory,
};
use crate::common::PathExt;

/// Port for the localhost asset server.
const ASSET_SERVER_PORT: u16 = 6124;

/// Map of tokens to their corresponding path.
static ASSET_SERVER_TOKENS: LazyLock<Mutex<HashMap<String, PathBuf>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));

pub fn setup_asset_server(
config: Arc<Config>,
widget_factory: Arc<WidgetFactory>,
) {
pub fn setup_asset_server() {
task::spawn(async move {
let rocket = rocket::build()
.configure(rocket::Config::figment().merge(("port", 6124)))
.manage(config)
.manage(widget_factory)
.configure(
rocket::Config::figment().merge(("port", ASSET_SERVER_PORT)),
)
.mount("/", routes![sw_js, normalize_css, init, serve]);

if let Err(err) = rocket.launch().await {
Expand All @@ -35,32 +39,48 @@ pub fn setup_asset_server(
}

pub async fn create_init_url(
widget_factory: &WidgetFactory,
parent_dir: &Path,
html_path: &Path,
) -> anyhow::Result<tauri::Url> {
// Generate a unique token to identify requests from the widget to the
// asset server.
let token = upsert_or_get_token(parent_dir).await;

let redirect = format!(
"/{}",
html_path.strip_prefix(parent_dir)?.to_unicode_string()
);

let url = tauri::Url::parse_with_params(
"http://127.0.0.1:6124/__zebar/init",
&[
(
"token",
// Generate a unique token to identify requests from the
// widget to the asset server.
&widget_factory.upsert_or_get_token(parent_dir).await,
),
(
"redirect",
&format!(
"/{}",
html_path.strip_prefix(parent_dir)?.to_unicode_string()
),
),
],
&format!("http://127.0.0.1:{}/__zebar/init", ASSET_SERVER_PORT),
&[("token", &token), ("redirect", &redirect)],
)?;

Ok(url)
}

/// Returns an asset server token for a given directory.
///
/// If the directory does not have an existing token, a new one is
/// generated and inserted.
async fn upsert_or_get_token(directory: &Path) -> String {
let mut asset_server_tokens = ASSET_SERVER_TOKENS.lock().await;

// Find existing token for this path.
let found_token = asset_server_tokens
.iter()
.find(|(_, path)| *path == directory)
.map(|(token, _)| token.clone());

found_token.unwrap_or_else(|| {
let new_token = Uuid::new_v4().to_string();

asset_server_tokens.insert(new_token.clone(), directory.to_path_buf());

new_token
})
}

#[get("/__zebar/init?<token>&<redirect>")]
pub fn init(
token: String,
Expand Down Expand Up @@ -106,10 +126,10 @@ pub fn normalize_css() -> (ContentType, &'static str) {
pub async fn serve(
path: Option<PathBuf>,
token: ServerToken,
widget_factory: &State<Arc<WidgetFactory>>,
) -> Option<NamedFile> {
// Retrieve base directory for the corresponding token.
let base_url = widget_factory.directory_by_token(&token.0).await?;
let base_url =
{ ASSET_SERVER_TOKENS.lock().await.get(&token.0).cloned() }?;

let asset_path = base_url
.join(path.unwrap_or("index.html".into()))
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async fn start_app(app: &mut tauri::App, cli: Cli) -> anyhow::Result<()> {
// guaranteed to be one of the open commands here.
setup_single_instance(app, widget_factory.clone())?;

setup_asset_server(config.clone(), widget_factory.clone());
setup_asset_server();

// Prevent windows from showing up in the dock on MacOS.
#[cfg(target_os = "macos")]
Expand Down
45 changes: 2 additions & 43 deletions packages/desktop/src/widget_factory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
path::PathBuf,
sync::{
atomic::{AtomicU32, Ordering},
Arc,
Expand All @@ -19,7 +19,6 @@ use tokio::{
task,
};
use tracing::{error, info};
use uuid::Uuid;

#[cfg(target_os = "macos")]
use crate::common::macos::WindowExtMacOs;
Expand All @@ -44,9 +43,6 @@ pub struct WidgetFactory {

pub close_tx: broadcast::Sender<String>,

/// Map of directory tokens to their corresponding path.
asset_server_tokens: Arc<Mutex<HashMap<String, PathBuf>>>,

/// Reference to `Config`.
config: Arc<Config>,

Expand Down Expand Up @@ -168,7 +164,6 @@ impl WidgetFactory {
_close_rx,
close_tx,
config,
asset_server_tokens: Arc::new(Mutex::new(HashMap::new())),
_open_rx,
open_tx,
monitor_state,
Expand Down Expand Up @@ -259,7 +254,7 @@ impl WidgetFactory {
}

let webview_url = WebviewUrl::External(
create_init_url(self, &parent_dir, &html_path).await?,
create_init_url(&parent_dir, &html_path).await?,
);

let mut state = WidgetState {
Expand Down Expand Up @@ -811,40 +806,4 @@ impl WidgetFactory {
},
)
}

/// Returns an asset server token for a given directory.
///
/// If the directory does not have an existing token, a new one is
/// generated and inserted.
pub async fn upsert_or_get_token(&self, directory: &Path) -> String {
let mut asset_server_tokens = self.asset_server_tokens.lock().await;

// Find existing token for this path.
let found_token = asset_server_tokens
.iter()
.find(|(_, path)| *path == directory)
.map(|(token, _)| token.clone());

found_token.unwrap_or_else(|| {
let new_token = Uuid::new_v4().to_string();

asset_server_tokens
.insert(new_token.clone(), directory.to_path_buf());

new_token
})
}

/// Returns the base directory for a given asset server token.
pub async fn directory_by_token(
&self,
asset_server_token: &str,
) -> Option<PathBuf> {
self
.asset_server_tokens
.lock()
.await
.get(asset_server_token)
.cloned()
}
}
Loading