From 7051c76b40509dde3375dcede8645f8d352aae99 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 8 Sep 2017 17:37:27 +0700 Subject: [PATCH] Init step for plugins and bootstrap, introspection service --- client/coral-admin/src/index.js | 28 ++++---- client/coral-embed-stream/src/index.js | 67 ++++++++++--------- client/coral-framework/services/bootstrap.js | 42 +++++++++--- client/coral-framework/services/client.js | 6 +- .../coral-framework/services/introspection.js | 31 +++++++++ client/coral-framework/services/plugins.js | 8 +++ 6 files changed, 122 insertions(+), 60 deletions(-) create mode 100644 client/coral-framework/services/introspection.js 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); + } } /**