Browse Source

Merge pull request 'A cleaner attempt to merge' (#87) from service-worker-final into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/87
kb/add-usage-guide
anomalist 12 months ago
parent
commit
6dbfc5f77d
  1. 4
      package.json
  2. 171
      src/App.vue
  3. 2
      src/registerServiceWorker.ts
  4. 80
      sw_scripts/additional-scripts.js
  5. 1051
      sw_scripts/nacl.js
  6. 5248
      sw_scripts/noble-curves.js
  7. 3068
      sw_scripts/noble-hashes.js
  8. 5679
      sw_scripts/safari-notifications.js
  9. 3
      vue.config.js

4
package.json

@ -72,13 +72,13 @@
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "~5.0.8",
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"eslint": "^8.48.0", "eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"postcss": "^8.4.29", "postcss": "^8.4.29",
"prettier": "^3.0.3", "prettier": "^3.1.0",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "~5.2.2" "typescript": "~5.2.2"
} }

171
src/App.vue

@ -261,7 +261,29 @@
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-facing-decorator"; import { Vue, Component } from "vue-facing-decorator";
import axios from "axios"; import axios, { AxiosError } from "axios";
interface ServiceWorkerMessage {
type: string;
data: string;
}
interface ServiceWorkerResponse {
// Define the properties and their types
success: boolean;
message?: string;
}
// Example interface for error
interface ErrorResponse {
message: string;
// Other properties as needed
}
interface VapidResponse {
data: {
vapidKey: string;
};
}
@Component @Component
export default class App extends Vue { export default class App extends Vue {
@ -269,45 +291,87 @@ export default class App extends Vue {
mounted() { mounted() {
axios axios
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid") .get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid")
.then((response) => { .then((response: VapidResponse) => {
this.b64 = response.data.vapidKey; this.b64 = response.data.vapidKey;
console.log(this.b64); console.log(this.b64);
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("New service worker is now controlling the page");
});
}) })
.catch((error) => { .catch((error: AxiosError) => {
console.error("API error", error); console.error("API error", error);
}); });
} }
private sendMessageToServiceWorker(
message: ServiceWorkerMessage,
): Promise<unknown> {
return new Promise((resolve, reject) => {
if (navigator.serviceWorker.controller) {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (event: MessageEvent) => {
if (event.data.error) {
reject(event.data.error as ErrorResponse);
} else {
resolve(event.data as ServiceWorkerResponse);
}
};
navigator.serviceWorker.controller.postMessage(message, [
messageChannel.port2,
]);
} else {
reject("Service worker controller not available");
}
});
}
private askPermission(): Promise<NotificationPermission> { private askPermission(): Promise<NotificationPermission> {
// Check if Notifications are supported if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
return Promise.reject("Service worker not available.");
}
const secret = localStorage.getItem("secret");
if (!secret) {
return Promise.reject("No secret found.");
}
return this.sendSecretToServiceWorker(secret)
.then(() => this.checkNotificationSupport())
.then(() => this.requestNotificationPermission())
.catch((error) => Promise.reject(error));
}
private sendSecretToServiceWorker(secret: string): Promise<void> {
const message: ServiceWorkerMessage = {
type: "SEND_LOCAL_DATA",
data: secret,
};
return this.sendMessageToServiceWorker(message).then((response) => {
console.log("Response from service worker:", response);
});
}
private checkNotificationSupport(): Promise<void> {
if (!("Notification" in window)) { if (!("Notification" in window)) {
alert("This browser does not support notifications."); alert("This browser does not support notifications.");
return Promise.reject("This browser does not support notifications."); return Promise.reject("This browser does not support notifications.");
} }
// Check existing permissions
if (Notification.permission === "granted") { if (Notification.permission === "granted") {
return Promise.resolve("granted"); return Promise.resolve();
} }
return Promise.resolve();
}
// Request permission private requestNotificationPermission(): Promise<NotificationPermission> {
return new Promise((resolve, reject) => { return Notification.requestPermission().then((permission) => {
const permissionResult = Notification.requestPermission((result) => { if (permission !== "granted") {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then((permissionResult) => {
console.log("Permission result:", permissionResult);
if (permissionResult !== "granted") {
alert("We need notification permission to provide certain features."); alert("We need notification permission to provide certain features.");
return Promise.reject("We weren't granted permission."); throw new Error("We weren't granted permission.");
} }
return permission;
return permissionResult;
}); });
} }
@ -366,34 +430,49 @@ export default class App extends Vue {
return outputArray; return outputArray;
} }
// The subscribeToPush method
private subscribeToPush(): Promise<void> { private subscribeToPush(): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if ("serviceWorker" in navigator && "PushManager" in window) { if (!("serviceWorker" in navigator && "PushManager" in window)) {
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
const options: PushSubscriptionOptions = {
userVisibleOnly: true,
applicationServerKey: applicationServerKey,
};
console.log(options);
navigator.serviceWorker.ready
.then((registration) => {
return registration.pushManager.subscribe(options);
})
.then((subscription) => {
console.log("Push subscription successful:", subscription);
resolve();
})
.catch((error) => {
console.error("Push subscription failed:", error, options);
reject(error);
});
} else {
const errorMsg = "Push messaging is not supported"; const errorMsg = "Push messaging is not supported";
console.warn(errorMsg); console.warn(errorMsg);
reject(new Error(errorMsg)); return reject(new Error(errorMsg));
} }
if (Notification.permission !== "granted") {
const errorMsg = "Notification permission not granted";
console.warn(errorMsg);
return reject(new Error(errorMsg));
}
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
const options: PushSubscriptionOptions = {
userVisibleOnly: true,
applicationServerKey: applicationServerKey,
};
navigator.serviceWorker.ready
.then((registration) => {
return registration.pushManager.subscribe(options);
})
.then((subscription) => {
console.log("Push subscription successful:", subscription);
resolve();
})
.catch((error) => {
console.error(
"Subscription or server communication failed:",
error,
options,
);
// Inform the user about the issue
alert(
"We encountered an issue setting up push notifications. " +
"If you wish to revoke notification permissions, please do so in your browser settings.",
);
reject(error);
});
}); });
} }

