diff --git a/.gitignore b/.gitignore index 932e0fcd..9f514857 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 a9a9a201..06b4c745 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 7e4c551a..9edcc86c 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 5f1267d7..00000000 --- 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 92312fea..00000000 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 6b969a84..00000000 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 953f65b2..00000000 --- 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 3283db2e..00000000 --- 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 a11777cc..00000000 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 9fec14cf..f054f83a 100644 --- a/server/index.gohtml +++ b/web/public/home.html @@ -1,11 +1,10 @@ -{{- /*gotype: heckel.io/ntfy/server.indexPage*/ -}}