<NME> context.js
<BEF> /**
 * Context prop of the ApolloServer config
 *
 * It sets up the server options based on the current request
 * Replacement to the syntax graphqlExpress(async req => {... })
 * Current pattern:
 * @see https://www.apollographql.com/docs/apollo-server/migration-two-dot.html#request-headers
 * @see https://github.com/apollographql/apollo-server/issues/1066
 * Previous implementation:
 * @see https://github.com/apollographql/apollo-server/issues/420
 */

//import deepmerge from 'deepmerge';
import DataLoader from 'dataloader';
import { getSetting } from '../../modules/settings.js';
import { _hashLoginToken, _tokenExpiration } from '../accounts_helpers';
import { Collections } from '../../modules/collections.js';
import { runCallbacks } from '../../modules/callbacks.js';
import findByIds from '../../modules/findbyids.js';
import { getHeaderLocale } from '../intl.js';
import { Utils } from '../../modules/utils.js';
import _merge from 'lodash/merge';

const computeContext = (currentContext, contextFromReq) => {
  // givenOptions can be either a function of the request or an object
  const getBaseContext = req => (contextFromReq ? { ...currentContext, ...contextFromReq(req) } : currentContext);
  const setupAuthToken = async (context, req) => {
import Cookies from 'universal-cookie';

// initial request will get the login token from a cookie, subsequent requests from
// the header
export const getAuthToken = req => {
  return req.headers.authorization || new Cookies(req.cookies).get('meteor_login_token');
};

const getUser = async loginToken => {
  if (loginToken) {
    check(loginToken, String)

    const hashedToken = Accounts._hashLoginToken(loginToken)

    const user = await Meteor.users.rawCollection().findOne({
      'services.resume.loginTokens.hashedToken': hashedToken
    })

    if (user) {
      // find the right login token corresponding, the current user may have
      // several sessions logged on different browsers / computers
      const tokenInformation = user.services.resume.loginTokens.find(
      }
    }
  };
  // go over context and add Dataloader to each collection
  const setupDataLoader = context => {
    Collections.forEach(collection => {
      context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, context), {
        cache: true
      });
    });
  };

  // create options given the current request
  const handleReq = async ({ req }) => {

      if (!isExpired) {

    context = getBaseContext(req);

    if (context) {
      // don't mutate the context provided in options
      context = { ...context };
    } else {
      context = {};
    }

    // note: custom default resolver doesn't currently work
    // see https://github.com/apollographql/apollo-server/issues/716
    // options.fieldResolver = (source, args, context, info) => {
// @see https://www.apollographql.com/docs/react/recipes/meteor#Server
export const setupAuthToken = async (context, req) => {
  const authToken = getAuthToken(req);
  const user = await getUser(authToken);
  if (user) {
    //add the headers to the context
    context.headers = req.headers;

    // merge with custom context
    // TODO: deemerge created an infinite loop here
    context = _merge({}, context, GraphQLSchema.context);

    setupDataLoader(context);

    // console.log('// apollo_server.js user-agent:', req.headers['user-agent']);
    // console.log('// apollo_server.js locale:', req.headers.locale);

  } else {
    context.userId = undefined;
    context.currentUser = undefined;
  }

  return handleReq;
};

export default computeContext;
const generateDataLoaders = context => {
  // go over context and add Dataloader to each collection
  Collections.forEach(collection => {
    context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, context), {
      cache: true,
    });
  });
  return context;
};

// 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 => (customContextFromReq ? { ...currentContext, ...customContextFromReq(req) } : { ...currentContext });

  // create options given the current request
  const handleReq = async req => {
    const { headers } = req;
    let context;

    // eslint-disable-next-line no-unused-vars
    let user = null;

    context = getBaseContext(req);

    generateDataLoaders(context);

    // note: custom default resolver doesn't currently work
    // see https://github.com/apollographql/apollo-server/issues/716
    // @options.fieldResolver = (source, args, context, info) => {
    //   return source[info.fieldName];
    // }

    await setupAuthToken(context, req);

    //add the headers to the context
    context.headers = headers;
    // pass the whole req for advanced usage, like fetching IP from connection
    context.req = req;

    // if apiKey is present, assign "fake" currentUser with admin rights
    if (headers.apikey && headers.apikey === getSetting('vulcan.apiKey')) {
      context.currentUser = { isAdmin: true, isApiUser: true };
    }

    context.locale = getHeaderLocale(headers, context.currentUser && context.currentUser.locale);
    const locale = getLocale(context.locale);

    // see https://forums.meteor.com/t/can-i-edit-html-tag-in-meteor/5867/7
    WebApp.addHtmlAttributeHook(function() {
      let htmlAttributes = {
        lang: context.locale
      };
      if (locale?.rtl === true) {
        htmlAttributes.class = 'rtl';
      } else {
        htmlAttributes.class = 'ltr';
      }
      return htmlAttributes;
    });

    context = await runCallbacks({ name: 'graphql.context', iterator: context });

    return context;
  };

  return handleReq;
};

