push notifications but installable to home screen yet

This commit is contained in:
m5r
2022-05-30 02:21:42 +02:00
parent b5bb8e1822
commit 4c22ee83e1
40 changed files with 1104 additions and 83 deletions

View File

@ -13,7 +13,7 @@ type SessionTwilioAccount = Pick<
TwilioAccount,
"accountSid" | "subAccountSid" | "subAccountAuthToken" | "apiKeySid" | "apiKeySecret" | "twimlAppSid"
>;
type SessionOrganization = Pick<Organization, "id"> & { role: MembershipRole };
type SessionOrganization = Pick<Organization, "id"> & { role: MembershipRole; membershipId: string };
type SessionPhoneNumber = Pick<PhoneNumber, "id" | "number">;
export type SessionUser = Pick<User, "id" | "role" | "email" | "fullName">;
export type SessionData = {
@ -190,6 +190,7 @@ async function buildSessionData(id: string): Promise<SessionData> {
},
},
role: true,
id: true,
},
},
},
@ -203,6 +204,7 @@ async function buildSessionData(id: string): Promise<SessionData> {
const organizations = memberships.map((membership) => ({
...membership.organization,
role: membership.role,
membershipId: membership.id,
}));
const { twilioAccount, ...organization } = organizations[0];
const phoneNumber = await db.phoneNumber.findUnique({

70
app/utils/pwa.client.ts Normal file
View File

@ -0,0 +1,70 @@
type ResponseObject = {
status: "success" | "bad";
message: string;
};
// use case: prevent making phone calls / queue messages when offline
export async function checkConnectivity(online: () => void, offline: () => void): Promise<ResponseObject> {
try {
if (navigator.onLine) {
online();
return {
status: "success",
message: "Connected to the internet",
};
} else {
offline();
return {
status: "bad",
message: "No internet connection available",
};
}
} catch (err) {
console.debug(err);
throw new Error("Unable to check network connectivity!");
}
}
// use case: display unread messages + missed phone calls count
export async function addBadge(numberCount: number): Promise<ResponseObject> {
try {
//@ts-ignore
if (navigator.setAppBadge) {
//@ts-ignore
await navigator.setAppBadge(numberCount);
return {
status: "success",
message: "Badge successfully added",
};
} else {
return {
status: "bad",
message: "Badging API not supported",
};
}
} catch (err) {
console.debug(err);
throw new Error("Error adding badge!");
}
}
export async function removeBadge(): Promise<ResponseObject> {
try {
//@ts-ignore
if (navigator.clearAppBadge) {
//@ts-ignore
await navigator.clearAppBadge();
return {
status: "success",
message: "Cleared badges",
};
} else {
return {
status: "bad",
message: "Badging API not supported in this browser!",
};
}
} catch (error) {
console.debug(error);
throw new Error("Error removing badge!");
}
}

View File

@ -4,5 +4,5 @@ export const { getSeo, getSeoMeta, getSeoLinks } = initSeo({
title: "",
titleTemplate: "%s | Shellphone",
description: "",
defaultTitle: "Shellphone",
defaultTitle: "Shellphone: Your Personal Cloud Phone",
});

View File

@ -15,7 +15,7 @@ export default function getTwilioClient({
throw new Error("unreachable");
}
return twilio(subAccountSid, subAccountAuthToken ?? serverConfig.twilio.authToken, {
return twilio(subAccountSid, serverConfig.twilio.authToken, {
accountSid,
});
}

View File

@ -0,0 +1,57 @@
import webpush, { type PushSubscription, WebPushError } from "web-push";
import serverConfig from "~/config/config.server";
import db from "~/utils/db.server";
import logger from "~/utils/logger.server";
export type NotificationPayload = NotificationOptions & {
title: string;
body: string;
};
export async function notify(phoneNumberId: string, payload: NotificationPayload) {
webpush.setVapidDetails("mailto:mokht@rmi.al", serverConfig.webPush.publicKey, serverConfig.webPush.privateKey);
const phoneNumber = await db.phoneNumber.findUnique({
where: { id: phoneNumberId },
select: {
organization: {
select: {
memberships: {
select: { notificationSubscription: true },
},
},
},
},
});
if (!phoneNumber) {
// TODO
return;
}
const subscriptions = phoneNumber.organization.memberships.flatMap(
(membership) => membership.notificationSubscription,
);
await Promise.all(
subscriptions.map(async (subscription) => {
const webPushSubscription: PushSubscription = {
endpoint: subscription.endpoint,
keys: {
p256dh: subscription.keys_p256dh,
auth: subscription.keys_auth,
},
};
try {
await webpush.sendNotification(webPushSubscription, JSON.stringify(payload));
} catch (error: any) {
logger.error(error);
if (error instanceof WebPushError) {
// subscription most likely expired or has been revoked
await db.notificationSubscription.delete({ where: { id: subscription.id } });
}
}
}),
);
}