Compare commits
6 commits
mine
...
web-improv
Author | SHA1 | Date | |
---|---|---|---|
|
2697600111 | ||
|
025ea3c1d6 | ||
|
be6d962cc3 | ||
|
4a0a22566a | ||
|
25d6725d8f | ||
|
1291c3afe9 |
20 changed files with 56 additions and 308 deletions
|
@ -1,3 +0,0 @@
|
||||||
dist
|
|
||||||
*/node_modules
|
|
||||||
Dockerfile*
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
||||||
dist/
|
dist/
|
||||||
dev-dist/
|
|
||||||
build/
|
build/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM r.batts.cloud/debian:testing
|
FROM alpine
|
||||||
|
|
||||||
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
|
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
|
||||||
LABEL org.opencontainers.image.url="https://ntfy.sh/"
|
LABEL org.opencontainers.image.url="https://ntfy.sh/"
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
FROM r.batts.cloud/golang:1.19 as builder
|
|
||||||
|
|
||||||
ARG VERSION=dev
|
|
||||||
ARG COMMIT=unknown
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash
|
|
||||||
RUN apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
nodejs \
|
|
||||||
python3-pip \
|
|
||||||
python3-venv
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
ADD Makefile .
|
|
||||||
|
|
||||||
# docs
|
|
||||||
ADD ./requirements.txt .
|
|
||||||
RUN make docs-deps
|
|
||||||
ADD ./mkdocs.yml .
|
|
||||||
ADD ./docs ./docs
|
|
||||||
RUN make docs-build
|
|
||||||
|
|
||||||
# web
|
|
||||||
ADD ./web/package.json ./web/package-lock.json ./web/
|
|
||||||
RUN make web-deps
|
|
||||||
ADD ./web ./web
|
|
||||||
RUN make web-build
|
|
||||||
|
|
||||||
# cli & server
|
|
||||||
ADD go.mod go.sum main.go ./
|
|
||||||
ADD ./client ./client
|
|
||||||
ADD ./cmd ./cmd
|
|
||||||
ADD ./log ./log
|
|
||||||
ADD ./server ./server
|
|
||||||
ADD ./user ./user
|
|
||||||
ADD ./util ./util
|
|
||||||
RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server
|
|
||||||
|
|
||||||
FROM r.batts.cloud/debian:testing
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
|
|
||||||
LABEL org.opencontainers.image.url="https://ntfy.sh/"
|
|
||||||
LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/"
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/binwiederhier/ntfy"
|
|
||||||
LABEL org.opencontainers.image.vendor="Philipp C. Heckel"
|
|
||||||
LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
|
|
||||||
LABEL org.opencontainers.image.title="ntfy"
|
|
||||||
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
|
|
||||||
|
|
||||||
COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy
|
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
|
||||||
ENTRYPOINT ["ntfy"]
|
|
37
Makefile
37
Makefile
|
@ -31,16 +31,12 @@ help:
|
||||||
@echo " make cli-darwin-server - Build client & server (no GoReleaser, current arch, macOS)"
|
@echo " make cli-darwin-server - Build client & server (no GoReleaser, current arch, macOS)"
|
||||||
@echo " make cli-client - Build client only (no GoReleaser, current arch, Linux/macOS/Windows)"
|
@echo " make cli-client - Build client only (no GoReleaser, current arch, Linux/macOS/Windows)"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build dev Docker:"
|
|
||||||
@echo " make docker-dev - Build client & server for current architecture using Docker only"
|
|
||||||
@echo
|
|
||||||
@echo "Build web app:"
|
@echo "Build web app:"
|
||||||
@echo " make web - Build the web app"
|
@echo " make web - Build the web app"
|
||||||
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
||||||
@echo " make web-build - Actually build the web app"
|
@echo " make web-build - Actually build the web app"
|
||||||
@echo " make web-lint - Run eslint on the web app"
|
@echo " make web-format - Run prettier on the web app
|
||||||
@echo " make web-format - Run prettier on the web app"
|
@echo " make web-format-check - Run prettier on the web app, but don't change anything
|
||||||
@echo " make web-format-check - Run prettier on the web app, but don't change anything"
|
|
||||||
@echo
|
@echo
|
||||||
@echo "Build documentation:"
|
@echo "Build documentation:"
|
||||||
@echo " make docs - Build the documentation"
|
@echo " make docs - Build the documentation"
|
||||||
|
@ -86,33 +82,23 @@ build: web docs cli
|
||||||
update: web-deps-update cli-deps-update docs-deps-update
|
update: web-deps-update cli-deps-update docs-deps-update
|
||||||
docker pull alpine
|
docker pull alpine
|
||||||
|
|
||||||
docker-dev:
|
|
||||||
docker build \
|
|
||||||
--file ./Dockerfile-build \
|
|
||||||
--tag binwiederhier/ntfy:$(VERSION) \
|
|
||||||
--tag binwiederhier/ntfy:dev \
|
|
||||||
--build-arg VERSION=$(VERSION) \
|
|
||||||
--build-arg COMMIT=$(COMMIT) \
|
|
||||||
./
|
|
||||||
|
|
||||||
# Ubuntu-specific
|
# Ubuntu-specific
|
||||||
|
|
||||||
build-deps-ubuntu:
|
build-deps-ubuntu:
|
||||||
sudo apt-get update
|
sudo apt update
|
||||||
sudo apt-get install -y \
|
sudo apt install -y \
|
||||||
curl \
|
curl \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
gcc-arm-linux-gnueabi \
|
gcc-arm-linux-gnueabi \
|
||||||
jq
|
jq
|
||||||
which pip3 || sudo apt-get install -y python3-pip
|
which pip3 || sudo apt install -y python3-pip
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
docs: docs-deps docs-build
|
docs: docs-deps docs-build
|
||||||
|
|
||||||
docs-build: venv .PHONY
|
docs-build: .PHONY
|
||||||
@. venv/bin/activate && \
|
@if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
||||||
if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
|
||||||
if which python3.8; then \
|
if which python3.8; then \
|
||||||
echo "python3.8 $(shell which mkdocs) build"; \
|
echo "python3.8 $(shell which mkdocs) build"; \
|
||||||
python3.8 $(shell which mkdocs) build; \
|
python3.8 $(shell which mkdocs) build; \
|
||||||
|
@ -125,15 +111,10 @@ docs-build: venv .PHONY
|
||||||
mkdocs build; \
|
mkdocs build; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
venv:
|
docs-deps: .PHONY
|
||||||
python3 -m venv ./venv
|
|
||||||
|
|
||||||
docs-deps: venv .PHONY
|
|
||||||
. venv/bin/activate && \
|
|
||||||
pip3 install -r requirements.txt
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
docs-deps-update: venv .PHONY
|
docs-deps-update: .PHONY
|
||||||
. venv/bin/activate && \
|
|
||||||
pip3 install -r requirements.txt --upgrade
|
pip3 install -r requirements.txt --upgrade
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,25 +11,23 @@ import (
|
||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Event type constants
|
||||||
const (
|
const (
|
||||||
// MessageEvent identifies a message event
|
MessageEvent = "message"
|
||||||
MessageEvent = "message"
|
KeepaliveEvent = "keepalive"
|
||||||
|
OpenEvent = "open"
|
||||||
|
PollRequestEvent = "poll_request"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxResponseBytes = 4096
|
maxResponseBytes = 4096
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // Same as in server/server.go
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is the ntfy client that can be used to publish and subscribe to ntfy topics
|
// Client is the ntfy client that can be used to publish and subscribe to ntfy topics
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Messages chan *Message
|
Messages chan *Message
|
||||||
|
@ -98,14 +96,8 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) (*Mess
|
||||||
// To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache,
|
// To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache,
|
||||||
// WithNoFirebase, and the generic WithHeader.
|
// WithNoFirebase, and the generic WithHeader.
|
||||||
func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) {
|
func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) {
|
||||||
topicURL, err := c.expandTopicURL(topic)
|
topicURL := c.expandTopicURL(topic)
|
||||||
if err != nil {
|
req, _ := http.NewRequest("POST", topicURL, body)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("POST", topicURL, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if err := option(req); err != nil {
|
if err := option(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -141,14 +133,11 @@ func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishO
|
||||||
// By default, all messages will be returned, but you can change this behavior using a SubscribeOption.
|
// By default, all messages will be returned, but you can change this behavior using a SubscribeOption.
|
||||||
// See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam.
|
// See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam.
|
||||||
func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) {
|
func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) {
|
||||||
topicURL, err := c.expandTopicURL(topic)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
messages := make([]*Message, 0)
|
messages := make([]*Message, 0)
|
||||||
msgChan := make(chan *Message)
|
msgChan := make(chan *Message)
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
|
topicURL := c.expandTopicURL(topic)
|
||||||
log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL))
|
log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL))
|
||||||
options = append(options, WithPoll())
|
options = append(options, WithPoll())
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -177,18 +166,15 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// c := client.New(client.NewConfig())
|
// c := client.New(client.NewConfig())
|
||||||
// subscriptionID, _ := c.Subscribe("mytopic")
|
// subscriptionID := c.Subscribe("mytopic")
|
||||||
// for m := range c.Messages {
|
// for m := range c.Messages {
|
||||||
// fmt.Printf("New message: %s", m.Message)
|
// fmt.Printf("New message: %s", m.Message)
|
||||||
// }
|
// }
|
||||||
func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, error) {
|
func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
|
||||||
topicURL, err := c.expandTopicURL(topic)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
subscriptionID := util.RandomString(10)
|
subscriptionID := util.RandomString(10)
|
||||||
|
topicURL := c.expandTopicURL(topic)
|
||||||
log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL))
|
log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL))
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
c.subscriptions[subscriptionID] = &subscription{
|
c.subscriptions[subscriptionID] = &subscription{
|
||||||
|
@ -197,7 +183,7 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, er
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
go handleSubscribeConnLoop(ctx, c.Messages, topicURL, subscriptionID, options...)
|
go handleSubscribeConnLoop(ctx, c.Messages, topicURL, subscriptionID, options...)
|
||||||
return subscriptionID, nil
|
return subscriptionID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe unsubscribes from a topic that has been previously subscribed to using the unique
|
// Unsubscribe unsubscribes from a topic that has been previously subscribed to using the unique
|
||||||
|
@ -213,16 +199,31 @@ func (c *Client) Unsubscribe(subscriptionID string) {
|
||||||
sub.cancel()
|
sub.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) expandTopicURL(topic string) (string, error) {
|
// UnsubscribeAll unsubscribes from a topic that has been previously subscribed with Subscribe.
|
||||||
|
// If there are multiple subscriptions matching the topic, all of them are unsubscribed from.
|
||||||
|
//
|
||||||
|
// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https://
|
||||||
|
// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the
|
||||||
|
// config (e.g. mytopic -> https://ntfy.sh/mytopic).
|
||||||
|
func (c *Client) UnsubscribeAll(topic string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
topicURL := c.expandTopicURL(topic)
|
||||||
|
for _, sub := range c.subscriptions {
|
||||||
|
if sub.topicURL == topicURL {
|
||||||
|
delete(c.subscriptions, sub.ID)
|
||||||
|
sub.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) expandTopicURL(topic string) string {
|
||||||
if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") {
|
if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") {
|
||||||
return topic, nil
|
return topic
|
||||||
} else if strings.Contains(topic, "/") {
|
} else if strings.Contains(topic, "/") {
|
||||||
return fmt.Sprintf("https://%s", topic), nil
|
return fmt.Sprintf("https://%s", topic)
|
||||||
}
|
}
|
||||||
if !topicRegex.MatchString(topic) {
|
return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic)
|
||||||
return "", fmt.Errorf("invalid topic name: %s", topic)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL, subcriptionID string, options ...SubscribeOption) {
|
func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL, subcriptionID string, options ...SubscribeOption) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestClient_Publish_Subscribe(t *testing.T) {
|
||||||
defer test.StopServer(t, s, port)
|
defer test.StopServer(t, s, port)
|
||||||
c := client.New(newTestConfig(port))
|
c := client.New(newTestConfig(port))
|
||||||
|
|
||||||
subscriptionID, _ := c.Subscribe("mytopic")
|
subscriptionID := c.Subscribe("mytopic")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
msg, err := c.Publish("mytopic", "some message")
|
msg, err := c.Publish("mytopic", "some message")
|
||||||
|
|
|
@ -72,7 +72,7 @@ ntfy subscribe TOPIC COMMAND
|
||||||
$NTFY_TITLE $title, $t Message title
|
$NTFY_TITLE $title, $t Message title
|
||||||
$NTFY_PRIORITY $priority, $prio, $p Message priority (1=min, 5=max)
|
$NTFY_PRIORITY $priority, $prio, $p Message priority (1=min, 5=max)
|
||||||
$NTFY_TAGS $tags, $tag, $ta Message tags (comma separated list)
|
$NTFY_TAGS $tags, $tag, $ta Message tags (comma separated list)
|
||||||
$NTFY_RAW $raw Raw JSON message
|
$NTFY_RAW $raw Raw JSON message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages
|
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages
|
||||||
|
@ -194,10 +194,7 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
||||||
topicOptions = append(topicOptions, auth)
|
topicOptions = append(topicOptions, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionID, err := cl.Subscribe(s.Topic, topicOptions...)
|
subscriptionID := cl.Subscribe(s.Topic, topicOptions...)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.Command != "" {
|
if s.Command != "" {
|
||||||
cmds[subscriptionID] = s.Command
|
cmds[subscriptionID] = s.Command
|
||||||
} else if conf.DefaultCommand != "" {
|
} else if conf.DefaultCommand != "" {
|
||||||
|
@ -207,10 +204,7 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if topic != "" {
|
if topic != "" {
|
||||||
subscriptionID, err := cl.Subscribe(topic, options...)
|
subscriptionID := cl.Subscribe(topic, options...)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmds[subscriptionID] = command
|
cmds[subscriptionID] = command
|
||||||
}
|
}
|
||||||
for m := range cl.Messages {
|
for m := range cl.Messages {
|
||||||
|
|
|
@ -163,15 +163,6 @@ $ make release-snapshot
|
||||||
|
|
||||||
During development, you may want to be more picky and build only certain things. Here are a few examples.
|
During development, you may want to be more picky and build only certain things. Here are a few examples.
|
||||||
|
|
||||||
### Build a Docker image only for Linux
|
|
||||||
|
|
||||||
This is useful to test the final build with web app, docs, and server without any dependencies locally
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ make docker-dev
|
|
||||||
$ docker run --rm -p 80:80 binwiederhier/ntfy:dev serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build the ntfy binary
|
### Build the ntfy binary
|
||||||
To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets:
|
To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets:
|
||||||
|
|
||||||
|
|
|
@ -1225,8 +1225,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
**Bug fixes:**
|
**Bug fixes:**
|
||||||
|
|
||||||
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
|
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
|
||||||
* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting)
|
|
||||||
* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting)
|
|
||||||
|
|
||||||
**Maintenance:**
|
**Maintenance:**
|
||||||
|
|
||||||
|
|
|
@ -760,7 +760,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
||||||
if s.config.TwilioAccount != "" && call != "" {
|
if s.config.TwilioAccount != "" && call != "" {
|
||||||
go s.callPhone(v, r, m, call)
|
go s.callPhone(v, r, m, call)
|
||||||
}
|
}
|
||||||
if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream
|
if s.config.UpstreamBaseURL != "" {
|
||||||
go s.forwardPollRequest(v, m)
|
go s.forwardPollRequest(v, m)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2559,29 +2559,6 @@ func TestServer_UpstreamBaseURL_With_Access_Token_Success(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_UpstreamBaseURL_DoNotForwardUnifiedPush(t *testing.T) {
|
|
||||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
t.Fatal("UnifiedPush messages should not be forwarded")
|
|
||||||
}))
|
|
||||||
defer upstreamServer.Close()
|
|
||||||
|
|
||||||
c := newTestConfigWithAuthFile(t)
|
|
||||||
c.BaseURL = "http://myserver.internal"
|
|
||||||
c.UpstreamBaseURL = upstreamServer.URL
|
|
||||||
s := newTestServer(t, c)
|
|
||||||
|
|
||||||
// Send UP message, this should not forward to upstream server
|
|
||||||
response := request(t, s, "PUT", "/mytopic?up=1", `hi there`, nil)
|
|
||||||
require.Equal(t, 200, response.Code)
|
|
||||||
m := toMessage(t, response.Body.String())
|
|
||||||
require.NotEmpty(t, m.ID)
|
|
||||||
require.Equal(t, "hi there", m.Message)
|
|
||||||
|
|
||||||
// Forwarding is done asynchronously, so wait a bit.
|
|
||||||
// This ensures that the t.Fatal above is actually not triggered.
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestConfig(t *testing.T) *Config {
|
func newTestConfig(t *testing.T) *Config {
|
||||||
conf := NewConfig()
|
conf := NewConfig()
|
||||||
conf.BaseURL = "http://127.0.0.1:12345"
|
conf.BaseURL = "http://127.0.0.1:12345"
|
||||||
|
|
|
@ -355,15 +355,5 @@
|
||||||
"account_upgrade_dialog_billing_contact_website": "Otázky týkající se fakturace naleznete na našich <Link>webových stránkách</Link>.",
|
"account_upgrade_dialog_billing_contact_website": "Otázky týkající se fakturace naleznete na našich <Link>webových stránkách</Link>.",
|
||||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} rezervované téma",
|
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} rezervované téma",
|
||||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} denní zpráva",
|
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} denní zpráva",
|
||||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} denní e-mail",
|
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} denní e-mail"
|
||||||
"publish_dialog_call_label": "Telefonát",
|
|
||||||
"publish_dialog_call_reset": "Odstranit telefonát",
|
|
||||||
"publish_dialog_chip_call_label": "Telefonát",
|
|
||||||
"account_basics_phone_numbers_title": "Telefonní čísla",
|
|
||||||
"account_basics_phone_numbers_dialog_description": "Pro oznámení prostřednictvím tel. hovoru, musíte přidat a ověřit alespoň jedno telefonní číslo. Ověření lze provést pomocí SMS nebo telefonátu.",
|
|
||||||
"account_basics_phone_numbers_description": "K oznámení telefonátem",
|
|
||||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Zatím žádná telefonní čísla",
|
|
||||||
"account_basics_phone_numbers_copied_to_clipboard": "Telefonní číslo zkopírováno do schránky",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Žádná ověřená telefonní čísla",
|
|
||||||
"publish_dialog_call_item": "Vytočit číslo {{number}}"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,31 +355,5 @@
|
||||||
"account_upgrade_dialog_billing_contact_email": "Para preguntas sobre facturación, por favor <Link>contáctenos</Link> directamente.",
|
"account_upgrade_dialog_billing_contact_email": "Para preguntas sobre facturación, por favor <Link>contáctenos</Link> directamente.",
|
||||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensaje diario",
|
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensaje diario",
|
||||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} correo electrónico diario",
|
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} correo electrónico diario",
|
||||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado",
|
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado"
|
||||||
"publish_dialog_call_label": "Llamada telefónica",
|
|
||||||
"publish_dialog_call_placeholder": "Número de teléfono al cual llamar con el mensaje, por ejemplo +12223334444, o \"sí\"",
|
|
||||||
"publish_dialog_chip_call_label": "Llamada telefónica",
|
|
||||||
"account_basics_phone_numbers_title": "Números de teléfono",
|
|
||||||
"account_basics_phone_numbers_description": "Para notificaciones por llamada teléfonica",
|
|
||||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Aún no hay números de teléfono",
|
|
||||||
"account_basics_phone_numbers_dialog_number_label": "Número de teléfono",
|
|
||||||
"account_basics_phone_numbers_dialog_number_placeholder": "p. ej. +1222333444",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Envía SMS",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_call": "Llámame",
|
|
||||||
"account_basics_phone_numbers_dialog_code_label": "Código de verificación",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_call": "Llamar",
|
|
||||||
"account_usage_calls_title": "Llamadas telefónicas realizadas",
|
|
||||||
"account_usage_calls_none": "No se pueden hacer llamadas telefónicas con esta cuenta",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_one": "{{llamadas}} llamadas telefónicas diarias",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_other": "{{llamadas}} llamadas telefónicas diarias",
|
|
||||||
"account_upgrade_dialog_tier_features_no_calls": "No hay llamadas telefónicas",
|
|
||||||
"publish_dialog_call_reset": "Eliminar llamada telefónica",
|
|
||||||
"account_basics_phone_numbers_dialog_description": "Para utilizar la función de notificación de llamadas, tiene que añadir y verificar al menos un número de teléfono. La verificación puede realizarse mediante un SMS o una llamada telefónica.",
|
|
||||||
"account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado al portapapeles",
|
|
||||||
"account_basics_phone_numbers_dialog_check_verification_button": "Confirmar código",
|
|
||||||
"account_basics_phone_numbers_dialog_title": "Agregar número de teléfono",
|
|
||||||
"account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456",
|
|
||||||
"publish_dialog_call_item": "Llamar al número de teléfono {{number}}",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "No hay números de teléfono verificados"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,7 +379,5 @@
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} panggilan telepon harian",
|
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} panggilan telepon harian",
|
||||||
"account_upgrade_dialog_tier_features_no_calls": "Tidak ada panggilan telepon",
|
"account_upgrade_dialog_tier_features_no_calls": "Tidak ada panggilan telepon",
|
||||||
"account_basics_phone_numbers_dialog_code_label": "Kode verifikasi",
|
"account_basics_phone_numbers_dialog_code_label": "Kode verifikasi"
|
||||||
"publish_dialog_call_item": "Panggil nomor telepon {{number}}",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Tidak ada nomor telepon terverifikasi"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,30 +355,5 @@
|
||||||
"prefs_reservations_table_topic_header": "Onderwerp",
|
"prefs_reservations_table_topic_header": "Onderwerp",
|
||||||
"prefs_reservations_table_access_header": "Toegang",
|
"prefs_reservations_table_access_header": "Toegang",
|
||||||
"prefs_reservations_table_everyone_read_write": "Iedereen kan publiceren en abonneren",
|
"prefs_reservations_table_everyone_read_write": "Iedereen kan publiceren en abonneren",
|
||||||
"prefs_reservations_table_not_subscribed": "Niet geabonneerd",
|
"prefs_reservations_table_not_subscribed": "Niet geabonneerd"
|
||||||
"publish_dialog_call_label": "Telefoongesprek",
|
|
||||||
"publish_dialog_call_reset": "Telefoongesprek verwijderen",
|
|
||||||
"publish_dialog_chip_call_label": "Telefoongesprek",
|
|
||||||
"account_basics_phone_numbers_title": "Telefoonnummers",
|
|
||||||
"account_basics_phone_numbers_description": "Voor meldingen via telefoongesprekken",
|
|
||||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Nog geen telefoonnummers",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_call": "Bel me",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagelijkse telefoontjes",
|
|
||||||
"account_basics_phone_numbers_copied_to_clipboard": "Telefoonnummer gekopieerd naar klembord",
|
|
||||||
"publish_dialog_call_item": "Bel telefoonnummer {{nummer}}",
|
|
||||||
"account_basics_phone_numbers_dialog_check_verification_button": "Bevestig code",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Geen geverifieerde telefoonnummers",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_call": "Telefoongesprek",
|
|
||||||
"account_basics_phone_numbers_dialog_number_label": "Telefoonnummer",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
|
||||||
"account_basics_phone_numbers_dialog_code_placeholder": "bijv. 123456",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagelijkse telefoontjes",
|
|
||||||
"account_upgrade_dialog_tier_features_no_calls": "Geen telefoontjes",
|
|
||||||
"account_basics_phone_numbers_dialog_description": "Als u de functie voor oproepmeldingen wilt gebruiken, moet u ten minste één telefoonnummer toevoegen en verifiëren. Verificatie kan worden gedaan via sms of een telefoontje.",
|
|
||||||
"account_basics_phone_numbers_dialog_title": "Telefoonnummer toevoegen",
|
|
||||||
"account_basics_phone_numbers_dialog_number_placeholder": "bijv. +1222333444",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Stuur SMS",
|
|
||||||
"account_basics_phone_numbers_dialog_code_label": "Verificatiecode",
|
|
||||||
"account_usage_calls_title": "Aantal telefoontjes",
|
|
||||||
"account_usage_calls_none": "Met dit account kan niet worden gebeld"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,17 +214,5 @@
|
||||||
"login_link_signup": "Registar",
|
"login_link_signup": "Registar",
|
||||||
"action_bar_reservation_add": "Reservar tópico",
|
"action_bar_reservation_add": "Reservar tópico",
|
||||||
"action_bar_sign_up": "Registar",
|
"action_bar_sign_up": "Registar",
|
||||||
"nav_button_account": "Conta",
|
"nav_button_account": "Conta"
|
||||||
"common_copy_to_clipboard": "Copiar",
|
|
||||||
"nav_upgrade_banner_label": "Atualizar para ntfy Pro",
|
|
||||||
"alert_not_supported_context_description": "Notificações são suportadas apenas sobre HTTPS. Essa é uma limitação da <mdnLink>API de Notificações</mdnLink>.",
|
|
||||||
"display_name_dialog_title": "Alterar nome mostrado",
|
|
||||||
"display_name_dialog_description": "Configura um nome alternativo ao tópico que é mostrado na lista de assinaturas. Isto ajuda a identificar tópicos com nomes complicados mais facilmente.",
|
|
||||||
"display_name_dialog_placeholder": "Nome exibido",
|
|
||||||
"reserve_dialog_checkbox_label": "Reservar tópico e configurar acesso",
|
|
||||||
"publish_dialog_call_label": "Chamada telefônica",
|
|
||||||
"publish_dialog_call_placeholder": "Número de telefone para ligar com a mensagem, ex: +12223334444, ou 'Sim'",
|
|
||||||
"publish_dialog_call_reset": "Remover chamada telefônica",
|
|
||||||
"publish_dialog_chip_call_label": "Chamada telefônica",
|
|
||||||
"subscribe_dialog_subscribe_button_generate_topic_name": "Gerar nome"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,30 +355,5 @@
|
||||||
"reservation_delete_dialog_action_keep_title": "Behåll cachade meddelanden och bilagor",
|
"reservation_delete_dialog_action_keep_title": "Behåll cachade meddelanden och bilagor",
|
||||||
"reservation_delete_dialog_action_keep_description": "Meddelanden och bilagor som lagras på servern blir offentligt synliga för personer som känner till ämnesnamnet.",
|
"reservation_delete_dialog_action_keep_description": "Meddelanden och bilagor som lagras på servern blir offentligt synliga för personer som känner till ämnesnamnet.",
|
||||||
"reservation_delete_dialog_action_delete_title": "Ta bort meddelanden och bilagor som sparats i cacheminnet",
|
"reservation_delete_dialog_action_delete_title": "Ta bort meddelanden och bilagor som sparats i cacheminnet",
|
||||||
"reservation_delete_dialog_description": "Om du tar bort en reservation ger du upp äganderätten till ämnet och låter andra reservera det. Du kan behålla eller radera befintliga meddelanden och bilagor.",
|
"reservation_delete_dialog_description": "Om du tar bort en reservation ger du upp äganderätten till ämnet och låter andra reservera det. Du kan behålla eller radera befintliga meddelanden och bilagor."
|
||||||
"publish_dialog_call_label": "Telefonsamtal",
|
|
||||||
"publish_dialog_call_reset": "Ta bort telefonsamtal",
|
|
||||||
"publish_dialog_chip_call_label": "Telefonsamtal",
|
|
||||||
"account_basics_phone_numbers_title": "Telefonnummer",
|
|
||||||
"account_basics_phone_numbers_description": "För notifieringar via telefonsamtal",
|
|
||||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Inga telefonnummer ännu",
|
|
||||||
"account_basics_phone_numbers_copied_to_clipboard": "Telefonnummer kopierat till urklipp",
|
|
||||||
"account_basics_phone_numbers_dialog_title": "Lägga till telefonnummer",
|
|
||||||
"account_basics_phone_numbers_dialog_number_label": "Telefonnummer",
|
|
||||||
"account_basics_phone_numbers_dialog_number_placeholder": "t.ex. +1222333444",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Skicka SMS",
|
|
||||||
"account_basics_phone_numbers_dialog_verify_button_call": "Ring mig",
|
|
||||||
"account_basics_phone_numbers_dialog_code_label": "Verifieringskod",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_call": "Ring",
|
|
||||||
"account_usage_calls_title": "Telefonsamtal som gjorts",
|
|
||||||
"account_usage_calls_none": "Inga telefonsamtal kan göras med detta konto",
|
|
||||||
"publish_dialog_call_item": "Ring telefonnummer {{number}}",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Inga verifierade telefonnummer",
|
|
||||||
"account_basics_phone_numbers_dialog_description": "För att använda funktionen för samtalsavisering måste du lägga till och verifiera minst ett telefonnummer. Verifieringen kan göras via SMS eller ett telefonsamtal.",
|
|
||||||
"account_basics_phone_numbers_dialog_code_placeholder": "t.ex. 123456",
|
|
||||||
"account_basics_phone_numbers_dialog_check_verification_button": "Bekräfta kod",
|
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagliga telefonsamtal",
|
|
||||||
"account_upgrade_dialog_tier_features_no_calls": "Inga telefonsamtal",
|
|
||||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagliga telefonsamtal"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,34 +352,5 @@
|
||||||
"account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, <strong>будь ласка, видаліть принаймні {{count}} резервувань</strong>. Ви можете видалити резервування в <Link>Налаштуваннях</Link>.",
|
"account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, <strong>будь ласка, видаліть принаймні {{count}} резервувань</strong>. Ви можете видалити резервування в <Link>Налаштуваннях</Link>.",
|
||||||
"account_upgrade_dialog_button_cancel": "Скасувати",
|
"account_upgrade_dialog_button_cancel": "Скасувати",
|
||||||
"account_upgrade_dialog_button_redirect_signup": "Зареєструватися зараз",
|
"account_upgrade_dialog_button_redirect_signup": "Зареєструватися зараз",
|
||||||
"account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися",
|
"account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися"
|
||||||
"prefs_reservations_add_button": "Додати зарезервовану тему",
|
|
||||||
"prefs_reservations_edit_button": "Редагувати доступ до теми",
|
|
||||||
"prefs_reservations_limit_reached": "Ви досягли ліміту зарезервованих тем.",
|
|
||||||
"prefs_reservations_table_click_to_subscribe": "Натисніть, щоб підписатися",
|
|
||||||
"prefs_reservations_table_topic_header": "Тема",
|
|
||||||
"prefs_reservations_description": "Тут ви можете зарезервувати назви тем для особистого користування. Резервування теми дає вам право власності на тему і дозволяє визначати права доступу до неї інших користувачів.",
|
|
||||||
"prefs_reservations_table": "Таблиця зарезервованих тем",
|
|
||||||
"prefs_reservations_table_access_header": "Доступ",
|
|
||||||
"prefs_reservations_table_everyone_deny_all": "Тільки я можу публікувати та підписуватись",
|
|
||||||
"prefs_reservations_table_everyone_read_only": "Я можу публікувати та підписуватись, кожен може підписатися",
|
|
||||||
"prefs_reservations_table_everyone_write_only": "Я можу публікувати і підписуватися, кожен може публікувати",
|
|
||||||
"prefs_reservations_table_everyone_read_write": "Кожен може публікувати та підписуватися",
|
|
||||||
"prefs_reservations_table_not_subscribed": "Не підписаний",
|
|
||||||
"prefs_reservations_dialog_title_add": "Зарезервувати тему",
|
|
||||||
"prefs_reservations_dialog_title_edit": "Редагувати зарезервовану тему",
|
|
||||||
"prefs_reservations_title": "Зарезервовані теми",
|
|
||||||
"prefs_reservations_delete_button": "Скинути доступ до теми",
|
|
||||||
"prefs_reservations_dialog_description": "Резервування теми дає вам право власності на цю тему і дозволяє визначати права доступу до неї інших користувачів.",
|
|
||||||
"prefs_reservations_dialog_topic_label": "Тема",
|
|
||||||
"prefs_reservations_dialog_access_label": "Доступ",
|
|
||||||
"reservation_delete_dialog_description": "Видалення резервування позбавляє вас права власності на тему і дозволяє іншим зарезервувати її. Ви можете зберегти або видалити існуючі повідомлення і вкладення.",
|
|
||||||
"reservation_delete_dialog_submit_button": "Видалити резервування",
|
|
||||||
"publish_dialog_call_item": "Телефонувати за номером {{номер}}",
|
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Немає підтверджених номерів телефонів",
|
|
||||||
"prefs_reservations_dialog_title_delete": "Видалити резервування теми",
|
|
||||||
"reservation_delete_dialog_action_delete_title": "Видалення кешованих повідомлень і вкладень",
|
|
||||||
"reservation_delete_dialog_action_keep_title": "Збереження кешованих повідомлень і вкладень",
|
|
||||||
"reservation_delete_dialog_action_keep_description": "Повідомлення і вкладення, які кешуються на сервері, стають загальнодоступними для людей, які знають назву теми.",
|
|
||||||
"reservation_delete_dialog_action_delete_description": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована."
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,13 +47,6 @@ export const useConnectionListeners = (account, subscriptions, users) => {
|
||||||
|
|
||||||
const handleMessage = async (subscriptionId, message) => {
|
const handleMessage = async (subscriptionId, message) => {
|
||||||
const subscription = await subscriptionManager.get(subscriptionId);
|
const subscription = await subscriptionManager.get(subscriptionId);
|
||||||
|
|
||||||
// Race condition: sometimes the subscription is already unsubscribed from account
|
|
||||||
// sync before the message is handled
|
|
||||||
if (!subscription) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscription.internal) {
|
if (subscription.internal) {
|
||||||
await handleInternalMessage(message);
|
await handleInternalMessage(message);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue