Remove global dependency on store, rest, pym and apollo client

This commit is contained in:
Chi Vinh Le
2017-08-22 21:59:41 +07:00
parent d47287fbd5
commit 54ea0b1a3f
20 changed files with 304 additions and 356 deletions
+4 -5
View File
@@ -8,7 +8,6 @@ import {
UPDATE_ASSETS
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
/**
@@ -17,9 +16,9 @@ import t from 'coral-framework/services/i18n';
// Fetch a page of assets
// Get comments to fill each of the three lists on the mod queue
export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch) => {
export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch, _, {rest}) => {
dispatch({type: FETCH_ASSETS_REQUEST});
return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`)
return rest(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`)
.then(({result, count}) =>
dispatch({type: FETCH_ASSETS_SUCCESS,
assets: result,
@@ -34,9 +33,9 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte
// Update an asset state
// Get comments to fill each of the three lists on the mod queue
export const updateAssetState = (id, closedAt) => (dispatch) => {
export const updateAssetState = (id, closedAt) => (dispatch, _, {rest}) => {
dispatch({type: UPDATE_ASSET_STATE_REQUEST, id, closedAt});
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
return rest(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
.then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch((error) => {
console.error(error);
+11 -13
View File
@@ -1,8 +1,6 @@
import bowser from 'bowser';
import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/request';
import * as Storage from 'coral-framework/helpers/storage';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
import jwtDecode from 'jwt-decode';
@@ -10,7 +8,7 @@ import jwtDecode from 'jwt-decode';
// SIGN IN
//==============================================================================
export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => {
export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, {rest, client}) => {
dispatch({type: actions.LOGIN_REQUEST});
const params = {
@@ -27,7 +25,7 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) =>
};
}
return coralApi('/auth/local', params)
return rest('/auth/local', params)
.then(({user, token}) => {
if (!user) {
@@ -38,7 +36,7 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) =>
}
dispatch(handleAuthToken(token));
resetWebsocket();
client.resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
@@ -84,11 +82,11 @@ const forgotPasswordFailure = (error) => ({
error,
});
export const requestPasswordReset = (email) => (dispatch) => {
export const requestPasswordReset = (email) => (dispatch, _, {rest}) => {
dispatch(forgotPasswordRequest(email));
const redirectUri = location.href;
return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
return rest('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPasswordSuccess()))
.catch((error) => {
console.error(error);
@@ -116,9 +114,9 @@ const checkLoginFailure = (error) => ({
error
});
export const checkLogin = () => (dispatch) => {
export const checkLogin = () => (dispatch, _, {rest, client}) => {
dispatch(checkLoginRequest());
return coralApi('/auth')
return rest('/auth')
.then(({user}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
@@ -127,7 +125,7 @@ export const checkLogin = () => (dispatch) => {
return dispatch(checkLoginFailure('not logged in'));
}
resetWebsocket();
client.resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
@@ -141,12 +139,12 @@ export const checkLogin = () => (dispatch) => {
// LOGOUT
//==============================================================================
export const logout = () => (dispatch) => {
return coralApi('/auth', {method: 'DELETE'}).then(() => {
export const logout = () => (dispatch, _, {rest, client}) => {
return rest('/auth', {method: 'DELETE'}).then(() => {
Storage.removeItem('token');
// Reset the websocket.
resetWebsocket();
client.resetWebsocket();
dispatch({type: actions.LOGOUT});
});
+6 -7
View File
@@ -14,13 +14,12 @@ import {
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const fetchAccounts = (query = {}) => (dispatch) => {
export const fetchAccounts = (query = {}) => (dispatch, _, {rest}) => {
dispatch(requestFetchAccounts());
coralApi(`/users?${qs.stringify(query)}`)
rest(`/users?${qs.stringify(query)}`)
.then(({result, page, count, limit, totalPages}) =>{
dispatch({
type: FETCH_COMMENTERS_SUCCESS,
@@ -51,15 +50,15 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => (dispatch) => {
return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
export const setRole = (id, role) => (dispatch, _, {rest}) => {
return rest(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
export const setCommenterStatus = (id, status) => (dispatch) => {
return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}})
export const setCommenterStatus = (id, status) => (dispatch, _, {rest}) => {
return rest(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
+4 -5
View File
@@ -1,4 +1,3 @@
import coralApi from 'coral-framework/helpers/request';
import * as actions from '../constants/install';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
@@ -106,10 +105,10 @@ export const submitUser = () => (dispatch, getState) => {
});
};
export const finishInstall = () => (dispatch, getState) => {
export const finishInstall = () => (dispatch, getState, {rest}) => {
const data = getState().install.data;
dispatch(installRequest());
return coralApi('/setup', {method: 'POST', body: data})
return rest('/setup', {method: 'POST', body: data})
.then(() => {
dispatch(installSuccess());
dispatch(nextStep());
@@ -129,9 +128,9 @@ 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) => {
export const checkInstall = (next) => (dispatch, _, {rest}) => {
dispatch(checkInstallRequest());
coralApi('/setup')
rest('/setup')
.then(({installed}) => {
dispatch(checkInstallSuccess(installed));
if (installed) {
+4 -5
View File
@@ -1,4 +1,3 @@
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
@@ -14,9 +13,9 @@ export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED';
export const WORDLIST_UPDATED = 'WORDLIST_UPDATED';
export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED';
export const fetchSettings = () => (dispatch) => {
export const fetchSettings = () => (dispatch, _, {rest}) => {
dispatch({type: SETTINGS_LOADING});
coralApi('/settings')
rest('/settings')
.then((settings) => {
dispatch({type: SETTINGS_RECEIVED, settings});
})
@@ -41,13 +40,13 @@ export const updateDomainlist = (listName, list) => {
return {type: DOMAINLIST_UPDATED, listName, list};
};
export const saveSettingsToServer = () => (dispatch, getState) => {
export const saveSettingsToServer = () => (dispatch, getState, {rest}) => {
let settings = getState().settings;
if (settings.charCount) {
settings.charCount = parseInt(settings.charCount);
}
dispatch({type: SAVE_SETTINGS_LOADING});
coralApi('/settings', {method: 'PUT', body: settings})
rest('/settings', {method: 'PUT', body: settings})
.then(() => {
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
})
+6 -7
View File
@@ -1,4 +1,3 @@
import coralApi from '../../../coral-framework/helpers/request';
import * as userTypes from '../constants/users';
import t from 'coral-framework/services/i18n';
@@ -7,9 +6,9 @@ import t from 'coral-framework/services/i18n';
*/
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
return (dispatch) => {
return (dispatch, _, {rest}) => {
dispatch({type: userTypes.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
return rest(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch((error) => {
console.error(error);
@@ -21,8 +20,8 @@ export const userStatusUpdate = (status, userId, commentId) => {
// change status of a user
export const sendNotificationEmail = (userId, subject, body) => {
return (dispatch) => {
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
return (dispatch, _, {rest}) => {
return rest(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
@@ -33,8 +32,8 @@ export const sendNotificationEmail = (userId, subject, body) => {
// let a user edit their username
export const enableUsernameEdit = (userId) => {
return (dispatch) => {
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
return (dispatch, _, {rest}) => {
return rest(`/users/${userId}/username-enable`, {method: 'POST'})
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
+4 -17
View File
@@ -1,34 +1,21 @@
import React from 'react';
import {render} from 'react-dom';
import smoothscroll from 'smoothscroll-polyfill';
import EventEmitter from 'eventemitter2';
import TalkProvider from 'coral-framework/components/TalkProvider';
import {getClient} from 'coral-framework/services/client';
import store from './services/store';
import {createContext} from 'coral-framework/services/bootstrap';
import reducers from './reducers';
import App from './components/App';
import 'react-mdl/extra/material.js';
import './graphql';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
import plugins from 'pluginsConfig';
const eventEmitter = new EventEmitter();
const client = getClient();
const context = createContext(reducers, plugins);
// TODO: pass redux actions through the emitter.
loadPluginsTranslations();
injectPluginsReducers();
smoothscroll.polyfill();
render(
<TalkProvider
eventEmitter={eventEmitter}
client={client}
store={store}
plugins={plugins}
{...context}
>
<App />
</TalkProvider>
@@ -1,5 +1,4 @@
import * as actions from '../constants/asset';
import coralApi from 'coral-framework/helpers/request';
import {addNotification} from 'coral-framework/actions/notification';
import t from 'coral-framework/services/i18n';
@@ -12,10 +11,10 @@ const updateAssetSettingsRequest = () => ({type: actions.UPDATE_ASSET_SETTINGS_R
const updateAssetSettingsSuccess = (settings) => ({type: actions.UPDATE_ASSET_SETTINGS_SUCCESS, settings});
const updateAssetSettingsFailure = (error) => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE, error});
export const updateConfiguration = (newConfig) => (dispatch, getState) => {
export const updateConfiguration = (newConfig) => (dispatch, getState, {rest}) => {
const assetId = getState().asset.id;
dispatch(updateAssetSettingsRequest());
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
rest(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
dispatch(addNotification('success', t('framework.success_update_settings')));
dispatch(updateAssetSettingsSuccess(newConfig));
@@ -26,10 +25,10 @@ export const updateConfiguration = (newConfig) => (dispatch, getState) => {
});
};
export const updateOpenStream = (closedBody) => (dispatch, getState) => {
export const updateOpenStream = (closedBody) => (dispatch, getState, {rest}) => {
const assetId = getState().asset.id;
dispatch(fetchAssetRequest());
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
rest(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
.then(() => {
dispatch(addNotification('success', t('framework.success_update_settings')));
dispatch(fetchAssetSuccess(closedBody));
+26 -30
View File
@@ -2,11 +2,8 @@ import jwtDecode from 'jwt-decode';
import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as Storage from 'coral-framework/helpers/storage';
import coralApi, {base} from 'coral-framework/helpers/request';
import pym from 'coral-framework/services/pym';
import {addNotification} from 'coral-framework/actions/notification';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
export const showSignInDialog = () => ({
@@ -59,9 +56,9 @@ export const updateUsername = ({username}) => ({
username
});
export const createUsername = (userId, formData) => (dispatch) => {
export const createUsername = (userId, formData) => (dispatch, _, {rest}) => {
dispatch(createUsernameRequest());
coralApi('/account/username', {method: 'PUT', body: formData})
rest('/account/username', {method: 'PUT', body: formData})
.then(() => {
dispatch(createUsernameSuccess());
dispatch(hideCreateUsernameDialog());
@@ -123,10 +120,10 @@ export const handleAuthToken = (token) => (dispatch) => {
//==============================================================================
export const fetchSignIn = (formData) => {
return (dispatch) => {
return (dispatch, _, {rest}) => {
dispatch(signInRequest());
return coralApi('/auth/local', {method: 'POST', body: formData})
return rest('/auth/local', {method: 'POST', body: formData})
.then(({token}) => {
if (!bowser.safari && !bowser.ios) {
dispatch(handleAuthToken(token));
@@ -171,10 +168,10 @@ const signInFacebookFailure = (error) => ({
error
});
export const fetchSignInFacebook = () => (dispatch) => {
export const fetchSignInFacebook = () => (dispatch, _, {rest}) => {
dispatch(signInFacebookRequest());
window.open(
`${base}/auth/facebook`,
`${rest.uri}/auth/facebook`,
'Continue with Facebook',
'menubar=0,resizable=0,width=500,height=500,top=200,left=500'
);
@@ -188,10 +185,10 @@ const signUpFacebookRequest = () => ({
type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST
});
export const fetchSignUpFacebook = () => (dispatch) => {
export const fetchSignUpFacebook = () => (dispatch, _, {rest}) => {
dispatch(signUpFacebookRequest());
window.open(
`${base}/auth/facebook`,
`${rest.uri}/auth/facebook`,
'Continue with Facebook',
'menubar=0,resizable=0,width=500,height=500,top=200,left=500'
);
@@ -220,11 +217,11 @@ const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST});
const signUpSuccess = (user) => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
const signUpFailure = (error) => ({type: actions.FETCH_SIGNUP_FAILURE, error});
export const fetchSignUp = (formData) => (dispatch, getState) => {
export const fetchSignUp = (formData) => (dispatch, getState, {rest}) => {
const redirectUri = getState().auth.redirectUri;
dispatch(signUpRequest());
coralApi('/users', {
rest('/users', {
method: 'POST',
body: formData,
headers: {'X-Pym-Url': redirectUri}
@@ -256,10 +253,10 @@ const forgotPasswordFailure = (error) => ({
error,
});
export const fetchForgotPassword = (email) => (dispatch, getState) => {
export const fetchForgotPassword = (email) => (dispatch, getState, {rest}) => {
dispatch(forgotPasswordRequest(email));
const redirectUri = getState().auth.redirectUri;
coralApi('/account/password/reset', {
rest('/account/password/reset', {
method: 'POST',
body: {email, loc: redirectUri}
})
@@ -275,16 +272,15 @@ export const fetchForgotPassword = (email) => (dispatch, getState) => {
// LOGOUT
//==============================================================================
export const logout = () => (dispatch) => {
return coralApi('/auth', {method: 'DELETE'}).then(() => {
Storage.removeItem('token');
export const logout = () => async (dispatch, _, {rest, client, pym}) => {
await rest('/auth', {method: 'DELETE'});
Storage.removeItem('token');
// Reset the websocket.
resetWebsocket();
// Reset the websocket.
client.resetWebsocket();
dispatch({type: actions.LOGOUT});
pym.sendMessage('coral-auth-changed');
});
dispatch({type: actions.LOGOUT});
pym.sendMessage('coral-auth-changed');
};
//==============================================================================
@@ -300,9 +296,9 @@ const checkLoginSuccess = (user, isAdmin) => ({
isAdmin
});
export const checkLogin = () => (dispatch) => {
export const checkLogin = () => (dispatch, _, {rest, client, pym}) => {
dispatch(checkLoginRequest());
coralApi('/auth')
rest('/auth')
.then((result) => {
if (!result.user) {
Storage.removeItem('token');
@@ -310,7 +306,7 @@ export const checkLogin = () => (dispatch) => {
}
// Reset the websocket.
resetWebsocket();
client.resetWebsocket();
dispatch(checkLoginSuccess(result.user));
pym.sendMessage('coral-auth-changed', JSON.stringify(result.user));
@@ -351,10 +347,10 @@ const verifyEmailFailure = () => ({
type: actions.VERIFY_EMAIL_FAILURE
});
export const requestConfirmEmail = (email) => (dispatch, getState) => {
export const requestConfirmEmail = (email) => (dispatch, getState, {rest}) => {
const redirectUri = getState().auth.redirectUri;
dispatch(verifyEmailRequest());
return coralApi('/users/resend-verify', {
return rest('/users/resend-verify', {
method: 'POST',
body: {email},
headers: {'X-Pym-Url': redirectUri}
@@ -387,8 +383,8 @@ export const setRedirectUri = (uri) => ({
const editUsernameFailure = (error) => ({type: actions.EDIT_USERNAME_FAILURE, error});
const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
export const editName = (username) => (dispatch) => {
return coralApi('/account/username', {method: 'PUT', body: {username}})
export const editName = (username) => (dispatch, _, {rest}) => {
return rest('/account/username', {method: 'PUT', body: {username}})
.then(() => {
dispatch(editUsernameSuccess());
dispatch(addNotification('success', t('framework.success_name_update')));
@@ -1,11 +1,10 @@
import pym from 'coral-framework/services/pym';
import * as actions from '../constants/stream';
import {buildUrl} from 'coral-framework/utils/url';
import queryString from 'query-string';
export const setActiveReplyBox = (id) => ({type: actions.SET_ACTIVE_REPLY_BOX, id});
export const viewAllComments = () => {
export const viewAllComments = () => (dispatch, _, {pym}) => {
const search = queryString.stringify({
...queryString.parse(location.search),
@@ -24,10 +23,10 @@ export const viewAllComments = () => {
pym.sendMessage('coral-view-all-comments');
} catch (e) { /* not sure if we're worried about old browsers */ }
return {type: actions.VIEW_ALL_COMMENTS};
dispatch({type: actions.VIEW_ALL_COMMENTS});
};
export const viewComment = (id) => {
export const viewComment = (id) => (dispatch, _, {pym}) => {
const search = queryString.stringify({
...queryString.parse(location.search),
@@ -46,7 +45,7 @@ export const viewComment = (id) => {
pym.sendMessage('coral-view-comment', id);
} catch (e) { /* not sure if we're worried about old browsers */ }
return {type: actions.VIEW_COMMENT, id};
dispatch({type: actions.VIEW_COMMENT, id});
};
export const addCommentClassName = (className) => ({
+5 -25
View File
@@ -4,24 +4,16 @@ import {render} from 'react-dom';
import {checkLogin, handleAuthToken, logout} from 'coral-embed-stream/src/actions/auth';
import './graphql';
import {addExternalConfig} from 'coral-embed-stream/src/actions/config';
import {getStore, injectReducers, addListener} from 'coral-framework/services/store';
import {getClient} from 'coral-framework/services/client';
import {createReduxEmitter} from 'coral-framework/services/events';
import pym from 'coral-framework/services/pym';
import {createContext} from 'coral-framework/services/bootstrap';
import AppRouter from './AppRouter';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
import reducers from './reducers';
import EventEmitter from 'eventemitter2';
import TalkProvider from 'coral-framework/components/TalkProvider';
import plugins from 'pluginsConfig';
const store = getStore();
const client = getClient();
const eventEmitter = new EventEmitter({wildcard: true});
const context = createContext(reducers, plugins);
loadPluginsTranslations();
injectPluginsReducers();
injectReducers(reducers);
// TODO: move init code into `bootstrap` service after auth has been refactored.
const {store, pym} = context;
function inIframe() {
try {
@@ -57,23 +49,11 @@ if (!window.opener) {
} else {
init();
}
// Pass any events through our parent.
eventEmitter.onAny((eventName, value) => {
pym.sendMessage('event', JSON.stringify({eventName, value}));
});
// Add a redux listener to pass through all actions to our event emitter.
addListener(createReduxEmitter(eventEmitter));
}
render(
<TalkProvider
eventEmitter={eventEmitter}
client={client}
store={store}
pym={pym}
plugins={plugins}
{...context}
>
<AppRouter />
</TalkProvider>
@@ -12,9 +12,9 @@ class TalkProvider extends React.Component {
}
render() {
const {children, client, store, plugins} = this.props;
const {children, client, store} = this.props;
return (
<ApolloProvider client={client} store={store} plugins={plugins}>
<ApolloProvider client={client} store={store}>
{children}
</ApolloProvider>
);
+3 -10
View File
@@ -6,8 +6,6 @@ import flattenDeep from 'lodash/flattenDeep';
import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import mapValues from 'lodash/mapValues';
import {loadTranslations} from 'coral-framework/services/i18n';
import {injectReducers} from 'coral-framework/services/store';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import camelize from './camelize';
import plugins from 'pluginsConfig';
@@ -133,23 +131,18 @@ export function getModQueueConfigs() {
.filter((o) => o));
}
function getTranslations() {
export function getTranslations(plugins) {
return plugins
.map((o) => o.module.translations)
.filter((o) => o);
}
export function loadPluginsTranslations() {
getTranslations().forEach((t) => loadTranslations(t));
}
export function injectPluginsReducers() {
const reducers = merge(
export function getReducers(plugins) {
return merge(
...plugins
.filter((o) => o.module.reducer)
.map((o) => ({[camelize(o.name)] : o.module.reducer}))
);
injectReducers(reducers);
}
function addMetaDataToSlotComponents() {
-84
View File
@@ -1,84 +0,0 @@
import bowser from 'bowser';
import * as Storage from './storage';
import merge from 'lodash/merge';
import {getStore} from 'coral-framework/services/store';
import {BASE_PATH} from 'coral-framework/constants/url';
/**
* 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}
*/
export const getAuthToken = () => {
let state = getStore().getState();
if (state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios) {
// Use local storage auth tokens where there's a stable api.
return Storage.getItem('token');
}
return null;
};
const buildOptions = (inputOptions = {}) => {
const defaultOptions = {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'same-origin'
};
let options = merge({}, defaultOptions, inputOptions);
// Apply authToken header
let authToken = getAuthToken();
if (authToken !== null) {
options.headers.Authorization = `Bearer ${authToken}`;
}
if (options.method.toLowerCase() !== 'get') {
options.body = JSON.stringify(options.body);
}
return options;
};
const handleResp = (res) => {
if (res.status > 399) {
return res.json().then((err) => {
let message = err.message || res.status;
const error = new Error();
if (err.error && err.error.metadata && err.error.metadata.message) {
error.metadata = err.error.metadata.message;
}
if (err.error && err.error.translation_key) {
error.translation_key = err.error.translation_key;
}
error.message = message;
error.status = res.status;
throw error;
});
} else if (res.status === 204) {
return res.text();
} else {
return res.json();
}
};
export const base = `${BASE_PATH}api/v1`;
export default (url, options) => {
return fetch(`${base}${url}`, buildOptions(options)).then(handleResp);
};
+92
View File
@@ -0,0 +1,92 @@
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 {getTranslations, getReducers} from '../helpers/plugins';
import bowser from 'bowser';
import * as Storage from '../helpers/storage';
import {BASE_PATH} from 'coral-framework/constants/url';
/**
* 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) => {
let state = store.getState();
if (state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios) {
// Use local storage auth tokens where there's a stable api.
return Storage.getItem('token');
}
return null;
};
export function createContext(reducers, plugins) {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const eventEmitter = new EventEmitter({wildcard: true});
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);
};
const rest = createRestClient({
uri: `${BASE_PATH}api/v1`,
token,
});
const client = createClient({
uri: `${BASE_PATH}api/v1/graph/ql`,
liveUri: `${protocol}://${location.host}${BASE_PATH}api/v1/live`,
token,
});
const context = {
client,
pym,
plugins,
eventEmitter,
rest,
};
// Load plugin translations.
getTranslations(plugins).forEach((t) => loadTranslations(t));
// Pass any events through our parent.
eventEmitter.onAny((eventName, value) => {
pym.sendMessage('event', JSON.stringify({eventName, value}));
});
const finalReducers = {
...reducers,
...getReducers(plugins),
apollo: client.reducer(),
};
store = createStore(finalReducers, [
client.middleware(),
thunk.withExtraArgument(context),
apolloErrorReporter,
createReduxEmitter(eventEmitter),
]);
return {
...context,
store,
};
}
+55 -44
View File
@@ -1,64 +1,55 @@
import ApolloClient, {addTypename, IntrospectionFragmentMatcher} from 'apollo-client';
import {networkInterface} from './transport';
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 {getAuthToken} from '../helpers/request';
import introspectionQueryResultData from '../graphql/introspection.json';
import {BASE_PATH} from 'coral-framework/constants/url';
let client, wsClient = null, wsClientToken = null;
export function resetWebsocket() {
if (wsClient === null) {
// Nothing to reset!
return;
// Redux middleware to report any errors to the console.
export const apolloErrorReporter = () => (next) => (action) => {
if (action.type === 'APOLLO_QUERY_ERROR') {
console.error(action.error);
}
return next(action);
};
// Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close();
// Reconnect to the server.
wsClient.connect();
// Reregister all subscriptions (uses non public api).
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
Object.keys(wsClient.operations).forEach((id) => {
wsClient.sendMessage(id, MessageTypes.GQL_START, wsClient.operations[id].options);
});
}
export function getClient(options = {}) {
if (client) {
return client;
}
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
wsClient = new SubscriptionClient(`${protocol}://${location.host}${BASE_PATH}api/v1/live`, {
export function createClient(options = {}) {
const {token, uri, liveUri, ...apolloOptions} = options;
const wsClient = new SubscriptionClient(liveUri, {
reconnect: true,
lazy: true,
connectionParams: {
get token() {
wsClientToken = getAuthToken();
// 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 wsClientToken;
}
token,
}
});
const networkInterface = createNetworkInterface({
uri,
opts: {
credentials: 'same-origin'
}
});
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
let authToken = typeof token === 'function' ? token() : token;
if (authToken) {
req.options.headers['authorization'] = `Bearer ${authToken}`;
}
next();
}
}]);
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
wsClient,
);
client = new ApolloClient({
...options,
const client = new ApolloClient({
...apolloOptions,
connectToDevTools: true,
addTypename: true,
fragmentMatcher: new IntrospectionFragmentMatcher({introspectionQueryResultData}),
@@ -72,5 +63,25 @@ export function getClient(options = {}) {
networkInterface: networkInterfaceWithSubscriptions,
});
client.resetWebsocket = () => {
if (wsClient === null) {
// Nothing to reset!
return;
}
// Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close();
// Reconnect to the server.
wsClient.connect();
// Reregister all subscriptions (uses non public api).
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
Object.keys(wsClient.operations).forEach((id) => {
wsClient.sendMessage(id, MessageTypes.GQL_START, wsClient.operations[id].options);
});
};
return client;
}
+4 -2
View File
@@ -1,5 +1,5 @@
export function createReduxEmitter(eventEmitter) {
return (action) => {
return () => (next) => (action) => {
// Handle apollo actions.
if (action.type.startsWith('APOLLO_')) {
@@ -7,8 +7,10 @@ export function createReduxEmitter(eventEmitter) {
const {operationName, variables, result: {data}} = action;
eventEmitter.emit(`subscription.${operationName}.data`, {variables, data});
}
return;
return next(action);
}
eventEmitter.emit(`action.${action.type}`, {action});
return next(action);
};
}
+62
View File
@@ -0,0 +1,62 @@
import merge from 'lodash/merge';
const buildOptions = (inputOptions = {}) => {
const defaultOptions = {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'same-origin'
};
const options = merge({}, defaultOptions, inputOptions);
if (options.method.toLowerCase() !== 'get') {
options.body = JSON.stringify(options.body);
}
return options;
};
const handleResp = (res) => {
if (res.status > 399) {
return res.json().then((err) => {
let message = err.message || res.status;
const error = new Error();
if (err.error && err.error.metadata && err.error.metadata.message) {
error.metadata = err.error.metadata.message;
}
if (err.error && err.error.translation_key) {
error.translation_key = err.error.translation_key;
}
error.message = message;
error.status = res.status;
throw error;
});
} else if (res.status === 204) {
return res.text();
} else {
return res.json();
}
};
export function createRestClient(options) {
const {token, uri} = options;
const client = (path, options) => {
const authToken = typeof token === 'function' ? token() : token;
let opts = options;
if (authToken) {
opts = merge({}, options, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
}
return fetch(`${uri}${path}`, buildOptions(opts)).then(handleResp);
};
client.uri = uri;
return client;
}
+8 -53
View File
@@ -1,66 +1,21 @@
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import {getClient} from './client';
import {createStore as reduxCreateStore, combineReducers, applyMiddleware, compose} from 'redux';
let listeners = [];
export function injectReducers(reducers) {
const store = getStore();
store.coralReducers = {...store.coralReducers, ...reducers};
store.replaceReducer(combineReducers(store.coralReducers));
}
/**
* Add a action listener to the redux store.
* The action is passed as the first argument to the callback.
*/
export function addListener(cb) {
listeners.push(cb);
}
export function getStore() {
if (window.coralStore) {
return window.coralStore;
}
const apolloErrorReporter = () => (next) => (action) => {
if (action.type === 'APOLLO_QUERY_ERROR') {
console.error(action.error);
}
return next(action);
};
const customListener = () => (next) => (action) => {
listeners.forEach((cb) => {cb(action);});
return next(action);
};
const middlewares = [
export function createStore(reducers, middlewares = []) {
const enhancers = [
applyMiddleware(
getClient().middleware(),
thunk,
apolloErrorReporter,
customListener,
...middlewares,
),
];
if (window.devToolsExtension) {
// we can't have the last argument of compose() be undefined
middlewares.push(window.devToolsExtension());
enhancers.push(window.devToolsExtension());
}
const coralReducers = {
apollo: getClient().reducer()
};
window.coralStore = createStore(
combineReducers(coralReducers),
return reduxCreateStore(
combineReducers(reducers),
{},
compose(...middlewares)
compose(...enhancers)
);
window.coralStore.coralReducers = coralReducers;
return window.coralStore;
}
@@ -1,37 +0,0 @@
import {createNetworkInterface} from 'apollo-client';
import {getAuthToken} from '../helpers/request';
import {BASE_PATH} from 'coral-framework/constants/url';
//==============================================================================
// NETWORK INTERFACE
//==============================================================================
const networkInterface = createNetworkInterface({
uri: `${BASE_PATH}api/v1/graph/ql`,
opts: {
credentials: 'same-origin'
}
});
//==============================================================================
// MIDDLEWARES
//==============================================================================
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
let authToken = getAuthToken();
if (authToken) {
req.options.headers['authorization'] = `Bearer ${authToken}`;
}
next();
}
}]);
export {
networkInterface
};