Merge#9
Conversation
Add text sharing copy flow
Fix desktop text uploads
feat: atualização
|
| Filename | Overview |
|---|---|
| src/server/dropServer.js | Adds /share-target route, cancelUpload method, startup temp-file cleanup, and static-file serving for manifest/SW; logic is well-guarded and tested. |
| src/renderer/public/waterdrop-sw.js | Adds fetch-event handler for share-target interception, syncStartedAt tracking, and a guard in markUploadQueued to prevent re-queuing non-syncing records. |
| src/renderer/src/App.jsx | Adds text composer UI, cancelUpload flow, copyFileText helper, and isDesktop guards for background fetch; canCancelStalledUpload correctly gates the cancel button. |
| src/renderer/src/uploadQueue.js | Adds STALLED_BACKGROUND_UPLOAD_MS constant, syncStartedAt field to all status-transition helpers, and exposes updatedAt/syncStartedAt in uploadRecordToView. |
| src/renderer/public/manifest.webmanifest | Adds share_target definition with a broad accept list; application/octet-stream is redundant because application/* already covers it. |
| test/dropServer.test.js | Adds five new integration tests covering share-target upload, error redirect, text-only share, cancel-in-flight, and startup temp cleanup — all new paths are exercised. |
| src/renderer/src/styles.css | Adds styling for text-compose panel and upload-cancel button; no functional concerns. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App as Mobile App
participant Browser
participant SW as Service Worker
participant IDB as IndexedDB
participant Server as WaterDrop Server
Note over App,Server: Web Share Target flow (SW intercepts)
App->>Browser: Share file/URL
Browser->>SW: POST /share-target (multipart)
SW->>SW: isShareTargetRequest() ✓
SW->>SW: queueShareTargetUploads()
SW->>IDB: putUploadRecord(status:"queued")
SW->>SW: sync.register("waterdrop-upload-queue")
SW-->>Browser: "303 → /?shared=N"
SW->>Browser: postMessage(WATERDROP_UPLOAD_QUEUED)
Browser->>SW: background sync fires
SW->>SW: drainUploadQueue()
SW->>Server: POST api/files/raw (blob)
Server-->>SW: 201 OK
Note over App,Server: Server fallback (no SW / desktop)
App->>Browser: Share file/URL
Browser->>Server: POST /share-target (multipart)
Server->>Server: handleShareTarget()
Server->>Server: "handleUpload(redirectTo="/")"
Server->>Server: buildSharedTextUpload() if text-only
Server->>Server: store.addFromTemp()
Server-->>Browser: 303 → /
Note over App,Server: Cancel stalled upload
Browser->>Browser: canCancelStalledUpload() → show button
Browser->>IDB: clearQueuedUpload(id)
Browser->>SW: backgroundFetch.abort(id)
Browser->>Server: DELETE /api/uploads/:id
Server->>Server: store.cancelUpload(id)
Server->>Server: rememberDeletedUploads + cancelInFlightUploads
Server-->>Browser: "200 {cancelled:true}"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App as Mobile App
participant Browser
participant SW as Service Worker
participant IDB as IndexedDB
participant Server as WaterDrop Server
Note over App,Server: Web Share Target flow (SW intercepts)
App->>Browser: Share file/URL
Browser->>SW: POST /share-target (multipart)
SW->>SW: isShareTargetRequest() ✓
SW->>SW: queueShareTargetUploads()
SW->>IDB: putUploadRecord(status:"queued")
SW->>SW: sync.register("waterdrop-upload-queue")
SW-->>Browser: "303 → /?shared=N"
SW->>Browser: postMessage(WATERDROP_UPLOAD_QUEUED)
Browser->>SW: background sync fires
SW->>SW: drainUploadQueue()
SW->>Server: POST api/files/raw (blob)
Server-->>SW: 201 OK
Note over App,Server: Server fallback (no SW / desktop)
App->>Browser: Share file/URL
Browser->>Server: POST /share-target (multipart)
Server->>Server: handleShareTarget()
Server->>Server: "handleUpload(redirectTo="/")"
Server->>Server: buildSharedTextUpload() if text-only
Server->>Server: store.addFromTemp()
Server-->>Browser: 303 → /
Note over App,Server: Cancel stalled upload
Browser->>Browser: canCancelStalledUpload() → show button
Browser->>IDB: clearQueuedUpload(id)
Browser->>SW: backgroundFetch.abort(id)
Browser->>Server: DELETE /api/uploads/:id
Server->>Server: store.cancelUpload(id)
Server->>Server: rememberDeletedUploads + cancelInFlightUploads
Server-->>Browser: "200 {cancelled:true}"
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/renderer/public/manifest.webmanifest:26-27
`application/octet-stream` is already covered by the `application/*` wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.
```suggestion
"application/*",
```
### Issue 2 of 2
src/server/dropServer.js:834-841
Non-DELETE methods that match `/api/uploads/:id` fall through the `if (uploadMatch)` block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.
```suggestion
const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
if (uploadMatch) {
if (req.method === "DELETE") {
const result = await store.cancelUpload(uploadMatch[1]);
sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
return;
}
sendJson(res, 405, { error: "Method not allowed" });
return;
}
```
Reviews (1): Last reviewed commit: "Merge pull request #8 from MusicMaster4/..." | Re-trigger Greptile
| "application/*", | ||
| "application/octet-stream", |
There was a problem hiding this comment.
application/octet-stream is already covered by the application/* wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.
| "application/*", | |
| "application/octet-stream", | |
| "application/*", |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/public/manifest.webmanifest
Line: 26-27
Comment:
`application/octet-stream` is already covered by the `application/*` wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.
```suggestion
"application/*",
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/); | ||
| if (uploadMatch) { | ||
| if (req.method === "DELETE") { | ||
| const result = await store.cancelUpload(uploadMatch[1]); | ||
| sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" }); | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
Non-DELETE methods that match
/api/uploads/:id fall through the if (uploadMatch) block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.
| const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/); | |
| if (uploadMatch) { | |
| if (req.method === "DELETE") { | |
| const result = await store.cancelUpload(uploadMatch[1]); | |
| sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" }); | |
| return; | |
| } | |
| } | |
| const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/); | |
| if (uploadMatch) { | |
| if (req.method === "DELETE") { | |
| const result = await store.cancelUpload(uploadMatch[1]); | |
| sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" }); | |
| return; | |
| } | |
| sendJson(res, 405, { error: "Method not allowed" }); | |
| return; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/dropServer.js
Line: 834-841
Comment:
Non-DELETE methods that match `/api/uploads/:id` fall through the `if (uploadMatch)` block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.
```suggestion
const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
if (uploadMatch) {
if (req.method === "DELETE") {
const result = await store.cancelUpload(uploadMatch[1]);
sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
return;
}
sendJson(res, 405, { error: "Method not allowed" });
return;
}
```
How can I resolve this? If you propose a fix, please make it concise.
No description provided.