Skip to content

Commit f02f16c

Browse files
authored
Merge pull request #223 from twitchdev/eventsub-websocket-ssl
Added SSL support for the EventSub WebSocket server
2 parents dad323d + 1ebd213 commit f02f16c

File tree

4 files changed

+94
-34
lines changed

4 files changed

+94
-34
lines changed

cmd/events.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ var (
4545
clientId string
4646
version string
4747
websocketClient string
48-
websocketServerIP string
49-
websocketServerPort int
5048
)
5149

5250
// websocketCmd-specific flags
@@ -57,6 +55,9 @@ var (
5755
wsSubscription string
5856
wsStatus string
5957
wsReason string
58+
wsServerIP string
59+
wsServerPort int
60+
wsSSL bool
6061
)
6162

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

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

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

336338
if args[0] == "start-server" || args[0] == "start" {
339+
log.Printf("Attempting to start WebSocket server on %v:%v", wsServerIP, wsServerPort)
337340
log.Printf("`Ctrl + C` to exit mock WebSocket servers.")
338-
mock_server.StartWebsocketServer(wsDebug, websocketServerIP, websocketServerPort, wsStrict)
341+
mock_server.StartWebsocketServer(wsDebug, wsServerIP, wsServerPort, wsSSL, wsStrict)
339342
} else {
340343
// Forward all other commands via RPC
341344
websocket.ForwardWebsocketCommand(args[0], websocket.WebsocketCommandParameters{

internal/events/websocket/mock_server/manager.go

+84-27
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ package mock_server
44

55
import (
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"log"
910
"net"
1011
"net/http"
1112
"os"
1213
"os/signal"
14+
"path/filepath"
1315
"strings"
1416
"time"
1517

@@ -28,11 +30,14 @@ type ServerManager struct {
2830
port int
2931
debugEnabled bool
3032
strictMode bool
33+
sslEnabled bool
34+
protocolHttp string
35+
protocolWs string
3136
}
3237

3338
var serverManager *ServerManager
3439

35-
func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool) {
40+
func StartWebsocketServer(enableDebug bool, ip string, port int, enableSSL bool, strictMode bool) {
3641
serverManager = &ServerManager{
3742
serverList: &util.List[WebSocketServer]{
3843
Elements: make(map[string]*WebSocketServer),
@@ -41,6 +46,7 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
4146
port: port,
4247
reconnectTesting: false,
4348
strictMode: strictMode,
49+
sslEnabled: enableSSL,
4450
}
4551

4652
serverManager.debugEnabled = enableDebug
@@ -83,39 +89,59 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
8389
return
8490
}
8591

86-
lightBlue := color.New(color.FgHiBlue).SprintFunc()
87-
lightGreen := color.New(color.FgHiGreen).SprintFunc()
8892
lightYellow := color.New(color.FgHiYellow).SprintFunc()
89-
yellow := color.New(color.FgYellow).SprintFunc()
93+
lightRed := color.New(color.FgHiRed).SprintFunc()
94+
brightWhite := color.New(color.FgHiWhite).SprintFunc()
9095

91-
log.Printf(lightBlue("Started WebSocket server on %v:%v"), ip, port)
92-
if serverManager.strictMode {
93-
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
94-
}
95-
96-
fmt.Println()
97-
98-
log.Printf(yellow("Simulate subscribing to events at: http://%v:%v/eventsub/subscriptions"), ip, port)
99-
log.Printf(yellow("POST, GET, and DELETE are supported"))
100-
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))
96+
// Serve HTTP server
97+
if serverManager.sslEnabled {
98+
serverManager.protocolHttp = "https"
99+
serverManager.protocolWs = "wss"
100+
101+
home, err := util.GetApplicationDir()
102+
if err != nil {
103+
log.Fatalf("Cannot start HTTP server: %v", err)
104+
return
105+
}
101106

102-
fmt.Println()
107+
crtFile := filepath.Join(home, "localhost.crt")
108+
keyFile := filepath.Join(home, "localhost.key")
109+
_, crtFileErr := os.Stat(crtFile)
110+
_, keyFileErr := os.Stat(keyFile)
111+
if errors.Is(crtFileErr, os.ErrNotExist) || errors.Is(keyFileErr, os.ErrNotExist) {
112+
log.Fatalf(`%v
113+
** Files must be placed in %v as %v and %v **
114+
%v
115+
** However, if you wish to generate the files using OpenSSL, run these commands: **
116+
openssl genrsa -out "%v" 2048
117+
openssl req -new -x509 -sha256 -key "%v" -out "%v" -days 365`,
118+
lightRed("ERROR: Missing one of the required SSL crt/key files."),
119+
brightWhite(home),
120+
brightWhite("localhost.crt"),
121+
brightWhite("localhost.key"),
122+
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. **"),
123+
keyFile, keyFile, crtFile)
124+
return
125+
}
103126

104-
log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
105-
fmt.Println()
106-
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\""))
127+
printWelcomeMsg()
107128

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

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

114-
// Serve HTTP server
115-
if err := http.Serve(listen, m); err != nil {
116-
log.Fatalf("Cannot start HTTP server: %v", err)
117-
return
139+
if err := http.Serve(listen, m); err != nil {
140+
log.Fatalf("Cannot start HTTP server: %v", err)
141+
return
142+
}
118143
}
144+
119145
}()
120146

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

164+
func printWelcomeMsg() {
165+
lightBlue := color.New(color.FgHiBlue).SprintFunc()
166+
lightGreen := color.New(color.FgHiGreen).SprintFunc()
167+
lightYellow := color.New(color.FgHiYellow).SprintFunc()
168+
yellow := color.New(color.FgYellow).SprintFunc()
169+
170+
log.Printf(lightBlue("Started WebSocket server on %v:%v"), serverManager.ip, serverManager.port)
171+
if serverManager.strictMode {
172+
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
173+
}
174+
175+
fmt.Println()
176+
177+
log.Printf(yellow("Simulate subscribing to events at: %v://%v:%v/eventsub/subscriptions"), serverManager.protocolHttp, serverManager.ip, serverManager.port)
178+
log.Printf(yellow("POST, GET, and DELETE are supported"))
179+
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))
180+
181+
fmt.Println()
182+
183+
log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
184+
fmt.Println()
185+
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\""))
186+
187+
fmt.Println()
188+
log.Printf(lightGreen("For further usage information, please see our official documentation:\nhttps://dev.twitch.tv/docs/cli/websocket-event-command/"))
189+
fmt.Println()
190+
191+
log.Printf(lightBlue("Connect to the WebSocket server at: ")+"%v://%v:%v/ws", serverManager.protocolWs, serverManager.ip, serverManager.port)
192+
}
193+
138194
func wsPageHandler(w http.ResponseWriter, r *http.Request) {
139195
server, ok := serverManager.serverList.Get(serverManager.primaryServer)
140196
if !ok {
141-
log.Printf("Failed to find primary server [%v] when new client was accessing ws://%v:%v/ws -- Aborting...", serverManager.primaryServer, serverManager.ip, serverManager.port)
197+
log.Printf("Failed to find primary server [%v] when new client was accessing %v://%v:%v/ws -- Aborting...",
198+
serverManager.primaryServer, serverManager.protocolHttp, serverManager.ip, serverManager.port)
142199
return
143200
}
144201

internal/events/websocket/mock_server/rpc_handler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func RPCSubscriptionHandler(args rpc.RPCArgs) rpc.RPCResponse {
215215
return rpc.RPCResponse{
216216
ResponseCode: COMMAND_RESPONSE_MISSING_FLAG,
217217
DetailedInfo: "Command \"subscription\" requires flags --status, --subscription, and --session" +
218-
fmt.Sprintf("\nThe flag --subscription must be the ID of the subscription made at http://%v:%v/eventsub/subscriptions", serverManager.ip, serverManager.port) +
218+
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) +
219219
"\nThe flag --status must be one of the non-webhook status options defined here:" +
220220
"\nhttps://dev.twitch.tv/docs/api/reference/#get-eventsub-subscriptions" +
221221
"\n\nExample: twitch event websocket subscription --status=user_removed --subscription=82a855-fae8-93bff0",

internal/events/websocket/mock_server/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func (ws *WebSocketServer) WsPageHandler(w http.ResponseWriter, r *http.Request)
5959
clientName: util.RandomGUID()[:8],
6060
conn: conn,
6161
ConnectedAtTimestamp: connectedAtTimestamp,
62-
connectionUrl: fmt.Sprintf("http://%v/ws", r.Host),
62+
connectionUrl: fmt.Sprintf("%v://%v/ws", serverManager.protocolHttp, r.Host),
6363
keepAliveChanOpen: false,
6464
pingChanOpen: false,
6565
}

0 commit comments

Comments
 (0)