Fix bug in usePush, also some bugs in install state
Test / test (push) Failing after 24s
Details
Test / test (push) Failing after 24s
Details
This commit is contained in:
parent
e2cb0eb214
commit
e09769e1ba
|
|
@ -0,0 +1,19 @@
|
|||
import { ReactNode, useEffect, useState } from "react"
|
||||
|
||||
export default function ClientOnly({
|
||||
fallback,
|
||||
children,
|
||||
}: {
|
||||
fallback: ReactNode
|
||||
children(): ReactNode
|
||||
}) {
|
||||
const isClient = typeof window !== "undefined"
|
||||
|
||||
const [rendered, setRendered] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setRendered(true)
|
||||
}, [])
|
||||
|
||||
return isClient && rendered ? children() : fallback
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react"
|
||||
import { useEffect } from "react"
|
||||
import { usePush } from "remix-pwa-monorepo/packages/push/client/hook"
|
||||
import { usePush } from "../usePush"
|
||||
|
||||
export default function EnableNotifications({
|
||||
onSubscribe,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import versionAtLeast from "semver/functions/gte"
|
|||
import UAParser from "ua-parser-js"
|
||||
import InstallPrompts from "../install/InstallPrompts"
|
||||
import useInstallState from "../useInstallState"
|
||||
import ClientOnly from "../ClientOnly"
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
|
|
@ -22,16 +23,24 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|||
const userAgent = request.headers.get("user-agent")
|
||||
const parsedUserAgent = new UAParser(userAgent ?? "")
|
||||
const os = parsedUserAgent.getOS()
|
||||
const isMobileSafari = parsedUserAgent.getBrowser().name !== "Mobile Safari"
|
||||
const isMobileSafari = parsedUserAgent.getBrowser().name === "Mobile Safari"
|
||||
const isSupported =
|
||||
os.name !== "iOS" ||
|
||||
versionAtLeast(coerceSemver(os.version) ?? "0.0.0", "16.4.0")
|
||||
|
||||
return json({ isSupported, isMobileSafari })
|
||||
return json({
|
||||
isSupported,
|
||||
isMobileSafari,
|
||||
name: os.name,
|
||||
version: os.version,
|
||||
})
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const { isSupported, isMobileSafari } = useLoaderData<typeof loader>()
|
||||
const { isSupported, isMobileSafari, name, version } =
|
||||
useLoaderData<typeof loader>()
|
||||
|
||||
console.log(name, version)
|
||||
|
||||
return (
|
||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
|
||||
|
|
@ -54,23 +63,22 @@ function LandingMessage({
|
|||
}) {
|
||||
const { installed } = useInstallState({ isSupported, isMobileSafari })
|
||||
|
||||
const [rendered, setRendered] = useState(false)
|
||||
const [isInstalled, setIsInstalled] = useState(installed)
|
||||
|
||||
useEffect(() => {
|
||||
setRendered(true)
|
||||
}, [])
|
||||
|
||||
return !rendered ? (
|
||||
<div>Loading</div>
|
||||
) : isInstalled ? (
|
||||
<div>Your Notifications</div>
|
||||
) : (
|
||||
<InstallPrompts
|
||||
isMobileSafari={isMobileSafari}
|
||||
isSupported={isSupported}
|
||||
notificationsEnabled={false}
|
||||
onInstallComplete={() => setIsInstalled(true)}
|
||||
/>
|
||||
return (
|
||||
<ClientOnly fallback={<div>Loading</div>}>
|
||||
{() =>
|
||||
isInstalled ? (
|
||||
<div>Your Notifications</div>
|
||||
) : (
|
||||
<InstallPrompts
|
||||
isMobileSafari={isMobileSafari}
|
||||
isSupported={isSupported}
|
||||
notificationsEnabled={false}
|
||||
onInstallComplete={() => setIsInstalled(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ClientOnly>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { usePush } from "remix-pwa-monorepo/packages/push/client/hook"
|
||||
import { usePush } from "./usePush"
|
||||
|
||||
type IOSInstallStep =
|
||||
| "loading"
|
||||
|
|
@ -14,7 +14,7 @@ export default function useInstallState({
|
|||
isSupported: boolean
|
||||
isMobileSafari: boolean
|
||||
}) {
|
||||
const isClient = typeof window !== undefined
|
||||
const isClient = typeof window !== "undefined"
|
||||
|
||||
if (!isClient)
|
||||
return {
|
||||
|
|
@ -34,13 +34,15 @@ export default function useInstallState({
|
|||
matchMedia("(dislay-mode: standalone)").matches
|
||||
|
||||
return {
|
||||
step: !isMobileSafari
|
||||
? "open safari"
|
||||
: !isRunningPWA && isMobileSafari
|
||||
? "install"
|
||||
: !notificationsEnabled
|
||||
? "enable notifications"
|
||||
: (null as IOSInstallStep | null),
|
||||
step: !isSupported
|
||||
? "unsupported"
|
||||
: !isMobileSafari
|
||||
? "open safari"
|
||||
: !isRunningPWA && isMobileSafari
|
||||
? "install"
|
||||
: !notificationsEnabled
|
||||
? "enable notifications"
|
||||
: (null as IOSInstallStep | null),
|
||||
installed: notificationsEnabled,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
import { useEffect, useState } from "react"
|
||||
|
||||
export type PushObject = {
|
||||
/**
|
||||
* Boolean state indicating whether the user is subscribed to push notifications or not.
|
||||
*/
|
||||
isSubscribed: boolean
|
||||
/**
|
||||
* The push subscription object
|
||||
*/
|
||||
pushSubscription: PushSubscription | null
|
||||
/**
|
||||
* Request permission for push notifications
|
||||
* @returns The permission status of the push notifications
|
||||
*/
|
||||
requestPermission: () => NotificationPermission
|
||||
/**
|
||||
* Utility to subscribe to push notifications
|
||||
* @param publicKey the public vapid key
|
||||
* @param callback a callback function to be called when the subscription is successful
|
||||
* @param errorCallback a callback function to be called if the subscription fails
|
||||
*/
|
||||
subscribeToPush: (
|
||||
publicKey: string,
|
||||
callback?: (subscription: PushSubscription) => void,
|
||||
errorCallback?: (error: any) => void
|
||||
) => void
|
||||
/**
|
||||
* Utility to unsubscribe from push notifications
|
||||
* @param callback a callback function to be called when the unsubscription is successful
|
||||
* @param errorCallback a callback function to be called if the unsubscription fails
|
||||
*/
|
||||
unsubscribeFromPush: (
|
||||
callback?: () => void,
|
||||
errorCallback?: (error: any) => void
|
||||
) => void
|
||||
/**
|
||||
* Boolean state indicating whether the user has allowed sending of push notifications or not.
|
||||
*/
|
||||
canSendPush: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Push API hook - contains all your necessities for handling push notifications in the client
|
||||
*/
|
||||
export const usePush = (): PushObject => {
|
||||
const [swRegistration, setSWRegistration] =
|
||||
useState<ServiceWorkerRegistration | null>(null)
|
||||
const [isSubscribed, setIsSubscribed] = useState<boolean>(false)
|
||||
const [pushSubscription, setPushSubscription] =
|
||||
useState<PushSubscription | null>(null)
|
||||
const [canSendPush, setCanSendPush] = useState<boolean>(false)
|
||||
|
||||
const requestPermission = () => {
|
||||
if (canSendPush) return "granted"
|
||||
|
||||
Notification.requestPermission().then((permission) => {
|
||||
if (permission === "granted") {
|
||||
setCanSendPush(true)
|
||||
return permission
|
||||
} else {
|
||||
setCanSendPush(false)
|
||||
return permission
|
||||
}
|
||||
})
|
||||
|
||||
return "default"
|
||||
}
|
||||
|
||||
const subscribeToPush = (
|
||||
publicKey: string,
|
||||
callback?: (subscription: PushSubscription) => void,
|
||||
errorCallback?: (error: any) => void
|
||||
) => {
|
||||
if (swRegistration === null || swRegistration.pushManager === undefined)
|
||||
return
|
||||
|
||||
swRegistration.pushManager
|
||||
.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey,
|
||||
})
|
||||
.then(
|
||||
(subscription) => {
|
||||
setIsSubscribed(true)
|
||||
setPushSubscription(subscription)
|
||||
callback && callback(subscription)
|
||||
},
|
||||
(error) => {
|
||||
errorCallback && errorCallback(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const unsubscribeFromPush = (
|
||||
callback?: () => void,
|
||||
errorCallback?: (error: any) => void
|
||||
) => {
|
||||
if (swRegistration === null || swRegistration.pushManager === undefined)
|
||||
return
|
||||
|
||||
swRegistration.pushManager
|
||||
.getSubscription()
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe().then(
|
||||
() => {
|
||||
setIsSubscribed(false)
|
||||
setPushSubscription(null)
|
||||
callback && callback()
|
||||
},
|
||||
(error) => {
|
||||
errorCallback && errorCallback(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
errorCallback && errorCallback(error)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
const getRegistration = async () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
try {
|
||||
const _registration = await navigator.serviceWorker.getRegistration()
|
||||
setSWRegistration(_registration ?? null)
|
||||
} catch (err) {
|
||||
console.error("Error getting service worker registration:", err)
|
||||
}
|
||||
} else {
|
||||
console.warn("Service Workers are not supported in this browser.")
|
||||
}
|
||||
}
|
||||
|
||||
const handleControllerChange = () => {
|
||||
getRegistration()
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.addEventListener(
|
||||
"controllerchange",
|
||||
handleControllerChange
|
||||
)
|
||||
}
|
||||
|
||||
getRegistration()
|
||||
|
||||
return () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.removeEventListener(
|
||||
"controllerchange",
|
||||
handleControllerChange
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (swRegistration && swRegistration.pushManager !== undefined) {
|
||||
swRegistration.pushManager.getSubscription().then((subscription) => {
|
||||
setIsSubscribed(!!subscription)
|
||||
setPushSubscription(subscription)
|
||||
})
|
||||
|
||||
Notification.permission === "granted"
|
||||
? setCanSendPush(true)
|
||||
: setCanSendPush(false)
|
||||
}
|
||||
}, [swRegistration])
|
||||
|
||||
return {
|
||||
isSubscribed,
|
||||
pushSubscription,
|
||||
requestPermission,
|
||||
subscribeToPush,
|
||||
unsubscribeFromPush,
|
||||
canSendPush,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { test, expect, Page } from "@playwright/test"
|
||||
import { before } from "node:test"
|
||||
|
||||
const { describe, beforeEach, skip, use } = test
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ describe("when user is on iOS", () => {
|
|||
describe("and the browser is not safari", () => {
|
||||
use({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en-gb) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
|
||||
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 16_4 like Mac OS X; en-gb) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
|
||||
})
|
||||
|
||||
test("the user is told they need to install through Safari, because reasons, I guess", async ({
|
||||
|
|
@ -29,9 +28,9 @@ describe("when user is on iOS", () => {
|
|||
}) => {
|
||||
await page.goto("/")
|
||||
|
||||
const installText = await page.getByText(/Safari/)
|
||||
const safariText = await page.getByText(/Open tackupnow.com in Safari/)
|
||||
|
||||
await expect(installText).toBeAttached()
|
||||
await expect(safariText).toBeAttached()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -58,7 +57,7 @@ describe("when user is on iOS", () => {
|
|||
await stubNotifications(page, { permission: "default" })
|
||||
await page.goto("/")
|
||||
|
||||
const enableButton = await page.getByText(/Enable notifications/)
|
||||
const enableButton = await page.getByText(/Enable Notifications/)
|
||||
|
||||
await expect(enableButton).toBeAttached()
|
||||
})
|
||||
|
|
@ -80,7 +79,7 @@ describe("when user is on iOS", () => {
|
|||
await page.goto("/")
|
||||
|
||||
const requestPromise = page.waitForRequest("/api/subscribe")
|
||||
await page.getByText(/Enable notifications/).click()
|
||||
await page.getByText(/Enable Notifications/).click()
|
||||
const request = await requestPromise
|
||||
|
||||
await expect(request.postDataJSON()).toEqual({ hi: "subscription" })
|
||||
|
|
@ -92,7 +91,7 @@ describe("when user is on iOS", () => {
|
|||
await page.goto("/")
|
||||
|
||||
const requestPromise = page.waitForRequest("/api/subscribe")
|
||||
await page.getByText(/Enable notifications/).click()
|
||||
await page.getByText(/Enable Notifications/).click()
|
||||
await requestPromise
|
||||
|
||||
const yourNotificationsHeading =
|
||||
|
|
@ -112,7 +111,7 @@ describe("when user is on iOS", () => {
|
|||
await page.goto("/")
|
||||
await page.evaluate(async () => await navigator.serviceWorker.ready)
|
||||
|
||||
const notificationText = await page.getByText(/Enable notifications/)
|
||||
const notificationText = await page.getByText(/Enable Notifications/)
|
||||
|
||||
await expect(notificationText).not.toBeAttached()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"name": "site",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@remix-pwa/sw": "^3.0.9",
|
||||
"@remix-pwa/worker-runtime": "^2.1.4",
|
||||
|
|
@ -17,8 +18,6 @@
|
|||
"isbot": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remix-pwa-monorepo": "github:remix-pwa/monorepo#main",
|
||||
"remix-utils": "^7.6.0",
|
||||
"semver": "^7.6.3",
|
||||
"ua-parser-js": "^1.0.39"
|
||||
},
|
||||
|
|
@ -14298,91 +14297,6 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remix-pwa-monorepo": {
|
||||
"resolved": "git+ssh://git@github.com/remix-pwa/monorepo.git#dda9d68b1c69642679d6ff17658f21fe24c668d6",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/cli",
|
||||
"packages/client",
|
||||
"packages/dev",
|
||||
"packages/eslint-config",
|
||||
"packages/lint-staged-config",
|
||||
"packages/push",
|
||||
"packages/sw",
|
||||
"packages/sync",
|
||||
"packages/worker-runtime",
|
||||
"playground"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remix-utils": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/remix-utils/-/remix-utils-7.6.0.tgz",
|
||||
"integrity": "sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@remix-run/cloudflare": "^2.0.0",
|
||||
"@remix-run/deno": "^2.0.0",
|
||||
"@remix-run/node": "^2.0.0",
|
||||
"@remix-run/react": "^2.0.0",
|
||||
"@remix-run/router": "^1.7.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"intl-parse-accept-language": "^1.0.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"react": "^18.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@remix-run/cloudflare": {
|
||||
"optional": true
|
||||
},
|
||||
"@remix-run/deno": {
|
||||
"optional": true
|
||||
},
|
||||
"@remix-run/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@remix-run/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"optional": true
|
||||
},
|
||||
"crypto-js": {
|
||||
"optional": true
|
||||
},
|
||||
"intl-parse-accept-language": {
|
||||
"optional": true
|
||||
},
|
||||
"is-ip": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/remix-utils/node_modules/type-fest": {
|
||||
"version": "4.26.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
|
||||
"integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
"start": "remix-serve ./build/server/index.js",
|
||||
"typecheck": "tsc",
|
||||
"watch": "jest --watch --config=jest.config.ts",
|
||||
"test": "jest --config=jest.config.ts"
|
||||
"test": "jest --config=jest.config.ts",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@remix-pwa/sw": "^3.0.9",
|
||||
|
|
@ -24,8 +25,6 @@
|
|||
"isbot": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remix-pwa-monorepo": "github:remix-pwa/monorepo#main",
|
||||
"remix-utils": "^7.6.0",
|
||||
"semver": "^7.6.3",
|
||||
"ua-parser-js": "^1.0.39"
|
||||
},
|
||||
|
|
@ -76,4 +75,4 @@
|
|||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue