diff --git a/build/admin/bundleAdmin.sh b/build/admin/bundleAdmin.sh index ba01fcefa..3b0ddbb52 100755 --- a/build/admin/bundleAdmin.sh +++ b/build/admin/bundleAdmin.sh @@ -18,6 +18,7 @@ trap shutdown INT TERM ABRT EXIT echo "Cloning owncast admin into $INSTALL_TEMP_DIRECTORY..." git clone https://github.com/owncast/owncast-admin 2> /dev/null cd owncast-admin +git checkout gek/chat-user-refactor echo "Installing npm modules for the owncast admin..." npm --silent install 2> /dev/null diff --git a/config/config.go b/config/config.go index a0b067386..3a87c56d0 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,13 @@ func GetCommit() string { return GitCommit } +var DefaultForbiddenUsernames = []string{ + "owncast", "operator", "admin", "system", +} + +// The maximum payload we will allow to to be received via the chat socket. +const MaxSocketPayloadSize = 2048 + // GetReleaseString gets the version string. func GetReleaseString() string { var versionNumber = VersionNumber diff --git a/controllers/admin/chat.go b/controllers/admin/chat.go index f9166ac0e..cf84f118f 100644 --- a/controllers/admin/chat.go +++ b/controllers/admin/chat.go @@ -9,16 +9,24 @@ import ( "net/http" "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core" "github.com/owncast/owncast/core/chat" - "github.com/owncast/owncast/core/data" - "github.com/owncast/owncast/models" + "github.com/owncast/owncast/core/chat/events" + "github.com/owncast/owncast/core/user" log "github.com/sirupsen/logrus" - "github.com/teris-io/shortid" ) +// ExternalUpdateMessageVisibility updates an array of message IDs to have the same visiblity. +func ExternalUpdateMessageVisibility(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + UpdateMessageVisibility(w, r) +} + // UpdateMessageVisibility updates an array of message IDs to have the same visiblity. func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { + type messageVisibilityUpdateRequest struct { + IDArray []string `json:"idArray"` + Visible bool `json:"visible"` + } + if r.Method != controllers.POST { controllers.WriteSimpleResponse(w, false, r.Method+" not supported") return @@ -27,8 +35,7 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var request messageVisibilityUpdateRequest - err := decoder.Decode(&request) - if err != nil { + if err := decoder.Decode(&request); err != nil { log.Errorln(err) controllers.WriteSimpleResponse(w, false, "") return @@ -42,103 +49,142 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { controllers.WriteSimpleResponse(w, true, "changed") } -type messageVisibilityUpdateRequest struct { - IDArray []string `json:"idArray"` - Visible bool `json:"visible"` +func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { + type blockUserRequest struct { + UserID string `json:"userId"` + Enabled bool `json:"enabled"` + } + + if r.Method != controllers.POST { + controllers.WriteSimpleResponse(w, false, r.Method+" not supported") + return + } + + decoder := json.NewDecoder(r.Body) + var request blockUserRequest + + if err := decoder.Decode(&request); err != nil { + log.Errorln(err) + controllers.WriteSimpleResponse(w, false, "") + return + } + + // Disable/enable the user + if err := user.SetEnabled(request.UserID, request.Enabled); err != nil { + log.Errorln("error changing user enabled status", err) + } + + // Hide/show the user's chat messages if disabling. + // Leave hidden messages hidden to be safe. + if !request.Enabled { + if err := chat.SetMessageVisibilityForUserId(request.UserID, request.Enabled); err != nil { + log.Errorln("error changing user messages visibility", err) + } + } + + // Forcefully disconnect the user from the chat + if !request.Enabled { + chat.DisconnectUser(request.UserID) + disconnectedUser := user.GetUserById(request.UserID) + _ = chat.SendSystemAction(fmt.Sprintf("**%s** has been removed from chat.", disconnectedUser.DisplayName), true) + } + + controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled)) +} + +func GetDisabledUsers(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + users := user.GetDisabledUsers() + controllers.WriteResponse(w, users) } // GetChatMessages returns all of the chat messages, unfiltered. func GetChatMessages(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - messages := core.GetModerationChatMessages() - - if err := json.NewEncoder(w).Encode(messages); err != nil { - log.Errorln(err) - } + messages := chat.GetChatModerationHistory() + controllers.WriteResponse(w, messages) } // SendSystemMessage will send an official "SYSTEM" message to chat on behalf of your server. -func SendSystemMessage(w http.ResponseWriter, r *http.Request) { +func SendSystemMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - var message models.ChatEvent + var message events.SystemMessageEvent if err := json.NewDecoder(r.Body).Decode(&message); err != nil { controllers.InternalErrorHandler(w, err) return } - message.MessageType = models.SystemMessageSent - message.Author = data.GetServerName() - message.ClientID = "owncast-server" - message.ID = shortid.MustGenerate() - message.Visible = true - - message.SetDefaults() - message.RenderBody() - - if err := core.SendMessageToChat(message); err != nil { + if err := chat.SendSystemMessage(message.Body, false); err != nil { controllers.BadRequestHandler(w, err) - return } controllers.WriteSimpleResponse(w, true, "sent") } -// SendUserMessage will send a message to chat on behalf of a user. -func SendUserMessage(w http.ResponseWriter, r *http.Request) { +// SendUserMessage will send a message to chat on behalf of a user. *Depreciated*. +func SendUserMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + controllers.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send")) +} + +func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - var message models.ChatEvent - if err := json.NewDecoder(r.Body).Decode(&message); err != nil { + name := integration.DisplayName + + if name == "" { + controllers.BadRequestHandler(w, errors.New("unknown integration for provided access token")) + return + } + + var event events.UserMessageEvent + if err := json.NewDecoder(r.Body).Decode(&event); err != nil { controllers.InternalErrorHandler(w, err) return } + event.SetDefaults() + event.RenderBody() + event.Type = "CHAT" - if !message.Valid() { - controllers.BadRequestHandler(w, errors.New("invalid chat message; id, author, and body are required")) + if event.Empty() { + controllers.BadRequestHandler(w, errors.New("invalid message")) return } - message.MessageType = models.MessageSent - message.ClientID = "external-request" - message.ID = shortid.MustGenerate() - message.Visible = true + event.User = &user.User{ + Id: integration.Id, + DisplayName: name, + DisplayColor: integration.DisplayColor, + CreatedAt: integration.CreatedAt, + } - message.SetDefaults() - message.RenderAndSanitizeMessageBody() - - if err := core.SendMessageToChat(message); err != nil { + if err := chat.Broadcast(&event); err != nil { controllers.BadRequestHandler(w, err) return } + chat.SaveUserMessage(event) + controllers.WriteSimpleResponse(w, true, "sent") } // SendChatAction will send a generic chat action. -func SendChatAction(w http.ResponseWriter, r *http.Request) { +func SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - var message models.ChatEvent + var message events.SystemActionEvent if err := json.NewDecoder(r.Body).Decode(&message); err != nil { controllers.InternalErrorHandler(w, err) return } - message.MessageType = models.ChatActionSent - message.ClientID = "external-request" - message.ID = shortid.MustGenerate() - message.Visible = true - - if message.Author != "" { - message.Body = fmt.Sprintf("%s %s", message.Author, message.Body) - } - message.SetDefaults() - message.RenderAndSanitizeMessageBody() + message.RenderBody() - if err := core.SendMessageToChat(message); err != nil { + if err := chat.SendSystemAction(message.Body, false); err != nil { controllers.BadRequestHandler(w, err) return } diff --git a/controllers/admin/config.go b/controllers/admin/config.go index e926e0ad5..471c22669 100644 --- a/controllers/admin/config.go +++ b/controllers/admin/config.go @@ -12,8 +12,9 @@ import ( "strings" "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core" + "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/models" "github.com/owncast/owncast/utils" log "github.com/sirupsen/logrus" @@ -71,17 +72,12 @@ func SetStreamTitle(w http.ResponseWriter, r *http.Request) { controllers.WriteSimpleResponse(w, true, "changed") } +func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + SetStreamTitle(w, r) +} + func sendSystemChatAction(messageText string, ephemeral bool) { - message := models.ChatEvent{} - message.Body = messageText - message.MessageType = models.ChatActionSent - message.ClientID = "internal-server" - message.Ephemeral = ephemeral - message.SetDefaults() - - message.RenderBody() - - if err := core.SendMessageToChat(message); err != nil { + if err := chat.SendSystemAction(messageText, ephemeral); err != nil { log.Errorln(err) } } @@ -576,17 +572,24 @@ func SetCustomStyles(w http.ResponseWriter, r *http.Request) { controllers.WriteSimpleResponse(w, true, "custom styles updated") } -// SetUsernameBlocklist will set the list of usernames we do not allow to use. -func SetUsernameBlocklist(w http.ResponseWriter, r *http.Request) { - usernames, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update chat username blocklist") +// SetForbiddenUsernameList will set the list of usernames we do not allow to use. +func SetForbiddenUsernameList(w http.ResponseWriter, r *http.Request) { + type forbiddenUsernameListRequest struct { + Value []string `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var request forbiddenUsernameListRequest + if err := decoder.Decode(&request); err != nil { + controllers.WriteSimpleResponse(w, false, "unable to update forbidden usernames with provided values") return } - data.SetUsernameBlocklist(usernames.Value.(string)) + if err := data.SetForbiddenUsernameList(request.Value); err != nil { + controllers.WriteSimpleResponse(w, false, err.Error()) + } - controllers.WriteSimpleResponse(w, true, "blocklist updated") + controllers.WriteSimpleResponse(w, true, "forbidden username list updated") } func requirePOST(w http.ResponseWriter, r *http.Request) bool { diff --git a/controllers/admin/connectedClients.go b/controllers/admin/connectedClients.go new file mode 100644 index 000000000..410f9e1a4 --- /dev/null +++ b/controllers/admin/connectedClients.go @@ -0,0 +1,25 @@ +package admin + +import ( + "encoding/json" + "net/http" + + "github.com/owncast/owncast/controllers" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/core/user" +) + +// GetConnectedClients returns currently connected clients. +func GetConnectedClients(w http.ResponseWriter, r *http.Request) { + clients := chat.GetClients() + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(clients); err != nil { + controllers.InternalErrorHandler(w, err) + } +} + +// ExternalGetConnectedClients returns currently connected clients. +func ExternalGetConnectedClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + GetConnectedClients(w, r) +} diff --git a/controllers/admin/accessToken.go b/controllers/admin/externalAPIUsers.go similarity index 56% rename from controllers/admin/accessToken.go rename to controllers/admin/externalAPIUsers.go index 077004273..3e932936b 100644 --- a/controllers/admin/accessToken.go +++ b/controllers/admin/externalAPIUsers.go @@ -7,31 +7,30 @@ import ( "time" "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core/data" - "github.com/owncast/owncast/models" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/utils" ) -type deleteTokenRequest struct { +type deleteExternalAPIUserRequest struct { Token string `json:"token"` } -type createTokenRequest struct { +type createExternalAPIUserRequest struct { Name string `json:"name"` Scopes []string `json:"scopes"` } -// CreateAccessToken will generate a 3rd party access token. -func CreateAccessToken(w http.ResponseWriter, r *http.Request) { +// CreateExternalAPIUser will generate a 3rd party access token. +func CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) - var request createTokenRequest + var request createExternalAPIUserRequest if err := decoder.Decode(&request); err != nil { controllers.BadRequestHandler(w, err) return } // Verify all the scopes provided are valid - if !models.HasValidScopes(request.Scopes) { + if !user.HasValidScopes(request.Scopes) { controllers.BadRequestHandler(w, errors.New("one or more invalid scopes provided")) return } @@ -42,26 +41,29 @@ func CreateAccessToken(w http.ResponseWriter, r *http.Request) { return } - if err := data.InsertToken(token, request.Name, request.Scopes); err != nil { + color := utils.GenerateRandomDisplayColor() + + if err := user.InsertExternalAPIUser(token, request.Name, color, request.Scopes); err != nil { controllers.InternalErrorHandler(w, err) return } w.Header().Set("Content-Type", "application/json") - controllers.WriteResponse(w, models.AccessToken{ - Token: token, - Name: request.Name, - Scopes: request.Scopes, - Timestamp: time.Now(), - LastUsed: nil, + controllers.WriteResponse(w, user.ExternalAPIUser{ + AccessToken: token, + DisplayName: request.Name, + DisplayColor: color, + Scopes: request.Scopes, + CreatedAt: time.Now(), + LastUsedAt: nil, }) } -// GetAccessTokens will return all 3rd party access tokens. -func GetAccessTokens(w http.ResponseWriter, r *http.Request) { +// GetExternalAPIUsers will return all 3rd party access tokens. +func GetExternalAPIUsers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - tokens, err := data.GetAccessTokens() + tokens, err := user.GetExternalAPIUser() if err != nil { controllers.InternalErrorHandler(w, err) return @@ -70,8 +72,8 @@ func GetAccessTokens(w http.ResponseWriter, r *http.Request) { controllers.WriteResponse(w, tokens) } -// DeleteAccessToken will return a single 3rd party access token. -func DeleteAccessToken(w http.ResponseWriter, r *http.Request) { +// DeleteExternalAPIUser will return a single 3rd party access token. +func DeleteExternalAPIUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != controllers.POST { @@ -80,7 +82,7 @@ func DeleteAccessToken(w http.ResponseWriter, r *http.Request) { } decoder := json.NewDecoder(r.Body) - var request deleteTokenRequest + var request deleteExternalAPIUserRequest if err := decoder.Decode(&request); err != nil { controllers.BadRequestHandler(w, err) return @@ -91,7 +93,7 @@ func DeleteAccessToken(w http.ResponseWriter, r *http.Request) { return } - if err := data.DeleteToken(request.Token); err != nil { + if err := user.DeleteExternalAPIUser(request.Token); err != nil { controllers.InternalErrorHandler(w, err) return } diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go index b7f95a304..bf854064a 100644 --- a/controllers/admin/serverConfig.go +++ b/controllers/admin/serverConfig.go @@ -15,6 +15,7 @@ import ( // GetServerConfig gets the config details of the server. func GetServerConfig(w http.ResponseWriter, r *http.Request) { ffmpeg := utils.ValidatedFfmpegPath(data.GetFfMpegPath()) + usernameBlocklist := data.GetForbiddenUsernameList() var videoQualityVariants = make([]models.StreamOutputVariant, 0) for _, variant := range data.GetStreamOutputVariants() { @@ -57,11 +58,11 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { Enabled: data.GetDirectoryEnabled(), InstanceURL: data.GetServerURL(), }, - S3: data.GetS3Config(), - ExternalActions: data.GetExternalActions(), - SupportedCodecs: transcoder.GetCodecs(ffmpeg), - VideoCodec: data.GetVideoCodec(), - UsernameBlocklist: data.GetUsernameBlocklist(), + S3: data.GetS3Config(), + ExternalActions: data.GetExternalActions(), + SupportedCodecs: transcoder.GetCodecs(ffmpeg), + VideoCodec: data.GetVideoCodec(), + ForbiddenUsernames: usernameBlocklist, } w.Header().Set("Content-Type", "application/json") @@ -71,20 +72,20 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { } type serverConfigAdminResponse struct { - InstanceDetails webConfigResponse `json:"instanceDetails"` - FFmpegPath string `json:"ffmpegPath"` - StreamKey string `json:"streamKey"` - WebServerPort int `json:"webServerPort"` - WebServerIP string `json:"webServerIP"` - RTMPServerPort int `json:"rtmpServerPort"` - S3 models.S3 `json:"s3"` - VideoSettings videoSettings `json:"videoSettings"` - YP yp `json:"yp"` - ChatDisabled bool `json:"chatDisabled"` - ExternalActions []models.ExternalAction `json:"externalActions"` - SupportedCodecs []string `json:"supportedCodecs"` - VideoCodec string `json:"videoCodec"` - UsernameBlocklist string `json:"usernameBlocklist"` + InstanceDetails webConfigResponse `json:"instanceDetails"` + FFmpegPath string `json:"ffmpegPath"` + StreamKey string `json:"streamKey"` + WebServerPort int `json:"webServerPort"` + WebServerIP string `json:"webServerIP"` + RTMPServerPort int `json:"rtmpServerPort"` + S3 models.S3 `json:"s3"` + VideoSettings videoSettings `json:"videoSettings"` + YP yp `json:"yp"` + ChatDisabled bool `json:"chatDisabled"` + ExternalActions []models.ExternalAction `json:"externalActions"` + SupportedCodecs []string `json:"supportedCodecs"` + VideoCodec string `json:"videoCodec"` + ForbiddenUsernames []string `json:"forbiddenUsernames"` } type videoSettings struct { diff --git a/controllers/chat.go b/controllers/chat.go index bba843a00..45a11738d 100644 --- a/controllers/chat.go +++ b/controllers/chat.go @@ -4,11 +4,17 @@ import ( "encoding/json" "net/http" - "github.com/owncast/owncast/core" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/router/middleware" log "github.com/sirupsen/logrus" ) +// ExternalGetChatMessages gets all of the chat messages. +func ExternalGetChatMessages(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + GetChatEmbed(w, r) +} + // GetChatMessages gets all of the chat messages. func GetChatMessages(w http.ResponseWriter, r *http.Request) { middleware.EnableCors(&w) @@ -16,7 +22,7 @@ func GetChatMessages(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - messages := core.GetAllChatMessages() + messages := chat.GetChatHistory() if err := json.NewEncoder(w).Encode(messages); err != nil { log.Errorln(err) @@ -28,3 +34,43 @@ func GetChatMessages(w http.ResponseWriter, r *http.Request) { } } } + +// RegisterAnonymousChatUser will register a new user. +func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method != POST { + WriteSimpleResponse(w, false, r.Method+" not supported") + return + } + + type registerAnonymousUserRequest struct { + DisplayName string `json:"displayName"` + } + + type registerAnonymousUserResponse struct { + Id string `json:"id"` + AccessToken string `json:"accessToken"` + DisplayName string `json:"displayName"` + } + + decoder := json.NewDecoder(r.Body) + var request registerAnonymousUserRequest + if err := decoder.Decode(&request); err != nil { //nolint + // this is fine. register a new user anyway. + } + + newUser, err := user.CreateAnonymousUser(request.DisplayName) + if err != nil { + WriteSimpleResponse(w, false, err.Error()) + return + } + + response := registerAnonymousUserResponse{ + Id: newUser.Id, + AccessToken: newUser.AccessToken, + DisplayName: newUser.DisplayName, + } + + WriteResponse(w, response) +} diff --git a/controllers/config.go b/controllers/config.go index ea302f24c..4fe29e7b5 100644 --- a/controllers/config.go +++ b/controllers/config.go @@ -12,18 +12,19 @@ import ( ) type webConfigResponse struct { - Name string `json:"name"` - Summary string `json:"summary"` - Logo string `json:"logo"` - Tags []string `json:"tags"` - Version string `json:"version"` - NSFW bool `json:"nsfw"` - ExtraPageContent string `json:"extraPageContent"` - StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream - SocialHandles []models.SocialHandle `json:"socialHandles"` - ChatDisabled bool `json:"chatDisabled"` - ExternalActions []models.ExternalAction `json:"externalActions"` - CustomStyles string `json:"customStyles"` + Name string `json:"name"` + Summary string `json:"summary"` + Logo string `json:"logo"` + Tags []string `json:"tags"` + Version string `json:"version"` + NSFW bool `json:"nsfw"` + ExtraPageContent string `json:"extraPageContent"` + StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream + SocialHandles []models.SocialHandle `json:"socialHandles"` + ChatDisabled bool `json:"chatDisabled"` + ExternalActions []models.ExternalAction `json:"externalActions"` + CustomStyles string `json:"customStyles"` + MaxSocketPayloadSize int `json:"maxSocketPayloadSize"` } // GetWebConfig gets the status of the server. @@ -45,18 +46,19 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) { serverSummary = utils.RenderPageContentMarkdown(serverSummary) configuration := webConfigResponse{ - Name: data.GetServerName(), - Summary: serverSummary, - Logo: "/logo", - Tags: data.GetServerMetadataTags(), - Version: config.GetReleaseString(), - NSFW: data.GetNSFW(), - ExtraPageContent: pageContent, - StreamTitle: data.GetStreamTitle(), - SocialHandles: socialHandles, - ChatDisabled: data.GetChatDisabled(), - ExternalActions: data.GetExternalActions(), - CustomStyles: data.GetCustomStyles(), + Name: data.GetServerName(), + Summary: serverSummary, + Logo: "/logo", + Tags: data.GetServerMetadataTags(), + Version: config.GetReleaseString(), + NSFW: data.GetNSFW(), + ExtraPageContent: pageContent, + StreamTitle: data.GetStreamTitle(), + SocialHandles: socialHandles, + ChatDisabled: data.GetChatDisabled(), + ExternalActions: data.GetExternalActions(), + CustomStyles: data.GetCustomStyles(), + MaxSocketPayloadSize: config.MaxSocketPayloadSize, } if err := json.NewEncoder(w).Encode(configuration); err != nil { diff --git a/controllers/connectedClients.go b/controllers/connectedClients.go deleted file mode 100644 index 384abd2d6..000000000 --- a/controllers/connectedClients.go +++ /dev/null @@ -1,18 +0,0 @@ -package controllers - -import ( - "encoding/json" - "net/http" - - "github.com/owncast/owncast/core" -) - -// GetConnectedClients returns currently connected clients. -func GetConnectedClients(w http.ResponseWriter, r *http.Request) { - clients := core.GetChatClients() - w.Header().Set("Content-Type", "application/json") - - if err := json.NewEncoder(w).Encode(clients); err != nil { - InternalErrorHandler(w, err) - } -} diff --git a/core/chat/chat.go b/core/chat/chat.go index 631887d7f..179d602de 100644 --- a/core/chat/chat.go +++ b/core/chat/chat.go @@ -2,84 +2,113 @@ package chat import ( "errors" - "time" + "net/http" + "sort" + "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/models" + log "github.com/sirupsen/logrus" ) -// Setup sets up the chat server. -func Setup(listener models.ChatListener) { +var getStatus func() models.Status + +func Start(getStatusFunc func() models.Status) error { setupPersistence() - clients := make(map[string]*Client) - addCh := make(chan *Client) - delCh := make(chan *Client) - sendAllCh := make(chan models.ChatEvent) - pingCh := make(chan models.PingMessage) - doneCh := make(chan bool) - errCh := make(chan error) + getStatus = getStatusFunc + _server = NewChat() - _server = &server{ - clients, - "/entry", //hardcoded due to the UI requiring this and it is not configurable - listener, - addCh, - delCh, - sendAllCh, - pingCh, - doneCh, - errCh, - } -} + go _server.Run() -// Start starts the chat server. -func Start() error { - if _server == nil { - return errors.New("chat server is nil") - } + log.Traceln("Chat server started with max connection count of", _server.maxClientCount) - ticker := time.NewTicker(30 * time.Second) - go func() { - for range ticker.C { - _server.ping() - } - }() - - _server.Listen() - - return errors.New("chat server failed to start") -} - -// SendMessage sends a message to all. -func SendMessage(message models.ChatEvent) { - if _server == nil { - return - } - - _server.SendToAll(message) -} - -// GetMessages gets all of the messages. -func GetMessages() []models.ChatEvent { - if _server == nil { - return []models.ChatEvent{} - } - - return getChatHistory() -} - -func GetModerationChatMessages() []models.ChatEvent { - return getChatModerationHistory() -} - -func GetClient(clientID string) *Client { - l.RLock() - defer l.RUnlock() - - for _, client := range _server.Clients { - if client.ClientID == clientID { - return client - } - } return nil } + +// GetClientsForUser will return chat connections that are owned by a specific user. +func GetClientsForUser(userID string) ([]*ChatClient, error) { + clients := map[string][]*ChatClient{} + + for _, client := range _server.clients { + clients[client.User.Id] = append(clients[client.User.Id], client) + } + + if _, exists := clients[userID]; !exists { + return nil, errors.New("no connections for user found") + } + + return clients[userID], nil +} + +func GetClients() []*ChatClient { + clients := []*ChatClient{} + + // Convert the keyed map to a slice. + for _, client := range _server.clients { + clients = append(clients, client) + } + + sort.Slice(clients, func(i, j int) bool { + return clients[i].ConnectedAt.Before(clients[j].ConnectedAt) + }) + + return clients +} + +func SendSystemMessage(text string, ephemeral bool) error { + message := events.SystemMessageEvent{ + MessageEvent: events.MessageEvent{ + Body: text, + }, + } + message.SetDefaults() + message.RenderBody() + + if err := Broadcast(&message); err != nil { + log.Errorln("error sending system message", err) + } + + if !ephemeral { + saveEvent(message.Id, "system", message.Body, message.GetMessageType(), nil, message.Timestamp) + } + + return nil +} + +func SendSystemAction(text string, ephemeral bool) error { + message := events.ActionEvent{ + MessageEvent: events.MessageEvent{ + Body: text, + }, + } + + message.SetDefaults() + message.RenderBody() + + if err := Broadcast(&message); err != nil { + log.Errorln("error sending system chat action") + } + + if !ephemeral { + saveEvent(message.Id, "action", message.Body, message.GetMessageType(), nil, message.Timestamp) + } + + return nil +} + +func SendAllWelcomeMessage() { + _server.sendAllWelcomeMessage() +} + +func Broadcast(event events.OutboundEvent) error { + return _server.Broadcast(event.GetBroadcastPayload()) +} + +func HandleClientConnection(w http.ResponseWriter, r *http.Request) { + _server.HandleClientConnection(w, r) +} + +// DisconnectUser will forcefully disconnect all clients belonging to a user by ID. +func DisconnectUser(userID string) { + _server.DisconnectUser(userID) +} diff --git a/core/chat/chatclient.go b/core/chat/chatclient.go new file mode 100644 index 000000000..69015b007 --- /dev/null +++ b/core/chat/chatclient.go @@ -0,0 +1,190 @@ +package chat + +import ( + "bytes" + "encoding/json" + "time" + + log "github.com/sirupsen/logrus" + "golang.org/x/time/rate" + + "github.com/gorilla/websocket" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core/chat/events" + "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/geoip" +) + +type ChatClient struct { + id uint + accessToken string + conn *websocket.Conn + User *user.User `json:"user"` + server *ChatServer + ipAddress string `json:"-"` + // Buffered channel of outbound messages. + send chan []byte + rateLimiter *rate.Limiter + Geo *geoip.GeoDetails `json:"geo"` + MessageCount int `json:"messageCount"` + UserAgent string `json:"userAgent"` + ConnectedAt time.Time `json:"connectedAt"` +} + +type chatClientEvent struct { + data []byte + client *ChatClient +} + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + // Larger messages get thrown away. + // Messages > *2 the socket gets closed. + maxMessageSize = config.MaxSocketPayloadSize +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +var ( + newline = []byte{'\n'} + space = []byte{' '} +) + +func (c *ChatClient) sendConnectedClientInfo() { + payload := events.EventPayload{ + "type": events.ConnectedUserInfo, + "user": c.User, + } + + c.sendPayload(payload) +} + +func (c *ChatClient) readPump() { + c.rateLimiter = rate.NewLimiter(0.6, 5) + + defer func() { + c.close() + }() + + // If somebody is sending 2x the max message size they're likely a bad actor + // and should be disconnected. Below we throw away messages > max size. + c.conn.SetReadLimit(maxMessageSize * 2) + + _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, message, err := c.conn.ReadMessage() + + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + c.close() + } + break + } + + // Throw away messages greater than max message size. + if len(message) > maxMessageSize { + c.sendAction("Sorry, that message exceeded the maximum size and can't be delivered.") + continue + } + + // Guard against floods. + if !c.passesRateLimit() { + continue + } + + message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) + c.handleEvent(message) + } +} + +func (c *ChatClient) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + + for { + select { + case message, ok := <-c.send: + _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The server closed the channel. + _ = c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + if _, err := w.Write(message); err != nil { + log.Debugln(err) + } + + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +func (c *ChatClient) handleEvent(data []byte) { + c.server.inbound <- chatClientEvent{data: data, client: c} +} + +func (c *ChatClient) close() { + log.Traceln("client closed:", c.User.DisplayName, c.id, c.ipAddress) + + c.conn.Close() + c.server.unregister <- c +} + +func (c *ChatClient) passesRateLimit() bool { + if !c.rateLimiter.Allow() { + log.Debugln("Client", c.id, c.User.DisplayName, "has exceeded the messaging rate limiting thresholds.") + return false + } + + return true +} + +func (c *ChatClient) sendPayload(payload events.EventPayload) { + var data []byte + data, err := json.Marshal(payload) + if err != nil { + log.Errorln(err) + return + } + + c.send <- data +} + +func (c *ChatClient) sendAction(message string) { + clientMessage := events.ActionEvent{ + MessageEvent: events.MessageEvent{ + Body: message, + }, + } + clientMessage.SetDefaults() + clientMessage.RenderBody() + c.sendPayload(clientMessage.GetBroadcastPayload()) +} diff --git a/core/chat/client.go b/core/chat/client.go deleted file mode 100644 index bb9275ab4..000000000 --- a/core/chat/client.go +++ /dev/null @@ -1,241 +0,0 @@ -package chat - -import ( - "encoding/json" - "fmt" - "io" - "time" - - log "github.com/sirupsen/logrus" - "golang.org/x/net/websocket" - - "github.com/owncast/owncast/geoip" - "github.com/owncast/owncast/models" - "github.com/owncast/owncast/utils" - - "github.com/teris-io/shortid" - "golang.org/x/time/rate" -) - -const channelBufSize = 100 - -//Client represents a chat client. -type Client struct { - ConnectedAt time.Time - MessageCount int - UserAgent string - IPAddress string - Username *string - ClientID string // How we identify unique viewers when counting viewer counts. - Geo *geoip.GeoDetails `json:"geo"` - Ignore bool // If set to true this will not be treated as a viewer - - socketID string // How we identify a single websocket client. - ws *websocket.Conn - ch chan models.ChatEvent - pingch chan models.PingMessage - usernameChangeChannel chan models.NameChangeEvent - userJoinedChannel chan models.UserJoinedEvent - - doneCh chan bool - - rateLimiter *rate.Limiter -} - -// NewClient creates a new chat client. -func NewClient(ws *websocket.Conn) *Client { - if ws == nil { - log.Panicln("ws cannot be nil") - } - - var ignoreClient = false - for _, extraData := range ws.Config().Protocol { - if extraData == "IGNORE_CLIENT" { - ignoreClient = true - } - } - - ch := make(chan models.ChatEvent, channelBufSize) - doneCh := make(chan bool) - pingch := make(chan models.PingMessage) - usernameChangeChannel := make(chan models.NameChangeEvent) - userJoinedChannel := make(chan models.UserJoinedEvent) - - ipAddress := utils.GetIPAddressFromRequest(ws.Request()) - userAgent := ws.Request().UserAgent() - socketID, _ := shortid.Generate() - clientID := socketID - - rateLimiter := rate.NewLimiter(0.6, 5) - - return &Client{time.Now(), 0, userAgent, ipAddress, nil, clientID, nil, ignoreClient, socketID, ws, ch, pingch, usernameChangeChannel, userJoinedChannel, doneCh, rateLimiter} -} - -func (c *Client) write(msg models.ChatEvent) { - select { - case c.ch <- msg: - default: - _server.removeClient(c) - _server.err(fmt.Errorf("client %s is disconnected", c.ClientID)) - } -} - -// Listen Write and Read request via channel. -func (c *Client) listen() { - go c.listenWrite() - c.listenRead() -} - -// Listen write request via channel. -func (c *Client) listenWrite() { - for { - select { - // Send a PING keepalive - case msg := <-c.pingch: - if err := websocket.JSON.Send(c.ws, msg); err != nil { - c.handleClientSocketError(err) - } - // send message to the client - case msg := <-c.ch: - if err := websocket.JSON.Send(c.ws, msg); err != nil { - c.handleClientSocketError(err) - } - case msg := <-c.usernameChangeChannel: - if err := websocket.JSON.Send(c.ws, msg); err != nil { - c.handleClientSocketError(err) - } - case msg := <-c.userJoinedChannel: - if err := websocket.JSON.Send(c.ws, msg); err != nil { - c.handleClientSocketError(err) - } - - // receive done request - case <-c.doneCh: - _server.removeClient(c) - c.doneCh <- true // for listenRead method - return - } - } -} - -func (c *Client) handleClientSocketError(err error) { - _server.removeClient(c) -} - -func (c *Client) passesRateLimit() bool { - if !c.rateLimiter.Allow() { - log.Debugln("Client", c.ClientID, "has exceeded the messaging rate limiting thresholds.") - return false - } - - return true -} - -// Listen read request via channel. -func (c *Client) listenRead() { - for { - select { - // receive done request - case <-c.doneCh: - _server.remove(c) - c.doneCh <- true // for listenWrite method - return - - // read data from websocket connection - default: - var data []byte - if err := websocket.Message.Receive(c.ws, &data); err != nil { - if err == io.EOF { - c.doneCh <- true - return - } - c.handleClientSocketError(err) - } - - if !c.passesRateLimit() { - continue - } - - var messageTypeCheck map[string]interface{} - - // Bad messages should be thrown away - if err := json.Unmarshal(data, &messageTypeCheck); err != nil { - log.Debugln("Badly formatted message received from", c.Username, c.ws.Request().RemoteAddr) - continue - } - - // If we can't tell the type of message, also throw it away. - if messageTypeCheck == nil { - log.Debugln("Untyped message received from", c.Username, c.ws.Request().RemoteAddr) - continue - } - - messageType := messageTypeCheck["type"].(string) - - if messageType == models.MessageSent { - c.chatMessageReceived(data) - } else if messageType == models.UserNameChanged { - c.userChangedName(data) - } else if messageType == models.UserJoined { - c.userJoined(data) - } - } - } -} - -func (c *Client) userJoined(data []byte) { - var msg models.UserJoinedEvent - if err := json.Unmarshal(data, &msg); err != nil { - log.Errorln(err) - return - } - - msg.ID = shortid.MustGenerate() - msg.Type = models.UserJoined - msg.Timestamp = time.Now() - - c.Username = &msg.Username - - _server.userJoined(msg) -} - -func (c *Client) userChangedName(data []byte) { - var msg models.NameChangeEvent - if err := json.Unmarshal(data, &msg); err != nil { - log.Errorln(err) - } - msg.Type = models.UserNameChanged - msg.ID = shortid.MustGenerate() - _server.usernameChanged(msg) - c.Username = &msg.NewName -} - -func (c *Client) chatMessageReceived(data []byte) { - var msg models.ChatEvent - if err := json.Unmarshal(data, &msg); err != nil { - log.Errorln(err) - } - - msg.SetDefaults() - - c.MessageCount++ - c.Username = &msg.Author - - msg.ClientID = c.ClientID - msg.RenderAndSanitizeMessageBody() - - _server.SendToAll(msg) -} - -// GetViewerClientFromChatClient returns a general models.Client from a chat websocket client. -func (c *Client) GetViewerClientFromChatClient() models.Client { - return models.Client{ - ConnectedAt: c.ConnectedAt, - MessageCount: c.MessageCount, - UserAgent: c.UserAgent, - IPAddress: c.IPAddress, - Username: c.Username, - ClientID: c.ClientID, - Geo: geoip.GetGeoFromIP(c.IPAddress), - } -} diff --git a/core/chat/events.go b/core/chat/events.go new file mode 100644 index 000000000..9277c556d --- /dev/null +++ b/core/chat/events.go @@ -0,0 +1,102 @@ +package chat + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/owncast/owncast/core/chat/events" + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/core/webhooks" + log "github.com/sirupsen/logrus" +) + +func (s *ChatServer) userNameChanged(eventData chatClientEvent) { + var receivedEvent events.NameChangeEvent + if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil { + log.Errorln("error unmarshalling to NameChangeEvent", err) + return + } + + proposedUsername := receivedEvent.NewName + blocklist := data.GetForbiddenUsernameList() + + for _, blockedName := range blocklist { + normalizedName := strings.TrimSpace(blockedName) + normalizedName = strings.ToLower(normalizedName) + if strings.Contains(normalizedName, proposedUsername) { + // Denied. + log.Debugln(eventData.client.User.DisplayName, "blocked from changing name to", proposedUsername, "due to blocked name", normalizedName) + message := fmt.Sprintf("You cannot change your name to **%s**.", proposedUsername) + s.sendActionToClient(eventData.client, message) + + // Resend the client's user so their username is in sync. + eventData.client.sendConnectedClientInfo() + + return + } + } + + savedUser := user.GetUserByToken(eventData.client.accessToken) + oldName := savedUser.DisplayName + + // Save the new name + user.ChangeUsername(eventData.client.User.Id, receivedEvent.NewName) + + // Update the connected clients associated user with the new name + eventData.client.User = savedUser + + // Send chat event letting everyone about about the name change + savedUser.DisplayName = receivedEvent.NewName + + broadcastEvent := events.NameChangeBroadcast{ + Oldname: oldName, + } + broadcastEvent.User = savedUser + broadcastEvent.SetDefaults() + payload := broadcastEvent.GetBroadcastPayload() + if err := s.Broadcast(payload); err != nil { + log.Errorln("error broadcasting NameChangeEvent", err) + return + } + + // Send chat user name changed webhook + receivedEvent.User = savedUser + webhooks.SendChatEventUsernameChanged(receivedEvent) +} + +func (s *ChatServer) userMessageSent(eventData chatClientEvent) { + var event events.UserMessageEvent + if err := json.Unmarshal(eventData.data, &event); err != nil { + log.Errorln("error unmarshalling to UserMessageEvent", err) + return + } + + event.SetDefaults() + + // Ignore empty messages + if event.Empty() { + return + } + + event.User = user.GetUserByToken(eventData.client.accessToken) + + // Guard against nil users + if event.User == nil { + return + } + + payload := event.GetBroadcastPayload() + if err := s.Broadcast(payload); err != nil { + log.Errorln("error broadcasting UserMessageEvent payload", err) + return + } + + // Send chat message sent webhook + webhooks.SendChatEvent(&event) + + SaveUserMessage(event) + + eventData.client.MessageCount = eventData.client.MessageCount + 1 +} diff --git a/core/chat/events/actionEvent.go b/core/chat/events/actionEvent.go new file mode 100644 index 000000000..8f8093a03 --- /dev/null +++ b/core/chat/events/actionEvent.go @@ -0,0 +1,20 @@ +package events + +type ActionEvent struct { + Event + MessageEvent +} + +// ActionEvent will return the object to send to all chat users. +func (e *ActionEvent) GetBroadcastPayload() EventPayload { + return EventPayload{ + "id": e.Id, + "timestamp": e.Timestamp, + "body": e.Body, + "type": e.GetMessageType(), + } +} + +func (e *ActionEvent) GetMessageType() EventType { + return ChatActionSent +} diff --git a/models/chatMessage.go b/core/chat/events/events.go similarity index 64% rename from models/chatMessage.go rename to core/chat/events/events.go index d0e65b317..1fb998060 100644 --- a/models/chatMessage.go +++ b/core/chat/events/events.go @@ -1,4 +1,4 @@ -package models +package events import ( "bytes" @@ -12,38 +12,59 @@ import ( "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/renderer/html" "mvdan.cc/xurls" + + "github.com/owncast/owncast/core/user" + log "github.com/sirupsen/logrus" ) -// ChatEvent represents a single chat message. -type ChatEvent struct { - ClientID string `json:"-"` +// EventPayload is a generic key/value map for sending out to chat clients. +type EventPayload map[string]interface{} - Author string `json:"author,omitempty"` - Body string `json:"body,omitempty"` - RawBody string `json:"-"` - ID string `json:"id"` - MessageType EventType `json:"type"` - Visible bool `json:"visible"` - Timestamp time.Time `json:"timestamp,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` +type OutboundEvent interface { + GetBroadcastPayload() EventPayload + GetMessageType() EventType } -// Valid checks to ensure the message is valid. -func (m ChatEvent) Valid() bool { - return m.Author != "" && m.Body != "" && m.ID != "" +// Event is any kind of event. A type is required to be specified. +type Event struct { + Type EventType `json:"type"` + Id string `json:"id"` + Timestamp time.Time `json:"timestamp"` } -// SetDefaults will set default values on a chat event object. -func (m *ChatEvent) SetDefaults() { - id, _ := shortid.Generate() - m.ID = id - m.Timestamp = time.Now() - m.Visible = true +type UserEvent struct { + User *user.User `json:"user"` + HiddenAt *time.Time `json:"hiddenAt,omitempty"` +} + +// MessageEvent is an event that has a message body. +type MessageEvent struct { + OutboundEvent `json:"-"` + Body string `json:"body"` + RawBody string `json:"-"` +} + +type SystemActionEvent struct { + Event + MessageEvent +} + +// SetDefaults will set default properties of all inbound events. +func (e *Event) SetDefaults() { + e.Id = shortid.MustGenerate() + e.Timestamp = time.Now() +} + +// SetDefaults will set default properties of all inbound events. +func (e *UserMessageEvent) SetDefaults() { + e.Id = shortid.MustGenerate() + e.Timestamp = time.Now() + e.RenderAndSanitizeMessageBody() } // RenderAndSanitizeMessageBody will turn markdown into HTML, sanitize raw user-supplied HTML and standardize // the message into something safe and renderable for clients. -func (m *ChatEvent) RenderAndSanitizeMessageBody() { +func (m *MessageEvent) RenderAndSanitizeMessageBody() { m.RawBody = m.Body // Set the new, sanitized and rendered message body @@ -51,12 +72,12 @@ func (m *ChatEvent) RenderAndSanitizeMessageBody() { } // Empty will return if this message's contents is empty. -func (m *ChatEvent) Empty() bool { +func (m *MessageEvent) Empty() bool { return m.Body == "" } -// RenderBody will render markdown to html without any sanitization -func (m *ChatEvent) RenderBody() { +// RenderBody will render markdown to html without any sanitization. +func (m *MessageEvent) RenderBody() { m.RawBody = m.Body m.Body = RenderMarkdown(m.RawBody) } @@ -92,7 +113,7 @@ func RenderMarkdown(raw string) string { trimmed := strings.TrimSpace(raw) var buf bytes.Buffer if err := markdown.Convert([]byte(trimmed), &buf); err != nil { - panic(err) + log.Debugln(err) } return buf.String() diff --git a/core/chat/events/eventtype.go b/core/chat/events/eventtype.go new file mode 100644 index 000000000..aaed78257 --- /dev/null +++ b/core/chat/events/eventtype.go @@ -0,0 +1,34 @@ +package events + +// EventType is the type of a websocket event. +type EventType = string + +const ( + // MessageSent is the event sent when a chat event takes place. + MessageSent EventType = "CHAT" + // UserJoined is the event sent when a chat user join action takes place. + UserJoined EventType = "USER_JOINED" + // UserNameChanged is the event sent when a chat username change takes place. + UserNameChanged EventType = "NAME_CHANGE" + // VisibiltyToggled is the event sent when a chat message's visibility changes. + VisibiltyToggled EventType = "VISIBILITY-UPDATE" + // PING is a ping message. + PING EventType = "PING" + // PONG is a pong message. + PONG EventType = "PONG" + // StreamStarted represents a stream started event. + StreamStarted EventType = "STREAM_STARTED" + // StreamStopped represents a stream stopped event. + StreamStopped EventType = "STREAM_STOPPED" + // SystemMessageSent is the event sent when a system message is sent. + SystemMessageSent EventType = "SYSTEM" + // ChatDisabled is when a user is explicitly disabled and blocked from using chat. + ChatDisabled EventType = "CHAT_DISABLED" + // ConnectedUserInfo is a private event to a user letting them know their user details. + ConnectedUserInfo EventType = "CONNECTED_USER_INFO" + // ChatActionSent is a generic chat action that can be used for anything that doesn't need specific handling or formatting. + ChatActionSent EventType = "CHAT_ACTION" + ErrorNeedsRegistration EventType = "ERROR_NEEDS_REGISTRATION" + ErrorMaxConnectionsExceeded EventType = "ERROR_MAX_CONNECTIONS_EXCEEDED" + ErrorUserDisabled EventType = "ERROR_USER_DISABLED" +) diff --git a/core/chat/events/nameChangeEvent.go b/core/chat/events/nameChangeEvent.go new file mode 100644 index 000000000..7eb898644 --- /dev/null +++ b/core/chat/events/nameChangeEvent.go @@ -0,0 +1,26 @@ +package events + +// NameChangeEvent is received when a user changes their chat display name. +type NameChangeEvent struct { + Event + UserEvent + NewName string `json:"newName"` +} + +// NameChangeEventBroadcast is fired when a user changes their chat display name. +type NameChangeBroadcast struct { + Event + UserEvent + Oldname string `json:"oldName"` +} + +// GetBroadcastPayload will return the object to send to all chat users. +func (e *NameChangeBroadcast) GetBroadcastPayload() EventPayload { + return EventPayload{ + "id": e.Id, + "timestamp": e.Timestamp, + "user": e.User, + "oldName": e.Oldname, + "type": UserNameChanged, + } +} diff --git a/core/chat/events/systemMessageEvent.go b/core/chat/events/systemMessageEvent.go new file mode 100644 index 000000000..0bb79081c --- /dev/null +++ b/core/chat/events/systemMessageEvent.go @@ -0,0 +1,26 @@ +package events + +import "github.com/owncast/owncast/core/data" + +// SystemMessageEvent is a message displayed in chat on behalf of the server. +type SystemMessageEvent struct { + Event + MessageEvent +} + +// SystemMessageEvent will return the object to send to all chat users. +func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload { + return EventPayload{ + "id": e.Id, + "timestamp": e.Timestamp, + "body": e.Body, + "type": SystemMessageSent, + "user": EventPayload{ + "displayName": data.GetServerName(), + }, + } +} + +func (e *SystemMessageEvent) GetMessageType() EventType { + return SystemMessageSent +} diff --git a/core/chat/events/userDisabledEvent.go b/core/chat/events/userDisabledEvent.go new file mode 100644 index 000000000..4f5b4b3c9 --- /dev/null +++ b/core/chat/events/userDisabledEvent.go @@ -0,0 +1,17 @@ +package events + +// UserDisabledEvent is the event fired when a user is banned/blocked and disconnected from chat. +type UserDisabledEvent struct { + Event + UserEvent +} + +// GetBroadcastPayload will return the object to send to all chat users. +func (e *UserDisabledEvent) GetBroadcastPayload() EventPayload { + return EventPayload{ + "type": ErrorUserDisabled, + "id": e.Id, + "timestamp": e.Timestamp, + "user": e.User, + } +} diff --git a/core/chat/events/userJoinedEvent.go b/core/chat/events/userJoinedEvent.go new file mode 100644 index 000000000..dfa332fdf --- /dev/null +++ b/core/chat/events/userJoinedEvent.go @@ -0,0 +1,17 @@ +package events + +// UserJoinedEvent is the event fired when a user joins chat. +type UserJoinedEvent struct { + Event + UserEvent +} + +// GetBroadcastPayload will return the object to send to all chat users. +func (e *UserJoinedEvent) GetBroadcastPayload() EventPayload { + return EventPayload{ + "type": UserJoined, + "id": e.Id, + "timestamp": e.Timestamp, + "user": e.User, + } +} diff --git a/core/chat/events/userMessageEvent.go b/core/chat/events/userMessageEvent.go new file mode 100644 index 000000000..63f9fb567 --- /dev/null +++ b/core/chat/events/userMessageEvent.go @@ -0,0 +1,24 @@ +package events + +// UserMessageEvent is an inbound message from a user. +type UserMessageEvent struct { + Event + UserEvent + MessageEvent +} + +// GetBroadcastPayload will return the object to send to all chat users. +func (e *UserMessageEvent) GetBroadcastPayload() EventPayload { + return EventPayload{ + "id": e.Id, + "timestamp": e.Timestamp, + "body": e.Body, + "user": e.User, + "type": MessageSent, + "visible": e.HiddenAt == nil, + } +} + +func (e *UserMessageEvent) GetMessageType() EventType { + return MessageSent +} diff --git a/core/chat/messageRendering_test.go b/core/chat/messageRendering_test.go index e5cf552f0..609b5d3ae 100644 --- a/core/chat/messageRendering_test.go +++ b/core/chat/messageRendering_test.go @@ -3,7 +3,7 @@ package chat import ( "testing" - "github.com/owncast/owncast/models" + "github.com/owncast/owncast/core/chat/events" ) // Test a bunch of arbitrary markup and markdown to make sure we get sanitized @@ -25,7 +25,7 @@ blah blah blah

