feat: accept browser file captures

This commit is contained in:
mirivlad 2026-06-29 09:57:21 +08:00
parent 2cbf542b13
commit df4538532f
2 changed files with 83 additions and 1 deletions

View File

@ -47,6 +47,7 @@ type CapturePayload struct {
Page CapturePage `json:"page"` Page CapturePage `json:"page"`
Selection *CaptureSelection `json:"selection,omitempty"` Selection *CaptureSelection `json:"selection,omitempty"`
Link *CaptureLink `json:"link,omitempty"` Link *CaptureLink `json:"link,omitempty"`
File *CaptureFile `json:"file,omitempty"`
Browser *CaptureBrowser `json:"browser,omitempty"` Browser *CaptureBrowser `json:"browser,omitempty"`
Context interface{} `json:"context,omitempty"` Context interface{} `json:"context,omitempty"`
} }
@ -66,6 +67,13 @@ type CaptureLink struct {
Text string `json:"text"` Text string `json:"text"`
} }
type CaptureFile struct {
Name string `json:"name"`
Mime string `json:"mime"`
Size int64 `json:"size"`
Text string `json:"text"`
}
type CaptureBrowser struct { type CaptureBrowser struct {
Name string `json:"name"` Name string `json:"name"`
} }
@ -209,7 +217,7 @@ func (p CapturePayload) Validate() error {
if strings.TrimSpace(p.CapturedAt) == "" { if strings.TrimSpace(p.CapturedAt) == "" {
return fmt.Errorf("capturedAt is required") return fmt.Errorf("capturedAt is required")
} }
if p.Kind != "page" && p.Kind != "selection" && p.Kind != "link" { if p.Kind != "page" && p.Kind != "selection" && p.Kind != "link" && p.Kind != "file" {
return fmt.Errorf("unsupported kind") return fmt.Errorf("unsupported kind")
} }
if strings.TrimSpace(p.Page.URL) == "" { if strings.TrimSpace(p.Page.URL) == "" {
@ -221,6 +229,12 @@ func (p CapturePayload) Validate() error {
if p.Kind == "link" && (p.Link == nil || strings.TrimSpace(p.Link.URL) == "") { if p.Kind == "link" && (p.Link == nil || strings.TrimSpace(p.Link.URL) == "") {
return fmt.Errorf("link.url is required") return fmt.Errorf("link.url is required")
} }
if p.Kind == "file" && (p.File == nil || strings.TrimSpace(p.File.Name) == "") {
return fmt.Errorf("file.name is required")
}
if p.Kind == "file" && (p.File == nil || p.File.Text == "") {
return fmt.Errorf("file.text is required")
}
return nil return nil
} }
@ -250,6 +264,11 @@ func (p CapturePayload) EventPayload() map[string]interface{} {
result["url"] = linkURL result["url"] = linkURL
result["title"] = strings.TrimSpace(p.Link.Text) result["title"] = strings.TrimSpace(p.Link.Text)
result["domain"] = captureDomain(linkURL, "") result["domain"] = captureDomain(linkURL, "")
case "file":
result["fileName"] = strings.TrimSpace(p.File.Name)
result["fileMime"] = strings.TrimSpace(p.File.Mime)
result["fileSize"] = p.File.Size
result["fileText"] = p.File.Text
} }
return result return result
} }

View File

@ -85,6 +85,69 @@ func TestReceiverAcceptsSelectionCaptureAndPublishesEvent(t *testing.T) {
} }
} }
func TestReceiverAcceptsFileCaptureAndPublishesEvent(t *testing.T) {
bus := events.NewBus()
received := make(chan events.Event, 1)
bus.Subscribe("browser.capture.file", func(event events.Event) {
received <- event
})
receiver := New(bus)
body := `{
"schemaVersion": 1,
"captureId": "capture-file",
"capturedAt": "2026-06-29T02:00:00.000Z",
"source": "verstak-browser-extension",
"kind": "file",
"page": {
"url": "https://example.com/files",
"title": "Example Files",
"domain": "example.com"
},
"file": {
"name": "notes.txt",
"mime": "text/plain",
"size": 11,
"text": "hello file"
},
"browser": {
"name": "Firefox"
}
}`
req := httptest.NewRequest(http.MethodPost, "/api/browser-inbox/v1/captures", bytes.NewBufferString(body))
rec := httptest.NewRecorder()
receiver.ServeHTTP(rec, req)
if rec.Code != http.StatusAccepted {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusAccepted, rec.Body.String())
}
event := <-received
if event.Name != "browser.capture.file" {
t.Fatalf("event name = %q, want browser.capture.file", event.Name)
}
payload, ok := event.Payload.(map[string]interface{})
if !ok {
t.Fatalf("event payload type = %T, want map[string]interface{}", event.Payload)
}
if payload["kind"] != "file" {
t.Fatalf("payload kind = %v, want file", payload["kind"])
}
if payload["fileName"] != "notes.txt" {
t.Fatalf("payload fileName = %v, want notes.txt", payload["fileName"])
}
if payload["fileMime"] != "text/plain" {
t.Fatalf("payload fileMime = %v, want text/plain", payload["fileMime"])
}
if payload["fileSize"] != int64(11) {
t.Fatalf("payload fileSize = %v, want 11", payload["fileSize"])
}
if payload["fileText"] != "hello file" {
t.Fatalf("payload fileText = %v, want hello file", payload["fileText"])
}
}
func TestReceiverAnnotatesCaptureWithCurrentWorkspace(t *testing.T) { func TestReceiverAnnotatesCaptureWithCurrentWorkspace(t *testing.T) {
bus := events.NewBus() bus := events.NewBus()
received := make(chan events.Event, 1) received := make(chan events.Event, 1)