bugfixing

This commit is contained in:
MuslemRahimi 2025-02-02 22:26:56 +01:00
parent f2d57877e6
commit 5f30f48c5d
7 changed files with 152 additions and 141 deletions

View File

@ -64,15 +64,17 @@ export async function unsubscribe() {
async function sendSubscriptionToServer(subscription) { async function sendSubscriptionToServer(subscription) {
try { try {
const res = await fetch('/api/addPushSubscription', { const response = await fetch('/api/addPushSubscription', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ subscription }) body: JSON.stringify({ subscription })
}); });
if (!res.ok)
throw new Error(`Error saving subscription on server: ${res.statusText} (${res.status})`); const output = await response?.json()
return output;
} catch (error) { } catch (error) {
console.error('Error saving subscription on server:', error); console.error('Error saving subscription on server:', error);
unsubscribe(); unsubscribe();
@ -87,9 +89,11 @@ export async function subscribeUser() {
userVisibleOnly: true, userVisibleOnly: true,
applicationServerKey: import.meta.env.VITE_VAPID_PUBLIC_KEY applicationServerKey: import.meta.env.VITE_VAPID_PUBLIC_KEY
}); });
sendSubscriptionToServer(subscription); const output = sendSubscriptionToServer(subscription);
return output;
} catch (err) { } catch (err) {
console.error('Error subscribing:', err); console.error('Error subscribing:', err);
return {'success': false}
} }
} }
} }
@ -104,7 +108,7 @@ export async function checkSubscriptionStatus() {
//this can be optional //this can be optional
if (exists) { if (exists) {
// just to make sure the subscription is saved on the server // just to make sure the subscription is saved on the server
sendSubscriptionToServer(subscription); //sendSubscriptionToServer(subscription);
} }
return exists; return exists;
} }

View File

@ -52,12 +52,6 @@
import Gem from "lucide-svelte/icons/gem"; import Gem from "lucide-svelte/icons/gem";
import stocknear_logo from "$lib/images/stocknear_logo.png"; import stocknear_logo from "$lib/images/stocknear_logo.png";
import {
requestNotificationPermission,
subscribeUser,
checkSubscriptionStatus,
} from "$lib/notifications";
export let data; export let data;
let hideHeader = false; let hideHeader = false;

View File

