diff --git a/.gitignore b/.gitignore index 932e0fc..9f51485 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist/ build/ .idea/ server/docs/ +server/site/ tools/fbsend/fbsend playground/ *.iml diff --git a/Makefile b/Makefile index a9a9a20..06b4c74 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,28 @@ docs-deps: .PHONY docs: docs-deps mkdocs build + +# Web app + +web-deps: + cd web && npm install + +web-build: + cd web \ + && npm run build \ + && mv build/index.html build/app.html \ + && rm -rf ../server/site \ + && mv build ../server/site \ + && rm \ + ../server/site/precache* \ + ../server/site/service-worker.js \ + ../server/site/asset-manifest.json \ + ../server/site/static/js/*.js.map \ + ../server/site/static/js/*.js.LICENSE.txt + +web: web-deps web-build + + # Test/check targets check: test fmt-check vet lint staticcheck @@ -94,7 +116,7 @@ staticcheck: .PHONY # Building targets -build-deps: docs +build-deps: docs web which arm-linux-gnueabi-gcc || { echo "ERROR: ARMv6/v7 cross compiler not installed. On Ubuntu, run: apt install gcc-arm-linux-gnueabi"; exit 1; } which aarch64-linux-gnu-gcc || { echo "ERROR: ARM64 cross compiler not installed. On Ubuntu, run: apt install gcc-aarch64-linux-gnu"; exit 1; } @@ -105,8 +127,9 @@ build-snapshot: build-deps goreleaser build --snapshot --rm-dist --debug build-simple: clean - mkdir -p dist/ntfy_linux_amd64 server/docs - touch server/docs/dummy + mkdir -p dist/ntfy_linux_amd64 server/docs server/site + touch server/docs/index.html + touch server/site/app.html export CGO_ENABLED=1 go build \ -o dist/ntfy_linux_amd64/ntfy \ diff --git a/server/server.go b/server/server.go index 7e4c551..9edcc86 100644 --- a/server/server.go +++ b/server/server.go @@ -13,7 +13,6 @@ import ( "golang.org/x/sync/errgroup" "heckel.io/ntfy/auth" "heckel.io/ntfy/util" - "html/template" "io" "log" "net" @@ -61,35 +60,31 @@ type handleFunc func(http.ResponseWriter, *http.Request, *visitor) error var ( // If changed, don't forget to update Android App and auth_sqlite.go - topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No /! - topicPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}$`) // Regex must match JS & Android app! - jsonPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`) - ssePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`) - rawPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`) - wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`) - authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`) - publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`) + topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No /! + topicPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}$`) // Regex must match JS & Android app! + extTopicPathRegex = regexp.MustCompile(`^/[^/]+\.[^/]+/[-_A-Za-z0-9]{1,64}$`) // Extended topic path, for web-app, e.g. /example.com/mytopic + jsonPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`) + ssePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`) + rawPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`) + wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`) + authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`) + publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`) staticRegex = regexp.MustCompile(`^/static/.+`) docsRegex = regexp.MustCompile(`^/docs(|/.*)$`) fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`) - disallowedTopics = []string{"docs", "static", "file"} // If updated, also update in Android app + disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app attachURLRegex = regexp.MustCompile(`^https?://`) - templateFnMap = template.FuncMap{ - "durationToHuman": util.DurationToHuman, - } - - //go:embed "index.gohtml" - indexSource string - indexTemplate = template.Must(template.New("index").Funcs(templateFnMap).Parse(indexSource)) - //go:embed "example.html" exampleSource string - //go:embed static - webStaticFs embed.FS - webStaticFsCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: webStaticFs} + //go:embed site + webFs embed.FS + webFsCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: webFs} + webSiteDir = "/site" + webHomeIndex = "/home.html" // Landing page, only if "web-index: home" + webAppIndex = "/app.html" // React app //go:embed docs docsStaticFs embed.FS @@ -284,8 +279,6 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit return s.limitRequests(s.handleFile)(w, r, v) } else if r.Method == http.MethodOptions { return s.handleOptions(w, r) - } else if r.Method == http.MethodGet && topicPathRegex.MatchString(r.URL.Path) { - return s.handleTopic(w, r) } else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && topicPathRegex.MatchString(r.URL.Path) { return s.limitRequests(s.authWrite(s.handlePublish))(w, r, v) } else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) { @@ -300,15 +293,15 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit return s.limitRequests(s.authRead(s.handleSubscribeWS))(w, r, v) } else if r.Method == http.MethodGet && authPathRegex.MatchString(r.URL.Path) { return s.limitRequests(s.authRead(s.handleTopicAuth))(w, r, v) + } else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || extTopicPathRegex.MatchString(r.URL.Path)) { + return s.handleTopic(w, r) } return errHTTPNotFound } func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) error { - return indexTemplate.Execute(w, &indexPage{ - Topic: r.URL.Path[1:], - CacheDuration: s.config.CacheDuration, - }) + r.URL.Path = webHomeIndex + return s.handleStatic(w, r) } func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error { @@ -319,7 +312,8 @@ func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error { _, err := io.WriteString(w, `{"unifiedpush":{"version":1}}`+"\n") return err } - return s.handleHome(w, r) + r.URL.Path = webAppIndex + return s.handleStatic(w, r) } func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error { @@ -339,7 +333,8 @@ func (s *Server) handleExample(w http.ResponseWriter, _ *http.Request) error { } func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) error { - http.FileServer(http.FS(webStaticFsCached)).ServeHTTP(w, r) + r.URL.Path = webSiteDir + r.URL.Path + http.FileServer(http.FS(webFsCached)).ServeHTTP(w, r) return nil } diff --git a/server/static/img/close.svg b/server/static/img/close.svg deleted file mode 100644 index 5f1267d..0000000 --- a/server/static/img/close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/server/static/img/favicon.png b/server/static/img/favicon.png deleted file mode 100644 index 92312fe..0000000 Binary files a/server/static/img/favicon.png and /dev/null differ diff --git a/server/static/img/ntfy.png b/server/static/img/ntfy.png deleted file mode 100644 index 6b969a8..0000000 Binary files a/server/static/img/ntfy.png and /dev/null differ diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 953f65b..0000000 --- a/web/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Create React App example - -## How to use - -Download the example [or clone the repo](https://github.com/mui/material-ui): - - - -```sh -curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/create-react-app -cd create-react-app -``` - -Install it and run: - -```sh -npm install -npm start -``` - -or: - - - -[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/mui/material-ui/tree/master/examples/create-react-app) - - - -[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/create-react-app) - -## The idea behind the example - - - -This example demonstrates how you can use [Create React App](https://github.com/facebookincubator/create-react-app). -It includes `@mui/material` and its peer dependencies, including `emotion`, the default style engine in MUI v5. -If you prefer, you can [use styled-components instead](https://mui.com/guides/interoperability/#styled-components). - -## What's next? - - - -You now have a working example project. -You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/getting-started/templates/) section. diff --git a/web/public/config.js b/web/public/config.js deleted file mode 100644 index 3283db2..0000000 --- a/web/public/config.js +++ /dev/null @@ -1,3 +0,0 @@ -var config = { - defaultBaseUrl: 'https://ntfy.sh' -}; diff --git a/web/public/favicon.ico b/web/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/web/public/favicon.ico and /dev/null differ diff --git a/server/index.gohtml b/web/public/home.html similarity index 96% rename from server/index.gohtml rename to web/public/home.html index 9fec14c..f054f83 100644 --- a/server/index.gohtml +++ b/web/public/home.html @@ -1,11 +1,10 @@ -{{- /*gotype: heckel.io/ntfy/server.indexPage*/ -}} ntfy.sh | Send push notifications to your phone via PUT/POST - + @@ -37,9 +36,9 @@
ntfy
  1. Getting started
  2. +
  3. Web app
  4. Android/iOS
  5. API
  6. -
  7. Self-hosting
  8. GitHub
