blerp
This commit is contained in:
parent
2e1ddc9ae1
commit
92bf7ebc52
11 changed files with 237 additions and 37 deletions
|
@ -8,7 +8,7 @@ import {
|
|||
topicUrlJsonPollWithSince,
|
||||
userAccountUrl,
|
||||
userTokenUrl,
|
||||
userStatsUrl
|
||||
userStatsUrl, userSubscriptionUrl, userSubscriptionDeleteUrl
|
||||
} from "./utils";
|
||||
import userManager from "./UserManager";
|
||||
|
||||
|
@ -186,6 +186,35 @@ class Api {
|
|||
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();
|
||||
|
|
|
@ -18,17 +18,43 @@ class SubscriptionManager {
|
|||
}
|
||||
|
||||
async add(baseUrl, topic) {
|
||||
const id = topicUrl(baseUrl, topic);
|
||||
const existingSubscription = await this.get(id);
|
||||
if (existingSubscription) {
|
||||
return existingSubscription;
|
||||
}
|
||||
const subscription = {
|
||||
id: topicUrl(baseUrl, topic),
|
||||
baseUrl: baseUrl,
|
||||
topic: topic,
|
||||
mutedUntil: 0,
|
||||
last: null
|
||||
last: null,
|
||||
remoteId: null
|
||||
};
|
||||
await db.subscriptions.put(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) {
|
||||
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) {
|
||||
await db.notifications
|
||||
.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 userTokenUrl = (baseUrl) => `${baseUrl}/user/token`;
|
||||
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 expandUrl = (url) => [`https://${url}`, `http://${url}`];
|
||||
export const expandSecureUrl = (url) => `https://${url}`;
|
||||
|
|
|
@ -32,7 +32,6 @@ import Button from "@mui/material/Button";
|
|||
const ActionBar = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const username = session.username();
|
||||
let title = "ntfy";
|
||||
if (props.selected) {
|
||||
title = topicDisplayName(props.selected);
|
||||
|
@ -112,9 +111,12 @@ const SettingsIcons = (props) => {
|
|||
};
|
||||
|
||||
const handleUnsubscribe = async (event) => {
|
||||
console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`);
|
||||
console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`, props.subscription);
|
||||
handleClose(event);
|
||||
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
|
||||
if (newSelected) {
|
||||
navigate(routes.forSubscription(newSelected));
|
||||
|
|
|
@ -96,10 +96,19 @@ const Layout = () => {
|
|||
if (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 (
|
||||
<Box sx={{display: 'flex'}}>
|
||||
<CssBaseline/>
|
||||
|
|
|
@ -28,12 +28,8 @@ const Login = () => {
|
|||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
console.log({
|
||||
email: data.get('email'),
|
||||
password: data.get('password'),
|
||||
});
|
||||
const user = {
|
||||
username: data.get('email'),
|
||||
username: data.get('username'),
|
||||
password: data.get('password'),
|
||||
}
|
||||
const token = await api.login("http://localhost:2586"/*window.location.origin*/, user);
|
||||
|
@ -63,10 +59,9 @@ const Login = () => {
|
|||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
id="username"
|
||||
label="Username"
|
||||
name="username"
|
||||
autoFocus
|
||||
/>
|
||||
<TextField
|
||||
|
|
|
@ -72,6 +72,13 @@ const Sound = () => {
|
|||
const sound = useLiveQuery(async () => prefs.sound());
|
||||
const handleChange = async (ev) => {
|
||||
await prefs.setSound(ev.target.value);
|
||||
if (session.exists()) {
|
||||
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||
notification: {
|
||||
sound: ev.target.value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!sound) {
|
||||
return null; // While loading
|
||||
|
@ -105,6 +112,13 @@ const MinPriority = () => {
|
|||
const minPriority = useLiveQuery(async () => prefs.minPriority());
|
||||
const handleChange = async (ev) => {
|
||||
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) {
|
||||
return null; // While loading
|
||||
|
@ -148,6 +162,13 @@ const DeleteAfter = () => {
|
|||
const deleteAfter = useLiveQuery(async () => prefs.deleteAfter());
|
||||
const handleChange = async (ev) => {
|
||||
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"
|
||||
return null; // While loading
|
||||
|
@ -445,9 +466,11 @@ const Language = () => {
|
|||
|
||||
const handleChange = async (ev) => {
|
||||
await i18n.changeLanguage(ev.target.value);
|
||||
await api.updateUserAccount("http://localhost:2586", session.token(), {
|
||||
language: ev.target.value
|
||||
});
|
||||
if (session.exists()) {
|
||||
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.
|
||||
|
|
|
@ -15,6 +15,7 @@ import subscriptionManager from "../app/SubscriptionManager";
|
|||
import poller from "../app/Poller";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import session from "../app/Session";
|
||||
|
||||
const publicBaseUrl = "https://ntfy.sh";
|
||||
|
||||
|
@ -26,6 +27,13 @@ const SubscribeDialog = (props) => {
|
|||
const handleSuccess = async () => {
|
||||
const actualBaseUrl = (baseUrl) ? baseUrl : window.location.origin;
|
||||
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!
|
||||
props.onSuccess(subscription);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import routes from "./routes";
|
|||
import connectionManager from "../app/ConnectionManager";
|
||||
import poller from "../app/Poller";
|
||||
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
|
||||
|
@ -61,6 +63,13 @@ export const useAutoSubscribe = (subscriptions, selected) => {
|
|||
console.log(`[App] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`);
|
||||
(async () => {
|
||||
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!
|
||||
})();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue