diff --git a/cmd/events.go b/cmd/events.go index 24e078ed..3e8bd06d 100644 --- a/cmd/events.go +++ b/cmd/events.go @@ -45,8 +45,6 @@ var ( clientId string version string websocketClient string - websocketServerIP string - websocketServerPort int ) // websocketCmd-specific flags @@ -57,6 +55,9 @@ var ( wsSubscription string wsStatus string wsReason string + wsServerIP string + wsServerPort int + wsSSL bool ) var eventCmd = &cobra.Command{ @@ -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.") @@ -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{ diff --git a/internal/events/websocket/mock_server/manager.go b/internal/events/websocket/mock_server/manager.go index bde642b4..c436bed2 100644 --- a/internal/events/websocket/mock_server/manager.go +++ b/internal/events/websocket/mock_server/manager.go @@ -4,12 +4,14 @@ package mock_server import ( "encoding/json" + "errors" "fmt" "log" "net" "net/http" "os" "os/signal" + "path/filepath" "strings" "time" @@ -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), @@ -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 @@ -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 @@ -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 } diff --git a/internal/events/websocket/mock_server/rpc_handler.go b/internal/events/websocket/mock_server/rpc_handler.go index 0ee743b2..dc9b1c36 100644 --- a/internal/events/websocket/mock_server/rpc_handler.go +++ b/internal/events/websocket/mock_server/rpc_handler.go @@ -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", diff --git a/internal/events/websocket/mock_server/server.go b/internal/events/websocket/mock_server/server.go index b9e1a30a..7e5ac2b5 100644 --- a/internal/events/websocket/mock_server/server.go +++ b/internal/events/websocket/mock_server/server.go @@ -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, }