test link

` - result := models.RenderAndSanitize(messageContent) + result := events.RenderAndSanitize(messageContent) if result != expected { t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected) } @@ -35,7 +35,7 @@ blah blah blah func TestBlockRemoteImages(t *testing.T) { messageContent := ` test ![](https://via.placeholder.com/350x150)` expected := `

test

` - result := models.RenderAndSanitize(messageContent) + result := events.RenderAndSanitize(messageContent) if result != expected { t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected) @@ -46,7 +46,7 @@ func TestBlockRemoteImages(t *testing.T) { func TestAllowEmojiImages(t *testing.T) { messageContent := ` test ![](/img/emoji/beerparrot.gif)` expected := `

test

` - result := models.RenderAndSanitize(messageContent) + result := events.RenderAndSanitize(messageContent) if result != expected { t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected) @@ -57,7 +57,7 @@ func TestAllowEmojiImages(t *testing.T) { func TestAllowHTML(t *testing.T) { messageContent := `` expected := "

\n" - result := models.RenderMarkdown(messageContent) + result := events.RenderMarkdown(messageContent) if result != expected { t.Errorf("message rendering does not match expected. Got\n%s, \n\n want:\n%s", result, expected) diff --git a/core/chat/messages.go b/core/chat/messages.go index 8d5dfa3e9..9d35be716 100644 --- a/core/chat/messages.go +++ b/core/chat/messages.go @@ -1,8 +1,8 @@ package chat import ( + "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/webhooks" - "github.com/owncast/owncast/models" log "github.com/sirupsen/logrus" ) @@ -22,8 +22,11 @@ func SetMessagesVisibility(messageIDs []string, visibility bool) error { log.Errorln(err) continue } - message.MessageType = models.VisibiltyToggled - _server.sendAll(message) + payload := message.GetBroadcastPayload() + payload["type"] = events.VisibiltyToggled + if err := _server.Broadcast(payload); err != nil { + log.Debugln(err) + } go webhooks.SendChatEvent(message) } diff --git a/core/chat/persistence.go b/core/chat/persistence.go index c25bc118e..4940ebc09 100644 --- a/core/chat/persistence.go +++ b/core/chat/persistence.go @@ -1,171 +1,309 @@ package chat import ( - "database/sql" + "fmt" "strings" "time" _ "github.com/mattn/go-sqlite3" + "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/models" log "github.com/sirupsen/logrus" ) -var _db *sql.DB +var _datastore *data.Datastore + +const ( + maxBacklogHours = 5 // Keep backlog max hours worth of messages + maxBacklogNumber = 50 // Return max number of messages in history request +) func setupPersistence() { - _db = data.GetDatabase() - createTable() + _datastore = data.GetDatastore() + createMessagesTable() + + chatDataPruner := time.NewTicker(5 * time.Minute) + go func() { + runPruner() + for range chatDataPruner.C { + runPruner() + } + }() } -func createTable() { +func createMessagesTable() { createTableSQL := `CREATE TABLE IF NOT EXISTS messages ( "id" string NOT NULL PRIMARY KEY, - "author" TEXT, + "user_id" INTEGER, "body" TEXT, - "messageType" TEXT, - "visible" INTEGER, - "timestamp" DATE + "eventType" TEXT, + "hidden_at" DATETIME, + "timestamp" DATETIME );` - stmt, err := _db.Prepare(createTableSQL) + stmt, err := _datastore.DB.Prepare(createTableSQL) if err != nil { - log.Fatal(err) + log.Fatal("error creating chat messages table", err) } defer stmt.Close() if _, err := stmt.Exec(); err != nil { - log.Warnln(err) + log.Fatal("error creating chat messages table", err) } } -func addMessage(message models.ChatEvent) { - tx, err := _db.Begin() - if err != nil { - log.Fatal(err) - } - stmt, err := tx.Prepare("INSERT INTO messages(id, author, body, messageType, visible, timestamp) values(?, ?, ?, ?, ?, ?)") +func SaveUserMessage(event events.UserMessageEvent) { + saveEvent(event.Id, event.User.Id, event.Body, event.Type, event.HiddenAt, event.Timestamp) +} +func saveEvent(id string, userId string, body string, eventType string, hidden *time.Time, timestamp time.Time) { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() if err != nil { - log.Fatal(err) + log.Errorln("error saving", eventType, err) + return } + + defer tx.Rollback() // nolint + + stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp) values(?, ?, ?, ?, ?, ?)") + if err != nil { + log.Errorln("error saving", eventType, err) + return + } + defer stmt.Close() - if _, err := stmt.Exec(message.ID, message.Author, message.Body, message.MessageType, 1, message.Timestamp); err != nil { - log.Fatal(err) + if _, err = stmt.Exec(id, userId, body, eventType, hidden, timestamp); err != nil { + log.Errorln("error saving", eventType, err) + return } - if err := tx.Commit(); err != nil { - log.Fatal(err) + if err = tx.Commit(); err != nil { + log.Errorln("error saving", eventType, err) + return } } -func getChat(query string) []models.ChatEvent { - history := make([]models.ChatEvent, 0) - rows, err := _db.Query(query) +func getChat(query string) []events.UserMessageEvent { + history := make([]events.UserMessageEvent, 0) + rows, err := _datastore.DB.Query(query) if err != nil { - log.Fatal(err) + log.Errorln("error fetching chat history", err) + return history } defer rows.Close() for rows.Next() { var id string - var author string + var userId string var body string var messageType models.EventType - var visible int + var hiddenAt *time.Time var timestamp time.Time - err = rows.Scan(&id, &author, &body, &messageType, &visible, ×tamp) + var userDisplayName *string + var userDisplayColor *int + var userCreatedAt *time.Time + var userDisabledAt *time.Time + var previousUsernames *string + var userNameChangedAt *time.Time + + // Convert a database row into a chat event + err = rows.Scan(&id, &userId, &body, &messageType, &hiddenAt, ×tamp, &userDisplayName, &userDisplayColor, &userCreatedAt, &userDisabledAt, &previousUsernames, &userNameChangedAt) if err != nil { - log.Debugln(err) - log.Error("There is a problem with the chat database. Restore a backup of owncast.db or remove it and start over.") + log.Errorln("There is a problem converting query to chat objects. Please report this:", query) break } - message := models.ChatEvent{} - message.ID = id - message.Author = author - message.Body = body - message.MessageType = messageType - message.Visible = visible == 1 - message.Timestamp = timestamp + // System messages and chat actions are special and are not from real users + if messageType == events.SystemMessageSent || messageType == events.ChatActionSent { + name := "Owncast" + userDisplayName = &name + color := 200 + userDisplayColor = &color + } + + if previousUsernames == nil { + previousUsernames = userDisplayName + } + + if userCreatedAt == nil { + now := time.Now() + userCreatedAt = &now + } + + user := user.User{ + Id: userId, + AccessToken: "", + DisplayName: *userDisplayName, + DisplayColor: *userDisplayColor, + CreatedAt: *userCreatedAt, + DisabledAt: userDisabledAt, + NameChangedAt: userNameChangedAt, + PreviousNames: strings.Split(*previousUsernames, ","), + } + + message := events.UserMessageEvent{ + Event: events.Event{ + Type: messageType, + Id: id, + Timestamp: timestamp, + }, + UserEvent: events.UserEvent{ + User: &user, + HiddenAt: hiddenAt, + }, + MessageEvent: events.MessageEvent{ + Body: body, + RawBody: body, + }, + } history = append(history, message) } - if err := rows.Err(); err != nil { - log.Fatal(err) - } - return history } -func getChatModerationHistory() []models.ChatEvent { - var query = "SELECT * FROM messages WHERE messageType == 'CHAT' AND datetime(timestamp) >=datetime('now', '-5 Hour')" +func GetChatModerationHistory() []events.UserMessageEvent { + // Get all messages regardless of visibility + var query = "SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC" return getChat(query) } -func getChatHistory() []models.ChatEvent { - // Get all messages sent within the past 5hrs, max 50 - var query = "SELECT * FROM (SELECT * FROM messages WHERE datetime(timestamp) >=datetime('now', '-5 Hour') AND visible = 1 ORDER BY timestamp DESC LIMIT 50) ORDER BY timestamp asc" +func GetChatHistory() []events.UserMessageEvent { + // Get all visible messages + var query = fmt.Sprintf("SELECT id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM (SELECT * FROM messages LEFT OUTER JOIN users ON messages.user_id = users.id WHERE hidden_at IS NULL ORDER BY timestamp DESC LIMIT %d) ORDER BY timestamp asc", maxBacklogNumber) return getChat(query) } +// SetMessageVisibilityForUserId will bulk change the visibility of messages for a user +// and then send out visibility changed events to chat clients. +func SetMessageVisibilityForUserId(userID string, visible bool) error { + // Get a list of IDs from this user within the 5hr window to send to the connected clients to hide + ids := make([]string, 0) + query := fmt.Sprintf("SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS '%s'", userID) + messages := getChat(query) + + if len(messages) == 0 { + return nil + } + + for _, message := range messages { + ids = append(ids, message.Id) + } + + // Tell the clients to hide/show these messages. + return SetMessagesVisibility(ids, visible) +} + func saveMessageVisibility(messageIDs []string, visible bool) error { - tx, err := _db.Begin() + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() if err != nil { - log.Fatal(err) + return err } - stmt, err := tx.Prepare("UPDATE messages SET visible=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")") + stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")") if err != nil { - log.Fatal(err) return err } defer stmt.Close() + var hiddenAt *time.Time + if !visible { + now := time.Now() + hiddenAt = &now + } else { + hiddenAt = nil + } + args := make([]interface{}, len(messageIDs)+1) - args[0] = visible + args[0] = hiddenAt for i, id := range messageIDs { args[i+1] = id } - if _, err := stmt.Exec(args...); err != nil { - log.Fatal(err) + if _, err = stmt.Exec(args...); err != nil { return err } - if err := tx.Commit(); err != nil { - log.Fatal(err) + if err = tx.Commit(); err != nil { return err } return nil } -func getMessageById(messageID string) (models.ChatEvent, error) { +func getMessageById(messageID string) (*events.UserMessageEvent, error) { var query = "SELECT * FROM messages WHERE id = ?" - row := _db.QueryRow(query, messageID) + row := _datastore.DB.QueryRow(query, messageID) var id string - var author string + var userId string var body string - var messageType models.EventType - var visible int + var eventType models.EventType + var hiddenAt *time.Time var timestamp time.Time - err := row.Scan(&id, &author, &body, &messageType, &visible, ×tamp) + err := row.Scan(&id, &userId, &body, &eventType, &hiddenAt, ×tamp) if err != nil { log.Errorln(err) - return models.ChatEvent{}, err + return nil, err } - return models.ChatEvent{ - ID: id, - Author: author, - Body: body, - MessageType: messageType, - Visible: visible == 1, - Timestamp: timestamp, + user := user.GetUserById(userId) + + return &events.UserMessageEvent{ + events.Event{ + Type: eventType, + Id: id, + Timestamp: timestamp, + }, + events.UserEvent{ + User: user, + HiddenAt: hiddenAt, + }, + events.MessageEvent{ + Body: body, + }, }, nil } + +// Only keep recent messages so we don't keep more chat data than needed +// for privacy and efficiency reasons. +func runPruner() { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + log.Traceln("Removing chat messages older than", maxBacklogHours, "hours") + + deleteStatement := `DELETE FROM messages WHERE timestamp <= datetime('now', 'localtime', ?)` + tx, err := _datastore.DB.Begin() + if err != nil { + log.Debugln(err) + return + } + + stmt, err := tx.Prepare(deleteStatement) + if err != nil { + log.Debugln(err) + return + } + defer stmt.Close() + + if _, err = stmt.Exec(fmt.Sprintf("-%d hours", maxBacklogHours)); err != nil { + log.Debugln(err) + return + } + if err = tx.Commit(); err != nil { + log.Debugln(err) + return + } +} diff --git a/core/chat/server.go b/core/chat/server.go index 01dfada58..55388bd1a 100644 --- a/core/chat/server.go +++ b/core/chat/server.go @@ -1,191 +1,317 @@ package chat import ( - "fmt" + "encoding/json" "net/http" "sync" "time" log "github.com/sirupsen/logrus" - "golang.org/x/net/websocket" + "github.com/gorilla/websocket" + + "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/core/webhooks" - "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" ) -var ( - _server *server -) +var _server *ChatServer -var l = &sync.RWMutex{} +type ChatServer struct { + mu sync.RWMutex + seq uint + clients map[uint]*ChatClient + maxClientCount uint -// Server represents the server which handles the chat. -type server struct { - Clients map[string]*Client + // send outbound message payload to all clients + outbound chan []byte - pattern string - listener models.ChatListener + // receive inbound message payload from all clients + inbound chan chatClientEvent - addCh chan *Client - delCh chan *Client - sendAllCh chan models.ChatEvent - pingCh chan models.PingMessage - doneCh chan bool - errCh chan error + // unregister requests from clients. + unregister chan *ChatClient } -// Add adds a client to the server. -func (s *server) add(c *Client) { - s.addCh <- c -} - -// Remove removes a client from the server. -func (s *server) remove(c *Client) { - s.delCh <- c -} - -// SendToAll sends a message to all of the connected clients. -func (s *server) SendToAll(msg models.ChatEvent) { - s.sendAllCh <- msg -} - -// Err handles an error. -func (s *server) err(err error) { - s.errCh <- err -} - -func (s *server) sendAll(msg models.ChatEvent) { - l.RLock() - for _, c := range s.Clients { - c.write(msg) +func NewChat() *ChatServer { + server := &ChatServer{ + clients: map[uint]*ChatClient{}, + outbound: make(chan []byte), + inbound: make(chan chatClientEvent), + unregister: make(chan *ChatClient), + maxClientCount: handleMaxConnectionCount(), } - l.RUnlock() + + return server } -func (s *server) ping() { - ping := models.PingMessage{MessageType: models.PING} - - l.RLock() - for _, c := range s.Clients { - c.pingch <- ping - } - l.RUnlock() -} - -func (s *server) usernameChanged(msg models.NameChangeEvent) { - l.RLock() - for _, c := range s.Clients { - c.usernameChangeChannel <- msg - } - l.RUnlock() - - go webhooks.SendChatEventUsernameChanged(msg) -} - -func (s *server) userJoined(msg models.UserJoinedEvent) { - l.RLock() - if s.listener.IsStreamConnected() { - for _, c := range s.Clients { - c.userJoinedChannel <- msg - } - } - l.RUnlock() - - go webhooks.SendChatEventUserJoined(msg) -} - -func (s *server) onConnection(ws *websocket.Conn) { - client := NewClient(ws) - - defer func() { - s.removeClient(client) - - if err := ws.Close(); err != nil { - log.Debugln(err) - //s.errCh <- err - } - }() - - s.add(client) - client.listen() -} - -// Listen and serve. -// It serves client connection and broadcast request. -func (s *server) Listen() { - http.Handle(s.pattern, websocket.Handler(s.onConnection)) - - log.Tracef("Starting the websocket listener on: %s", s.pattern) - +func (s *ChatServer) Run() { for { select { - // add new a client - case c := <-s.addCh: - l.Lock() - s.Clients[c.socketID] = c - - if !c.Ignore { - s.listener.ClientAdded(c.GetViewerClientFromChatClient()) - s.sendWelcomeMessageToClient(c) - } - l.Unlock() - - // remove a client - case c := <-s.delCh: - s.removeClient(c) - case msg := <-s.sendAllCh: - if data.GetChatDisabled() { - break + case client := <-s.unregister: + if _, ok := s.clients[client.id]; ok { + s.mu.Lock() + delete(s.clients, client.id) + close(client.send) + s.mu.Unlock() } - if !msg.Empty() { - // set defaults before sending msg to anywhere - msg.SetDefaults() - - s.listener.MessageSent(msg) - s.sendAll(msg) - - // Store in the message history - if !msg.Ephemeral { - addMessage(msg) - } - - // Send webhooks - go webhooks.SendChatEvent(msg) - } - case ping := <-s.pingCh: - fmt.Println("PING?", ping) - - case err := <-s.errCh: - log.Trace("Error: ", err.Error()) - - case <-s.doneCh: - return + case message := <-s.inbound: + s.eventReceived(message) } } } -func (s *server) removeClient(c *Client) { - l.Lock() - if _, ok := s.Clients[c.socketID]; ok { - delete(s.Clients, c.socketID) - - s.listener.ClientRemoved(c.socketID) - log.Tracef("The client was connected for %s and sent %d messages (%s)", time.Since(c.ConnectedAt), c.MessageCount, c.ClientID) +// Addclient registers new connection as a User. +func (s *ChatServer) Addclient(conn *websocket.Conn, user *user.User, accessToken string, userAgent string) *ChatClient { + client := &ChatClient{ + server: s, + conn: conn, + User: user, + ipAddress: conn.RemoteAddr().String(), + accessToken: accessToken, + send: make(chan []byte, 256), + UserAgent: userAgent, + ConnectedAt: time.Now(), } - l.Unlock() + + s.mu.Lock() + { + client.id = s.seq + s.clients[client.id] = client + s.seq++ + } + s.mu.Unlock() + + log.Traceln("Adding client", client.id, "total count:", len(s.clients)) + + go client.writePump() + go client.readPump() + + client.sendConnectedClientInfo() + + if getStatus().Online { + s.sendUserJoinedMessage(client) + s.sendWelcomeMessageToClient(client) + } + + return client } -func (s *server) sendWelcomeMessageToClient(c *Client) { - go func() { - // Add an artificial delay so people notice this message come in. - time.Sleep(7 * time.Second) +func (s *ChatServer) sendUserJoinedMessage(c *ChatClient) { + userJoinedEvent := events.UserJoinedEvent{} + userJoinedEvent.SetDefaults() + userJoinedEvent.User = c.User - welcomeMessage := data.GetServerWelcomeMessage() - if welcomeMessage != "" { - initialMessage := models.ChatEvent{ClientID: "owncast-server", Author: data.GetServerName(), Body: welcomeMessage, ID: "initial-message-1", MessageType: "SYSTEM", Visible: true, Timestamp: time.Now()} - c.write(initialMessage) + if err := s.Broadcast(userJoinedEvent.GetBroadcastPayload()); err != nil { + log.Errorln("error adding client to chat server", err) + } + + // Send chat user joined webhook + webhooks.SendChatEventUserJoined(userJoinedEvent) +} + +func (s *ChatServer) ClientClosed(c *ChatClient) { + s.mu.Lock() + defer s.mu.Unlock() + c.close() + + if _, ok := s.clients[c.id]; ok { + log.Debugln("Deleting", c.id) + delete(s.clients, c.id) + } +} + +func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Request) { + if data.GetChatDisabled() { + _, _ = w.Write([]byte(events.ChatDisabled)) + return + } + + // Limit concurrent chat connections + if uint(len(s.clients)) >= s.maxClientCount { + log.Warnln("rejecting incoming client connection as it exceeds the max client count of", s.maxClientCount) + _, _ = w.Write([]byte(events.ErrorMaxConnectionsExceeded)) + return + } + + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Debugln(err) + return + } + + accessToken := r.URL.Query().Get("accessToken") + if accessToken == "" { + log.Errorln("Access token is required") + // Return HTTP status code + conn.Close() + return + } + + // A user is required to use the websocket + user := user.GetUserByToken(accessToken) + if user == nil { + _ = conn.WriteJSON(events.EventPayload{ + "type": events.ErrorNeedsRegistration, + }) + // Send error that registration is required + conn.Close() + return + } + + // User is disabled therefore we should disconnect. + if user.DisabledAt != nil { + log.Traceln("Disabled user", user.Id, user.DisplayName, "rejected") + _ = conn.WriteJSON(events.EventPayload{ + "type": events.ErrorUserDisabled, + }) + conn.Close() + return + } + + userAgent := r.UserAgent() + + s.Addclient(conn, user, accessToken, userAgent) +} + +// Broadcast sends message to all connected clients. +func (s *ChatServer) Broadcast(payload events.EventPayload) error { + data, err := json.Marshal(payload) + if err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + + for _, client := range s.clients { + if client == nil { + continue } - }() + + select { + case client.send <- data: + default: + close(client.send) + delete(s.clients, client.id) + } + } + + return nil +} + +func (s *ChatServer) Send(payload events.EventPayload, client *ChatClient) { + data, err := json.Marshal(payload) + if err != nil { + log.Errorln(err) + return + } + + client.send <- data +} + +// DisconnectUser will forcefully disconnect all clients belonging to a user by ID. +func (s *ChatServer) DisconnectUser(userID string) { + s.mu.Lock() + clients, err := GetClientsForUser(userID) + s.mu.Unlock() + + if err != nil || clients == nil || len(clients) == 0 { + log.Debugln("Requested to disconnect user", userID, err) + return + } + + for _, client := range clients { + log.Traceln("Disconnecting client", client.User.Id, "owned by", client.User.DisplayName) + + go func(client *ChatClient) { + event := events.UserDisabledEvent{} + event.SetDefaults() + + // Send this disabled event specifically to this single connected client + // to let them know they've been banned. + _server.Send(event.GetBroadcastPayload(), client) + + // Give the socket time to send out the above message. + // Unfortunately I don't know of any way to get a real callback to know when + // the message was successfully sent, so give it a couple seconds. + time.Sleep(2 * time.Second) + + // Forcefully disconnect if still valid. + if client != nil { + client.close() + } + }(client) + } +} + +func (s *ChatServer) eventReceived(event chatClientEvent) { + var typecheck map[string]interface{} + if err := json.Unmarshal(event.data, &typecheck); err != nil { + log.Debugln(err) + } + + eventType := typecheck["type"] + + switch eventType { + case events.MessageSent: + s.userMessageSent(event) + + case events.UserNameChanged: + s.userNameChanged(event) + + default: + log.Debugln(eventType, "event not found:", typecheck) + } +} + +func (s *ChatServer) sendWelcomeMessageToClient(c *ChatClient) { + // Add an artificial delay so people notice this message come in. + time.Sleep(7 * time.Second) + + welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage()) + + if welcomeMessage != "" { + s.sendSystemMessageToClient(c, welcomeMessage) + } +} + +func (s *ChatServer) sendAllWelcomeMessage() { + welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage()) + + if welcomeMessage != "" { + clientMessage := events.SystemMessageEvent{ + Event: events.Event{}, + MessageEvent: events.MessageEvent{ + Body: welcomeMessage, + }, + } + clientMessage.SetDefaults() + _ = s.Broadcast(clientMessage.GetBroadcastPayload()) + } +} + +func (s *ChatServer) sendSystemMessageToClient(c *ChatClient, message string) { + clientMessage := events.SystemMessageEvent{ + Event: events.Event{}, + MessageEvent: events.MessageEvent{ + Body: message, + }, + } + clientMessage.SetDefaults() + s.Send(clientMessage.GetBroadcastPayload(), c) +} + +func (s *ChatServer) sendActionToClient(c *ChatClient, message string) { + clientMessage := events.ActionEvent{ + MessageEvent: events.MessageEvent{ + Body: message, + }, + } + clientMessage.SetDefaults() + clientMessage.RenderBody() + s.Send(clientMessage.GetBroadcastPayload(), c) } diff --git a/core/chat/utils.go b/core/chat/utils.go new file mode 100644 index 000000000..1e69b93e4 --- /dev/null +++ b/core/chat/utils.go @@ -0,0 +1,29 @@ +package chat + +import ( + "syscall" + + log "github.com/sirupsen/logrus" +) + +// Set the soft file handler limit as 70% of +// the max as the client connection limit. +func handleMaxConnectionCount() uint { + var rLimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + panic(err) + } + + originalLimit := rLimit.Cur + // Set the limit to 70% of max so the machine doesn't die even if it's maxed out for some reason. + proposedLimit := int(float32(rLimit.Max) * 0.7) + + rLimit.Cur = uint64(proposedLimit) + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + panic(err) + } + + log.Traceln("Max process connection count increased from", originalLimit, "to", proposedLimit) + + return uint(float32(rLimit.Cur)) +} diff --git a/core/chatListener.go b/core/chatListener.go deleted file mode 100644 index b868b97dc..000000000 --- a/core/chatListener.go +++ /dev/null @@ -1,44 +0,0 @@ -package core - -import ( - "github.com/owncast/owncast/core/chat" - "github.com/owncast/owncast/models" -) - -// ChatListenerImpl the implementation of the chat client. -type ChatListenerImpl struct{} - -// ClientAdded is for when a client is added the system. -func (cl ChatListenerImpl) ClientAdded(client models.Client) { - SetChatClientActive(client) -} - -// ClientRemoved is for when a client disconnects/is removed. -func (cl ChatListenerImpl) ClientRemoved(clientID string) { - RemoveChatClient(clientID) -} - -// MessageSent is for when a message is sent. -func (cl ChatListenerImpl) MessageSent(message models.ChatEvent) { -} - -// IsStreamConnected will return if the stream is connected. -func (cl ChatListenerImpl) IsStreamConnected() bool { - return IsStreamConnected() -} - -// SendMessageToChat sends a message to the chat server. -func SendMessageToChat(message models.ChatEvent) error { - chat.SendMessage(message) - - return nil -} - -// GetAllChatMessages gets all of the chat messages. -func GetAllChatMessages() []models.ChatEvent { - return chat.GetMessages() -} - -func GetModerationChatMessages() []models.ChatEvent { - return chat.GetModerationChatMessages() -} diff --git a/core/core.go b/core/core.go index 2bd5672d1..a9818d3d7 100644 --- a/core/core.go +++ b/core/core.go @@ -12,6 +12,7 @@ import ( "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/rtmp" "github.com/owncast/owncast/core/transcoder" + "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/models" "github.com/owncast/owncast/utils" "github.com/owncast/owncast/yp" @@ -53,6 +54,8 @@ func Start() error { log.Errorln("storage error", err) } + user.SetupUsers() + fileWriter.SetupFileWriterReceiverService(&handler) if err := createInitialOfflineState(); err != nil { @@ -62,7 +65,9 @@ func Start() error { _yp = yp.NewYP(GetStatus) - chat.Setup(ChatListenerImpl{}) + if err := chat.Start(GetStatus); err != nil { + log.Errorln(err) + } // start the rtmp server go rtmp.Start(setStreamAsConnected, setBroadcaster) diff --git a/core/data/accessTokens.go b/core/data/accessTokens.go deleted file mode 100644 index 77a8bff56..000000000 --- a/core/data/accessTokens.go +++ /dev/null @@ -1,198 +0,0 @@ -package data - -import ( - "errors" - "strings" - "time" - - "github.com/owncast/owncast/models" - log "github.com/sirupsen/logrus" -) - -func createAccessTokensTable() { - log.Traceln("Creating access_tokens table...") - - createTableSQL := `CREATE TABLE IF NOT EXISTS access_tokens ( - "token" string NOT NULL PRIMARY KEY, - "name" string, - "scopes" TEXT, - "timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP, - "last_used" DATETIME - );` - - stmt, err := _db.Prepare(createTableSQL) - if err != nil { - log.Fatal(err) - } - defer stmt.Close() - if _, err := stmt.Exec(); err != nil { - log.Warnln(err) - } -} - -// InsertToken will add a new token to the database. -func InsertToken(token string, name string, scopes []string) error { - log.Println("Adding new access token:", name) - - scopesString := strings.Join(scopes, ",") - - tx, err := _db.Begin() - if err != nil { - return err - } - stmt, err := tx.Prepare("INSERT INTO access_tokens(token, name, scopes) values(?, ?, ?)") - - if err != nil { - return err - } - defer stmt.Close() - - if _, err := stmt.Exec(token, name, scopesString); err != nil { - return err - } - - if err = tx.Commit(); err != nil { - return err - } - - return nil -} - -// DeleteToken will delete a token from the database. -func DeleteToken(token string) error { - log.Println("Deleting access token:", token) - - tx, err := _db.Begin() - if err != nil { - return err - } - stmt, err := tx.Prepare("DELETE FROM access_tokens WHERE token = ?") - - if err != nil { - return err - } - defer stmt.Close() - - result, err := stmt.Exec(token) - if err != nil { - return err - } - - if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 { - tx.Rollback() //nolint - return errors.New(token + " not found") - } - - if err = tx.Commit(); err != nil { - return err - } - - return nil -} - -// DoesTokenSupportScope will determine if a specific token has access to perform a scoped action. -func DoesTokenSupportScope(token string, scope string) (bool, error) { - // This will split the scopes from comma separated to individual rows - // so we can efficiently find if a token supports a single scope. - // This is SQLite specific, so if we ever support other database - // backends we need to support other methods. - var query = `SELECT count(*) FROM ( - WITH RECURSIVE split(token, scope, rest) AS ( - SELECT token, '', scopes || ',' FROM access_tokens - UNION ALL - SELECT token, - substr(rest, 0, instr(rest, ',')), - substr(rest, instr(rest, ',')+1) - FROM split - WHERE rest <> '') - SELECT token, scope - FROM split - WHERE scope <> '' - ORDER BY token, scope - ) AS token WHERE token.token = ? AND token.scope = ?;` - - row := _db.QueryRow(query, token, scope) - - var count = 0 - err := row.Scan(&count) - - return count > 0, err -} - -// GetAccessTokens will return all access tokens. -func GetAccessTokens() ([]models.AccessToken, error) { //nolint - tokens := make([]models.AccessToken, 0) - - // Get all messages sent within the past day - var query = "SELECT * FROM access_tokens" - - rows, err := _db.Query(query) - if err != nil { - return tokens, err - } - defer rows.Close() - - for rows.Next() { - var token string - var name string - var scopes string - var timestampString string - var lastUsedString *string - - if err := rows.Scan(&token, &name, &scopes, ×tampString, &lastUsedString); err != nil { - log.Error("There is a problem reading the database.", err) - return tokens, err - } - - timestamp, err := time.Parse(time.RFC3339, timestampString) - if err != nil { - return tokens, err - } - - var lastUsed *time.Time = nil - if lastUsedString != nil { - lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString) - lastUsed = &lastUsedTime - } - - singleToken := models.AccessToken{ - Name: name, - Token: token, - Scopes: strings.Split(scopes, ","), - Timestamp: timestamp, - LastUsed: lastUsed, - } - - tokens = append(tokens, singleToken) - } - - if err := rows.Err(); err != nil { - return tokens, err - } - - return tokens, nil -} - -// SetAccessTokenAsUsed will update the last used timestamp for a token. -func SetAccessTokenAsUsed(token string) error { - tx, err := _db.Begin() - if err != nil { - return err - } - stmt, err := tx.Prepare("UPDATE access_tokens SET last_used = CURRENT_TIMESTAMP WHERE token = ?") - - if err != nil { - return err - } - defer stmt.Close() - - if _, err := stmt.Exec(token); err != nil { - return err - } - - if err = tx.Commit(); err != nil { - return err - } - - return nil -} diff --git a/core/data/config.go b/core/data/config.go index 12f538487..0fd01cccc 100644 --- a/core/data/config.go +++ b/core/data/config.go @@ -539,7 +539,9 @@ func VerifySettings() error { if err := utils.Copy(defaultLogo, filepath.Join(config.DataDirectory, "logo.svg")); err != nil { log.Errorln("error copying default logo: ", err) } - SetLogoPath("logo.svg") + if err := SetLogoPath("logo.svg"); err != nil { + log.Errorln("unable to set default logo to logo.svg", err) + } } return nil @@ -577,19 +579,25 @@ func FindHighestVideoQualityIndex(qualities []models.StreamOutputVariant) int { return indexedQualities[0].index } -// GetUsernameBlocklist will return the blocked usernames as a comma separated string. -func GetUsernameBlocklist() string { +// GetForbiddenUsernameList will return the blocked usernames as a comma separated string. +func GetForbiddenUsernameList() []string { usernameString, err := _datastore.GetString(blockedUsernamesKey) if err != nil { - log.Traceln(blockedUsernamesKey, err) - return "" + return config.DefaultForbiddenUsernames } - return usernameString + if usernameString == "" { + return config.DefaultForbiddenUsernames + } + + blocklist := strings.Split(usernameString, ",") + + return blocklist } -// SetUsernameBlocklist set the username blocklist as a comma separated string. -func SetUsernameBlocklist(usernames string) error { - return _datastore.SetString(blockedUsernamesKey, usernames) +// SetForbiddenUsernameList set the username blocklist as a comma separated string. +func SetForbiddenUsernameList(usernames []string) error { + usernameListString := strings.Join(usernames, ",") + return _datastore.SetString(blockedUsernamesKey, usernameListString) } diff --git a/core/data/data.go b/core/data/data.go index b2484c30c..1048c6d41 100644 --- a/core/data/data.go +++ b/core/data/data.go @@ -17,7 +17,7 @@ import ( ) const ( - schemaVersion = 0 + schemaVersion = 1 ) var _db *sql.DB @@ -45,7 +45,13 @@ func SetupPersistence(file string) error { } } - db, err := sql.Open("sqlite3", file) + db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", file)) + db.SetMaxOpenConns(1) + _db = db + + createWebhooksTable() + createUsersTable(db) + if err != nil { return err } @@ -86,11 +92,6 @@ func SetupPersistence(file string) error { } } - _db = db - - createWebhooksTable() - createAccessTokensTable() - _datastore = &Datastore{} _datastore.Setup() @@ -106,13 +107,14 @@ func SetupPersistence(file string) error { } func migrateDatabase(db *sql.DB, from, to int) error { - log.Printf("Migrating database from version %d to %d\n", from, to) + log.Printf("Migrating database from version %d to %d", from, to) dbBackupFile := filepath.Join(config.BackupDirectory, fmt.Sprintf("owncast-v%d.bak", from)) utils.Backup(db, dbBackupFile) for v := from; v < to; v++ { switch v { case 0: - log.Printf("Migration step from %d to %d\n", v, v+1) + log.Printf("Migration step from %d to %d", v, v+1) + migrateToSchema1(db) default: panic("missing database migration step") } diff --git a/core/data/data_test.go b/core/data/data_test.go index f5d277d0f..2210dd711 100644 --- a/core/data/data_test.go +++ b/core/data/data_test.go @@ -2,13 +2,18 @@ package data import ( "fmt" + "io/ioutil" + "os" "testing" ) func TestMain(m *testing.M) { - dbFile := "../../test/test.db" + dbFile, err := ioutil.TempFile(os.TempDir(), "owncast-test-db.db") + if err != nil { + panic(err) + } - SetupPersistence(dbFile) + SetupPersistence(dbFile.Name()) m.Run() } diff --git a/core/data/migrations.go b/core/data/migrations.go new file mode 100644 index 000000000..ddc12c51e --- /dev/null +++ b/core/data/migrations.go @@ -0,0 +1,118 @@ +package data + +import ( + "database/sql" + "time" + + "github.com/owncast/owncast/utils" + log "github.com/sirupsen/logrus" + "github.com/teris-io/shortid" +) + +func migrateToSchema1(db *sql.DB) { + // Since it's just a backlog of chat messages let's wipe the old messages + // and recreate the table. + + // Drop the old messages table + stmt, err := db.Prepare("DROP TABLE messages") + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + _, err = stmt.Exec() + if err != nil { + log.Warnln(err) + } + + // Recreate it + createUsersTable(db) + + // Migrate access tokens to become chat users + type oldAccessToken struct { + accessToken string + displayName string + scopes string + createdAt time.Time + lastUsedAt *time.Time + } + + oldAccessTokens := make([]oldAccessToken, 0) + + query := `SELECT * FROM access_tokens` + + rows, err := db.Query(query) + if err != nil || rows.Err() != nil { + log.Errorln("error migrating access tokens to schema v1", err, rows.Err()) + return + } + defer rows.Close() + + for rows.Next() { + var token string + var name string + var scopes string + var timestampString string + var lastUsedString *string + + if err := rows.Scan(&token, &name, &scopes, ×tampString, &lastUsedString); err != nil { + log.Error("There is a problem reading the database.", err) + return + } + + timestamp, err := time.Parse(time.RFC3339, timestampString) + if err != nil { + return + } + + var lastUsed *time.Time = nil + if lastUsedString != nil { + lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString) + lastUsed = &lastUsedTime + } + + oldToken := oldAccessToken{ + accessToken: token, + displayName: name, + scopes: scopes, + createdAt: timestamp, + lastUsedAt: lastUsed, + } + + oldAccessTokens = append(oldAccessTokens, oldToken) + } + + // Recreate them as users + for _, token := range oldAccessTokens { + color := utils.GenerateRandomDisplayColor() + if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil { + log.Errorln("Error migrating access token", err) + } + } +} + +func insertAPIToken(db *sql.DB, token string, name string, color int, scopes string) error { + log.Debugln("Adding new access token:", name) + + id := shortid.MustGenerate() + + tx, err := db.Begin() + if err != nil { + return err + } + stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type) values(?, ?, ?, ?, ?, ?)") + + if err != nil { + return err + } + defer stmt.Close() + + if _, err = stmt.Exec(id, token, name, color, scopes, "API"); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} diff --git a/core/data/persistence.go b/core/data/persistence.go index de9482141..deb677769 100644 --- a/core/data/persistence.go +++ b/core/data/persistence.go @@ -4,6 +4,7 @@ import ( "bytes" "database/sql" "encoding/gob" + "sync" // sqlite requires a blank import. _ "github.com/mattn/go-sqlite3" @@ -12,14 +13,15 @@ import ( // Datastore is the global key/value store for configuration values. type Datastore struct { - db *sql.DB - cache map[string][]byte + DB *sql.DB + cache map[string][]byte + DbLock *sync.Mutex } func (ds *Datastore) warmCache() { log.Traceln("Warming config value cache") - res, err := ds.db.Query("SELECT key, value FROM datastore") + res, err := ds.DB.Query("SELECT key, value FROM datastore") if err != nil || res.Err() != nil { log.Errorln("error warming config cache", err, res.Err()) } @@ -48,7 +50,7 @@ func (ds *Datastore) Get(key string) (ConfigEntry, error) { var resultKey string var resultValue []byte - row := ds.db.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key) + row := ds.DB.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key) if err := row.Scan(&resultKey, &resultValue); err != nil { return ConfigEntry{}, err } @@ -63,36 +65,26 @@ func (ds *Datastore) Get(key string) (ConfigEntry, error) { // Save will save the ConfigEntry to the database. func (ds *Datastore) Save(e ConfigEntry) error { + ds.DbLock.Lock() + defer ds.DbLock.Unlock() + var dataGob bytes.Buffer enc := gob.NewEncoder(&dataGob) if err := enc.Encode(e.Value); err != nil { return err } - tx, err := ds.db.Begin() + tx, err := ds.DB.Begin() if err != nil { return err } var stmt *sql.Stmt - var count int - row := ds.db.QueryRow("SELECT COUNT(*) FROM datastore WHERE key = ? LIMIT 1", e.Key) - if err := row.Scan(&count); err != nil { + stmt, err = tx.Prepare("INSERT INTO datastore (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value") + if err != nil { return err } + _, err = stmt.Exec(e.Key, dataGob.Bytes()) - if count == 0 { - stmt, err = tx.Prepare("INSERT INTO datastore(key, value) values(?, ?)") - if err != nil { - return err - } - _, err = stmt.Exec(e.Key, dataGob.Bytes()) - } else { - stmt, err = tx.Prepare("UPDATE datastore SET value=? WHERE key=?") - if err != nil { - return err - } - _, err = stmt.Exec(dataGob.Bytes(), e.Key) - } if err != nil { return err } @@ -110,7 +102,8 @@ func (ds *Datastore) Save(e ConfigEntry) error { // Setup will create the datastore table and perform initial initialization. func (ds *Datastore) Setup() { ds.cache = make(map[string][]byte) - ds.db = GetDatabase() + ds.DB = GetDatabase() + ds.DbLock = &sync.Mutex{} createTableSQL := `CREATE TABLE IF NOT EXISTS datastore ( "key" string NOT NULL PRIMARY KEY, @@ -118,7 +111,7 @@ func (ds *Datastore) Setup() { "timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL );` - stmt, err := ds.db.Prepare(createTableSQL) + stmt, err := ds.DB.Prepare(createTableSQL) if err != nil { log.Fatal(err) } @@ -137,7 +130,7 @@ func (ds *Datastore) Setup() { // Reset will delete all config entries in the datastore and start over. func (ds *Datastore) Reset() { sql := "DELETE FROM datastore" - stmt, err := ds.db.Prepare(sql) + stmt, err := ds.DB.Prepare(sql) if err != nil { log.Fatalln(err) } @@ -150,3 +143,7 @@ func (ds *Datastore) Reset() { PopulateDefaults() } + +func GetDatastore() *Datastore { + return _datastore +} diff --git a/core/data/users.go b/core/data/users.go new file mode 100644 index 000000000..3b1373cbb --- /dev/null +++ b/core/data/users.go @@ -0,0 +1,37 @@ +package data + +import ( + "database/sql" + + log "github.com/sirupsen/logrus" +) + +func createUsersTable(db *sql.DB) { + log.Traceln("Creating users table...") + + createTableSQL := `CREATE TABLE IF NOT EXISTS users ( + "id" TEXT, + "access_token" string NOT NULL, + "display_name" TEXT NOT NULL, + "display_color" NUMBER NOT NULL, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "disabled_at" TIMESTAMP, + "previous_names" TEXT DEFAULT '', + "namechanged_at" TIMESTAMP, + "scopes" TEXT, + "type" TEXT DEFAULT 'STANDARD', + "last_used" DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, access_token), + UNIQUE(id, access_token) + );CREATE INDEX index ON users (id, access_token)` + + stmt, err := db.Prepare(createTableSQL) + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + _, err = stmt.Exec() + if err != nil { + log.Warnln(err) + } +} diff --git a/core/data/webhooks.go b/core/data/webhooks.go index 8717401fb..073c30631 100644 --- a/core/data/webhooks.go +++ b/core/data/webhooks.go @@ -33,7 +33,7 @@ func createWebhooksTable() { // InsertWebhook will add a new webhook to the database. func InsertWebhook(url string, events []models.EventType) (int, error) { - log.Println("Adding new webhook:", url) + log.Traceln("Adding new webhook:", url) eventsString := strings.Join(events, ",") @@ -67,7 +67,7 @@ func InsertWebhook(url string, events []models.EventType) (int, error) { // DeleteWebhook will delete a webhook from the database. func DeleteWebhook(id int) error { - log.Println("Deleting webhook:", id) + log.Traceln("Deleting webhook:", id) tx, err := _db.Begin() if err != nil { @@ -86,7 +86,7 @@ func DeleteWebhook(id int) error { } if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 { - tx.Rollback() //nolint + _ = tx.Rollback() return errors.New(fmt.Sprint(id) + " not found") } diff --git a/core/stats.go b/core/stats.go index 3f716ce9e..c63beaf40 100644 --- a/core/stats.go +++ b/core/stats.go @@ -7,7 +7,6 @@ import ( log "github.com/sirupsen/logrus" - "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/geoip" "github.com/owncast/owncast/models" @@ -86,22 +85,6 @@ func RemoveChatClient(clientID string) { l.Unlock() } -func GetChatClients() []models.Client { - l.RLock() - clients := make([]models.Client, 0) - for _, client := range _stats.ChatClients { - chatClient := chat.GetClient(client.ClientID) - if chatClient != nil { - clients = append(clients, chatClient.GetViewerClientFromChatClient()) - } else { - clients = append(clients, client) - } - } - l.RUnlock() - - return clients -} - // SetViewerIdActive sets a client as active and connected. func SetViewerIdActive(id string) { l.Lock() diff --git a/core/streamState.go b/core/streamState.go index b95fb243c..556998d20 100644 --- a/core/streamState.go +++ b/core/streamState.go @@ -11,6 +11,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/rtmp" "github.com/owncast/owncast/core/transcoder" @@ -72,10 +73,15 @@ func setStreamAsConnected(rtmpOut *io.PipeReader) { go webhooks.SendStreamStatusEvent(models.StreamStarted) transcoder.StartThumbnailGenerator(segmentPath, data.FindHighestVideoQualityIndex(_currentBroadcast.OutputSettings)) + + _ = chat.SendSystemAction("Stay tuned, the stream is starting!", true) + chat.SendAllWelcomeMessage() } // SetStreamAsDisconnected sets the stream as disconnected. func SetStreamAsDisconnected() { + _ = chat.SendSystemAction("The stream is ending.", true) + _stats.StreamConnected = false _stats.LastDisconnectTime = utils.NullTime{Time: time.Now(), Valid: true} _broadcaster = nil diff --git a/core/user/externalAPIUser.go b/core/user/externalAPIUser.go new file mode 100644 index 000000000..e411ca5ec --- /dev/null +++ b/core/user/externalAPIUser.go @@ -0,0 +1,264 @@ +package user + +import ( + "database/sql" + "errors" + "strings" + "time" + + "github.com/owncast/owncast/utils" + log "github.com/sirupsen/logrus" + "github.com/teris-io/shortid" +) + +// ExternalAPIUser represents a single 3rd party integration that uses an access token. +// This struct mostly matches the User struct so they can be used interchangeably. +type ExternalAPIUser struct { + Id string `json:"id"` + AccessToken string `json:"accessToken"` + DisplayName string `json:"displayName"` + DisplayColor int `json:"displayColor"` + CreatedAt time.Time `json:"createdAt"` + Scopes []string `json:"scopes"` + Type string `json:"type,omitempty"` // Should be API + LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` +} + +const ( + // ScopeCanSendChatMessages will allow sending chat messages as itself. + ScopeCanSendChatMessages = "CAN_SEND_MESSAGES" + // ScopeCanSendSystemMessages will allow sending chat messages as the system. + ScopeCanSendSystemMessages = "CAN_SEND_SYSTEM_MESSAGES" + // ScopeHasAdminAccess will allow performing administrative actions on the server. + ScopeHasAdminAccess = "HAS_ADMIN_ACCESS" +) + +// For a scope to be seen as "valid" it must live in this slice. +var validAccessTokenScopes = []string{ + ScopeCanSendChatMessages, + ScopeCanSendSystemMessages, + ScopeHasAdminAccess, +} + +// InsertToken will add a new token to the database. +func InsertExternalAPIUser(token string, name string, color int, scopes []string) error { + log.Traceln("Adding new API user:", name) + + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + scopesString := strings.Join(scopes, ",") + id := shortid.MustGenerate() + + tx, err := _datastore.DB.Begin() + if err != nil { + return err + } + stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type, previous_names) values(?, ?, ?, ?, ?, ?, ?)") + + if err != nil { + return err + } + defer stmt.Close() + + if _, err = stmt.Exec(id, token, name, color, scopesString, "API", name); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +// DeleteExternalAPIUser will delete a token from the database. +func DeleteExternalAPIUser(token string) error { + log.Traceln("Deleting access token:", token) + + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() + if err != nil { + return err + } + stmt, err := tx.Prepare("UPDATE users SET disabled_at = ? WHERE access_token = ?") + + if err != nil { + return err + } + defer stmt.Close() + + result, err := stmt.Exec(time.Now(), token) + if err != nil { + return err + } + + if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 { + tx.Rollback() //nolint + return errors.New(token + " not found") + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +// GetExternalAPIUserForAccessTokenAndScope will determine if a specific token has access to perform a scoped action. +func GetExternalAPIUserForAccessTokenAndScope(token string, scope string) (*ExternalAPIUser, error) { + // This will split the scopes from comma separated to individual rows + // so we can efficiently find if a token supports a single scope. + // This is SQLite specific, so if we ever support other database + // backends we need to support other methods. + var query = `SELECT id, access_token, scopes, display_name, display_color, created_at, last_used FROM ( + WITH RECURSIVE split(id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, scope, rest) AS ( + SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, '', scopes || ',' FROM users + UNION ALL + SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, + substr(rest, 0, instr(rest, ',')), + substr(rest, instr(rest, ',')+1) + FROM split + WHERE rest <> '') + SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, scope + FROM split + WHERE scope <> '' + ORDER BY access_token, scope + ) AS token WHERE token.access_token = ? AND token.scope = ?` + + row := _datastore.DB.QueryRow(query, token, scope) + integration, err := makeExternalAPIUserFromRow(row) + + return integration, err +} + +// GetIntegrationNameForAccessToken will return the integration name associated with a specific access token. +func GetIntegrationNameForAccessToken(token string) *string { + query := "SELECT display_name FROM users WHERE access_token IS ? AND disabled_at IS NULL" + row := _datastore.DB.QueryRow(query, token) + + var name string + err := row.Scan(&name) + + if err != nil { + log.Warnln(err) + return nil + } + + return &name +} + +// GetExternalAPIUser will return all access tokens. +func GetExternalAPIUser() ([]ExternalAPIUser, error) { //nolint + // Get all messages sent within the past day + var query = "SELECT id, access_token, display_name, display_color, scopes, created_at, last_used FROM users WHERE type IS 'API' AND disabled_at IS NULL" + + rows, err := _datastore.DB.Query(query) + if err != nil { + return []ExternalAPIUser{}, err + } + defer rows.Close() + + integrations, err := makeExternalAPIUsersFromRows(rows) + + return integrations, err +} + +// SetExternalAPIUserAccessTokenAsUsed will update the last used timestamp for a token. +func SetExternalAPIUserAccessTokenAsUsed(token string) error { + tx, err := _datastore.DB.Begin() + if err != nil { + return err + } + stmt, err := tx.Prepare("UPDATE users SET last_used = CURRENT_TIMESTAMP WHERE access_token = ?") + + if err != nil { + return err + } + defer stmt.Close() + + if _, err := stmt.Exec(token); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +func makeExternalAPIUserFromRow(row *sql.Row) (*ExternalAPIUser, error) { + var id string + var accessToken string + var displayName string + var displayColor int + var scopes string + var createdAt time.Time + var lastUsedAt *time.Time + + err := row.Scan(&id, &accessToken, &scopes, &displayName, &displayColor, &createdAt, &lastUsedAt) + if err != nil { + log.Errorln(err) + return nil, err + } + + integration := ExternalAPIUser{ + Id: id, + AccessToken: accessToken, + DisplayName: displayName, + DisplayColor: displayColor, + CreatedAt: createdAt, + Scopes: strings.Split(scopes, ","), + LastUsedAt: lastUsedAt, + } + + return &integration, nil +} + +func makeExternalAPIUsersFromRows(rows *sql.Rows) ([]ExternalAPIUser, error) { + integrations := make([]ExternalAPIUser, 0) + + for rows.Next() { + var id string + var accessToken string + var displayName string + var displayColor int + var scopes string + var createdAt time.Time + var lastUsedAt *time.Time + + err := rows.Scan(&id, &accessToken, &displayName, &displayColor, &scopes, &createdAt, &lastUsedAt) + if err != nil { + log.Errorln(err) + return nil, err + } + + integration := ExternalAPIUser{ + Id: id, + AccessToken: accessToken, + DisplayName: displayName, + DisplayColor: displayColor, + CreatedAt: createdAt, + Scopes: strings.Split(scopes, ","), + LastUsedAt: lastUsedAt, + } + integrations = append(integrations, integration) + } + + return integrations, nil +} + +// HasValidScopes will verify that all the scopes provided are valid. +func HasValidScopes(scopes []string) bool { + for _, scope := range scopes { + _, foundInSlice := utils.FindInSlice(validAccessTokenScopes, scope) + if !foundInSlice { + log.Errorln("Invalid scope", scope) + return false + } + } + return true +} diff --git a/core/user/user.go b/core/user/user.go new file mode 100644 index 000000000..cb3e25707 --- /dev/null +++ b/core/user/user.go @@ -0,0 +1,261 @@ +package user + +import ( + "database/sql" + "fmt" + "sort" + "strings" + "time" + + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/utils" + "github.com/teris-io/shortid" + + log "github.com/sirupsen/logrus" +) + +var _datastore *data.Datastore + +type User struct { + Id string `json:"id"` + AccessToken string `json:"-"` + DisplayName string `json:"displayName"` + DisplayColor int `json:"displayColor"` + CreatedAt time.Time `json:"createdAt"` + DisabledAt *time.Time `json:"disabledAt,omitempty"` + PreviousNames []string `json:"previousNames"` + NameChangedAt *time.Time `json:"nameChangedAt,omitempty"` +} + +func (u *User) IsEnabled() bool { + return u.DisabledAt == nil +} + +func SetupUsers() { + _datastore = data.GetDatastore() +} + +func CreateAnonymousUser(username string) (*User, error) { + id := shortid.MustGenerate() + accessToken, err := utils.GenerateAccessToken() + if err != nil { + log.Errorln("Unable to create access token for new user") + return nil, err + } + + var displayName = username + if displayName == "" { + displayName = utils.GeneratePhrase() + } + + displayColor := utils.GenerateRandomDisplayColor() + + user := &User{ + Id: id, + AccessToken: accessToken, + DisplayName: displayName, + DisplayColor: displayColor, + CreatedAt: time.Now(), + } + + if err := create(user); err != nil { + return nil, err + } + + return user, nil +} + +func ChangeUsername(userId string, username string) { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() + + if err != nil { + log.Debugln(err) + } + defer func() { + if err := tx.Rollback(); err != nil { + log.Debugln(err) + } + }() + + stmt, err := tx.Prepare("UPDATE users SET display_name = ?, previous_names = previous_names || ?, namechanged_at = ? WHERE id = ?") + + if err != nil { + log.Debugln(err) + } + defer stmt.Close() + + _, err = stmt.Exec(username, fmt.Sprintf(",%s", username), time.Now(), userId) + if err != nil { + log.Errorln(err) + } + + if err := tx.Commit(); err != nil { + log.Errorln("error changing display name of user", userId, err) + } +} + +func create(user *User) error { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() + if err != nil { + log.Debugln(err) + } + defer func() { + _ = tx.Rollback() + }() + + stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, previous_names, created_at) values(?, ?, ?, ?, ?, ?)") + + if err != nil { + log.Debugln(err) + } + defer stmt.Close() + + _, err = stmt.Exec(user.Id, user.AccessToken, user.DisplayName, user.DisplayColor, user.DisplayName, user.CreatedAt) + if err != nil { + log.Errorln("error creating new user", err) + } + + return tx.Commit() +} + +func SetEnabled(userID string, enabled bool) error { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + tx, err := _datastore.DB.Begin() + if err != nil { + return err + } + + defer tx.Rollback() //nolint + + var stmt *sql.Stmt + if !enabled { + stmt, err = tx.Prepare("UPDATE users SET disabled_at=DATETIME('now', 'localtime') WHERE id IS ?") + } else { + stmt, err = tx.Prepare("UPDATE users SET disabled_at=null WHERE id IS ?") + } + + if err != nil { + return err + } + + defer stmt.Close() + + if _, err := stmt.Exec(userID); err != nil { + return err + } + + return tx.Commit() +} + +// GetUserByToken will return a user by an access token. +func GetUserByToken(token string) *User { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE access_token = ?" + row := _datastore.DB.QueryRow(query, token) + + return getUserFromRow(row) +} + +// GetUserById will return a user by a user ID. +func GetUserById(id string) *User { + _datastore.DbLock.Lock() + defer _datastore.DbLock.Unlock() + + query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE id = ?" + row := _datastore.DB.QueryRow(query, id) + if row == nil { + log.Errorln(row) + return nil + } + return getUserFromRow(row) +} + +// GetDisabledUsers will return back all the currently disabled users that are not API users. +func GetDisabledUsers() []*User { + query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE disabled_at IS NOT NULL AND type IS NOT 'API'" + + rows, err := _datastore.DB.Query(query) + if err != nil { + log.Errorln(err) + return nil + } + defer rows.Close() + + users := getUsersFromRows(rows) + + sort.Slice(users, func(i, j int) bool { + return users[i].DisabledAt.Before(*users[j].DisabledAt) + }) + + return users +} + +func getUsersFromRows(rows *sql.Rows) []*User { + users := make([]*User, 0) + + for rows.Next() { + var id string + var displayName string + var displayColor int + var createdAt time.Time + var disabledAt *time.Time + var previousUsernames string + var userNameChangedAt *time.Time + + if err := rows.Scan(&id, &displayName, &displayColor, &createdAt, &disabledAt, &previousUsernames, &userNameChangedAt); err != nil { + log.Errorln("error creating collection of users from results", err) + return nil + } + + user := &User{ + Id: id, + DisplayName: displayName, + DisplayColor: displayColor, + CreatedAt: createdAt, + DisabledAt: disabledAt, + PreviousNames: strings.Split(previousUsernames, ","), + NameChangedAt: userNameChangedAt, + } + users = append(users, user) + } + + sort.Slice(users, func(i, j int) bool { + return users[i].CreatedAt.Before(users[j].CreatedAt) + }) + + return users +} + +func getUserFromRow(row *sql.Row) *User { + var id string + var displayName string + var displayColor int + var createdAt time.Time + var disabledAt *time.Time + var previousUsernames string + var userNameChangedAt *time.Time + + if err := row.Scan(&id, &displayName, &displayColor, &createdAt, &disabledAt, &previousUsernames, &userNameChangedAt); err != nil { + return nil + } + + return &User{ + Id: id, + DisplayName: displayName, + DisplayColor: displayColor, + CreatedAt: createdAt, + DisabledAt: disabledAt, + PreviousNames: strings.Split(previousUsernames, ","), + NameChangedAt: userNameChangedAt, + } +} diff --git a/core/webhooks/chat.go b/core/webhooks/chat.go index b0c1bd6f8..464628e23 100644 --- a/core/webhooks/chat.go +++ b/core/webhooks/chat.go @@ -1,18 +1,19 @@ package webhooks import ( + "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/models" ) -func SendChatEvent(chatEvent models.ChatEvent) { +func SendChatEvent(chatEvent *events.UserMessageEvent) { webhookEvent := WebhookEvent{ - Type: chatEvent.MessageType, + Type: chatEvent.GetMessageType(), EventData: &WebhookChatMessage{ - Author: chatEvent.Author, + User: chatEvent.User, Body: chatEvent.Body, RawBody: chatEvent.RawBody, - ID: chatEvent.ID, - Visible: chatEvent.Visible, + ID: chatEvent.Id, + Visible: chatEvent.HiddenAt == nil, Timestamp: &chatEvent.Timestamp, }, } @@ -20,7 +21,7 @@ func SendChatEvent(chatEvent models.ChatEvent) { SendEventToWebhooks(webhookEvent) } -func SendChatEventUsernameChanged(event models.NameChangeEvent) { +func SendChatEventUsernameChanged(event events.NameChangeEvent) { webhookEvent := WebhookEvent{ Type: models.UserNameChanged, EventData: event, @@ -29,7 +30,7 @@ func SendChatEventUsernameChanged(event models.NameChangeEvent) { SendEventToWebhooks(webhookEvent) } -func SendChatEventUserJoined(event models.UserJoinedEvent) { +func SendChatEventUserJoined(event events.UserJoinedEvent) { webhookEvent := WebhookEvent{ Type: models.UserNameChanged, EventData: event, diff --git a/core/webhooks/webhooks.go b/core/webhooks/webhooks.go index 377e689ef..1c07fd6b8 100644 --- a/core/webhooks/webhooks.go +++ b/core/webhooks/webhooks.go @@ -8,6 +8,8 @@ import ( log "github.com/sirupsen/logrus" + "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/models" ) @@ -18,7 +20,7 @@ type WebhookEvent struct { } type WebhookChatMessage struct { - Author string `json:"author,omitempty"` + User *user.User `json:"user,omitempty"` Body string `json:"body,omitempty"` RawBody string `json:"rawBody,omitempty"` ID string `json:"id,omitempty"` diff --git a/doc/api/index.html b/doc/api/index.html index 7cfa99e93..a139d01b0 100644 --- a/doc/api/index.html +++ b/doc/api/index.html @@ -12,568 +12,159 @@ margin: 0; } - "},this.getStyleTags=function(){return e.sealed?Ko(2):e._emitSheetCSS()},this.getStyleElement=function(){var t;if(e.sealed)return Ko(2);var n=((t={})[Ho]="",t["data-styled-version"]="5.3.0",t.dangerouslySetInnerHTML={__html:e.instance.toString()},t),r=ci();return r&&(n.nonce=r),[s.createElement("style",Fo({},n,{key:"sc-0-0"}))]},this.seal=function(){e.sealed=!0},this.instance=new vi({isServer:!0}),this.sealed=!1}var t=e.prototype;return t.collectStyles=function(e){return this.sealed?Ko(2):s.createElement(Ni,{sheet:this.instance},e)},t.interleaveWithNodeStream=function(e){return Ko(3)},e}(),da=function(e){var t=s.forwardRef((function(t,n){var r=(0,s.useContext)(ta),o=e.defaultProps,i=Wi(t,r,o);return s.createElement(e,Fo({},t,{theme:i,ref:n}))}));return Do()(t,e),t.displayName="WithTheme("+Vo(e)+")",t},ha=function(){return(0,s.useContext)(ta)},ma={StyleSheet:vi,masterSheet:Ri},va=aa,ga=e.css,ya=e.createGlobalStyle,ba=e.keyframes,xa=e.ThemeProvider,wa=function(e,t,n){return function(){return ga(ua||(ua=Ao(["\n @media "," screen and (max-width: ",")"," {\n ",";\n }\n "])),t?"print, ":"",(function(t){return t.theme.breakpoints[e]}),n||"",ga.apply(void 0,arguments))}},ka=e.default;function Ea(e){return function(t){if(t.theme.extensionsHook)return t.theme.extensionsHook(e,t)}}var Sa,_a,Oa,Aa=ka.div(pa||(pa=Ao(["\n padding: 20px;\n color: red;\n"]))),Ia=function(e){Eo(n,e);var t=Oo(n);function n(e){var r;return nr(this,n),(r=t.call(this,e)).state={error:void 0},r}return or(n,[{key:"componentDidCatch",value:function(e){return this.setState({error:e}),!1}},{key:"render",value:function(){return this.state.error?s.createElement(Aa,null,s.createElement("h1",null,"Something went wrong..."),s.createElement("small",null," ",this.state.error.message," "),s.createElement("p",null,s.createElement("details",null,s.createElement("summary",null,"Stack trace"),s.createElement("pre",null,this.state.error.stack))),s.createElement("small",null," ReDoc Version: ","2.0.0-rc.55")," ",s.createElement("br",null),s.createElement("small",null," Commit: ","68690d18")):s.Children.only(this.props.children)}}]),n}(s.Component),Ca=ba(Sa||(Sa=Ao(["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"]))),Ta=ka((function(e){return s.createElement("svg",{className:e.className,version:"1.1",width:"512",height:"512",viewBox:"0 0 512 512"},s.createElement("path",{d:"M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z"}),s.createElement("path",{d:"M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z"}),s.createElement("path",{d:"M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z"}),s.createElement("path",{d:"M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z"}),s.createElement("path",{d:"M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z"}),s.createElement("path",{d:"M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z"}),s.createElement("path",{d:"M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z"}),s.createElement("path",{d:"M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z"}))}))(_a||(_a=Ao(["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"])),Ca,(function(e){return e.color})),Ra=ka.div(Oa||(Oa=Ao(["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"])),(function(e){return e.color})),Pa=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement("div",{style:{textAlign:"center"}},s.createElement(Ra,{color:this.props.color},"Loading ..."),s.createElement(Ta,{color:this.props.color}))}}]),n}(s.PureComponent),ja=r(5697),La=s.createContext(new ko({})),Na=La.Provider,Ma=La.Consumer;function Da(e,t,n,r,o,i,a){try{var s=e[i](a),l=s.value}catch(e){return void n(e)}s.done?t(l):Promise.resolve(l).then(r,o)}function Fa(e){return function(){var t=this,n=arguments;return new Promise((function(r,o){var i=e.apply(t,n);function a(e){Da(i,r,o,a,s,"next",e)}function s(e){Da(i,r,o,a,s,"throw",e)}a(void 0)}))}}var za=r(7757),Ua=r.n(za),Ba=r(2840),$a=r(7306),qa=r(6399);function Va(e){return Wa.apply(this,arguments)}function Wa(){return(Wa=Fa(Ua().mark((function e(t){var n,o,i,a;return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=new $a.De({}),o={config:n,base:Jr?window.location.href:process.cwd()},Jr&&(n.resolve.http.customFetch=r.g.fetch),"object"==typeof t&&null!==t?o.doc={source:{absoluteRef:""},parsed:t}:o.ref=t,e.next=6,(0,Ba.bundle)(o);case 6:return i=e.sent,a=i.bundle.parsed,e.abrupt("return",void 0!==a.swagger?Ha(a):a);case 9:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ha(e){return console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"),new Promise((function(t,n){return(0,qa.convertObj)(e,{patch:!0,warnOnly:!0,text:"{}",anchors:!0},(function(e,r){if(e)return n(e);t(r&&r.openapi)}))}))}function Ya(e,t,n,r,o){var i={};return Object.keys(r).forEach((function(e){i[e]=r[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),i),o&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(o):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}var Ga=r(1851),Qa=r(6729),Xa=r(3573),Ka=r.n(Xa),Za=Xa.parse,Ja=function(){function e(){nr(this,e)}return or(e,null,[{key:"baseName",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,r=e.parse(t);return r[r.length-n]}},{key:"dirName",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,r=e.parse(t);return Xa.compile(r.slice(0,r.length-n))}},{key:"relative",value:function(t,n){var r=e.parse(t);return e.parse(n).slice(r.length)}},{key:"parse",value:function(e){var t=e;return"#"===t.charAt(0)&&(t=t.substring(1)),Za(t)}},{key:"join",value:function(t,n){var r=e.parse(t).concat(n);return Xa.compile(r)}},{key:"get",value:function(e,t){return Xa.get(e,t)}},{key:"compile",value:function(e){return Xa.compile(e)}},{key:"escape",value:function(e){return Xa.escape(e)}}]),e}();function es(e){return function(e){if(Array.isArray(e))return to(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||no(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}Xa.parse=Ja.parse,Object.assign(Ja,Xa),r(6699),r(2023),r(4723);var ts=r(6470),ns=r(3578);function rs(e){return"string"==typeof e&&/\dxx/i.test(e)}function os(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if("default"===e)return t?"error":"success";var n="string"==typeof e?parseInt(e,10):e;if(rs(e)&&(n*=100),n<100||n>599)throw new Error("invalid HTTP code");var r="success";return n>=300&&n<400?r="redirect":n>=400?r="error":n<200&&(r="info"),r}var is={get:!0,post:!0,put:!0,head:!0,patch:!0,delete:!0,options:!0,$ref:!0};function as(e){return e in is}function ss(e){return e.summary||e.operationId||e.description&&e.description.substring(0,50)||e.pathName||""}var ls={multipleOf:"number",maximum:"number",exclusiveMaximum:"number",minimum:"number",exclusiveMinimum:"number",maxLength:"string",minLength:"string",pattern:"string",items:"array",maxItems:"array",minItems:"array",uniqueItems:"array",maxProperties:"object",minProperties:"object",required:"object",additionalProperties:"object",properties:"object"};function cs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e.type;if(void 0!==e.oneOf||void 0!==e.anyOf)return!1;var n=!0,r=Array.isArray(t);return("object"===t||r&&null!=t&&t.includes("object"))&&(n=void 0!==e.properties?0===Object.keys(e.properties).length:void 0===e.additionalProperties),void 0!==e.items&&("array"===t||r&&null!=t&&t.includes("array"))&&(n=cs(e.items,e.items.type)),n}function us(e){return-1!==e.search(/json/i)}function ps(e,t,n){return Array.isArray(e)?e.map((function(e){return e.toString()})).join(n):"object"==typeof e?Object.keys(e).map((function(t){return"".concat(t).concat(n).concat(e[t])})).join(n):t+"="+e.toString()}function fs(e,t){return Array.isArray(e)?(console.warn("deepObject style cannot be used with array value:"+e.toString()),""):"object"==typeof e?Object.keys(e).map((function(n){return"".concat(t,"[").concat(n,"]=").concat(e[n])})).join("&"):(console.warn("deepObject style cannot be used with non-object value:"+e.toString()),"")}function ds(e,t,r){var o="__redoc_param_name__",i=t?"*":"";return ns.parse("{?".concat(o).concat(i,"}")).expand(n({},o,r)).substring(1).replace(/__redoc_param_name__/g,e)}function hs(e,t){return us(t)?JSON.stringify(e):(console.warn("Parameter serialization as ".concat(t," is not supported")),"")}function ms(e){return/^#\/components\/(schemas|pathItems)\/[^\/]+$/.test(e||"")}function vs(e){if(e){var t=e.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/);return null===t?void 0:t[1]}}function gs(e,t,n){var r;return void 0!==t&&void 0!==n?r=t===n?"".concat(t," ").concat(e):"[ ".concat(t," .. ").concat(n," ] ").concat(e):void 0!==n?r="<= ".concat(n," ").concat(e):void 0!==t&&(r=1===t?"non-empty":">= ".concat(t," ").concat(e)),r}function ys(e){var t=[],n=gs("characters",e.minLength,e.maxLength);void 0!==n&&t.push(n);var r=gs("items",e.minItems,e.maxItems);void 0!==r&&t.push(r);var o,i=function(e){if(void 0!==e){var t=e.toString(10);return/^0\.0*1$/.test(t)?"decimal places <= ".concat(t.split(".")[1].length):"multiple of ".concat(t)}}(e.multipleOf);if(void 0!==i&&t.push(i),void 0!==e.minimum&&void 0!==e.maximum?(o=e.exclusiveMinimum?"( ":"[ ",o+=e.minimum,o+=" .. ",o+=e.maximum,o+=e.exclusiveMaximum?" )":" ]"):void 0!==e.maximum?(o=e.exclusiveMaximum?"< ":"<= ",o+=e.maximum):void 0!==e.minimum&&(o=e.exclusiveMinimum?"> ":">= ",o+=e.minimum),"number"==typeof e.exclusiveMinimum||"number"==typeof e.exclusiveMaximum){var a=0,s=0;e.minimum&&(a=e.minimum),"number"==typeof e.exclusiveMinimum&&(a=a<=e.exclusiveMinimum?a:e.exclusiveMinimum),e.maximum&&(s=e.maximum),"number"==typeof e.exclusiveMaximum&&(s=s>e.exclusiveMaximum?s:e.exclusiveMaximum),o="[".concat(a," .. ").concat(s,"]")}return void 0!==o&&t.push(o),e.uniqueItems&&t.push("unique"),t}function bs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=[],r=[],o=[];return e.forEach((function(e){e.required?t.includes(e.name)?r.push(e):o.push(e):n.push(e)})),r.sort((function(e,n){return t.indexOf(e.name)-t.indexOf(n.name)})),[].concat(r,o,n)}function xs(e,t){return es(e).sort((function(e,n){return e[t].localeCompare(n[t])}))}function ws(e,t){var n=void 0===e?function(e){try{var t=mo(e);return t.search="",t.toString()}catch(t){return e}}(function(){if(!Jr)return"";var e=window.location.href;return e.endsWith(".html")?(0,ts.dirname)(e):e}()):(0,ts.dirname)(e);return 0===t.length&&(t=[{url:"/"}]),t.map((function(e){return a(a({},e),{},{url:(t=e.url,function(e,t){var n;if(t.startsWith("//")){var r=(0,ao.parse)(e).protocol;n="".concat(r||"https:").concat(t)}else if(function(e){return/(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}(t))n=t;else if(t.startsWith("/")){var o=(0,ao.parse)(e);n=(0,ao.format)(a(a({},o),{},{pathname:t}))}else n=co(e)+"/"+t;return co(n)}(n,t)),description:e.description||""});var t}))}var ks="security-definitions",Es="SecurityDefinitions",Ss="section/Authentication/";function _s(e){Ss=e}var Os=function(e){return{delete:"del",options:"opts"}[e]||e};function As(e,t){return Object.keys(e).filter((function(e){return!0===t?e.startsWith("x-")&&!function(e){return e in{"x-circular-ref":!0,"x-code-samples":!0,"x-codeSamples":!0,"x-displayName":!0,"x-examples":!0,"x-ignoredHeaderParameters":!0,"x-logo":!0,"x-nullable":!0,"x-servers":!0,"x-tagGroups":!0,"x-traitTag":!0,"x-additionalPropertiesName":!0,"x-explicitMappingOnly":!0}}(e):e.startsWith("x-")&&t.indexOf(e)>-1})).reduce((function(t,n){return t[n]=e[n],t}),{})}var Is=r(5660),Cs=(r(7874),r(4279),r(5433),r(6213),r(2731),r(9016),r(7046),r(57),r(2503),r(6841),r(6854),r(4335),r(1426),r(8246),r(9945),r(366),r(9385),r(2886),r(5266),r(874),"clike");function Ts(e){return{json:"js","c++":"cpp","c#":"csharp","objective-c":"objectivec",shell:"bash",viml:"vim"}[e]||Cs}function Rs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Cs;t=t.toLowerCase();var n=Is.languages[t];return n||(n=Is.languages[Ts(t)]),Is.highlight(e.toString(),n,t)}Is.languages.insertBefore("javascript","string",{"property string":{pattern:/([{,]\s*)"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,lookbehind:!0}},void 0),Is.languages.insertBefore("javascript","punctuation",{property:{pattern:/([{,]\s*)[a-z]\w*(?=\s*:)/i,lookbehind:!0}},void 0);var Ps,js={};function Ls(e,t,n){if("function"==typeof n.value)return function(e,t,n){if(!n.value||n.value.length>0)throw new Error("@memoize decorator can only be applied to methods of zero arguments");var r="_memoized_".concat(t),o=n.value;return e[r]=js,a(a({},n),{},{value:function(){return this[r]===js&&(this[r]=o.call(this)),this[r]}})}(e,t,n);if("function"==typeof n.get)return function(e,t,n){var r="_memoized_".concat(t),o=n.get;return e[r]=js,a(a({},n),{},{get:function(){return this[r]===js&&(this[r]=o.call(this)),this[r]}})}(e,t,n);throw new Error("@memoize decorator can be applied to methods or getters, got "+String(n.value)+" instead")}var Ns="hashchange",Ms=new(Ya((Ps=function(){function e(){var t=this;nr(this,e),n(this,"_emiter",void 0),n(this,"emit",(function(){t._emiter.emit(Ns,t.currentId)})),this._emiter=new Qa.EventEmitter,this.bind()}return or(e,[{key:"currentId",get:function(){return Jr?decodeURIComponent(window.location.hash.substring(1)):""}},{key:"linkForId",value:function(e){return e?"#"+e:""}},{key:"subscribe",value:function(e){var t=this._emiter.addListener(Ns,e);return function(){return t.removeListener(Ns,e)}}},{key:"bind",value:function(){Jr&&window.addEventListener("hashchange",this.emit,!1)}},{key:"dispose",value:function(){Jr&&window.removeEventListener("hashchange",this.emit)}},{key:"replace",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];Jr&&null!=e&&e!==this.currentId&&(t?window.history.replaceState(null,"",window.location.href.split("#")[0]+this.linkForId(e)):(window.history.pushState(null,"",window.location.href.split("#")[0]+this.linkForId(e)),this.emit()))}}]),e}()).prototype,"replace",[Ga.bind,Ga.debounce],Object.getOwnPropertyDescriptor(Ps.prototype,"replace"),Ps.prototype),Ps),Ds=r(813),Fs=function(){function e(){nr(this,e),n(this,"map",new Map),n(this,"prevTerm","")}return or(e,[{key:"add",value:function(e){this.map.set(e,new Ds(e))}},{key:"delete",value:function(e){this.map.delete(e)}},{key:"addOnly",value:function(e){var t=this;this.map.forEach((function(n,r){-1===e.indexOf(r)&&(n.unmark(),t.map.delete(r))}));var n,r=ro(e);try{for(r.s();!(n=r.n()).done;){var o=n.value;this.map.has(o)||this.map.set(o,new Ds(o))}}catch(e){r.e(e)}finally{r.f()}}},{key:"clearAll",value:function(){this.unmark(),this.map.clear()}},{key:"mark",value:function(e){var t=this;(e||this.prevTerm)&&(this.map.forEach((function(n){n.unmark(),n.mark(e||t.prevTerm)})),this.prevTerm=e||this.prevTerm)}},{key:"unmark",value:function(){this.map.forEach((function(e){return e.unmark()})),this.prevTerm=""}}]),e}();function zs(e,t,n,r){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})}r(9826);var Us=r(7084),Bs=new Us.Renderer;Us.setOptions({renderer:Bs,highlight:function(e,t){return Rs(e,t)}});var $s="(?:^ {0,3}\x3c!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?--\x3e\\s*$|(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,})))",qs=function(){function e(t){var r=this;nr(this,e),this.options=t,n(this,"headings",[]),n(this,"currentTopHeading",void 0),n(this,"headingEnhanceRenderer",void 0),n(this,"originalHeadingRule",void 0),n(this,"headingRule",(function(e,t,n,o){return 1===t?r.currentTopHeading=r.saveHeading(e,t):2===t&&r.saveHeading(e,t,r.currentTopHeading&&r.currentTopHeading.items,r.currentTopHeading&&r.currentTopHeading.id),r.originalHeadingRule(e,t,n,o)})),this.headingEnhanceRenderer=new Us.Renderer,this.originalHeadingRule=this.headingEnhanceRenderer.heading.bind(this.headingEnhanceRenderer),this.headingEnhanceRenderer.heading=this.headingRule}return or(e,[{key:"saveHeading",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.headings,r=arguments.length>3?arguments[3]:void 0;e=vo(e);var o={id:r?"".concat(r,"/").concat(ho(e)):"section/".concat(ho(e)),name:e,level:t,items:[]};return n.push(o),o}},{key:"flattenHeadings",value:function(e){if(void 0===e)return[];var t,n=[],r=ro(e);try{for(r.s();!(t=r.n()).done;){var o=t.value;n.push(o),n.push.apply(n,es(this.flattenHeadings(o.items)))}}catch(e){r.e(e)}finally{r.f()}return n}},{key:"attachHeadingsDescriptions",value:function(e){var t=function(e){return new RegExp("##?\\s+".concat(e.name.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")))},n=this.flattenHeadings(this.headings);if(!(n.length<1)){for(var r=n[0],o=t(r),i=e.search(o),a=1;a1&&void 0!==arguments[1]&&arguments[1],n=t?{renderer:this.headingEnhanceRenderer}:void 0,r=Us(e.toString(),n);return r}},{key:"extractHeadings",value:function(e){this.renderMd(e,!0),this.attachHeadingsDescriptions(e);var t=this.headings;return this.headings=[],t}},{key:"renderMdWithComponents",value:function(e){var t=this.options&&this.options.allowedMdComponents;if(!t||0===Object.keys(t).length)return[this.renderMd(e)];for(var n=Object.keys(t).join("|"),r=new RegExp($s.replace(/{component}/g,n),"mig"),o=[],i=[],s=r.exec(e),l=0;s;){o.push(e.substring(l,s.index)),l=r.lastIndex;var c=t[s[1]||s[2]||s[5]],u=s[3]||s[6],p=s[4];c&&i.push({component:c.component,propsSelector:c.propsSelector,props:a(a(a({},Vs(u)),c.props),{},{children:p})}),s=r.exec(e)}o.push(e.substring(l));for(var f=[],d=0;d-1?e.substring(0,n):e}}]),e}();function Vs(e){if(!e)return{};for(var t,n=/([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim,r={};null!==(t=n.exec(e));)if(t[3])r[t[1]]=t[3];else if(t[2]){var o=void 0;try{o=JSON.parse(t[2])}catch(e){}r[t[1]]=o}return r}var Ws,Hs,Ys,Gs,Qs=function(){function e(t){nr(this,e),this.parser=t,n(this,"title",void 0),n(this,"version",void 0),n(this,"description",void 0),n(this,"summary",void 0),n(this,"termsOfService",void 0),n(this,"contact",void 0),n(this,"license",void 0),n(this,"downloadLink",void 0),n(this,"downloadFileName",void 0),Object.assign(this,t.spec.info),this.description=t.spec.info.description||"",this.summary=t.spec.info.summary||"";var r=this.description.search(/^##?\s+/m);r>-1&&(this.description=this.description.substring(0,r)),this.downloadLink=this.getDownloadLink(),this.downloadFileName=this.getDownloadFileName()}return or(e,[{key:"getDownloadLink",value:function(){if(this.parser.specUrl)return this.parser.specUrl;if(Jr&&window.Blob&&window.URL&&window.URL.createObjectURL){var e=new Blob([JSON.stringify(this.parser.spec,null,2)],{type:"application/json"});return window.URL.createObjectURL(e)}}},{key:"getDownloadFileName",value:function(){if(!this.parser.specUrl)return"swagger.json"}}]),e}(),Xs=function e(t,r){nr(this,e),n(this,"schemes",void 0);var o=r.spec.components&&r.spec.components.securitySchemes||{};this.schemes=Object.keys(t||{}).map((function(e){var n=r.deref(o[e]),i=t[e]||[];if(n)return a(a({},n),{},{id:e,sectionId:Ss+e,scopes:i});console.warn("Non existing security scheme referenced: ".concat(e,". Skipping"))})).filter((function(e){return void 0!==e}))},Ks=(Hs=Ya((Ws=function(){function e(t,r,o,i,s){nr(this,e),zs(this,"expanded",Hs,this),n(this,"name",void 0),n(this,"operations",[]),an(this),this.name=r;var l=t.deref(o);t.exitRef(o);for(var c=0,u=Object.keys(l);c4&&void 0!==arguments[4]&&arguments[4];nr(this,e),this.options=i,n(this,"pointer",void 0),n(this,"type",void 0),n(this,"displayType",void 0),n(this,"typePrefix",""),n(this,"title",void 0),n(this,"description",void 0),n(this,"externalDocs",void 0),n(this,"isPrimitive",void 0),n(this,"isCircular",!1),n(this,"format",void 0),n(this,"displayFormat",void 0),n(this,"nullable",void 0),n(this,"deprecated",void 0),n(this,"pattern",void 0),n(this,"example",void 0),n(this,"enum",void 0),n(this,"default",void 0),n(this,"readOnly",void 0),n(this,"writeOnly",void 0),n(this,"constraints",void 0),n(this,"fields",void 0),n(this,"items",void 0),n(this,"oneOf",void 0),n(this,"oneOfType",void 0),n(this,"discriminatorProp",void 0),zs(this,"activeOneOf",Gs,this),n(this,"rawSchema",void 0),n(this,"schema",void 0),n(this,"extensions",void 0),n(this,"const",void 0),an(this),this.pointer=r.$ref||o||"",this.rawSchema=t.deref(r,!1,!0),this.schema=t.mergeAllOf(this.rawSchema,this.pointer,a),this.init(t,a),t.exitRef(r),t.exitParents(this.schema),i.showExtensions&&(this.extensions=As(this.schema,i.showExtensions))}return or(e,[{key:"activateOneOf",value:function(e){this.activeOneOf=e}},{key:"init",value:function(t,n){var r=this,o=this.schema;if(this.isCircular=o["x-circular-ref"],this.title=o.title||ms(this.pointer)&&Ja.baseName(this.pointer)||"",this.description=o.description||"",this.type=o.type||function(e){if(void 0!==e.type&&!Array.isArray(e.type))return e.type;for(var t=0,n=Object.keys(ls);t-1;return new il(e,{name:i,required:c,schema:a(a({},l),{},{default:void 0===l.default?s[i]:l.default})},n+"/properties/"+i,r)}));return r.sortPropsAlphabetically&&(l=xs(l,"name")),r.requiredPropsFirst&&(l=bs(l,r.sortPropsAlphabetically?void 0:t.required)),"object"!=typeof i&&!0!==i||l.push(new il(e,{name:("object"==typeof i&&i["x-additionalPropertiesName"]||"property name").concat("*"),required:!1,schema:!0===i?{}:i,kind:"additionalProperties"},n+"/additionalProperties",r)),l}(t,o,this.pointer,this.options);else if(("array"===this.type||Array.isArray(this.type))&&o.items&&(this.items=new e(t,o.items,this.pointer+"/items",this.options),this.displayType=this.items.displayType.split(" or ").map((function(e){return e.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/,"$1s$2")})).join(" or "),this.displayFormat=this.items.format,this.typePrefix=this.items.typePrefix+bo("arrayOf"),this.title=this.title||this.items.title,this.isPrimitive=this.items.isPrimitive,void 0===this.example&&void 0!==this.items.example&&(this.example=[this.items.example]),this.items.isPrimitive&&(this.enum=this.items.enum),Array.isArray(this.type))){var i=this.type.filter((function(e){return"array"!==e}));i.length&&(this.displayType+=" or ".concat(i.join(" or ")))}this.enum.length&&this.options.sortEnumValuesAlphabetically&&this.enum.sort()}else this.initDiscriminator(o,t)}},{key:"initOneOf",value:function(t,n){var r,o,i=this;if(this.oneOf=t.map((function(t,r){var o=n.deref(t,!1,!0),s=n.mergeAllOf(o,i.pointer+"/oneOf/"+r),l=ms(t.$ref)&&!s.title?Ja.baseName(t.$ref):"".concat(s.title||"").concat(s.const&&JSON.stringify(s.const)||""),c=new e(n,a(a({},s),{},{title:l,allOf:[a(a({},i.schema),{},{oneOf:void 0,anyOf:void 0})]}),i.pointer+"/oneOf/"+r,i.options);return n.exitRef(t),n.exitParents(s),c})),this.options.simpleOneOfTypeLabel){var s=(r=this,o=new Set,function e(t){var n,r=ro(t.oneOf||[]);try{for(r.s();!(n=r.n()).done;){var i=n.value;i.oneOf?e(i):i.type&&o.add(i.type)}}catch(e){r.e(e)}finally{r.f()}}(r),Array.from(o.values()));this.displayType=s.join(" or ")}else this.displayType=this.oneOf.map((function(e){var t=e.typePrefix+(e.title?"".concat(e.title," (").concat(e.displayType,")"):e.displayType);return t.indexOf(" or ")>-1&&(t="(".concat(t,")")),t})).join(" or ")}},{key:"initDiscriminator",value:function(t,n){var r=this,o=Js(t);this.discriminatorProp=o.propertyName;var i=n.findDerived([].concat(es(t.parentRefs||[]),[this.pointer]));if(t.oneOf){var s,l=ro(t.oneOf);try{for(l.s();!(s=l.n()).done;){var c=s.value;if(void 0!==c.$ref){var u=Ja.baseName(c.$ref);i[c.$ref]=u}}}catch(e){l.e(e)}finally{l.f()}}var p=o.mapping||{},f=o["x-explicitMappingOnly"]||!1;0===Object.keys(p).length&&(f=!1);var d={};for(var h in p){var m=p[h];Array.isArray(d[m])?d[m].push(h):d[m]=[h]}for(var v=a(f?{}:a({},i),d),g=[],y=0,b=Object.keys(v);y1&&void 0!==arguments[1]?arguments[1]:{};if(Array.isArray(e))throw new Error("Payload must have fields: "+e.toString());return Object.keys(e).map((function(n){var r=e[n],o=t[n]||{},i=o.style,a=void 0===i?"form":i,s=o.explode,l=void 0===s||s;switch(a){case"form":return ds(n,l,r);case"spaceDelimited":return ps(r,n,"%20");case"pipeDelimited":return ps(r,n,"|");case"deepObject":return fs(r,n);default:return console.warn("Incorrect or unsupported encoding style: "+a),""}})).join("&")}(this.value,i))}return or(e,[{key:"getExternalValue",value:function(e){return this.externalValueUrl?(nl[this.externalValueUrl]||(nl[this.externalValueUrl]=fetch(this.externalValueUrl).then((function(t){return t.text().then((function(n){if(!t.ok)return Promise.reject(new Error(n));if(!us(e))return n;try{return JSON.parse(n)}catch(e){return n}}))}))),nl[this.externalValueUrl]):Promise.resolve(void 0)}}]),e}(),ol={path:{style:"simple",explode:!1},query:{style:"form",explode:!0},header:{style:"simple",explode:!1},cookie:{style:"form",explode:!0}},il=(tl=Ya((el=function(){function e(t,r,o,i){var a;nr(this,e),zs(this,"expanded",tl,this),n(this,"schema",void 0),n(this,"name",void 0),n(this,"required",void 0),n(this,"description",void 0),n(this,"example",void 0),n(this,"examples",void 0),n(this,"deprecated",void 0),n(this,"in",void 0),n(this,"kind",void 0),n(this,"extensions",void 0),n(this,"explode",void 0),n(this,"style",void 0),n(this,"const",void 0),n(this,"serializationMime",void 0),an(this);var s=t.deref(r);this.kind=r.kind||"field",this.name=r.name||s.name,this.in=s.in,this.required=!!s.required;var l,c,u=s.schema,p="";if(!u&&s.in&&s.content&&(p=Object.keys(s.content)[0],u=s.content[p]&&s.content[p].schema),this.schema=new Zs(t,u||{},o,i),this.description=void 0===s.description?this.schema.description||"":s.description,this.example=s.example||this.schema.example,void 0!==s.examples&&(this.examples=lo(s.examples,(function(e,n){return new rl(t,e,n,s.encoding)}))),p)this.serializationMime=p;else if(s.style)this.style=s.style;else if(this.in){var f,d;this.style=null!==(f=null===(d=ol[this.in])||void 0===d?void 0:d.style)&&void 0!==f?f:"form"}void 0===s.explode&&this.in?this.explode=null===(l=null===(c=ol[this.in])||void 0===c?void 0:c.explode)||void 0===l||l:this.explode=!!s.explode,this.deprecated=void 0===s.deprecated?!!this.schema.deprecated:s.deprecated,t.exitRef(r),i.showExtensions&&(this.extensions=As(s,i.showExtensions)),this.const=(null===(a=this.schema)||void 0===a?void 0:a.const)||(null==s?void 0:s.const)||""}return or(e,[{key:"toggle",value:function(){this.expanded=!this.expanded}}]),e}()).prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(el.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(el.prototype,"toggle"),el.prototype),el);function al(e){return e<10?"0"+e:e}function sl(e,t){return t>e.length?e.repeat(Math.trunc(t/e.length)+1).substring(0,t):e}function ll(){for(var e=function(e){return e&&"object"==typeof e},t=arguments.length,n=new Array(t),r=0;rt.maxSampleDepth)return ul(hl,r),cl(fl(e));if(e.$ref){if(!n)throw new Error("Your schema contains $ref. You must provide full specification in the third parameter.");var o=decodeURIComponent(e.$ref);o.startsWith("#")&&(o=o.substring(1));var i,s=Ka().get(n,o);return!0!==dl[o]?(dl[o]=!0,i=ml(s,t,n,r),dl[o]=!1):i=cl(fl(s)),ul(hl,r),i}if(void 0!==e.example)return ul(hl,r),{value:e.example,readOnly:e.readOnly,writeOnly:e.writeOnly,type:e.type};if(void 0!==e.allOf)return ul(hl,r),function(e,t,n,r,o){var i,s=ml(e,n,r),l=[],c=ro(t);try{for(c.s();!(i=c.n()).done;){var u=i.value,p=ml(a({type:s.type},u),n,r,o),f=p.type,d=p.readOnly,h=p.writeOnly,m=p.value;s.type&&f&&f!==s.type&&(console.warn("allOf: schemas with different types can't be merged"),s.type=f),s.type=s.type||f,s.readOnly=s.readOnly||d,s.writeOnly=s.writeOnly||h,null!=m&&l.push(m)}}catch(e){c.e(e)}finally{c.f()}if("object"===s.type)return s.value=ll.apply(void 0,[s.value||{}].concat(es(l.filter((function(e){return"object"==typeof e}))))),s;"array"===s.type&&(n.quiet||console.warn('OpenAPI Sampler: found allOf with "array" type. Result may be incorrect'));var v=l[l.length-1];return s.value=null!=v?v:s.value,s}(a(a({},e),{},{allOf:void 0}),e.allOf,t,n,r);if(e.oneOf&&e.oneOf.length)return e.anyOf&&(t.quiet||console.warn("oneOf and anyOf are not supported on the same level. Skipping anyOf")),ul(hl,r),ml(e.oneOf[0],t,n,r);if(e.anyOf&&e.anyOf.length)return ul(hl,r),ml(e.anyOf[0],t,n,r);if(e.if&&e.then)return ml(ll(e.if,e.then),t,n,r);var l=null,c=null;if(void 0!==e.default)l=e.default;else if(void 0!==e.const)l=e.const;else if(void 0!==e.enum&&e.enum.length)l=e.enum[0];else if(void 0!==e.examples&&e.examples.length)l=e.examples[0];else{c=e.type,Array.isArray(c)&&e.type.length>0&&(c=e.type[0]),c||(c=fl(e));var u=xl[c];u&&(l=u(e,t,n,r))}return ul(hl,r),{value:l,readOnly:e.readOnly,writeOnly:e.writeOnly,type:c}}function vl(e){var t=0;if("boolean"==typeof e.exclusiveMinimum||"boolean"==typeof e.exclusiveMaximum){if(e.maximum&&e.minimum)return t=e.exclusiveMinimum?Math.floor(e.minimum)+1:e.minimum,(e.exclusiveMaximum&&t>=e.maximum||!e.exclusiveMaximum&&t>e.maximum)&&(t=(e.maximum+e.minimum)/2),t;if(e.minimum)return e.exclusiveMinimum?Math.floor(e.minimum)+1:e.minimum;if(e.maximum)return e.exclusiveMaximum?e.maximum>0?0:Math.floor(e.maximum)-1:e.maximum>0?0:e.maximum}else{if(e.minimum)return e.minimum;e.exclusiveMinimum?(t=Math.floor(e.exclusiveMinimum)+1)===e.exclusiveMaximum&&(t=(t+Math.floor(e.exclusiveMaximum)-1)/2):e.exclusiveMaximum?t=Math.floor(e.exclusiveMaximum)-1:e.maximum&&(t=e.maximum)}return t}function gl(e){var t=e.min,n=e.max,r=e.omitTime,o=e.omitDate,i=function(e,t,n,r){var o=n?"":e.getUTCFullYear()+"-"+al(e.getUTCMonth()+1)+"-"+al(e.getUTCDate());return t||(o+="T"+al(e.getUTCHours())+":"+al(e.getUTCMinutes())+":"+al(e.getUTCSeconds())+"Z"),o}(new Date("2019-08-24T14:15:22.123Z"),r,o);return i.lengthn&&console.warn("Using maxLength = ".concat(n,' is incorrect with format "date-time"')),i}function yl(e,t){var n=sl("string",e);return t&&n.length>t&&(n=n.substring(0,t)),n}var bl={email:function(){return"user@example.com"},"idn-email":function(){return"пошта@укр.нет"},password:function(e,t){var n="pa$$word";return e>n.length&&(n+="_",n+=sl("qwerty!@#$%^123456",e-n.length).substring(0,e-n.length)),n},"date-time":function(e,t){return gl({min:e,max:t,omitTime:!1,omitDate:!1})},date:function(e,t){return gl({min:e,max:t,omitTime:!0,omitDate:!1})},time:function(e,t){return gl({min:e,max:t,omitTime:!1,omitDate:!0}).slice(1)},ipv4:function(){return"192.168.0.1"},ipv6:function(){return"2001:0db8:85a3:0000:0000:8a2e:0370:7334"},hostname:function(){return"example.com"},"idn-hostname":function(){return"приклад.укр"},iri:function(){return"http://example.com"},"iri-reference":function(){return"../словник"},uri:function(){return"http://example.com"},"uri-reference":function(){return"../dictionary"},"uri-template":function(){return"http://example.com/{endpoint}"},uuid:function(e,t,n){return r=function(e){var t=0;if(0==e.length)return t;for(var n=0;n>>5)|0;return e=t^((n|=0)<<17|n>>>15),t=n+(r|=0)|0,n=r+o|0,((r=e+o|0)>>>0)/4294967296}}(r,r,r,r),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){var t=16*o()%16|0;return("x"==e?t:3&t|8).toString(16)}));var r,o},default:yl,"json-pointer":function(){return"/json/pointer"},"relative-json-pointer":function(){return"1/relative/json/pointer"},regex:function(){return"/regex/"}},xl={},wl={skipReadOnly:!1,maxSampleDepth:15};function kl(e,t,n){var r=Object.assign({},wl,t);return dl={},hl=[],ml(e,r,n).value}function El(e,t){xl[e]=t}El("array",(function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=r&&r.depth||1,i=Math.min(null!=e.maxItems?e.maxItems:1/0,e.minItems||1),a=e.items||e.contains;Array.isArray(a)&&(i=Math.max(i,a.length));var s=function(t){return Array.isArray(e.items)?a[t]||{}:a||{}},l=[];if(!a)return l;for(var c=0;c1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o={},i=r&&r.depth||1;if(e&&"object"==typeof e.properties){var a=Array.isArray(e.required)?e.required:[],s=a.reduce((function(e,t){return e[t]=!0,e}),{});Object.keys(e.properties).forEach((function(r){if(!t.skipNonRequired||s.hasOwnProperty(r)){var a=ml(e.properties[r],t,n,{propertyName:r,depth:i+1});t.skipReadOnly&&a.readOnly||t.skipWriteOnly&&a.writeOnly||(o[r]=a.value)}}))}return e&&"object"==typeof e.additionalProperties&&(o.property1=ml(e.additionalProperties,t,n,{depth:i+1}).value,o.property2=ml(e.additionalProperties,t,n,{depth:i+1}).value),o})),El("string",(function(e,t,n,r){var o=e.format||"default",i=bl[o]||yl,a=r&&r.propertyName;return i(0|e.minLength,e.maxLength,a)}));var Sl,_l,Ol,Al,Il,Cl,Tl,Rl,Pl=function(){function e(t,r,o,i,a){nr(this,e),n(this,"examples",void 0),n(this,"schema",void 0),n(this,"name",void 0),n(this,"isRequestType",void 0),n(this,"onlyRequiredInSamples",void 0),this.name=r,this.isRequestType=o,this.schema=i.schema&&new Zs(t,i.schema,"",a),this.onlyRequiredInSamples=a.onlyRequiredInSamples,void 0!==i.examples?this.examples=lo(i.examples,(function(e){return new rl(t,e,r,i.encoding)})):void 0!==i.example?this.examples={default:new rl(t,{value:t.shalowDeref(i.example)},r,i.encoding)}:us(r)&&this.generateExample(t,i)}return or(e,[{key:"generateExample",value:function(e,t){var n={skipReadOnly:this.isRequestType,skipNonRequired:this.isRequestType&&this.onlyRequiredInSamples,skipWriteOnly:!this.isRequestType,maxSampleDepth:10};if(this.schema&&this.schema.oneOf){this.examples={};var r,o=ro(this.schema.oneOf);try{for(o.s();!(r=o.n()).done;){var i=r.value,a=kl(i.rawSchema,n,e.spec);this.schema.discriminatorProp&&"object"==typeof a&&a&&(a[this.schema.discriminatorProp]=i.title),this.examples[i.title]=new rl(e,{value:a},this.name,t.encoding)}}catch(e){o.e(e)}finally{o.f()}}else this.schema&&(this.examples={default:new rl(e,{value:kl(t.schema,n,e.spec)},this.name,t.encoding)})}}]),e}(),jl=(_l=Ya((Sl=function(){function e(t,r,o,i){var s,l;nr(this,e),this.isRequestType=o,n(this,"mediaTypes",void 0),zs(this,"activeMimeIdx",_l,this),an(this),i.unstable_ignoreMimeParameters&&(s=r,l={},Object.keys(s).forEach((function(e){var t=s[e],n=e.split(";")[0].trim();l[n]?l[n]=a(a({},l[n]),t):l[n]=t})),r=l),this.mediaTypes=Object.keys(r).map((function(e){var n=r[e];return t.resetVisited(),new Pl(t,e,o,n,i)}))}return or(e,[{key:"activate",value:function(e){this.activeMimeIdx=e}},{key:"active",get:function(){return this.mediaTypes[this.activeMimeIdx]}},{key:"hasSample",get:function(){return this.mediaTypes.filter((function(e){return!!e.examples})).length>0}}]),e}()).prototype,"activeMimeIdx",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return 0}}),Ya(Sl.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Sl.prototype,"activate"),Sl.prototype),Ya(Sl.prototype,"active",[Ne],Object.getOwnPropertyDescriptor(Sl.prototype,"active"),Sl.prototype),Sl),Ll=function e(t,r,o){nr(this,e),n(this,"description",void 0),n(this,"required",void 0),n(this,"content",void 0);var i=t.deref(r);this.description=i.description||"",this.required=!!i.required,t.exitRef(r),void 0!==i.content&&(this.content=new jl(t,i.content,!0,o))},Nl=(Al=Ya((Ol=function(){function e(t,r,o,i,s){nr(this,e),zs(this,"expanded",Al,this),n(this,"content",void 0),n(this,"code",void 0),n(this,"summary",void 0),n(this,"description",void 0),n(this,"type",void 0),n(this,"headers",[]),an(this),this.expanded="all"===s.expandResponses||s.expandResponses[r];var l=t.deref(i);t.exitRef(i),this.code=r,void 0!==l.content&&(this.content=new jl(t,l.content,!1,s)),void 0!==l["x-summary"]?(this.summary=l["x-summary"],this.description=l.description||""):(this.summary=l.description||"",this.description=""),this.type=os(r,o);var c=l.headers;void 0!==c&&(this.headers=Object.keys(c).map((function(e){var n=c[e];return new il(t,a(a({},n),{},{name:e}),"",s)})))}return or(e,[{key:"toggle",value:function(){this.expanded=!this.expanded}}]),e}()).prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Ol.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(Ol.prototype,"toggle"),Ol.prototype),Ol);function Ml(e){return"payload"===e.lang&&e.requestBodyContent}var Dl,Fl,zl,Ul,Bl,$l,ql,Vl,Wl,Hl,Yl,Gl,Ql,Xl,Kl,Zl=!1,Jl=(Cl=Ya((Il=function(){function e(t,r,o,i){var a=arguments.length>4&&void 0!==arguments[4]&&arguments[4];nr(this,e),this.parser=t,this.operationSpec=r,this.options=i,n(this,"id",void 0),n(this,"absoluteIdx",void 0),n(this,"name",void 0),n(this,"description",void 0),n(this,"type","operation"),n(this,"parent",void 0),n(this,"externalDocs",void 0),n(this,"items",[]),n(this,"depth",void 0),zs(this,"ready",Cl,this),zs(this,"active",Tl,this),zs(this,"expanded",Rl,this),n(this,"pointer",void 0),n(this,"operationId",void 0),n(this,"httpVerb",void 0),n(this,"deprecated",void 0),n(this,"path",void 0),n(this,"servers",void 0),n(this,"security",void 0),n(this,"extensions",void 0),n(this,"isCallback",void 0),n(this,"isWebhook",void 0),an(this),this.pointer=r.pointer,this.description=r.description,this.parent=o,this.externalDocs=r.externalDocs,this.deprecated=!!r.deprecated,this.httpVerb=r.httpVerb,this.deprecated=!!r.deprecated,this.operationId=r.operationId,this.path=r.pathName,this.isCallback=a,this.isWebhook=!!r.isWebhook,this.name=ss(r),this.isCallback?(this.security=(r.security||[]).map((function(e){return new Xs(e,t)})),this.servers=ws("",r.servers||r.pathServers||[])):(this.id=void 0!==r.operationId?"operation/"+r.operationId:void 0!==o?o.id+this.pointer:this.pointer,this.security=(r.security||t.spec.security||[]).map((function(e){return new Xs(e,t)})),this.servers=ws(t.specUrl,r.servers||r.pathServers||t.spec.servers||[])),i.showExtensions&&(this.extensions=As(r,i.showExtensions))}return or(e,[{key:"activate",value:function(){this.active=!0}},{key:"deactivate",value:function(){this.active=!1}},{key:"toggle",value:function(){this.expanded=!this.expanded}},{key:"expand",value:function(){this.parent&&this.parent.expand()}},{key:"collapse",value:function(){}},{key:"requestBody",get:function(){return this.operationSpec.requestBody&&new Ll(this.parser,this.operationSpec.requestBody,this.options)}},{key:"codeSamples",get:function(){var e=this.operationSpec["x-codeSamples"]||this.operationSpec["x-code-samples"]||[];this.operationSpec["x-code-samples"]&&!Zl&&(Zl=!0,console.warn('"x-code-samples" is deprecated. Use "x-codeSamples" instead'));var t=this.requestBody&&this.requestBody.content;if(t&&t.hasSample){var n=Math.min(e.length,this.options.payloadSampleIdx);e=[].concat(es(e.slice(0,n)),[{lang:"payload",label:"Payload",source:"",requestBodyContent:t}],es(e.slice(n)))}return e}},{key:"parameters",get:function(){var e=this,t=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r={};return n.forEach((function(t){t=e.shalowDeref(t),r[t.name+"_"+t.in]=!0})),(t=t.filter((function(t){return t=e.shalowDeref(t),!r[t.name+"_"+t.in]}))).concat(n)}(this.parser,this.operationSpec.pathParameters,this.operationSpec.parameters).map((function(t){return new il(e.parser,t,e.pointer,e.options)}));return this.options.sortPropsAlphabetically?xs(t,"name"):this.options.requiredPropsFirst?bs(t):t}},{key:"responses",get:function(){var e=this,t=!1;return Object.keys(this.operationSpec.responses||[]).filter((function(e){return"default"===e||("success"===os(e)&&(t=!0),"default"===(n=e)||uo(n)||rs(n));var n})).map((function(n){return new Nl(e.parser,n,t,e.operationSpec.responses[n],e.options)}))}},{key:"callbacks",get:function(){var e=this;return Object.keys(this.operationSpec.callbacks||[]).map((function(t){return new Ks(e.parser,t,e.operationSpec.callbacks[t],e.pointer,e.options)}))}}]),e}()).prototype,"ready",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!0}}),Tl=Ya(Il.prototype,"active",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Rl=Ya(Il.prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Il.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"activate"),Il.prototype),Ya(Il.prototype,"deactivate",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"deactivate"),Il.prototype),Ya(Il.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"toggle"),Il.prototype),Ya(Il.prototype,"requestBody",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"requestBody"),Il.prototype),Ya(Il.prototype,"codeSamples",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"codeSamples"),Il.prototype),Ya(Il.prototype,"parameters",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"parameters"),Il.prototype),Ya(Il.prototype,"responses",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"responses"),Il.prototype),Ya(Il.prototype,"callbacks",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"callbacks"),Il.prototype),Il),ec=ka.div(Dl||(Dl=Ao(["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.spacing.sectionHorizontal}),(function(e){var t=e.compact,n=e.theme;return wa("medium",!0)(Fl||(Fl=Ao(["\n width: 100%;\n padding: ",";\n "])),"".concat(t?0:n.spacing.sectionVertical,"px ").concat(n.spacing.sectionHorizontal,"px"))})),tc=ka.div.attrs((function(e){return n({},Uf,e.id)}))(zl||(zl=Ao(["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"])),(function(e){return e.theme.spacing.sectionVertical}),wa("medium",!0)(Ul||(Ul=Ao(["\n padding: 0;\n "]))),(function(e){return e.underlined?"\n position: relative;\n\n &:not(:last-of-type):after {\n position: absolute;\n bottom: 0;\n width: 100%;\n display: block;\n content: '';\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n ":""})),nc=ka.div(Bl||(Bl=Ao(["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){return e.theme.spacing.sectionHorizontal}),wa("medium",!0)($l||($l=Ao(["\n width: 100%;\n padding: ",";\n "])),(function(e){return"".concat(e.theme.spacing.sectionVertical,"px ").concat(e.theme.spacing.sectionHorizontal,"px")}))),rc=ka(nc)(ql||(ql=Ao(["\n background-color: ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor})),oc=ka.div(Vl||(Vl=Ao(["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"])),wa("medium",!0)(Wl||(Wl=Ao(["\n flex-direction: column;\n "])))),ic={1:"1.85714em",2:"1.57143em",3:"1.27em"},ac=function(e){return ga(Hl||(Hl=Ao(["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontWeight}),ic[e],(function(e){return e.theme.typography.headings.lineHeight}))},sc=ka.h1(Yl||(Yl=Ao(["\n ",";\n color: ",";\n\n ",";\n"])),ac(1),(function(e){return e.theme.colors.primary.main}),Ea("H1")),lc=ka.h2(Gl||(Gl=Ao(["\n ",";\n color: black;\n\n ",";\n"])),ac(2),Ea("H2")),cc=(ka.h2(Ql||(Ql=Ao(["\n ",";\n color: black;\n\n ",";\n"])),ac(3),Ea("H3")),ka.h3(Xl||(Xl=Ao(["\n color: ",";\n\n ",";\n"])),(function(e){return e.theme.rightPanel.textColor}),Ea("RightPanelHeader"))),uc=ka.h5(Kl||(Kl=Ao(["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"])),Ea("UnderlinedHeader"));function pc(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=e&&("undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"]);if(null!=n){var r,o,i=[],a=!0,s=!1;try{for(n=n.call(e);!(a=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);a=!0);}catch(e){s=!0,o=e}finally{try{a||null==n.return||n.return()}finally{if(s)throw o}}return i}}(e,t)||no(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var fc,dc,hc=(0,s.createContext)(void 0),mc=hc.Provider,vc=hc.Consumer;function gc(e){var t=e.spec,n=e.specUrl,r=e.options,o=e.onLoaded,i=e.children,a=pc(s.useState(null),2),l=a[0],c=a[1];s.useEffect((function(){function e(){return(e=Fa(Ua().mark((function e(){var r;return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(t||n){e.next=2;break}return e.abrupt("return",void 0);case 2:return c(null),e.next=5,Va(t||n);case 5:r=e.sent,c(r);case 7:case"end":return e.stop()}}),e)})))).apply(this,arguments)}!function(){e.apply(this,arguments)}()}),[t,n]);var u=s.useMemo((function(){if(!l)return null;try{return new pv(l,n,r)}catch(e){throw o&&o(e),e}}),[l,n,r]);return s.useEffect((function(){u&&o&&o()}),[u,o]),i({loading:!u,store:u})}var yc=function(e){return ga(fc||(fc=Ao(["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n outline: 0;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"])),e,e,e,e,e)};function bc(e){var t=s.useContext(hc),n=s.useCallback((function(n){t&&function(e,t,n){t.defaultPrevented||0!==t.button||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(t)||(t.preventDefault(),e.replace(n))}(t.menu.history,n,e.to)}),[t]);return t?s.createElement("a",{className:e.className,href:t.menu.history.linkForId(e.to),onClick:n,"aria-label":e.to},e.children):null}var xc,wc,kc=ka(bc)(dc||(dc=Ao(["\n ",";\n"])),yc("&"));function Ec(e){return s.createElement(kc,{to:e.to})}var Sc,_c,Oc,Ac,Ic,Cc,Tc,Rc,Pc,jc,Lc,Nc,Mc,Dc,Fc,zc,Uc,Bc,$c,qc,Vc,Wc={left:"90deg",right:"-90deg",up:"-180deg",down:"0"},Hc=ka(function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement("svg",{className:this.props.className,style:this.props.style,version:"1.1",viewBox:"0 0 24 24",x:"0",xmlns:"http://www.w3.org/2000/svg",y:"0","aria-hidden":"true"},s.createElement("polygon",{points:"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "}))}}]),n}(s.PureComponent))(xc||(xc=Ao(["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"])),(function(e){return e.size||"18px"}),(function(e){return e.size||"18px"}),(function(e){return e.float||""}),(function(e){return Wc[e.direction||"down"]}),(function(e){var t=e.color,n=e.theme;return t&&n.colors.responses[t]&&n.colors.responses[t].color||t})),Yc=ka.span(wc||(wc=Ao(["\n display: inline-block;\n padding: 2px 8px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: middle;\n line-height: 1.6;\n border-radius: 4px;\n font-weight: ",";\n font-size: 12px;\n + span[type] {\n margin-left: 4px;\n }\n"])),(function(e){return e.theme.colors[e.type].main}),(function(e){return e.theme.colors[e.type].contrastText}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.fontWeightBold})),Gc=ga(Sc||(Sc=Ao(["\n text-decoration: line-through;\n color: #707070;\n"]))),Qc=ka.caption(_c||(_c=Ao(["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"])),(function(e){return e.theme.colors.text.secondary})),Xc=ka.td(Oc||(Oc=Ao(["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n ","\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"])),(function(e){return e.theme.schema.linesColor}),wa("small")(Ac||(Ac=Ao(["\n display: block;\n overflow: hidden;\n "]))),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),Kc=ka(Xc)(Ic||(Ic=Ao(["\n padding: 0;\n"]))),Zc=ka(Xc)(Cc||(Cc=Ao(["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 13px;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),Gc,(function(e){return"field"!==e.kind?"font-style: italic":""}),Ea("PropertyNameCell")),Jc=ka.td(Tc||(Tc=Ao(["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n\n ","\n\n ",";\n"])),(function(e){return e.theme.schema.defaultDetailsWidth}),wa("small")(Rc||(Rc=Ao(["\n padding: 0 20px;\n border-bottom: none;\n border-left: 1px solid ",";\n\n tr.last > & {\n border-left: none;\n }\n "])),(function(e){return e.theme.schema.linesColor})),Ea("PropertyDetailsCell")),eu=ka.span(Pc||(Pc=Ao(["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),tu=ka.div(jc||(jc=Ao(["\n padding: ",";\n"])),(function(e){return e.theme.schema.nestingSpacing})),nu=ka.table(Lc||(Lc=Ao(["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n ","\n\n ","\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"])),(function(e){return e.theme.typography.fontSize}),wa("small")(Nc||(Nc=Ao(["\n display: block;\n > tr, > tbody > tr {\n display: block;\n }\n "]))),wa("small",!1," and (-ms-high-contrast:none)")(Mc||(Mc=Ao(["\n td {\n float: left;\n width: 100%;\n }\n "]))),tu,tu,tu,tu,tu,tu,tu,tu,tu,(function(e){return e.theme.schema.nestingSpacing}),(function(e){return e.theme.schema.nestedBackground}),tu,tu,tu,tu,tu,tu,tu,tu,tu,tu,tu,tu),ru=ka.div(Dc||(Dc=Ao(["\n margin: 0 0 3px 0;\n display: inline-block;\n"]))),ou=ka.span(Fc||(Fc=Ao(["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily})),iu=ka.button(zc||(zc=Ao(["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n line-height: 1.5em;\n outline: none;\n &:focus {\n box-shadow: 0 0 0 1px ",";\n }\n\n ","\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.active?"\n color: white;\n background-color: ".concat(e.theme.colors.primary.main,";\n &:focus {\n box-shadow: none;\n background-color: ").concat(zr(.15,e.theme.colors.primary.main),";\n }\n "):"\n color: ".concat(e.theme.colors.primary.main,";\n background-color: white;\n ")})),au=ka.div(Uc||(Uc=Ao(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),su=ka.div(Bc||(Bc=Ao(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),lu=function(){return(lu=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=0&&n.push(s)}else a.value.toLowerCase().includes(r)&&n.push(a)}return n}(t,o):t}),[t,o]),l=(0,s.useCallback)((function(t,n){void 0===n&&(n=!0),i(t),n&&e(0)}),[e,i]);return{searchTerm:o,setSearchTerm:l,filteredOptions:a}},Bu=0,$u=function(e){var t,n=e.arrowRenderer,r=e.contentClassName,o=e.className,i=e.disabled,a=e.hideArrow,l=e.id,c=e.optionItemRenderer,u=e.pageKeyTraverseSize,p=e.placeholder,f=e.searchable,d=e.value,h=e.selectedValueClassName,m=(0,s.useMemo)((function(){return t||(e=Bu,Bu+=1,"react_dropdown_aria_"+e);var e}),[t=l]),v=function(e,t){var n=e.options,r=e.searchable,o=e.onChange,i=e.disabled,a=e.ariaDescribedBy,l=e.ariaLabel,c=e.ariaLabelledBy,u=e.value,p=e.defaultOpen,f=(0,s.useState)(0),d=f[0],h=f[1],m=(0,s.useState)(p),v=m[0],g=m[1],y=(0,s.useRef)(null),b=(0,s.useRef)(null),x=(0,s.useRef)(null),w=(0,s.useState)(!1),k=w[0],E=w[1],S=Uu(h,n,r),_=S.searchTerm,O=S.setSearchTerm,A=S.filteredOptions,I=(0,s.useMemo)((function(){return A.reduce(gu,[])}),[A]),C=(0,s.useMemo)((function(){return I.map((function(e){return e.value})).indexOf(u)}),[I,u]),T=(0,s.useCallback)((function(e){void 0===e&&(e=!1),O("",!1),g(!1),e&&b.current&&b.current.focus()}),[b.current,O,g]),R=(0,s.useCallback)((function(){h(C>0?C:0),g(!0)}),[g,h,C]),P=(0,s.useCallback)((function(e,t){void 0===t&&(t=!1),e&&(o(e),O("",!1)),t&&T(!0)}),[o,T,O]);!function(e,t){var n=function(n){t.current&&!t.current.contains(n.target)&&e()};(0,s.useEffect)((function(){return document.addEventListener("mouseup",n,!1),document.addEventListener("touchend",n,!1),function(){document.removeEventListener("mouseup",n),document.removeEventListener("touchend",n)}}),[])}(T,y),function(e,t){(0,s.useEffect)((function(){if(t.current&&e>=0){var n=t.current.getElementsByClassName("dropdown-option"),r=n&&n.length?n[e]:null;if(r&&r.getBoundingClientRect){var o=r.getBoundingClientRect().height,i=t.current.getBoundingClientRect().height,a=t.current.scrollTop,s=r.offsetTop<=a;r.offsetTop>=a&&r.offsetTop+o<=a+i||(s?t.current.scrollTo({top:r.offsetTop}):t.current.scrollTo({top:r.offsetTop-i+o+8}))}}}),[e])}(d,x);var j=(0,s.useMemo)((function(){return{"aria-hidden":i,"aria-expanded":v,"aria-haspopup":"listbox","aria-activedescendant":t+"_list_"+d,"aria-controls":t+"_list","aria-label":l,"aria-labelledby":c,"aria-describedby":a}}),[i,v,t,d,l,c,a]);return{focusedIndex:d,setFocusedIndex:h,open:v,setOpen:g,searchTerm:_,setSearchTerm:O,dropdownFocused:k,setDropdownFocused:E,setValue:P,filteredOptions:A,openDropdown:R,closeDropdown:T,flattenedOptions:I,container:y,inputRef:b,listWrapper:x,ariaProps:j,ariaList:zu(I,C,t)}}(e,m),g=v.open,y=v.dropdownFocused,b=v.focusedIndex,x=v.setFocusedIndex,w=v.setDropdownFocused,k=v.setValue,E=v.openDropdown,S=v.closeDropdown,_=v.searchTerm,O=v.setSearchTerm,A=v.filteredOptions,I=v.flattenedOptions,C=v.container,T=v.inputRef,R=v.listWrapper,P=v.ariaProps,j=v.ariaList,L=(0,s.useCallback)((function(){T.current&&T.current.focus()}),[T.current]),N=(0,s.useCallback)((function(){L(),i||g&&f||(g?S(!0):E())}),[g,i,f,S,E]),M=(0,s.useCallback)((function(e){switch(e){case qc.UP_ARROW:x((function(e){return 0===e?I.length-1:e-1}));break;case qc.DOWN_ARROW:x((function(e){return(e+1)%I.length}));break;case qc.PAGE_UP:x((function(e){return e-u<0&&0!==e?0:e-u<0?I.length-1:e-u}));break;case qc.PAGE_DOWN:x((function(e){return e===I.length-1?0:e+u>I.length-1?I.length-1:(e+u)%I.length}));break;case qc.ESCAPE:S(!0)}}),[x,I,u,S]),D=(0,s.useCallback)((function(e){var t=e.keyCode;-1!==cu.indexOf(t)?(e.preventDefault(),e.stopPropagation(),M(t)):t!==qc.ENTER&&(t!==qc.SPACE||f)||g?t!==qc.TAB||f?(t===qc.TAB||t===qc.ENTER)&&I.length>0&&b>=0&&g&&(e.stopPropagation(),e.preventDefault(),k(I[b],!0)):S():(e.preventDefault(),E())}),[I,k,b,g,M,E,f,S]),F=(0,s.useCallback)((function(e){O(e.target.value)}),[O]),z=(0,s.useCallback)((function(){return w(!0)}),[w]),U=(0,s.useCallback)((function(){return w(!1)}),[w]),B=(0,s.useMemo)((function(){if(a)return null;if(n)return(0,s.createElement)(Ou,{className:"dropdown-arrow"},n(g));var e=g&&f;return(0,s.createElement)(Ou,{className:"dropdown-arrow"},e&&(0,s.createElement)(Mu,null),!e&&(0,s.createElement)(Nu,null))}),[g,n,f,a]);return(0,s.createElement)(xu,{ref:C,onFocus:L,onClick:N,role:"button",width:e.width,height:e.height,disabled:i,open:g,dropdownFocused:y,className:bu("dropdown",o)},(0,s.createElement)(wu,{className:"dropdown-selector",open:g,searchable:e.searchable},(0,s.createElement)(ku,{className:"dropdown-selector-search"},(0,s.createElement)("input",lu({id:m,ref:T,value:_,onChange:F,onKeyDown:D,onFocus:z,onBlur:U,readOnly:!g||!f,disabled:i,autoComplete:"off",role:"combobox"},P))),!d&&!_&&(0,s.createElement)(_u,{className:"dropdown-selector-placeholder",centerText:e.centerText},p),d&&!_&&(0,s.createElement)(Su,{className:bu("dropdown-selector-value",h),centerText:e.centerText,value:d,open:g},d),B),j,(0,s.createElement)(Au,{maxContentHeight:e.maxContentHeight,openUp:e.openUp,open:g,className:bu("dropdown-selector-content",r),ref:R},(0,s.createElement)(Du,{selectedOption:d,options:A,focusedIndex:b,onOptionClicked:k,optionItemRenderer:c,empty:0===I.length})))};$u.defaultProps={ariaDescribedBy:null,ariaLabel:null,ariaLabelledBy:null,arrowRenderer:void 0,centerText:!1,className:void 0,contentClassName:null,defaultOpen:!1,disabled:!1,height:null,hideArrow:!1,id:null,maxContentHeight:150,openUp:!1,optionItemRenderer:void 0,pageKeyTraverseSize:10,placeholder:"Select ...",searchable:!1,selectedValueClassName:null,style:{},value:void 0,width:null};var qu,Vu,Wu,Hu=ka($u)(qu||(qu=Ao(["\n && {\n box-sizing: border-box;\n min-width: 100px;\n outline: none;\n display: inline-block;\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.5);\n vertical-align: bottom;\n padding: 2px 0px 2px 6px;\n position: relative;\n width: auto;\n background: white;\n color: #263238;\n font-family: ",";\n font-size: 0.929em;\n line-height: 1.5em;\n cursor: pointer;\n transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;\n &:hover,\n &:focus-within {\n border: 1px solid ",";\n color: ",";\n box-shadow: 0px 0px 0px 1px ",";\n }\n .dropdown-selector {\n display: inline-flex;\n padding: 0;\n height: auto;\n padding-right: 20px;\n position: relative;\n margin-bottom: 5px;\n }\n .dropdown-selector-value {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1;\n vertical-align: middle;\n color: #263238;\n left: 0;\n transition: color 0.25s ease, text-shadow 0.25s ease;\n }\n .dropdown-arrow {\n position: absolute;\n right: 3px;\n top: 50%;\n transform: translateY(-50%);\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n width: 0;\n svg {\n display: none;\n }\n }\n\n .dropdown-selector-content {\n position: absolute;\n margin-top: 2px;\n left: -2px;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n background-color: #ffffff;\n\n &[aria-selected='true'] {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n input {\n cursor: pointer;\n height: 1px;\n background-color: transparent;\n }\n }\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main})),Yu=ka(Hu)(Vu||(Vu=Ao(["\n && {\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover,\n &:focus-within {\n border: none;\n box-shadow: none;\n .dropdown-selector-value {\n color: ",";\n text-shadow: 0px 0px 0px ",";\n }\n }\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main})),Gu=ka.span(Wu||(Wu=Ao(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"])));function Qu(e){return function(t){return!!t.type&&t.type.tabsRole===e}}var Xu=Qu("Tab"),Ku=Qu("TabList"),Zu=Qu("TabPanel");function Ju(){return(Ju=Object.assign||function(e){for(var t=1;t=this.getTabsCount())){var n=this.props;(0,n.onSelect)(e,n.selectedIndex,t)}},o.getNextTab=function(e){for(var t=this.getTabsCount(),n=e+1;ne;)if(!fp(this.getTab(t)))return t;return e},o.getFirstTab=function(){for(var e=this.getTabsCount(),t=0;t=0||(o[n]=e[n]);return o}(t,["children","className","disabledTabClassName","domRef","focus","forceRenderTabPanel","onSelect","selectedIndex","selectedTabClassName","selectedTabPanelClassName","environment","disableUpDownKeys"]));return s.createElement("div",lp({},o,{className:rp(n),onClick:this.handleClick,onKeyDown:this.handleKeyDown,ref:function(t){e.node=t,r&&r(t)},"data-tabs":!0}),this.getChildren())},r}(s.Component);function hp(e,t){return(hp=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}dp.defaultProps={className:"react-tabs",focus:!1},dp.propTypes={};var mp=function(e){var t,n;function r(t){var n;return(n=e.call(this,t)||this).handleSelected=function(e,t,r){var o=n.props.onSelect,i=n.state.mode;if("function"!=typeof o||!1!==o(e,t,r)){var a={focus:"keydown"===r.type};1===i&&(a.selectedIndex=e),n.setState(a)}},n.state=r.copyPropsToState(n.props,{},t.defaultFocus),n}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,hp(t,n),r.getDerivedStateFromProps=function(e,t){return r.copyPropsToState(e,t)},r.getModeFromProps=function(e){return null===e.selectedIndex?1:0},r.copyPropsToState=function(e,t,n){void 0===n&&(n=!1);var o={focus:n,mode:r.getModeFromProps(e)};if(1===o.mode){var i,a=Math.max(0,sp(e.children)-1);i=null!=t.selectedIndex?Math.min(t.selectedIndex,a):e.defaultIndex||0,o.selectedIndex=i}return o},r.prototype.render=function(){var e=this.props,t=e.children,n=(e.defaultIndex,e.defaultFocus,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,["children","defaultIndex","defaultFocus"])),r=this.state,o=r.focus,i=r.selectedIndex;return n.focus=o,n.onSelect=this.handleSelected,null!=i&&(n.selectedIndex=i),s.createElement(dp,n,t)},r}(s.Component);function vp(){return(vp=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,["children","className"]);return s.createElement("ul",vp({},r,{className:rp(n),role:"tablist"}),t)},r}(s.Component);function bp(){return(bp=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(n,["children","className","disabled","disabledClassName","focus","id","panelId","selected","selectedClassName","tabIndex","tabRef"]);return s.createElement("li",bp({},h,{className:rp(o,(e={},e[p]=u,e[a]=i,e)),ref:function(e){t.node=e,d&&d(e)},role:"tab",id:l,"aria-selected":u?"true":"false","aria-disabled":i?"true":"false","aria-controls":c,tabIndex:f||(u?"0":null)}),r)},r}(s.Component);function Ep(){return(Ep=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(t,["children","className","forceRender","id","selected","selectedClassName","tabId"]);return s.createElement("div",Ep({},u,{className:rp(r,(e={},e[l]=a,e)),role:"tabpanel",id:i,"aria-labelledby":c}),o||a?n:null)},r}(s.Component);Ap.defaultProps={className:"react-tabs__tab-panel",forceRender:!1,selectedClassName:"react-tabs__tab-panel--selected"},Ap.propTypes={},Ap.tabsRole="TabPanel";var Ip,Cp,Tp,Rp,Pp=ka(mp)(_p||(_p=Ao(["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n &:focus {\n outline: auto;\n }\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){var t=e.theme;return"".concat(t.spacing.unit,"px ").concat(t.spacing.unit,"px ").concat(t.spacing.unit,"px")}),(function(e){var t=e.theme;return zr(.05,t.codeBlock.backgroundColor)}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.colors.responses.success.tabTextColor}),(function(e){return e.theme.colors.responses.redirect.tabTextColor}),(function(e){return e.theme.colors.responses.info.tabTextColor}),(function(e){return e.theme.colors.responses.error.tabTextColor}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),jp=(ka(Pp)(Op||(Op=Ao(["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"])),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return 2*e.theme.spacing.unit})),ka.div(Ip||(Ip=Ao(["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: #e64441;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n .token.property.string {\n color: white;\n }\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"])),Ea("Prism"))),Lp=ka.div(Cp||(Cp=Ao(["\n opacity: 0.7;\n transition: opacity 0.3s ease;\n text-align: right;\n &:focus-within {\n opacity: 1;\n }\n > button {\n background-color: transparent;\n border: 0;\n color: inherit;\n padding: 2px 10px;\n font-family: ",";\n font-size: ",";\n line-height: ",";\n cursor: pointer;\n outline: 0;\n\n :hover,\n :focus {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontSize}),(function(e){return e.theme.typography.lineHeight})),Np=ka.div(Tp||(Tp=Ao(["\n &:hover "," {\n opacity: 1;\n }\n"])),Lp),Mp=ka(jp.withComponent("pre"))(Rp||(Rp=Ao(["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}));function Dp(e){return getComputedStyle(e)}function Fp(e,t){for(var n in t){var r=t[n];"number"==typeof r&&(r+="px"),e.style[n]=r}return e}function zp(e){var t=document.createElement("div");return t.className=e,t}var Up="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector);function Bp(e,t){if(!Up)throw new Error("No element matching method supported");return Up.call(e,t)}function $p(e){e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}function qp(e,t){return Array.prototype.filter.call(e.children,(function(e){return Bp(e,t)}))}var Vp=function(e){return"ps__thumb-"+e},Wp=function(e){return"ps__rail-"+e},Hp="ps__child--consume",Yp="ps--focus",Gp="ps--clicking",Qp=function(e){return"ps--active-"+e},Xp=function(e){return"ps--scrolling-"+e},Kp={x:null,y:null};function Zp(e,t){var n=e.element.classList,r=Xp(t);n.contains(r)?clearTimeout(Kp[t]):n.add(r)}function Jp(e,t){Kp[t]=setTimeout((function(){return e.isAlive&&e.element.classList.remove(Xp(t))}),e.settings.scrollingThreshold)}var ef=function(e){this.element=e,this.handlers={}},tf={isEmpty:{configurable:!0}};ef.prototype.bind=function(e,t){void 0===this.handlers[e]&&(this.handlers[e]=[]),this.handlers[e].push(t),this.element.addEventListener(e,t,!1)},ef.prototype.unbind=function(e,t){var n=this;this.handlers[e]=this.handlers[e].filter((function(r){return!(!t||r===t)||(n.element.removeEventListener(e,r,!1),!1)}))},ef.prototype.unbindAll=function(){for(var e in this.handlers)this.unbind(e)},tf.isEmpty.get=function(){var e=this;return Object.keys(this.handlers).every((function(t){return 0===e.handlers[t].length}))},Object.defineProperties(ef.prototype,tf);var nf=function(){this.eventElements=[]};function rf(e){if("function"==typeof window.CustomEvent)return new CustomEvent(e);var t=document.createEvent("CustomEvent");return t.initCustomEvent(e,!1,!1,void 0),t}function of(e,t,n,r,o){var i;if(void 0===r&&(r=!0),void 0===o&&(o=!1),"top"===t)i=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==t)throw new Error("A proper axis should be provided");i=["contentWidth","containerWidth","scrollLeft","x","left","right"]}!function(e,t,n,r,o){var i=n[0],a=n[1],s=n[2],l=n[3],c=n[4],u=n[5];void 0===r&&(r=!0),void 0===o&&(o=!1);var p=e.element;e.reach[l]=null,p[s]<1&&(e.reach[l]="start"),p[s]>e[i]-e[a]-1&&(e.reach[l]="end"),t&&(p.dispatchEvent(rf("ps-scroll-"+l)),t<0?p.dispatchEvent(rf("ps-scroll-"+c)):t>0&&p.dispatchEvent(rf("ps-scroll-"+u)),r&&function(e,t){Zp(e,t),Jp(e,t)}(e,l)),e.reach[l]&&(t||o)&&p.dispatchEvent(rf("ps-"+l+"-reach-"+e.reach[l]))}(e,n,i,r,o)}function af(e){return parseInt(e,10)||0}nf.prototype.eventElement=function(e){var t=this.eventElements.filter((function(t){return t.element===e}))[0];return t||(t=new ef(e),this.eventElements.push(t)),t},nf.prototype.bind=function(e,t,n){this.eventElement(e).bind(t,n)},nf.prototype.unbind=function(e,t,n){var r=this.eventElement(e);r.unbind(t,n),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},nf.prototype.unbindAll=function(){this.eventElements.forEach((function(e){return e.unbindAll()})),this.eventElements=[]},nf.prototype.once=function(e,t,n){var r=this.eventElement(e),o=function(e){r.unbind(t,o),n(e)};r.bind(t,o)};var sf={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||"maxTouchPoints"in window.navigator&&window.navigator.maxTouchPoints>0||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)};function lf(e){var t=e.element,n=Math.floor(t.scrollTop),r=t.getBoundingClientRect();e.containerWidth=Math.ceil(r.width),e.containerHeight=Math.ceil(r.height),e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight,t.contains(e.scrollbarXRail)||(qp(t,Wp("x")).forEach((function(e){return $p(e)})),t.appendChild(e.scrollbarXRail)),t.contains(e.scrollbarYRail)||(qp(t,Wp("y")).forEach((function(e){return $p(e)})),t.appendChild(e.scrollbarYRail)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),function(e,t){var n={width:t.railXWidth},r=Math.floor(e.scrollTop);t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft,t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-r:n.top=t.scrollbarXTop+r,Fp(t.scrollbarXRail,n);var o={top:r,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?o.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth-9:o.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?o.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:o.left=t.scrollbarYLeft+e.scrollLeft,Fp(t.scrollbarYRail,o),Fp(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),Fp(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}(t,e),e.scrollbarXActive?t.classList.add(Qp("x")):(t.classList.remove(Qp("x")),e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=!0===e.isRtl?e.contentWidth:0),e.scrollbarYActive?t.classList.add(Qp("y")):(t.classList.remove(Qp("y")),e.scrollbarYHeight=0,e.scrollbarYTop=0,t.scrollTop=0)}function cf(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function uf(e,t){var n=t[0],r=t[1],o=t[2],i=t[3],a=t[4],s=t[5],l=t[6],c=t[7],u=t[8],p=e.element,f=null,d=null,h=null;function m(t){t.touches&&t.touches[0]&&(t[o]=t.touches[0].pageY),p[l]=f+h*(t[o]-d),Zp(e,c),lf(e),t.stopPropagation(),t.preventDefault()}function v(){Jp(e,c),e[u].classList.remove(Gp),e.event.unbind(e.ownerDocument,"mousemove",m)}function g(t,a){f=p[l],a&&t.touches&&(t[o]=t.touches[0].pageY),d=t[o],h=(e[r]-e[n])/(e[i]-e[s]),a?e.event.bind(e.ownerDocument,"touchmove",m):(e.event.bind(e.ownerDocument,"mousemove",m),e.event.once(e.ownerDocument,"mouseup",v),t.preventDefault()),e[u].classList.add(Gp),t.stopPropagation()}e.event.bind(e[a],"mousedown",(function(e){g(e)})),e.event.bind(e[a],"touchstart",(function(e){g(e,!0)}))}var pf={"click-rail":function(e){e.element,e.event.bind(e.scrollbarY,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarYRail,"mousedown",(function(t){var n=t.pageY-window.pageYOffset-e.scrollbarYRail.getBoundingClientRect().top>e.scrollbarYTop?1:-1;e.element.scrollTop+=n*e.containerHeight,lf(e),t.stopPropagation()})),e.event.bind(e.scrollbarX,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarXRail,"mousedown",(function(t){var n=t.pageX-window.pageXOffset-e.scrollbarXRail.getBoundingClientRect().left>e.scrollbarXLeft?1:-1;e.element.scrollLeft+=n*e.containerWidth,lf(e),t.stopPropagation()}))},"drag-thumb":function(e){uf(e,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),uf(e,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(e){var t=e.element;e.event.bind(e.ownerDocument,"keydown",(function(n){if(!(n.isDefaultPrevented&&n.isDefaultPrevented()||n.defaultPrevented)&&(Bp(t,":hover")||Bp(e.scrollbarX,":focus")||Bp(e.scrollbarY,":focus"))){var r,o=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(Bp(r=o,"input,[contenteditable]")||Bp(r,"select,[contenteditable]")||Bp(r,"textarea,[contenteditable]")||Bp(r,"button,[contenteditable]"))return}var i=0,a=0;switch(n.which){case 37:i=n.metaKey?-e.contentWidth:n.altKey?-e.containerWidth:-30;break;case 38:a=n.metaKey?e.contentHeight:n.altKey?e.containerHeight:30;break;case 39:i=n.metaKey?e.contentWidth:n.altKey?e.containerWidth:30;break;case 40:a=n.metaKey?-e.contentHeight:n.altKey?-e.containerHeight:-30;break;case 32:a=n.shiftKey?e.containerHeight:-e.containerHeight;break;case 33:a=e.containerHeight;break;case 34:a=-e.containerHeight;break;case 36:a=e.contentHeight;break;case 35:a=-e.contentHeight;break;default:return}e.settings.suppressScrollX&&0!==i||e.settings.suppressScrollY&&0!==a||(t.scrollTop-=a,t.scrollLeft+=i,lf(e),function(n,r){var o=Math.floor(t.scrollTop);if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&r<0)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&n<0||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}(i,a)&&n.preventDefault())}}))},wheel:function(e){var t=e.element;function n(n){var r=function(e){var t=e.deltaX,n=-1*e.deltaY;return void 0!==t&&void 0!==n||(t=-1*e.wheelDeltaX/6,n=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,n*=10),t!=t&&n!=n&&(t=0,n=e.wheelDelta),e.shiftKey?[-n,-t]:[t,n]}(n),o=r[0],i=r[1];if(!function(e,n,r){if(!sf.isWebKit&&t.querySelector("select:focus"))return!0;if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(Hp))return!0;var i=Dp(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft0))return!0}o=o.parentNode}return!1}(n.target,o,i)){var a=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(i?t.scrollTop-=i*e.settings.wheelSpeed:t.scrollTop+=o*e.settings.wheelSpeed,a=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(o?t.scrollLeft+=o*e.settings.wheelSpeed:t.scrollLeft-=i*e.settings.wheelSpeed,a=!0):(t.scrollTop-=i*e.settings.wheelSpeed,t.scrollLeft+=o*e.settings.wheelSpeed),lf(e),(a=a||function(n,r){var o=Math.floor(t.scrollTop),i=0===t.scrollTop,a=o+t.offsetHeight===t.scrollHeight,s=0===t.scrollLeft,l=t.scrollLeft+t.offsetWidth===t.scrollWidth;return!(Math.abs(r)>Math.abs(n)?i||a:s||l)||!e.settings.wheelPropagation}(o,i))&&!n.ctrlKey&&(n.stopPropagation(),n.preventDefault())}}void 0!==window.onwheel?e.event.bind(t,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(t,"mousewheel",n)},touch:function(e){if(sf.supportsTouch||sf.supportsIePointer){var t=e.element,n={},r=0,o={},i=null;sf.supportsTouch?(e.event.bind(t,"touchstart",c),e.event.bind(t,"touchmove",u),e.event.bind(t,"touchend",p)):sf.supportsIePointer&&(window.PointerEvent?(e.event.bind(t,"pointerdown",c),e.event.bind(t,"pointermove",u),e.event.bind(t,"pointerup",p)):window.MSPointerEvent&&(e.event.bind(t,"MSPointerDown",c),e.event.bind(t,"MSPointerMove",u),e.event.bind(t,"MSPointerUp",p)))}function a(n,r){t.scrollTop-=r,t.scrollLeft-=n,lf(e)}function s(e){return e.targetTouches?e.targetTouches[0]:e}function l(e){return!(e.pointerType&&"pen"===e.pointerType&&0===e.buttons||(!e.targetTouches||1!==e.targetTouches.length)&&(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE))}function c(e){if(l(e)){var t=s(e);n.pageX=t.pageX,n.pageY=t.pageY,r=(new Date).getTime(),null!==i&&clearInterval(i)}}function u(i){if(l(i)){var c=s(i),u={pageX:c.pageX,pageY:c.pageY},p=u.pageX-n.pageX,f=u.pageY-n.pageY;if(function(e,n,r){if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(Hp))return!0;var i=Dp(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft0))return!0}o=o.parentNode}return!1}(i.target,p,f))return;a(p,f),n=u;var d=(new Date).getTime(),h=d-r;h>0&&(o.x=p/h,o.y=f/h,r=d),function(n,r){var o=Math.floor(t.scrollTop),i=t.scrollLeft,a=Math.abs(n),s=Math.abs(r);if(s>a){if(r<0&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return 0===window.scrollY&&r>0&&sf.isChrome}else if(a>s&&(n<0&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!0;return!0}(p,f)&&i.preventDefault()}}function p(){e.settings.swipeEasing&&(clearInterval(i),i=setInterval((function(){e.isInitialized?clearInterval(i):o.x||o.y?Math.abs(o.x)<.01&&Math.abs(o.y)<.01?clearInterval(i):(a(30*o.x,30*o.y),o.x*=.8,o.y*=.8):clearInterval(i)}),10))}}},ff=function(e,t){var n=this;if(void 0===t&&(t={}),"string"==typeof e&&(e=document.querySelector(e)),!e||!e.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");for(var r in this.element=e,e.classList.add("ps"),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:1},t)this.settings[r]=t[r];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var o,i,a=function(){return e.classList.add(Yp)},s=function(){return e.classList.remove(Yp)};this.isRtl="rtl"===Dp(e).direction,!0===this.isRtl&&e.classList.add("ps__rtl"),this.isNegativeScroll=(i=e.scrollLeft,e.scrollLeft=-1,o=e.scrollLeft<0,e.scrollLeft=i,o),this.negativeScrollAdjustment=this.isNegativeScroll?e.scrollWidth-e.clientWidth:0,this.event=new nf,this.ownerDocument=e.ownerDocument||document,this.scrollbarXRail=zp(Wp("x")),e.appendChild(this.scrollbarXRail),this.scrollbarX=zp(Vp("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",a),this.event.bind(this.scrollbarX,"blur",s),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var l=Dp(this.scrollbarXRail);this.scrollbarXBottom=parseInt(l.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=af(l.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=af(l.borderLeftWidth)+af(l.borderRightWidth),Fp(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=af(l.marginLeft)+af(l.marginRight),Fp(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=zp(Wp("y")),e.appendChild(this.scrollbarYRail),this.scrollbarY=zp(Vp("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",a),this.event.bind(this.scrollbarY,"blur",s),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var c=Dp(this.scrollbarYRail);this.scrollbarYRight=parseInt(c.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=af(c.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?function(e){var t=Dp(e);return af(t.width)+af(t.paddingLeft)+af(t.paddingRight)+af(t.borderLeftWidth)+af(t.borderRightWidth)}(this.scrollbarY):null,this.railBorderYWidth=af(c.borderTopWidth)+af(c.borderBottomWidth),Fp(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=af(c.marginTop)+af(c.marginBottom),Fp(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:e.scrollLeft<=0?"start":e.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:e.scrollTop<=0?"start":e.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach((function(e){return pf[e](n)})),this.lastScrollTop=Math.floor(e.scrollTop),this.lastScrollLeft=e.scrollLeft,this.event.bind(this.element,"scroll",(function(e){return n.onScroll(e)})),lf(this)};ff.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,Fp(this.scrollbarXRail,{display:"block"}),Fp(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=af(Dp(this.scrollbarXRail).marginLeft)+af(Dp(this.scrollbarXRail).marginRight),this.railYMarginHeight=af(Dp(this.scrollbarYRail).marginTop)+af(Dp(this.scrollbarYRail).marginBottom),Fp(this.scrollbarXRail,{display:"none"}),Fp(this.scrollbarYRail,{display:"none"}),lf(this),of(this,"top",0,!1,!0),of(this,"left",0,!1,!0),Fp(this.scrollbarXRail,{display:""}),Fp(this.scrollbarYRail,{display:""}))},ff.prototype.onScroll=function(e){this.isAlive&&(lf(this),of(this,"top",this.element.scrollTop-this.lastScrollTop),of(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},ff.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),$p(this.scrollbarX),$p(this.scrollbarY),$p(this.scrollbarXRail),$p(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},ff.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter((function(e){return!e.match(/^ps([-_].+|)$/)})).join(" ")};var df,hf,mf=ff,vf=r(4875),gf=mf||t,yf=ya(df||(df=Ao(["",""])),vf.Z&&vf.Z.toString()),bf=ka.div(hf||(hf=Ao(["\n position: relative;\n"]))),xf=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a2&&void 0!==arguments[2]?arguments[2]:new ko({});nr(this,e),this.options=i,n(this,"specUrl",void 0),n(this,"spec",void 0),n(this,"_refCounter",new Nf),n(this,"allowMergeRefs",!1),n(this,"byRef",(function(e){var t;if(o.spec){"#"!==e.charAt(0)&&(e="#"+e),e=decodeURIComponent(e);try{t=Ja.get(o.spec,e)}catch(e){}return t||{}}})),this.validate(t),this.preprocess(t),this.spec=t,this.allowMergeRefs=t.openapi.startsWith("3.1");var a=Jr?window.location.href:"";"string"==typeof r&&(this.specUrl=(0,ao.resolve)(a,r))}return or(e,[{key:"validate",value:function(e){if(void 0===e.openapi)throw new Error("Document must be valid OpenAPI 3.0.0 definition")}},{key:"preprocess",value:function(e){if(!this.options.noAutoAuth&&e.info&&e.components&&e.components.securitySchemes){var t=e.info.description||"";if(!qs.containsComponent(t,ks)&&!qs.containsComponent(t,Es)){var n="\x3c!-- ReDoc-Inject: <".concat(ks,"> --\x3e");e.info.description=function(e,t,n){var r=new RegExp("(^|\\n)#\\s?".concat(t,"\\s*\\n"),"i"),o=new RegExp("((\\n|^)#\\s*".concat(t,"\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)"),"i");if(r.test(e))return e.replace(o,"$1\n\n".concat(n,"\n$4"));var i=""===e||e.endsWith("\n\n")?"":e.endsWith("\n")?"\n":"\n\n";return"".concat(e).concat(i,"# ").concat(t,"\n\n").concat(n)}(t,"Authentication",n)}}}},{key:"isRef",value:function(e){return!!e&&void 0!==e.$ref&&null!==e.$ref}},{key:"resetVisited",value:function(){this._refCounter=new Nf}},{key:"exitRef",value:function(e){this.isRef(e)&&this._refCounter.exit(e.$ref)}},{key:"deref",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(this.isRef(e)){var r=vs(e.$ref);if(r&&this.options.ignoreNamedSchemas.has(r))return{type:"object",title:r};var o=this.byRef(e.$ref),i=this._refCounter.visited(e.$ref);if(this._refCounter.visit(e.$ref),i&&!t)return Object.assign({},o,{"x-circular-ref":!0});var a=o;return this.isRef(o)&&(a=this.deref(o,!1,n),this.exitRef(o)),this.allowMergeRefs?this.mergeRefs(e,o,n):a}return e}},{key:"mergeRefs",value:function(e,t,n){e.$ref;var r=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,Lf),o=Object.keys(r);return 0===o.length?t:n&&o.some((function(e){return"description"!==e&&"title"!==e&&"externalDocs"!==e}))?{allOf:[t,r]}:a(a({},t),r)}},{key:"shalowDeref",value:function(e){return this.isRef(e)?this.byRef(e.$ref):e}},{key:"mergeAllOf",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:new Set;if(t&&o.add(t),void 0===(e=this.hoistOneOfs(e)).allOf)return e;var i=a(a({},e),{},{allOf:void 0,parentRefs:[],title:e.title||vs(t)});void 0!==i.properties&&"object"==typeof i.properties&&(i.properties=a({},i.properties)),void 0!==i.items&&"object"==typeof i.items&&(i.items=a({},i.items));var s,l=e.allOf.map((function(e){var t;if(!(e&&e.$ref&&o.has(e.$ref))){var a=n.deref(e,r,!0),s=e.$ref||void 0,l=n.mergeAllOf(a,s,r,o);return(t=i.parentRefs).push.apply(t,es(l.parentRefs||[])),{$ref:s,schema:l}}})).filter((function(e){return void 0!==e})),c=ro(l);try{for(c.s();!(s=c.n()).done;){var u=s.value,p=u.$ref,f=u.schema;if(i.type!==f.type&&void 0!==i.type&&void 0!==f.type&&console.warn('Incompatible types in allOf at "'.concat(t,'": "').concat(i.type,'" and "').concat(f.type,'"')),void 0!==f.type&&(i.type=f.type),void 0!==f.properties)for(var d in i.properties=i.properties||{},f.properties)if(i.properties[d]){var h=this.mergeAllOf({allOf:[i.properties[d],f.properties[d]]},t+"/properties/"+d);i.properties[d]=h,this.exitParents(h)}else i.properties[d]=f.properties[d];void 0!==f.items&&(i.items=i.items||{},i.items=this.mergeAllOf({allOf:[i.items,f.items]},t+"/items")),void 0!==f.required&&(i.required=(i.required||[]).concat(f.required)),i=a(a({},f),i),p&&(i.parentRefs.push(p),void 0===i.title&&ms(p))}}catch(e){c.e(e)}finally{c.f()}return i}},{key:"findDerived",value:function(e){var t={},n=this.spec.components&&this.spec.components.schemas||{};for(var r in n){var o=this.deref(n[r]);void 0!==o.allOf&&o.allOf.find((function(t){return void 0!==t.$ref&&e.indexOf(t.$ref)>-1}))&&(t["#/components/schemas/"+r]=[o["x-discriminator-value"]||r])}return t}},{key:"exitParents",value:function(e){var t,n=ro(e.parentRefs||[]);try{for(n.s();!(t=n.n()).done;){var r=t.value;this.exitRef({$ref:r})}}catch(e){n.e(e)}finally{n.f()}}},{key:"hoistOneOfs",value:function(e){var t=this;if(void 0===e.allOf)return e;for(var n=e.allOf,r=0;r0?o.push.apply(o,es(e.getTagGroupsItems(t,void 0,r["x-tagGroups"],i,n))):o.push.apply(o,es(e.getTagsItems(t,i,void 0,void 0,n))),o}},{key:"addMarkdownItems",value:function(e,t,n,r){var o=new qs(r).extractHeadings(e||"");return o.length&&t&&t.description&&(t.description=qs.getTextBeforeHading(t.description,o[0].name)),function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;return n.map((function(n){var o=new Ff("section",n,t);return o.depth=r,n.items&&(o.items=e(o,n.items,r+1)),qs.containsComponent(o.description||"",ks)&&_s(o.id+"/"),o}))}(t,o,n)}},{key:"getTagGroupsItems",value:function(t,n,r,o,i){var a,s=[],l=ro(r);try{for(l.s();!(a=l.n()).done;){var c=a.value,u=new Ff("group",c,n);u.depth=0,u.items=e.getTagsItems(t,o,u,c,i),s.push(u)}}catch(e){l.e(e)}finally{l.f()}return s}},{key:"getTagsItems",value:function(t,n,r,o,i){var a,s=[],l=ro((void 0===o?Object.keys(n):o.tags).map((function(e){return n[e]?(n[e].used=!0,n[e]):(console.warn('Non-existing tag "'.concat(e,'" is added to the group "').concat(o.name,'"')),null)})));try{for(l.s();!(a=l.n()).done;){var c=a.value;if(c){var u=new Ff("tag",c,r);if(u.depth=1,""!==c.name)u.items=[].concat(es(e.addMarkdownItems(c.description||"",u,u.depth+1,i)),es(this.getOperationsItems(t,u,c,u.depth+1,i))),s.push(u);else{var p=[].concat(es(e.addMarkdownItems(c.description||"",u,u.depth+1,i)),es(this.getOperationsItems(t,void 0,c,u.depth+1,i)));s.push.apply(s,es(p))}}}}catch(e){l.e(e)}finally{l.f()}return s}},{key:"getOperationsItems",value:function(e,t,n,r,o){if(0===n.operations.length)return[];var i,a=[],s=ro(n.operations);try{for(s.s();!(i=s.n()).done;){var l=i.value,c=new Jl(e,l,t,o);c.depth=r,a.push(c)}}catch(e){s.e(e)}finally{s.f()}return a}},{key:"getTagsWithOperations",value:function(e,t){var r,o={},i=t["x-webhooks"]||t.webhooks,s=ro(t.tags||[]);try{for(s.s();!(r=s.n()).done;){var l=r.value;o[l.name]=a(a({},l),{},{operations:[]})}}catch(e){s.e(e)}finally{s.f()}function c(e,t,r){for(var i=0,s=Object.keys(t);i=s.flatItems.length-1&&e);){if(e){var r=s.getElementAtOrFirstChild(n+1);if(s.scroll.isElementBellow(r))break}else{var o=s.getElementAt(n);if(s.scroll.isElementAbove(o))break}n+=t}s.activate(s.flatItems[n],!0,!0)})),n(this,"updateOnHistory",(function(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s.history.currentId;t&&((e=s.flatItems.find((function(e){return e.id===t})))?s.activateAndScroll(e,!1):(t.startsWith(Ss)&&(e=s.flatItems.find((function(e){return Ss.startsWith(e.id)})),s.activate(e)),s.scroll.scrollIntoViewBySelector("[".concat(Uf,'="').concat(t,'"]'))))})),n(this,"getItemById",(function(e){return s.flatItems.find((function(t){return t.id===e}))})),an(this),this.items=t.contentItems,this.flatItems=(i=this.items||[],"items",a=[],function e(t){var n,r=ro(t);try{for(r.s();!(n=r.n()).done;){var o=n.value;a.push(o),o.items&&e(o.items)}}catch(e){r.e(e)}finally{r.f()}}(i),a),this.flatItems.forEach((function(e,t){return e.absoluteIdx=t})),this.subscribe()}return or(e,[{key:"subscribe",value:function(){this._unsubscribe=this.scroll.subscribe(this.updateOnScroll),this._hashUnsubscribe=this.history.subscribe(this.updateOnHistory)}},{key:"toggleSidebar",value:function(){this.sideBarOpened=!this.sideBarOpened}},{key:"closeSidebar",value:function(){this.sideBarOpened=!1}},{key:"getElementAt",value:function(e){var t=this.flatItems[e];return t&&eo("[".concat(Uf,'="').concat(t.id,'"]'))||null}},{key:"getElementAtOrFirstChild",value:function(e){var t=this.flatItems[e];return t&&"group"===t.type&&(t=t.items[0]),t&&eo("[".concat(Uf,'="').concat(t.id,'"]'))||null}},{key:"activeItem",get:function(){return this.flatItems[this.activeItemIdx]||void 0}},{key:"activate",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];(this.activeItem&&this.activeItem.id)!==(e&&e.id)&&(e&&"group"===e.type||(this.deactivate(this.activeItem),e?e.depth<=0||(this.activeItemIdx=e.absoluteIdx,t&&this.history.replace(e.id,n),e.activate(),e.expand()):this.history.replace("",n)))}},{key:"deactivate",value:function(e){if(void 0!==e)for(e.deactivate();void 0!==e;)e.collapse(),e=e.parent}},{key:"activateAndScroll",value:function(e,t,n){var r=e&&this.getItemById(e.id)||e;this.activate(r,t,n),this.scrollToActive(),r&&r.items.length||this.closeSidebar()}},{key:"scrollToActive",value:function(){this.scroll.scrollIntoView(this.getElementAt(this.activeItemIdx))}},{key:"dispose",value:function(){this._unsubscribe(),this._hashUnsubscribe()}}],[{key:"updateOnHistory",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Ms.currentId,t=arguments.length>1?arguments[1]:void 0;e&&t.scrollIntoViewBySelector("[".concat(Uf,'="').concat(e,'"]'))}}]),e}()).prototype,"activeItemIdx",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return-1}}),Rf=Ya(Cf.prototype,"sideBarOpened",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Cf.prototype,"toggleSidebar",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"toggleSidebar"),Cf.prototype),Ya(Cf.prototype,"closeSidebar",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"closeSidebar"),Cf.prototype),Ya(Cf.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"activate"),Cf.prototype),Ya(Cf.prototype,"activateAndScroll",[If],Object.getOwnPropertyDescriptor(Cf.prototype,"activateAndScroll"),Cf.prototype),Cf),$f="scroll",qf=(100,Pf=function(e,t,n){var r,o,i,a,s,l,c,u;n.value=(r=n.value,o=100,l=null,c=0,u=function(){c=(new Date).getTime(),l=null,s=r.apply(i,a),l||(i=a=null)},function(){var e=(new Date).getTime(),t=o-(e-c);return i=this,a=arguments,t<=0||t>o?(l&&(clearTimeout(l),l=null),c=e,s=r.apply(i,a),l||(i=a=null)):l||(l=setTimeout(u,t)),s})},Ya((jf=function(){function e(t){nr(this,e),this.options=t,n(this,"_scrollParent",void 0),n(this,"_emiter",void 0),n(this,"_prevOffsetY",0),this._scrollParent=Jr?window:void 0,this._emiter=new Qa,this.bind()}return or(e,[{key:"bind",value:function(){this._prevOffsetY=this.scrollY(),this._scrollParent&&this._scrollParent.addEventListener("scroll",this.handleScroll)}},{key:"dispose",value:function(){this._scrollParent&&this._scrollParent.removeEventListener("scroll",this.handleScroll),this._emiter.removeAllListeners($f)}},{key:"scrollY",value:function(){return"undefined"!=typeof HTMLElement&&this._scrollParent instanceof HTMLElement?this._scrollParent.scrollTop:void 0!==this._scrollParent?this._scrollParent.pageYOffset:0}},{key:"isElementBellow",value:function(e){if(null!==e)return e.getBoundingClientRect().top>this.options.scrollYOffset()}},{key:"isElementAbove",value:function(e){if(null!==e){var t=e.getBoundingClientRect().top;return(t>0?Math.floor(t):Math.ceil(t))<=this.options.scrollYOffset()}}},{key:"subscribe",value:function(e){var t=this._emiter.addListener($f,e);return function(){return t.removeListener($f,e)}}},{key:"scrollIntoView",value:function(e){null!==e&&(e.scrollIntoView(),this._scrollParent&&this._scrollParent.scrollBy&&this._scrollParent.scrollBy(0,1-this.options.scrollYOffset()))}},{key:"scrollIntoViewBySelector",value:function(e){var t=eo(e);this.scrollIntoView(t)}},{key:"handleScroll",value:function(){var e=this.scrollY()-this._prevOffsetY>0;this._prevOffsetY=this.scrollY(),this._emiter.emit($f,e)}}]),e}()).prototype,"handleScroll",[Ga.bind,Pf],Object.getOwnPropertyDescriptor(jf.prototype,"handleScroll"),jf.prototype),jf),Vf=function(){function e(){nr(this,e),n(this,"searchWorker",function(){var e;if(Jr)try{e=r(5114)}catch(t){e=r(308).default}else e=r(308).default;return new e}())}var t;return or(e,[{key:"indexItems",value:function(e){var t=this;!function e(n){n.forEach((function(n){"group"!==n.type&&t.add(n.name,n.description||"",n.id),e(n.items)}))}(e),this.searchWorker.done()}},{key:"add",value:function(e,t,n){this.searchWorker.add(e,t,n)}},{key:"dispose",value:function(){this.searchWorker.terminate(),this.searchWorker.dispose()}},{key:"search",value:function(e){return this.searchWorker.search(e)}},{key:"toJS",value:(t=Fa(Ua().mark((function e(){return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.searchWorker.toJS());case 1:case"end":return e.stop()}}),e,this)}))),function(){return t.apply(this,arguments)})},{key:"load",value:function(e){this.searchWorker.load(e)}},{key:"fromExternalJS",value:function(e,t){e&&t&&this.searchWorker.fromExternalJS(e,t)}}]),e}();function Wf(e){var t=e.Label,n=void 0===t?Gu:t,r=e.Dropdown,o=void 0===r?Yu:r;return 1===e.options.length?s.createElement(n,null,e.options[0].value):s.createElement(o,ir({},e,{searchable:!1}))}var Hf,Yf,Gf=r(7856),Qf=ga(Hf||(Hf=Ao(["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"])),(function(e){return e.theme.typography.links.color}),(function(e){return e.theme.typography.links.visited}),(function(e){return e.theme.typography.links.hover})),Xf=ka(jp)(Yf||(Yf=Ao(["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: ",";\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontWeightRegular}),(function(e){return e.theme.typography.lineHeight}),(function(e){return e.compact&&"\n p:first-child {\n margin-top: 0;\n }\n p:last-child {\n margin-bottom: 0;\n }\n "}),(function(e){return e.inline&&" p {\n display: inline-block;\n }"}),ac(1),(function(e){return e.theme.colors.primary.main}),ac(2),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.code.color}),(function(e){return e.theme.typography.code.backgroundColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontWeight}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.schema.nestedBackground}),yc(".share-link"),Qf,Ea("Markdown")),Kf=Xf.withComponent("span");function Zf(e){var t=e.inline?Kf:Xf;return s.createElement(Ma,null,(function(n){return s.createElement(t,ir({className:"redoc-markdown "+(e.className||""),dangerouslySetInnerHTML:{__html:(r=n.untrustedSpec,o=e.html,r?Gf.sanitize(o):o)},"data-role":e["data-role"]},e));var r,o}))}var Jf,ed,td,nd,rd,od,id=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.source,n=e.inline,r=e.compact,o=e.className,i=e["data-role"],a=new qs;return s.createElement(Zf,{html:a.renderMd(t),inline:n,compact:r,className:o,"data-role":i})}}]),n}(s.Component),ad=ka.div(Jf||(Jf=Ao(["\n position: relative;\n"]))),sd=ka.div(ed||(ed=Ao(["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"]))),ld=ka.div(td||(td=Ao(["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"]))),cd=ka.div(nd||(nd=Ao(["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"]))),ud=ka.div(rd||(rd=Ao(["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"]))),pd=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.open,n=e.title,r=e.children;return s.createElement(ad,null,r,t&&s.createElement(sd,null,s.createElement(ld,null,n),s.createElement(cd,null),s.createElement(ud,null)))}}]),n}(s.Component),fd="undefined"!=typeof document&&document.queryCommandSupported&&document.queryCommandSupported("copy"),dd=function(){function e(){nr(this,e)}return or(e,null,[{key:"isSupported",value:function(){return fd}},{key:"selectElement",value:function(e){var t,n;document.body.createTextRange?((t=document.body.createTextRange()).moveToElementText(e),t.select()):document.createRange&&window.getSelection&&(n=window.getSelection(),(t=document.createRange()).selectNodeContents(e),n.removeAllRanges(),n.addRange(t))}},{key:"deselect",value:function(){if(document.selection)document.selection.empty();else if(window.getSelection){var e=window.getSelection();e&&e.removeAllRanges()}}},{key:"copySelected",value:function(){var e;try{e=document.execCommand("copy")}catch(t){e=!1}return e}},{key:"copyElement",value:function(t){e.selectElement(t);var n=e.copySelected();return n&&e.deselect(),n}},{key:"copyCustom",value:function(t){var n=document.createElement("textarea");n.style.position="fixed",n.style.top="0",n.style.left="0",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.value=t,document.body.appendChild(n),n.select();var r=e.copySelected();return document.body.removeChild(n),r}}]),e}(),hd=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"copy",(function(){var e="string"==typeof o.props.data?o.props.data:JSON.stringify(o.props.data,null,2);dd.copyCustom(e),o.showTooltip()})),n(ar(o),"renderCopyButton",(function(){return s.createElement("button",{onClick:o.copy},s.createElement(pd,{title:dd.isSupported()?"Copied":"Not supported in your browser",open:o.state.tooltipShown},"Copy"))})),o.state={tooltipShown:!1},o}return or(r,[{key:"render",value:function(){return this.props.children({renderCopyButton:this.renderCopyButton})}},{key:"showTooltip",value:function(){var e=this;this.setState({tooltipShown:!0}),setTimeout((function(){e.setState({tooltipShown:!1})}),1500)}}]),r}(s.PureComponent),md=1;function vd(e,t){md=1;var n="";return n+='
',n+="",n+=wd(e,t),(n+="")+"
"}function gd(e){return void 0!==e?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""}function yd(e){return JSON.stringify(e).slice(1,-1)}function bd(e,t){return''+gd(e)+""}function xd(e){return''+e+""}function wd(e,t){var n=typeof e,r="";return null==e?r+=bd("null","token keyword"):e&&e.constructor===Array?(md++,r+=function(e,t){for(var n=md>t?"collapsed":"",r='').concat(xd("["),'
    '),o=!1,i=e.length,a=0;a
    ',r+=wd(e[a],t),a";return r+="
".concat(xd("]")),o||(r=xd("[ ]")),r}(e,t),md--):e&&e.constructor===Date?r+=bd('"'+e.toISOString()+'"',"token string"):"object"===n?(md++,r+=function(e,t){for(var n=md>t?"collapsed":"",r=Object.keys(e),o=r.length,i='').concat(xd("{"),'
    '),a=!1,s=0;s
    ',i+='"'+gd(l)+'": ',i+=wd(e[l],t),s"}return i+="
".concat(xd("}")),a||(i=xd("{ }")),i}(e,t),md--):"number"===n?r+=bd(e,"token number"):"string"===n?/^(http|https):\/\/[^\s]+$/.test(e)?r+=bd('"',"token string")+''+gd(yd(e))+""+bd('"',"token string"):r+=bd('"'+yd(e)+'"',"token string"):"boolean"===n&&(r+=bd(e,"token boolean")),r}var kd,Ed,Sd,_d,Od,Ad,Id,Cd=ga(od||(od=Ao(["\n .redoc-json code > .collapser {\n display: none;\n pointer-events: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n background-color: transparent;\n border: 0;\n color: #fff;\n font-family: ",";\n font-size: ",";\n padding-right: 6px;\n padding-left: 6px;\n padding-top: 0;\n padding-bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15px;\n height: 15px;\n position: absolute;\n top: 4px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n padding: 2px;\n &:focus {\n outline-color: #fff;\n outline-style: dotted;\n outline-width: 1px;\n }\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize})),Td=ka.div(kd||(kd=Ao(["\n &:hover > "," {\n opacity: 1;\n }\n"])),Lp),Rd=ka(function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a1){var i=o.map((function(e,n){return{value:t[e].summary||e,idx:n}})),a=t[o[e]],l=a.description;return s.createElement(Vd,null,s.createElement(Ud,null,s.createElement(zd,null,"Example"),this.props.renderDropdown({value:i[e].value,options:i,onChange:this.switchMedia,ariaLabel:"Example"})),s.createElement("div",null,l&&s.createElement(id,{source:l}),s.createElement(Nd,{example:a,mimeType:n})))}var c=t[o[0]];return s.createElement(Vd,null,c.description&&s.createElement(id,{source:c.description}),s.createElement(Nd,{example:c,mimeType:n}))}}]),r}(s.Component),Vd=ka.div(Dd||(Dd=Ao(["\n margin-top: 15px;\n"])));if(!s.useState)throw new Error("mobx-react-lite requires React with Hooks support");if(!an)throw new Error("mobx-react-lite@3 requires mobx at least version 6 to be available");function Wd(e){e()}var Hd=[];function Yd(e){return zt(Yn(e,t));var t}var Gd="undefined"==typeof FinalizationRegistry?void 0:FinalizationRegistry;function Qd(e){return{reaction:e,mounted:!1,changedBeforeMount:!1,cleanAt:Date.now()+Xd}}var Xd=1e4,Kd=Gd?function(e){var t=new Map,n=1,r=new e((function(e){var n=t.get(e);n&&(n.reaction.dispose(),t.delete(e))}));return{addReactionToTrack:function(e,o,i){var a=n++;return r.register(i,a,e),e.current=Qd(o),e.current.finalizationRegistryCleanupToken=a,t.set(a,e.current),e.current},recordReactionAsCommitted:function(e){r.unregister(e),e.current&&e.current.finalizationRegistryCleanupToken&&t.delete(e.current.finalizationRegistryCleanupToken)},forceCleanupTimerToRunNowForTests:function(){},resetCleanupScheduleForTests:function(){}}}(Gd):function(){var e,t=new Set;function n(){void 0===e&&(e=setTimeout(r,1e4))}function r(){e=void 0;var r=Date.now();t.forEach((function(e){var n=e.current;n&&r>=n.cleanAt&&(n.reaction.dispose(),e.current=null,t.delete(e))})),t.size>0&&n()}return{addReactionToTrack:function(e,r,o){var i;return e.current=Qd(r),i=e,t.add(i),n(),e.current},recordReactionAsCommitted:function(e){t.delete(e)},forceCleanupTimerToRunNowForTests:function(){e&&(clearTimeout(e),r())},resetCleanupScheduleForTests:function(){var n,r;if(t.size>0){try{for(var o=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}(t),i=o.next();!i.done;i=o.next()){var a=i.value,s=a.current;s&&(s.reaction.dispose(),a.current=null)}}catch(e){n={error:e}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(n)throw n.error}}t.clear()}e&&(clearTimeout(e),e=void 0)}}}(),Zd=Kd.addReactionToTrack,Jd=Kd.recordReactionAsCommitted,eh=(Kd.resetCleanupScheduleForTests,Kd.forceCleanupTimerToRunNowForTests,!1);function th(){return eh}function nh(e){return"observer"+e}var rh=function(){};function oh(e,t){if(void 0===t&&(t="observed"),th())return e();var n,r=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a}(s.useState(new rh),1)[0],o=(n=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a}((0,s.useState)(0),2)[1],(0,s.useCallback)((function(){n((function(e){return e+1}))}),Hd)),i=s.useRef(null);if(!i.current)var a=new yt(nh(t),(function(){l.mounted?o():l.changedBeforeMount=!0})),l=Zd(i,a,r);var c,u,p=i.current.reaction;if(s.useDebugValue(p,Yd),s.useEffect((function(){return Jd(i),i.current?(i.current.mounted=!0,i.current.changedBeforeMount&&(i.current.changedBeforeMount=!1,o())):(i.current={reaction:new yt(nh(t),(function(){o()})),mounted:!0,changedBeforeMount:!1,cleanAt:1/0},o()),function(){i.current.reaction.dispose(),i.current=null}}),[]),p.track((function(){try{c=e()}catch(e){u=e}})),u)throw u;return c}var ih=function(){return(ih=Object.assign||function(e){for(var t,n=1,r=arguments.length;n2?r-2:0),i=2;i"}function Ah(e){var t=this;if(!0===th())return e.call(this);dh(this,Eh,!1),dh(this,Sh,!1);var n=Oh(this),r=e.bind(this),o=!1,i=new yt(n+".render()",(function(){if(!o&&(o=!0,!0!==t[kh])){var e=!0;try{dh(t,Sh,!0),t[Eh]||s.Component.prototype.forceUpdate.call(t),e=!1}finally{dh(t,Sh,!1),e&&i.dispose()}}}));function a(){o=!1;var e=void 0,t=void 0;if(i.track((function(){try{t=function(e,t){var n=qe(e);try{return t()}finally{Ve(n)}}(!1,r)}catch(t){e=t}})),e)throw e;return t}return i.reactComponent=this,a[xh]=i,this.render=a,a.call(this)}function Ih(e,t){return th()&&console.warn("[mobx-react] It seems that a re-rendering of a React component is triggered while in static (server-side) mode. Please make sure components are rendered only once server-side."),this.state!==t||!ph(this.props,e)}function Ch(e,t){var n=uh("reactProp_"+t+"_valueHolder"),r=uh("reactProp_"+t+"_atomHolder");function o(){return this[r]||dh(this,r,K("reactive "+t)),this[r]}Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:function(){var e=!1;return it&&at&&(e=it(!0)),o.call(this).reportObserved(),it&&at&&at(e),this[n]},set:function(e){this[Sh]||ph(this[n],e)?dh(this,n,e):(dh(this,n,e),dh(this,Eh,!0),o.call(this).reportChanged(),dh(this,Eh,!1))}})}var Th,Rh,Ph,jh,Lh,Nh,Mh,Dh,Fh,zh,Uh,Bh,$h="function"==typeof Symbol&&Symbol.for,qh=$h?Symbol.for("react.forward_ref"):"function"==typeof s.forwardRef&&(0,s.forwardRef)((function(e){return null})).$$typeof,Vh=$h?Symbol.for("react.memo"):"function"==typeof s.memo&&(0,s.memo)((function(e){return null})).$$typeof;function Wh(e){if(!0===e.isMobxInjector&&console.warn("Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"),Vh&&e.$$typeof===Vh)throw new Error("Mobx observer: You are trying to use 'observer' on a function component wrapped in either another observer or 'React.memo'. The observer already applies 'React.memo' for you.");if(qh&&e.$$typeof===qh){var t=e.render;if("function"!=typeof t)throw new Error("render property of ForwardRef was not a function");return(0,s.forwardRef)((function(){var e=arguments;return(0,s.createElement)(sh,null,(function(){return t.apply(void 0,e)}))}))}return"function"!=typeof e||e.prototype&&e.prototype.render||e.isReactClass||Object.prototype.isPrototypeOf.call(s.Component,e)?_h(e):function(e,t){if(th())return e;var n,r,o,i=ih({forwardRef:!1},t),a=e.displayName||e.name,l=function(t,n){return oh((function(){return e(t,n)}),a)};return l.displayName=a,n=i.forwardRef?(0,s.memo)((0,s.forwardRef)(l)):(0,s.memo)(l),r=e,o=n,Object.keys(r).forEach((function(e){ah[e]||Object.defineProperty(o,e,Object.getOwnPropertyDescriptor(r,e))})),n.displayName=a,n}(e)}if(!s.Component)throw new Error("mobx-react requires React to be available");if(!Re)throw new Error("mobx-react requires mobx to be available");var Hh,Yh,Gh,Qh=ka(Zc)(Th||(Th=Ao(["\n button {\n background-color: transparent;\n border: 0;\n outline: 0;\n font-size: 13px;\n font-family: ",";\n cursor: pointer;\n padding: 0;\n color: ",";\n &:focus {\n font-weight: ",";\n }\n }\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.fontWeightBold}),Hc,(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.color})),Xh=ka.span(Rh||(Rh=Ao(["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"])),(function(e){return e.theme.typography.code.fontSize})),Kh=ka(Xh)(Ph||(Ph=Ao(["\n color: ",";\n"])),(function(e){return Qr(.1,e.theme.schema.typeNameColor)})),Zh=ka(Xh)(jh||(jh=Ao(["\n color: ",";\n"])),(function(e){return e.theme.schema.typeNameColor})),Jh=ka(Xh)(Lh||(Lh=Ao(["\n color: ",";\n word-break: break-word;\n"])),(function(e){return e.theme.schema.typeTitleColor})),em=Zh,tm=ka(Xh.withComponent("div"))(Nh||(Nh=Ao(["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"])),(function(e){return e.theme.schema.requireLabelColor}),(function(e){return e.theme.schema.labelsTextSize})),nm=ka(Xh)(Mh||(Mh=Ao(["\n color: ",";\n font-size: 13px;\n"])),(function(e){return e.theme.colors.warning.main})),rm=ka(Xh)(Dh||(Dh=Ao(["\n color: #0e7c86;\n &::before,\n &::after {\n font-weight: bold;\n }\n"]))),om=ka(Xh)(Fh||(Fh=Ao(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: ".concat(Qr(.95,t.colors.text.primary),";\n color: ").concat(Qr(.1,t.colors.text.primary),";\n\n padding: 0 ").concat(t.spacing.unit,"px;\n border: 1px solid ").concat(Qr(.9,t.colors.text.primary),";\n font-family: ").concat(t.typography.code.fontFamily,";\n}")}),Ea("ExampleValue")),im=ka(om)(zh||(zh=Ao([""]))),am=ka(Xh)(Uh||(Uh=Ao(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: ".concat(Qr(.95,t.colors.primary.light),";\n color: ").concat(Qr(.1,t.colors.primary.main),";\n\n margin: 0 ").concat(t.spacing.unit,"px;\n padding: 0 ").concat(t.spacing.unit,"px;\n border: 1px solid ").concat(Qr(.9,t.colors.primary.main),";\n font-family: ").concat(t.typography.code.fontFamily,";\n}")}),Ea("ConstraintItem")),sm=ka.button(Bh||(Bh=Ao(["\n background-color: transparent;\n border: 0;\n color: ",";\n margin-left: ","px;\n border-radius: 2px;\n cursor: pointer;\n outline-color: ",";\n font-size: 12px;\n"])),(function(e){return e.theme.colors.text.secondary}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.colors.text.secondary})),lm=(r(2479),ka.div(Hh||(Hh=Ao(["\n ",";\n ","\n"])),Qf,(function(e){return e.compact?"":"margin: 1em 0"}))),cm=Wh(Yh=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.externalDocs;return e&&e.url?s.createElement(lm,{compact:this.props.compact},s.createElement("a",{href:e.url},e.description||e.url)):null}}]),n}(s.Component))||Yh,um=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;al,p=l?o?"… ".concat(n.length-l," more"):"Hide":"";return s.createElement("div",null,s.createElement(Xh,null,"array"===r?bo("enumArray"):""," ",1===n.length?bo("enumSingleValue"):bo("enum"),":")," ",c.map((function(e,t){var n=a?e:JSON.stringify(e);return s.createElement(s.Fragment,{key:t},s.createElement(om,null,n)," ")})),u?s.createElement(dm,{onClick:function(){e.toggle()}},p):null)}}]),r}(s.PureComponent);n(um,"contextType",La);var pm,fm,dm=ka.span(Gh||(Gh=Ao(["\n color: ",";\n vertical-align: middle;\n font-size: 13px;\n line-height: 20px;\n padding: 0 5px;\n cursor: pointer;\n"])),(function(e){return e.theme.colors.primary.main})),hm=ka(Xf)(pm||(pm=Ao(["\n margin: 2px 0;\n"]))),mm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.extensions;return s.createElement(La.Consumer,null,(function(t){return s.createElement(s.Fragment,null,t.showExtensions&&Object.keys(e).map((function(t){return s.createElement(hm,{key:t},s.createElement(Xh,null," ",t.substring(2),": ")," ",s.createElement(im,null,"string"==typeof e[t]?e[t]:JSON.stringify(e[t])))})))}))}}]),n}(s.PureComponent),vm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return 0===this.props.constraints.length?null:s.createElement("span",null," ",this.props.constraints.map((function(e){return s.createElement(am,{key:e}," ",e," ")})))}}]),n}(s.PureComponent),gm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){if(void 0===this.props.value)return null;var e=this.props.raw?this.props.value:JSON.stringify(this.props.value);return s.createElement("div",null,s.createElement(Xh,null," ",this.props.label," ")," ",s.createElement(om,null,e))}}]),n}(s.PureComponent),ym=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a"," "),p.title&&!c&&s.createElement(Jh,null," (",p.title,") "),s.createElement(vm,{constraints:p.constraints}),p.pattern&&!u&&s.createElement(s.Fragment,null,s.createElement(rm,null,o||p.pattern.length<45?p.pattern:"".concat(p.pattern.substr(0,45),"...")),p.pattern.length>45&&s.createElement(sm,{onClick:this.togglePattern},o?"Hide pattern":"Show pattern")),p.isCircular&&s.createElement(nm,null," ",bo("recursive")," ")),h&&s.createElement("div",null,s.createElement(Yc,{type:"warning"}," ",bo("deprecated")," ")),s.createElement(gm,{raw:v,label:bo("default")+":",value:p.default}),!r&&s.createElement(um,{type:p.type,values:p.enum})," ",g,s.createElement(mm,{extensions:a(a({},n.extensions),p.extensions)}),s.createElement("div",null,s.createElement(id,{compact:!0,source:f})),p.externalDocs&&s.createElement(cm,{externalDocs:p.externalDocs,compact:!0}),r&&r(this.props)||null,n.const&&s.createElement(gm,{label:bo("const")+":",value:n.const})||null)}}]),r}(s.PureComponent);function bm(e){var t=e.field;return t.examples?s.createElement(s.Fragment,null,s.createElement(Xh,null," ",bo("examples"),": "),s.createElement(Km,null,Object.values(t.examples).map((function(e,n){return s.createElement("li",{key:n},s.createElement(om,null,xm(t,e.value))," - ",e.summary||e.description)})))):null}function xm(e,t){return e.in?decodeURIComponent(function(e,t){var r=e.name,o=e.style,i=e.explode,a=void 0!==i&&i,s=e.serializationMime;if(s)switch(e.in){case"path":case"header":return hs(t,s);case"cookie":case"query":return"".concat(r,"=").concat(hs(t,s));default:return console.warn("Unexpected parameter location: "+e.in),""}if(!o)return console.warn("Missing style attribute or content for parameter ".concat(r)),"";switch(e.in){case"path":return function(e,t,r,o){var i=r?"*":"",a="";"label"===t?a=".":"matrix"===t&&(a=";");var s="__redoc_param_name__";return ns.parse("{".concat(a).concat(s).concat(i,"}")).expand(n({},s,o)).replace(/__redoc_param_name__/g,e)}(r,o,a,t);case"query":return function(e,t,n,r){switch(t){case"form":return ds(e,n,r);case"spaceDelimited":return Array.isArray(r)?n?ds(e,n,r):"".concat(e,"=").concat(r.join("%20")):(console.warn("The style spaceDelimited is only applicable to arrays"),"");case"pipeDelimited":return Array.isArray(r)?n?ds(e,n,r):"".concat(e,"=").concat(r.join("|")):(console.warn("The style pipeDelimited is only applicable to arrays"),"");case"deepObject":return!n||Array.isArray(r)||"object"!=typeof r?(console.warn("The style deepObject is only applicable for objects with explode=true"),""):fs(r,e);default:return console.warn("Unexpected style for query: "+t),""}}(r,o,a,t);case"header":return function(e,t,r){switch(e){case"simple":var o=t?"*":"",i="__redoc_param_name__",a=ns.parse("{".concat(i).concat(o,"}"));return decodeURIComponent(a.expand(n({},i,r)));default:return console.warn("Unexpected style for header: "+e),""}}(o,a,t);case"cookie":return function(e,t,n,r){switch(t){case"form":return ds(e,n,r);default:return console.warn("Unexpected style for cookie: "+t),""}}(r,o,a,t);default:return console.warn("Unexpected parameter location: "+e.in),""}}(e,t)):t}n(ym,"contextType",La);var wm,km,Em,Sm,_m,Om,Am,Im,Cm,Tm,Rm,Pm,jm,Lm,Nm,Mm,Dm,Fm,zm,Um,Bm,$m,qm,Vm,Wm,Hm,Ym,Gm,Qm,Xm,Km=ka.ul(fm||(fm=Ao(["\n margin-top: 1em;\n padding-left: 0;\n list-style-position: inside;\n"]))),Zm=ka.div(wm||(wm=Ao(["\n padding-left: ","px;\n"])),(function(e){return 2*e.theme.spacing.unit})),Jm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.schema.items,t=ys(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;return{type:"array",minItems:e,maxItems:t}}(e.schema.minItems,e.schema.maxItems));return s.createElement("div",null,s.createElement(au,null," Array (",t,")"),s.createElement(Zm,null,s.createElement(iv,ir({},this.props,{schema:e}))),s.createElement(su,null))}}]),n}(s.PureComponent),ev=Wh(km=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;an[t.value]?1:-1}))}}},{key:"render",value:function(){var e=this.props,t=e.parent,n=e.enumValues;if(void 0===t.oneOf)return null;var r=t.oneOf.map((function(e,t){return{value:e.title,idx:t}})),o=r[t.activeOneOf].value;return this.sortOptions(r,n),s.createElement(Hu,{value:o,options:r,onChange:this.changeActiveChild,ariaLabel:"Example"})}}]),r}(s.Component))||Em,nv=Wh((Om=_m=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"parentSchema",get:function(){return this.props.discriminator.parentSchema}},{key:"render",value:function(){var e=this,t=this.props,n=t.schema.fields,r=void 0===n?[]:n,o=t.showTitle,i=t.discriminator,a=this.props.skipReadOnly||this.props.skipWriteOnly?r.filter((function(t){return!(e.props.skipReadOnly&&t.schema.readOnly||e.props.skipWriteOnly&&t.schema.writeOnly)})):r,l=this.context.expandSingleSchemaField&&1===a.length;return s.createElement(nu,null,o&&s.createElement(Qc,null,this.props.schema.title),s.createElement("tbody",null,so(a,(function(t,n){return s.createElement(ev,{key:t.name,isLast:n,field:t,expandByDefault:l,renderDiscriminatorSwitch:i&&i.fieldName===t.name&&function(){return s.createElement(tv,{parent:e.parentSchema,enumValues:t.schema.enum})}||void 0,className:t.expanded?"expanded":void 0,showExamples:!1,skipReadOnly:e.props.skipReadOnly,skipWriteOnly:e.props.skipWriteOnly,showTitle:e.props.showTitle})}))))}}]),n}(s.Component),n(_m,"contextType",La),Sm=Om))||Sm,rv=Wh(Am=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),lv={oauth2:"OAuth2",apiKey:"API Key",http:"HTTP",openIdConnect:"OpenID Connect"},cv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.type,n=e.flow;return s.createElement("tr",null,s.createElement("th",null," ",t," OAuth Flow "),s.createElement("td",null,"implicit"===t||"authorizationCode"===t?s.createElement("div",null,s.createElement("strong",null," Authorization URL: "),n.authorizationUrl):null,"password"===t||"clientCredentials"===t||"authorizationCode"===t?s.createElement("div",null,s.createElement("strong",null," Token URL: "),n.tokenUrl):null,n.refreshUrl&&s.createElement("div",null,s.createElement("strong",null," Refresh URL: "),n.refreshUrl),s.createElement("div",null,s.createElement("strong",null," Scopes: ")),s.createElement("ul",null,Object.keys(n.scopes||{}).map((function(e){return s.createElement("li",{key:e},s.createElement("code",null,e)," - ",s.createElement(id,{inline:!0,source:n.scopes[e]||""}))})))))}}]),n}(s.PureComponent),uv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return this.props.securitySchemes.schemes.map((function(e){return s.createElement(tc,{id:e.sectionId,key:e.id},s.createElement(oc,null,s.createElement(ec,null,s.createElement(lc,null,s.createElement(Ec,{to:e.sectionId}),e.id),s.createElement(id,{source:e.description||""}),s.createElement(Xf,null,s.createElement("table",{className:"security-details"},s.createElement("tbody",null,s.createElement("tr",null,s.createElement("th",null," Security Scheme Type "),s.createElement("td",null," ",lv[e.type]||e.type," ")),e.apiKey?s.createElement("tr",null,s.createElement("th",null," ",(t=e.apiKey.in||"").charAt(0).toUpperCase()+t.slice(1)," parameter name:"),s.createElement("td",null," ",e.apiKey.name," ")):e.http?[s.createElement("tr",{key:"scheme"},s.createElement("th",null," HTTP Authorization Scheme "),s.createElement("td",null," ",e.http.scheme," ")),"bearer"===e.http.scheme&&e.http.bearerFormat&&s.createElement("tr",{key:"bearer"},s.createElement("th",null," Bearer format "),s.createElement("td",null,' "',e.http.bearerFormat,'" '))]:e.openId?s.createElement("tr",null,s.createElement("th",null," Connect URL "),s.createElement("td",null,s.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:e.openId.connectUrl},e.openId.connectUrl))):e.flows?Object.keys(e.flows).map((function(t){return s.createElement(cv,{key:t,type:t,flow:e.flows[t]})})):null))))));var t}))}}]),n}(s.PureComponent),pv=function(){function e(t,r){var o=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];nr(this,e),n(this,"menu",void 0),n(this,"spec",void 0),n(this,"rawOptions",void 0),n(this,"options",void 0),n(this,"search",void 0),n(this,"marker",new Fs),n(this,"scroll",void 0),n(this,"disposer",null),this.rawOptions=i,this.options=new ko(i,fv),this.scroll=new qf(this.options),Bf.updateOnHistory(Ms.currentId,this.scroll),this.spec=new Df(t,r,this.options),this.menu=new Bf(this.spec,this.scroll,Ms),this.options.disableSearch||(this.search=new Vf,a&&this.search.indexItems(this.menu.items),this.disposer=Gt(this.menu,"activeItemIdx",(function(e){o.updateMarkOnMenu(e.newValue)})))}var t;return or(e,[{key:"onDidMount",value:function(){this.menu.updateOnHistory(),this.updateMarkOnMenu(this.menu.activeItemIdx)}},{key:"dispose",value:function(){this.scroll.dispose(),this.menu.dispose(),this.search&&this.search.dispose(),null!=this.disposer&&this.disposer()}},{key:"toJS",value:(t=Fa(Ua().mark((function e(){return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(e.t0={activeItemIdx:this.menu.activeItemIdx},e.t1={url:this.spec.parser.specUrl,data:this.spec.parser.spec},!this.search){e.next=8;break}return e.next=5,this.search.toJS();case 5:e.t2=e.sent,e.next=9;break;case 8:e.t2=void 0;case 9:return e.t3=e.t2,e.t4=this.rawOptions,e.abrupt("return",{menu:e.t0,spec:e.t1,searchIndex:e.t3,options:e.t4});case 12:case"end":return e.stop()}}),e,this)}))),function(){return t.apply(this,arguments)})},{key:"updateMarkOnMenu",value:function(e){for(var t=Math.max(0,e),n=Math.min(this.menu.flatItems.length,t+5),r=[],o=t;o1?zr(.1,n.sidebar.backgroundColor):1===e?zr(.05,n.sidebar.backgroundColor):""}var Iv,Cv,Tv,Rv,Pv,jv,Lv,Nv,Mv,Dv,Fv,zv,Uv,Bv=ka.ul(qm||(qm=Ao(["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"])),(function(e){return e.expanded?"":"display: none;"})),$v=ka.li(Vm||(Vm=Ao(["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"])),(function(e){return 0===e.depth?"margin-top: 15px":""})),qv={0:ga(Wm||(Wm=Ao(["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "])),(function(e){return e.theme.sidebar.groupItems.textTransform}),(function(e){return e.theme.sidebar.textColor})),1:ga(Hm||(Hm=Ao(["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "])),(function(e){return e.theme.sidebar.level1Items.textTransform}),(function(e){return e.theme.sidebar.activeTextColor})),2:ga(Ym||(Ym=Ao(["\n color: ",";\n "])),(function(e){return e.theme.sidebar.textColor}))},Vv=ka.label.attrs((function(e){return{role:"menuitem",className:_v()("-depth"+e.depth,{active:e.active})}}))(Gm||(Gm=Ao(["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.active?e.theme.sidebar.activeTextColor:e.theme.sidebar.textColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.depth,n=e.type,r=e.theme;return"section"===n&&t>1&&"padding-left: "+8*r.spacing.unit+"px;"||""}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return qv[e.depth]}),(function(e){return e.active?Av(e.depth,e):""}),(function(e){return e.deprecated&&Gc||""}),(function(e){return Av(e.depth,e)}),Hc,(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.color})),Wv=ka.span(Qm||(Qm=Ao(["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.width?e.width:"auto"})),Hv=ka.div(Xm||(Xm=Ao(["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-size: 0.8em;\n margin-top: ".concat(2*t.spacing.unit,"px;\n padding: 0 ").concat(4*t.spacing.unit,"px;\n text-align: left;\n\n opacity: 0.7;\n\n a,\n a:visited,\n a:hover {\n color: ").concat(t.sidebar.textColor," !important;\n border-top: 1px solid ").concat(zr(.1,t.sidebar.backgroundColor),";\n padding: ").concat(t.spacing.unit,"px 0;\n display: block;\n }\n")})),Yv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.name,n=e.opened,r=e.className,o=e.onClick,i=e.httpVerb,a=e.deprecated;return s.createElement(Gv,{className:r,onClick:o||void 0},s.createElement(Xv,{type:i},Os(i)),s.createElement(Hc,{size:"1.5em",direction:n?"down":"right",float:"left"}),s.createElement(Qv,{deprecated:a},t),a?s.createElement(Yc,{type:"warning"}," ",bo("deprecated")," "):null)}}]),n}(s.PureComponent),Gv=ka.button(Iv||(Iv=Ao(["\n border: 0;\n width: 100%;\n text-align: left;\n & > * {\n vertical-align: middle;\n }\n\n "," {\n polygon {\n fill: ",";\n }\n }\n"])),Hc,(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.colors.gray[100])})),Qv=ka.span(Cv||(Cv=Ao(["\n text-decoration: ",";\n margin-right: 8px;\n"])),(function(e){return e.deprecated?"line-through":"none"})),Xv=ka(Ov)(Tv||(Tv=Ao(["\n margin: 0px 5px 0px 0px;\n"]))),Kv=ka(Yv)(Rv||(Rv=Ao(["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: ",";\n cursor: pointer;\n outline-color: ",";\n"])),(function(e){return e.theme.colors.gray[100]}),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.colors.gray[100])})),Zv=ka.div(Pv||(Pv=Ao(["\n padding: 10px 25px;\n background-color: ",";\n margin-bottom: 5px;\n margin-top: 5px;\n"])),(function(e){return e.theme.colors.gray[50]})),Jv=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a span {\n color: ",";\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.text.primary})),sg=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"toggle",(function(){o.setState({expanded:!o.state.expanded})})),o.state={expanded:!1},o}return or(r,[{key:"render",value:function(){var e=this,t=this.props,n=t.operation,r=t.inverted,o=t.hideHostname,i=this.state.expanded;return s.createElement(La.Consumer,null,(function(t){return s.createElement(eg,null,s.createElement(ng,{onClick:e.toggle,expanded:i,inverted:r},s.createElement(rg,{type:n.httpVerb,compact:e.props.compact},n.httpVerb),s.createElement(tg,null,n.path),s.createElement(Hc,{float:"right",color:r?"black":"white",size:"20px",direction:i?"up":"down",style:{marginRight:"-25px"}})),s.createElement(og,{expanded:i,"aria-hidden":!i},n.servers.map((function(e){var r=t.expandDefaultServerVariables?function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.replace(/(?:{)([\w-.]+)(?:})/g,(function(e,n){return t[n]&&t[n].default||e}))}(e.url,e.variables):e.url,i=function(e){try{return mo(e).pathname}catch(t){return e}}(r);return s.createElement(ig,{key:r},s.createElement(id,{source:e.description||"",compact:!0}),s.createElement(Jv,null,s.createElement(ag,null,s.createElement("span",null,o||t.hideHostname?"/"===i?"":i:r),n.path)))}))))}))}}]),r}(s.Component),lg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.place,n=e.parameters;return n&&n.length?s.createElement("div",{key:t},s.createElement(uc,null,t," Parameters"),s.createElement(nu,null,s.createElement("tbody",null,so(n,(function(e,t){return s.createElement(ev,{key:e.name,isLast:t,field:e,showExamples:!0})}))))):null}}]),n}(s.PureComponent),cg=Wh(Uv=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0?ug:[],a=t&&t.content,l=t&&t.description;return s.createElement(s.Fragment,null,i.map((function(e){return s.createElement(lg,{key:e,place:e,parameters:o[e]})})),a&&s.createElement(dg,{content:a,description:l}))}}]),n}(s.PureComponent);function fg(e){return s.createElement(uc,{key:"header"},"Request Body schema: ",s.createElement(Wf,e))}function dg(e){var t=e.content,n=e.description;return s.createElement(cg,{content:t,renderDropdown:fg},(function(e){var t=e.schema;return s.createElement(s.Fragment,null,void 0!==n&&s.createElement(id,{source:n}),s.createElement(iv,{skipReadOnly:!0,key:"schema",schema:t}))}))}var hg,mg,vg,gg,yg,bg,xg,wg,kg,Eg,Sg,_g,Og,Ag,Ig,Cg,Tg,Rg,Pg,jg,Lg,Ng,Mg,Dg,Fg,zg,Ug,Bg,$g,qg,Vg,Wg,Hg,Yg,Gg,Qg,Xg,Kg,Zg,Jg,ey,ty,ny,ry,oy,iy,ay,sy,ly,cy,uy,py,fy=ka(function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.title,n=e.type,r=e.empty,o=e.code,i=e.opened,a=e.className,l=e.onClick;return s.createElement("button",{className:a,onClick:!r&&l||void 0,"aria-expanded":i,disabled:r},!r&&s.createElement(Hc,{size:"1.5em",color:n,direction:i?"down":"right",float:"left"}),s.createElement(my,null,o," "),s.createElement(id,{compact:!0,inline:!0,source:t}))}}]),n}(s.PureComponent))(hg||(hg=Ao(["\n display: block;\n border: 0;\n width: 100%;\n text-align: left;\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n &:focus {\n outline: auto;\n outline-color: ",";\n }\n ",";\n"])),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.theme.colors.responses[e.type].backgroundColor}),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.empty?'\ncursor: default;\n&::before {\n content: "—";\n font-weight: bold;\n width: 1.5em;\n text-align: center;\n display: inline-block;\n vertical-align: top;\n}\n&:focus {\n outline: 0;\n}\n':""})),dy=ka.div(mg||(mg=Ao(["\n padding: 10px;\n"]))),hy=ka(uc.withComponent("caption"))(vg||(vg=Ao(["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"]))),my=ka.strong(gg||(gg=Ao(["\n vertical-align: top;\n"]))),vy=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.headers;return void 0===e||0===e.length?null:s.createElement(nu,null,s.createElement(hy,null," Response Headers "),s.createElement("tbody",null,so(e,(function(e,t){return s.createElement(ev,{isLast:t,key:e.name,field:e,showExamples:!0})}))))}}]),n}(s.PureComponent),gy=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&" (",e.scopes.map((function(e){return s.createElement(wy,{key:e},e)})),e.scopes.length>0&&") ")})))}}]),n}(s.PureComponent),_y=ka.div(Eg||(Eg=Ao(["\n flex: 1 1 auto;\n"]))),Oy=ka.div(Sg||(Sg=Ao(["\n width: ",";\n ","\n"])),(function(e){return e.theme.schema.defaultDetailsWidth}),wa("small")(_g||(_g=Ao(["\n margin-top: 10px;\n "])))),Ay=ka(uc)(Og||(Og=Ao(["\n display: inline-block;\n margin: 0;\n"]))),Iy=ka.div(Ag||(Ag=Ao(["\n width: 100%;\n display: flex;\n margin: 1em 0;\n\n ","\n"])),wa("small")(Ig||(Ig=Ao(["\n flex-direction: column;\n "])))),Cy=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.securities;return e.length?s.createElement(Iy,null,s.createElement(_y,null,s.createElement(Ay,null,"Authorizations: ")),s.createElement(Oy,null,e.map((function(e,t){return s.createElement(Sy,{key:t,security:e})})))):null}}]),n}(s.PureComponent),Ty=Wh(Cg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.operation,t=e.description,n=e.externalDocs,r=!(!t&&!n);return s.createElement(Zv,null,r&&s.createElement(Ry,null,void 0!==t&&s.createElement(id,{source:t}),n&&s.createElement(cm,{externalDocs:n})),s.createElement(sg,{operation:this.props.operation,inverted:!0,compact:!0}),s.createElement(mm,{extensions:e.extensions}),s.createElement(Cy,{securities:e.security}),s.createElement(pg,{parameters:e.parameters,body:e.requestBody}),s.createElement(xy,{responses:e.responses,isCallback:e.isCallback}))}}]),n}(s.Component))||Cg,Ry=ka.div(Tg||(Tg=Ao(["\n margin-bottom: ","px;\n"])),(function(e){return 3*e.theme.spacing.unit})),Py=Wh(Rg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0})))return null;var r=n.map((function(e,t){return{value:"".concat(e.httpVerb.toUpperCase(),": ").concat(e.name),idx:t}}));return s.createElement("div",null,s.createElement(cc,null," Callback payload samples "),s.createElement(Uy,null,s.createElement(Ny,{items:n,renderDropdown:this.renderDropdown,label:"Callback",options:r},(function(t){return s.createElement(Dy,{key:"callbackPayloadSample",callback:t,renderDropdown:e.renderDropdown})}))))}}]),r}(s.Component),n(Dg,"contextType",La),Mg=Fg))||Mg,Uy=ka.div(zg||(zg=Ao(["\n background: ",";\n padding: ","px;\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),By=Wh(($g=Bg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0,n=1===e.length&&this.context.hideSingleRequestSampleTab;return t&&s.createElement("div",null,s.createElement(cc,null," Request samples "),s.createElement(Pp,{defaultIndex:0},s.createElement(yp,{hidden:n},e.map((function(e){return s.createElement(kp,{key:e.lang+"_"+(e.label||"")},void 0!==e.label?e.label:e.lang)}))),e.map((function(e){return s.createElement(Ap,{key:e.lang+"_"+(e.label||"")},Ml(e)?s.createElement("div",null,s.createElement(My,{content:e.requestBodyContent})):s.createElement(jd,{lang:e.lang,source:e.source}))}))))||null}}]),r}(s.Component),n(Bg,"contextType",La),Ug=$g))||Ug,$y=Wh(qg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&s.createElement("div",null,s.createElement(cc,null," Response samples "),s.createElement(Pp,{defaultIndex:0},s.createElement(yp,null,e.map((function(e){return s.createElement(kp,{className:"tab-"+e.type,key:e.code},e.code)}))),e.map((function(e){return s.createElement(Ap,{key:e.code},s.createElement("div",null,s.createElement(My,{content:e.content})))}))))||null}}]),r}(s.Component))||qg,qy=ka(oc)(Vg||(Vg=Ao(["\n backface-visibility: hidden;\n contain: content;\n overflow: hidden;\n"]))),Vy=ka.div(Wg||(Wg=Ao(["\n margin-bottom: ","px;\n"])),(function(e){return 6*e.theme.spacing.unit})),Wy=Wh(Hg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.operation,t=e.name,n=e.description,r=e.deprecated,o=e.externalDocs,i=e.isWebhook,a=!(!n&&!o);return s.createElement(La.Consumer,null,(function(l){return s.createElement(qy,null,s.createElement(ec,null,s.createElement(lc,null,s.createElement(Ec,{to:e.id}),t," ",r&&s.createElement(Yc,{type:"warning"}," Deprecated "),i&&s.createElement(Yc,{type:"primary"}," Webhook ")),l.pathInMiddlePanel&&!i&&s.createElement(sg,{operation:e,inverted:!0}),a&&s.createElement(Vy,null,void 0!==n&&s.createElement(id,{source:n}),o&&s.createElement(cm,{externalDocs:o})),s.createElement(mm,{extensions:e.extensions}),s.createElement(Cy,{securities:e.security}),s.createElement(pg,{parameters:e.parameters,body:e.requestBody}),s.createElement(xy,{responses:e.responses}),s.createElement(jy,{callbacks:e.callbacks})),s.createElement(rc,null,!l.pathInMiddlePanel&&!i&&s.createElement(sg,{operation:e}),s.createElement(By,{operation:e}),s.createElement($y,{operation:e}),s.createElement(zy,{callbacks:e.callbacks})))}))}}]),n}(s.Component))||Hg,Hy=Wh(Yg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.items;return 0===e.length?null:e.map((function(e){return s.createElement(Yy,{key:e.id,item:e})}))}}]),n}(s.Component))||Yg,Yy=Wh(Gg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e,t=this.props.item;switch(t.type){case"group":e=null;break;case"tag":case"section":e=s.createElement(Qy,this.props);break;case"operation":e=s.createElement(Xy,{item:t});break;default:e=s.createElement(Qy,this.props)}return s.createElement(s.Fragment,null,e&&s.createElement(tc,{id:t.id,underlined:"operation"===t.type},e),t.items&&s.createElement(Hy,{items:t.items}))}}]),n}(s.Component))||Gg,Gy=function(e){return s.createElement(ec,{compact:!0},e)},Qy=Wh(Qg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.item,t=e.name,n=e.description,r=e.externalDocs,o=2===e.level?lc:sc;return s.createElement(s.Fragment,null,s.createElement(oc,null,s.createElement(ec,{compact:!1},s.createElement(o,null,s.createElement(Ec,{to:this.props.item.id}),t))),s.createElement(Ev,{source:n||"",htmlWrap:Gy}),r&&s.createElement(oc,null,s.createElement(ec,null,s.createElement(cm,{externalDocs:r}))))}}]),n}(s.Component))||Qg,Xy=Wh(Xg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement(Wy,{operation:this.props.item})}}]),n}(s.Component))||Xg,Ky=Wh(Kg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&t.items.length>0&&s.createElement(Hc,{float:"right",direction:t.expanded?"down":"right"})||null),!n&&t.items&&t.items.length>0&&s.createElement(Jy,{expanded:t.expanded,items:t.items,onActivate:this.props.onActivate}))}}]),r}(s.Component))||Kg,Zy=Wh(Zg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a.5?zr:Vr)(.1,t.sidebar.backgroundColor)}),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.sidebar.textColor})),_b=ka((function(e){return s.createElement("svg",{className:e.className,version:"1.1",viewBox:"0 0 1000 1000",x:"0px",xmlns:"http://www.w3.org/2000/svg",y:"0px"},s.createElement("path",{d:"M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"}))})).attrs({className:"search-icon"})(pb||(pb=Ao(["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"])),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.sidebar.textColor})),Ob=ka.div(fb||(fb=Ao(["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n \n li {\n background-color: inherit;\n }\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"])),(function(e){return e.theme.spacing.unit}),(function(e){var t=e.theme;return zr(.05,t.sidebar.backgroundColor)}),(function(e){return e.theme.sidebar.textColor}),(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)}),(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)}),Vv,(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)})),Ab=ka.i(db||(db=Ao(["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"])),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit})),Ib=(hb=(0,Ga.debounce)(400),Ya((mb=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"activeItemRef",null),n(ar(o),"clear",(function(){o.setState({results:[],term:"",activeItemIdx:-1}),o.props.marker.unmark()})),n(ar(o),"handleKeyDown",(function(e){if(27===e.keyCode&&o.clear(),40===e.keyCode&&(o.setState({activeItemIdx:Math.min(o.state.activeItemIdx+1,o.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(o.setState({activeItemIdx:Math.max(0,o.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){var t=o.state.results[o.state.activeItemIdx];if(t){var n=o.props.getItemById(t.meta);n&&o.props.onActivate(n)}}})),n(ar(o),"search",(function(e){var t=e.target.value;t.length<3?o.clearResults(t):o.setState({term:t},(function(){return o.searchCallback(o.state.term)}))})),o.state={results:[],term:"",activeItemIdx:-1},o}return or(r,[{key:"clearResults",value:function(e){this.setState({results:[],term:e}),this.props.marker.unmark()}},{key:"setResults",value:function(e,t){this.setState({results:e}),this.props.marker.mark(t)}},{key:"searchCallback",value:function(e){var t=this;this.props.search.search(e).then((function(n){t.setResults(n,e)}))}},{key:"render",value:function(){var e=this,t=this.state.activeItemIdx,n=this.state.results.map((function(t){return{item:e.props.getItemById(t.meta),score:t.score}}));return n.sort((function(e,t){return t.score-e.score})),s.createElement(Eb,{role:"search"},this.state.term&&s.createElement(Ab,{onClick:this.clear},"×"),s.createElement(_b,null),s.createElement(Sb,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...","aria-label":"Search",type:"text",onChange:this.search}),n.length>0&&s.createElement(wf,{options:{wheelPropagation:!1}},s.createElement(Ob,{"data-role":"search:results"},n.map((function(n,r){return s.createElement(Ky,{item:Object.create(n.item,{active:{value:r===t}}),onActivate:e.props.onActivate,withoutChildren:!0,key:n.item.id,"data-role":"search:result"})})))))}}]),r}(s.PureComponent)).prototype,"searchCallback",[Ga.bind,hb],Object.getOwnPropertyDescriptor(mb.prototype,"searchCallback"),mb.prototype),mb),Cb=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"componentDidMount",value:function(){this.props.store.onDidMount()}},{key:"componentWillUnmount",value:function(){this.props.store.dispose()}},{key:"render",value:function(){var e=this.props.store,t=e.spec,n=e.menu,r=e.options,o=e.search,i=e.marker,a=this.props.store;return s.createElement(xa,{theme:r.theme},s.createElement(mc,{value:a},s.createElement(Na,{value:r},s.createElement(xb,{className:"redoc-wrap"},s.createElement(bb,{menu:n,className:"menu-content"},s.createElement(kv,{info:t.info}),!r.disableSearch&&s.createElement(Ib,{search:o,marker:i,getItemById:n.getItemById,onActivate:n.activateAndScroll})||null,s.createElement(eb,{menu:n})),s.createElement(wb,{className:"api-content"},s.createElement(yv,{store:a}),s.createElement(Hy,{items:n.items})),s.createElement(kb,null)))))}}]),n}(s.Component);n(Cb,"propTypes",{store:ja.instanceOf(pv).isRequired});var Tb=function(e){var t=e.spec,n=e.specUrl,r=e.options,o=void 0===r?{}:r,i=e.onLoaded,a=void 0!==o.hideLoading,l=new ko(o);return s.createElement(Ia,null,s.createElement(gc,{spec:t,specUrl:n,options:o,onLoaded:i},(function(e){var t=e.loading,n=e.store;return t?a?null:s.createElement(Pa,{color:l.theme.colors.primary.main}):s.createElement(Cb,{store:n})})))};Ft({useProxies:"ifavailable"});var Rb="2.0.0-rc.55",Pb="68690d18";function jb(e){var t=function(e){for(var t={},n=e.attributes,r=0;r1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:eo("redoc"),i=arguments.length>3?arguments[3]:void 0;if(null===o)throw new Error('"element" argument is not provided and tag is not found on the page');"string"==typeof e?t=e:"object"==typeof e&&(n=e),(0,l.render)(s.createElement(Tb,{spec:n,onLoaded:i,specUrl:t,options:a(a({},r),jb(o))},["Loading..."]),o)}function Nb(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:eo("redoc"),n=arguments.length>2?arguments[2]:void 0,r=pv.fromJS(e);setTimeout((function(){(0,l.hydrate)(s.createElement(Cb,{store:r}),t,n)}),0)}!function(){var e=eo("redoc");if(e){var t=e.getAttribute("spec-url");t&&Lb(t,{},e)}}()}(),o}()})); +//# sourceMappingURL=redoc.standalone.js.map -

Owncast (0.0.7)

Download OpenAPI specification:Download

Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software. The following APIs represent the state in the development branch.

-

Authentication

AdminBasicAuth

The username for admin basic auth is admin and the password is the stream key.

-
Security Scheme Type HTTP
HTTP Authorization Scheme basic

AccessToken

3rd party integration auth where a service user must provide an access token.

-
Security Scheme Type HTTP
HTTP Authorization Scheme bearer

Admin

Admin operations requiring authentication.

-

Server status and broadcaster

Authorizations:

Responses

Response samples

Content type
application/json
{
  • "broadcaster": {
    },
  • "online": true,
  • "viewerCount": 3,
  • "overallPeakViewerCount": 4,
  • "sessionPeakViewerCount": 4,
  • "versionNumber": "0.0.3"
}

Disconnect Broadcaster

Disconnect the active inbound stream, if one exists, and terminate the broadcast.

-
Authorizations:

Responses

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Reset your YP registration key.

Used when there is a problem with your registration to the Owncast Directory via the YP APIs. This will reset your local registration key.

-
Authorizations:

Responses

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Return a list of currently connected clients

Return a list of currently connected clients with optional geo details.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Return recent log entries

Returns server logs.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    }
]

Return recent warning and error logs.

Return recent warning and error logs.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    }
]

