Skip to content

Instantly share code, notes, and snippets.

@maiconcarraro
Last active August 14, 2024 17:54
Show Gist options
  • Save maiconcarraro/0845af1acc7490fb57e4c119815f50fc to your computer and use it in GitHub Desktop.
Save maiconcarraro/0845af1acc7490fb57e4c119815f50fc to your computer and use it in GitHub Desktop.
PWABuilder iOS shell + React example
export default function SubscribeButton() {
const [subscribing, setSubscribing] = useState<boolean>(true);
const [token, setToken] = useState<string | null>(null);
const [iOSPushCapability, setiOSPushCapability] = useState(false);
const saveFCMToken = api.user.saveFCMToken.useMutation({ // I'm using tRPC
onSuccess() {
setSubscribing(false);
toast.success("Success.");
},
onError() {
toast.error("Error.");
},
});
useEffect(() => {
if (typeof window !== "object") {
return;
}
// For iOS shell
if (
window.webkit &&
window.webkit.messageHandlers &&
window.webkit.messageHandlers["push-permission-request"] &&
window.webkit.messageHandlers["push-permission-state"]
) {
setiOSPushCapability(true);
return;
}
// Below this is for Web and android shell only
const granted = Notification?.permission === "granted";
if (granted) {
getToken(getMessaging(firebaseApp), {
vapidKey: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
})
.then((token) => {
setToken(token);
setSubscribing(false);
})
.catch((err) => console.error(err));
} else {
setSubscribing(false);
}
}, []);
const subscribeButtonOnClick = async () => {
setSubscribing(true);
const messaging = getMessaging(firebaseApp);
// Request notification permission
const permission = await Notification.requestPermission();
if (permission === "granted") {
const currentToken = await getToken(messaging, {
vapidKey: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY, // I'm using Next.js
}).catch((err) => {
console.error(err);
return "";
});
setToken(currentToken);
if (currentToken) {
await saveFCMToken.mutateAsync({ fcmToken: currentToken });
}
} else {
setSubscribing(false);
toast.error("Error.");
}
};
const onTokenChange = useCallback((token: string) => {
return saveFCMToken
.mutateAsync({ fcmToken: token })
.then(() => setToken(token));
}, []);
return (
<div>
{iOSPushCapability ? (
<SubscribeButtonIOS
loading={saveFCMToken.isPending}
onTokenChange={onTokenChange}
>
Subscribe (iOS)
</SubscribeButtonIOS>
) : (
<Button
loading={saveFCMToken.isPending || subscribing}
onClick={subscribeButtonOnClick}
>
Subscribe (Web)
</Button>
)}
</div>
);
}
declare global {
interface Window {
webkit?: {
messageHandlers: {
[x: string]: {
postMessage: (data: string) => void;
};
};
};
}
}
export default function SubscribeButtonIOS({
onTokenChange,
...props
}: ButtonProps & {
onTokenChange: (token: string) => void;
}) {
const [synced, setSynced] = useState(false);
const [token, setToken] = useState<string | null>(null);
const webkitPushTokenRequest = useCallback(() => {
window.webkit?.messageHandlers["push-token"]?.postMessage("push-token");
}, []);
const webkitPushPermissionRequest = useCallback(() => {
window.webkit?.messageHandlers["push-permission-request"]?.postMessage(
"push-permission-request",
);
}, []);
const webkitPushPermissionState = useCallback(() => {
window.webkit?.messageHandlers["push-permission-state"]?.postMessage(
"push-permission-state",
);
}, []);
useEffect(() => {
if (typeof window !== "object") {
return;
}
const pushPermissionRequest = (event: CustomEvent) => {
if (event && event.detail) {
switch (event.detail) {
case "granted":
webkitPushTokenRequest();
break;
default:
toast.error("Denied.");
break;
}
}
};
// @ts-ignore
window.addEventListener("push-permission-state", (event: CustomEvent) => {
setSynced(true);
if (event && event.detail) {
switch (event.detail) {
case "notDetermined":
// permission not asked
break;
case "denied":
toast.error("Denied.");
// permission denied
break;
case "authorized":
case "ephemeral":
case "provisional":
webkitPushTokenRequest();
break;
case "unknown":
default:
// something wrong
break;
}
}
});
// Sync state
webkitPushPermissionState();
// sendPushToWebView
const pushNotification = (event: CustomEvent) => {
if (event && event.detail) {
// logMessage(JSON.stringify(event.detail));
}
};
const pushToken = (event: CustomEvent) => {
if (event && event.detail) {
setToken(event.detail);
}
};
// @ts-ignore
window.addEventListener("push-permission-request", pushPermissionRequest);
// @ts-ignore
window.addEventListener("push-notification", pushNotification);
// @ts-ignore
window.addEventListener("push-token", pushToken);
return () => {
// @ts-ignore
window.removeEventListener(
"push-permission-request",
pushPermissionRequest,
);
// @ts-ignore
window.removeEventListener("push-notification", pushNotification);
// @ts-ignore
window.removeEventListener("push-token", pushToken);
};
}, []);
useEffect(() => {
if (token) {
onTokenChange(token);
}
}, [token, onTokenChange]);
return (
<Button
{...props}
loading={!synced || props.loading}
onClick={() => {
setToken("");
webkitPushPermissionRequest();
}}
/>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment