diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js
index 5df2ce391..736d3efc9 100644
--- a/client/coral-admin/src/index.js
+++ b/client/coral-admin/src/index.js
@@ -12,23 +12,23 @@ import {toast} from 'react-toastify';
import {createNotificationService} from './services/notification';
import {hideShortcutsNote} from './actions/moderation';
-function hidrateStore({store, storage}) {
+smoothscroll.polyfill();
+
+function init({store, storage}) {
if (storage && storage.getItem('coral:shortcutsNote') === 'hide') {
store.dispatch(hideShortcutsNote());
}
}
-smoothscroll.polyfill();
+async function main() {
+ const notification = createNotificationService(toast);
+ const context = await createContext({reducers, graphqlExtension, pluginsConfig, notification, init});
+ render(
+
+
+
+ , document.querySelector('#root')
+ );
+}
-const notification = createNotificationService(toast);
-const context = createContext({reducers, graphqlExtension, pluginsConfig, notification});
-
-// hidrate Store with external data.
-hidrateStore(context);
-
-render(
-
-
-
- , document.querySelector('#root')
-);
+main();
diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js
index 5d72ea3e7..c8d3101ab 100644
--- a/client/coral-embed-stream/src/index.js
+++ b/client/coral-embed-stream/src/index.js
@@ -10,11 +10,6 @@ import reducers from './reducers';
import TalkProvider from 'coral-framework/components/TalkProvider';
import pluginsConfig from 'pluginsConfig';
-const context = createContext({reducers, graphqlExtension, pluginsConfig});
-
-// TODO: move init code into `bootstrap` service after auth has been refactored.
-const {store, pym} = context;
-
function inIframe() {
try {
return window.self !== window.top;
@@ -23,37 +18,45 @@ function inIframe() {
}
}
-function init(config = {}) {
- store.dispatch(addExternalConfig(config));
- store.dispatch(checkLogin());
-}
+// TODO: move init code into `bootstrap` service after auth has been refactored.
+function preInit({store, pym}) {
-// Don't run this in the popup.
-if (!window.opener) {
- if (inIframe()) {
+ // TODO: This is popup specific code and needs to be refactored.
+ if (!inIframe()) {
+ store.dispatch(addExternalConfig({}));
+ store.dispatch(checkLogin());
+ return;
+ }
+
+ pym.onMessage('login', (token) => {
+ if (token) {
+ store.dispatch(handleAuthToken(token));
+ }
+ store.dispatch(checkLogin());
+ });
+
+ pym.onMessage('logout', () => {
+ store.dispatch(logout());
+ });
+
+ return new Promise((resolve) => {
pym.sendMessage('getConfig');
pym.onMessage('config', (config) => {
- init(JSON.parse(config));
- });
-
- pym.onMessage('login', (token) => {
- if (token) {
- store.dispatch(handleAuthToken(token));
- }
+ store.dispatch(addExternalConfig(config));
store.dispatch(checkLogin());
+ resolve();
});
-
- pym.onMessage('logout', () => {
- store.dispatch(logout());
- });
- } else {
- init();
- }
+ });
}
-render(
-
-
-
- , document.querySelector('#talk-embed-stream-container')
-);
+async function main() {
+ const context = await createContext({reducers, graphqlExtension, pluginsConfig, preInit});
+ render(
+
+
+
+ , document.querySelector('#talk-embed-stream-container')
+ );
+}
+
+main();
diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js
index a3ae5b18e..5b627125e 100644
--- a/client/coral-framework/services/bootstrap.js
+++ b/client/coral-framework/services/bootstrap.js
@@ -14,6 +14,8 @@ import {createGraphQLRegistry} from './graphqlRegistry';
import globalFragments from 'coral-framework/graphql/fragments';
import {createStorage} 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';
/**
* getStaticConfiguration will return a singleton of the static configuration
@@ -60,17 +62,27 @@ const getAuthToken = (store, storage) => {
/**
* 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
- * @return {Object} context
+ * @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 function createContext({reducers = {}, pluginsConfig = [], graphqlExtension = {}, notification} = {}) {
+export async function createContext({
+ reducers = {},
+ pluginsConfig = [],
+ graphqlExtension = {},
+ notification,
+ preInit,
+ init,
+} = {}) {
const eventEmitter = new EventEmitter({wildcard: true});
const storage = createStorage();
const history = createHistory(BASE_PATH);
+ const introspection = createIntrospection(introspectionData);
let store = null;
const token = () => {
@@ -104,6 +116,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
uri: `${BASE_PATH}api/v1/graph/ql`,
liveUri,
token,
+ introspectionData,
});
const plugins = createPluginsService(pluginsConfig);
const graphqlRegistry = createGraphQLRegistry(plugins.getSlotFragments.bind(plugins));
@@ -123,6 +136,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
notification,
storage,
history,
+ introspection,
};
// Load framework fragments.
@@ -155,8 +169,14 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
createReduxEmitter(eventEmitter),
]);
- return {
- ...context,
- store,
- };
+ context.store = store;
+
+ // Run pre initialization.
+ if (preInit) {
+ await preInit(context);
+ }
+
+ // Run initialization.
+ await Promise.all([init, plugins.executeInit(context)]);
+ return context;
}
diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js
index 5907488e3..52088ad79 100644
--- a/client/coral-framework/services/client.js
+++ b/client/coral-framework/services/client.js
@@ -1,7 +1,6 @@
import ApolloClient, {addTypename, IntrospectionFragmentMatcher, createNetworkInterface} from 'apollo-client';
import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
import MessageTypes from 'subscriptions-transport-ws/dist/message-types';
-import introspectionQueryResultData from '../graphql/introspection.json';
// Redux middleware to report any errors to the console.
export const apolloErrorReporter = () => (next) => (action) => {
@@ -21,10 +20,11 @@ function resolveToken(token) {
* @param {string|function} [options.token] auth token
* @param {string} [options.uri] uri of the graphql server
* @param {string} [options.liveUri] uri of the graphql subscription server
+ * @param {Object} [options.introspectionData] introspection query result data
* @return {Object} apollo client
*/
export function createClient(options = {}) {
- const {token, uri, liveUri} = options;
+ const {token, uri, liveUri, introspectionData} = options;
const wsClient = new SubscriptionClient(liveUri, {
reconnect: true,
lazy: true,
@@ -63,7 +63,7 @@ export function createClient(options = {}) {
const client = new ApolloClient({
connectToDevTools: true,
addTypename: true,
- fragmentMatcher: new IntrospectionFragmentMatcher({introspectionQueryResultData}),
+ fragmentMatcher: new IntrospectionFragmentMatcher({introspectionQueryResultData: introspectionData}),
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
diff --git a/client/coral-framework/services/introspection.js b/client/coral-framework/services/introspection.js
new file mode 100644
index 000000000..fd82d41d6
--- /dev/null
+++ b/client/coral-framework/services/introspection.js
@@ -0,0 +1,31 @@
+class Introspection {
+ _enums=null;
+
+ constructor(data) {
+ this._enums = data.__schema.types
+ .filter((type) => type.kind === 'ENUM')
+ .reduce((obj, enumType) => {
+ obj[enumType.name] = enumType.enumValues.map((value) => value.name);
+ return obj;
+ }, {});
+ }
+
+ /**
+ * isValidEnumValue returns true when given enum and value exists.
+ * @param {string} name
+ * @param {string} value
+ * @return {boolean}
+ */
+ isValidEnumValue(name, value) {
+ return this._enums[name] && this._enums[name].indexOf(value) >= 0;
+ }
+}
+
+/**
+ * createIntrospection returns a introspection service
+ * @param {Object} data introspection query data
+ * @return {Object} introspection service
+ */
+export function createIntrospection(data) {
+ return new Introspection(data);
+}
diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js
index 301428c12..dbda672ba 100644
--- a/client/coral-framework/services/plugins.js
+++ b/client/coral-framework/services/plugins.js
@@ -172,6 +172,14 @@ class PluginsService {
.map((o) => ({[camelize(o.name)] : o.module.reducer}))
);
}
+
+ async executeInit(context) {
+ const results = this.plugins
+ .map((o) => o.module.init)
+ .filter((fn) => fn)
+ .map((fn) => fn(context));
+ await Promise.all(results);
+ }
}
/**