Server Configuration

Get the current configuration of the Owncast server.

-
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "instanceDetails": {
    },
  • "ffmpegPath": "string",
  • "webServerPort": 0,
  • "rtmpServerPort": 0,
  • "s3": {
    },
  • "videoSettings": {
    },
  • "yp": {
    }
}

Chat messages, unfiltered.

Get a list of all chat messages with no filters applied.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Update the visibility of chat messages.

Pass an array of IDs you want to change the chat visibility of.

-
Authorizations:
Request Body schema: application/json
visible
boolean

Are these messages visible.

-
idArray
Array of strings

Responses

Request samples

Content type
application/json
{
  • "visible": true,
  • "idArray": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the stream key.

Set the stream key. Also used as the admin password.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the custom page content.

Set the custom page content using markdown.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
"# Welcome to my cool server!<br><br>I _hope_ you enjoy it."

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the stream title.

Set the title of the currently streaming content.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "Streaming my favorite game, Desert Bus."
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server name.

Set the name associated with your server. Often is your name, username or identity.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server summary.

Set the summary of your server's streaming content.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "The best in Desert Bus Streaming"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server logo.

Set the logo for your server. Path is relative to webroot.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "/img/mylogo.png"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server tags.

Set the tags displayed for your server and the categories you can show up in on the directory.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the ffmpeg binary path

Set the path for a specific copy of ffmpeg on your system.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "/home/owncast/ffmpeg"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the owncast web port.

Set the port the owncast web server should listen on.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": 8080
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the inbound rtmp server port.

Set the port where owncast service will listen for inbound broadcasts.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": 1935
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Mark if your stream is not safe for work

Mark if your stream can be consitered not safe for work. Used in different contexts, including the directory for filtering purposes.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": false
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set if this server supports the Owncast directory.

If set to true the server will attempt to register itself with the Owncast Directory. Off by default.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": true
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the public url of this owncast server.

Set the public url of this owncast server. Used for the directory and optional integrations.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the latency level for the stream.

Sets the latency level that determines how much video is buffered between the server and viewer. Less latency can end up with more buffering.

-
Authorizations:
Request Body schema: application/json
value
integer

The latency level

-

Responses

Request samples

Content type
application/json
{
  • "value": 4
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the configuration of your stream output.

Sets the detailed configuration for all of the stream variants you support.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the video codec.

Sets the specific video codec that will be used for video encoding. Some codecs will support hardware acceleration. Not all codecs will be supported for all systems.

-
Authorizations:
Request Body schema: application/json
value
string

The video codec to change to.

-

Responses

Request samples

Content type
application/json
{
  • "value": "libx264"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set your storage configration.

Sets your S3 storage provider configuration details to enable external storage.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": {
    }
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set your social handles.

Sets the external links to social networks and profiles.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Custom CSS styles to be used in the web front endpoints.

Save a string containing CSS to be inserted in to the web frontend page.

-
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "body { color: orange; background: black; }"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Viewers Over Time

Get the tracked viewer count over the collected period.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    }
]

