Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 72 additions & 17 deletions app/javascript/controllers/profile_webpush_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default class extends Controller {
static targets = ["checkbox"]

connect() {

console.log('webpush connected!');

if ('serviceWorker' in navigator && 'PushManager' in window) {
Expand All @@ -18,28 +17,57 @@ export default class extends Controller {
this.checkboxTarget.style.disabled = true;
this.checkboxTarget.classList.add('disabled');
}
}
)}
)}
}).catch(error => {
console.error('Error checking subscription:', error);
});
});
}
}

setupPushNotifications() {
const applicationServerKey = this.urlBase64ToUint8Array(this.vapidPublicValue);
if ("Notification" in window) {
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
this.registerAndSubscribe();
} else {
console.warn("User rejected to allow notifications.");
}
});
} else {
console.warn("Push notifications not supported.");
}
}

navigator.serviceWorker.register("/service-worker.js", {scope: "./" }).then((registration) => {
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
}).then((subscription) => {
registerAndSubscribe() {
const applicationServerKey = this.urlBase64ToUint8Array(this.vapidPublicValue);

navigator.serviceWorker.register("/service-worker.js", {scope: "./" })
.then((registration) => {
console.log('Service Worker registered successfully:', registration);
return navigator.serviceWorker.ready;
})
.then((serviceWorkerRegistration) => {
return serviceWorkerRegistration.pushManager.getSubscription()
.then((existingSubscription) => {
if (!existingSubscription) {
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
});
}
return existingSubscription;
});
})
.then((subscription) => {
const endpoint = subscription.endpoint;
const p256dh = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh'))));
const auth = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth'))));

fetch('/push_subscriptions', {
return fetch('/push_subscriptions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
Expand All @@ -49,14 +77,41 @@ export default class extends Controller {
auth: auth
}
})
}).then(response => {
if (response.ok) {
console.log("Subscription successfully saved on the server.");
localStorage.setItem('block-webpush-modal', 'true');
const modal = document.querySelector('.webpush-modal');
if (modal) {
modal.style.display = 'none';
}
this.checkboxTarget.style.disabled = true;
this.checkboxTarget.classList.add('disabled');
} else {
throw new Error(`Server responded with status: ${response.status}`);
}
});

localStorage.setItem('block-webpush-modal', 'true');
document.querySelector('.webpush-modal').style.display = 'none';
this.checkboxTarget.style.disabled = true;
this.checkboxTarget.classList.add('disabled');
})
.catch(error => {
console.error('Service Worker registration or subscription failed:', error);
alert('Failed to enable push notifications. Please try again later.');
});
});
}

urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');

var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);

for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}

return outputArray;
}

}
118 changes: 81 additions & 37 deletions app/javascript/controllers/push_notification_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,108 @@ export default class extends Controller {
if (element) {
element.style.display = 'none';
}
return;
}

// Check if the browser supports notifications
// Check if notification permission was already denied
if ("Notification" in window && Notification.permission === "denied") {
const element = document.querySelector('.webpush-modal');
if (element) {
element.style.display = 'none';
}
return;
}

// Check if notification permission was already granted and subscription exists
if ("Notification" in window && Notification.permission === "granted") {
this.checkExistingSubscription();
}

// Modal will be shown automatically, permission will be requested only when user clicks "Accept"
}

checkExistingSubscription() {
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.ready.then(registration => {
registration.pushManager.getSubscription().then(subscription => {
if (subscription) {
// Already subscribed, hide modal
const element = document.querySelector('.webpush-modal');
if (element) {
element.style.display = 'none';
}
}
});
});
}
}

setupPushNotifications() {
// First request permission from the user
if ("Notification" in window) {
// Request permission from the user to send notifications
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
// If permission is granted, register the service worker
this.registerServiceWorker();
// Permission granted, now register service worker
this.registerAndSubscribe();
} else if (permission === "denied") {
console.warn("User rejected to allow notifications.");
localStorage.setItem('block-webpush-modal', 'true');
const modal = document.querySelector('.webpush-modal');
if (modal) {
modal.style.display = 'none';
}
} else {
console.warn("User still didn't give an answer about notifications.");
console.warn("User dismissed the permission dialog.");
}
});
} else {
console.warn("Push notifications not supported.");
}
}

setupPushNotifications() {
// Check if the browser supports service workers
if ("serviceWorker" in navigator) {
// Register the service worker script (service_worker.js)
registerAndSubscribe() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js', { scope: '/' })
.then(registration => {
console.log('Service Worker registered successfully:', registration);
// Wait for the service worker to be ready
return navigator.serviceWorker.ready;
})
.then((serviceWorkerRegistration) => {
// Check if a subscription to push notifications already exists
serviceWorkerRegistration.pushManager
.getSubscription()
return serviceWorkerRegistration.pushManager.getSubscription()
.then((existingSubscription) => {
if (!existingSubscription) {
// If no subscription exists, subscribe to push notifications
serviceWorkerRegistration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicValue),
})
.then((subscription) => {
// Save the subscription on the server
this.saveSubscription(subscription);
});
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicValue),
});
}
return existingSubscription;
});

localStorage.setItem('block-webpush-modal', 'true');
document.querySelector('.webpush-modal').style.display = 'none';
})
.catch((error) => {
console.error("Error during registration Service Worker:", error);
});
}
}

registerServiceWorker() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js', { scope: '/' })
.then(registration => {
console.log('Service Worker registered successfully:', registration);
this.setupPushNotifications(registration);
.then((subscription) => {
// Save the subscription on the server
this.saveSubscription(subscription);
localStorage.setItem('block-webpush-modal', 'true');
const modal = document.querySelector('.webpush-modal');
if (modal) {
modal.style.display = 'none';
}
})
.catch(error => {
console.error('Service Worker registration failed:', error);
console.error('Service Worker registration or subscription failed:', error);

// Hide modal even if Service Worker registration failed (for development)
localStorage.setItem('block-webpush-modal', 'true');
const modal = document.querySelector('.webpush-modal');
if (modal) {
modal.style.display = 'none';
}

// Show user-friendly message
alert('Push notifications are not available in this environment. This is expected in local development with self-signed SSL certificates.');
});
} else {
console.warn('Service Workers are not supported in this browser.');
Expand Down Expand Up @@ -109,17 +149,21 @@ export default class extends Controller {
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
},
body: JSON.stringify({ endpoint, p256dh, auth }),
body: JSON.stringify({
subscription: { endpoint, p256dh, auth }
}),
})
.then((response) => {
if (response.ok) {
console.log("Subscription successfully saved on the server.");
} else {
console.error("Error saving subscription on the server.");
throw new Error(`Server responded with status: ${response.status}`);
}
})
.catch((error) => {
console.error("Error sending subscription to the server:", error);
alert('Failed to save push notification subscription. Please try again later.');
});
}

Expand Down
2 changes: 1 addition & 1 deletion app/views/common/_webpush_modal.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="webpush-modal" data-controller="push-notification" data-turbo-temporary>
<div class="webpush-modal" data-turbo-temporary>
<div class="webpush-modal__content">
<button class="webpush-modal__close" data-action="click->push-notification#close">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
Expand Down
Loading
Loading