Skip to content

Commit 8f99195

Browse files
johnsonwMartinKavik
authored andcommitted
Add a service-worker example
Service worker can be integrated into the seed framework. This example demonstrates the following: 1. Use service worker to cache all assets (including the generated wasm file). 2. Register the service worker. 3. If the service worker is not yet activated, an even listener will be registered, waiting for the state to reach "activated". 4. When the state reaches "activated", the Notification object will request permission for notifications. 5. If permission is granted, the PushManager will subscribe to the service using an example vapid key. 6. Finally, a PushSubscription will be returned, containing the information that can be passed to a notifcation back-end server. Signed-off-by: johnsonw <[email protected]>
1 parent 845f4f6 commit 8f99195

14 files changed

+879
-4
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- [BREAKING] Rename `linear_gradient!` to `linearGradient!` for consistency with the other svg macros (same with `radial_gradient!` and `mesh_gradient!`) (#377).
55
- Fixed `base_path` with a trailing slash parsing / handling.
66
- Fixed `C` macro memory / WASM file size issue.
7-
- Added examples `resize_observer`, `component_builder`, `i18n` and `unsaved_changes` (#459).
7+
- Added examples `service_worker`, `resize_observer`, `component_builder`, `i18n` and `unsaved_changes` (#459).
88
- Fixed `UrlRequested` handling (#459).
99
- [BREAKING] Hidden and renamed module `effects` to `effect`.
1010
- Added `App::update_with_option`.
@@ -29,7 +29,7 @@
2929
- Build Changes - Enforce minimal cargo-make version: 0.32.1.
3030
- Added new `Orders` methods `request_url` (#518) and `msg_sender` (#502).
3131
- [BREAKING] `Orders::msg_mapper` returns `Rc<..>` instead of `Box<..>`.
32-
- Reexported `pub use wasm_bindgen_futures::{self, spawn_local};` in `lib.rs`.
32+
- Reexported `pub use wasm_bindgen_futures::{self, spawn_local, JsFuture};` and `pub use futures::{self, future::{self, FutureExt, TryFutureExt}};` in `lib.rs`.
3333
- Updated example `websocket`.
3434
- Fixed link handling (#527).
3535

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ members = [
134134
"examples/pages_keep_state",
135135
"examples/resize_observer",
136136
# "examples/server_integration", # has own workspace
137+
"examples/service_worker",
137138
"examples/subscribe",
138139
"examples/tea_component",
139140
"examples/todomvc",

examples/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ A demonstration of event-handlers attached to the `window`.
9898
## Server
9999
Backend server integration & interaction examples.
100100

101+
## Service Worker
102+
Service worker intergration demo. Includes registration, asset caching, state change messaging and notication subscription.
103+
101104
### [Auth](auth)
102105
How to implement login / logout.
103106

examples/service_worker/Cargo.toml

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[package]
2+
name = "service_worker"
3+
description = "Seed service worker example"
4+
repository = "https://github.com/seed-rs/seed"
5+
version = "0.1.0"
6+
authors = ["Will Johnson <[email protected]>"]
7+
edition = "2018"
8+
license = "MIT"
9+
10+
[lib]
11+
crate-type = ["cdylib"]
12+
path = "src/lib.rs"
13+
14+
[[bin]]
15+
name = "server"
16+
path = "src/main.rs"
17+
18+
[dependencies]
19+
#common
20+
serde = "1.0"
21+
serde_json = "1.0"
22+
23+
# server
24+
anyhow = { version = "1.0", optional = true }
25+
tokio = { version = "0.2", features = ["macros"], optional = true }
26+
warp = { version = "0.2", optional = true }
27+
tracing = { version = "0.1", optional = true }
28+
tracing-subscriber = { version = "0.2", optional = true }
29+
web-push = { version = "0.7", optional = true }
30+
31+
# client
32+
apply = { version = "0.3.0", optional = true }
33+
seed = { path = "../../", optional = true }
34+
35+
[dependencies.web-sys]
36+
version = "0.3.44"
37+
features = [
38+
"CacheStorage",
39+
"Notification",
40+
"NotificationPermission",
41+
"ServiceWorker",
42+
"ServiceWorkerContainer",
43+
"ServiceWorkerRegistration",
44+
"ServiceWorkerState",
45+
"PushManager",
46+
"PushSubscription",
47+
"PushSubscriptionJson",
48+
"PushSubscriptionKeys",
49+
"PushSubscriptionOptionsInit",
50+
]
51+
optional = true
52+
53+
[features]
54+
default = []
55+
client = ["apply", "seed", "web-sys"]
56+
server = ["anyhow", "tokio", "warp", "tracing", "tracing-subscriber", "web-push"]

examples/service_worker/Makefile.toml

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
extend = "../../Makefile.toml"
2+
3+
# ---- BUILD ----
4+
5+
[tasks.clean]
6+
command = "cargo"
7+
args = ["clean", "--target-dir", "pkg"]
8+
9+
[tasks.build]
10+
description = "Build client and server"
11+
clear = true
12+
dependencies = ["build_client", "build_server"]
13+
14+
[tasks.build_release]
15+
extend = "build"
16+
description = "Build client and server in release mode"
17+
dependencies = ["build_client_release", "build_server_release"]
18+
19+
[tasks.build_client]
20+
description = "Build client"
21+
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "-V" }
22+
command = "wasm-pack"
23+
args = [
24+
"build",
25+
"--target", "web",
26+
"--out-name", "package",
27+
"--dev",
28+
"--",
29+
"--features", "client"
30+
]
31+
dependencies = ["clean"]
32+
33+
[tasks.build_client_release]
34+
extend = "build"
35+
description = "Build client in release mode"
36+
args = [
37+
"build",
38+
"--target", "web",
39+
"--out-name", "package",
40+
"--release",
41+
"--",
42+
"--features", "client"
43+
]
44+
dependencies = ["clean"]
45+
46+
[tasks.build_server]
47+
description = "Build server"
48+
command = "cargo"
49+
args = ["build", "--bin", "server", "--features", "server"]
50+
51+
[tasks.build_server_release]
52+
extend = "build_server"
53+
description = "Build server in release mode"
54+
command = "cargo"
55+
args = ["build", "--bin", "server", "--features", "server", "--release"]
56+
57+
# ---- START ----
58+
59+
[tasks.start]
60+
alias = "default_start"
61+
dependencies = ["build_client"]
62+
63+
[tasks.start_release]
64+
alias = "default_start_release"
65+
dependencies = ["build_client_release"]
66+
67+
[tasks.start_server]
68+
description = "Run the backend server on 8001"
69+
workspace = false
70+
env = { "RUST_LOG" = "debug" }
71+
command = "cargo"
72+
args = ["run", "--bin", "server", "--features", "server"]
73+
dependencies = ["build_server"]
74+
75+
[tasks.start_server_release]
76+
extend = "start_server"
77+
description = "Run the backend server on 8001 in release mode"
78+
args = ["run", "--bin", "server", "--features", "server", "--release"]
79+
dependencies = ["build_server_release"]
80+
81+
# ---- LINT ----
82+
83+
[tasks.clippy]
84+
alias = "default_clippy"

examples/service_worker/README.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Service Worker example
2+
3+
Service worker is an exciting technology that is available in most major browsers. To summarize, it can be used to cache
4+
assets, providing a positive offline experience. Additionally, service worker has the ability to send notifications that
5+
are generated both locally from a web application as well as from a remote server.
6+
7+
The example in this crate demonstrates the following features:
8+
9+
1. Use service worker to cache all assets (including the generated wasm file).
10+
1. Register the service worker.
11+
1. If the service worker is not yet activated, an even listener will be registered, waiting for the
12+
state to reach "activated".
13+
1. When the state reaches "activated", the Notification object will request permission for notifications.
14+
1. If permission is granted, the PushManager will subscribe to the service using an example vapid key.
15+
1. Finally, a PushSubscription will be returned, containing the information that can be passed to a
16+
notification back-end server.
17+
18+
---
19+
20+
## Prerequisites - Openssl Installation
21+
22+
The server requires that openssl be installed. For OS X systems, openssl can be installed using [`Homebrew`](https://brew.sh/).
23+
For windows, openssl can be installed by running the following:
24+
25+
1. `git clone https://github.com/microsoft/vcpkg`
26+
1. cd vcpkg
27+
1. ./bootstrap-vcpkg.bat
28+
1. ./vcpkg integrate install
29+
1. ./vcpkg install openssl-windows:x64-windows
30+
1. ./vcpkg install openssl:x64-windows-static-md
31+
32+
## Running
33+
34+
Running this example requires both a server and a client to be running at the same time. This is easily achieved by running each
35+
in its own terminal from the root of the seed project:
36+
37+
Terminal A:
38+
39+
```bash
40+
cargo make start_server service_worker
41+
```
42+
43+
Terminal B:
44+
45+
```bash
46+
cargo make start service_worker
47+
```
48+
49+
1. Open [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your browser. This will cache the assets.
50+
1. Kill the running cargo process to terminate the local dev server.
51+
1. Refresh the browser and notice that the page loads with all assets.
52+
1. Click the `Request Push Subscription` button. This will register the subscription with the Push Manager.
53+
1. Click on the `Send Push Notification` button. A notification should pop up on the browser.
54+
1. Open Dev Tools and select the `Application` tab.
55+
1. Click on the `Service Workers` item and view the registered service worker.
56+
1. In the web page, click the `Unregister Service Worker` button and notice that the service worker is unregistered.
57+
1. View the `Cache Storage` item in dev tools and take note of the cached items.
58+
1. In the web page, click the `Clear Cache` button and notice the cache is erased in Dev Tools (Firefox is a bit finicky at showing this).

examples/service_worker/index.html

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7+
<title>Service Worker Example</title>
8+
</head>
9+
10+
<body>
11+
<section id="app"></section>
12+
<script src="/public/subscribe.js" type="module"></script>
13+
<script type="module">
14+
import init from '/pkg/package.js';
15+
init('/pkg/package_bg.wasm');
16+
</script>
17+
</body>
18+
19+
</html>
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN EC PARAMETERS-----
2+
BggqhkjOPQMBBw==
3+
-----END EC PARAMETERS-----
4+
-----BEGIN EC PRIVATE KEY-----
5+
MHcCAQEEIMij1KbDMDP+OZR8oT8zhJSqEGeRDsL5emKZQIugvflPoAoGCCqGSM49
6+
AwEHoUQDQgAEpunruwa2ksfEo8Gsxa6v9i8bbaOCH/tp2lBLahsJwn+cxhFYeeXo
7+
O6eVaqdV2MTNwjam0shaTIgoD18UVVmzrQ==
8+
-----END EC PRIVATE KEY-----
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// ------ ------
2+
// Subscribe
3+
// @manager: web_sys::PushManager - The PushManager interface of the Push API provides a way to
4+
// receive notifications from third-party servers as well as request URLs for push notifications.
5+
// - https://developer.mozilla.org/en-US/docs/Web/API/PushManager
6+
// - https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.PushManager.html
7+
// @api_key: Uint8Array - A Base64-encoded DOMString or ArrayBuffer containing an ECDSA P-256 public key that
8+
// the push server will use to authenticate your application server.
9+
// - https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe
10+
// @returns Promise<web_sys::PushSubscription>
11+
// Until https://github.com/rustwasm/wasm-bindgen/pull/2288 is included in the new release of wasm-bindgen,
12+
// this function should be called from the seed app to subscribe to the PushManager. The applicationServerKey
13+
// is the vapid key that is used for identification on the back-end server. The `userVisibleOnly` property
14+
// indicates that the returned push subscription will only be used for messages whose effect is made visible
15+
// to the user. It must be set to `true` or the browser will reject the subscription request. This holds true
16+
// for both chrome and firefox.
17+
// ------ ------
18+
window.subscribe = async (manager, api_key) => {
19+
let subscription = await manager.subscribe({
20+
applicationServerKey: api_key,
21+
userVisibleOnly: true
22+
});
23+
24+
console.log("JS subscription", subscription);
25+
return subscription;
26+
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
2+
3+
// workbox.core - Provides core workbox functionality. Ths will be used for service worker updating.
4+
const core = workbox.core;
5+
6+
// workbox.precaching - Helps to simplify the caching process
7+
const precaching = workbox.precaching;
8+
9+
// We want to publish a new service worker and immediately update and control the page.
10+
// - https://developers.google.com/web/tools/workbox/modules/workbox-core#skip_waiting_and_clients_claim
11+
core.skipWaiting();
12+
core.clientsClaim();
13+
14+
// Cache all of the assets for offline viewing. This can be done manually or by using a tool, such as
15+
// `workbox-cli`- https://developers.google.com/web/tools/workbox/modules/workbox-cli.
16+
// By updating the revision hash after an asset has been updated, the cached resource will be
17+
// updated in the browser's cache.
18+
precaching.precacheAndRoute(
19+
[
20+
{ "revision": "12345", "url": "index.html" },
21+
{ "revision": "12345", "url": "public/subscribe.js" },
22+
{ "revision": "12345", "url": "public/images/important-notes.png" },
23+
{ "revision": "12345", "url": "/" },
24+
{ "revision": "12345", "url": "pkg/package_bg.wasm" },
25+
{ "revision": "12345", "url": "pkg/package.js" },
26+
]
27+
);
28+
29+
// Listen for and display a push notification if the push event is triggered from the server.
30+
self.addEventListener('push', (event) => {
31+
const title = 'Seed service worker!';
32+
const options = {
33+
body: event.data.text()
34+
};
35+
event.waitUntil(self.registration.showNotification(title, options));
36+
});

0 commit comments

Comments
 (0)