Hardware Stats

Get the CPU, Memory and Disk utilization levels over the collected period.

-
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "cpu": [
    ],
  • "memory": [
    ],
  • "disk": [
    ]
}

Return all webhooks.

Return all of the configured webhooks for external events.

-
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "url": "string",
  • "events": [
    ],
  • "timestamp": "2019-08-24T14:15:22Z",
  • "lastUsed": "2019-08-24T14:15:22Z"
}

Set external action URLs.

Set a collection of external action URLs that are displayed in the UI.

-
Authorizations:
Request Body schema: application/json
Array ()
url
string

URL of the external action content.

-
title
string

The title to put on the external action button.

-
description
string

Optional additional description to display in the UI.

-
icon
string

The URL to an image to place on the external action button.

-
color
string

Optional color to use for drawing the action button.

-
openExternally
boolean

If set this action will open in a new browser tab instead of an internal modal.

-

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Delete a single webhook.

Delete a single webhook by its ID.

-
Authorizations:
Request Body schema: application/json
id
string

The webhook id to delete

-

Responses

Request samples

Content type
application/json
{
  • "id": "string"
}

Create a webhook.

Create a single webhook that acts on the requested events.

-
Authorizations:
Request Body schema: application/json
url
string

The url to post the events to.

-
events
Array of strings

