Compare commits
27 Commits
master
...
spike-noti
| Author | SHA1 | Date |
|---|---|---|
|
|
c684e2e31c | |
|
|
f3e3077374 | |
|
|
4f0e68be41 | |
|
|
65185a4e9f | |
|
|
b966b30c06 | |
|
|
43ee91c8e4 | |
|
|
cc7faccecc | |
|
|
478cd10022 | |
|
|
5b5c8fa847 | |
|
|
b816cb56da | |
|
|
121cc90de9 | |
|
|
3ff16c58b2 | |
|
|
00876f08e9 | |
|
|
fc043461a2 | |
|
|
e3a0b6e730 | |
|
|
b92750e5d5 | |
|
|
a07f028b75 | |
|
|
5741b9fee2 | |
|
|
16250ef533 | |
|
|
1d3edec7bc | |
|
|
07d638b66c | |
|
|
4a7d752230 | |
|
|
0c3e6f60b1 | |
|
|
d655492d16 | |
|
|
a23f5bdd4a | |
|
|
8890093dfd | |
|
|
93340fb1dd |
|
|
@ -8,7 +8,7 @@ app.use(express.json())
|
||||||
|
|
||||||
app.get("/api", (req, res) => res.send("HI"))
|
app.get("/api", (req, res) => res.send("HI"))
|
||||||
|
|
||||||
app.post("/api/subscription", postSubscription)
|
app.post("/api/subscription/", postSubscription)
|
||||||
app.put("/api/subscription/:id/", putSubscription)
|
app.put("/api/subscription/:id/", putSubscription)
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,6 @@ describe("/api/subscription", () => {
|
||||||
mockedCreateSubscription.mockImplementation((subscription) =>
|
mockedCreateSubscription.mockImplementation((subscription) =>
|
||||||
Promise.resolve({ id: 40, subscription })
|
Promise.resolve({ id: 40, subscription })
|
||||||
)
|
)
|
||||||
|
|
||||||
mockedUpdateSubscription.mockImplementation((subscription, id) =>
|
|
||||||
Promise.resolve({ id, subscription })
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("POST", async () => {
|
test("POST", async () => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import { createSubscription } from "../data/subscription"
|
||||||
export async function postSubscription(req: Request, res: Response) {
|
export async function postSubscription(req: Request, res: Response) {
|
||||||
const subscriptionBody = req.body
|
const subscriptionBody = req.body
|
||||||
|
|
||||||
|
console.log("Posted subscription", subscriptionBody)
|
||||||
|
|
||||||
const { id: subscriptionId } = await createSubscription(subscriptionBody)
|
const { id: subscriptionId } = await createSubscription(subscriptionBody)
|
||||||
|
|
||||||
res.status(200).send({ subscriptionId })
|
res.status(200).send({ subscriptionId })
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import React from "react"
|
import React, { useState } from "react"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { usePush } from "../usePush"
|
import { usePush } from "../usePush"
|
||||||
import pushPublicKey from "../../pushPublicKey"
|
import { request } from "http"
|
||||||
import urlB64ToUint8Array from "../../b64ToUInt8"
|
import urlB64ToUint8Array from "../../b64ToUInt8"
|
||||||
|
import pushPublicKey from "../../pushPublicKey"
|
||||||
|
|
||||||
export default function EnableNotifications({
|
export default function EnableNotifications({
|
||||||
onSubscribe,
|
onSubscribe,
|
||||||
|
|
@ -24,6 +25,9 @@ export default function EnableNotifications({
|
||||||
function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
|
function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
|
||||||
const { subscribeToPush, requestPermission, canSendPush } = usePush()
|
const { subscribeToPush, requestPermission, canSendPush } = usePush()
|
||||||
|
|
||||||
|
const [error, setError] = useState<Error>()
|
||||||
|
const [log, setLog] = useState<string[]>([])
|
||||||
|
|
||||||
function subscribe() {
|
function subscribe() {
|
||||||
requestPermission()
|
requestPermission()
|
||||||
}
|
}
|
||||||
|
|
@ -31,8 +35,45 @@ function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canSendPush) return
|
if (!canSendPush) return
|
||||||
|
|
||||||
subscribeToPush(urlB64ToUint8Array(pushPublicKey) as any, onSubscribe)
|
setLog((prev) => [...prev, "Subscribing to push notifications"])
|
||||||
|
|
||||||
|
subscribeToPush(
|
||||||
|
urlB64ToUint8Array(pushPublicKey) as any,
|
||||||
|
(subscription) => {
|
||||||
|
setLog((prev) => [
|
||||||
|
...prev,
|
||||||
|
`controller is undefined? ${navigator.serviceWorker.controller === undefined}`,
|
||||||
|
])
|
||||||
|
setLog((prev) => [
|
||||||
|
...prev,
|
||||||
|
`subscriptions: ${JSON.stringify(subscription.toJSON())}`,
|
||||||
|
])
|
||||||
|
try {
|
||||||
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
|
registration.active?.postMessage({
|
||||||
|
type: "subscribed",
|
||||||
|
subscription: subscription.toJSON(),
|
||||||
|
})
|
||||||
|
setLog((prev) => [...prev, "After post message"])
|
||||||
|
})
|
||||||
|
// onSubscribe()
|
||||||
|
} catch (error) {
|
||||||
|
setError(error as Error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
setError(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
}, [canSendPush])
|
}, [canSendPush])
|
||||||
|
|
||||||
return <button onClick={subscribe}>Enable Notifications</button>
|
return (
|
||||||
|
<>
|
||||||
|
<button onClick={subscribe}>Enable Notifications</button>
|
||||||
|
<div>{error?.toString()}</div>
|
||||||
|
{log.map((log, index) => (
|
||||||
|
<div key={index}>{log}</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,21 +93,33 @@ function TackUpNow({
|
||||||
|
|
||||||
const [isInstalled, setIsInstalled] = useState(installed)
|
const [isInstalled, setIsInstalled] = useState(installed)
|
||||||
|
|
||||||
|
const [messages, setMessages] = useState<any[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigator.serviceWorker.onmessage = (event) =>
|
||||||
|
setMessages((prev) => [...prev, JSON.stringify(event.data)])
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClientOnly fallback={<div>Loading</div>}>
|
<ClientOnly fallback={<div>Loading</div>}>
|
||||||
{() =>
|
{() => (
|
||||||
isInstalled ? (
|
<>
|
||||||
<div>Your Notifications</div>
|
{messages.map((message, index) => (
|
||||||
) : (
|
<div key={index}>{JSON.stringify(message)}</div>
|
||||||
<InstallPrompts
|
))}
|
||||||
isMobileSafari={isMobileSafari}
|
{isInstalled ? (
|
||||||
isSupported={isSupported}
|
<div>Your Notifications</div>
|
||||||
isIOS={isIOS}
|
) : (
|
||||||
notificationsEnabled={false}
|
<InstallPrompts
|
||||||
onInstallComplete={() => setIsInstalled(true)}
|
isMobileSafari={isMobileSafari}
|
||||||
/>
|
isSupported={isSupported}
|
||||||
)
|
isIOS={isIOS}
|
||||||
}
|
notificationsEnabled={false}
|
||||||
|
onInstallComplete={() => setIsInstalled(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
201
app/worker.ts
201
app/worker.ts
|
|
@ -40,54 +40,177 @@ export default function start(self: ServiceWorkerGlobalScope) {
|
||||||
// )
|
// )
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
async function sendMessage(message: any) {
|
||||||
|
const clients = await self.clients.matchAll()
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
clients.map(async (client) => client.postMessage(message))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener("message", function (event) {
|
||||||
|
const waitEvent = event as ExtendableEvent
|
||||||
|
waitEvent.waitUntil(
|
||||||
|
(async () => {
|
||||||
|
await sendMessage({
|
||||||
|
message: "Got message",
|
||||||
|
data: event.data,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
"type" in event.data &&
|
||||||
|
event.data.type === "subscribed" &&
|
||||||
|
"subscription" in event.data
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await event.waitUntil(
|
||||||
|
submitSubscription(
|
||||||
|
self.registration,
|
||||||
|
event.data.subscription as PushSubscription
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
await sendMessage({
|
||||||
|
message: "Got error processing subscription",
|
||||||
|
error: (e as Error).toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: "Processed subscription",
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
self.addEventListener("pushsubscriptionchange", function (event) {
|
self.addEventListener("pushsubscriptionchange", function (event) {
|
||||||
const waitEvent = event as ExtendableEvent
|
const waitEvent = event as ExtendableEvent
|
||||||
|
|
||||||
waitEvent.waitUntil(submitSubscription(self.registration))
|
waitEvent.waitUntil(
|
||||||
|
(async () => {
|
||||||
|
const existingSubscription =
|
||||||
|
await self.registration.pushManager.getSubscription()
|
||||||
|
const newSubscription = await submitSubscription(
|
||||||
|
self.registration,
|
||||||
|
existingSubscription
|
||||||
|
)
|
||||||
|
|
||||||
self.registration.pushManager.getSubscription()
|
await sendMessage({ type: "sent subscription", newSubscription })
|
||||||
})
|
})()
|
||||||
}
|
)
|
||||||
|
|
||||||
async function submitSubscription(registration: ServiceWorkerRegistration) {
|
|
||||||
const db = database()
|
|
||||||
|
|
||||||
const subscription = await registration.pushManager.getSubscription()
|
|
||||||
if (subscription === null) return
|
|
||||||
|
|
||||||
const existingSubscriptionId = await db.subscriptions.get(1)
|
|
||||||
|
|
||||||
const applicationServerKey = urlB64ToUint8Array(pushPublicKey)
|
|
||||||
const newSubscription = await registration.pushManager.subscribe({
|
|
||||||
userVisibleOnly: true,
|
|
||||||
applicationServerKey: applicationServerKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await (existingSubscriptionId === undefined
|
async function submitSubscription(
|
||||||
? postSubscription(newSubscription, db)
|
registration: ServiceWorkerRegistration,
|
||||||
: putSubscription(newSubscription, existingSubscriptionId.subscriptionId))
|
subscription: PushSubscription | null
|
||||||
}
|
) {
|
||||||
|
const db = database()
|
||||||
|
|
||||||
async function postSubscription(
|
await sendMessage({
|
||||||
subscription: PushSubscription,
|
message: `Is subscription null? ${subscription === null}`,
|
||||||
db: ReturnType<typeof database>
|
})
|
||||||
) {
|
|
||||||
const response = await fetch(`${process.env.BASE_URL}/api/subscription/`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(subscription),
|
|
||||||
})
|
|
||||||
|
|
||||||
db.subscriptions.put({
|
if (subscription === null) return
|
||||||
id: 1,
|
|
||||||
subscriptionId: (await response.json()).subscriptionId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function putSubscription(subscription: PushSubscription, id: number) {
|
const existingSubscriptionId = await db.subscriptions.get(1)
|
||||||
return fetch(`${process.env.BASE_URL}/api/subscription/${id}`, {
|
|
||||||
method: "PUT",
|
await sendMessage({
|
||||||
body: JSON.stringify(subscription),
|
message: `Existing subscription ID ${existingSubscriptionId}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `pushPublicKey ${pushPublicKey}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
let applicationServerKey
|
||||||
|
try {
|
||||||
|
applicationServerKey = urlB64ToUint8Array(pushPublicKey)
|
||||||
|
} catch (error) {
|
||||||
|
await sendMessage({
|
||||||
|
message: `B64 error ${(error as Error).toString()}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `Converted public key`,
|
||||||
|
})
|
||||||
|
const newSubscription = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: applicationServerKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `subscribed via pushManager`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const stupid =
|
||||||
|
existingSubscriptionId === undefined
|
||||||
|
? postSubscription(newSubscription.toJSON(), db)
|
||||||
|
: putSubscription(
|
||||||
|
newSubscription.toJSON(),
|
||||||
|
existingSubscriptionId.subscriptionId
|
||||||
|
)
|
||||||
|
await stupid
|
||||||
|
|
||||||
|
return newSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postSubscription(
|
||||||
|
subscription: PushSubscriptionJSON,
|
||||||
|
db: ReturnType<typeof database>
|
||||||
|
) {
|
||||||
|
await sendMessage({
|
||||||
|
message: "Log something you piece of garbage",
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendMessage({
|
||||||
|
message: `URL ${process.env.BASE_URL}/api/subscription/`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `Submitting subscription ${subscription}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `JSON formatted: ${JSON.stringify(subscription)}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${process.env.BASE_URL}/api/subscription/`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(subscription),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await sendMessage({
|
||||||
|
message: `Response status ${response.status}`,
|
||||||
|
})
|
||||||
|
db.subscriptions.put({
|
||||||
|
id: 1,
|
||||||
|
subscriptionId: (await response.json()).subscriptionId,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
await sendMessage({
|
||||||
|
message: `ERRRROR ${(error as Error).toString()}}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function putSubscription(subscription: PushSubscriptionJSON, id: number) {
|
||||||
|
return fetch(`${process.env.BASE_URL}/api/subscription/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify(subscription),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function database() {
|
function database() {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ export default function urlB64ToUint8Array(base64String: string) {
|
||||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
|
||||||
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
|
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
|
||||||
|
|
||||||
const rawData = window.atob(base64)
|
const rawData = atob(base64)
|
||||||
const outputArray = new Uint8Array(rawData.length)
|
const outputArray = new Uint8Array(rawData.length)
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
remix({ ignoredRouteFiles: ["**/*.test.*"] }),
|
remix({ ignoredRouteFiles: ["**/*.test.*"] }),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
remixPWA(),
|
remixPWA({
|
||||||
|
buildVariables: {
|
||||||
|
"process.env.BASE_URL": process.env.BASE_URL!,
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue