Tag browser captures with current workspace

This commit is contained in:
mirivlad 2026-06-28 04:00:50 +08:00
parent 6d30015a1d
commit 93597a2c45
3 changed files with 77 additions and 5 deletions

View File

@ -19,9 +19,12 @@ const capturePath = "/api/browser-inbox/v1/captures"
const DefaultAddr = "127.0.0.1:47731" const DefaultAddr = "127.0.0.1:47731"
type Receiver struct { type Receiver struct {
bus *events.Bus bus *events.Bus
workspaceProvider WorkspaceProvider
} }
type WorkspaceProvider func() string
type Server struct { type Server struct {
listener net.Listener listener net.Listener
server *http.Server server *http.Server
@ -59,8 +62,12 @@ type CaptureBrowser struct {
Name string `json:"name"` Name string `json:"name"`
} }
func New(bus *events.Bus) *Receiver { func New(bus *events.Bus, providers ...WorkspaceProvider) *Receiver {
return &Receiver{bus: bus} var provider WorkspaceProvider
if len(providers) > 0 {
provider = providers[0]
}
return &Receiver{bus: bus, workspaceProvider: provider}
} }
func Start(addr string, receiver *Receiver) (*Server, error) { func Start(addr string, receiver *Receiver) (*Server, error) {
@ -128,10 +135,12 @@ func (r *Receiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
writeError(w, http.StatusServiceUnavailable, "browser inbox unavailable") writeError(w, http.StatusServiceUnavailable, "browser inbox unavailable")
return return
} }
eventPayload := payload.EventPayload()
r.annotateWorkspace(eventPayload)
r.bus.Publish(events.Event{ r.bus.Publish(events.Event{
Name: eventName, Name: eventName,
Timestamp: time.Now().UTC().Format(time.RFC3339Nano), Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
Payload: payload.EventPayload(), Payload: eventPayload,
}) })
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
@ -141,6 +150,21 @@ func (r *Receiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}) })
} }
func (r *Receiver) annotateWorkspace(payload map[string]interface{}) {
if r == nil || r.workspaceProvider == nil || payload == nil {
return
}
if _, ok := payload["workspaceRootPath"]; ok {
return
}
workspaceRoot := strings.TrimSpace(r.workspaceProvider())
if workspaceRoot == "" {
return
}
payload["workspaceRootPath"] = workspaceRoot
payload["workspaceName"] = workspaceRoot
}
func (p CapturePayload) Validate() error { func (p CapturePayload) Validate() error {
if p.SchemaVersion != 1 { if p.SchemaVersion != 1 {
return fmt.Errorf("unsupported schemaVersion") return fmt.Errorf("unsupported schemaVersion")

View File

@ -85,6 +85,48 @@ func TestReceiverAcceptsSelectionCaptureAndPublishesEvent(t *testing.T) {
} }
} }
func TestReceiverAnnotatesCaptureWithCurrentWorkspace(t *testing.T) {
bus := events.NewBus()
received := make(chan events.Event, 1)
bus.Subscribe("browser.capture.page", func(event events.Event) {
received <- event
})
receiver := New(bus, func() string { return "Project" })
body := `{
"schemaVersion": 1,
"captureId": "capture-workspace",
"capturedAt": "2026-06-27T00:00:00.000Z",
"source": "verstak-browser-extension",
"kind": "page",
"page": {
"url": "https://example.com/article",
"title": "Example Article"
}
}`
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
payload, ok := event.Payload.(map[string]interface{})
if !ok {
t.Fatalf("event payload type = %T, want map[string]interface{}", event.Payload)
}
if payload["workspaceRootPath"] != "Project" {
t.Fatalf("payload workspaceRootPath = %v, want Project", payload["workspaceRootPath"])
}
if payload["workspaceName"] != "Project" {
t.Fatalf("payload workspaceName = %v, want Project", payload["workspaceName"])
}
}
func TestServerStartsOnLocalAddressAndAcceptsCapture(t *testing.T) { func TestServerStartsOnLocalAddressAndAcceptsCapture(t *testing.T) {
bus := events.NewBus() bus := events.NewBus()
bus.Subscribe("browser.capture.page", func(event events.Event) {}) bus.Subscribe("browser.capture.page", func(event events.Event) {})

View File

@ -252,7 +252,13 @@ func main() {
syncService = syncsvc.NewService(vaultService.GetVaultPath(), "") syncService = syncsvc.NewService(vaultService.GetVaultPath(), "")
} }
app := api.NewApp(capRegistry, contribRegistry, permRegistry, eventBus, plugins, vaultService, storageService, filesService, appSettingsMgr, pluginStateMgr, workspaceMgr, syncService, debugEnabled) app := api.NewApp(capRegistry, contribRegistry, permRegistry, eventBus, plugins, vaultService, storageService, filesService, appSettingsMgr, pluginStateMgr, workspaceMgr, syncService, debugEnabled)
browserReceiver := browserreceiver.New(eventBus) browserReceiver := browserreceiver.New(eventBus, func() string {
current := app.GetCurrentWorkspace()
if root, ok := current["rootPath"].(string); ok {
return root
}
return ""
})
browserReceiverServer, err := browserreceiver.Start(browserreceiver.DefaultAddr, browserReceiver) browserReceiverServer, err := browserreceiver.Start(browserreceiver.DefaultAddr, browserReceiver)
if err != nil { if err != nil {
log.Printf("[browserreceiver] local receiver disabled: %v", err) log.Printf("[browserreceiver] local receiver disabled: %v", err)