The events to be notified about.

-

Responses

Request samples

Content type
application/json
{
  • "url": "string",
  • "events": [
    ]
}

Response samples

Content type
application/json
{
  • "name": "your new token",
  • "token": "zG2xO-mHTFnelCp5xaIkYEFWcPhoOswOSRmFC1BkI="
}

Chat

Endpoints related to the chat interface.

-

Historical Chat Messages

Used to get all chat messages prior to connecting to the websocket.

-

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Get Custom Emoji

Get a list of custom emoji that are supported in chat.

-

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Integrations

APIs built to allow 3rd parties to interact with an Owncast server.

-

Set the stream title.

Set the title of the currently streaming content.

-
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "Streaming my favorite game, Desert Bus."
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Send a user chat message.

Send a chat message on behalf of a user. Could be a bot name or a real user.

-
Authorizations:
Request Body schema: application/json
user
string

The user you want to send this message as.

-
body
string

The message text that will be sent as the user.

-

Responses

Request samples

Content type
application/json
{
  • "user": "string",
  • "body": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Send a system chat message.

Send a chat message on behalf of the system/server.

-
Authorizations:
Request Body schema: application/json
body
string

The message text that will be sent as the system user.

-

Responses

Request samples

Content type
application/json
{
  • "body": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Send a chat action.

Send an action that took place to the chat.

-
Authorizations:
Request Body schema: application/json
body
required
string

The message text that will be sent as the system user.

-
author
string

An optional user name that performed the action.

-

Responses

Request samples

Content type
application/json
{
  • "body": "rolled a 15 on the dice",
  • "author": "JohnSmith"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Create an access token.

Create a single access token that has access to the access scopes provided.

-
Authorizations:
Request Body schema: application/json
name
string

The human-readable name to give this access token.

-
scopes
Array of strings

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "name": "your new token",
  • "token": "zG2xO-mHTFnelCp5xaIkYEFWcPhoOswOSRmFC1BkI="
}

Delete an access token.

Delete a single access token.

-
Authorizations:
Request Body schema: application/json
token
string

The token to delete

-

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "deleted token"
}

Return all access tokens.

Return all of the available access tokens.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • "string"
]

