mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(push): tell iPhone Safari users to open the Home Screen app
iOS offers Web Push only to a web app opened from the Home Screen. In a Safari tab the push card said the browser does not support push notifications, and the step that fixes it was only in the docs. On an iPhone or iPad outside the installed app the card now says to add HealthLog to the Home Screen, open it from there and turn push on. WebPushCard grows by two lines for the second sentence; its baseline goes from 201 to 203, still below the 215 it had before the previous change.
This commit is contained in:
1 parent
32911fea37
commit
7f8ba0ae98
13 files changed
+97
-8
No files matched your search
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
|
||||
"count": 344,
|
||||
"total": 111493,
|
||||
"total": 111495,
|
||||
"entries": {
|
||||
"app/achievements/page.tsx :: AchievementsPage": 206,
|
||||
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 275,
|
||||
@@ -224,7 +224,7 @@
|
||||
"components/settings/sharing-section.tsx :: ShareLinksCard": 260,
|
||||
"components/settings/sources-section.tsx :: SourcesSection": 601,
|
||||
"components/settings/thresholds-editor-section.tsx :: MetricRow": 241,
|
||||
"components/settings/web-push-card.tsx :: WebPushCard": 201,
|
||||
"components/settings/web-push-card.tsx :: WebPushCard": 203,
|
||||
"components/symptoms/symptom-entry-form.tsx :: SymptomEntryForm": 317,
|
||||
"components/symptoms/symptoms-section.tsx :: SymptomsSection": 205,
|
||||
"components/targets/range-bar.tsx :: RangeBar": 220,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Which "push is unavailable" sentence the Web Push card shows.
|
||||
*
|
||||
* An iPhone in a Safari tab has no PushManager, the same as a browser that
|
||||
* cannot do push at all, and the card used to tell both that push is not
|
||||
* supported. On iOS the fix is to open HealthLog from the Home Screen, so
|
||||
* that case gets its own sentence.
|
||||
*/
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { needsHomeScreen } from "../web-push-card";
|
||||
|
||||
const IPHONE =
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1";
|
||||
const IPAD_AS_MAC =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15";
|
||||
const ANDROID_FIREFOX =
|
||||
"Mozilla/5.0 (Android 14; Mobile; rv:132.0) Gecko/132.0 Firefox/132.0";
|
||||
|
||||
function device(
|
||||
userAgent: string,
|
||||
maxTouchPoints: number,
|
||||
standalone: boolean,
|
||||
) {
|
||||
vi.stubGlobal("navigator", { userAgent, maxTouchPoints });
|
||||
vi.stubGlobal("window", {
|
||||
matchMedia: (query: string) => ({
|
||||
matches: standalone && query === "(display-mode: standalone)",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("needsHomeScreen", () => {
|
||||
it("asks an iPhone in a Safari tab to open the Home Screen app", () => {
|
||||
device(IPHONE, 5, false);
|
||||
expect(needsHomeScreen()).toBe(true);
|
||||
});
|
||||
|
||||
it("asks an iPad, which reports itself as a Mac, the same", () => {
|
||||
device(IPAD_AS_MAC, 5, false);
|
||||
expect(needsHomeScreen()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not ask the installed app, where push is missing for real", () => {
|
||||
device(IPHONE, 5, true);
|
||||
expect(needsHomeScreen()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not ask a Mac without a touch screen", () => {
|
||||
device(IPAD_AS_MAC, 0, false);
|
||||
expect(needsHomeScreen()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not ask a browser that has no push at all", () => {
|
||||
device(ANDROID_FIREFOX, 5, false);
|
||||
expect(needsHomeScreen()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -25,10 +25,25 @@ function urlBase64ToArrayBuffer(base64String: string): ArrayBuffer {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* iPhone and iPad offer Web Push only to HealthLog opened from the Home
|
||||
* Screen; in a Safari tab the API is missing. iPadOS Safari reports itself
|
||||
* as a Mac, so a Mac with a touch screen counts too.
|
||||
*/
|
||||
export function needsHomeScreen(): boolean {
|
||||
const ua = navigator.userAgent;
|
||||
const appleTouch =
|
||||
/iPhone|iPad|iPod/.test(ua) ||
|
||||
(/Macintosh/.test(ua) && navigator.maxTouchPoints > 1);
|
||||
return appleTouch && !window.matchMedia("(display-mode: standalone)").matches;
|
||||
}
|
||||
|
||||
type Unsupported = "browser" | "install" | null;
|
||||
|
||||
export function WebPushCard() {
|
||||
const { t } = useTranslations();
|
||||
const [isSubscribed, setIsSubscribed] = useState(false);
|
||||
const [isSupported, setIsSupported] = useState(true);
|
||||
const [unsupported, setUnsupported] = useState<Unsupported>(null);
|
||||
const [isDenied, setIsDenied] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [actionLoading, setActionLoading] = useState(false);
|
||||
@@ -41,7 +56,7 @@ export function WebPushCard() {
|
||||
// the effect body is rejected.
|
||||
async function checkSubscription() {
|
||||
if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
|
||||
setIsSupported(false);
|
||||
setUnsupported(needsHomeScreen() ? "install" : "browser");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +171,7 @@ export function WebPushCard() {
|
||||
// carries a status badge.
|
||||
const cardAction = loading ? (
|
||||
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin motion-reduce:animate-none" />
|
||||
) : !isSupported || isDenied ? null : (
|
||||
) : unsupported || isDenied ? null : (
|
||||
<SettingsCardActions>
|
||||
{isSubscribed && (
|
||||
<TestConnectionButton endpoint="/api/notifications/web-push/test" />
|
||||
@@ -199,11 +214,13 @@ export function WebPushCard() {
|
||||
description={t("settings.webPushDescription")}
|
||||
/>
|
||||
|
||||
{(!loading && !isSupported) || (!loading && isDenied) || msg ? (
|
||||
{(!loading && unsupported) || (!loading && isDenied) || msg ? (
|
||||
<div className="space-y-4">
|
||||
{!loading && !isSupported ? (
|
||||
{!loading && unsupported ? (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("settings.webPushNotSupported")}
|
||||
{unsupported === "install"
|
||||
? t("settings.webPushInstallFirst")
|
||||
: t("settings.webPushNotSupported")}
|
||||
</p>
|
||||
) : !loading && isDenied ? (
|
||||
<p className="text-destructive text-sm">
|
||||
|
||||
Reference in new issue
Block a user