Compare commits
29 commits
switch-to-
...
mine
Author | SHA1 | Date | |
---|---|---|---|
8eecd3c72a | |||
77f5dd705c | |||
|
f58c1e4c84 | ||
|
dc8932cd95 | ||
|
04cc71af90 | ||
|
44d189179d | ||
|
d084a415f3 | ||
|
953efbee47 | ||
|
807f24723d | ||
|
453bf435b0 | ||
|
ca25b80bfb | ||
|
afb585e6fd | ||
|
2e7f474775 | ||
|
bd39072596 | ||
|
b222541ea8 | ||
|
1368dae849 | ||
|
578ccf1643 | ||
|
217c660ba0 | ||
|
11f8984127 | ||
|
232c889ce3 | ||
|
02524ca101 | ||
|
38bd4f3ce3 | ||
|
3101f93d22 | ||
|
da17e4ee8a | ||
|
d178be7576 | ||
|
4d90e32fe9 | ||
|
9056d68fc9 | ||
|
c16da26780 | ||
|
c50633d990 |
27 changed files with 392 additions and 170 deletions
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
dist
|
||||
*/node_modules
|
||||
Dockerfile*
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
dist/
|
||||
dev-dist/
|
||||
build/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine
|
||||
FROM r.batts.cloud/debian:testing
|
||||
|
||||
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
|
||||
LABEL org.opencontainers.image.url="https://ntfy.sh/"
|
||||
|
|
54
Dockerfile-build
Normal file
54
Dockerfile-build
Normal file
|
@ -0,0 +1,54 @@
|
|||
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,12 +31,16 @@ help:
|
|||
@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
|
||||
@echo "Build dev Docker:"
|
||||
@echo " make docker-dev - Build client & server for current architecture using Docker only"
|
||||
@echo
|
||||
@echo "Build web app:"
|
||||
@echo " make web - Build the web app"
|
||||
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
||||
@echo " make web-build - Actually build 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-lint - Run eslint 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
|
||||
@echo "Build documentation:"
|
||||
@echo " make docs - Build the documentation"
|
||||
|
@ -82,23 +86,33 @@ build: web docs cli
|
|||
update: web-deps-update cli-deps-update docs-deps-update
|
||||
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
|
||||
|
||||
build-deps-ubuntu:
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
curl \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabi \
|
||||
jq
|
||||
which pip3 || sudo apt install -y python3-pip
|
||||
which pip3 || sudo apt-get install -y python3-pip
|
||||
|
||||
# Documentation
|
||||
|
||||
docs: docs-deps docs-build
|
||||
|
||||
docs-build: .PHONY
|
||||
@if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
||||
docs-build: venv .PHONY
|
||||
@. venv/bin/activate && \
|
||||
if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
||||
if which python3.8; then \
|
||||
echo "python3.8 $(shell which mkdocs) build"; \
|
||||
python3.8 $(shell which mkdocs) build; \
|
||||
|
@ -111,10 +125,15 @@ docs-build: .PHONY
|
|||
mkdocs build; \
|
||||
fi
|
||||
|
||||
docs-deps: .PHONY
|
||||
venv:
|
||||
python3 -m venv ./venv
|
||||
|
||||
docs-deps: venv .PHONY
|
||||
. venv/bin/activate && \
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
docs-deps-update: .PHONY
|
||||
docs-deps-update: venv .PHONY
|
||||
. venv/bin/activate && \
|
||||
pip3 install -r requirements.txt --upgrade
|
||||
|
||||
|
||||
|
|
|
@ -11,23 +11,25 @@ import (
|
|||
"heckel.io/ntfy/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event type constants
|
||||
const (
|
||||
MessageEvent = "message"
|
||||
KeepaliveEvent = "keepalive"
|
||||
OpenEvent = "open"
|
||||
PollRequestEvent = "poll_request"
|
||||
// MessageEvent identifies a message event
|
||||
MessageEvent = "message"
|
||||
)
|
||||
|
||||
const (
|
||||
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
|
||||
type Client struct {
|
||||
Messages chan *Message
|
||||
|
@ -96,8 +98,14 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) (*Mess
|
|||
// To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache,
|
||||
// WithNoFirebase, and the generic WithHeader.
|
||||
func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) {
|
||||
topicURL := c.expandTopicURL(topic)
|
||||
req, _ := http.NewRequest("POST", topicURL, body)
|
||||
topicURL, err := c.expandTopicURL(topic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", topicURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, option := range options {
|
||||
if err := option(req); err != nil {
|
||||
return nil, err
|
||||
|
@ -133,11 +141,14 @@ 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.
|
||||
// See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam.
|
||||
func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) {
|
||||
topicURL, err := c.expandTopicURL(topic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := context.Background()
|
||||
messages := make([]*Message, 0)
|
||||
msgChan := make(chan *Message)
|
||||
errChan := make(chan error)
|
||||
topicURL := c.expandTopicURL(topic)
|
||||
log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL))
|
||||
options = append(options, WithPoll())
|
||||
go func() {
|
||||
|
@ -166,15 +177,18 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err
|
|||
// Example:
|
||||
//
|
||||
// c := client.New(client.NewConfig())
|
||||
// subscriptionID := c.Subscribe("mytopic")
|
||||
// subscriptionID, _ := c.Subscribe("mytopic")
|
||||
// for m := range c.Messages {
|
||||
// fmt.Printf("New message: %s", m.Message)
|
||||
// }
|
||||
func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
|
||||
func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, error) {
|
||||
topicURL, err := c.expandTopicURL(topic)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
subscriptionID := util.RandomString(10)
|
||||
topicURL := c.expandTopicURL(topic)
|
||||
log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c.subscriptions[subscriptionID] = &subscription{
|
||||
|
@ -183,7 +197,7 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
|
|||
cancel: cancel,
|
||||
}
|
||||
go handleSubscribeConnLoop(ctx, c.Messages, topicURL, subscriptionID, options...)
|
||||
return subscriptionID
|
||||
return subscriptionID, nil
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes from a topic that has been previously subscribed to using the unique
|
||||
|
@ -199,31 +213,16 @@ func (c *Client) Unsubscribe(subscriptionID string) {
|
|||
sub.cancel()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (c *Client) expandTopicURL(topic string) (string, error) {
|
||||
if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") {
|
||||
return topic
|
||||
return topic, nil
|
||||
} else if strings.Contains(topic, "/") {
|
||||
return fmt.Sprintf("https://%s", topic)
|
||||
return fmt.Sprintf("https://%s", topic), nil
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic)
|
||||
if !topicRegex.MatchString(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) {
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestClient_Publish_Subscribe(t *testing.T) {
|
|||
defer test.StopServer(t, s, port)
|
||||
c := client.New(newTestConfig(port))
|
||||
|
||||
subscriptionID := c.Subscribe("mytopic")
|
||||
subscriptionID, _ := c.Subscribe("mytopic")
|
||||
time.Sleep(time.Second)
|
||||
|
||||
msg, err := c.Publish("mytopic", "some message")
|
||||
|
|
|
@ -72,7 +72,7 @@ ntfy subscribe TOPIC COMMAND
|
|||
$NTFY_TITLE $title, $t Message title
|
||||
$NTFY_PRIORITY $priority, $prio, $p Message priority (1=min, 5=max)
|
||||
$NTFY_TAGS $tags, $tag, $ta Message tags (comma separated list)
|
||||
$NTFY_RAW $raw Raw JSON message
|
||||
$NTFY_RAW $raw Raw JSON message
|
||||
|
||||
Examples:
|
||||
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages
|
||||
|
@ -194,7 +194,10 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
|||
topicOptions = append(topicOptions, auth)
|
||||
}
|
||||
|
||||
subscriptionID := cl.Subscribe(s.Topic, topicOptions...)
|
||||
subscriptionID, err := cl.Subscribe(s.Topic, topicOptions...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Command != "" {
|
||||
cmds[subscriptionID] = s.Command
|
||||
} else if conf.DefaultCommand != "" {
|
||||
|
@ -204,7 +207,10 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
|||
}
|
||||
}
|
||||
if topic != "" {
|
||||
subscriptionID := cl.Subscribe(topic, options...)
|
||||
subscriptionID, err := cl.Subscribe(topic, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmds[subscriptionID] = command
|
||||
}
|
||||
for m := range cl.Messages {
|
||||
|
|
|
@ -163,6 +163,15 @@ $ make release-snapshot
|
|||
|
||||
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
|
||||
To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets:
|
||||
|
||||
|
|
|
@ -1225,9 +1225,12 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
|||
**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)
|
||||
* 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:**
|
||||
|
||||
* Improved GitHub Actions flow ([#745](https://github.com/binwiederhier/ntfy/pull/745), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
* Web: Add JS formatter "prettier" ([#746](https://github.com/binwiederhier/ntfy/pull/746), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
|
|
|
@ -760,7 +760,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
|||
if s.config.TwilioAccount != "" && call != "" {
|
||||
go s.callPhone(v, r, m, call)
|
||||
}
|
||||
if s.config.UpstreamBaseURL != "" {
|
||||
if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream
|
||||
go s.forwardPollRequest(v, m)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -2559,6 +2559,29 @@ 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 {
|
||||
conf := NewConfig()
|
||||
conf.BaseURL = "http://127.0.0.1:12345"
|
||||
|
|
64
web/package-lock.json
generated
64
web/package-lock.json
generated
|
@ -405,25 +405,6 @@
|
|||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
|
||||
|
@ -1708,6 +1689,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
|
@ -2084,11 +2073,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
|
@ -2472,18 +2464,6 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eslint/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/globals": {
|
||||
"version": "13.20.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
|
||||
|
@ -4202,9 +4182,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||
"integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==",
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -4240,6 +4220,14 @@
|
|||
"stackframe": "^1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/stacktrace-gps/node_modules/source-map": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||
"integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stacktrace-js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz",
|
||||
|
|
|
@ -355,5 +355,15 @@
|
|||
"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_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,5 +355,31 @@
|
|||
"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_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,5 +379,7 @@
|
|||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} panggilan telepon harian",
|
||||
"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,5 +355,30 @@
|
|||
"prefs_reservations_table_topic_header": "Onderwerp",
|
||||
"prefs_reservations_table_access_header": "Toegang",
|
||||
"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,5 +214,17 @@
|
|||
"login_link_signup": "Registar",
|
||||
"action_bar_reservation_add": "Reservar tópico",
|
||||
"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,5 +355,30 @@
|
|||
"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_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,5 +352,34 @@
|
|||
"account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, <strong>будь ласка, видаліть принаймні {{count}} резервувань</strong>. Ви можете видалити резервування в <Link>Налаштуваннях</Link>.",
|
||||
"account_upgrade_dialog_button_cancel": "Скасувати",
|
||||
"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": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована."
|
||||
}
|
||||
|
|
|
@ -61,9 +61,7 @@ class ConnectionManager {
|
|||
const { connectionId } = subscription;
|
||||
const added = !this.connections.get(connectionId);
|
||||
if (added) {
|
||||
const { baseUrl } = subscription;
|
||||
const { topic } = subscription;
|
||||
const { user } = subscription;
|
||||
const { baseUrl, topic, user } = subscription;
|
||||
const since = subscription.last;
|
||||
const connection = new Connection(
|
||||
connectionId,
|
||||
|
|
|
@ -21,15 +21,16 @@ class Poller {
|
|||
async pollAll() {
|
||||
console.log(`[Poller] Polling all subscriptions`);
|
||||
const subscriptions = await subscriptionManager.all();
|
||||
for (const s of subscriptions) {
|
||||
try {
|
||||
// TODO(eslint): Switch to Promise.all
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.poll(s);
|
||||
} catch (e) {
|
||||
console.log(`[Poller] Error polling ${s.id}`, e);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
subscriptions.map(async (s) => {
|
||||
try {
|
||||
await this.poll(s);
|
||||
} catch (e) {
|
||||
console.log(`[Poller] Error polling ${s.id}`, e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async poll(subscription) {
|
||||
|
|
|
@ -5,13 +5,12 @@ class SubscriptionManager {
|
|||
/** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */
|
||||
async all() {
|
||||
const subscriptions = await db.subscriptions.toArray();
|
||||
await Promise.all(
|
||||
subscriptions.map(async (s) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count();
|
||||
})
|
||||
return Promise.all(
|
||||
subscriptions.map(async (s) => ({
|
||||
...s,
|
||||
new: await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(),
|
||||
}))
|
||||
);
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
async get(subscriptionId) {
|
||||
|
@ -40,33 +39,31 @@ class SubscriptionManager {
|
|||
console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions);
|
||||
|
||||
// Add remote subscriptions
|
||||
const remoteIds = []; // = topicUrl(baseUrl, topic)
|
||||
for (let i = 0; i < remoteSubscriptions.length; i += 1) {
|
||||
const remote = remoteSubscriptions[i];
|
||||
// TODO(eslint): Switch to Promise.all
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const local = await this.add(remote.base_url, remote.topic, false);
|
||||
const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null;
|
||||
// TODO(eslint): Switch to Promise.all
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.update(local.id, {
|
||||
displayName: remote.display_name, // May be undefined
|
||||
reservation, // May be null!
|
||||
});
|
||||
remoteIds.push(local.id);
|
||||
}
|
||||
const remoteIds = await Promise.all(
|
||||
remoteSubscriptions.map(async (remote) => {
|
||||
const local = await this.add(remote.base_url, remote.topic, false);
|
||||
const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null;
|
||||
|
||||
await this.update(local.id, {
|
||||
displayName: remote.display_name, // May be undefined
|
||||
reservation, // May be null!
|
||||
});
|
||||
|
||||
return local.id;
|
||||
})
|
||||
);
|
||||
|
||||
// Remove local subscriptions that do not exist remotely
|
||||
const localSubscriptions = await db.subscriptions.toArray();
|
||||
for (let i = 0; i < localSubscriptions.length; i += 1) {
|
||||
const local = localSubscriptions[i];
|
||||
const remoteExists = remoteIds.includes(local.id);
|
||||
if (!local.internal && !remoteExists) {
|
||||
// TODO(eslint): Switch to Promise.all
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.remove(local.id);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
localSubscriptions.map(async (local) => {
|
||||
const remoteExists = remoteIds.includes(local.id);
|
||||
if (!local.internal && !remoteExists) {
|
||||
await this.remove(local.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async updateState(subscriptionId, state) {
|
||||
|
@ -108,9 +105,12 @@ class SubscriptionManager {
|
|||
return false;
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
||||
await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
|
||||
await db.notifications.add({
|
||||
...notification,
|
||||
subscriptionId,
|
||||
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
||||
new: 1,
|
||||
}); // FIXME consider put() for double tab
|
||||
await db.subscriptions.update(subscriptionId, {
|
||||
last: notification.id,
|
||||
});
|
||||
|
|
|
@ -118,10 +118,10 @@ export const maybeWithBearerAuth = (headers, token) => {
|
|||
export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) });
|
||||
|
||||
export const maybeWithAuth = (headers, user) => {
|
||||
if (user && user.password) {
|
||||
if (user?.password) {
|
||||
return withBasicAuth(headers, user.username, user.password);
|
||||
}
|
||||
if (user && user.token) {
|
||||
if (user?.token) {
|
||||
return withBearerAuth(headers, user.token);
|
||||
}
|
||||
return headers;
|
||||
|
@ -139,17 +139,14 @@ export const maybeAppendActionErrors = (message, notification) => {
|
|||
};
|
||||
|
||||
export const shuffle = (arr) => {
|
||||
let j;
|
||||
let x;
|
||||
for (let index = arr.length - 1; index > 0; index -= 1) {
|
||||
j = Math.floor(Math.random() * (index + 1));
|
||||
x = arr[index];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
arr[index] = arr[j];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
arr[j] = x;
|
||||
const returnArr = [...arr];
|
||||
|
||||
for (let index = returnArr.length - 1; index > 0; index -= 1) {
|
||||
const j = Math.floor(Math.random() * (index + 1));
|
||||
[returnArr[index], returnArr[j]] = [returnArr[j], returnArr[index]];
|
||||
}
|
||||
return arr;
|
||||
|
||||
return returnArr;
|
||||
};
|
||||
|
||||
export const splitNoEmpty = (s, delimiter) =>
|
||||
|
|
|
@ -127,17 +127,7 @@ const Category = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const emojiMatches = (emoji, words) => {
|
||||
if (words.length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (const word of words) {
|
||||
if (emoji.searchBase.indexOf(word) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const emojiMatches = (emoji, words) => words.length === 0 || words.some((word) => emoji.searchBase.includes(word));
|
||||
|
||||
const Emoji = (props) => {
|
||||
const { emoji } = props;
|
||||
|
|
|
@ -436,15 +436,10 @@ const ACTION_LABEL_SUFFIX = {
|
|||
};
|
||||
|
||||
const updateActionStatus = (notification, action, progress, error) => {
|
||||
// TODO(eslint): Fix by spreading? Does the code depend on the change, though?
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
notification.actions = notification.actions.map((a) => {
|
||||
if (a.id !== action.id) {
|
||||
return a;
|
||||
}
|
||||
return { ...a, progress, error };
|
||||
subscriptionManager.updateNotification({
|
||||
...notification,
|
||||
actions: notification.actions.map((a) => (a.id === action.id ? { ...a, progress, error } : a)),
|
||||
});
|
||||
subscriptionManager.updateNotification(notification);
|
||||
};
|
||||
|
||||
const performHttpAction = async (notification, action) => {
|
||||
|
|
|
@ -47,6 +47,13 @@ export const useConnectionListeners = (account, subscriptions, users) => {
|
|||
|
||||
const handleMessage = async (subscriptionId, message) => {
|
||||
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) {
|
||||
await handleInternalMessage(message);
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue