Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 0 additions & 21 deletions LICENCE.txt

This file was deleted.

25 changes: 0 additions & 25 deletions README.md

This file was deleted.

35 changes: 35 additions & 0 deletions extension/README.md.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const extraContents = `## 🛠 Setup PWA Features with Serwist

This guide will help you set up Progressive Web App (PWA) features in your Scaffold-ETH 2 project using Serwist.

#### ✅ Step 1: Generate VAPID Keys ✅

First, you'll need to generate VAPID (Voluntary Application Server Identification) keys for web push notifications:

\`\`\`bash
yarn generate-vapid-keys
\`\`\`

After generating the keys, paste them into your \`.env\` file.

#### ✅ Step 2: Leverage Serwist Features ✅

This extension comes with [Serwist](https://serwist.pages.dev/) integration out of the box, providing the following PWA capabilities:

- 📱 **Installable Web App**: Users can install your dApp on their devices
- 🔄 **Offline Support**: Basic functionality works without internet connection
- 📨 **Push Notifications**: Engage users with web push notifications
- 💨 **Fast Loading**: Improved performance through service worker caching

#### ✅ Step 3: Configure Your PWA ✅

You can customize your PWA settings in the following locations:

- PWA manifest: \`packages/nextjs/public/manifest.json\`
- Serwist configuration: \`packages/nextjs/serwist.config.ts\`
- Push notification handling: \`packages/nextjs/utils/push-notifications.ts\`

#### 📚 Additional Resources

- [Serwist Documentation](https://serwist.pages.dev/)
- [Web Push Notifications Guide](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)`;
5 changes: 5 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"scripts": {
"generate-vapid-keys": "yarn workspace @se-2/nextjs web-push generate-vapid-keys --json"
}
}
4 changes: 4 additions & 0 deletions extension/packages/nextjs/.env.example.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const additionalVars = "# Generate VAPID keys with: `yarn generate-vapid-keys`
WEB_PUSH_EMAIL=
WEB_PUSH_PRIVATE_KEY=
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=";
29 changes: 29 additions & 0 deletions extension/packages/nextjs/.gitignore.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// This is the current gitignore template
//// But I don't get how we can add things to it.

// const contents = () =>
// `# dependencies
// node_modules

// # yarn
// .yarn/*
// !.yarn/patches
// !.yarn/plugins
// !.yarn/releases
// !.yarn/sdks
// !.yarn/versions

// # eslint
// .eslintcache

// # misc
// .DS_Store

// # IDE
// .vscode
// .idea

// # cli
// dist`;

// export default contents
141 changes: 141 additions & 0 deletions extension/packages/nextjs/app/SendNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"use client";

import type { MouseEventHandler } from "react";
import { useEffect, useState } from "react";

const base64ToUint8Array = (base64: string) => {
const padding = "=".repeat((4 - (base64.length % 4)) % 4);
const b64 = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");

const rawData = window.atob(b64);
const outputArray = new Uint8Array(rawData.length);

for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};

export default function SendNotification() {
const [isSubscribed, setIsSubscribed] = useState(false);
const [subscription, setSubscription] = useState<PushSubscription | null>(null);
const [registration, setRegistration] = useState<ServiceWorkerRegistration | null>(null);

useEffect(() => {
if (typeof window !== "undefined" && "serviceWorker" in navigator && window.serwist !== undefined) {
// run only in browser
navigator.serviceWorker.ready.then(reg => {
reg.pushManager.getSubscription().then(sub => {
if (sub && !(sub.expirationTime && Date.now() > sub.expirationTime - 5 * 60 * 1000)) {
setSubscription(sub);
setIsSubscribed(true);
}
});
setRegistration(reg);
});
}
}, []);

const subscribeButtonOnClick: MouseEventHandler<HTMLButtonElement> = async event => {
if (!process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY) {
throw new Error("Environment variables supplied not sufficient.");
}
if (!registration) {
console.error("No SW registration available.");
return;
}
event.preventDefault();
const sub = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY),
});
// TODO: you should call your API to save subscription data on the server in order to send web push notification from the server
setSubscription(sub);
setIsSubscribed(true);
alert("Web push subscribed!");
console.log(sub);
};

const unsubscribeButtonOnClick: MouseEventHandler<HTMLButtonElement> = async event => {
if (!subscription) {
console.error("Web push not subscribed");
return;
}
event.preventDefault();
await subscription.unsubscribe();
// TODO: you should call your API to delete or invalidate subscription data on the server
setSubscription(null);
setIsSubscribed(false);
console.log("Web push unsubscribed!");
};

