Skip to content

Commit

Permalink
fix web push not working in self host (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
KMKoushik authored Aug 26, 2024
1 parent ae1a36c commit ec1155f
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ FEEDBACK_EMAIL=
# generate web push keys using this command: web-push generate-vapid-keys --json

WEB_PUSH_PRIVATE_KEY=
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=
WEB_PUSH_PUBLIC_KEY=
WEB_PUSH_EMAIL=
8 changes: 8 additions & 0 deletions docker/prod/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ services:
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?err}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:?err}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:?err}
- WEB_PUSH_PRIVATE_KEY=${WEB_PUSH_PRIVATE_KEY}
- WEB_PUSH_PUBLIC_KEY=${WEB_PUSH_PUBLIC_KEY}
- WEB_PUSH_EMAIL=${WEB_PUSH_EMAIL}
- R2_ACCESS_KEY=${R2_ACCESS_KEY}
- R2_SECRET_KEY=${R2_SECRET_KEY}
- R2_BUCKET=${R2_BUCKET}
- R2_URL=${R2_URL}
- NEXT_PUBLIC_R2_PUBLIC_URL=${NEXT_PUBLIC_R2_PUBLIC_URL}
depends_on:
postgres:
condition: service_healthy
Expand Down
8 changes: 5 additions & 3 deletions src/components/Account/SubscribeNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toast } from 'sonner';
import { env } from '~/env';
import { Bell, BellOff, ChevronRight } from 'lucide-react';
import { api } from '~/utils/api';
import { useAppStore } from '~/store/appStore';

