mattermost/server/public/pluginapi/grpc/proto/hooks.proto
Nick Misasi 19c392084b feat(08-01): add protobuf contract for ServeHTTP request streaming
Add bidirectional streaming RPC for ServeHTTP hook to support
efficient HTTP request/response transfer between Go and Python.

Key changes:
- New hooks_http.proto with ServeHTTPRequest/Response messages
- HTTPHeader message for multi-value header support
- ServeHTTPRequestInit with full request metadata
- ServeHTTPResponseInit for status and headers
- Body chunks with completion flag for streaming
- Updated hooks.proto with ServeHTTP streaming RPC
- Regenerated Go and Python code

Design decisions:
- 64KB default chunk size per gRPC best practices
- First message carries metadata, subsequent messages carry body
- body_complete flag signals end of stream
- Headers as repeated HTTPHeader for multi-value support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:23:38 -05:00

410 lines
20 KiB
Protocol Buffer

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
syntax = "proto3";
package mattermost.pluginapi.v1;
option go_package = "github.com/mattermost/mattermost/server/public/pluginapi/grpc/generated/go/pluginapiv1";
import "hooks_lifecycle.proto";
import "hooks_message.proto";
import "hooks_user_channel.proto";
import "hooks_command.proto";
import "hooks_http.proto";
// ==============================================================================
// PLUGIN HOOKS SERVICE
// ==============================================================================
//
// This service defines the gRPC interface for invoking plugin hooks.
// The Go server implements this service's client to call into Python plugins.
// Python plugins implement this service's server to receive hook invocations.
//
// Direction: Server -> Plugin (server calls plugin hooks)
//
// ERROR HANDLING CONVENTION:
// --------------------------
// For hooks whose Go signature returns `error`:
// - Errors are encoded in the response message's `error` field (AppError)
// - gRPC status codes are reserved for transport-level failures only
//
// For hooks whose Go signature returns nothing (void):
// - Response message has no `error` field
// - Hook failures are logged server-side but do not propagate
//
// HOOK GROUPS:
// ------------
// This file imports and exposes RPCs for different hook categories:
// - hooks_lifecycle.proto: Lifecycle and system hooks (this plan: 03-01)
// - hooks_message.proto: Message hooks (planned: 03-02)
// - hooks_user_channel.proto: User and channel hooks (03-03)
// - hooks_command.proto: Command, WebSocket, cluster, shared channels hooks (03-04)
//
// ==============================================================================
// PluginHooks is the gRPC service for invoking plugin hooks.
// The server acts as the gRPC client, calling into the plugin process.
// The plugin acts as the gRPC server, implementing the hook handlers.
service PluginHooks {
// ===========================================================================
// LIFECYCLE HOOKS
// ===========================================================================
// Implemented returns the list of hooks that the plugin implements.
// Called during plugin startup to optimize hook dispatch.
// Plugins that don't implement this are assumed to implement all hooks.
//
// Go signature: Implemented() ([]string, error)
rpc Implemented(ImplementedRequest) returns (ImplementedResponse);
// OnActivate is invoked when the plugin is activated.
// If an error is returned, the plugin will be terminated.
// The plugin will not receive hooks until after OnActivate returns without error.
// OnConfigurationChange will be called once before OnActivate.
//
// Go signature: OnActivate() error
rpc OnActivate(OnActivateRequest) returns (OnActivateResponse);
// OnDeactivate is invoked when the plugin is deactivated.
// This is the plugin's last chance to use the API.
// The plugin will be terminated shortly after this invocation.
//
// Go signature: OnDeactivate() error
rpc OnDeactivate(OnDeactivateRequest) returns (OnDeactivateResponse);
// OnConfigurationChange is invoked when configuration changes may have been made.
// Any returned error is logged but does not stop the plugin.
// It is called once before OnActivate.
//
// Go signature: OnConfigurationChange() error
rpc OnConfigurationChange(OnConfigurationChangeRequest) returns (OnConfigurationChangeResponse);
// ===========================================================================
// SYSTEM HOOKS
// ===========================================================================
// OnInstall is invoked after the installation of a plugin as part of onboarding.
// It's called on every installation, not only once.
//
// Go signature: OnInstall(c *Context, event model.OnInstallEvent) error
rpc OnInstall(OnInstallRequest) returns (OnInstallResponse);
// OnSendDailyTelemetry is invoked when the server sends daily telemetry data.
// Plugins can use this to send their own telemetry metrics.
//
// Go signature: OnSendDailyTelemetry()
rpc OnSendDailyTelemetry(OnSendDailyTelemetryRequest) returns (OnSendDailyTelemetryResponse);
// RunDataRetention is invoked during a DataRetentionJob.
// Plugins should delete data older than their retention policy.
//
// Go signature: RunDataRetention(nowTime, batchSize int64) (int64, error)
rpc RunDataRetention(RunDataRetentionRequest) returns (RunDataRetentionResponse);
// OnCloudLimitsUpdated is invoked when cloud product limits change.
// For example, when plan tiers change affecting storage or message limits.
//
// Go signature: OnCloudLimitsUpdated(limits *model.ProductLimits)
rpc OnCloudLimitsUpdated(OnCloudLimitsUpdatedRequest) returns (OnCloudLimitsUpdatedResponse);
// ConfigurationWillBeSaved is invoked before saving configuration to the backing store.
// An error can be returned to reject the operation.
// Additionally, a new config object can be returned to be stored in place of the provided one.
//
// Go signature: ConfigurationWillBeSaved(newCfg *model.Config) (*model.Config, error)
rpc ConfigurationWillBeSaved(ConfigurationWillBeSavedRequest) returns (ConfigurationWillBeSavedResponse);
// ===========================================================================
// MESSAGE HOOKS
// ===========================================================================
// MessageWillBePosted is invoked when a message is posted before it is committed
// to the database. Use this to modify or reject posts before they are saved.
//
// Return values:
// - To allow unchanged: return nil post and empty string
// - To modify: return modified post and empty string
// - To reject: return nil post and rejection reason string
// - To dismiss silently: return nil post and "plugin.message_will_be_posted.dismiss_post"
//
// Go signature: MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string)
rpc MessageWillBePosted(MessageWillBePostedRequest) returns (MessageWillBePostedResponse);
// MessageWillBeUpdated is invoked when a message is updated before it is committed
// to the database. Use this to modify or reject post updates.
//
// Return values:
// - To allow unchanged: return nil post and empty string
// - To modify: return modified post and empty string
// - To reject: return nil post and rejection reason (post stays unchanged)
//
// Go signature: MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string)
rpc MessageWillBeUpdated(MessageWillBeUpdatedRequest) returns (MessageWillBeUpdatedResponse);
// MessageHasBeenPosted is invoked after the message has been committed to the database.
// This is a notification hook - you cannot modify or reject the post.
// Use MessageWillBePosted if you need to modify or reject.
//
// Go signature: MessageHasBeenPosted(c *Context, post *model.Post)
rpc MessageHasBeenPosted(MessageHasBeenPostedRequest) returns (MessageHasBeenPostedResponse);
// MessageHasBeenUpdated is invoked after a message update has been committed to the database.
// This is a notification hook - you cannot modify or reject the update.
// Use MessageWillBeUpdated if you need to modify or reject.
//
// Go signature: MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post)
rpc MessageHasBeenUpdated(MessageHasBeenUpdatedRequest) returns (MessageHasBeenUpdatedResponse);
// MessagesWillBeConsumed is invoked when messages are requested by a client
// before they are returned. Use this to filter or modify posts before delivery.
//
// Note: This hook has no Context parameter and no error return.
//
// Go signature: MessagesWillBeConsumed(posts []*model.Post) []*model.Post
rpc MessagesWillBeConsumed(MessagesWillBeConsumedRequest) returns (MessagesWillBeConsumedResponse);
// MessageHasBeenDeleted is invoked after a message has been deleted from the database.
// This is a notification hook - you cannot undo the deletion.
//
// Go signature: MessageHasBeenDeleted(c *Context, post *model.Post)
rpc MessageHasBeenDeleted(MessageHasBeenDeletedRequest) returns (MessageHasBeenDeletedResponse);
// FileWillBeUploaded is invoked when a file is uploaded before it is committed
// to storage. Use this to modify or reject file uploads.
//
// Note: Phase 8 will add streaming support. Currently uses bytes for file content.
//
// Return values:
// - To allow unchanged: return nil FileInfo, empty bytes, empty string
// - To modify: return modified FileInfo and/or content, empty string
// - To reject: return nil FileInfo, empty bytes, and rejection reason
//
// Go signature: FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string)
rpc FileWillBeUploaded(FileWillBeUploadedRequest) returns (FileWillBeUploadedResponse);
// ReactionHasBeenAdded is invoked after a reaction has been committed to the database.
// This is a notification hook.
//
// Go signature: ReactionHasBeenAdded(c *Context, reaction *model.Reaction)
rpc ReactionHasBeenAdded(ReactionHasBeenAddedRequest) returns (ReactionHasBeenAddedResponse);
// ReactionHasBeenRemoved is invoked after a reaction has been removed from the database.
// This is a notification hook.
//
// Go signature: ReactionHasBeenRemoved(c *Context, reaction *model.Reaction)
rpc ReactionHasBeenRemoved(ReactionHasBeenRemovedRequest) returns (ReactionHasBeenRemovedResponse);
// NotificationWillBePushed is invoked before a push notification is sent to
// the push notification server. Use this to modify or reject push notifications.
//
// Note: This hook has no Context parameter.
//
// Return values:
// - To allow unchanged: return nil notification and empty string
// - To modify: return modified notification and empty string
// - To reject: return nil notification and rejection reason
//
// Go signature: NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string)
rpc NotificationWillBePushed(NotificationWillBePushedRequest) returns (NotificationWillBePushedResponse);
// EmailNotificationWillBeSent is invoked before an email notification is sent.
// Use this to customize email content or reject the notification.
//
// Note: Core identifiers (PostId, ChannelId, etc.) are immutable.
// Only content fields (subject, title, message, etc.) can be modified.
// Note: This hook has no Context parameter.
//
// Return values:
// - To allow unchanged: return nil content and empty string
// - To modify: return modified EmailNotificationContent and empty string
// - To reject: return nil content and rejection reason
//
// Go signature: EmailNotificationWillBeSent(emailNotification *model.EmailNotification) (*model.EmailNotificationContent, string)
rpc EmailNotificationWillBeSent(EmailNotificationWillBeSentRequest) returns (EmailNotificationWillBeSentResponse);
// PreferencesHaveChanged is invoked after one or more of a user's preferences
// have changed. This is a notification hook.
//
// Go signature: PreferencesHaveChanged(c *Context, preferences []model.Preference)
rpc PreferencesHaveChanged(PreferencesHaveChangedRequest) returns (PreferencesHaveChangedResponse);
// ===========================================================================
// USER HOOKS
// ===========================================================================
// UserHasBeenCreated is invoked after a user was created.
// This is a notification hook - you cannot modify or reject the creation.
//
// Go signature: UserHasBeenCreated(c *Context, user *model.User)
rpc UserHasBeenCreated(UserHasBeenCreatedRequest) returns (UserHasBeenCreatedResponse);
// UserWillLogIn is invoked before the login of the user is returned.
// Return a non-empty string to reject the login.
// If you don't need to reject the login event, see UserHasLoggedIn.
//
// Go signature: UserWillLogIn(c *Context, user *model.User) string
rpc UserWillLogIn(UserWillLogInRequest) returns (UserWillLogInResponse);
// UserHasLoggedIn is invoked after a user has logged in.
// This is a notification hook - you cannot modify or reject the login.
//
// Go signature: UserHasLoggedIn(c *Context, user *model.User)
rpc UserHasLoggedIn(UserHasLoggedInRequest) returns (UserHasLoggedInResponse);
// UserHasBeenDeactivated is invoked when a user is deactivated.
// This is a notification hook.
//
// Go signature: UserHasBeenDeactivated(c *Context, user *model.User)
rpc UserHasBeenDeactivated(UserHasBeenDeactivatedRequest) returns (UserHasBeenDeactivatedResponse);
// OnSAMLLogin is invoked after a successful SAML login.
// Return an error to reject the login.
//
// Go signature: OnSAMLLogin(c *Context, user *model.User, assertion *saml2.AssertionInfo) error
rpc OnSAMLLogin(OnSAMLLoginRequest) returns (OnSAMLLoginResponse);
// ===========================================================================
// CHANNEL AND TEAM HOOKS
// ===========================================================================
// ChannelHasBeenCreated is invoked after a channel has been created.
// This is a notification hook - you cannot modify or reject the creation.
//
// Go signature: ChannelHasBeenCreated(c *Context, channel *model.Channel)
rpc ChannelHasBeenCreated(ChannelHasBeenCreatedRequest) returns (ChannelHasBeenCreatedResponse);
// UserHasJoinedChannel is invoked after a user has joined a channel.
// This is a notification hook. The actor is optional (nil if self-join).
//
// Go signature: UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
rpc UserHasJoinedChannel(UserHasJoinedChannelRequest) returns (UserHasJoinedChannelResponse);
// UserHasLeftChannel is invoked after a user has left a channel.
// This is a notification hook. The actor is optional (nil if self-removal).
//
// Go signature: UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
rpc UserHasLeftChannel(UserHasLeftChannelRequest) returns (UserHasLeftChannelResponse);
// UserHasJoinedTeam is invoked after a user has joined a team.
// This is a notification hook. The actor is optional (nil if self-join).
//
// Go signature: UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
rpc UserHasJoinedTeam(UserHasJoinedTeamRequest) returns (UserHasJoinedTeamResponse);
// UserHasLeftTeam is invoked after a user has left a team.
// This is a notification hook. The actor is optional (nil if self-removal).
//
// Go signature: UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
rpc UserHasLeftTeam(UserHasLeftTeamRequest) returns (UserHasLeftTeamResponse);
// ===========================================================================
// COMMAND HOOKS
// ===========================================================================
// ExecuteCommand executes a registered slash command.
//
// Go signature: ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError)
rpc ExecuteCommand(ExecuteCommandRequest) returns (ExecuteCommandResponse);
// ===========================================================================
// WEBSOCKET HOOKS
// ===========================================================================
// OnWebSocketConnect is invoked when a new WebSocket connection is opened.
// This is a notification hook with no Context parameter.
//
// Go signature: OnWebSocketConnect(webConnID, userID string)
rpc OnWebSocketConnect(OnWebSocketConnectRequest) returns (OnWebSocketConnectResponse);
// OnWebSocketDisconnect is invoked when a WebSocket connection is closed.
// This is a notification hook with no Context parameter.
//
// Go signature: OnWebSocketDisconnect(webConnID, userID string)
rpc OnWebSocketDisconnect(OnWebSocketDisconnectRequest) returns (OnWebSocketDisconnectResponse);
// WebSocketMessageHasBeenPosted is invoked when a WebSocket message is received.
// This is a notification hook with no Context parameter.
//
// Go signature: WebSocketMessageHasBeenPosted(webConnID, userID string, req *model.WebSocketRequest)
rpc WebSocketMessageHasBeenPosted(WebSocketMessageHasBeenPostedRequest) returns (WebSocketMessageHasBeenPostedResponse);
// ===========================================================================
// CLUSTER HOOKS
// ===========================================================================
// OnPluginClusterEvent is invoked when an intra-cluster plugin event is received.
// Used for communication between plugin instances in a High-Availability cluster.
// This is a notification hook.
//
// Go signature: OnPluginClusterEvent(c *Context, ev model.PluginClusterEvent)
rpc OnPluginClusterEvent(OnPluginClusterEventRequest) returns (OnPluginClusterEventResponse);
// ===========================================================================
// SHARED CHANNELS HOOKS
// ===========================================================================
// OnSharedChannelsSyncMsg is invoked when a shared channels sync message is received.
// Plugins can use this to synchronize data with remote clusters.
//
// Go signature: OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error)
rpc OnSharedChannelsSyncMsg(OnSharedChannelsSyncMsgRequest) returns (OnSharedChannelsSyncMsgResponse);
// OnSharedChannelsPing is invoked to check the health of the shared channels plugin.
// Return true if the plugin and upstream connections are healthy.
//
// Go signature: OnSharedChannelsPing(rc *model.RemoteCluster) bool
rpc OnSharedChannelsPing(OnSharedChannelsPingRequest) returns (OnSharedChannelsPingResponse);
// OnSharedChannelsAttachmentSyncMsg is invoked when a file attachment sync message is received.
// Used to synchronize file attachments between shared channel participants.
//
// Go signature: OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
rpc OnSharedChannelsAttachmentSyncMsg(OnSharedChannelsAttachmentSyncMsgRequest) returns (OnSharedChannelsAttachmentSyncMsgResponse);
// OnSharedChannelsProfileImageSyncMsg is invoked when a profile image sync message is received.
// Used to synchronize user profile images between shared channel participants.
//
// Go signature: OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
rpc OnSharedChannelsProfileImageSyncMsg(OnSharedChannelsProfileImageSyncMsgRequest) returns (OnSharedChannelsProfileImageSyncMsgResponse);
// ===========================================================================
// SUPPORT HOOKS
// ===========================================================================
// GenerateSupportData is invoked when a Support Packet is generated.
// Plugins can include their own diagnostic data in the support packet.
//
// Go signature: GenerateSupportData(c *Context) ([]*model.FileData, error)
rpc GenerateSupportData(GenerateSupportDataRequest) returns (GenerateSupportDataResponse);
// ===========================================================================
// HTTP STREAMING HOOKS (Phase 8)
// ===========================================================================
// ServeHTTP handles HTTP requests to /plugins/{plugin_id}.
// Uses bidirectional streaming for efficient large body transfer.
//
// Request flow (Go -> Python):
// - First message: init metadata (method, URL, headers) + optional first body chunk
// - Subsequent messages: body chunks until body_complete=true
//
// Response flow (Python -> Go):
// - First message: init metadata (status, headers) + optional first body chunk
// - Subsequent messages: body chunks until body_complete=true
//
// Cancellation: HTTP client disconnect propagates via gRPC context.
// Body chunks are 64KB by default (configurable).
//
// Go signature: ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request)
rpc ServeHTTP(stream ServeHTTPRequest) returns (stream ServeHTTPResponse);
// ===========================================================================
// DEFERRED HOOKS (Phase 8 - remaining)
// ===========================================================================
//
// NOTE: ServeMetrics is deferred to 08-02 or 08-03 (same pattern as ServeHTTP).
//
// Signature:
// - ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request)
}