blerp
parent
2e1ddc9ae1
commit
92bf7ebc52
|
@ -76,14 +76,15 @@ type UserPrefs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSubscription struct {
|
type UserSubscription struct {
|
||||||
|
ID string `json:"id"`
|
||||||
BaseURL string `json:"base_url"`
|
BaseURL string `json:"base_url"`
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserNotificationPrefs struct {
|
type UserNotificationPrefs struct {
|
||||||
Sound string `json:"sound"`
|
Sound string `json:"sound,omitempty"`
|
||||||
MinPriority string `json:"min_priority"`
|
MinPriority int `json:"min_priority,omitempty"`
|
||||||
DeleteAfter int `json:"delete_after"`
|
DeleteAfter int `json:"delete_after,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grant is a struct that represents an access control entry to a topic
|
// Grant is a struct that represents an access control entry to a topic
|
||||||
|
|
124
server/server.go
124
server/server.go
|
@ -40,6 +40,7 @@ import (
|
||||||
auto-refresh tokens from UI
|
auto-refresh tokens from UI
|
||||||
pricing page
|
pricing page
|
||||||
home page
|
home page
|
||||||
|
reserve topics
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,16 +81,18 @@ var (
|
||||||
authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
|
authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
|
||||||
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
||||||
|
|
||||||
webConfigPath = "/config.js"
|
webConfigPath = "/config.js"
|
||||||
userStatsPath = "/user/stats" // FIXME get rid of this in favor of /user/account
|
userStatsPath = "/user/stats" // FIXME get rid of this in favor of /user/account
|
||||||
userTokenPath = "/user/token"
|
userTokenPath = "/user/token"
|
||||||
userAccountPath = "/user/account"
|
userAccountPath = "/user/account"
|
||||||
matrixPushPath = "/_matrix/push/v1/notify"
|
userSubscriptionPath = "/user/subscription"
|
||||||
staticRegex = regexp.MustCompile(`^/static/.+`)
|
userSubscriptionDeleteRegex = regexp.MustCompile(`^/user/subscription/([-_A-Za-z0-9]{16})$`)
|
||||||
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
|
matrixPushPath = "/_matrix/push/v1/notify"
|
||||||
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
|
staticRegex = regexp.MustCompile(`^/static/.+`)
|
||||||
disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
|
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
|
||||||
urlRegex = regexp.MustCompile(`^https?://`)
|
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
|
||||||
|
disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
|
||||||
|
urlRegex = regexp.MustCompile(`^https?://`)
|
||||||
|
|
||||||
//go:embed site
|
//go:embed site
|
||||||
webFs embed.FS
|
webFs embed.FS
|
||||||
|
@ -325,6 +328,10 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
||||||
return s.handleUserAccount(w, r, v)
|
return s.handleUserAccount(w, r, v)
|
||||||
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == userAccountPath {
|
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == userAccountPath {
|
||||||
return s.handleUserAccountUpdate(w, r, v)
|
return s.handleUserAccountUpdate(w, r, v)
|
||||||
|
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == userSubscriptionPath {
|
||||||
|
return s.handleUserSubscriptionAdd(w, r, v)
|
||||||
|
} else if r.Method == http.MethodDelete && userSubscriptionDeleteRegex.MatchString(r.URL.Path) {
|
||||||
|
return s.handleUserSubscriptionDelete(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == matrixPushPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == matrixPushPath {
|
||||||
return s.handleMatrixDiscovery(w)
|
return s.handleMatrixDiscovery(w)
|
||||||
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
|
||||||
|
@ -461,10 +468,12 @@ type userPlanResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type userAccountResponse struct {
|
type userAccountResponse struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role string `json:"role,omitempty"`
|
Role string `json:"role,omitempty"`
|
||||||
Plan *userPlanResponse `json:"plan,omitempty"`
|
Plan *userPlanResponse `json:"plan,omitempty"`
|
||||||
Settings *auth.UserPrefs `json:"settings,omitempty"`
|
Language string `json:"language,omitempty"`
|
||||||
|
Notification *auth.UserNotificationPrefs `json:"notification,omitempty"`
|
||||||
|
Subscriptions []*auth.UserSubscription `json:"subscriptions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleUserAccount(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
func (s *Server) handleUserAccount(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
@ -474,7 +483,17 @@ func (s *Server) handleUserAccount(w http.ResponseWriter, r *http.Request, v *vi
|
||||||
if v.user != nil {
|
if v.user != nil {
|
||||||
response.Username = v.user.Name
|
response.Username = v.user.Name
|
||||||
response.Role = string(v.user.Role)
|
response.Role = string(v.user.Role)
|
||||||
response.Settings = v.user.Prefs
|
if v.user.Prefs != nil {
|
||||||
|
if v.user.Prefs.Language != "" {
|
||||||
|
response.Language = v.user.Prefs.Language
|
||||||
|
}
|
||||||
|
if v.user.Prefs.Notification != nil {
|
||||||
|
response.Notification = v.user.Prefs.Notification
|
||||||
|
}
|
||||||
|
if v.user.Prefs.Subscriptions != nil {
|
||||||
|
response.Subscriptions = v.user.Prefs.Subscriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
response = &userAccountResponse{
|
response = &userAccountResponse{
|
||||||
Username: auth.Everyone,
|
Username: auth.Everyone,
|
||||||
|
@ -516,12 +535,83 @@ func (s *Server) handleUserAccountUpdate(w http.ResponseWriter, r *http.Request,
|
||||||
if newPrefs.Notification.DeleteAfter > 0 {
|
if newPrefs.Notification.DeleteAfter > 0 {
|
||||||
prefs.Notification.DeleteAfter = newPrefs.Notification.DeleteAfter
|
prefs.Notification.DeleteAfter = newPrefs.Notification.DeleteAfter
|
||||||
}
|
}
|
||||||
// ...
|
if newPrefs.Notification.Sound != "" {
|
||||||
|
prefs.Notification.Sound = newPrefs.Notification.Sound
|
||||||
|
}
|
||||||
|
if newPrefs.Notification.MinPriority > 0 {
|
||||||
|
prefs.Notification.MinPriority = newPrefs.Notification.MinPriority
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// ...
|
|
||||||
return s.auth.ChangeSettings(v.user)
|
return s.auth.ChangeSettings(v.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleUserSubscriptionAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
if v.user == nil {
|
||||||
|
return errors.New("no user")
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
|
||||||
|
body, err := util.Peek(r.Body, 4096) // FIXME
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
var newSubscription auth.UserSubscription
|
||||||
|
if err := json.NewDecoder(body).Decode(&newSubscription); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v.user.Prefs == nil {
|
||||||
|
v.user.Prefs = &auth.UserPrefs{}
|
||||||
|
}
|
||||||
|
newSubscription.ID = "" // Client cannot set ID
|
||||||
|
for _, subscription := range v.user.Prefs.Subscriptions {
|
||||||
|
if newSubscription.BaseURL == subscription.BaseURL && newSubscription.Topic == subscription.Topic {
|
||||||
|
newSubscription = *subscription
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newSubscription.ID == "" {
|
||||||
|
newSubscription.ID = util.RandomString(16)
|
||||||
|
v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, &newSubscription)
|
||||||
|
if err := s.auth.ChangeSettings(v.user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(newSubscription); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleUserSubscriptionDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
if v.user == nil {
|
||||||
|
return errors.New("no user")
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
|
||||||
|
matches := userSubscriptionDeleteRegex.FindStringSubmatch(r.URL.Path)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return errHTTPInternalErrorInvalidFilePath // FIXME
|
||||||
|
}
|
||||||
|
subscriptionID := matches[1]
|
||||||
|
if v.user.Prefs == nil || v.user.Prefs.Subscriptions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
newSubscriptions := make([]*auth.UserSubscription, 0)
|
||||||
|
for _, subscription := range v.user.Prefs.Subscriptions {
|
||||||
|
if subscription.ID != subscriptionID {
|
||||||
|
newSubscriptions = append(newSubscriptions, subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newSubscriptions) < len(v.user.Prefs.Subscriptions) {
|
||||||
|
v.user.Prefs.Subscriptions = newSubscriptions
|
||||||
|
if err := s.auth.ChangeSettings(v.user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
r.URL.Path = webSiteDir + r.URL.Path
|
r.URL.Path = webSiteDir + r.URL.Path
|
||||||
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
topicUrlJsonPollWithSince,
|
topicUrlJsonPollWithSince,
|
||||||
userAccountUrl,
|
userAccountUrl,
|
||||||
userTokenUrl,
|
userTokenUrl,
|
||||||
userStatsUrl
|
userStatsUrl, userSubscriptionUrl, userSubscriptionDeleteUrl
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import userManager from "./UserManager";
|
import userManager from "./UserManager";
|
||||||
|
|
||||||
|
@ -186,6 +186,35 @@ class Api {
|
||||||
throw new Error(`Unexpected server response ${response.status}`);
|
throw new Error(`Unexpected server response ${response.status}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async userSubscriptionAdd(baseUrl, token, payload) {
|
||||||
|
const url = userSubscriptionUrl(baseUrl);
|
||||||
|
const body = JSON.stringify(payload);
|
||||||
|
console.log(`[Api] Adding user subscription ${url}: ${body}`);
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: maybeWithBearerAuth({}, token),
|
||||||
|
body: body
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Unexpected server response ${response.status}`);
|
||||||
|
}
|
||||||
|
const subscription = await response.json();
|
||||||
|
console.log(`[Api] Subscription`, subscription);
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
async userSubscriptionDelete(baseUrl, token, remoteId) {
|
||||||
|
const url = userSubscriptionDeleteUrl(baseUrl, remoteId);
|
||||||
|
console.log(`[Api] Removing user subscription ${url}`);
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: maybeWithBearerAuth({}, token)
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Unexpected server response ${response.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = new Api();
|
const api = new Api();
|
||||||
|
|
|
@ -18,17 +18,43 @@ class SubscriptionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(baseUrl, topic) {
|
async add(baseUrl, topic) {
|
||||||
|
const id = topicUrl(baseUrl, topic);
|
||||||
|
const existingSubscription = await this.get(id);
|
||||||
|
if (existingSubscription) {
|
||||||
|
return existingSubscription;
|
||||||
|
}
|
||||||
const subscription = {
|
const subscription = {
|
||||||
id: topicUrl(baseUrl, topic),
|
id: topicUrl(baseUrl, topic),
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
mutedUntil: 0,
|
mutedUntil: 0,
|
||||||
last: null
|
last: null,
|
||||||
|
remoteId: null
|
||||||
};
|
};
|
||||||
await db.subscriptions.put(subscription);
|
await db.subscriptions.put(subscription);
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncFromRemote(remoteSubscriptions) {
|
||||||
|
// Add remote subscriptions
|
||||||
|
let remoteIds = [];
|
||||||
|
for (let i = 0; i < remoteSubscriptions.length; i++) {
|
||||||
|
const remote = remoteSubscriptions[i];
|
||||||
|
const local = await this.add(remote.base_url, remote.topic);
|
||||||
|
await this.setRemoteId(local.id, remote.id);
|
||||||
|
remoteIds.push(remote.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove local subscriptions that do not exist remotely
|
||||||
|
const localSubscriptions = await db.subscriptions.toArray();
|
||||||
|
for (let i = 0; i < localSubscriptions.length; i++) {
|
||||||
|
const local = localSubscriptions[i];
|
||||||
|
if (local.remoteId && !remoteIds.includes(local.remoteId)) {
|
||||||
|
await this.remove(local.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateState(subscriptionId, state) {
|
async updateState(subscriptionId, state) {
|
||||||
db.subscriptions.update(subscriptionId, { state: state });
|
db.subscriptions.update(subscriptionId, { state: state });
|
||||||
}
|
}
|
||||||
|
@ -139,6 +165,12 @@ class SubscriptionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setRemoteId(subscriptionId, remoteId) {
|
||||||
|
await db.subscriptions.update(subscriptionId, {
|
||||||
|
remoteId: remoteId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async pruneNotifications(thresholdTimestamp) {
|
async pruneNotifications(thresholdTimestamp) {
|
||||||
await db.notifications
|
await db.notifications
|
||||||
.where("time").below(thresholdTimestamp)
|
.where("time").below(thresholdTimestamp)
|
||||||
|
|
|
@ -21,6 +21,8 @@ export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topi
|
||||||
export const userStatsUrl = (baseUrl) => `${baseUrl}/user/stats`;
|
export const userStatsUrl = (baseUrl) => `${baseUrl}/user/stats`;
|
||||||
export const userTokenUrl = (baseUrl) => `${baseUrl}/user/token`;
|
export const userTokenUrl = (baseUrl) => `${baseUrl}/user/token`;
|
||||||
export const userAccountUrl = (baseUrl) => `${baseUrl}/user/account`;
|
export const userAccountUrl = (baseUrl) => `${baseUrl}/user/account`;
|
||||||
|
export const userSubscriptionUrl = (baseUrl) => `${baseUrl}/user/subscription`;
|
||||||
|
export const userSubscriptionDeleteUrl = (baseUrl, id) => `${baseUrl}/user/subscription/${id}`;
|
||||||
export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
|
export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
|
||||||
export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
||||||
export const expandSecureUrl = (url) => `https://${url}`;
|
export const expandSecureUrl = (url) => `https://${url}`;
|
||||||
|
|
|
@ -32,7 +32,6 @@ import Button from "@mui/material/Button";
|
||||||
const ActionBar = (props) => {
|
const ActionBar = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const username = session.username();
|
|
||||||
let title = "ntfy";
|
let title = "ntfy";
|
||||||
if (props.selected) {
|
if (props.selected) {
|
||||||
title = topicDisplayName(props.selected);
|
title = topicDisplayName(props.selected);
|
||||||
|
@ -112,9 +111,12 @@ const SettingsIcons = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnsubscribe = async (event) => {
|
const handleUnsubscribe = async (event) => {
|
||||||
console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`);
|
console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`, props.subscription);
|
||||||
handleClose(event);
|
handleClose(event);
|
||||||
await subscriptionManager.remove(props.subscription.id);
|
await subscriptionManager.remove(props.subscription.id);
|
||||||
|
if (session.exists() && props.subscription.remoteId) {
|
||||||
|
await api.userSubscriptionDelete("http://localhost:2586", session.token(), props.subscription.remoteId);
|
||||||
|
}
|
||||||
const newSelected = await subscriptionManager.first(); // May be undefined
|
const newSelected = await subscriptionManager.first(); // May be undefined
|
||||||
if (newSelected) {
|
if (newSelected) {
|
||||||
navigate(routes.forSubscription(newSelected));
|
navigate(routes.forSubscription(newSelected));
|
||||||
|
|
|
@ -96,10 +96,19 @@ const Layout = () => {
|
||||||
if (account.notification.sound) {
|
if (account.notification.sound) {
|
||||||
await prefs.setSound(account.notification.sound);
|
await prefs.setSound(account.notification.sound);
|
||||||
}
|
}
|
||||||
|
if (account.notification.delete_after) {
|
||||||
|
await prefs.setDeleteAfter(account.notification.delete_after);
|
||||||
|
}
|
||||||
|
if (account.notification.min_priority) {
|
||||||
|
await prefs.setMinPriority(account.notification.min_priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (account.subscriptions) {
|
||||||
|
await subscriptionManager.syncFromRemote(account.subscriptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Box sx={{display: 'flex'}}>
|
<Box sx={{display: 'flex'}}>
|
||||||
<CssBaseline/>
|
<CssBaseline/>
|
||||||
|
|
|
@ -28,12 +28,8 @@ const Login = () => {
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const data = new FormData(event.currentTarget);
|
const data = new FormData(event.currentTarget);
|
||||||
console.log({
|
|
||||||
email: data.get('email'),
|
|
||||||
password: data.get('password'),
|
|
||||||
});
|
|
||||||
const user = {
|
const user = {
|
||||||
username: data.get('email'),
|
username: data.get('username'),
|
||||||
password: data.get('password'),
|
password: data.get('password'),
|
||||||
}
|
}
|
||||||
const token = await api.login("http://localhost:2586"/*window.location.origin*/, user);
|
const token = await api.login("http://localhost:2586"/*window.location.origin*/, user);
|
||||||
|
@ -63,10 +59,9 @@ const Login = () => {
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="email"
|
id="username"
|
||||||
label="Email Address"
|
label="Username"
|
||||||
name="email"
|
name="username"
|
||||||
autoComplete="email"
|
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -72,6 +72,13 @@ const Sound = () => {
|
||||||
const sound = useLiveQuery(async () => prefs.sound());
|
const sound = useLiveQuery(async () => prefs.sound());
|
||||||
const handleChange = async (ev) => {
|
const handleChange = async (ev) => {
|
||||||
await prefs.setSound(ev.target.value);
|
await prefs.setSound(ev.target.value);
|
||||||
|
if (session.exists()) {
|
||||||
|
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||||
|
notification: {
|
||||||
|
sound: ev.target.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!sound) {
|
if (!sound) {
|
||||||
return null; // While loading
|
return null; // While loading
|
||||||
|
@ -105,6 +112,13 @@ const MinPriority = () => {
|
||||||
const minPriority = useLiveQuery(async () => prefs.minPriority());
|
const minPriority = useLiveQuery(async () => prefs.minPriority());
|
||||||
const handleChange = async (ev) => {
|
const handleChange = async (ev) => {
|
||||||
await prefs.setMinPriority(ev.target.value);
|
await prefs.setMinPriority(ev.target.value);
|
||||||
|
if (session.exists()) {
|
||||||
|
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||||
|
notification: {
|
||||||
|
min_priority: ev.target.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!minPriority) {
|
if (!minPriority) {
|
||||||
return null; // While loading
|
return null; // While loading
|
||||||
|
@ -148,6 +162,13 @@ const DeleteAfter = () => {
|
||||||
const deleteAfter = useLiveQuery(async () => prefs.deleteAfter());
|
const deleteAfter = useLiveQuery(async () => prefs.deleteAfter());
|
||||||
const handleChange = async (ev) => {
|
const handleChange = async (ev) => {
|
||||||
await prefs.setDeleteAfter(ev.target.value);
|
await prefs.setDeleteAfter(ev.target.value);
|
||||||
|
if (session.exists()) {
|
||||||
|
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||||
|
notification: {
|
||||||
|
delete_after: ev.target.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0"
|
if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0"
|
||||||
return null; // While loading
|
return null; // While loading
|
||||||
|
@ -445,9 +466,11 @@ const Language = () => {
|
||||||
|
|
||||||
const handleChange = async (ev) => {
|
const handleChange = async (ev) => {
|
||||||
await i18n.changeLanguage(ev.target.value);
|
await i18n.changeLanguage(ev.target.value);
|
||||||
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
if (session.exists()) {
|
||||||
language: ev.target.value
|
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||||
});
|
language: ev.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remember: Flags are not languages. Don't put flags next to the language in the list.
|
// Remember: Flags are not languages. Don't put flags next to the language in the list.
|
||||||
|
|
|
@ -15,6 +15,7 @@ import subscriptionManager from "../app/SubscriptionManager";
|
||||||
import poller from "../app/Poller";
|
import poller from "../app/Poller";
|
||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
import session from "../app/Session";
|
||||||
|
|
||||||
const publicBaseUrl = "https://ntfy.sh";
|
const publicBaseUrl = "https://ntfy.sh";
|
||||||
|
|
||||||
|
@ -26,6 +27,13 @@ const SubscribeDialog = (props) => {
|
||||||
const handleSuccess = async () => {
|
const handleSuccess = async () => {
|
||||||
const actualBaseUrl = (baseUrl) ? baseUrl : window.location.origin;
|
const actualBaseUrl = (baseUrl) ? baseUrl : window.location.origin;
|
||||||
const subscription = await subscriptionManager.add(actualBaseUrl, topic);
|
const subscription = await subscriptionManager.add(actualBaseUrl, topic);
|
||||||
|
if (session.exists()) {
|
||||||
|
const remoteSubscription = await api.userSubscriptionAdd("http://localhost:2586", session.token(), {
|
||||||
|
base_url: actualBaseUrl,
|
||||||
|
topic: topic
|
||||||
|
});
|
||||||
|
await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id);
|
||||||
|
}
|
||||||
poller.pollInBackground(subscription); // Dangle!
|
poller.pollInBackground(subscription); // Dangle!
|
||||||
props.onSuccess(subscription);
|
props.onSuccess(subscription);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import routes from "./routes";
|
||||||
import connectionManager from "../app/ConnectionManager";
|
import connectionManager from "../app/ConnectionManager";
|
||||||
import poller from "../app/Poller";
|
import poller from "../app/Poller";
|
||||||
import pruner from "../app/Pruner";
|
import pruner from "../app/Pruner";
|
||||||
|
import session from "../app/Session";
|
||||||
|
import api from "../app/Api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wire connectionManager and subscriptionManager so that subscriptions are updated when the connection
|
* Wire connectionManager and subscriptionManager so that subscriptions are updated when the connection
|
||||||
|
@ -61,6 +63,13 @@ export const useAutoSubscribe = (subscriptions, selected) => {
|
||||||
console.log(`[App] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`);
|
console.log(`[App] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`);
|
||||||
(async () => {
|
(async () => {
|
||||||
const subscription = await subscriptionManager.add(baseUrl, params.topic);
|
const subscription = await subscriptionManager.add(baseUrl, params.topic);
|
||||||
|
if (session.exists()) {
|
||||||
|
const remoteSubscription = await api.userSubscriptionAdd("http://localhost:2586", session.token(), {
|
||||||
|
base_url: baseUrl,
|
||||||
|
topic: params.topic
|
||||||
|
});
|
||||||
|
await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id);
|
||||||
|
}
|
||||||
poller.pollInBackground(subscription); // Dangle!
|
poller.pollInBackground(subscription); // Dangle!
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue