Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions native_backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ func EmitEvent(_ string, _ any) {}
// RecordActivity is a no-op outside WASM.
func RecordActivity(_, _, _, _ string, _ any) {}

// FetchResponse is also defined in wasm_backends.go (wasip1); provide the
// type here so non-WASM consumers can reference it.
type FetchResponse struct {
Status int `json:"status"`
Body string `json:"body"`
Headers map[string]string `json:"headers"`
Error string `json:"error"`
}

// Fetch always returns errNotWASM outside a WASM module.
func Fetch(_, _ string, _ map[string]string, _ string) (*FetchResponse, error) {
return nil, errNotWASM
}

// ptrOf and hostError are used by wasm_backends.go (wasip1 only); provide
// stubs here so the non-WASM build does not need them.
//
Expand Down
51 changes: 51 additions & 0 deletions wasm_backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package plugin

import (
"encoding/json"
"fmt"
"unsafe"
)

Expand Down Expand Up @@ -206,6 +207,56 @@ func RecordActivity(taskID, projectID, actorUserID, activityType string, content
hostActivityRecord(int64(ptrOf(payloadBytes)), int64(len(payloadBytes)))
}

// ── Fetch ─────────────────────────────────────────────────────────────────────

// FetchResponse is the result of a Fetch call.
type FetchResponse struct {
Status int `json:"status"`
Body string `json:"body"`
Headers map[string]string `json:"headers"`
Error string `json:"error"`
}

// Fetch makes an outbound HTTP request via the paca.fetch host function.
// The URL's domain must be listed in the plugin manifest's allowedOutboundDomains.
func Fetch(method, rawURL string, headers map[string]string, body string) (*FetchResponse, error) {
req := struct {
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
}{
Method: method,
URL: rawURL,
Headers: headers,
Body: body,
}
reqJSON, err := json.Marshal(req)
if err != nil {
return nil, err
}
outputBuf := make([]byte, 8)
hostFetch(
int64(ptrOf(reqJSON)), int64(len(reqJSON)),
int64(ptrOf(outputBuf)), int64(ptrOf(outputBuf[4:])),
)
resPtr := int32(uint32(outputBuf[0]) | uint32(outputBuf[1])<<8 | uint32(outputBuf[2])<<16 | uint32(outputBuf[3])<<24)
resLen := int32(uint32(outputBuf[4]) | uint32(outputBuf[5])<<8 | uint32(outputBuf[6])<<16 | uint32(outputBuf[7])<<24)
if resLen == 0 {
return nil, fmt.Errorf("plugin: fetch: empty response from host")
}
resBytes := append([]byte(nil), wasmSlice(resPtr, resLen)...)
wasmResetAllocator()
var resp FetchResponse
if err := json.Unmarshal(resBytes, &resp); err != nil {
return nil, fmt.Errorf("plugin: fetch: decode response: %w", err)
}
if resp.Error != "" {
return nil, fmt.Errorf("plugin: fetch: %s", resp.Error)
}
return &resp, nil
}
Comment thread
pikann marked this conversation as resolved.

// ── Helpers ───────────────────────────────────────────────────────────────────

//go:nocheckptr
Expand Down
6 changes: 6 additions & 0 deletions wasm_imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ func hostStorageDelete(keyPtr, keyLen int64) int32
//go:noescape
func hostEventEmit(topicPtr, topicLen, payloadPtr, payloadLen int64) int32

// paca.fetch(reqPtr i64, reqLen i64, resPtrPtr i64, resLenPtr i64)
//
//go:wasmimport paca fetch
//go:noescape
func hostFetch(reqPtr, reqLen, resPtrPtr, resLenPtr int64)

// paca.activity_record(payloadPtr i64, payloadLen i64) -> ok i32
//
//go:wasmimport paca activity_record
Expand Down
Loading