Set external action URLs.

Set a collection of external action URLs that are displayed in the UI.

-
Authorizations:
Request Body schema: application/json
Array ()
url
string

URL of the external action content.

-
title
string

The title to put on the external action button.

-
description
string

Optional additional description to display in the UI.

-
icon
string

The URL to an image to place on the external action button.

-
color
string

Optional color to use for drawing the action button.

-
openExternally
boolean

If set this action will open in a new browser tab instead of an internal modal.

-

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Return a list of currently connected clients

Return a list of currently connected clients with optional geo details.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Historical Chat Messages

Used to get the backlog of chat messages.

-
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Update the visibility of chat messages.

Pass an array of IDs you want to change the chat visibility of.

-
Authorizations:
Request Body schema: application/json
visible
boolean

Are these messages visible.

-
idArray
Array of strings

Responses

Request samples

Content type
application/json
{
  • "visible": true,
  • "idArray": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Server

Information

The client configuration. Information useful for the user interface.

-

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "summary": "string",
  • "logo": "string",
  • "tags": [
    ],
  • "socialHandles": [],
  • "extraPageContent": "<p>This page is <strong>super</strong> cool!",
  • "version": "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
}

Current Status

This endpoint is used to discover when a server is broadcasting, the number of active viewers as well as other useful information for updating the user interface.

