Files
talk/client/coral-framework/services/bootstrap.js
T
2018-02-08 23:06:01 +01:00

243 lines
7.1 KiB
JavaScript

import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
import { createStore } from './store';
import { createClient, apolloErrorReporter } from './client';
import pym from './pym';
import EventEmitter from 'eventemitter2';
import { createReduxEmitter } from './events';
import { createRestClient } from './rest';
import thunk from 'redux-thunk';
import { loadTranslations } from './i18n';
import bowser from 'bowser';
import noop from 'lodash/noop';
import { BASE_PATH } from 'coral-framework/constants/url';
import { createPluginsService } from './plugins';
import { createNotificationService } from './notification';
import { createGraphQLRegistry } from './graphqlRegistry';
import { createGraphQLService } from './graphql';
import globalFragments from 'coral-framework/graphql/fragments';
import {
createStorage,
createPymStorage,
} from 'coral-framework/services/storage';
import { createHistory } from 'coral-framework/services/history';
import { createIntrospection } from 'coral-framework/services/introspection';
import introspectionData from 'coral-framework/graphql/introspection.json';
import coreReducers from '../reducers';
import { checkLogin as checkLoginAction } from '../actions/auth';
import { mergeConfig } from '../actions/config';
import { setAuthToken, logout } from '../actions/auth';
/**
* getAuthToken returns the active auth token or null
* Note: this method does not have access to the cookie based token used by
* browsers that don't allow us to use cross domain iframe local storage.
* @return {string|null}
*/
const getAuthToken = (store, storage) => {
let state = store.getState();
if (state.config && state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios && storage) {
// Use local storage auth tokens where there's a stable api.
return storage.getItem('token');
}
return null;
};
function areWeInIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
function initExternalConfig({ store, pym, inIframe }) {
if (!inIframe) {
return;
}
return new Promise(resolve => {
pym.sendMessage('getConfig');
pym.onMessage('config', config => {
store.dispatch(mergeConfig(JSON.parse(config)));
resolve();
});
});
}
/**
* createContext setups and returns Talk dependencies that should be
* passed to `TalkProvider`.
* @param {Object} [config] configuration
* @param {Object} [config.reducers] extra reducers to add to redux
* @param {Array} [config.pluginsConfig] plugin configuration as returned by importing pluginsConfig
* @param {Object} [config.graphqlExtensions] additional extension to the graphql framework
* @param {Object} [config.notification] replace default notification service
* @param {Function} [config.init] run initialization e.g. to hydrate redux store
* @param {Function} [config.preInit] same as init but run and resolve before init and plugins init
* @return {Object} context
*/
export async function createContext({
reducers = {},
pluginsConfig = [],
graphqlExtension = {},
notification,
preInit,
init = noop,
checkLogin = true,
addExternalConfig = true,
} = {}) {
const inIframe = areWeInIframe();
const eventEmitter = new EventEmitter({ wildcard: true });
const localStorage = createStorage('localStorage');
const sessionStorage = createStorage('sessionStorage');
const pymLocalStorage = inIframe
? createPymStorage(pym, 'localStorage')
: localStorage;
const pymSessionStorage = inIframe
? createPymStorage(pym, 'sessionStorage')
: sessionStorage;
const history = createHistory(BASE_PATH);
const introspection = createIntrospection(introspectionData);
let store = null;
const token = () => {
// Try to get the token from localStorage. If it isn't here, it may
// be passed as a cookie.
// NOTE: THIS IS ONLY EVER EVALUATED ONCE, IN ORDER TO SEND A DIFFERNT
// TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT.
return getAuthToken(store, localStorage);
};
const rest = createRestClient({
uri: `${BASE_PATH}api/v1`,
token,
});
const staticConfig = getStaticConfiguration();
let { LIVE_URI: liveUri } = staticConfig;
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,
token,
introspectionData,
});
const plugins = createPluginsService(pluginsConfig);
const graphql = createGraphQLService(
createGraphQLRegistry(plugins.getSlotFragments.bind(plugins))
);
if (!notification) {
// Use default notification service (pym based)
notification = createNotificationService(pym);
}
const context = {
client,
pym,
plugins,
eventEmitter,
rest,
graphql,
notification,
localStorage,
sessionStorage,
history,
introspection,
pymLocalStorage,
pymSessionStorage,
inIframe,
};
// Load framework fragments.
Object.keys(globalFragments).forEach(key =>
graphql.registry.addFragment(key, globalFragments[key])
);
// Register graphql extension
graphql.registry.add(graphqlExtension);
// Register plugin graphql extensions.
plugins.getGraphQLExtensions().forEach(ext => graphql.registry.add(ext));
// Load plugin translations.
plugins.getTranslations().forEach(t => loadTranslations(t));
// Pass any events through our parent.
eventEmitter.onAny((eventName, value) => {
pym.sendMessage('event', JSON.stringify({ eventName, value }));
});
// Create our redux store.
const finalReducers = {
...coreReducers,
...reducers,
...plugins.getReducers(),
};
store = createStore(finalReducers, [
thunk.withExtraArgument(context),
createReduxEmitter(eventEmitter),
]);
context.store = store;
// Create apollo redux store.
context.apolloStore = createStore(
{
apollo: client.reducer(),
},
[client.middleware(), apolloErrorReporter, createReduxEmitter(eventEmitter)]
);
if (inIframe) {
pym.onMessage('login', token => {
if (token) {
store.dispatch(setAuthToken(token));
}
});
pym.onMessage('logout', () => {
store.dispatch(logout());
});
}
const preInitList = [];
store.dispatch(
mergeConfig({
static: staticConfig,
})
);
if (preInit) {
preInitList.push(preInit(context));
}
if (addExternalConfig) {
preInitList.push(initExternalConfig(context));
}
// Run pre initialization.
await Promise.all(preInitList);
if (checkLogin) {
store.dispatch(checkLoginAction());
}
// Run initialization.
await Promise.all([init(context), plugins.executeInit(context)]);
return context;
}