diff --git a/packages/vulcan-lib/lib/server/apollo-server/apollo_server2.js b/packages/vulcan-lib/lib/server/apollo-server/apollo_server2.js index c235e0ad9..f074895f9 100644 --- a/packages/vulcan-lib/lib/server/apollo-server/apollo_server2.js +++ b/packages/vulcan-lib/lib/server/apollo-server/apollo_server2.js @@ -32,24 +32,27 @@ import { enableSSR } from '../apollo-ssr' * * Config: a config specific to Vulcan */ -const createApolloServer = ({ options: givenOptions = {}, config: givenConfig = {}, contextFromReq }) => { +const createApolloServer = ({ options: givenOptions = {}, config: givenConfig = {}, customContextFromReq }) => { const graphiqlOptions = { ...defaultConfig.graphiqlOptions, ...givenConfig.graphiqlOptions }; const config = { ...defaultConfig, ...givenConfig }; config.graphiqlOptions = graphiqlOptions; // get the options and merge in defaults const options = { ...defaultOptions, ...givenOptions }; - const context = initContext(options.context); + const initialContext = initContext(options.context); + + // this replace the previous syntax graphqlExpress(async req => { ... }) + // this function takes the context, which contains the current request, + // and setup the options accordingly ({req}) => { ...; return options } + const contextFromReq = computeContextFromReq(initialContext, customContextFromReq) // given options contains the schema const apolloServer = new ApolloServer({ engine: engineConfig, // graphql playground (replacement to graphiql), available on the app path playground: getPlaygroundConfig(config), ...options, - // this replace the previous syntax graphqlExpress(async req => { ... }) - // this function takes the context, which contains the current request, - // and setup the options accordingly ({req}) => { ...; return options } - context: computeContextFromReq(context, contextFromReq) + // context optionbject or a function of the current request (+ maybe some other params) + context: ({req}) => contextFromReq(req) }); // default function does nothing @@ -90,7 +93,7 @@ const createApolloServer = ({ options: givenOptions = {}, config: givenConfig = // ssr - enableSSR() + enableSSR({ computeContext: contextFromReq}) /* * Alternative syntax with Express instead of Connect diff --git a/packages/vulcan-lib/lib/server/apollo-server/context.js b/packages/vulcan-lib/lib/server/apollo-server/context.js index 65082d116..5430320cb 100644 --- a/packages/vulcan-lib/lib/server/apollo-server/context.js +++ b/packages/vulcan-lib/lib/server/apollo-server/context.js @@ -45,24 +45,24 @@ export const initContext = currentContext => { return context; }; -// Call on every request -export const computeContextFromReq = (currentContext, contextFromReq) => { +// @see https://www.apollographql.com/docs/react/recipes/meteor#Server +const setupAuthToken = async (context, req) => { + const user = await getUser(req.headers.authorization); + if (user) { + context.userId = user._id; + context.currentUser = user; + // identify user to any server-side analytics providers + runCallbacks('events.identify', user); + } else { + context.userId = undefined; + context.currentUser = undefined; + } +}; +// Returns a function called on every request to compute context +export const computeContextFromReq = (currentContext, customContextFromReq) => { // givenOptions can be either a function of the request or an object const getBaseContext = req => - contextFromReq ? { ...currentContext, ...contextFromReq(req) } : { ...currentContext }; - // @see https://www.apollographql.com/docs/react/recipes/meteor#Server - const setupAuthToken = async (context, req) => { - const user = await getUser(req.headers.authorization); - if (user) { - context.userId = user._id; - context.currentUser = user; - // identify user to any server-side analytics providers - runCallbacks('events.identify', user); - } else { - context.userId = undefined; - context.currentUser = undefined; - } - }; + customContextFromReq ? { ...currentContext, ...customContextFromReq(req) } : { ...currentContext }; // Previous implementation // Now meteor/apollo already provide this // Get the token from the header @@ -91,7 +91,7 @@ export const computeContextFromReq = (currentContext, contextFromReq) => { //} // create options given the current request - const handleReq = async ({ req }) => { + const handleReq = async (req) => { let context; let user = null; diff --git a/packages/vulcan-lib/lib/server/apollo-ssr/apolloClient.js b/packages/vulcan-lib/lib/server/apollo-ssr/apolloClient.js index 4b96705e8..210b27ec6 100644 --- a/packages/vulcan-lib/lib/server/apollo-ssr/apolloClient.js +++ b/packages/vulcan-lib/lib/server/apollo-ssr/apolloClient.js @@ -18,14 +18,16 @@ import { ApolloLink } from 'apollo-link'; // import { createHttpLink } from 'apollo-link-http'; // import fetch from 'node-fetch' -export const createClient = (req) => { +export const createClient = async ({req, computeContext}) => { // init // stateLink will init the client internal state const cache = new InMemoryCache() const stateLink = createStateLink({ cache }); // schemaLink will fetch data directly based on the executable schema const schema = GraphQLSchema.getExecutableSchema() - const schemaLink = new SchemaLink({ schema }) + // this is the resolver context + const context = await computeContext(req) + const schemaLink = new SchemaLink({ schema, context }) const client = new ApolloClient({ ssrMode: true, link: ApolloLink.from([stateLink, schemaLink, errorLink]), diff --git a/packages/vulcan-lib/lib/server/apollo-ssr/enableSSR.js b/packages/vulcan-lib/lib/server/apollo-ssr/enableSSR.js index fdab5deab..33baa8e70 100644 --- a/packages/vulcan-lib/lib/server/apollo-ssr/enableSSR.js +++ b/packages/vulcan-lib/lib/server/apollo-ssr/enableSSR.js @@ -11,17 +11,17 @@ import { // excepts it is tailored to handle Meteor server side rendering import { onPageLoad } from 'meteor/server-render' -import renderPage from './renderPage' +import makePageRenderer from './renderPage' -const enableSSR = () => { +const enableSSR = ({computeContext}) => { Meteor.startup(() => { // init the application components and routes, including components & routes from 3rd-party packages initializeFragments(); populateComponentsApp(); populateRoutesApp(); // render the page - onPageLoad(renderPage) + onPageLoad(makePageRenderer({computeContext})) }); } diff --git a/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js b/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js index b6a23a0df..f8fbc4b13 100644 --- a/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js +++ b/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js @@ -14,40 +14,44 @@ import Head from './components/Head' import ApolloState from './components/ApolloState' import AppGenerator from './components/AppGenerator'; -const renderPage = async sink => { - const req = sink.request - // according to the Apollo doc, client needs to be recreated on every request - // this avoids caching server side - const client = createClient(req); +const makePageRenderer = ({ computeContext }) => { + // onPageLoad callback + const renderPage = async sink => { + const req = sink.request + // according to the Apollo doc, client needs to be recreated on every request + // this avoids caching server side + const client = await createClient({req, computeContext}); - // TODO? do we need this? - const context = {}; + // TODO? do we need this? + const context = {}; - // equivalent to calling getDataFromTree and then renderToStringWithData - const content = await renderToStringWithData( - - ) + // equivalent to calling getDataFromTree and then renderToStringWithData + const content = await renderToStringWithData( + + ) - // TODO: there should be a cleaner way to set this wrapper - // id must always match the client side start.jsx file - const wrappedContent = `
${content}
` - sink.appendToBody(wrappedContent) - // TODO: this sounds cleaner but where do we add the
? - //sink.renderIntoElementById('react-app', content) + // TODO: there should be a cleaner way to set this wrapper + // id must always match the client side start.jsx file + const wrappedContent = `
${content}
` + sink.appendToBody(wrappedContent) + // TODO: this sounds cleaner but where do we add the
? + //sink.renderIntoElementById('react-app', content) - // add headers using helmet - const head = ReactDOM.renderToString() - sink.appendToHead(head) + // add headers using helmet + const head = ReactDOM.renderToString() + sink.appendToHead(head) - // add Apollo state, the client will then parse the string - const initialState = client.extract(); - const serializedApolloState = ReactDOM.renderToString( - - ) - sink.appendToBody(serializedApolloState) + // add Apollo state, the client will then parse the string + const initialState = client.extract(); + const serializedApolloState = ReactDOM.renderToString( + + ) + sink.appendToBody(serializedApolloState) + } + return renderPage } -export default renderPage +export default makePageRenderer // FIRST TRY WITH EXPRESS // However this won't work as Meteor is in charge of loading relevant JS script // This code would only render dead HTML