-

Responses

Response samples

Content type
application/json
{
  • "lastConnectTime": "2020-10-03T21:36:22-05:00",
  • "lastDisconnectTime": null,
  • "online": true,
  • "overallMaxViewerCount": 420,
  • "sessionMaxViewerCount": 12,
  • "viewerCount": 7
}

Yellow Pages Information

Information to be used in the Yellow Pages service, a global directory of Owncast servers.

-

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "logo": "string",
  • "nsfw": true,
  • "tags": [
    ],
  • "online": true,
  • "viewerCount": 0,
  • "overallMaxViewerCount": 0,
  • "sessionMaxViewerCount": 0,
  • "lastConnectTime": "2019-08-24T14:15:22Z"
}
+ " fill="currentColor">

Owncast (0.0.8-develop)

Download OpenAPI specification:Download

Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software. The following APIs represent the state in the development branch.

+

Authentication

AdminBasicAuth

The username for admin basic auth is admin and the password is the stream key.

+
Security Scheme Type HTTP
HTTP Authorization Scheme basic

AccessToken

3rd party integration auth where a service user must provide an access token.

+
Security Scheme Type HTTP
HTTP Authorization Scheme bearer

UserToken

3rd party integration auth where a service user must provide an access token.

+
Security Scheme Type API Key
Query parameter name: accessToken