@@ -90,7 +89,7 @@ Here's what that looks like in the Android app:

- +
Urgent notification with pop-over
@@ -170,7 +169,6 @@
Made with ❤️ by Philipp C. Heckel
- - + diff --git a/web/public/index.html b/web/public/index.html index 3512b54..19e1450 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -20,18 +20,15 @@ - - - + + + - - - diff --git a/web/public/manifest.json b/web/public/manifest.json deleted file mode 100644 index f99717a..0000000 --- a/web/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "Your Orders", - "name": "Your Orders", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/server/static/css/app.css b/web/public/static/css/home.css similarity index 100% rename from server/static/css/app.css rename to web/public/static/css/home.css diff --git a/server/static/font/roboto-v29-latin-300.woff b/web/public/static/font/roboto-v29-latin-300.woff similarity index 100% rename from server/static/font/roboto-v29-latin-300.woff rename to web/public/static/font/roboto-v29-latin-300.woff diff --git a/server/static/font/roboto-v29-latin-300.woff2 b/web/public/static/font/roboto-v29-latin-300.woff2 similarity index 100% rename from server/static/font/roboto-v29-latin-300.woff2 rename to web/public/static/font/roboto-v29-latin-300.woff2 diff --git a/server/static/font/roboto-v29-latin-500.woff b/web/public/static/font/roboto-v29-latin-500.woff similarity index 100% rename from server/static/font/roboto-v29-latin-500.woff rename to web/public/static/font/roboto-v29-latin-500.woff diff --git a/server/static/font/roboto-v29-latin-500.woff2 b/web/public/static/font/roboto-v29-latin-500.woff2 similarity index 100% rename from server/static/font/roboto-v29-latin-500.woff2 rename to web/public/static/font/roboto-v29-latin-500.woff2 diff --git a/server/static/font/roboto-v29-latin-regular.woff b/web/public/static/font/roboto-v29-latin-regular.woff similarity index 100% rename from server/static/font/roboto-v29-latin-regular.woff rename to web/public/static/font/roboto-v29-latin-regular.woff diff --git a/server/static/font/roboto-v29-latin-regular.woff2 b/web/public/static/font/roboto-v29-latin-regular.woff2 similarity index 100% rename from server/static/font/roboto-v29-latin-regular.woff2 rename to web/public/static/font/roboto-v29-latin-regular.woff2 diff --git a/server/static/img/android-video-overview.mp4 b/web/public/static/img/android-video-overview.mp4 similarity index 100% rename from server/static/img/android-video-overview.mp4 rename to web/public/static/img/android-video-overview.mp4 diff --git a/server/static/img/android-video-subscribe-api.mp4 b/web/public/static/img/android-video-subscribe-api.mp4 similarity index 100% rename from server/static/img/android-video-subscribe-api.mp4 rename to web/public/static/img/android-video-subscribe-api.mp4 diff --git a/server/static/img/badge-appstore.png b/web/public/static/img/badge-appstore.png similarity index 100% rename from server/static/img/badge-appstore.png rename to web/public/static/img/badge-appstore.png diff --git a/server/static/img/badge-fdroid.png b/web/public/static/img/badge-fdroid.png similarity index 100% rename from server/static/img/badge-fdroid.png rename to web/public/static/img/badge-fdroid.png diff --git a/server/static/img/badge-googleplay.png b/web/public/static/img/badge-googleplay.png similarity index 100% rename from server/static/img/badge-googleplay.png rename to web/public/static/img/badge-googleplay.png diff --git a/server/static/img/basic-notification.png b/web/public/static/img/basic-notification.png similarity index 100% rename from server/static/img/basic-notification.png rename to web/public/static/img/basic-notification.png diff --git a/server/static/img/screenshot-curl.png b/web/public/static/img/screenshot-curl.png similarity index 100% rename from server/static/img/screenshot-curl.png rename to web/public/static/img/screenshot-curl.png diff --git a/server/static/img/screenshot-docs.png b/web/public/static/img/screenshot-docs.png similarity index 100% rename from server/static/img/screenshot-docs.png rename to web/public/static/img/screenshot-docs.png diff --git a/server/static/img/screenshot-phone-add.jpg b/web/public/static/img/screenshot-phone-add.jpg similarity index 100% rename from server/static/img/screenshot-phone-add.jpg rename to web/public/static/img/screenshot-phone-add.jpg diff --git a/server/static/img/screenshot-phone-detail.jpg b/web/public/static/img/screenshot-phone-detail.jpg similarity index 100% rename from server/static/img/screenshot-phone-detail.jpg rename to web/public/static/img/screenshot-phone-detail.jpg diff --git a/server/static/img/screenshot-phone-main.jpg b/web/public/static/img/screenshot-phone-main.jpg similarity index 100% rename from server/static/img/screenshot-phone-main.jpg rename to web/public/static/img/screenshot-phone-main.jpg diff --git a/server/static/img/screenshot-phone-notification.jpg b/web/public/static/img/screenshot-phone-notification.jpg similarity index 100% rename from server/static/img/screenshot-phone-notification.jpg rename to web/public/static/img/screenshot-phone-notification.jpg diff --git a/server/static/img/priority-notification.png b/web/public/static/img/screenshot-phone-popover.png similarity index 100% rename from server/static/img/priority-notification.png rename to web/public/static/img/screenshot-phone-popover.png diff --git a/server/static/img/screenshot-web-detail.png b/web/public/static/img/screenshot-web-detail.png similarity index 100% rename from server/static/img/screenshot-web-detail.png rename to web/public/static/img/screenshot-web-detail.png diff --git a/server/static/js/app.js b/web/public/static/js/home.js similarity index 100% rename from server/static/js/app.js rename to web/public/static/js/home.js diff --git a/web/src/app/config.js b/web/src/app/config.js index 71a9ece..1976d79 100644 --- a/web/src/app/config.js +++ b/web/src/app/config.js @@ -1,2 +1,5 @@ -const config = window.config; +//const config = window.config; +const config = { + defaultBaseUrl: "https://ntfy.sh" +}; export default config; diff --git a/web/src/components/App.js b/web/src/components/App.js index c39fd41..7834de4 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -21,7 +21,6 @@ import {BrowserRouter, Route, Routes, useLocation, useNavigate} from "react-rout import {subscriptionRoute} from "../app/utils"; // TODO support unsubscribed routes -// TODO embed into ntfy server // TODO googlefonts // TODO new notification indicator // TODO sound diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js index d64ef0a..38e8c9c 100644 --- a/web/src/components/Notifications.js +++ b/web/src/components/Notifications.js @@ -251,7 +251,7 @@ const NothingHereYet = (props) => { return ( - No notifications
+ No notifications
You haven't received any notifications for this topic yet.
diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js index c3a362d..aa2de67 100644 --- a/web/src/components/SubscribeDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -109,6 +109,7 @@ const SubscribePage = (props) => { margin="dense" id="topic" placeholder="Topic name, e.g. phil_alerts" + inputProps={{ maxLength: 64 }} value={props.topic} onChange={ev => props.setTopic(ev.target.value)} type="text"