pwa prep
This commit is contained in:
parent
579869f2f0
commit
8664f9d210
9 changed files with 302 additions and 9 deletions
1
TODO.md
Normal file
1
TODO.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Installable PWAs need https...
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
[application]
|
[application]
|
||||||
|
name = "cursor-move-webapp"
|
||||||
|
default_platform = "web"
|
||||||
|
|
||||||
[web.app]
|
[web.app]
|
||||||
|
|
||||||
|
|
@ -19,3 +21,7 @@ script = []
|
||||||
# Javascript code file
|
# Javascript code file
|
||||||
# serve: [dev-server] only
|
# serve: [dev-server] only
|
||||||
script = []
|
script = []
|
||||||
|
|
||||||
|
[web.watcher]
|
||||||
|
reload_html = true
|
||||||
|
watch_path = ["src", "public"]
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 4.2 KiB |
22
crates/cursor-move-webapp/index.html
Normal file
22
crates/cursor-move-webapp/index.html
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{app_title}</title>
|
||||||
|
<script>
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
navigator.serviceWorker.register("/{base_path}/assets/sw.js");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="manifest" href="/assets/manifest.json" />
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||||
|
<meta
|
||||||
|
http-equiv="Content-Security-Policy"
|
||||||
|
content="worker-src 'self'"
|
||||||
|
/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
crates/cursor-move-webapp/public/assets/logo_192.png
Normal file
BIN
crates/cursor-move-webapp/public/assets/logo_192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
crates/cursor-move-webapp/public/assets/logo_512.png
Normal file
BIN
crates/cursor-move-webapp/public/assets/logo_512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
34
crates/cursor-move-webapp/public/assets/manifest.json
Normal file
34
crates/cursor-move-webapp/public/assets/manifest.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "Cursor Mover",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "logo_192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo_512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo_512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "any",
|
||||||
|
"purpose": "any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"id": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"display_override": ["window-control-overlay", "standalone"],
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"short_name": "Cursr Mover",
|
||||||
|
"description": "A web control your cursor and keyboard via a smartphone.",
|
||||||
|
"dir": "ltr",
|
||||||
|
"lang": "en",
|
||||||
|
"orientation": "portrait"
|
||||||
|
}
|
||||||
198
crates/cursor-move-webapp/public/assets/sw.js
Normal file
198
crates/cursor-move-webapp/public/assets/sw.js
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//console.log('WORKER: executing.');
|
||||||
|
|
||||||
|
/* A version number is useful when updating the worker logic,
|
||||||
|
allowing you to remove outdated cache entries during the update.
|
||||||
|
*/
|
||||||
|
var version = "v1.0.0::";
|
||||||
|
|
||||||
|
/* These resources will be downloaded and cached by the service worker
|
||||||
|
during the installation process. If any resource fails to be downloaded,
|
||||||
|
then the service worker won't be installed either.
|
||||||
|
*/
|
||||||
|
var offlineFundamentals = [
|
||||||
|
// add here the files you want to cache
|
||||||
|
//"favicon.ico",
|
||||||
|
];
|
||||||
|
|
||||||
|
/* The install event fires when the service worker is first installed.
|
||||||
|
You can use this event to prepare the service worker to be able to serve
|
||||||
|
files while visitors are offline.
|
||||||
|
*/
|
||||||
|
self.addEventListener("install", function (event) {
|
||||||
|
//console.log('WORKER: install event in progress.');
|
||||||
|
/* Using event.waitUntil(p) blocks the installation process on the provided
|
||||||
|
promise. If the promise is rejected, the service worker won't be installed.
|
||||||
|
*/
|
||||||
|
event.waitUntil(
|
||||||
|
/* The caches built-in is a promise-based API that helps you cache responses,
|
||||||
|
as well as finding and deleting them.
|
||||||
|
*/
|
||||||
|
caches
|
||||||
|
/* You can open a cache by name, and this method returns a promise. We use
|
||||||
|
a versioned cache name here so that we can remove old cache entries in
|
||||||
|
one fell swoop later, when phasing out an older service worker.
|
||||||
|
*/
|
||||||
|
.open(version + "fundamentals")
|
||||||
|
.then(function (cache) {
|
||||||
|
/* After the cache is opened, we can fill it with the offline fundamentals.
|
||||||
|
The method below will add all resources in `offlineFundamentals` to the
|
||||||
|
cache, after making requests for them.
|
||||||
|
*/
|
||||||
|
return cache.addAll(offlineFundamentals);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
//console.log('WORKER: install completed');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* The fetch event fires whenever a page controlled by this service worker requests
|
||||||
|
a resource. This isn't limited to `fetch` or even XMLHttpRequest. Instead, it
|
||||||
|
comprehends even the request for the HTML page on first load, as well as JS and
|
||||||
|
CSS resources, fonts, any images, etc.
|
||||||
|
*/
|
||||||
|
self.addEventListener("fetch", function (event) {
|
||||||
|
//console.log('WORKER: fetch event in progress.');
|
||||||
|
|
||||||
|
/* We should only cache GET requests, and deal with the rest of method in the
|
||||||
|
client-side, by handling failed POST,PUT,PATCH,etc. requests.
|
||||||
|
*/
|
||||||
|
if (event.request.method !== "GET") {
|
||||||
|
/* If we don't block the event as shown below, then the request will go to
|
||||||
|
the network as usual.
|
||||||
|
*/
|
||||||
|
//console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Similar to event.waitUntil in that it blocks the fetch event on a promise.
|
||||||
|
Fulfillment result will be used as the response, and rejection will end in a
|
||||||
|
HTTP response indicating failure.
|
||||||
|
*/
|
||||||
|
event.respondWith(
|
||||||
|
caches
|
||||||
|
/* This method returns a promise that resolves to a cache entry matching
|
||||||
|
the request. Once the promise is settled, we can then provide a response
|
||||||
|
to the fetch request.
|
||||||
|
*/
|
||||||
|
.match(event.request)
|
||||||
|
.then(function (cached) {
|
||||||
|
/* Even if the response is in our cache, we go to the network as well.
|
||||||
|
This pattern is known for producing "eventually fresh" responses,
|
||||||
|
where we return cached responses immediately, and meanwhile pull
|
||||||
|
a network response and store that in the cache.
|
||||||
|
|
||||||
|
Read more:
|
||||||
|
https://ponyfoo.com/articles/progressive-networking-serviceworker
|
||||||
|
*/
|
||||||
|
var networked = fetch(event.request)
|
||||||
|
// We handle the network request with success and failure scenarios.
|
||||||
|
.then(fetchedFromNetwork, unableToResolve)
|
||||||
|
// We should catch errors on the fetchedFromNetwork handler as well.
|
||||||
|
.catch(unableToResolve);
|
||||||
|
|
||||||
|
/* We return the cached response immediately if there is one, and fall
|
||||||
|
back to waiting on the network as usual.
|
||||||
|
*/
|
||||||
|
//console.log('WORKER: fetch event', cached ? '(cached)' : '(network)', event.request.url);
|
||||||
|
return cached || networked;
|
||||||
|
|
||||||
|
function fetchedFromNetwork(response) {
|
||||||
|
/* We copy the response before replying to the network request.
|
||||||
|
This is the response that will be stored on the ServiceWorker cache.
|
||||||
|
*/
|
||||||
|
var cacheCopy = response.clone();
|
||||||
|
|
||||||
|
//console.log('WORKER: fetch response from network.', event.request.url);
|
||||||
|
|
||||||
|
caches
|
||||||
|
// We open a cache to store the response for this request.
|
||||||
|
.open(version + "pages")
|
||||||
|
.then(function add(cache) {
|
||||||
|
/* We store the response for this request. It'll later become
|
||||||
|
available to caches.match(event.request) calls, when looking
|
||||||
|
for cached responses.
|
||||||
|
*/
|
||||||
|
cache.put(event.request, cacheCopy);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
//console.log('WORKER: fetch response stored in cache.', event.request.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the response so that the promise is settled in fulfillment.
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When this method is called, it means we were unable to produce a response
|
||||||
|
from either the cache or the network. This is our opportunity to produce
|
||||||
|
a meaningful response even when all else fails. It's the last chance, so
|
||||||
|
you probably want to display a "Service Unavailable" view or a generic
|
||||||
|
error response.
|
||||||
|
*/
|
||||||
|
function unableToResolve() {
|
||||||
|
/* There's a couple of things we can do here.
|
||||||
|
- Test the Accept header and then return one of the `offlineFundamentals`
|
||||||
|
e.g: `return caches.match('/some/cached/image.png')`
|
||||||
|
- You should also consider the origin. It's easier to decide what
|
||||||
|
"unavailable" means for requests against your origins than for requests
|
||||||
|
against a third party, such as an ad provider.
|
||||||
|
- Generate a Response programmatically, as shown below, and return that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//console.log('WORKER: fetch request failed in both cache and network.');
|
||||||
|
|
||||||
|
/* Here we're creating a response programmatically. The first parameter is the
|
||||||
|
response body, and the second one defines the options for the response.
|
||||||
|
*/
|
||||||
|
return new Response("<h1>Service Unavailable</h1>", {
|
||||||
|
status: 503,
|
||||||
|
statusText: "Service Unavailable",
|
||||||
|
headers: new Headers({
|
||||||
|
"Content-Type": "text/html",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* The activate event fires after a service worker has been successfully installed.
|
||||||
|
It is most useful when phasing out an older version of a service worker, as at
|
||||||
|
this point you know that the new worker was installed correctly. In this example,
|
||||||
|
we delete old caches that don't match the version in the worker we just finished
|
||||||
|
installing.
|
||||||
|
*/
|
||||||
|
self.addEventListener("activate", function (event) {
|
||||||
|
/* Just like with the install event, event.waitUntil blocks activate on a promise.
|
||||||
|
Activation will fail unless the promise is fulfilled.
|
||||||
|
*/
|
||||||
|
//console.log('WORKER: activate event in progress.');
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
caches
|
||||||
|
/* This method returns a promise which will resolve to an array of available
|
||||||
|
cache keys.
|
||||||
|
*/
|
||||||
|
.keys()
|
||||||
|
.then(function (keys) {
|
||||||
|
// We return a promise that settles when all outdated caches are deleted.
|
||||||
|
return Promise.all(
|
||||||
|
keys
|
||||||
|
.filter(function (key) {
|
||||||
|
// Filter by keys that don't start with the latest version prefix.
|
||||||
|
return !key.startsWith(version);
|
||||||
|
})
|
||||||
|
.map(function (key) {
|
||||||
|
/* Return a promise that's fulfilled
|
||||||
|
when each outdated cache is deleted.
|
||||||
|
*/
|
||||||
|
return caches.delete(key);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
//console.log('WORKER: activate completed.');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -13,7 +13,7 @@ pub fn Controls() -> Element {
|
||||||
#[css_module("/assets/styling/controls.module.css")]
|
#[css_module("/assets/styling/controls.module.css")]
|
||||||
struct Styles;
|
struct Styles;
|
||||||
|
|
||||||
let mut socket = use_websocket(move || mouse_move(WebSocketOptions::new()));
|
let mut socket = use_websocket(move || remote_control(WebSocketOptions::new()));
|
||||||
|
|
||||||
use_future(move || async move {
|
use_future(move || async move {
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -34,6 +34,15 @@ pub fn Controls() -> Element {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
match *socket.status().read() {
|
||||||
|
dioxus_fullstack::WebsocketState::Connecting => {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"Connecting..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dioxus_fullstack::WebsocketState::Open => {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: Styles::controls,
|
class: Styles::controls,
|
||||||
|
|
@ -41,6 +50,29 @@ pub fn Controls() -> Element {
|
||||||
KeyboardArea { onevent: event_handler }
|
KeyboardArea { onevent: event_handler }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dioxus_fullstack::WebsocketState::Closing => {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"Closing..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dioxus_fullstack::WebsocketState::Closed => {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"Closed..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dioxus_fullstack::WebsocketState::FailedToConnect => {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"Failed to connect..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -60,8 +92,8 @@ enum ServerEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::unused_async)]
|
#[expect(clippy::unused_async)]
|
||||||
#[get("/api/mouse_move_ws", mouse_service: State<crate::server::input_proxy_service::InputProxyService>)]
|
#[get("/api/remote_control_wss", mouse_service: State<crate::server::input_proxy_service::InputProxyService>)]
|
||||||
async fn mouse_move(
|
async fn remote_control(
|
||||||
options: WebSocketOptions
|
options: WebSocketOptions
|
||||||
) -> Result<Websocket<ClientEvent, ServerEvent, CborEncoding>> {
|
) -> Result<Websocket<ClientEvent, ServerEvent, CborEncoding>> {
|
||||||
Ok(options.on_upgrade(move |mut socket| async move {
|
Ok(options.on_upgrade(move |mut socket| async move {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue