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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ var (
clientId string
version string
websocketClient string
websocketServerIP string
websocketServerPort int
)

// websocketCmd-specific flags
Expand All @@ -57,6 +55,9 @@ var (
wsSubscription string
wsStatus string
wsReason string
wsServerIP string
wsServerPort int
wsSSL bool
)

var eventCmd = &cobra.Command{
Expand Down Expand Up @@ -175,8 +176,9 @@ func init() {

// websocket flags
/// flags for start-server
websocketCmd.Flags().StringVar(&websocketServerIP, "ip", "127.0.0.1", "Defines the ip that the mock EventSub websocket server will bind to.")
websocketCmd.Flags().IntVarP(&websocketServerPort, "port", "p", 8080, "Defines the port that the mock EventSub websocket server will run on.")
websocketCmd.Flags().StringVar(&wsServerIP, "ip", "127.0.0.1", "Defines the ip that the mock EventSub websocket server will bind to.")
websocketCmd.Flags().IntVarP(&wsServerPort, "port", "p", 8080, "Defines the port that the mock EventSub websocket server will run on.")
websocketCmd.Flags().BoolVar(&wsSSL, "ssl", false, "Enables SSL for EventSub websocket server (wss) and EventSub mock subscription server (https).")
websocketCmd.Flags().BoolVar(&wsDebug, "debug", false, "Set on/off for debug messages for the EventSub WebSocket server.")
websocketCmd.Flags().BoolVarP(&wsStrict, "require-subscription", "S", false, "Requires subscriptions for all events, and activates 10 second subscription requirement.")

Expand Down Expand Up @@ -334,8 +336,9 @@ func websocketCmdRun(cmd *cobra.Command, args []string) {
}

if args[0] == "start-server" || args[0] == "start" {
log.Printf("Attempting to start WebSocket server on %v:%v", wsServerIP, wsServerPort)
log.Printf("`Ctrl + C` to exit mock WebSocket servers.")
mock_server.StartWebsocketServer(wsDebug, websocketServerIP, websocketServerPort, wsStrict)
mock_server.StartWebsocketServer(wsDebug, wsServerIP, wsServerPort, wsSSL, wsStrict)
} else {
// Forward all other commands via RPC
websocket.ForwardWebsocketCommand(args[0], websocket.WebsocketCommandParameters{
Expand Down
111 changes: 84 additions & 27 deletions internal/events/websocket/mock_server/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ package mock_server

import (
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"

Expand All @@ -28,11 +30,14 @@ type ServerManager struct {
port int
debugEnabled bool
strictMode bool
sslEnabled bool
protocolHttp string
protocolWs string
}

var serverManager *ServerManager

func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool) {
func StartWebsocketServer(enableDebug bool, ip string, port int, enableSSL bool, strictMode bool) {
serverManager = &ServerManager{
serverList: &util.List[WebSocketServer]{
Elements: make(map[string]*WebSocketServer),
Expand All @@ -41,6 +46,7 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
port: port,
reconnectTesting: false,
strictMode: strictMode,
sslEnabled: enableSSL,
}

serverManager.debugEnabled = enableDebug
Expand Down Expand Up @@ -83,39 +89,59 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
return
}

lightBlue := color.New(color.FgHiBlue).SprintFunc()
lightGreen := color.New(color.FgHiGreen).SprintFunc()
lightYellow := color.New(color.FgHiYellow).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
lightRed := color.New(color.FgHiRed).SprintFunc()
brightWhite := color.New(color.FgHiWhite).SprintFunc()

log.Printf(lightBlue("Started WebSocket server on %v:%v"), ip, port)
if serverManager.strictMode {
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
}

fmt.Println()

log.Printf(yellow("Simulate subscribing to events at: http://%v:%v/eventsub/subscriptions"), ip, port)
log.Printf(yellow("POST, GET, and DELETE are supported"))
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))
// Serve HTTP server
if serverManager.sslEnabled {
serverManager.protocolHttp = "https"
serverManager.protocolWs = "wss"

home, err := util.GetApplicationDir()
if err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}

fmt.Println()
crtFile := filepath.Join(home, "localhost.crt")
keyFile := filepath.Join(home, "localhost.key")
_, crtFileErr := os.Stat(crtFile)
_, keyFileErr := os.Stat(keyFile)
if errors.Is(crtFileErr, os.ErrNotExist) || errors.Is(keyFileErr, os.ErrNotExist) {
log.Fatalf(`%v
** Files must be placed in %v as %v and %v **
%v
** However, if you wish to generate the files using OpenSSL, run these commands: **
openssl genrsa -out "%v" 2048
openssl req -new -x509 -sha256 -key "%v" -out "%v" -days 365`,
lightRed("ERROR: Missing one of the required SSL crt/key files."),
brightWhite(home),
brightWhite("localhost.crt"),
brightWhite("localhost.key"),
lightYellow("** Testing with Twitch CLI using SSL is meant for users experienced with SSL already, as these files must be added to your systems keychain to work without errors. **"),
keyFile, keyFile, crtFile)
return
}

log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
fmt.Println()
log.Printf(lightYellow("You can send to a specific client after its connected with --session\nExample: \"twitch event trigger channel.ban --transport=websocket --session=e411cc1e_a2613d4e\""))
printWelcomeMsg()

fmt.Println()
log.Printf(lightGreen("For further usage information, please see our official documentation:\nhttps://dev.twitch.tv/docs/cli/websocket-event-command/"))
fmt.Println()
if err := http.ServeTLS(listen, m, crtFile, keyFile); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}
} else {
serverManager.protocolHttp = "http"
serverManager.protocolWs = "ws"

log.Printf(lightBlue("Connect to the WebSocket server at: ")+"ws://%v:%v/ws", ip, port)
printWelcomeMsg()

// Serve HTTP server
if err := http.Serve(listen, m); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
if err := http.Serve(listen, m); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}
}

}()

// Initalize RPC handler, to accept EventSub transports
Expand All @@ -135,10 +161,41 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
<-stop // Wait for Ctrl + C
}