@ -25,6 +25,7 @@
let AppInstalled = null; let AppInstalled = null;
function getClosedPWA() { function getClosedPWA() {
//if user closed the banner
const item = localStorage.getItem("closePWA"); const item = localStorage.getItem("closePWA");
if (!item) return null; if (!item) return null;

View File

@ -1,59 +1,20 @@
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
import { error} from '@sveltejs/kit';
import webpush from "web-push";
const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY;
const VAPID_PRIVATE_KEY = import.meta.env.VITE_VAPID_PRIVATE_KEY;
function initWebPush() {
webpush.setVapidDetails('mailto:contact@stocknear.com',VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY )
}
async function sendNotification(subscription, payload) {
try {
const res = await webpush.sendNotification(subscription, payload);
return {
ok: res.statusCode === 201,
status: res.statusCode,
body: res.body
};
} catch (err) {
const msg = `Could not send notification: ${err}`;
console.error(msg);
return {
ok: false,
status: undefined,
body: msg
};
}
}
export const POST = (async ({ locals, request }) => { export const POST = (async ({ locals, request }) => {
const { user, pb } = locals; const { user, pb } = locals;
let output = false;
if (!user?.id) {
console.log('No username passed to addSubscription');
throw error(401, 'Unauthorized');
}
const data = await request.json(); const data = await request.json();
if (!data?.subscription) { try {
console.log('No subscription passed to addSubscription', data); const items = await pb.collection("pushSubscription").getFullList({
throw error(400, 'Bad Request');
}
initWebPush()
// find all subscription of users if they exist and delete them first before creating the new one.
const output = await pb.collection("pushSubscription").getFullList({
filter: `user="${user?.id}"`, filter: `user="${user?.id}"`,
}); });
if (output?.length > 0) { if (output?.length > 0) {
for (const item of output) { for (const item of items) {
await pb.collection("pushSubscription").delete(item?.id); await pb.collection("pushSubscription").delete(item?.id);
} }
} }
@ -61,10 +22,12 @@ export const POST = (async ({ locals, request }) => {
await pb.collection("pushSubscription").create({user: user?.id, subscription: data}) await pb.collection("pushSubscription").create({user: user?.id, subscription: data})
output = true;
//addUserDevice(username, data.subscription); } catch(err) {
//addUserToChannel(username, 'album-updates'); console.log(err)
}
return new Response(JSON.stringify({'success': true})); return new Response(JSON.stringify({'success': output}));
}) satisfies RequestHandler; }) satisfies RequestHandler;

View File

@ -12,46 +12,54 @@ webPush.setVapidDetails(
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
const { pb, apiKey } = locals; const { pb, apiKey } = locals;
const { body, key } = await request?.json();
const { body, key } = await request?.json(); if (apiKey !== key) {
console.warn('Invalid API key');
return new Response(JSON.stringify({ success: false, error: 'Invalid API key' }), { status: 401 });
}
if (apiKey === key) { try {
// Get all push subscriptions
const subscriptions = await pb.collection('pushSubscription').getFullList({ sort: '-created' });
try { if (!subscriptions.length) {
console.warn('No subscriptions found.');
// Get all push subscriptions return new Response(JSON.stringify({ success: false, error: 'No subscriptions found' }), { status: 404 });
const subscriptions = await pb.collection('pushSubscription').getFullList({
sort: '-created'
});
// Send notifications to all subscriptions
const sendNotifications = subscriptions?.map(async (subRecord) => {
try {
const subscriptionData = subRecord.subscription?.subscription;
await webPush.sendNotification(
subscriptionData, // Ensure correct format
body
);
} catch (error: any) {
console.error('Error sending notification:', error);
// Delete invalid subscriptions (410 means "Gone")
if (error.statusCode === 410) {
await pb.collection('pushSubscription').delete(subRecord.id);
}
}
});
await Promise.all(sendNotifications);
return new Response(JSON.stringify({ success: true, message: `Notifications sent to ${subscriptions.length} devices` }));
} catch (error: any) {
console.error('Error sending notifications:', error);
return new Response(JSON.stringify({ success: false, error: error.message }, { status: 500 }));
}
} else {
console.log('key is wrong')
} }
// Send notifications
const sendNotifications = subscriptions.map(async (subRecord) => {
try {
const subscriptionData = subRecord.subscription?.subscription;
if (!subscriptionData || !subscriptionData.endpoint) {
console.warn(`Skipping invalid subscription: ${subRecord.id}`);
return;
}
// Apple Push Notifications do not support VAPID
const payload = subscriptionData.endpoint.includes('web.push.apple.com') ? '' : body;
await webPush.sendNotification(subscriptionData, payload);
console.log(`Notification sent to: ${subscriptionData.endpoint}`);
} catch (error: any) {
console.error(`Error sending notification to ${subRecord.id}:`, error);
// Remove invalid subscriptions
if (error.statusCode === 410 || error.statusCode === 404) {
console.warn(`Deleting invalid subscription: ${subRecord.id}`);
await pb.collection('pushSubscription').delete(subRecord.id);
}
}
});
await Promise.all(sendNotifications);
return new Response(JSON.stringify({ success: true, message: `Notifications sent to ${subscriptions.length} devices` }));
} catch (error: any) {
console.error('Error sending notifications:', error);
return new Response(JSON.stringify({ success: false, error: error.message }), { status: 500 });
}
}; };

View File

@ -6,6 +6,30 @@ export const load = async ({ locals }) => {
redirect(303, "/login"); redirect(303, "/login");
} }
const getPushSubscriptionData = async () => {
let output = {};
try {
output = await pb.collection("pushSubscription").getFullList({
filter: `user="${user?.id}"`,
sort: "-created", // Sorts newest first
});
if (output?.length > 1) {
const [, ...toDelete] = output; // Keep the first item, delete the rest
await Promise.all(
toDelete.map((item) => pb.collection("pushSubscription").delete(item?.id))
);
}
} catch (err) {
console.log(err);
}
return output?.at(0) || null; // Return only the latest item
};
const getSubscriptionData = async () => { const getSubscriptionData = async () => {
const output = const output =
( (
@ -23,7 +47,9 @@ export const load = async ({ locals }) => {
return { return {
getSubscriptionData: await getSubscriptionData(), getSubscriptionData: await getSubscriptionData(),
getPushSubscriptionData: await getPushSubscriptionData(),
}; };
}; };
export const actions = { export const actions = {

View File

@ -2,6 +2,7 @@
import SEO from "$lib/components/SEO.svelte"; import SEO from "$lib/components/SEO.svelte";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import { isPWAInstalled } from "$lib/utils";
import { import {
requestNotificationPermission, requestNotificationPermission,
checkSubscriptionStatus, checkSubscriptionStatus,
@ -13,8 +14,9 @@
export let data; export let data;
export let form; export let form;
let pwaInstalled;
let nottifPermGranted: boolean | null = null; let nottifPermGranted: boolean | null = null;
let isPushSubscribed = false; let isPushSubscribed = data?.getPushSubscriptionData !== null ? true : false;
let subscriptionData = data?.getSubscriptionData; let subscriptionData = data?.getSubscriptionData;
let isClicked = false; let isClicked = false;
@ -159,11 +161,13 @@
}; };
onMount(async () => { onMount(async () => {
if (data?.user?.id) { pwaInstalled = isPWAInstalled();
nottifPermGranted = await requestNotificationPermission(); nottifPermGranted = await requestNotificationPermission();
if (nottifPermGranted) { if (nottifPermGranted) {
isPushSubscribed = (await checkSubscriptionStatus()) || false; isPushSubscribed =
} ((await checkSubscriptionStatus()) &&
data?.getPushSubscriptionData !== null) ||
false;
} }
}); });
@ -177,12 +181,20 @@
} }
async function handlePushSubscribe() { async function handlePushSubscribe() {
subscribeUser(); const output = await subscribeUser();
isPushSubscribed = true; console.log(output);
toast.success("Push notification activated successfully!", { if (output?.success === true) {
style: isPushSubscribed = true;
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", toast.success("Push notification activated successfully!", {
}); style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
} else {
toast.error("Your browser does not support push notifications...", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
}
} }
</script> </script>
@ -236,46 +248,49 @@
> >
</div> </div>
<div {#if !pwaInstalled}
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white" <div
> class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
<h2 class="text-white text-2xl font-semibold mb-3"> >
Push Notification <h2 class="text-white text-2xl font-semibold mb-3">
</h2> Push Notification
<div class="mt-3"> </h2>
{#if nottifPermGranted === null} <div class="mt-3">
<p>Checking permissions...</p> {#if nottifPermGranted === null}
{:else if nottifPermGranted === true} <p>Checking permissions...</p>
{#if isPushSubscribed} {:else if nottifPermGranted === true}
<p class="mb-3">Push notifications are currently active.</p> {#if isPushSubscribed}
<div class="mt-3"> <p class="mb-3">Push notifications are currently active.</p>
<div class="mt-3">
<button
class="border border-gray-600 w-fit px-5 py-1.5 bg-white text-black text-sm font-semibold rounded sm:hover:bg-white/80 transition ease-out duration-100"
type="button"
on:click={handlePushUnsubscribe}
>Disable notifications</button
>
</div>
{:else}
<p class="mb-3">
Stay up-to-date with real-time price alerts, the latest
stock news, and earnings calls delivered straight to your
device.
</p>
<button <button
class="border border-gray-600 w-fit px-5 py-1.5 bg-white text-black text-sm font-semibold rounded sm:hover:bg-white/80 transition ease-out duration-100" class="border border-gray-600 w-fit px-5 py-1.5 bg-white text-black text-sm font-semibold rounded sm:hover:bg-white/80 transition ease-out duration-100"
type="button" type="button"
on:click={handlePushUnsubscribe} on:click={handlePushSubscribe}
>Disable notifications</button >Enable notifications</button
> >
</div> {/if}
{:else} {:else if nottifPermGranted === false}
<p class="mb-3"> <p class="">
Stay up-to-date with real-time price alerts, the latest Review your settings and enable notifications to stay
stock news, and earnings calls delivered straight to your updated with Stocknear alerts.
device.
</p> </p>
<button
class="border border-gray-600 w-fit px-5 py-1.5 bg-white text-black text-sm font-semibold rounded sm:hover:bg-white/80 transition ease-out duration-100"
type="button"
on:click={handlePushSubscribe}>Enable notifications</button
>
{/if} {/if}
{:else if nottifPermGranted === false} </div>
<p class="">
Review your settings and enable notifications to stay updated
with Stocknear alerts.
</p>
{/if}
</div> </div>
</div> {/if}
<div <div
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white" class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"