diff --git a/app/pages/_document.tsx b/app/pages/_document.tsx
index 8b825fa..8217839 100644
--- a/app/pages/_document.tsx
+++ b/app/pages/_document.tsx
@@ -1,4 +1,4 @@
-import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz";
+import { Document, Html, DocumentHead, Main, BlitzScript, Head /*DocumentContext*/ } from "blitz";
class MyDocument extends Document {
// Only uncomment if you need to customize this behaviour
@@ -11,6 +11,19 @@ class MyDocument extends Document {
return (
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/_offline.tsx b/app/pages/_offline.tsx
new file mode 100644
index 0000000..7233ac5
--- /dev/null
+++ b/app/pages/_offline.tsx
@@ -0,0 +1,22 @@
+import { useRouter } from "blitz";
+
+import Layout from "../core/layouts/layout";
+
+export default function Offline() {
+ const router = useRouter();
+ return (
+
+
+ Oops, looks like you went offline.
+
+
+ Once you're back online,{" "}
+
+
+
+ );
+}
diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest
new file mode 100644
index 0000000..c7d10c7
--- /dev/null
+++ b/public/manifest.webmanifest
@@ -0,0 +1,23 @@
+{
+ "name": "Shellphone: Your Personal Virtual Phone",
+ "short_name": "Shellphone",
+ "lang": "en-US",
+ "start_url": "/",
+ "scope": "/",
+ "shortcuts": [
+ {
+ "name": "Shellphone Messages",
+ "short_name": "Messages",
+ "url": "/messages"
+ },
+ {
+ "name": "Shellphone Calls",
+ "short_name": "Calls",
+ "url": "/calls"
+ }
+ ],
+ "display": "standalone",
+ "orientation": "portrait",
+ "theme_color": "#663399",
+ "background_color": "#F9FAFB"
+}
diff --git a/tsconfig.json b/tsconfig.json
index e4883fa..41d73ea 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": ["dom", "dom.iterable", "esnext", "webworker"],
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
diff --git a/worker/index.js b/worker/index.js
deleted file mode 100644
index 50bd606..0000000
--- a/worker/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-"use strict";
-
-self.addEventListener("push", function (event) {
- console.log("event.data.text()", event.data.text());
- const data = JSON.parse(event.data.text());
- event.waitUntil(
- registration.showNotification(data.title, {
- body: data.message,
- icon: "/icons/android-chrome-192x192.png",
- }),
- );
-});
-
-self.addEventListener("notificationclick", function (event) {
- event.notification.close();
- event.waitUntil(
- clients.matchAll({ type: "window", includeUncontrolled: true }).then(function (clientList) {
- if (clientList.length > 0) {
- let client = clientList[0];
- for (let i = 0; i < clientList.length; i++) {
- if (clientList[i].focused) {
- client = clientList[i];
- }
- }
- return client.focus();
- }
- return clients.openWindow("/");
- }),
- );
-});
-
-// self.addEventListener('pushsubscriptionchange', function(event) {
-// event.waitUntil(
-// Promise.all([
-// Promise.resolve(event.oldSubscription ? deleteSubscription(event.oldSubscription) : true),
-// Promise.resolve(event.newSubscription ? event.newSubscription : subscribePush(registration))
-// .then(function(sub) { return saveSubscription(sub) })
-// ])
-// )
-// })
diff --git a/worker/notifications.ts b/worker/notifications.ts
new file mode 100644
index 0000000..0d42bf1
--- /dev/null
+++ b/worker/notifications.ts
@@ -0,0 +1,53 @@
+import { Routes } from "blitz";
+
+const worker = self as unknown as ServiceWorkerGlobalScope & typeof globalThis;
+
+worker.addEventListener("push", function (event) {
+ if (!event.data) {
+ return;
+ }
+
+ console.log("event.data.text()", event.data.text());
+ const data = JSON.parse(event.data.text());
+ event.waitUntil(
+ worker.registration.showNotification(data.title, {
+ body: data.message,
+ icon: "/icons/android-chrome-192x192.png",
+ actions: [
+ { title: "Open", action: "open" },
+ { title: "Mark as read", action: "mark-as-read" },
+ ],
+ }),
+ );
+});
+
+worker.addEventListener("notificationclick", (event) => {
+ event.notification.close();
+ event.waitUntil(
+ worker.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
+ if (!event.notification.data) {
+ return;
+ }
+
+ switch (event.action) {
+ case "mark-as-read":
+ // TODO
+ return;
+ case "open":
+ default: {
+ const data = JSON.parse(event.notification.data.text());
+ const route = Routes.ConversationPage({ recipient: data.recipient });
+ const url = `${route.pathname}${route.query}`;
+
+ if (clientList.length > 0) {
+ const client = clientList.find((client) => client.focused) ?? clientList[0]!;
+
+ client.navigate(url);
+ return client.focus();
+ }
+ return worker.clients.openWindow(url);
+ }
+ }
+ }),
+ );
+});