2
src/registerServiceWorker.ts

@ -3,7 +3,7 @@
import { register } from "register-service-worker"; import { register } from "register-service-worker";
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
register(`${process.env.BASE_URL}service-worker.js`, { register("/additional-scripts.js", {
ready() { ready() {
console.log( console.log(
"App is being served from cache by a service worker.\n" + "App is being served from cache by a service worker.\n" +

80
sw_scripts/additional-scripts.js

@ -1,33 +1,65 @@
const notifications = require("./safari-notifications.js"); /* eslint-env serviceworker */
/* global workbox */
importScripts(
"https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js",
);
self.addEventListener("install", (event) => {
console.error(event);
importScripts(
"safari-notifications.js",
"nacl.js",
"noble-curves.js",
"noble-hashes.js",
);
});
self.addEventListener("push", function (event) { self.addEventListener("push", function (event) {
let payload; event.waitUntil(
if (event.data) { (async () => {
payload = JSON.parse(event.data.text()); try {
} let payload;
if (event.data) {
const title = payload ? payload.title : "Custom Title"; payload = JSON.parse(event.data.text());
const options = { }
body: payload ? payload.body : "Custom body text", const message = await self.getNotificationCount();
icon: payload ? payload.icon : "icon.png", console.error(message);
badge: payload ? payload.badge : "badge.png", const title = payload ? payload.title : "Custom Title";
}; const options = {
body: message,
event.waitUntil(self.registration.showNotification(title, options)); icon: payload ? payload.icon : "icon.png",
badge: payload ? payload.badge : "badge.png",
};
await self.registration.showNotification(title, options);
} catch (error) {
console.error("Error in processing the push event:", error);
}
})(),
);
}); });
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SEND_LOCAL_DATA") {
self.secret = event.data.data;
event.ports[0].postMessage({ success: true });
}
});
self.addEventListener("message", function (event) { self.addEventListener("activate", (event) => {
const data = event.data; event.waitUntil(clients.claim());
console.log("Service worker activated", event);
const result = notifications.getNotificationCount() });
switch (data.command) { self.addEventListener("fetch", (event) => {
case "account": console.log(event.request);
break; });
default: self.addEventListener("error", (event) => {
console.log("Unknown command:", data.command); console.error("Error in Service Worker:", event.message);
} console.error("File:", event.filename);
console.error("Line:", event.lineno);
console.error("Column:", event.colno);
console.error("Error Object:", event.error);
}); });
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);

1051
sw_scripts/nacl.js

File diff suppressed because it is too large

5248
sw_scripts/noble-curves.js

File diff suppressed because it is too large

3068
sw_scripts/noble-hashes.js

File diff suppressed because it is too large

5679
sw_scripts/safari-notifications.js

File diff suppressed because it is too large

3
vue.config.js

@ -11,8 +11,9 @@ module.exports = defineConfig({
iconPaths: { iconPaths: {
faviconSVG: "img/icons/safari-pinned-tab.svg", faviconSVG: "img/icons/safari-pinned-tab.svg",
}, },
workboxPluginMode: "InjectManifest",
workboxOptions: { workboxOptions: {
importScripts: ["additional-scripts.js"], swSrc: "./sw_scripts/additional-scripts.js",
}, },
}, },
}); });

Loading…
Cancel
Save