<MSG> split context creation to avoid unecessary computations

<DFF> @@ -13,7 +13,7 @@
 //import deepmerge from 'deepmerge';
 import DataLoader from 'dataloader';
 import { getSetting } from '../../modules/settings.js';
-import { _hashLoginToken, _tokenExpiration } from '../accounts_helpers';
+import { _hashLoginToken, _tokenExpiration } from '../accounts_helpers.js';
 import { Collections } from '../../modules/collections.js';
 import { runCallbacks } from '../../modules/callbacks.js';
 import findByIds from '../../modules/findbyids.js';
@@ -21,7 +21,31 @@ import { GraphQLSchema } from '../../modules/graphql.js';
 import { Utils } from '../../modules/utils.js';
 import _merge from 'lodash/merge';
 
-const computeContext = (currentContext, contextFromReq) => {
+/**
+ * Called once on server creation
+ * @param {*} currentContext
+ */
+export const initContext = currentContext => {
+  let context;
+  if (currentContext) {
+    context = { ...currentContext };
+  } else {
+    context = {};
+  }
+  // merge with custom context
+  // TODO: deepmerge created an infinite loop here
+  context = _merge({}, context, GraphQLSchema.context);
+  // go over context and add Dataloader to each collection
+  Collections.forEach(collection => {
+    context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, context), {
+      cache: true
+    });
+  });
+  return context;
+};
+
+// Call on every request
+export const computeContextFromReq = (currentContext, contextFromReq) => {
   // givenOptions can be either a function of the request or an object
   const getBaseContext = req => (contextFromReq ? { ...currentContext, ...contextFromReq(req) } : currentContext);
   const setupAuthToken = async (context, req) => {
@@ -50,14 +74,6 @@ const computeContext = (currentContext, contextFromReq) => {
       }
     }
   };
-  // go over context and add Dataloader to each collection
-  const setupDataLoader = context => {
-    Collections.forEach(collection => {
-      context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, context), {
-        cache: true
-      });
-    });
-  };
 
   // create options given the current request
   const handleReq = async ({ req }) => {
@@ -66,13 +82,6 @@ const computeContext = (currentContext, contextFromReq) => {
 
     context = getBaseContext(req);
 
-    if (context) {
-      // don't mutate the context provided in options
-      context = { ...context };
-    } else {
-      context = {};
-    }
-
     // note: custom default resolver doesn't currently work
     // see https://github.com/apollographql/apollo-server/issues/716
     // options.fieldResolver = (source, args, context, info) => {
@@ -84,12 +93,6 @@ const computeContext = (currentContext, contextFromReq) => {
     //add the headers to the context
     context.headers = req.headers;
 
-    // merge with custom context
-    // TODO: deemerge created an infinite loop here
-    context = _merge({}, context, GraphQLSchema.context);
-
-    setupDataLoader(context);
-
     // console.log('// apollo_server.js user-agent:', req.headers['user-agent']);
     // console.log('// apollo_server.js locale:', req.headers.locale);
 
@@ -100,5 +103,3 @@ const computeContext = (currentContext, contextFromReq) => {
 
   return handleReq;
 };
-
-export default computeContext;

{'addition_count': 26, 'commit_subject': 'split context creation to avoid unecessary computations', 'deletion_count': 25, 'file_extension': '.js', 'lang': 'js', 'license': 'mit', 'repo_name': 'VulcanJS/Vulcan'}