diff --git a/server/errors.go b/server/errors.go index 695da852..6051a3f5 100644 --- a/server/errors.go +++ b/server/errors.go @@ -34,7 +34,6 @@ var ( errHTTPBadRequestTopicInvalid = &errHTTP{40009, http.StatusBadRequest, "invalid topic: path invalid", ""} errHTTPBadRequestTopicDisallowed = &errHTTP{40010, http.StatusBadRequest, "invalid topic: topic name is disallowed", ""} errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid message: message must be UTF-8 encoded", ""} - errHTTPBadRequestAttachmentTooLarge = &errHTTP{40012, http.StatusBadRequest, "invalid request: attachment too large, or bandwidth limit reached", ""} errHTTPBadRequestAttachmentURLInvalid = &errHTTP{40013, http.StatusBadRequest, "invalid request: attachment URL is invalid", "https://ntfy.sh/docs/publish/#attachments"} errHTTPBadRequestAttachmentsDisallowed = &errHTTP{40014, http.StatusBadRequest, "invalid request: attachments not allowed", "https://ntfy.sh/docs/config/#attachments"} errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", "https://ntfy.sh/docs/publish/#scheduled-delivery"} @@ -43,6 +42,7 @@ var ( errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"} + errHTTPEntityTooLargeAttachmentTooLarge = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", ""} errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} diff --git a/server/server.go b/server/server.go index 726d5a4d..b041f697 100644 --- a/server/server.go +++ b/server/server.go @@ -395,6 +395,7 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito if err != nil { return err } + return errHTTPEntityTooLargeAttachmentTooLarge body, err := util.Peak(r.Body, s.config.MessageLimit) if err != nil { return err @@ -590,7 +591,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, if contentLengthStr != "" { // Early "do-not-trust" check, hard limit see below contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64) if err == nil && (contentLength > remainingVisitorAttachmentSize || contentLength > s.config.AttachmentFileSizeLimit) { - return errHTTPBadRequestAttachmentTooLarge + return errHTTPEntityTooLargeAttachmentTooLarge } } if m.Attachment == nil { @@ -609,7 +610,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, } m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(remainingVisitorAttachmentSize)) if err == util.ErrLimitReached { - return errHTTPBadRequestAttachmentTooLarge + return errHTTPEntityTooLargeAttachmentTooLarge } else if err != nil { return err } diff --git a/web/src/app/Api.js b/web/src/app/Api.js index 99f3f763..56fb9007 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -52,19 +52,16 @@ class Api { const send = new Promise(function (resolve, reject) { xhr.open("PUT", url); xhr.addEventListener('readystatechange', (ev) => { + console.log("read change", xhr.readyState, xhr.status, xhr.responseText, xhr) if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) { - console.log(`[Api] Publish successful`, ev); + console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response); resolve(xhr.response); } else if (xhr.readyState === 4) { - console.log(`[Api] Publish failed (1)`, ev); + console.log(`[Api] Publish failed`, xhr.status, xhr.responseText, xhr); xhr.abort(); reject(ev); } }) - xhr.onerror = (ev) => { - console.log(`[Api] Publish failed (2)`, ev); - reject(ev); - }; xhr.upload.addEventListener("progress", onProgress); if (body.type) { xhr.overrideMimeType(body.type); diff --git a/web/src/components/App.js b/web/src/components/App.js index 7ee3242d..4dcb9990 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -82,7 +82,6 @@ const Layout = () => { return ( - setMobileDrawerOpen(!mobileDrawerOpen)} @@ -99,7 +98,7 @@ const Layout = () => { - + ); } @@ -125,79 +124,28 @@ const Main = (props) => { ); }; -const Sender = (props) => { +const Messaging = (props) => { const [message, setMessage] = useState(""); - const [sendDialogKey, setSendDialogKey] = useState(0); - const [sendDialogOpen, setSendDialogOpen] = useState(false); - const subscription = props.selected; - - const handleSendClick = () => { - api.publish(subscription.baseUrl, subscription.topic, message); // FIXME - setMessage(""); - }; - - const handleSendDialogClose = () => { - setSendDialogOpen(false); - setSendDialogKey(prev => prev+1); - }; - - if (!props.selected) { - return null; - } - - return ( - theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] - }} - > - setSendDialogOpen(true)}> - - - setMessage(ev.target.value)} - onKeyPress={(ev) => { - if (ev.key === 'Enter') { - ev.preventDefault(); - handleSendClick(); - } - }} - /> - - - - - - ); -}; - -const DropZone = (props) => { + const [dialogKey, setDialogKey] = useState(0); + const [showDialog, setShowDialog] = useState(false); const [showDropZone, setShowDropZone] = useState(false); + const subscription = props.selected; + const selectedTopicUrl = (subscription) ? topicUrl(subscription.baseUrl, subscription.topic) : ""; + useEffect(() => { - window.addEventListener('dragenter', () => setShowDropZone(true)); + window.addEventListener('dragenter', () => { + setShowDialog(true); + setShowDropZone(true); + }); }, []); + const handleSendDialogClose = () => { + setShowDialog(false); + setShowDropZone(false); + setDialogKey(prev => prev+1); + }; + const allowSubmit = () => true; const allowDrag = (e) => { @@ -212,22 +160,68 @@ const DropZone = (props) => { console.log(e.dataTransfer.files[0]); }; - if (!showDropZone) { - return null; - } - return ( - setShowDropZone(false)} - onDragEnter={allowDrag} - onDragOver={allowDrag} - onDragLeave={() => setShowDropZone(false)} - onDrop={handleDrop} - > + <> + {subscription && setShowDialog(true)} + />} + + + ); +} - +const MessageBar = (props) => { + const subscription = props.subscription; + const handleSendClick = () => { + api.publish(subscription.baseUrl, subscription.topic, props.message); // FIXME + props.onMessageChange(""); + }; + return ( + theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] + }} + > + + + + props.onMessageChange(ev.target.value)} + onKeyPress={(ev) => { + if (ev.key === 'Enter') { + ev.preventDefault(); + handleSendClick(); + } + }} + /> + + + + ); }; diff --git a/web/src/components/SendDialog.js b/web/src/components/SendDialog.js index 10bdd397..488e01f4 100644 --- a/web/src/components/SendDialog.js +++ b/web/src/components/SendDialog.js @@ -40,7 +40,7 @@ const SendDialog = (props) => { const [delay, setDelay] = useState(""); const [publishAnother, setPublishAnother] = useState(false); - const [showTopicUrl, setShowTopicUrl] = useState(props.topicUrl === ""); + const [showTopicUrl, setShowTopicUrl] = useState(props.topicUrl === ""); // FIXME const [showClickUrl, setShowClickUrl] = useState(false); const [showAttachUrl, setShowAttachUrl] = useState(false); const [showEmail, setShowEmail] = useState(false); @@ -49,17 +49,21 @@ const SendDialog = (props) => { const showAttachFile = !!attachFile && !showAttachUrl; const attachFileInput = useRef(); - const [sendRequest, setSendRequest] = useState(null); + const [activeRequest, setActiveRequest] = useState(null); const [statusText, setStatusText] = useState(""); - const disabled = !!sendRequest; + const disabled = !!activeRequest; + + const dropZone = props.dropZone; const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const sendButtonEnabled = (() => { if (!validTopicUrl(topicUrl)) { return false; } return true; })(); + const handleSubmit = async () => { const { baseUrl, topic } = splitTopicUrl(topicUrl); const headers = {}; @@ -106,7 +110,7 @@ const SendDialog = (props) => { } }; const request = api.publishXHR(baseUrl, topic, body, headers, progressFn); - setSendRequest(request); + setActiveRequest(request); await request; if (!publishAnother) { props.onClose(); @@ -117,11 +121,13 @@ const SendDialog = (props) => { console.log("error", e); setStatusText("An error occurred"); } - setSendRequest(null); + setActiveRequest(null); }; + const handleAttachFileClick = () => { attachFileInput.current.click(); }; + const handleAttachFileChanged = (ev) => { const file = ev.target.files[0]; setAttachFile(file); @@ -129,10 +135,57 @@ const SendDialog = (props) => { console.log(ev.target.files[0]); console.log(URL.createObjectURL(ev.target.files[0])); }; + + const handleDrop = (ev) => { + ev.preventDefault(); + const file = ev.dataTransfer.files[0]; + setAttachFile(file); + setFilename(file.name); + }; + + const allowDrag = (ev) => { + if (true /* allowSubmit */) { + ev.dataTransfer.dropEffect = 'copy'; + ev.preventDefault(); + } + }; + return ( Publish to {shortUrl(topicUrl)} + {dropZone && + + + Drop file here + + + } {showTopicUrl && { setTopicUrl(props.topicUrl); @@ -203,7 +256,7 @@ const SendDialog = (props) => { disabled={disabled} > {[5,4,3,2,1].map(priority => - +
{priorities[priority].label}
@@ -348,8 +401,8 @@ const SendDialog = (props) => { - {sendRequest && } - {!sendRequest && + {activeRequest && } + {!activeRequest && <>