Better test messages
parent
488aeb119b
commit
3f978bc45f
|
@ -1,7 +1,7 @@
|
||||||
:root {
|
:root {
|
||||||
--md-primary-fg-color: #3a9784;
|
--md-primary-fg-color: #338574;
|
||||||
--md-primary-fg-color--light: #3a9784;
|
--md-primary-fg-color--light: #338574;
|
||||||
--md-primary-fg-color--dark: #3a9784;
|
--md-primary-fg-color--dark: #338574;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-header__button.md-logo :is(img, svg) {
|
.md-header__button.md-logo :is(img, svg) {
|
||||||
|
|
|
@ -882,7 +882,7 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
|
||||||
func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request) error {
|
func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request) error {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST")
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization") // CORS, allow auth via JS
|
w.Header().Set("Access-Control-Allow-Headers", "*") // CORS, allow auth via JS // FIXME is this terrible?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,24 @@ class Api {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(baseUrl, topic, message) {
|
async publish(baseUrl, topic, message, title, priority, tags) {
|
||||||
const user = await userManager.get(baseUrl);
|
const user = await userManager.get(baseUrl);
|
||||||
const url = topicUrl(baseUrl, topic);
|
const url = topicUrl(baseUrl, topic);
|
||||||
console.log(`[Api] Publishing message to ${url}`);
|
console.log(`[Api] Publishing message to ${url}`);
|
||||||
|
const headers = {};
|
||||||
|
if (title) {
|
||||||
|
headers["X-Title"] = title;
|
||||||
|
}
|
||||||
|
if (priority !== 3) {
|
||||||
|
headers["X-Priority"] = `${priority}`;
|
||||||
|
}
|
||||||
|
if (tags.length > 0) {
|
||||||
|
headers["X-Tags"] = tags.join(",");
|
||||||
|
}
|
||||||
await fetch(url, {
|
await fetch(url, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: message,
|
body: message,
|
||||||
headers: maybeWithBasicAuth({}, user)
|
headers: maybeWithBasicAuth(headers, user)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,17 @@ export const encodeBase64Url = (s) => {
|
||||||
return Base64.encodeURI(s);
|
return Base64.encodeURI(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const shuffle = (arr) => {
|
||||||
|
let j, x;
|
||||||
|
for (let index = arr.length - 1; index > 0; index--) {
|
||||||
|
j = Math.floor(Math.random() * (index + 1));
|
||||||
|
x = arr[index];
|
||||||
|
arr[index] = arr[j];
|
||||||
|
arr[j] = x;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
// https://jameshfisher.com/2017/10/30/web-cryptography-api-hello-world/
|
// https://jameshfisher.com/2017/10/30/web-cryptography-api-hello-world/
|
||||||
export const sha256 = async (s) => {
|
export const sha256 = async (s) => {
|
||||||
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode(s));
|
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode(s));
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Typography from "@mui/material/Typography";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {useEffect, useRef, useState} from "react";
|
import {useEffect, useRef, useState} from "react";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import {topicShortUrl} from "../app/utils";
|
import {formatShortDateTime, shuffle, topicShortUrl} from "../app/utils";
|
||||||
import {useLocation, useNavigate} from "react-router-dom";
|
import {useLocation, useNavigate} from "react-router-dom";
|
||||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||||
import Grow from '@mui/material/Grow';
|
import Grow from '@mui/material/Grow';
|
||||||
|
@ -108,8 +108,31 @@ const SettingsIcons = (props) => {
|
||||||
const handleSendTestMessage = () => {
|
const handleSendTestMessage = () => {
|
||||||
const baseUrl = props.subscription.baseUrl;
|
const baseUrl = props.subscription.baseUrl;
|
||||||
const topic = props.subscription.topic;
|
const topic = props.subscription.topic;
|
||||||
api.publish(baseUrl, topic,
|
const tags = shuffle([
|
||||||
`This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored
|
"grinning", "octopus", "upside_down_face", "palm_tree", "maple_leaf", "apple", "skull", "warning", "jack_o_lantern",
|
||||||
|
"de-server-1", "backups", "cron-script", "script-error", "phils-automation", "mouse", "go-rocks", "hi-ben"])
|
||||||
|
.slice(0, Math.round(Math.random() * 4));
|
||||||
|
const priority = shuffle([1, 2, 3, 4, 5])[0];
|
||||||
|
const title = shuffle([
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"", // Higher chance of no title
|
||||||
|
"Oh my, another test message?",
|
||||||
|
"Titles are optional, did you know that?",
|
||||||
|
"ntfy is open source, and will always be free. Cool, right?",
|
||||||
|
"I don't really like apples",
|
||||||
|
"My favorite TV show is The Wire. You should watch it!"
|
||||||
|
])[0];
|
||||||
|
const message = shuffle([
|
||||||
|
`Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime(Date.now())} right now. Is that early or late?`,
|
||||||
|
`So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`,
|
||||||
|
`It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`,
|
||||||
|
`Alright then, it's ${formatShortDateTime(Date.now())} already. Boy oh boy, where did the time go? I hope you're alright, friend.`,
|
||||||
|
`There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`,
|
||||||
|
`I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/annoucements.`,
|
||||||
|
`It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?`
|
||||||
|
])[0];
|
||||||
|
api.publish(baseUrl, topic, message, title, priority, tags);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import ErrorBoundary from "./ErrorBoundary";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import {useAutoSubscribe, useConnectionListeners} from "./hooks";
|
import {useAutoSubscribe, useConnectionListeners} from "./hooks";
|
||||||
|
|
||||||
// TODO better "send test message" (a la android app)
|
|
||||||
// TODO docs
|
// TODO docs
|
||||||
// TODO screenshot on homepage
|
// TODO screenshot on homepage
|
||||||
// TODO "copy url" toast
|
// TODO "copy url" toast
|
||||||
|
|
|
@ -14,7 +14,7 @@ import SubscribeDialog from "./SubscribeDialog";
|
||||||
import {Alert, AlertTitle, Badge, CircularProgress, ListSubheader} from "@mui/material";
|
import {Alert, AlertTitle, Badge, CircularProgress, ListSubheader} from "@mui/material";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {topicShortUrl, topicUrl} from "../app/utils";
|
import {openUrl, topicShortUrl, topicUrl} from "../app/utils";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import {ConnectionState} from "../app/Connection";
|
import {ConnectionState} from "../app/Connection";
|
||||||
import {useLocation, useNavigate} from "react-router-dom";
|
import {useLocation, useNavigate} from "react-router-dom";
|
||||||
|
@ -23,6 +23,7 @@ import {ChatBubble, NotificationsOffOutlined} from "@mui/icons-material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import notifier from "../app/Notifier";
|
import notifier from "../app/Notifier";
|
||||||
import config from "../app/config";
|
import config from "../app/config";
|
||||||
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
|
|
||||||
const navWidth = 280;
|
const navWidth = 280;
|
||||||
|
|
||||||
|
@ -113,6 +114,10 @@ const NavList = (props) => {
|
||||||
<ListItemIcon><SettingsIcon/></ListItemIcon>
|
<ListItemIcon><SettingsIcon/></ListItemIcon>
|
||||||
<ListItemText primary="Settings"/>
|
<ListItemText primary="Settings"/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
<ListItemButton onClick={() => openUrl("/docs")}>
|
||||||
|
<ListItemIcon><ArticleIcon/></ListItemIcon>
|
||||||
|
<ListItemText primary="Documentation"/>
|
||||||
|
</ListItemButton>
|
||||||
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
|
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
|
||||||
<ListItemIcon><AddIcon/></ListItemIcon>
|
<ListItemIcon><AddIcon/></ListItemIcon>
|
||||||
<ListItemText primary="Add subscription"/>
|
<ListItemText primary="Add subscription"/>
|
||||||
|
|
Loading…
Reference in New Issue