Web Push Notifications
* Add support for Web Push Notifications when users receive a new Message or Friend Request on the main website. * Users opt in or out of this on their Notification Settings. They can also individually opt out of Message and Friend Request push notifications.
This commit is contained in:
parent
dbeb5060e4
commit
a314aab7ec
|
@ -142,6 +142,10 @@ const (
|
|||
NotificationOptOutSubscriptions = "notif_optout_subscriptions"
|
||||
NotificationOptOutFriendRequestAccepted = "notif_optout_friend_request_accepted"
|
||||
NotificationOptOutPrivateGrant = "notif_optout_private_grant"
|
||||
|
||||
// Web Push Notifications
|
||||
PushNotificationOptOutMessage = "notif_optout_push_messages"
|
||||
PushNotificationOptOutFriends = "notif_optout_push_friends"
|
||||
)
|
||||
|
||||
// Notification opt-outs (stored in ProfileField table)
|
||||
|
@ -155,3 +159,9 @@ var NotificationOptOutFields = []string{
|
|||
NotificationOptOutFriendRequestAccepted,
|
||||
NotificationOptOutPrivateGrant,
|
||||
}
|
||||
|
||||
// Push Notification opt-outs (stored in ProfileField table)
|
||||
var PushNotificationOptOutFields = []string{
|
||||
PushNotificationOptOutMessage,
|
||||
PushNotificationOptOutFriends,
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/encryption/coldstorage"
|
||||
"code.nonshy.com/nonshy/website/pkg/encryption/keygen"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"github.com/SherClockHolmes/webpush-go"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -31,6 +32,7 @@ type Variable struct {
|
|||
BareRTC BareRTC
|
||||
Maintenance Maintenance
|
||||
Encryption Encryption
|
||||
WebPush WebPush
|
||||
Turnstile Turnstile
|
||||
UseXForwardedFor bool
|
||||
}
|
||||
|
@ -111,6 +113,19 @@ func LoadSettings() {
|
|||
writeSettings = true
|
||||
}
|
||||
|
||||
// Initialize the VAPID keys for Web Push Notification.
|
||||
if len(Current.WebPush.VAPIDPublicKey) == 0 {
|
||||
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
|
||||
if err != nil {
|
||||
log.Error("Initializing VAPID keys for Web Push: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
Current.WebPush.VAPIDPrivateKey = privateKey
|
||||
Current.WebPush.VAPIDPublicKey = publicKey
|
||||
writeSettings = true
|
||||
}
|
||||
|
||||
// Have we added new config fields? Save the settings.json.
|
||||
if Current.Version != currentVersion || writeSettings {
|
||||
log.Warn("New options are available for your settings.json file. Your settings will be re-saved now.")
|
||||
|
@ -181,6 +196,12 @@ type Encryption struct {
|
|||
ColdStorageRSAPublicKey []byte
|
||||
}
|
||||
|
||||
// WebPush settings.
|
||||
type WebPush struct {
|
||||
VAPIDPublicKey string
|
||||
VAPIDPrivateKey string
|
||||
}
|
||||
|
||||
// Turnstile (Cloudflare CAPTCHA) settings.
|
||||
type Turnstile struct {
|
||||
Enabled bool
|
||||
|
|
|
@ -276,6 +276,28 @@ func Settings() http.HandlerFunc {
|
|||
session.Flash(w, r, "Unsubscribed from all comment threads!")
|
||||
}
|
||||
}
|
||||
case "push_notifications":
|
||||
hashtag = "#notifications"
|
||||
|
||||
// Store their notification opt-outs.
|
||||
for _, key := range config.PushNotificationOptOutFields {
|
||||
var value = r.PostFormValue(key)
|
||||
|
||||
if value == "" {
|
||||
value = "true" // opt-out, store opt-out=true in the DB
|
||||
} else if value == "true" {
|
||||
value = "false" // the box remained checked, they don't opt-out, store opt-out=false in the DB
|
||||
}
|
||||
|
||||
// Save it.
|
||||
user.SetProfileField(key, value)
|
||||
}
|
||||
session.Flash(w, r, "Notification preferences updated!")
|
||||
|
||||
// Save the user for new fields to be committed to DB.
|
||||
if err := user.Save(); err != nil {
|
||||
session.FlashError(w, r, "Failed to save user to database: %s", err)
|
||||
}
|
||||
case "location":
|
||||
hashtag = "#location"
|
||||
var (
|
||||
|
@ -472,6 +494,9 @@ func Settings() http.HandlerFunc {
|
|||
// Count of subscribed comment threads.
|
||||
vars["SubscriptionCount"] = models.CountSubscriptions(user)
|
||||
|
||||
// Count of push notification subscriptions.
|
||||
vars["PushNotificationsCount"] = models.CountPushNotificationSubscriptions(user)
|
||||
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
"code.nonshy.com/nonshy/website/pkg/webpush"
|
||||
)
|
||||
|
||||
// AddFriend controller to send a friend request.
|
||||
|
@ -122,6 +123,21 @@ func AddFriend() http.HandlerFunc {
|
|||
} else {
|
||||
// Log the change.
|
||||
models.LogCreated(currentUser, "friends", user.ID, "Sent a friend request to "+user.Username+".")
|
||||
|
||||
// Send a push notification to the recipient.
|
||||
go func() {
|
||||
// Opted out of this one?
|
||||
if user.GetProfileField(config.PushNotificationOptOutFriends) == "true" {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Try and send Web Push notification about new Friend Request to: %s", user.Username)
|
||||
webpush.SendNotification(user, webpush.Payload{
|
||||
Topic: "friend",
|
||||
Title: "New Friend Request!",
|
||||
Body: fmt.Sprintf("%s wants to be your friend on %s.", currentUser.Username, config.Title),
|
||||
})
|
||||
}()
|
||||
}
|
||||
session.Flash(w, r, "Friend request sent!")
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
"code.nonshy.com/nonshy/website/pkg/webpush"
|
||||
)
|
||||
|
||||
// Compose a new chat coming from a user's profile page.
|
||||
|
@ -61,9 +64,25 @@ func Compose() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Send a push notification to the recipient.
|
||||
go func() {
|
||||
// Opted out of this one?
|
||||
if user.GetProfileField(config.PushNotificationOptOutMessage) == "true" {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Try and send Web Push notification about new Message to: %s", user.Username)
|
||||
webpush.SendNotification(user, webpush.Payload{
|
||||
Topic: "inbox",
|
||||
Title: "New Message!",
|
||||
Body: fmt.Sprintf("%s has left you a message on %s.", currentUser.Username, config.Title),
|
||||
})
|
||||
}()
|
||||
|
||||
session.Flash(w, r, "Your message has been delivered!")
|
||||
if from == "inbox" {
|
||||
templates.Redirect(w, fmt.Sprintf("/messages/read/%d", m.ID))
|
||||
return
|
||||
}
|
||||
templates.Redirect(w, "/messages")
|
||||
return
|
||||
|
|
|
@ -38,3 +38,12 @@ func Manifest() http.HandlerFunc {
|
|||
http.ServeFile(w, r, config.StaticPath+"/manifest.json")
|
||||
})
|
||||
}
|
||||
|
||||
// Service Worker for web push.
|
||||
func ServiceWorker() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/javascript; charset=UTF-8")
|
||||
w.Header().Add("Service-Worker-Allowed", "/")
|
||||
http.ServeFile(w, r, config.StaticPath+"/js/service-worker.js")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ func DeleteUser(user *models.User) error {
|
|||
{"User Notes", DeleteUserNotes},
|
||||
{"Change Logs", DeleteChangeLogs},
|
||||
{"IP Addresses", DeleteIPAddresses},
|
||||
{"Push Notifications", DeletePushNotifications},
|
||||
}
|
||||
for _, item := range todo {
|
||||
if err := item.Fn(user.ID); err != nil {
|
||||
|
@ -361,3 +362,13 @@ func DeleteIPAddresses(userID uint64) error {
|
|||
).Delete(&models.IPAddress{})
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// DeletePushNotifications scrubs data for deleting a user.
|
||||
func DeletePushNotifications(userID uint64) error {
|
||||
log.Error("DeleteUser: DeletePushNotifications(%d)", userID)
|
||||
result := models.DB.Where(
|
||||
"user_id = ?",
|
||||
userID,
|
||||
).Delete(&models.PushNotification{})
|
||||
return result.Error
|
||||
}
|
||||
|
|
|
@ -33,4 +33,5 @@ func AutoMigrate() {
|
|||
DB.AutoMigrate(&TwoFactor{})
|
||||
DB.AutoMigrate(&ChangeLog{})
|
||||
DB.AutoMigrate(&IPAddress{})
|
||||
DB.AutoMigrate(&PushNotification{})
|
||||
}
|
||||
|
|
85
pkg/models/notification_push.go
Normal file
85
pkg/models/notification_push.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
)
|
||||
|
||||
// PushNotification table for Web Push subscriptions.
|
||||
type PushNotification struct {
|
||||
ID uint64 `gorm:"primaryKey"`
|
||||
UserID uint64 `gorm:"index"`
|
||||
Subscription string `gorm:"index"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// RegisterPushNotification stores a registration for the user.
|
||||
func RegisterPushNotification(user *User, subscription string) (*PushNotification, error) {
|
||||
// Check for an existing registration.
|
||||
pn, err := GetPushNotificationFromSubscription(user, subscription)
|
||||
if err == nil {
|
||||
return pn, nil
|
||||
}
|
||||
|
||||
// Create it.
|
||||
pn = &PushNotification{
|
||||
UserID: user.ID,
|
||||
Subscription: subscription,
|
||||
}
|
||||
result := DB.Create(pn)
|
||||
return pn, result.Error
|
||||
}
|
||||
|
||||
// GetPushNotificationFromSubscription checks for an existing subscription.
|
||||
func GetPushNotificationFromSubscription(user *User, subscription string) (*PushNotification, error) {
|
||||
var (
|
||||
pn *PushNotification
|
||||
result = DB.Model(&PushNotification{}).Where(
|
||||
"user_id = ? AND subscription = ?",
|
||||
user.ID, subscription,
|
||||
).First(&pn)
|
||||
)
|
||||
return pn, result.Error
|
||||
}
|
||||
|
||||
// CountPushNotificationSubscriptions returns how many subscriptions the user has for push.
|
||||
func CountPushNotificationSubscriptions(user *User) int64 {
|
||||
var count int64
|
||||
result := DB.Where(
|
||||
"user_id = ?",
|
||||
user.ID,
|
||||
).Model(&PushNotification{}).Count(&count)
|
||||
if result.Error != nil {
|
||||
log.Error("CountPushNotificationSubscriptions(%d): %s", user.ID, result.Error)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// GetPushNotificationSubscriptions returns all subscriptions for a user.
|
||||
func GetPushNotificationSubscriptions(user *User) ([]*PushNotification, error) {
|
||||
var (
|
||||
pn = []*PushNotification{}
|
||||
result = DB.Model(&PushNotification{}).Where("user_id = ?", user.ID).Scan(&pn)
|
||||
)
|
||||
return pn, result.Error
|
||||
}
|
||||
|
||||
// DeletePushNotifications scrubs data for deleting a user.
|
||||
func DeletePushNotificationSubscriptions(user *User) error {
|
||||
result := DB.Where(
|
||||
"user_id = ?",
|
||||
user.ID,
|
||||
).Delete(&PushNotification{})
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// DeletePushNotification removes a single subscription from the database.
|
||||
func DeletePushNotification(user *User, subscription string) error {
|
||||
result := DB.Where(
|
||||
"user_id = ? AND subscription = ?",
|
||||
user.ID, subscription,
|
||||
).Delete(&PushNotification{})
|
||||
return result.Error
|
||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/controller/poll"
|
||||
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||
nst "code.nonshy.com/nonshy/website/pkg/templates"
|
||||
"code.nonshy.com/nonshy/website/pkg/webpush"
|
||||
)
|
||||
|
||||
func New() http.Handler {
|
||||
|
@ -30,6 +31,7 @@ func New() http.Handler {
|
|||
mux.HandleFunc("/", index.Create())
|
||||
mux.HandleFunc("GET /favicon.ico", index.Favicon())
|
||||
mux.HandleFunc("GET /manifest.json", index.Manifest())
|
||||
mux.HandleFunc("GET /sw.js", index.ServiceWorker())
|
||||
mux.HandleFunc("GET /about", index.StaticTemplate("about.html")())
|
||||
mux.HandleFunc("GET /features", index.StaticTemplate("features.html")())
|
||||
mux.HandleFunc("GET /faq", index.StaticTemplate("faq.html")())
|
||||
|
@ -111,6 +113,9 @@ func New() http.Handler {
|
|||
mux.HandleFunc("GET /v1/version", api.Version())
|
||||
mux.HandleFunc("GET /v1/users/me", api.LoginOK())
|
||||
mux.HandleFunc("POST /v1/users/check-username", api.UsernameCheck())
|
||||
mux.HandleFunc("GET /v1/web-push/vapid-public-key", webpush.VAPIDPublicKey)
|
||||
mux.Handle("POST /v1/web-push/register", middleware.LoginRequired(webpush.Register()))
|
||||
mux.Handle("GET /v1/web-push/unregister", middleware.LoginRequired(webpush.UnregisterAll()))
|
||||
mux.Handle("POST /v1/likes", middleware.LoginRequired(api.Likes()))
|
||||
mux.Handle("GET /v1/likes/users", middleware.LoginRequired(api.WhoLikes()))
|
||||
mux.Handle("POST /v1/notifications/read", middleware.LoginRequired(api.ReadNotification()))
|
||||
|
|
180
pkg/webpush/webpush.go
Normal file
180
pkg/webpush/webpush.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
// Package webpush provides Web Push Notification functionality.
|
||||
package webpush
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/controller/api"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
webpush "github.com/SherClockHolmes/webpush-go"
|
||||
)
|
||||
|
||||
// VAPIDPublicKey returns the site's public key as an endpoint.
|
||||
func VAPIDPublicKey(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(config.Current.WebPush.VAPIDPublicKey))
|
||||
}
|
||||
|
||||
// UnregisterAll resets a user's stored push notification subscriptions.
|
||||
func UnregisterAll() http.HandlerFunc {
|
||||
var next = "/settings#notifications"
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
currentUser, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "You must be logged in to do that!")
|
||||
templates.Redirect(w, next)
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DeletePushNotificationSubscriptions(currentUser); err != nil {
|
||||
session.FlashError(w, r, "Error removing your subscriptions: %s", err)
|
||||
} else {
|
||||
session.Flash(w, r, "Your push notification subscriptions have been reset!")
|
||||
}
|
||||
templates.Redirect(w, next)
|
||||
}
|
||||
}
|
||||
|
||||
// Register endpoint for push notification.
|
||||
func Register() http.HandlerFunc {
|
||||
type Request struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
ExpirationTime float64 `json:"expirationTime"`
|
||||
Keys struct {
|
||||
Auth string `json:"auth"`
|
||||
P256DH string `json:"p256dh"`
|
||||
} `json:"keys"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
OK bool `json:"OK"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
currentUser, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
api.SendJSON(w, http.StatusUnauthorized, Response{
|
||||
Error: "You must be logged in to do that!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request payload.
|
||||
var req Request
|
||||
if err := api.ParseJSON(r, &req); err != nil {
|
||||
api.SendJSON(w, http.StatusBadRequest, Response{
|
||||
Error: fmt.Sprintf("Error with request payload: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate it looked correct.
|
||||
if req.Endpoint == "" || req.Keys.Auth == "" || req.Keys.P256DH == "" {
|
||||
api.SendJSON(w, http.StatusBadRequest, Response{
|
||||
Error: "Subscription fields were missing.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Serialize and store it in the database.
|
||||
buf, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
api.SendJSON(w, http.StatusInternalServerError, Response{
|
||||
Error: "Couldn't reserialize your subscription!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = models.RegisterPushNotification(currentUser, string(buf))
|
||||
if err != nil {
|
||||
api.SendJSON(w, http.StatusInternalServerError, Response{
|
||||
Error: "Couldn't create the registration in the database!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
api.SendJSON(w, http.StatusCreated, Response{
|
||||
OK: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Payload sent in push notifications.
|
||||
type Payload struct {
|
||||
Topic string `json:"-"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// SendNotification sends a push notification to a user, broadcast to all of their subscriptions.
|
||||
func SendNotification(user *models.User, body Payload) error {
|
||||
// Send to all of their subscriptions.
|
||||
subs, err := models.GetPushNotificationSubscriptions(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sub := range subs {
|
||||
if err := SendRawNotification(user, payload, body.Topic, sub.Subscription); err != nil {
|
||||
log.Error("SendNotification: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNotificationToSubscription sends to a specific push subscriber.
|
||||
func SendNotificationToSubscription(user *models.User, subscription string, body Payload) error {
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SendRawNotification(user, payload, body.Topic, subscription)
|
||||
}
|
||||
|
||||
// SendRawNotification sends out a push message.
|
||||
func SendRawNotification(user *models.User, message []byte, topic, subscription string) error {
|
||||
// Decode the subscription.
|
||||
var (
|
||||
s = &webpush.Subscription{}
|
||||
err = json.Unmarshal([]byte(subscription), s)
|
||||
options = &webpush.Options{
|
||||
Topic: topic,
|
||||
Subscriber: user.Email,
|
||||
VAPIDPublicKey: config.Current.WebPush.VAPIDPublicKey,
|
||||
VAPIDPrivateKey: config.Current.WebPush.VAPIDPrivateKey,
|
||||
TTL: 30,
|
||||
}
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := webpush.SendNotification(message, s, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("webpush.SendNotification: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Handle error response codes.
|
||||
if resp.StatusCode >= 400 {
|
||||
log.Error("Got StatusCode %d when sending push notification; removing the subscription from DB", resp.StatusCode)
|
||||
models.DeletePushNotification(user, subscription)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
BIN
web/static/img/favicon-128.png
Normal file
BIN
web/static/img/favicon-128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
BIN
web/static/img/site-settings.png
Normal file
BIN
web/static/img/site-settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
19
web/static/js/service-worker.js
Normal file
19
web/static/js/service-worker.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* nonshy service worker, for web push notifications */
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
const payload = JSON.parse(event.data.text());
|
||||
try {
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(payload.title, {
|
||||
body: payload.body,
|
||||
icon: "/static/img/favicon-192.png",
|
||||
})
|
||||
);
|
||||
} catch(e) {
|
||||
console.error("sw.showNotification:", e);
|
||||
}
|
||||
});
|
45
web/static/js/web-push.js
Normal file
45
web/static/js/web-push.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/* nonshy web push notification helper */
|
||||
navigator.serviceWorker.register("/sw.js", {
|
||||
scope: "/",
|
||||
}).catch(err => {
|
||||
console.error("Service Worker NOT registered:", err);
|
||||
});
|
||||
|
||||
function PushNotificationSubscribe() {
|
||||
navigator.serviceWorker.ready.then(async function(registration) {
|
||||
return registration.pushManager.getSubscription().then(async function(subscription) {
|
||||
// If a subscription was already found, return it.
|
||||
if (subscription) {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
// Get the server's public key.
|
||||
const response = await fetch("/v1/web-push/vapid-public-key");
|
||||
const vapidPublicKey = await response.text();
|
||||
|
||||
// Subscribe the user.
|
||||
return registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: vapidPublicKey,
|
||||
});
|
||||
}).then(subscription => {
|
||||
|
||||
// Post it to the backend.
|
||||
const serialized = JSON.stringify(subscription);
|
||||
fetch("/v1/web-push/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: serialized
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If the user has already given notification permission, (re)subscribe for push.
|
||||
document.addEventListener("DOMContentLoaded", e => {
|
||||
if (Notification.permission === "granted") {
|
||||
PushNotificationSubscribe();
|
||||
}
|
||||
});
|
|
@ -930,188 +930,279 @@
|
|||
</div>
|
||||
|
||||
<!-- Notification Settings -->
|
||||
<div class="card mb-5" id="notifications">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">
|
||||
<i class="fa fa-bell pr-2"></i>
|
||||
Notification Settings
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="block">
|
||||
On this page you may opt-out of certain kinds of (on-site) notification messages.
|
||||
{{PrettyTitle}} does not send you any e-mails or push notification -- these on-site
|
||||
notifications only appear while you are visiting the website (on your
|
||||
<a href="/me">home/user dashboard page</a>).
|
||||
</p>
|
||||
|
||||
<form method="POST" action="/settings">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="intent" value="notifications">
|
||||
|
||||
<h2 class="subtitle">New Photo Uploads</h2>
|
||||
<div id="notifications">
|
||||
<div class="card mb-5">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">
|
||||
<i class="fa fa-bell pr-2"></i>
|
||||
Web Push Notifications <span class="tag is-success ml-2">New!</span>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="block">
|
||||
By default you will be notified when your friends upload a new picture to the site.
|
||||
Below, you may opt-out of new photo upload notifications.
|
||||
You may opt-in to receive Web Push Notifications for some of your important updates
|
||||
from {{PrettyTitle}}, such as when you receive a new Direct Message on the main website,
|
||||
even when you have closed your browser.
|
||||
</p>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_friends_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_friends_photos") "true"}}checked{{end}}>
|
||||
My friends upload a new photo
|
||||
</label>
|
||||
<p class="help">
|
||||
If unchecked, the following two notifications will not be sent either.
|
||||
<p class="block">
|
||||
<strong>Push Notification Permission:</strong>
|
||||
<strong class="has-text-success" id="push-status-enabled" style="display: none">
|
||||
<i class="fa fa-check mr-1"></i> Granted
|
||||
</strong>
|
||||
<strong class="has-text-danger" id="push-status-disabled" style="display: none">
|
||||
<i class="fa fa-xmark mr-1"></i> Denied
|
||||
</strong>
|
||||
<strong class="has-text-warning" id="push-status-default" style="display: none">
|
||||
<i class="fa fa-xmark mr-1"></i> Not Granted
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<p class="block">
|
||||
<!-- Button to Grant Permission or Test Notifications -->
|
||||
<a href="#" id="grant-push-permission"
|
||||
class="button is-small is-success">
|
||||
Test Notifications
|
||||
</a>
|
||||
|
||||
<!-- Help error if the permission is denied -->
|
||||
<span id="push-denied-help" style="display: none">
|
||||
<i class="fa fa-info-circle has-text-warning mr-1"></i>
|
||||
You had denied notification permission to this site. Please check in your web browser's
|
||||
settings (or click in your address bar to the left of the website URL) to reset your
|
||||
permission setting. Please <a href="/faq#troubleshoot-web-push">see this page</a> for
|
||||
help in case you want to resolve this.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<!-- Have existing subscriptions? -->
|
||||
{{if .PushNotificationsCount}}
|
||||
<p class="block">
|
||||
<strong>Sessions:</strong> you have enabled push notifications on {{.PushNotificationsCount}} web browser{{Pluralize64 .PushNotificationsCount}}. You may
|
||||
<a href="/v1/web-push/unregister">click here</a> to reset your subscriptions. Devices that you actively use
|
||||
(and had granted permission on before) may re-subscribe on your next visit.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<!-- Specific Push Notification Opt-out Form -->
|
||||
<form method="POST" action="/settings">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="intent" value="push_notifications">
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Send a Web Push Notification when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_push_messages"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_push_messages") "true"}}checked{{end}}>
|
||||
I receive a Direct Message on the main website
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_push_friends"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_push_friends") "true"}}checked{{end}}>
|
||||
I receive a new Friend Request
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button type="submit" class="button is-primary">
|
||||
<i class="fa fa-save mr-2"></i> Save Push Notification Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-5">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">
|
||||
<i class="fa fa-bell pr-2"></i>
|
||||
Notification Settings
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="block">
|
||||
On this page you may opt-out of certain kinds of (on-site) notification messages.
|
||||
{{PrettyTitle}} does not send you any e-mails or push notification -- these on-site
|
||||
notifications only appear while you are visiting the website (on your
|
||||
<a href="/me">home/user dashboard page</a>).
|
||||
</p>
|
||||
|
||||
<form method="POST" action="/settings">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="intent" value="notifications">
|
||||
|
||||
<h2 class="subtitle">New Photo Uploads</h2>
|
||||
|
||||
<p class="block">
|
||||
By default you will be notified when your friends upload a new picture to the site.
|
||||
Below, you may opt-out of new photo upload notifications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_private_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_photos") "true"}}checked{{end}}>
|
||||
A friend who shared their private photos with me uploads a new private photo
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_friends_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_friends_photos") "true"}}checked{{end}}>
|
||||
My friends upload a new photo
|
||||
</label>
|
||||
<p class="help">
|
||||
If unchecked, the following two notifications will not be sent either.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_explicit_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_explicit_photos") "true"}}checked{{end}}>
|
||||
Allow notifications for 'explicit' photo uploads by my friends
|
||||
</label>
|
||||
<p class="help">
|
||||
This will also depend on your <a href="/settings#prefs" target="_blank">opt-in to see explicit content</a> -- otherwise
|
||||
notifications about explicit photo uploads will not be sent to you.
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_private_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_photos") "true"}}checked{{end}}>
|
||||
A friend who shared their private photos with me uploads a new private photo
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_explicit_photos"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_explicit_photos") "true"}}checked{{end}}>
|
||||
Allow notifications for 'explicit' photo uploads by my friends
|
||||
</label>
|
||||
<p class="help">
|
||||
This will also depend on your <a href="/settings#prefs" target="_blank">opt-in to see explicit content</a> -- otherwise
|
||||
notifications about explicit photo uploads will not be sent to you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle mt-5">Likes & Comments</h2>
|
||||
|
||||
<p class="block">
|
||||
By default you will be notified when somebody 'likes' or comments on your profile page
|
||||
or photos. You may turn off those notifications with the options below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle mt-5">Likes & Comments</h2>
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_likes"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_likes") "true"}}checked{{end}}>
|
||||
Somebody 'likes' my profile page, photos, or comments
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="block">
|
||||
By default you will be notified when somebody 'likes' or comments on your profile page
|
||||
or photos. You may turn off those notifications with the options below.
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_comments"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_comments") "true"}}checked{{end}}>
|
||||
Somebody leaves a comment on one of my photos
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_likes"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_likes") "true"}}checked{{end}}>
|
||||
Somebody 'likes' my profile page, photos, or comments
|
||||
</label>
|
||||
</div>
|
||||
<h2 class="subtitle mt-5">Comment Thread Subscriptions</h2>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_comments"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_comments") "true"}}checked{{end}}>
|
||||
Somebody leaves a comment on one of my photos
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle mt-5">Comment Thread Subscriptions</h2>
|
||||
|
||||
<p class="block">
|
||||
Comment threads and forum posts may be 'subscribed' to so that you can be notified about
|
||||
comments left by other people after you. By default, you will subscribe to comment threads
|
||||
after you leave your first comment.
|
||||
</p>
|
||||
|
||||
<p class="block">
|
||||
<strong>Note:</strong> you may unsubscribe from comment threads by using the link at the
|
||||
top of its page (for example: at the top of a forum thread page or the top of the list of
|
||||
comments on a photo page). You may also opt <em>in</em> to get notifications on a thread
|
||||
that you didn't comment on by using the same link at the top of their pages.
|
||||
</p>
|
||||
|
||||
<p class="block">
|
||||
The options below can control the automatic opt-in for subscriptions when you leave a
|
||||
comment on a new comment thread.
|
||||
</p>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_subscriptions"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_subscriptions") "true"}}checked{{end}}>
|
||||
Subscribe to notifications for future comments when I leave a comment on something
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Unsubscribe from Comment Threads</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="unsubscribe_all_threads"
|
||||
value="true">
|
||||
Unsubscribe NOW from <strong>all ({{.SubscriptionCount}}) comment threads</strong> that I am currently following.
|
||||
</label>
|
||||
<p class="help">
|
||||
You are currently subscribed to <strong>{{.SubscriptionCount}}</strong> comment thread{{Pluralize64 .SubscriptionCount}}.
|
||||
You may immediately unsubscribe from all of these threads by checking this box and clicking "Save" below.
|
||||
<p class="block">
|
||||
Comment threads and forum posts may be 'subscribed' to so that you can be notified about
|
||||
comments left by other people after you. By default, you will subscribe to comment threads
|
||||
after you leave your first comment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle mt-5">Miscellaneous</h2>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_friend_request_accepted"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_friend_request_accepted") "true"}}checked{{end}}>
|
||||
Somebody approves my friendship request
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_private_grant"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_grant") "true"}}checked{{end}}>
|
||||
Somebody unlocks their private photos for me to see
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Read-only box for certification photo response -->
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
value="true"
|
||||
checked
|
||||
disabled>
|
||||
My certification photo is approved or rejected
|
||||
</label>
|
||||
<p class="help">
|
||||
This notification is important for your account status, is rarely sent out, and can
|
||||
not be opted-out from.
|
||||
<p class="block">
|
||||
<strong>Note:</strong> you may unsubscribe from comment threads by using the link at the
|
||||
top of its page (for example: at the top of a forum thread page or the top of the list of
|
||||
comments on a photo page). You may also opt <em>in</em> to get notifications on a thread
|
||||
that you didn't comment on by using the same link at the top of their pages.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button type="submit" class="button is-primary">
|
||||
<i class="fa fa-save mr-2"></i> Save Privacy Settings
|
||||
</button>
|
||||
</div>
|
||||
<p class="block">
|
||||
The options below can control the automatic opt-in for subscriptions when you leave a
|
||||
comment on a new comment thread.
|
||||
</p>
|
||||
|
||||
</form>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_subscriptions"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_subscriptions") "true"}}checked{{end}}>
|
||||
Subscribe to notifications for future comments when I leave a comment on something
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Unsubscribe from Comment Threads</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="unsubscribe_all_threads"
|
||||
value="true">
|
||||
Unsubscribe NOW from <strong>all ({{.SubscriptionCount}}) comment threads</strong> that I am currently following.
|
||||
</label>
|
||||
<p class="help">
|
||||
You are currently subscribed to <strong>{{.SubscriptionCount}}</strong> comment thread{{Pluralize64 .SubscriptionCount}}.
|
||||
You may immediately unsubscribe from all of these threads by checking this box and clicking "Save" below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle mt-5">Miscellaneous</h2>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Notify me when...</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_friend_request_accepted"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_friend_request_accepted") "true"}}checked{{end}}>
|
||||
Somebody approves my friendship request
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="notif_optout_private_grant"
|
||||
value="true"
|
||||
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_grant") "true"}}checked{{end}}>
|
||||
Somebody unlocks their private photos for me to see
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Read-only box for certification photo response -->
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
value="true"
|
||||
checked
|
||||
disabled>
|
||||
My certification photo is approved or rejected
|
||||
</label>
|
||||
<p class="help">
|
||||
This notification is important for your account status, is rarely sent out, and can
|
||||
not be opted-out from.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button type="submit" class="button is-primary">
|
||||
<i class="fa fa-save mr-2"></i> Save Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1143,8 +1234,8 @@
|
|||
<p>
|
||||
Your Two-Factor is currently:
|
||||
{{if .TwoFactorEnabled}}
|
||||
<i class="fa fa-check mr-1 has-text-success-dark"></i>
|
||||
<strong class="has-text-success-dark">Enabled</strong>
|
||||
<i class="fa fa-check mr-1 has-text-success"></i>
|
||||
<strong class="has-text-success">Enabled</strong>
|
||||
{{else}}
|
||||
<i class="fa fa-xmark mr-1 has-text-danger"></i>
|
||||
<strong class="has-text-danger">Not Enabled</strong>
|
||||
|
@ -1462,6 +1553,59 @@ window.addEventListener("DOMContentLoaded", (event) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Notifications tab scripts.
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
// Get useful controls from the tab.
|
||||
const $pushStatusGranted = document.querySelector("#push-status-enabled"),
|
||||
$pushStatusDenied = document.querySelector("#push-status-disabled"),
|
||||
$pushStatusDefault = document.querySelector("#push-status-default"),
|
||||
$pushEnableButton = document.querySelector("#grant-push-permission"),
|
||||
$pushDeniedHelp = document.querySelector("#push-denied-help");
|
||||
|
||||
// Get the current permission status: default, granted, denied.
|
||||
const showPermission = (permission) => {
|
||||
$pushStatusGranted.style.display = "none";
|
||||
$pushStatusDenied.style.display = "none";
|
||||
$pushStatusDefault.style.display = "none";
|
||||
switch (permission) {
|
||||
case "granted":
|
||||
$pushStatusGranted.style.display = "";
|
||||
$pushEnableButton.innerHTML = "Test Push Notification";
|
||||
break;
|
||||
case "denied":
|
||||
$pushEnableButton.style.display = "none";
|
||||
$pushDeniedHelp.style.display = "";
|
||||
$pushStatusDenied.style.display = "";
|
||||
break;
|
||||
default:
|
||||
$pushStatusDefault.style.display = "";
|
||||
$pushEnableButton.innerHTML = "Grant Push Notification Permission";
|
||||
break;
|
||||
}
|
||||
};
|
||||
showPermission(Notification.permission);
|
||||
|
||||
$pushEnableButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
Notification.requestPermission().then(permission => {
|
||||
// Update the displayed permission status.
|
||||
showPermission(Notification.permission);
|
||||
|
||||
// If granted, subscribe to push notifications now.
|
||||
if (permission === "granted") {
|
||||
// In static/js/web-push.js
|
||||
PushNotificationSubscribe();
|
||||
|
||||
// Test the notification now.
|
||||
const notification = new Notification(`Hello from ${document.location.hostname}!`, {
|
||||
body: "This is an example notification from this site.",
|
||||
icon: "/static/img/favicon-192.png",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Location tab scripts.
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
// Get useful controls from the tab.
|
||||
|
|
|
@ -387,6 +387,7 @@
|
|||
<script type="text/javascript" src="/static/js/vue-3.2.45.js"></script>
|
||||
<script type="text/javascript" src="/static/js/htmx-1.9.12.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/slim-forms.js?build={{.BuildHash}}"></script>
|
||||
<script type="text/javascript" src="/static/js/web-push.js?build={{.BuildHash}}"></script>
|
||||
{{template "scripts" .}}
|
||||
|
||||
<!-- Likes modal -->
|
||||
|
|
|
@ -70,10 +70,19 @@
|
|||
<ul>
|
||||
<li><a href="#chat-access">Who can access the chat rooms?</a></li>
|
||||
<li><a href="#chat-support">What are the technical requirements to use the chat room?</a></li>
|
||||
<li><a href="#webcam-support">I can't share or connect to other peoples' webcams</a></li>
|
||||
<li><a href="#webcam-support">I am experiencing a problem with <strong>webcam sharing</strong></a></li>
|
||||
<li><a href="#chat-more">Where can I learn more about the chat room?</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#notification-faqs">Notification FAQs</a>
|
||||
<ul>
|
||||
<li><a href="#notifications">Does nonshy send me notifications?</a></li>
|
||||
<li><a href="#web-push">About <strong>Web Push Notifications</strong></a></li>
|
||||
<li><a href="#cancel-web-push">How do I turn off Web Push Notifications?</a></li>
|
||||
<li><a href="#troubleshoot-web-push">Troubleshooting Web Push Notifications</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#shy-faqs">Shy Account FAQs</a>
|
||||
<ul>
|
||||
|
@ -908,56 +917,75 @@
|
|||
<h3 id="chat-support">What are the technical requirements to use the chat room?</h3>
|
||||
|
||||
<p>
|
||||
The chat room seems to work the best on the following combination of devices and
|
||||
web browsers:
|
||||
The chat room should generally work well on all major web browsers, operating systems
|
||||
and device types. Recommended browsers include Mozilla Firefox, Google Chrome (or any
|
||||
other Chromium-based browser of your choice, such as Microsoft Edge, Opera or Brave),
|
||||
or Apple's Safari browser. Most Androids, iPads and iPhones should be able to use the
|
||||
chat room successfully, including webcam support.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Firefox</strong> and <strong>Chromium</strong>-based web browsers
|
||||
on <strong>all desktop-like operating systems</strong> including Windows, Mac OS
|
||||
and Linux. Chromium-based browsers include Google Chrome, Microsoft Edge, Opera,
|
||||
Brave or others that are based on the open source Chromium browser.
|
||||
</li>
|
||||
<li>
|
||||
On <strong>Android</strong> devices, all <strong>Firefox</strong> and <strong>Chromium-based</strong>
|
||||
browsers are generally working quite well.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The chat room and video sharing generally works well on the above devices. Below
|
||||
are some that are known to have issues with the chat room at this time:
|
||||
<strong>When opening many webcams:</strong> the number of cameras you can watch at a
|
||||
time is mainly limited by your device's hardware specifications and your local network
|
||||
bandwidth. The chat room doesn't enforce an arbitrary limit to the number of cameras,
|
||||
so you can experiment and find out how many your device can support.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Safari on Mac OS X</strong> usually works for the text chat portions
|
||||
(entering the room and chatting), but webcam sharing doesn't seem to work (either
|
||||
broadcasting or viewing others' cameras).
|
||||
</li>
|
||||
<li>
|
||||
<strong>iPhone and iPad</strong> (all web browsers) have difficulty logging in to
|
||||
the chat room at all. Chrome or Firefox do not work on iOS, either - because under
|
||||
the hood all web browsers on iOS are just custom wrappers around Mobile Safari,
|
||||
which doesn't like my chat room right now. So unfortunately, Apple mobile devices
|
||||
can not use the chat room.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
For some examples: a Macbook Air M3 laptop from 2024 is able to comfortably open more
|
||||
than 20 webcams at a time. However, a Dell XPS 13 laptop from 2018 (which had 16GB RAM,
|
||||
an nVIDIA graphics card, etc.) was seen to only be able to open 10 or 15 cameras before
|
||||
the laptop became very warm.
|
||||
</p>
|
||||
|
||||
<h3 id="webcam-support">I can't share or connect to other peoples' webcams</h3>
|
||||
<h3 id="webcam-support">I am experiencing a problem with webcam sharing</h3>
|
||||
|
||||
<p>
|
||||
First, verify that you're using a <a href="#chat-support">known supported device</a>
|
||||
Please see the <strong><a href="/forum/thread/309">Webcam Troubleshooting</a></strong>
|
||||
thread in the forums for some advice on things to try.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Verify that you're using a <a href="#chat-support">known supported device</a>
|
||||
when accessing the chat room. Generally this means you're running a Firefox or
|
||||
Chromium-based browser on a desktop computer, laptop, or Android device. Webcam
|
||||
sharing does not work <strong>at all</strong> in Safari on Mac OS, and portable
|
||||
Apple devices such as iPad and iPhone do not work with the chat room at all.
|
||||
Chromium-based browser on a desktop computer, laptop, or Android device. The Apple
|
||||
Safari browser should also work from a Mac, iPhone or iPad computer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The most common type of error message people encounter on chat looks like:
|
||||
<strong>NotAllowedError: Permission denied.</strong> This error message usually has
|
||||
one of three causes:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Your web browser has denied permission to chat.nonshy.com, and you should check in
|
||||
your web browser's settings (Privacy & Security section) for Webcam and Microphone
|
||||
and remove chat.nonshy.com from the list of sites. After doing so, restart your browser
|
||||
and log onto the chat room, and be sure to click "Allow" when it asks for permission
|
||||
when going on webcam.
|
||||
</li>
|
||||
<li>
|
||||
Sometimes, your operating system itself is actually denying permission to your web
|
||||
browser. You can check in your System Settings for App Permissions for your webcam
|
||||
and microphone, and ensure that your web browser has this permission on your device.
|
||||
</li>
|
||||
<li>
|
||||
Sometimes this error comes up when your webcam is already in use by another
|
||||
application (such as Skype or Discord). Ensure that there is no other app currently
|
||||
using your webcam before you go on camera in the chat room.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
The linked Webcam Troubleshooting thread above has instructions for common web browsers
|
||||
and operating systems on where to check for permission errors.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are on a supported device, check out the following information about how
|
||||
webcam sharing works:
|
||||
webcam sharing works in general:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
|
@ -1017,15 +1045,6 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
If all else fails, it's unfortunate but at least the text chat functions should work at least.
|
||||
Some big video apps like Zoom or Jitsi Meet tend to work better because, if you can't establish
|
||||
a peer-to-peer connection, they will fall back on using a proxy server to transmit your video
|
||||
through to the other party. The {{PrettyTitle}} chat room does not have such a proxy server --
|
||||
because the bandwidth can get expensive to carry video across! Only peer-to-peer video sharing
|
||||
is supported at this time.
|
||||
</p>
|
||||
|
||||
<h3 id="chat-more">Where can I learn more about the chat room?</h3>
|
||||
|
||||
<p>
|
||||
|
@ -1033,6 +1052,85 @@
|
|||
a tour of the chat room interface and some additional information about how to use the chat room.
|
||||
</p>
|
||||
|
||||
<h1 id="notification-faqs">Notification FAQs</h1>
|
||||
|
||||
<h3 id="notifications">Does nonshy send me notifications?</h3>
|
||||
|
||||
<p>
|
||||
Most {{PrettyTitle}} notifications are "on-site only" by default, meaning you need to log onto
|
||||
the website to see them. We send <em>very</em> few e-mails from this website, ever: only for
|
||||
your new account verification e-mail, certification photo approval, and when you forgot your password.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The on-site notifications include things like when your friends upload a new picture, or when somebody
|
||||
comments on or likes something you posted. You can manage your {{PrettyTitle}} notifications on your
|
||||
<a href="/settings#notifications">Notification Settings</a> page, to opt in or out of any of these.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We have optional <a href="#web-push">Web Push Notifications</a> that you may enable so you can
|
||||
know when somebody has left you a message or a friend request on the website.
|
||||
</p>
|
||||
|
||||
<h3 id="web-push">About Web Push Notifications</h3>
|
||||
|
||||
<p>
|
||||
If you would like to enable timely notifications when you receive a new Message or Friend Request
|
||||
on the website, you may enable <a href="/settings#notifications">Web Push Notifications</a> in your
|
||||
settings.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Currently, only a small subset of the site notifications can be sent via push notification: when you
|
||||
get a new Direct Message or Friend Request. You may opt either of those out, in case you only care to
|
||||
be notified about messages but not friend requests.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All your other notifications (likes, comments, etc.) are still "on-site only" so you will need to log in
|
||||
and check your <a href="/me">Dashboard Page</a> to catch up on those.
|
||||
</p>
|
||||
|
||||
<h3 id="cancel-web-push">How do I turn off Web Push Notifications?</h3>
|
||||
|
||||
<p>
|
||||
The easiest way to turn these off is to revoke your Notifications permission given to the nonshy website.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
On Firefox, Chrome and Chrome-like web browsers: in your URL address bar there should be a button to the
|
||||
left of address which you can click on and see permissions for the website, where the "Notification" permission
|
||||
can be easily revoked.
|
||||
</p>
|
||||
|
||||
<img src="/static/img/site-settings.png" alt="A screenshot of the Google Chrome settings drop-down near the address bar." style="padding: 2px; border: 1px solid #666; background-color: #aaa">
|
||||
|
||||
<p>
|
||||
On other web browsers (or on mobile) you may need to go into your browser's settings. Under a section for
|
||||
"Websites" (maybe under "Privacy & Security"), find the {{PrettyTitle}} website or the Notifications permission
|
||||
and you can change your setting there.
|
||||
</p>
|
||||
|
||||
<h3 id="troubleshoot-web-push">Troubleshooting Web Push Notifications</h3>
|
||||
|
||||
<p>
|
||||
When you first enable Web Push Notifications, your web browser should have prompted you for permission. In case
|
||||
you have clicked "Deny" or "Never Allow," your web browser remembers your decision and the website is not allowed
|
||||
to ask again.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When this happens, the <a href="/settings#notifications">Notification Settings</a> page will say that you have "Denied"
|
||||
notification permission.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In case you want to undo this: you will need to change or reset your Notification permission for this website. Please
|
||||
see the <a href="#cancel-web-push">previous answer</a> for places to look. After you have reset your Notification permission,
|
||||
refresh the <a href="/settings#notifications">Notification Settings</a> page and try enabling notifications again.
|
||||
</p>
|
||||
|
||||
<h1 id="shy-faqs"><i class="fa fa-ghost"></i> Shy Account FAQs</h1>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -207,6 +207,13 @@
|
|||
other column to read the conversation here.
|
||||
</p>
|
||||
|
||||
<!-- Push Notifications banner -->
|
||||
<div class="notification is-success is-light" id="push-notification-tip" style="display: none">
|
||||
<i class="fa fa-gift mr-1"></i>
|
||||
<strong>New Feature:</strong> You can now enable <a href="/settings#notifications">Web Push Notifications</a>
|
||||
so you can be notified that a new message was received on {{PrettyTitle}}, even if your web browser is not running.
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<i class="fa fa-info-circle has-text-success"></i>
|
||||
<strong class="has-text-success">Pro Tip:</strong>
|
||||
|
@ -335,3 +342,14 @@
|
|||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "scripts"}}
|
||||
<script type="text/javascript">
|
||||
// Push Notification tip.
|
||||
document.addEventListener("DOMContentLoaded", (e) => {
|
||||
const permission = Notification.permission;
|
||||
if (permission !== "granted") {
|
||||
document.querySelector("#push-notification-tip").style.display = "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
Loading…
Reference in New Issue
Block a user