bugfixing
This commit is contained in:
parent
f2d57877e6
commit
5f30f48c5d
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -1,70 +1,33 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await pb.collection("pushSubscription").create({user: user?.id, subscription: data})
|
await pb.collection("pushSubscription").create({user: user?.id, subscription: data})
|
||||||
|
|
||||||
|
|
||||||
//addUserDevice(username, data.subscription);
|
output = true;
|
||||||
//addUserToChannel(username, 'album-updates');
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({'success': true}));
|
} catch(err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({'success': output}));
|
||||||
|
|
||||||
}) satisfies RequestHandler;
|
}) satisfies RequestHandler;
|
||||||
@ -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
|
||||||
try {
|
const subscriptions = await pb.collection('pushSubscription').getFullList({ sort: '-created' });
|
||||||
|
|
||||||
// Get all push subscriptions
|
if (!subscriptions.length) {
|
||||||
const subscriptions = await pb.collection('pushSubscription').getFullList({
|
console.warn('No subscriptions found.');
|
||||||
sort: '-created'
|
return new Response(JSON.stringify({ success: false, error: 'No subscriptions found' }), { status: 404 });
|
||||||
});
|
|
||||||
|
|
||||||
// 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 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user