From d24ddc2b0ade53fbc11bf98c3f125b104c3cfd66 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Sun, 6 Mar 2022 17:11:51 -0800 Subject: [PATCH] Add support for and use socket host override. (#1682) * Add support for and use socket host override. Closes #1378 * Fix embeds with the new websocket constructor --- controllers/admin/config.go | 19 ++++++ controllers/admin/serverConfig.go | 2 + controllers/config.go | 2 + core/data/config.go | 13 ++++ router/router.go | 3 + test/automated/browser/package-lock.json | 86 ++++-------------------- webroot/js/app-standalone-chat.js | 12 +++- webroot/js/app.js | 14 +++- webroot/js/utils/websocket.js | 6 +- 9 files changed, 75 insertions(+), 82 deletions(-) diff --git a/controllers/admin/config.go b/controllers/admin/config.go index c46625086..086e71d8b 100644 --- a/controllers/admin/config.go +++ b/controllers/admin/config.go @@ -408,6 +408,25 @@ func SetServerURL(w http.ResponseWriter, r *http.Request) { controllers.WriteSimpleResponse(w, true, "server url set") } +// SetSocketHostOverride will set the host override for the websocket. +func SetSocketHostOverride(w http.ResponseWriter, r *http.Request) { + if !requirePOST(w, r) { + return + } + + configValue, success := getValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetWebsocketOverrideHost(configValue.Value.(string)); err != nil { + controllers.WriteSimpleResponse(w, false, err.Error()) + return + } + + controllers.WriteSimpleResponse(w, true, "websocket host override set") +} + // SetDirectoryEnabled will handle the web config request to enable or disable directory registration. func SetDirectoryEnabled(w http.ResponseWriter, r *http.Request) { if !requirePOST(w, r) { diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go index ad860f67e..ad5595e53 100644 --- a/controllers/admin/serverConfig.go +++ b/controllers/admin/serverConfig.go @@ -53,6 +53,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { RTMPServerPort: data.GetRTMPPortNumber(), ChatDisabled: data.GetChatDisabled(), ChatJoinMessagesEnabled: data.GetChatJoinMessagesEnabled(), + SocketHostOverride: data.GetWebsocketOverrideHost(), VideoSettings: videoSettings{ VideoQualityVariants: videoQualityVariants, LatencyLevel: data.GetStreamLatencyLevel().Level, @@ -103,6 +104,7 @@ type serverConfigAdminResponse struct { ForbiddenUsernames []string `json:"forbiddenUsernames"` Federation federationConfigResponse `json:"federation"` SuggestedUsernames []string `json:"suggestedUsernames"` + SocketHostOverride string `json:"socketHostOverride,omitempty"` } type videoSettings struct { diff --git a/controllers/config.go b/controllers/config.go index b91152bdc..e77945651 100644 --- a/controllers/config.go +++ b/controllers/config.go @@ -21,6 +21,7 @@ type webConfigResponse struct { Tags []string `json:"tags"` Version string `json:"version"` NSFW bool `json:"nsfw"` + SocketHostOverride string `json:"socketHostOverride,omitempty"` ExtraPageContent string `json:"extraPageContent"` StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream SocialHandles []models.SocialHandle `json:"socialHandles"` @@ -78,6 +79,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) { Tags: data.GetServerMetadataTags(), Version: config.GetReleaseString(), NSFW: data.GetNSFW(), + SocketHostOverride: data.GetWebsocketOverrideHost(), ExtraPageContent: pageContent, StreamTitle: data.GetStreamTitle(), SocialHandles: socialHandles, diff --git a/core/data/config.go b/core/data/config.go index 3375d5495..ee031e3c8 100644 --- a/core/data/config.go +++ b/core/data/config.go @@ -24,6 +24,7 @@ const ( serverURLKey = "server_url" httpPortNumberKey = "http_port_number" httpListenAddressKey = "http_listen_address" + websocketHostOverrideKey = "websocket_host_override" rtmpPortNumberKey = "rtmp_port_number" serverMetadataTagsKey = "server_metadata_tags" directoryEnabledKey = "directory_enabled" @@ -200,6 +201,18 @@ func GetHTTPPortNumber() int { return int(port) } +// SetWebsocketOverrideHost will set the host override for websockets. +func SetWebsocketOverrideHost(host string) error { + return _datastore.SetString(websocketHostOverrideKey, host) +} + +// GetWebsocketOverrideHost will return the host override for websockets. +func GetWebsocketOverrideHost() string { + host, _ := _datastore.GetString(websocketHostOverrideKey) + + return host +} + // SetHTTPPortNumber will set the server HTTP port. func SetHTTPPortNumber(port float64) error { return _datastore.SetNumber(httpPortNumberKey, port) diff --git a/router/router.go b/router/router.go index 439cc0aad..21c237aea 100644 --- a/router/router.go +++ b/router/router.go @@ -245,6 +245,9 @@ func Start() error { // Server rtmp port http.HandleFunc("/api/admin/config/rtmpserverport", middleware.RequireAdminAuth(admin.SetRTMPServerPort)) + // Websocket host override + http.HandleFunc("/api/admin/config/sockethostoverride", middleware.RequireAdminAuth(admin.SetSocketHostOverride)) + // Is server marked as NSFW http.HandleFunc("/api/admin/config/nsfw", middleware.RequireAdminAuth(admin.SetNSFW)) diff --git a/test/automated/browser/package-lock.json b/test/automated/browser/package-lock.json index 2936cc939..73a33385c 100644 --- a/test/automated/browser/package-lock.json +++ b/test/automated/browser/package-lock.json @@ -2087,9 +2087,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", "dev": true, "funding": [ { @@ -3652,45 +3652,12 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, "engines": { "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" } }, "node_modules/node-int64": { @@ -6589,9 +6556,9 @@ } }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", "dev": true }, "for-in": { @@ -7780,37 +7747,10 @@ "dev": true }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", + "dev": true }, "node-int64": { "version": "0.4.0", diff --git a/webroot/js/app-standalone-chat.js b/webroot/js/app-standalone-chat.js index 18663d66a..227a86ad6 100644 --- a/webroot/js/app-standalone-chat.js +++ b/webroot/js/app-standalone-chat.js @@ -20,6 +20,7 @@ import { URL_CONFIG, TIMER_STATUS_UPDATE, } from './utils/constants.js'; +import { URL_WEBSOCKET } from './utils/constants.js'; export default class StandaloneChat extends Component { constructor(props, context) { @@ -53,6 +54,8 @@ export default class StandaloneChat extends Component { this.setupChatAuth = this.setupChatAuth.bind(this); this.disableChat = this.disableChat.bind(this); + this.socketHostOverride = null; + // user events this.handleWebsocketMessage = this.handleWebsocketMessage.bind(this); @@ -98,7 +101,7 @@ export default class StandaloneChat extends Component { } setConfigData(data = {}) { - const { chatDisabled } = data; + const { chatDisabled, socketHostOverride } = data; // If this is the first time setting the config // then setup chat if it's enabled. @@ -107,7 +110,7 @@ export default class StandaloneChat extends Component { } this.hasConfiguredChat = true; - + this.socketHostOverride = socketHostOverride; this.setState({ canChat: !chatDisabled, configData: { @@ -277,7 +280,10 @@ export default class StandaloneChat extends Component { } // Without a valid access token he websocket connection will be rejected. - const websocket = new Websocket(accessToken); + const websocket = new Websocket( + accessToken, + this.socketHostOverride || URL_WEBSOCKET + ); websocket.addListener( CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, this.handleWebsocketMessage diff --git a/webroot/js/app.js b/webroot/js/app.js index 0cb6d7174..8d02d1180 100644 --- a/webroot/js/app.js +++ b/webroot/js/app.js @@ -2,6 +2,8 @@ import { h, Component } from '/js/web_modules/preact.js'; import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); +import { URL_WEBSOCKET } from './utils/constants.js'; + import { OwncastPlayer } from './components/player.js'; import SocialIconsList from './components/platform-logos-list.js'; import UsernameForm from './components/chat/username.js'; @@ -154,6 +156,7 @@ export default class App extends Component { this.hasConfiguredChat = false; this.setupChatAuth = this.setupChatAuth.bind(this); this.disableChat = this.disableChat.bind(this); + this.socketHostOverride = null; } componentDidMount() { @@ -245,9 +248,11 @@ export default class App extends Component { } setConfigData(data = {}) { - const { name, summary, chatDisabled } = data; + const { name, summary, chatDisabled, socketHostOverride } = data; window.document.title = name; + this.socketHostOverride = socketHostOverride; + // If this is the first time setting the config // then setup chat if it's enabled. if (!this.hasConfiguredChat && !chatDisabled) { @@ -638,8 +643,11 @@ export default class App extends Component { }); } - // Without a valid access token he websocket connection will be rejected. - const websocket = new Websocket(accessToken); + // Without a valid access token the websocket connection will be rejected. + const websocket = new Websocket( + accessToken, + this.socketHostOverride || URL_WEBSOCKET + ); websocket.addListener( CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, this.handleWebsocketMessage diff --git a/webroot/js/utils/websocket.js b/webroot/js/utils/websocket.js index e1001fbf3..31fd5aab1 100644 --- a/webroot/js/utils/websocket.js +++ b/webroot/js/utils/websocket.js @@ -1,4 +1,3 @@ -import { URL_WEBSOCKET } from './constants.js'; /** * These are the types of messages that we can handle with the websocket. * Mostly used by `websocket.js` but if other components need to handle @@ -30,8 +29,9 @@ export const CALLBACKS = { const TIMER_WEBSOCKET_RECONNECT = 5000; // ms export default class Websocket { - constructor(accessToken) { + constructor(accessToken, path) { this.websocket = null; + this.path = path; this.websocketReconnectTimer = null; this.accessToken = accessToken; @@ -50,7 +50,7 @@ export default class Websocket { } createAndConnect() { - const url = new URL(URL_WEBSOCKET); + const url = new URL(this.path); url.searchParams.append('accessToken', this.accessToken); const ws = new WebSocket(url.toString());