const base64ToUint8Array = (base64: string) => {
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
Expand All @@ -21,6 +22,7 @@ const base64ToUint8Array = (base64: string) => {
export const SubscribeNotification: React.FC = () => {
const updatePushSubscription = api.user.updatePushNotification.useMutation();
const [isSubscribed, setIsSubscribed] = useState(false);
const webPushPublicKey = useAppStore((s) => s.webPushPublicKey);

useEffect(() => {
if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
Expand All @@ -47,14 +49,14 @@ export const SubscribeNotification: React.FC = () => {
toast.success('You will receive notifications now');
navigator.serviceWorker.ready
.then(async (reg) => {
if (!env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY) {
if (!webPushPublicKey) {
toast.error('Notification is not supported');
return;
}

const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY),
applicationServerKey: base64ToUint8Array(webPushPublicKey),
});

setIsSubscribed(true);
Expand Down Expand Up @@ -82,7 +84,7 @@ export const SubscribeNotification: React.FC = () => {
}
}

if (!env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY) {
if (!webPushPublicKey) {
return null;
}

Expand Down
9 changes: 6 additions & 3 deletions src/components/NotificationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from './ui/alert-dialog';
import { useAppStore } from '~/store/appStore';

const base64ToUint8Array = (base64: string) => {
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
Expand All @@ -32,6 +33,8 @@ const NOTIFICATION_DISMISSED_TIME_THRESHOLD = 1000 * 60 * 60 * 24 * 30; // 14 da

export const NotificationModal: React.FC = () => {
const updatePushSubscription = api.user.updatePushNotification.useMutation();
const webPushPublicKey = useAppStore((s) => s.webPushPublicKey);

const [modalOpen, setModalOpen] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -65,12 +68,12 @@ export const NotificationModal: React.FC = () => {
toast.success('You will receive notifications now');
navigator.serviceWorker.ready
.then(async (reg) => {
if (!env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY) {
if (!webPushPublicKey) {
return;
}
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY),
applicationServerKey: base64ToUint8Array(webPushPublicKey),
});

updatePushSubscription.mutate({ subscription: JSON.stringify(sub) });
Expand All @@ -90,7 +93,7 @@ export const NotificationModal: React.FC = () => {
setModalOpen(false);
}

if (!env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY) {
if (!webPushPublicKey) {
return null;
}

Expand Down
4 changes: 2 additions & 2 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const env = createEnv({
WEB_PUSH_PRIVATE_KEY: z.string().optional(),
UNSEND_API_KEY: z.string().optional(),
UNSEND_URL: z.string().optional(),
WEB_PUSH_PUBLIC_KEY: z.string().optional(),
},

/**
Expand All @@ -44,7 +45,6 @@ export const env = createEnv({
*/
client: {
NEXT_PUBLIC_R2_PUBLIC_URL: z.string().optional(),
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY: z.string().optional(),
NEXT_PUBLIC_BEAM_ID: z.string().optional(),
},

Expand All @@ -70,7 +70,7 @@ export const env = createEnv({
NEXT_PUBLIC_R2_PUBLIC_URL: process.env.NEXT_PUBLIC_R2_PUBLIC_URL,
WEB_PUSH_EMAIL: process.env.WEB_PUSH_EMAIL,
WEB_PUSH_PRIVATE_KEY: process.env.WEB_PUSH_PRIVATE_KEY,
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
WEB_PUSH_PUBLIC_KEY: process.env.WEB_PUSH_PUBLIC_KEY,
NEXT_PUBLIC_BEAM_ID: process.env.NEXT_PUBLIC_BEAM_ID,
UNSEND_API_KEY: process.env.UNSEND_API_KEY,
UNSEND_URL: process.env.UNSEND_URL,
Expand Down
10 changes: 10 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { type NextPageWithUser } from '~/types';
import { LoadingSpinner } from '~/components/ui/spinner';
import { useEffect, useState } from 'react';
import { useAddExpenseStore } from '~/store/addStore';
import { useAppStore } from '~/store/appStore';

const poppins = Poppins({ weight: ['200', '300', '400', '500', '600', '700'], subsets: ['latin'] });

Expand Down Expand Up @@ -82,13 +83,22 @@ const Auth: React.FC<{ Page: NextPageWithUser; pageProps: any }> = ({ Page, page
const [showSpinner, setShowSpinner] = useState(false);

const { setCurrency } = useAddExpenseStore((s) => s.actions);
const { setWebPushPublicKey } = useAppStore((s) => s.actions);

const { data: webPushPublicKey } = api.user.getWebPushPublicKey.useQuery();

useEffect(() => {
setTimeout(() => {
setShowSpinner(true);
}, 300);
}, []);

useEffect(() => {
if (webPushPublicKey) {
setWebPushPublicKey(webPushPublicKey);
}
}, [webPushPublicKey, setWebPushPublicKey]);

useEffect(() => {
if (status === 'authenticated') {
setCurrency(data.user.currency);
Expand Down
6 changes: 6 additions & 0 deletions src/pages/balances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { type NextPageWithUser } from '~/types';
import useEnableAfter from '~/hooks/useEnableAfter';
import { LoadingSpinner } from '~/components/ui/spinner';
import { NotificationModal } from '~/components/NotificationModal';
import { GetServerSideProps } from 'next';

const BalancePage: NextPageWithUser = () => {
function shareWithFriends() {
Expand Down Expand Up @@ -179,4 +180,9 @@ const FriendBalance: React.FC<{

BalancePage.auth = true;

// export const getServerSideProps = (async () => {

// return { props: { webPushKey: env } };
// }) satisfies GetServerSideProps<{ webPushKey: string }>;

export default BalancePage;
5 changes: 5 additions & 0 deletions src/server/api/routers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { sendFeedbackEmail, sendInviteEmail } from '~/server/mailer';
import { pushNotification } from '~/server/notification';
import { toFixedNumber, toUIString } from '~/utils/numbers';
import { SplitwiseGroupSchema, SplitwiseUserSchema } from '~/types';
import { env } from '~/env';

export const userRouter = createTRPCRouter({
me: protectedProcedure.query(async ({ ctx }) => {
Expand Down Expand Up @@ -477,4 +478,8 @@ export const userRouter = createTRPCRouter({
await importUserBalanceFromSplitWise(ctx.session.user.id, input.usersWithBalance);
await importGroupFromSplitwise(ctx.session.user.id, input.groups);
}),

getWebPushPublicKey: protectedProcedure.query(async ({ ctx }) => {
return env.WEB_PUSH_PUBLIC_KEY;
}),
});
4 changes: 2 additions & 2 deletions src/server/notification.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import webPush, { type PushSubscription } from 'web-push';
import { env } from '~/env';

if (env.WEB_PUSH_EMAIL && env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY && env.WEB_PUSH_PRIVATE_KEY) {
if (env.WEB_PUSH_EMAIL && env.WEB_PUSH_PUBLIC_KEY && env.WEB_PUSH_PRIVATE_KEY) {
webPush.setVapidDetails(
`mailto:${env.WEB_PUSH_EMAIL}`,
env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
env.WEB_PUSH_PUBLIC_KEY,
env.WEB_PUSH_PRIVATE_KEY,
);
}
Expand Down
15 changes: 15 additions & 0 deletions src/store/appStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { create } from 'zustand';

interface AppState {
webPushPublicKey: string | null;
actions: {
setWebPushPublicKey: (key: string) => void;
};
}

export const useAppStore = create<AppState>()((set) => ({
webPushPublicKey: null,
actions: {
setWebPushPublicKey: (key) => set({ webPushPublicKey: key }),
},
}));

0 comments on commit ec1155f

Please sign in to comment.