1diff --git a/components/app.js b/components/app.js
2index 4788788..ec5d74a 100644
3--- a/components/app.js
4+++ b/components/app.js
5@@ -28,6 +28,21 @@ const baseConfig = {
6 server: {},
7 };
8
9+function urlBase64ToUint8Array(base64String) {
10+ var padding = '='.repeat((4 - base64String.length % 4) % 4);
11+ var base64 = (base64String + padding)
12+ .replace(/\-/g, '+')
13+ .replace(/_/g, '/');
14+
15+ var rawData = window.atob(base64);
16+ var outputArray = new Uint8Array(rawData.length);
17+
18+ for (var i = 0; i < rawData.length; ++i) {
19+ outputArray[i] = rawData.charCodeAt(i);
20+ }
21+ return outputArray;
22+}
23+
24 const configPromise = fetch("./config.json")
25 .then((resp) => {
26 if (resp.ok) {
27@@ -981,9 +996,37 @@ export default class App extends Component {
28 affectedBuffers.push(prefix.name);
29 }
30 return affectedBuffers;
31+ case irc.RPL_ISUPPORT: {
32+ const params = msg.params;
33+ for (const p of params) {
34+ if (p.indexOf("VAPID") === 0) {
35+ const value = p.replace("VAPID=", "");
36+ const vapidPubKey = urlBase64ToUint8Array(value);
37+ navigator.serviceWorker.ready.then((registration) => {
38+ return registration.pushManager.getSubscription()
39+ .then((subscription) => {
40+ if (subscription) {
41+ return subscription;
42+ }
43+ return registration.pushManager.subscribe({
44+ userVisibleOnly: true,
45+ applicationServerKey: vapidPubKey,
46+ });
47+ }).then((subscription) => {
48+ var data = subscription.toJSON();
49+ var keysStr = irc.formatTags(data.keys);
50+ client.send({
51+ command: "WEBPUSH",
52+ params: ["REGISTER", data.endpoint, keysStr],
53+ });
54+ });
55+ });
56+ }
57+ }
58+ return [];
59+ }
60 case irc.RPL_YOURHOST:
61 case irc.RPL_MYINFO:
62- case irc.RPL_ISUPPORT:
63 case irc.RPL_ENDOFMOTD:
64 case irc.ERR_NOMOTD:
65 case irc.RPL_AWAY:
66diff --git a/lib/client.js b/lib/client.js
67index a1a969a..90e736e 100644
68--- a/lib/client.js
69+++ b/lib/client.js
70@@ -25,6 +25,7 @@ const permanentCaps = [
71 "draft/read-marker",
72
73 "soju.im/bouncer-networks",
74+ "soju.im/webpush",
75 ];
76
77 const RECONNECT_MIN_DELAY_MSEC = 10 * 1000; // 10s
78diff --git a/main.js b/main.js
79index 2b73e7d..93a7737 100644
80--- a/main.js
81+++ b/main.js
82@@ -1,4 +1,6 @@
83 import { html, render } from "./lib/index.js";
84 import App from "./components/app.js";
85
86+navigator.serviceWorker.register("./service-worker.js").catch(console.error);
87+
88 render(html`<${App}/>`, document.body);
89diff --git a/service-worker.js b/service-worker.js
90new file mode 100644
91index 0000000..6398c4e
92--- /dev/null
93+++ b/service-worker.js
94@@ -0,0 +1,6 @@
95+self.addEventListener("push", (event) => {
96+ var payload = event.data ? event.data.text() : "no payload";
97+ event.waitUntil(self.registration.showNotification("gamja service worker", {
98+ body: payload,
99+ }));
100+});