Admin

Admin operations requiring authentication.

+

Server status and broadcaster

Authorizations:

Responses

Response samples

Content type
application/json
{
  • "broadcaster": {
    },
  • "online": true,
  • "viewerCount": 3,
  • "overallPeakViewerCount": 4,
  • "sessionPeakViewerCount": 4,
  • "versionNumber": "0.0.3"
}

Disconnect Broadcaster

Disconnect the active inbound stream, if one exists, and terminate the broadcast.

+
Authorizations:

Responses

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Reset your YP registration key.

Used when there is a problem with your registration to the Owncast Directory via the YP APIs. This will reset your local registration key.

+
Authorizations:

Responses

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Return a list of currently connected clients

Return a list of currently connected clients with optional geo details.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Return a list of currently connected clients

Return a list of currently connected clients with optional geo details.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Return recent log entries

Returns server logs.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    }
]

Return recent warning and error logs.

Return recent warning and error logs.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    },
  • {
    }
]

Server Configuration

Get the current configuration of the Owncast server.

+
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "instanceDetails": {
    },
  • "ffmpegPath": "string",
  • "webServerPort": 0,
  • "rtmpServerPort": 0,
  • "s3": {
    },
  • "videoSettings": {
    },
  • "yp": {
    }
}

Chat messages, unfiltered.

Get a list of all chat messages with no filters applied.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Update the visibility of chat messages.

Pass an array of IDs you want to change the chat visibility of.

+
Authorizations:
Request Body schema: application/json
visible
boolean

Are these messages visible.

+
idArray
Array of strings

Responses

Request samples

Content type
application/json
{
  • "visible": true,
  • "idArray": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Enable or disable a single user.

Enable or disable a single user. Disabling will also hide all the user's chat messages.

+
Authorizations:
Request Body schema: application/json
userId
string

User ID to act upon.

+
enabled
boolean

Set the enabled state of this user.

+

Responses

Request samples

Content type
application/json
{
  • "userId": "yklw5Imng",
  • "enabled": true
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the stream key.

Set the stream key. Also used as the admin password.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the custom page content.

Set the custom page content using markdown.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
"# Welcome to my cool server!<br><br>I _hope_ you enjoy it."

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the stream title.

Set the title of the currently streaming content.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "Streaming my favorite game, Desert Bus."
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server name.

Set the name associated with your server. Often is your name, username or identity.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server summary.

Set the summary of your server's streaming content.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "The best in Desert Bus Streaming"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server logo.

Set the logo for your server. Path is relative to webroot.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "/img/mylogo.png"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the server tags.

Set the tags displayed for your server and the categories you can show up in on the directory.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the ffmpeg binary path

Set the path for a specific copy of ffmpeg on your system.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "/home/owncast/ffmpeg"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the owncast web port.

Set the port the owncast web server should listen on.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": 8080
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the inbound rtmp server port.

Set the port where owncast service will listen for inbound broadcasts.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": 1935
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Mark if your stream is not safe for work

Mark if your stream can be consitered not safe for work. Used in different contexts, including the directory for filtering purposes.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": false
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set if this server supports the Owncast directory.

If set to true the server will attempt to register itself with the Owncast Directory. Off by default.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": true
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the public url of this owncast server.

Set the public url of this owncast server. Used for the directory and optional integrations.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the latency level for the stream.

Sets the latency level that determines how much video is buffered between the server and viewer. Less latency can end up with more buffering.

+
Authorizations:
Request Body schema: application/json
value
integer

The latency level

+

Responses

Request samples

Content type
application/json
{
  • "value": 4
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the configuration of your stream output.

Sets the detailed configuration for all of the stream variants you support.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set the video codec.

Sets the specific video codec that will be used for video encoding. Some codecs will support hardware acceleration. Not all codecs will be supported for all systems.

+
Authorizations:
Request Body schema: application/json
value
string

The video codec to change to.

+

Responses

Request samples

Content type
application/json
{
  • "value": "libx264"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set your storage configration.

Sets your S3 storage provider configuration details to enable external storage.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": {
    }
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Set your social handles.

Sets the external links to social networks and profiles.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Custom CSS styles to be used in the web front endpoints.

Save a string containing CSS to be inserted in to the web frontend page.

+
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "body { color: orange; background: black; }"
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Viewers Over Time

Get the tracked viewer count over the collected period.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    }
]

Hardware Stats

Get the CPU, Memory and Disk utilization levels over the collected period.

+
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "cpu": [
    ],
  • "memory": [
    ],
  • "disk": [
    ]
}

Return all webhooks.

Return all of the configured webhooks for external events.

+
Authorizations:

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "url": "string",
  • "events": [
    ],
  • "timestamp": "2019-08-24T14:15:22Z",
  • "lastUsed": "2019-08-24T14:15:22Z"
}

Set external action URLs.

Set a collection of external action URLs that are displayed in the UI.

+
Authorizations:
Request Body schema: application/json
Array ()
url
string

URL of the external action content.

+
title
string

The title to put on the external action button.

+
description
string

Optional additional description to display in the UI.

+
icon
string

The URL to an image to place on the external action button.

+
color
string

Optional color to use for drawing the action button.

+
openExternally
boolean

If set this action will open in a new browser tab instead of an internal modal.

+

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Delete a single webhook.

Delete a single webhook by its ID.

+
Authorizations:
Request Body schema: application/json
id
string

The webhook id to delete

+

Responses

Request samples

Content type
application/json
{
  • "id": "string"
}

Create a webhook.

Create a single webhook that acts on the requested events.

+
Authorizations:
Request Body schema: application/json
url
string

The url to post the events to.

+
events
Array of strings

The events to be notified about.

+

Responses

Request samples

Content type
application/json
{
  • "url": "string",
  • "events": [
    ]
}

Response samples

Content type
application/json
{
  • "name": "your new token",
  • "token": "zG2xO-mHTFnelCp5xaIkYEFWcPhoOswOSRmFC1BkI="
}

Chat

Endpoints related to the chat interface.

+

Register a chat user

Register a user that returns an access token for accessing chat.

+
Authorizations:
Request Body schema: application/json
displayName
string

Optionally provide a display name you want to assign to this user when registering.

+

Responses

Request samples

Content type
application/json
{
  • "displayName": "string"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "accessToken": "string",
  • "displayName": "string"
}

Chat Messages Backlog

Used to get chat messages prior to connecting to the websocket.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Get Custom Emoji

Get a list of custom emoji that are supported in chat.

+

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Integrations

APIs built to allow 3rd parties to interact with an Owncast server.

+

Set the stream title.

Set the title of the currently streaming content.

+
Authorizations:
Request Body schema: application/json
string or integer or object or boolean

Responses

Request samples

Content type
application/json
{
  • "value": "Streaming my favorite game, Desert Bus."
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Send a chat message.

Send a chat message on behalf of a 3rd party integration, bot or service.

+
Authorizations:
Request Body schema: application/json
body
string

The message text that will be sent as the user.

+

Responses

Request samples

Content type
application/json
{
  • "body": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Send a system chat message.

Send a chat message on behalf of the system/server.

+
Authorizations:
Request Body schema: application/json
body
string

The message text that will be sent as the system user.

+

Responses

Request samples

Content type
application/json
{
  • "body": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Send a chat action.

Send an action that took place to the chat.

+
Authorizations:
Request Body schema: application/json
body
required
string

The message text that will be sent as the system user.

+
author
string

An optional user name that performed the action.

+

Responses

Request samples

Content type
application/json
{
  • "body": "rolled a 15 on the dice",
  • "author": "JohnSmith"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "sent"
}

Create an access token.

Create a single access token that has access to the access scopes provided.

+
Authorizations:
Request Body schema: application/json
name
string

The human-readable name to give this access token.

+
scopes
Array of strings

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "name": "your new token",
  • "token": "zG2xO-mHTFnelCp5xaIkYEFWcPhoOswOSRmFC1BkI="
}

Delete an access token.

Delete a single access token.

+
Authorizations:
Request Body schema: application/json
token
string

The token to delete

+

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "deleted token"
}

Return all access tokens.

Return all of the available access tokens.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • "string"
]

Set external action URLs.

Set a collection of external action URLs that are displayed in the UI.

+
Authorizations:
Request Body schema: application/json
Array ()
url
string

URL of the external action content.

+
title
string

The title to put on the external action button.

+
description
string

Optional additional description to display in the UI.

+
icon
string

The URL to an image to place on the external action button.

+
color
string

Optional color to use for drawing the action button.

+
openExternally
boolean

If set this action will open in a new browser tab instead of an internal modal.

+

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Return a list of currently connected clients

Return a list of currently connected clients with optional geo details.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Historical Chat Messages

Used to get the backlog of chat messages.

+
Authorizations:

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Update the visibility of chat messages.

Pass an array of IDs you want to change the chat visibility of.

+
Authorizations:
Request Body schema: application/json
visible
boolean

Are these messages visible.

+
idArray
Array of strings

Responses

Request samples

Content type
application/json
{
  • "visible": true,
  • "idArray": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "success": true,
  • "message": "context specific success message"
}

Server

Information

The client configuration. Information useful for the user interface.

+

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "summary": "string",
  • "logo": "string",
  • "tags": [
    ],
  • "socialHandles": [],
  • "extraPageContent": "<p>This page is <strong>super</strong> cool!",
  • "version": "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
}

Mark the current viewer as active.

For tracking viewer count, periodically hit the ping endpoint.

+

Responses

Current Status

This endpoint is used to discover when a server is broadcasting, the number of active viewers as well as other useful information for updating the user interface.

+

Responses

Response samples

Content type
application/json
{
  • "lastConnectTime": "2020-10-03T21:36:22-05:00",
  • "lastDisconnectTime": null,
  • "online": true,
  • "overallMaxViewerCount": 420,
  • "sessionMaxViewerCount": 12,
  • "viewerCount": 7
}

Yellow Pages Information

Information to be used in the Yellow Pages service, a global directory of Owncast servers.

+

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "logo": "string",
  • "nsfw": true,
  • "tags": [
    ],
  • "online": true,
  • "viewerCount": 0,
  • "overallMaxViewerCount": 0,
  • "sessionMaxViewerCount": 0,
  • "lastConnectTime": "2019-08-24T14:15:22Z"
}