package caps import ( "context" abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1" "git.dev.alexdunmow.com/block/pluginsdk/plugin" ) // bridgeStub implements plugin.PluginBridge over bridge.* capability calls. // // The bridge shares in-process Go values today; across sandboxes only the // registration/lookup *surface* serializes โ€” the service value itself cannot // cross. RegisterService therefore forwards only plugin/service names to the // host AND records the value locally so this plugin's own services are // dispatchable via HOOK_BRIDGE_CALL; GetService returns nil for remote // services (availability is checked but a typed value cannot be reconstructed // guest-side). Cross-plugin calls use Invoke (bridge.invoke) with opaque // payloads instead. // // Instances are single-threaded, so the plain map needs no locking; register // bridge services in Register (every pooled instance runs it), not Load // (which fires on one instance only) โ€” see core/docs/wasm-abi.md // ยง"Per-instance state". type bridgeStub struct { base services map[string]any // serviceName โ†’ registered value (this plugin's own) } var _ plugin.PluginBridge = (*bridgeStub)(nil) // RegisterService has no error channel; a transport failure is dropped. func (s *bridgeStub) RegisterService(pluginName, serviceName string, service any) { if s.services == nil { s.services = make(map[string]any) } s.services[serviceName] = service req := &abiv1.BridgeRegisterServiceRequest{PluginName: pluginName, ServiceName: serviceName} _ = s.invoke(context.Background(), "register_service", req, &abiv1.BridgeRegisterServiceResponse{}) } // GetService reports availability host-side but cannot return the concrete Go // value across the sandbox boundary, so it always returns nil for remote // services. Callers using plugin.GetServiceAs correctly observe (zero, false); // cross-plugin calls go through Invoke. func (s *bridgeStub) GetService(pluginName, serviceName string) any { req := &abiv1.BridgeGetServiceRequest{PluginName: pluginName, ServiceName: serviceName} _ = s.invoke(context.Background(), "get_service", req, &abiv1.BridgeGetServiceResponse{}) return nil } // Invoke calls a method on another plugin's registered bridge service with an // opaque payload (bridge.invoke; the provider answers via HOOK_BRIDGE_CALL or // its in-process plugin.BridgeInvokable). func (s *bridgeStub) Invoke(ctx context.Context, pluginName, serviceName, method string, payload []byte) ([]byte, error) { req := &abiv1.BridgeInvokeRequest{ PluginName: pluginName, ServiceName: serviceName, Method: method, Payload: payload, } resp := &abiv1.BridgeInvokeResponse{} if err := s.invoke(ctx, "invoke", req, resp); err != nil { return nil, err } return resp.GetPayload(), nil } // Service returns this plugin's own registered service value, for // HOOK_BRIDGE_CALL dispatch by the wasm shim. func (s *bridgeStub) Service(serviceName string) (any, bool) { v, ok := s.services[serviceName] return v, ok }