2022-03-18 21:33:23 +01:00
import { h } from '/js/web_modules/preact.js' ;
import { useState , useEffect } from '/js/web_modules/preact/hooks.js' ;
import htm from '/js/web_modules/htm.js' ;
import { ExternalActionButton } from './external-action-modal.js' ;
import {
registerWebPushNotifications ,
isPushNotificationSupported ,
} from '../notification/registerWeb.js' ;
import {
URL _REGISTER _NOTIFICATION ,
URL _REGISTER _EMAIL _NOTIFICATION ,
HAS _DISPLAYED _NOTIFICATION _MODAL _KEY ,
USER _VISIT _COUNT _KEY ,
USER _DISMISSED _ANNOYING _NOTIFICATION _POPUP _KEY ,
} from '../utils/constants.js' ;
import { setLocalStorage , getLocalStorage } from '../utils/helpers.js' ;
const html = htm . bind ( h ) ;
export function NotifyModal ( { notifications , streamName , accessToken } ) {
const [ error , setError ] = useState ( null ) ;
const [ loaderStyle , setLoaderStyle ] = useState ( 'none' ) ;
const [ emailNotificationsButtonEnabled , setEmailNotificationsButtonEnabled ] =
useState ( false ) ;
const [ emailAddress , setEmailAddress ] = useState ( null ) ;
const emailNotificationButtonState = emailNotificationsButtonEnabled
? ''
: 'cursor-not-allowed opacity-50' ;
const [ browserPushPermissionsPending , setBrowserPushPermissionsPending ] =
useState ( false ) ;
2022-03-21 21:42:59 +01:00
const { browser } = notifications ;
2022-03-18 21:33:23 +01:00
const { publicKey } = browser ;
const browserPushEnabled = browser . enabled && isPushNotificationSupported ( ) ;
2022-03-21 21:42:59 +01:00
let emailEnabled = false ;
2022-03-18 21:33:23 +01:00
// Store that the user has opened the notifications modal at least once
// so we don't ever need to remind them to do it again.
useEffect ( ( ) => {
setLocalStorage ( HAS _DISPLAYED _NOTIFICATION _MODAL _KEY , true ) ;
} , [ ] ) ;
async function saveNotificationRegistration ( channel , destination ) {
const options = {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
body : JSON . stringify ( { channel : channel , destination : destination } ) ,
} ;
try {
await fetch (
URL _REGISTER _NOTIFICATION + ` ?accessToken= ${ accessToken } ` ,
options
) ;
} catch ( e ) {
console . error ( e ) ;
}
}
async function startBrowserPushRegistration ( ) {
// If it's already denied or granted, don't do anything.
if ( Notification . permission !== 'default' ) {
return ;
}
setBrowserPushPermissionsPending ( true ) ;
try {
const subscription = await registerWebPushNotifications ( publicKey ) ;
saveNotificationRegistration ( 'BROWSER_PUSH_NOTIFICATION' , subscription ) ;
setError ( null ) ;
} catch ( e ) {
setError (
` Error registering for live notifications: ${ e . message } . Make sure you're not inside a private browser environment or have previously disabled notifications for this stream. `
) ;
}
setBrowserPushPermissionsPending ( false ) ;
}
async function handlePushToggleChange ( ) {
// Nothing can be done if they already denied access.
if ( Notification . permission === 'denied' ) {
return ;
}
if ( ! pushEnabled ) {
startBrowserPushRegistration ( ) ;
}
}
async function registerForEmailButtonPressed ( ) {
try {
const options = {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
body : JSON . stringify ( { emailAddress : emailAddress } ) ,
} ;
try {
await fetch (
URL _REGISTER _EMAIL _NOTIFICATION + ` ?accessToken= ${ accessToken } ` ,
options
) ;
} catch ( e ) {
console . error ( e ) ;
}
} catch ( e ) {
setError ( ` Error registering for email notifications: ${ e . message } . ` ) ;
}
}
function onEmailInput ( e ) {
const { value } = e . target ;
// TODO: Add validation for email
const valid = true ;
setEmailAddress ( value ) ;
setEmailNotificationsButtonEnabled ( valid ) ;
}
function getBrowserPushButtonText ( ) {
let pushNotificationButtonText = html ` <span id="push-notification-arrow"
> ← < / s p a n
>
CLICK TO ENABLE ` ;
if ( browserPushPermissionsPending ) {
pushNotificationButtonText = '↑ ACCEPT THE BROWSER PERMISSIONS' ;
} else if ( Notification . permission === 'granted' ) {
pushNotificationButtonText = 'ENABLED' ;
} else if ( Notification . permission === 'denied' ) {
pushNotificationButtonText = 'DENIED. PLEASE FIX BROWSER PERMISSIONS.' ;
}
return pushNotificationButtonText ;
}
const pushEnabled = Notification . permission === 'granted' ;
return html `
< div class = "bg-gray-100 bg-center bg-no-repeat p-6" >
< div
style = $ { { display : emailEnabled ? 'grid' : 'none' } }
class = "grid grid-cols-2 gap-10 px-5 py-8"
>
< div >
< h2 class = "text-slate-600 text-2xl mb-2 font-semibold" >
Email Notifications
< / h 2 >
< h2 >
Get notified directly to your email when this stream goes live .
< / h 2 >
< / d i v >
< div >
< div class = "font-semibold" > Enter your email address : < / d i v >
< input
class = "border bg-white rounded-l w-8/12 mt-2 mb-1 mr-1 py-2 px-3 text-indigo-700 leading-tight focus:outline-none focus:shadow-outline"
value = $ { emailAddress }
onInput = $ { onEmailInput }
placeholder = "streamlover42@gmail.com"
/ >
< button
class = "rounded-sm inline px-3 py-2 text-base text-white bg-indigo-700 ${emailNotificationButtonState}"
onClick = $ { registerForEmailButtonPressed }
>
Save
< / b u t t o n >
< div class = "text-sm mt-3 text-gray-700" >
Stop receiving emails any time by clicking the unsubscribe link in
the email . < a href = "" > Learn more . < / a >
< / d i v >
< / d i v >
< / d i v >
< hr
style = $ { { display : pushEnabled && emailEnabled ? 'block' : 'none' } }
/ >
< div
class = "grid grid-cols-2 gap-10 px-5 py-8"
style = $ { { display : browserPushEnabled ? 'grid' : 'none' } }
>
< div >
< div >
< div
class = "text-sm border-2 p-4 border-red-300"
style = $ { {
display :
Notification . permission === 'denied' ? 'block' : 'none' ,
} }
>
Browser notification permissions were denied . Please visit your
browser settings to re - enable in order to get notifications .
< / d i v >
< div
class = "form-check form-switch"
style = $ { {
display :
Notification . permission === 'denied' ? 'none' : 'block' ,
} }
>
< div
class = "relative inline-block w-10 align-middle select-none transition duration-200 ease-in"
>
< input
checked = $ { pushEnabled || browserPushPermissionsPending }
disabled = $ { pushEnabled }
type = "checkbox"
name = "toggle"
id = "toggle"
onchange = $ { handlePushToggleChange }
class = "toggle-checkbox absolute block w-8 h-8 rounded-full bg-white border-4 appearance-none cursor-pointer"
/ >
< label
for = "toggle"
style = $ { { width : '50px' } }
class = "toggle-label block overflow-hidden h-8 rounded-full bg-gray-300 cursor-pointer"
> < / l a b e l >
< / d i v >
< div class = "ml-8 text-xs inline-block text-gray-700" >
$ { getBrowserPushButtonText ( ) }
< / d i v >
< / d i v >
< / d i v >
< h2 class = "text-slate-600 text-2xl mt-4 mb-2 font-semibold" >
Browser Notifications
< / h 2 >
< h2 >
Get notified right in the browser each time this stream goes live .
< / h 2 >
< / d i v >
< div >
< div
class = "text-sm mt-3"
style = $ { { display : ! pushEnabled ? 'none' : 'block' } }
>
To disable push notifications from $ { window . location . hostname }
$ { ' ' } access your browser permissions for this site and turn off
notifications .
< div style = $ { { 'margin-top' : '5px' } } >
2022-04-25 23:08:08 +02:00
< a href = "https://owncast.online/docs/notifications"
> Learn more . < / a
>
2022-03-18 21:33:23 +01:00
< / d i v >
< / d i v >
< div
id = "browser-push-preview-box"
class = "w-full bg-white p-4 m-2 mt-4"
style = $ { { display : pushEnabled ? 'none' : 'block' } }
>
< div class = "text-lg text-gray-700 ml-2 my-2" >
$ { window . location . toString ( ) } wants to
< / d i v >
< div class = "text-sm text-gray-700 my-2" >
< svg
class = "mr-3"
style = $ { { display : 'inline-block' } }
width = "16"
height = "16"
viewBox = "0 0 16 16"
fill = "none"
xmlns = "http://www.w3.org/2000/svg"
>
< path
d = "M14 12.3333V13H2V12.3333L3.33333 11V7C3.33333 4.93333 4.68667 3.11333 6.66667 2.52667C6.66667 2.46 6.66667 2.4 6.66667 2.33333C6.66667 1.97971 6.80714 1.64057 7.05719 1.39052C7.30724 1.14048 7.64638 1 8 1C8.35362 1 8.69276 1.14048 8.94281 1.39052C9.19286 1.64057 9.33333 1.97971 9.33333 2.33333C9.33333 2.4 9.33333 2.46 9.33333 2.52667C11.3133 3.11333 12.6667 4.93333 12.6667 7V11L14 12.3333ZM9.33333 13.6667C9.33333 14.0203 9.19286 14.3594 8.94281 14.6095C8.69276 14.8595 8.35362 15 8 15C7.64638 15 7.30724 14.8595 7.05719 14.6095C6.80714 14.3594 6.66667 14.0203 6.66667 13.6667"
fill = "#676670"
/ >
< / s v g >
Show notifications
< / d i v >
< div class = "flex flex-row justify-end" >
< button
class = "bg-blue-500 py-1 px-4 mr-4 rounded-sm text-white"
onClick = $ { startBrowserPushRegistration }
>
Allow
< / b u t t o n >
< button
class = "bg-slate-200 py-1 px-4 rounded-sm text-gray-500 cursor-not-allowed"
style = $ { {
'outline-width' : 1 ,
'outline-color' : '#e2e8f0' ,
'outline-style' : 'solid' ,
} }
>
Block
< / b u t t o n >
< / d i v >
< / d i v >
< p
class = "text-gray-700 text-sm mt-6"
style = $ { { display : pushEnabled ? 'none' : 'block' } }
>
You ' ll need to allow your browser to receive notifications from
$ { ' ' } $ { streamName } , first .
< / p >
< / d i v >
< / d i v >
< div
id = "follow-loading-spinner-container"
style = "display: ${loaderStyle}"
>
< img id = "follow-loading-spinner" src = "/img/loading.gif" / >
< p class = "text-gray-700 text-lg" > Contacting your server . < / p >
< p class = "text-gray-600 text-lg" > Please wait ... < / p >
< / d i v >
< / d i v >
` ;
}
export function NotifyButton ( { serverName , onClick } ) {
const hasDisplayedNotificationModal = getLocalStorage (
HAS _DISPLAYED _NOTIFICATION _MODAL _KEY
) ;
const hasPreviouslyDismissedAnnoyingPopup = getLocalStorage (
USER _DISMISSED _ANNOYING _NOTIFICATION _POPUP _KEY
) ;
let visits = parseInt ( getLocalStorage ( USER _VISIT _COUNT _KEY ) ) ;
if ( isNaN ( visits ) ) {
visits = 0 ;
}
// Only show the annoying popup if the user has never opened the notification
// modal previously _and_ they've visited more than 3 times.
const [ showPopup , setShowPopup ] = useState (
! hasPreviouslyDismissedAnnoyingPopup &&
! hasDisplayedNotificationModal &&
visits > 3
) ;
const notifyAction = {
color : 'rgba(219, 223, 231, 1)' ,
description : ` Never miss a stream! Get notified when ${ serverName } goes live. ` ,
icon : '/img/notification-bell.svg' ,
openExternally : false ,
} ;
const buttonClicked = ( e ) => {
onClick ( e ) ;
setShowPopup ( false ) ;
} ;
2022-04-17 21:15:11 +02:00
const notifyPopupDismissedClicked = ( e ) => {
e . stopPropagation ( ) ;
2022-03-18 21:33:23 +01:00
setShowPopup ( false ) ;
setLocalStorage ( USER _DISMISSED _ANNOYING _NOTIFICATION _POPUP _KEY , true ) ;
} ;
return html `
< span id = "notify-button-container" class = "relative" >
< div
2022-04-17 21:15:11 +02:00
id = "notify-button-popup"
class = "text-gray-200 p-4 rounded-md cursor-pointer"
2022-03-18 21:33:23 +01:00
style = $ { { display : showPopup ? 'block' : 'none' } }
2022-04-17 21:15:11 +02:00
onClick = $ { buttonClicked }
2022-03-18 21:33:23 +01:00
>
2022-04-17 20:59:42 +02:00
< div class = "flex justify-between items-center mb-2" >
< div class = "font-bold" > Stay updated ! < / d i v >
< button
class = "popout-close-button rounded-md p-1 color-gray-500"
onClick = $ { notifyPopupDismissedClicked }
>
< svg
class = "w-6 h-6"
fill = "none"
stroke = "currentColor"
viewBox = "0 0 24 24"
>
< path
stroke - linecap = "round"
stroke - linejoin = "round"
stroke - width = "2"
d = "M6 18L18 6M6 6l12 12"
> < / p a t h >
< / s v g >
< / b u t t o n >
2022-04-17 20:59:05 +02:00
< / d i v >
2022-04-17 21:15:11 +02:00
< div > Click and never miss future streams ! < / d i v >
2022-03-18 21:33:23 +01:00
< / d i v >
< $ { ExternalActionButton }
onClick = $ { buttonClicked }
action = $ { notifyAction }
/ >
< / s p a n >
` ;
}