const sendNotificationButtonOnClick: MouseEventHandler<HTMLButtonElement> = async event => {
event.preventDefault();

if (!subscription) {
alert("Web push not subscribed");
return;
}

try {
await fetch("/notification", {
method: "POST",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify({
subscription,
}),
signal: AbortSignal.timeout(10000),
});
} catch (err) {
if (err instanceof Error) {
if (err.name === "TimeoutError") {
console.error("Timeout: It took too long to get the result.");
} else if (err.name === "AbortError") {
console.error("Fetch aborted by user action (browser stop button, closing tab, etc.)");
} else if (err.name === "TypeError") {
console.error("The AbortSignal.timeout() method is not supported.");
} else {
// A network error, or some other problem.
console.error(`Error: type: ${err.name}, message: ${err.message}`);
}
} else {
console.error(err);
}
alert("An error happened.");
}
};

return (
<div className="flex flex-col items-center gap-2 bg-base-100 p-4 m-8 rounded-lg">
<h2 className="text-lg font-bold">Web Push Notifications</h2>
<div className="flex flex-col md:flex-row gap-4">
<button
type="button"
className="btn btn-xs btn-primary"
onClick={subscribeButtonOnClick}
disabled={isSubscribed}
>
Subscribe
</button>
<button
type="button"
className="btn btn-xs btn-primary"
onClick={unsubscribeButtonOnClick}
disabled={!isSubscribed}
>
Unsubscribe
</button>
<button
type="button"
className="btn btn-xs btn-primary"
onClick={sendNotificationButtonOnClick}
disabled={!isSubscribed}
>
Send Notification
</button>
</div>
</div>
);
}
44 changes: 44 additions & 0 deletions extension/packages/nextjs/app/notification/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { type NextRequest, NextResponse } from "next/server";
import webPush from "web-push";

export const POST = async (req: NextRequest) => {
if (
!process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY ||
!process.env.WEB_PUSH_EMAIL ||
!process.env.WEB_PUSH_PRIVATE_KEY
) {
throw new Error("Environment variables supplied not sufficient.");
}
const { subscription } = (await req.json()) as {
subscription: webPush.PushSubscription;
};
try {
webPush.setVapidDetails(
`mailto:${process.env.WEB_PUSH_EMAIL}`,
process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
process.env.WEB_PUSH_PRIVATE_KEY,
);
const response = await webPush.sendNotification(
subscription,
JSON.stringify({
title: "Hello Scaffold-Serwist",
message: "Your web push notification is here!",
}),
);
return new NextResponse(response.body, {
status: response.statusCode,
headers: response.headers,
});
} catch (err) {
if (err instanceof webPush.WebPushError) {
return new NextResponse(err.body, {
status: err.statusCode,
headers: err.headers,
});
}
console.log(err);
return new NextResponse("Internal Server Error", {
status: 500,
});
}
};
65 changes: 65 additions & 0 deletions extension/packages/nextjs/app/page.tsx.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export default {
imports: `
import SendNotification from "./SendNotification";
import { BellAlertIcon, BoltIcon, CubeIcon, GlobeAltIcon } from "@heroicons/react/24/outline";
import { InstallPWA } from "~~/components/InstallPWA";`,

description: `
<div className="flex flex-col items-center gap-4 mb-8">
<SendNotification />
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-8">
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<div className="flex items-center gap-4">
<div className="text-primary"><BoltIcon className="h-8 w-8" /></div>
<h2 className="card-title">Quick Start</h2>
</div>
<p>Get your Web3 project up and running in minutes with our pre-configured setup.</p>
</div>
</div>

<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<div className="flex items-center gap-4">
<div className="text-primary"><CubeIcon className="h-8 w-8" /></div>
<h2 className="card-title">Smart Contracts</h2>
</div>
<p>Easily deploy and interact with Solidity smart contracts.</p>
</div>
</div>

<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<div className="flex items-center gap-4">
<div className="text-primary"><GlobeAltIcon className="h-8 w-8" /></div>
<h2 className="card-title">PWA Support</h2>
</div>
<p>Built-in Progressive Web App capabilities for offline access and improved performance with{" "}
<a href="https://serwist.pages.dev/" target="_blank" rel="noopener noreferrer" className="underline">
Serwist
</a>
.</p>
</div>
</div>

<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<div className="flex items-center gap-4">
<div className="text-primary"><BellAlertIcon className="h-8 w-8" /></div>
<h2 className="card-title">Web Push Notifications</h2>
</div>
<p>Engage users with push notifications, even when the app is closed.</p>
</div>
</div>
</div>

<div className="mt-8 flex flex-col sm:flex-row items-center justify-center gap-4 bg-base-100 rounded-lg p-4">
<span className="text-lg font-semibold">Learn more:</span>
<a href="https://serwist.pages.dev/" target="_blank" rel="noopener noreferrer" className="underline">Serwist</a>
<a href="https://scaffoldeth.io" target="_blank" rel="noopener noreferrer" className="underline">Scaffold-ETH 2</a>
</div>`,

externalExtensionName: ["PWA"],
};
Loading