func printWelcomeMsg() {
lightBlue := color.New(color.FgHiBlue).SprintFunc()
lightGreen := color.New(color.FgHiGreen).SprintFunc()
lightYellow := color.New(color.FgHiYellow).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()

log.Printf(lightBlue("Started WebSocket server on %v:%v"), serverManager.ip, serverManager.port)
if serverManager.strictMode {
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
}

fmt.Println()

log.Printf(yellow("Simulate subscribing to events at: %v://%v:%v/eventsub/subscriptions"), serverManager.protocolHttp, serverManager.ip, serverManager.port)
log.Printf(yellow("POST, GET, and DELETE are supported"))
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))

fmt.Println()

log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
fmt.Println()
log.Printf(lightYellow("You can send to a specific client after its connected with --session\nExample: \"twitch event trigger channel.ban --transport=websocket --session=e411cc1e_a2613d4e\""))

fmt.Println()
log.Printf(lightGreen("For further usage information, please see our official documentation:\nhttps://dev.twitch.tv/docs/cli/websocket-event-command/"))
fmt.Println()

log.Printf(lightBlue("Connect to the WebSocket server at: ")+"%v://%v:%v/ws", serverManager.protocolWs, serverManager.ip, serverManager.port)
}

func wsPageHandler(w http.ResponseWriter, r *http.Request) {
server, ok := serverManager.serverList.Get(serverManager.primaryServer)
if !ok {
log.Printf("Failed to find primary server [%v] when new client was accessing ws://%v:%v/ws -- Aborting...", serverManager.primaryServer, serverManager.ip, serverManager.port)
log.Printf("Failed to find primary server [%v] when new client was accessing %v://%v:%v/ws -- Aborting...",
serverManager.primaryServer, serverManager.protocolHttp, serverManager.ip, serverManager.port)
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/events/websocket/mock_server/rpc_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func RPCSubscriptionHandler(args rpc.RPCArgs) rpc.RPCResponse {
return rpc.RPCResponse{
ResponseCode: COMMAND_RESPONSE_MISSING_FLAG,
DetailedInfo: "Command \"subscription\" requires flags --status, --subscription, and --session" +
fmt.Sprintf("\nThe flag --subscription must be the ID of the subscription made at http://%v:%v/eventsub/subscriptions", serverManager.ip, serverManager.port) +
fmt.Sprintf("\nThe flag --subscription must be the ID of the subscription made at %v://%v:%v/eventsub/subscriptions", serverManager.protocolHttp, serverManager.ip, serverManager.port) +
"\nThe flag --status must be one of the non-webhook status options defined here:" +
"\nhttps://dev.twitch.tv/docs/api/reference/#get-eventsub-subscriptions" +
"\n\nExample: twitch event websocket subscription --status=user_removed --subscription=82a855-fae8-93bff0",
Expand Down
2 changes: 1 addition & 1 deletion internal/events/websocket/mock_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (ws *WebSocketServer) WsPageHandler(w http.ResponseWriter, r *http.Request)
clientName: util.RandomGUID()[:8],
conn: conn,
ConnectedAtTimestamp: connectedAtTimestamp,
connectionUrl: fmt.Sprintf("http://%v/ws", r.Host),
connectionUrl: fmt.Sprintf("%v://%v/ws", serverManager.protocolHttp, r.Host),
keepAliveChanOpen: false,
pingChanOpen: false,
}
Expand Down