Merge pull request #921 from coralproject/change-websocket-uri

Change Websocket URI
This commit is contained in:
Wyatt Johnson
2017-09-07 11:21:54 -06:00
committed by GitHub
9 changed files with 136 additions and 36 deletions
+36 -2
View File
@@ -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,
+3
View File
@@ -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
//------------------------------------------------------------------------------
+2
View File
@@ -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:
+1 -1
View File
@@ -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
+10
View File
@@ -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 `<pathname>` 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.
+50
View File
@@ -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.
+17 -17
View File
@@ -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
+4 -2
View File
@@ -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});
+13 -14
View File
@@ -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;