diff --git a/.eslintrc.json b/.eslintrc.json
index 237650932..8ca153cbc 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,7 +3,9 @@
"es6": true,
"node": true
},
- "extends": "eslint:recommended",
+ "extends": [
+ "eslint:recommended"
+ ],
"parserOptions": {
"ecmaVersion": 2017
},
@@ -12,9 +14,7 @@
"json"
],
"rules": {
- "indent": ["error",
- 2
- ],
+ "indent": ["error", 2],
"no-console": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
@@ -29,7 +29,7 @@
"no-global-assign": "error",
"no-implied-eval": "error",
"lines-around-comment": ["warn", {"beforeLineComment": true}],
- "spaced-comment": ["warn", "always", { "line": { "exceptions": ["-", "="] } }],
+ "spaced-comment": ["warn", "always", {"line": {"exceptions": ["-", "="]}}],
"no-script-url": "error",
"no-throw-literal": "error",
"yoda": "warn",
@@ -41,32 +41,20 @@
"object-curly-spacing": "warn",
"space-infix-ops": ["error"],
"space-in-parens": ["error", "never"],
- "space-unary-ops": ["error", {
- "words": true,
- "nonwords": false
- }],
+ "space-unary-ops": ["error", {"words": true, "nonwords": false}],
"no-const-assign": "error",
"no-duplicate-imports": "error",
"prefer-template": "warn",
- "comma-spacing": ["error", {
- "after": true
- }],
+ "comma-spacing": ["error", {"after": true}],
"no-var": "error",
"no-lonely-if": "error",
"curly": "error",
- "no-unused-vars": ["error", {
- "argsIgnorePattern": "^_|next",
- "varsIgnorePattern": "^_"
- }],
- "no-multiple-empty-lines": ["error", {
- "max": 1
- }],
- "newline-per-chained-call": ["error", {
- "ignoreChainWithDepth": 2
- }],
+ "no-unused-vars": ["error", {"argsIgnorePattern": "^_|next", "varsIgnorePattern": "^_"}],
+ "no-multiple-empty-lines": ["error", {"max": 1}],
+ "newline-per-chained-call": ["error", {"ignoreChainWithDepth": 2}],
"promise/no-return-wrap": "error",
"promise/param-names": "error",
- "promise/catch-or-return": "error",
+ "promise/catch-or-return": "warn",
"promise/no-native": "off",
"promise/no-nesting": "warn",
"promise/no-promise-in-callback": "warn",
diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js
index d6b27115a..02d562d65 100644
--- a/client/coral-admin/src/actions/install.js
+++ b/client/coral-admin/src/actions/install.js
@@ -128,18 +128,18 @@ const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST});
const checkInstallSuccess = (installed) => ({type: actions.CHECK_INSTALL_SUCCESS, installed});
const checkInstallFailure = (error) => ({type: actions.CHECK_INSTALL_FAILURE, error});
-export const checkInstall = (next) => (dispatch, _, {rest}) => {
+export const checkInstall = (next) => async (dispatch, _, {rest}) => {
dispatch(checkInstallRequest());
- rest('/setup')
- .then(({installed}) => {
- dispatch(checkInstallSuccess(installed));
- if (installed) {
- next();
- }
- })
- .catch((error) => {
- console.error(error);
- const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
- dispatch(checkInstallFailure(errorMessage));
- });
+
+ try {
+ const {installed} = await rest('/setup');
+ dispatch(checkInstallSuccess(installed));
+ if (installed) {
+ next();
+ }
+ } catch (error) {
+ console.error(error);
+ const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
+ dispatch(checkInstallFailure(errorMessage));
+ }
};
diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js
index a7eadbac3..fd44d5f17 100644
--- a/client/coral-admin/src/components/UserDetail.js
+++ b/client/coral-admin/src/components/UserDetail.js
@@ -28,16 +28,26 @@ export default class UserDetail extends React.Component {
bulkReject: PropTypes.func.isRequired,
}
- rejectThenReload = (info) => {
- this.props.rejectComment(info).then(() => {
+ rejectThenReload = async (info) => {
+ try {
+ await this.props.rejectComment(info);
this.props.data.refetch();
- });
+ } catch (err) {
+
+ // TODO: handle error.
+ console.error(err);
+ }
}
- acceptThenReload = (info) => {
- this.props.acceptComment(info).then(() => {
+ acceptThenReload = async (info) => {
+ try {
+ await this.props.acceptComment(info);
this.props.data.refetch();
- });
+ } catch (err) {
+
+ // TODO: handle error.
+ console.error(err);
+ }
}
showAll = () => {
@@ -133,7 +143,7 @@ export default class UserDetail extends React.Component {
diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js
index 0eb81a9a1..5effb2b84 100644
--- a/client/coral-admin/src/containers/UserDetail.js
+++ b/client/coral-admin/src/containers/UserDetail.js
@@ -36,14 +36,19 @@ class UserDetailContainer extends React.Component {
isLoadingMore = false;
// status can be 'ACCEPTED' or 'REJECTED'
- bulkSetCommentStatus = (status) => {
+ bulkSetCommentStatus = async (status) => {
const changes = this.props.selectedCommentIds.map((commentId) => {
return this.props.setCommentStatus({commentId, status});
});
- Promise.all(changes).then(() => {
+ try {
+ await Promise.all(changes);
this.props.clearUserDetailSelections(); // un-select everything
- });
+ } catch (err) {
+
+ // TODO: handle error.
+ console.error(err);
+ }
}
bulkReject = () => {
diff --git a/client/coral-admin/src/routes/Community/components/FlaggedUser.js b/client/coral-admin/src/routes/Community/components/FlaggedUser.js
index 5b2238592..81d7790fe 100644
--- a/client/coral-admin/src/routes/Community/components/FlaggedUser.js
+++ b/client/coral-admin/src/routes/Community/components/FlaggedUser.js
@@ -85,7 +85,7 @@ class User extends React.Component {
{t('community.flags')}({ user.actions.length })
:
- { user.action_summaries.map(
+ { user.action_summaries.map(
(action, i) => {
return
{shortReasons[action.reason]} ({action.count})
diff --git a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js
index 128946b61..4932021bd 100644
--- a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js
+++ b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js
@@ -48,11 +48,15 @@ class RejectUsernameDialog extends Component {
const cancel = this.props.handleClose;
const next = () => this.setState({stage: stage + 1});
- const suspend = () => {
- rejectUsername({id: user.user.id, message: this.state.email})
- .then(() => {
- this.props.handleClose();
- });
+ const suspend = async () => {
+ try {
+ await rejectUsername({id: user.user.id, message: this.state.email});
+ this.props.handleClose();
+ } catch (err) {
+
+ // TODO: handle error.
+ console.error(err);
+ }
};
const suspendModalActions = [
diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js
index 5b931e6fc..d57b8f779 100644
--- a/client/coral-admin/src/routes/Stories/components/Stories.js
+++ b/client/coral-admin/src/routes/Stories/components/Stories.js
@@ -50,17 +50,22 @@ export default class Stories extends Component {
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
- onStatusClick = (closeStream, id, statusMenuOpen) => () => {
+ onStatusClick = (closeStream, id, statusMenuOpen) => async () => {
if (statusMenuOpen) {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
- this.props.updateAssetState(id, closeStream ? Date.now() : null)
- .then(() => {
- const {search, sort, filter, page} = this.state;
- this.props.fetchAssets(page, limit, search, sort, filter);
- });
+
+ try {
+ await this.props.updateAssetState(id, closeStream ? Date.now() : null);
+ const {search, sort, filter, page} = this.state;
+ this.props.fetchAssets(page, limit, search, sort, filter);
+ } catch (err) {
+
+ // TODO: handle error.
+ console.error(err);
+ }
} else {
this.setState((prev) => {
prev.statusMenus[id] = true;
diff --git a/client/coral-embed-stream/src/components/StreamTabPanel.js b/client/coral-embed-stream/src/components/StreamTabPanel.js
index a511ac555..50f54c2a0 100644
--- a/client/coral-embed-stream/src/components/StreamTabPanel.js
+++ b/client/coral-embed-stream/src/components/StreamTabPanel.js
@@ -15,8 +15,8 @@ class StreamTabPanel extends React.Component {
{loading
?
:
- {tabPanes}
-
+ {tabPanes}
+
}
);
diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js
index ac94d8b29..a3ae5b18e 100644
--- a/client/coral-framework/services/bootstrap.js
+++ b/client/coral-framework/services/bootstrap.js
@@ -15,6 +15,25 @@ import globalFragments from 'coral-framework/graphql/fragments';
import {createStorage} from 'coral-framework/services/storage';
import {createHistory} from 'coral-framework/services/history';
+/**
+ * getStaticConfiguration will return a singleton of the static configuration
+ * object provided via a JSON DOM element.
+ */
+const getStaticConfiguration = (() => {
+ let staticConfiguration = null;
+ return () => {
+ if (staticConfiguration != null) {
+ return staticConfiguration;
+ }
+
+ const configElement = document.querySelector('#data');
+
+ staticConfiguration = JSON.parse(configElement ? configElement.textContent : '{}');
+
+ return staticConfiguration;
+ };
+})();
+
/**
* getAuthToken returns the active auth token or null
* Note: this method does not have access to the cookie based token used by
@@ -49,7 +68,6 @@ const getAuthToken = (store, storage) => {
* @return {Object} context
*/
export function createContext({reducers = {}, pluginsConfig = [], graphqlExtension = {}, notification} = {}) {
- const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const eventEmitter = new EventEmitter({wildcard: true});
const storage = createStorage();
const history = createHistory(BASE_PATH);
@@ -63,13 +81,28 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
// TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT.
return getAuthToken(store, storage);
};
+
const rest = createRestClient({
uri: `${BASE_PATH}api/v1`,
token,
});
+
+ // Try to get an overrided liveUri from the static config, if none is found,
+ // build it.
+ let {LIVE_URI: liveUri} = getStaticConfiguration();
+ if (liveUri == null) {
+
+ // The protocol must match the origin protocol, secure/insecure.
+ const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
+
+ // Compose the live url from this protocol, the current host + base path
+ // with the live path appended to it.
+ liveUri = `${protocol}://${location.host}${BASE_PATH}api/v1/live`;
+ }
+
const client = createClient({
uri: `${BASE_PATH}api/v1/graph/ql`,
- liveUri: `${protocol}://${location.host}${BASE_PATH}api/v1/live`,
+ liveUri,
token,
});
const plugins = createPluginsService(pluginsConfig);
@@ -79,6 +112,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
// Use default notification service (pym based)
notification = createNotificationService(pym);
}
+
const context = {
client,
pym,
diff --git a/config.js b/config.js
index f48159444..d7e247ec3 100644
--- a/config.js
+++ b/config.js
@@ -135,6 +135,9 @@ const CONFIG = {
RECAPTCHA_PUBLIC: process.env.TALK_RECAPTCHA_PUBLIC,
RECAPTCHA_SECRET: process.env.TALK_RECAPTCHA_SECRET,
+ // WEBSOCKET_LIVE_URI is the absolute url to the live endpoint.
+ WEBSOCKET_LIVE_URI: process.env.TALK_WEBSOCKET_LIVE_URI || null,
+
//------------------------------------------------------------------------------
// SMTP Server configuration
//------------------------------------------------------------------------------
diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml
index 7226f6003..9fe39d103 100755
--- a/docs/_data/navigation.yml
+++ b/docs/_data/navigation.yml
@@ -38,6 +38,8 @@ docs:
url: /docs/running/plugins/
- title: "Database Migrations"
url: /docs/running/migrations/
+ - title: "Persistence"
+ url: /docs/running/persistence/
- title: "Architecture"
url: /docs/architecture/
children:
diff --git a/docs/_docs/00-01-faq.md b/docs/_docs/00-01-faq.md
index 01172f84a..e114fce72 100644
--- a/docs/_docs/00-01-faq.md
+++ b/docs/_docs/00-01-faq.md
@@ -91,7 +91,7 @@ source process), then:
```
cd docs
-docker run --rm --volume=$PWD:/srv/jekyll -p 127.0.0.1:4000:4000 -it jekyll/jekyll:pages jekyll serve
+docker run --rm --volume=$PWD:/srv/jekyll -p 127.0.0.1:4000:4000 -it jekyll/jekyll:pages bash -c "bundle install && jekyll serve"
```
You can edit the files in docs with any editor and view the live updates in a
diff --git a/docs/_docs/02-01-configuration.md b/docs/_docs/02-01-configuration.md
index df790b24b..6ffb9afc0 100644
--- a/docs/_docs/02-01-configuration.md
+++ b/docs/_docs/02-01-configuration.md
@@ -53,6 +53,7 @@ These are only used during the webpack build.
- `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database.
#### Advanced
+{:.no_toc}
- `TALK_REDIS_RECONNECTION_MAX_ATTEMPTS` (_optional_) - the amount of attempts
that a redis connection will attempt to reconnect before aborting with an
@@ -76,11 +77,19 @@ These are only used during the webpack build.
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - When `TRUE`, disables the dynamic setup endpoint. (Default `FALSE`)
#### Advanced
+{:.no_toc}
- `TALK_ROOT_URL_MOUNT_PATH` (_optional_) - when set to `TRUE`, the routes will
be mounted onto the `` component of the `TALK_ROOT_URL`. You would
use this when your upstream proxy cannot strip the prefix from the url.
(Default `FALSE`)
+- `TALK_WEBSOCKET_LIVE_URI` (_optional_) - used to override the location to
+ connect to the websocket endpoint to potentially another host. This should
+ be used when you need to route websocket requests out of your CDN in order to
+ serve traffic more efficiently for websockets. **Warning: if used without
+ managing the auth state manually, auth cannot be persisted, for further
+ information refer to the [Persistence Documentation]({{ "/docs/running/persistence/" | absolute_url }})**
+ (Default `${ssl ? 'ws' : 'wss'}://${location.host}${TALK_ROOT_URL_MOUNT_PATH}api/v1/live`)
### Word Filter
@@ -105,6 +114,7 @@ variable. Refer to the [Secrets Documentation]({{ "/docs/running/secrets/" | abs
on the contents of those variables.**
#### Advanced
+{:.no_toc}
These are advanced settings for fine tuning the auth integration, and
is not needed in most situations.
diff --git a/docs/_docs/02-05-persistence.md b/docs/_docs/02-05-persistence.md
new file mode 100644
index 000000000..a1d3a5e2f
--- /dev/null
+++ b/docs/_docs/02-05-persistence.md
@@ -0,0 +1,50 @@
+---
+title: User Persistence
+permalink: /docs/running/persistence/
+---
+
+One of the biggest problems on the internet today is the proliferation of
+sophisticated tracking systems and the outcome of invading user's privacy. This
+has had quite a big impact on our design of Talk, as we've had to work around
+the safeguards that are there to keep your data safe while still allowing our
+application to run smoothly on the page it's embedded on.
+
+---
+
+**Problem**: Safari has inconsistent behavior around localStorage when used
+within an iFrame.
+
+**Solution**: We set a cookie instead when Safari is detected to store the auth
+state.
+
+---
+
+**Problem**: Safari's default privacy settings block cookies from domains that
+do not match the current domain.
+
+**Solution**: When using Talk's built in auth, we will open a pop-out when
+setting the cookie, so that the domain of the setting domain matches the issuer.
+
+---
+
+**Problem**: When using a different domain for websockets, and using the built
+in auth solution, cookies are not set on that domain for use with Safari.
+
+**No Solution Exists**: It is our expectation that for users that must deploy
+Talk in environments that must run the websockets out of a separate domain will
+use and integrate their own auth solution. During the login process in Talk,
+users submit their user credentials to an auth endpoint, and receive a token
+back, or for Safari, a cookie. Aggressive defaults in Safari make it not
+possible to have one domain set cookies for another domain during this process.
+This results in a situation where we have no way to persist the auth credentials
+for this specific situation for the time being.
+
+---
+
+If you are using a custom auth solution, (Which involves providing the user's
+jwt token via the `auth_token` parameter on the call to
+`Talk.render(... {auth_token})` and creating the
+[tokenUserNotFound]({{ "/docs/plugins/server/" | absolute_url }}) hook to lookup
+that user) then concerns related to cookies/localStorage are moot! As it isn't
+necessary for Talk to maintain any state beyond what is passed to it via the
+pym bridge.
\ No newline at end of file
diff --git a/docs/_docs/04-04-plugins-server.md b/docs/_docs/04-04-plugins-server.md
index f807da141..e639cd308 100644
--- a/docs/_docs/04-04-plugins-server.md
+++ b/docs/_docs/04-04-plugins-server.md
@@ -85,7 +85,7 @@ configure the context plugin before it would be mounted at `context.plugins`.
This plugin above would mount at: `context.plugins.Slack`, or, if you're using
[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`.
-##### `Sort`
+##### Field: `Sort`
A special context hook, `Sort` will allow plugin authors to provide new
methods to sort data. An example is as follows:
@@ -269,22 +269,6 @@ The function is async, and should return the user object that was created in the
database, or null if the user wasn't found. The `jwt` parameter of the object
is the unpacked token, while `token` is the original jwt token string.
-### Routes
-
-#### Field: `router`
-
-```js
-(router) => {
- router.get('/api/v1/people', (req, res) => {
- res.json({people: [{name: 'Bob'}]});
- });
-}
-```
-
-The Router hook allows you to create a function that accepts the base express
-router where you can mount any amount of middleware/routes to do any form of
-action needed by external applications.
-
#### Field: `tags`
The tags hook allows a plugin to define tags that are code controlled (added
@@ -309,6 +293,22 @@ on how to create a hook for the `OFF_TOPIC` name:
You can refer to `models/schema/tag.js` for the available schema to match when
creating models to enable/disable specific features.
+### Routes
+
+#### Field: `router`
+
+```js
+(router) => {
+ router.get('/api/v1/people', (req, res) => {
+ res.json({people: [{name: 'Bob'}]});
+ });
+}
+```
+
+The Router hook allows you to create a function that accepts the base express
+router where you can mount any amount of middleware/routes to do any form of
+action needed by external applications.
+
### Authorization middleware
The following example creates the requisite callback route and passport
diff --git a/graph/loaders/users.js b/graph/loaders/users.js
index ac3493479..b943c60ed 100644
--- a/graph/loaders/users.js
+++ b/graph/loaders/users.js
@@ -3,6 +3,10 @@ const DataLoader = require('dataloader');
const util = require('./util');
const union = require('lodash/union');
+const {
+ SEARCH_OTHER_USERS,
+} = require('../../perms/constants');
+
const UsersService = require('../../services/users');
const UserModel = require('../../models/user');
@@ -28,12 +32,23 @@ const genUserByIDs = async (context, ids) => {
* @param {Object} query query terms to apply to the users query
*/
const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => {
-
let query = UserModel.find();
- if (action_type) {
- const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'});
- ids = ids ? union(ids, userIds) : userIds;
+ if (action_type || statuses) {
+ if (!user || !user.can(SEARCH_OTHER_USERS)) {
+ return null;
+ }
+
+ if (statuses) {
+ query = query.where({
+ status: {
+ $in: statuses
+ }
+ });
+ } else {
+ const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'});
+ ids = ids ? union(ids, userIds) : userIds;
+ }
}
if (ids) {
@@ -44,14 +59,6 @@ const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor,
});
}
- if (statuses) {
- query = query.where({
- status: {
- $in: statuses
- }
- });
- }
-
if (cursor) {
if (sortOrder === 'DESC') {
query = query.where({
diff --git a/package.json b/package.json
index 3d054e0d6..29ba80ae8 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "3.5.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
+ "private": true,
"scripts": {
"postinstall": "./bin/cli plugins reconcile --skip-remote",
"start": "./bin/cli serve -j -w",
@@ -11,8 +12,8 @@
"build": "WEBPACK=TRUE NODE_ENV=production webpack -p --config webpack.config.js --bail",
"prebuild-watch": "yarn generate-introspection",
"build-watch": "WEBPACK=TRUE NODE_ENV=development webpack --progress --config webpack.config.js --watch",
- "lint": "eslint --ext .json bin/* .",
- "lint-fix": "eslint bin/* . --fix",
+ "lint": "eslint --ext=.js --ext=.json bin/* .",
+ "lint-fix": "yarn lint --fix",
"test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
"test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec",
"heroku-postbuild": "./bin/cli plugins reconcile && yarn build",
diff --git a/routes/admin/index.js b/routes/admin/index.js
index 30bb69f5e..ab6189e85 100644
--- a/routes/admin/index.js
+++ b/routes/admin/index.js
@@ -1,7 +1,8 @@
const express = require('express');
const router = express.Router();
const {
- RECAPTCHA_PUBLIC
+ RECAPTCHA_PUBLIC,
+ WEBSOCKET_LIVE_URI,
} = require('../../config');
// Get /email-confirmation expects a signed JWT in the hash
@@ -20,7 +21,8 @@ router.get('/password-reset', (req, res) => {
router.get('*', (req, res) => {
const data = {
- TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC
+ TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC,
+ LIVE_URI: WEBSOCKET_LIVE_URI,
};
res.render('admin', {data});
diff --git a/routes/embed/index.js b/routes/embed/index.js
index 0aaa9ff9c..256d03b30 100644
--- a/routes/embed/index.js
+++ b/routes/embed/index.js
@@ -2,25 +2,24 @@ const express = require('express');
const router = express.Router();
const SettingsService = require('../../services/settings');
const {
- RECAPTCHA_PUBLIC
+ RECAPTCHA_PUBLIC,
+ WEBSOCKET_LIVE_URI,
} = require('../../config');
-router.use('/:embed', (req, res, next) => {
+router.use('/:embed', async (req, res, next) => {
switch (req.params.embed) {
- case 'stream':
- return SettingsService.retrieve()
- .then(({customCssUrl}) => {
- const data = {
- TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC
- };
+ case 'stream': {
+ const {customCssUrl} = await SettingsService.retrieve();
+ const data = {
+ TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC,
+ LIVE_URI: WEBSOCKET_LIVE_URI,
+ };
- return res.render('embed/stream', {customCssUrl, data});
- });
- default:
-
- // will return a 404.
- return next();
+ return res.render('embed/stream', {customCssUrl, data});
}
+ }
+
+ return next();
});
module.exports = router;