diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index a42636bc631..d29c5e0b5ac 100644 --- a/packages/apollo-server-core/package.json +++ b/packages/apollo-server-core/package.json @@ -41,6 +41,7 @@ "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", + "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.11", "ws": "^6.0.0" }, diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 23d57646f69..e7d39f09126 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -29,7 +29,6 @@ import { PersistedQueryNotSupportedError, PersistedQueryNotFoundError, } from 'apollo-server-errors'; -import { createHash } from 'crypto'; import { GraphQLRequest, GraphQLResponse, @@ -53,8 +52,10 @@ export { InvalidGraphQLRequestError, }; +import createSHA from './utils/createSHA'; + function computeQueryHash(query: string) { - return createHash('sha256') + return createSHA('sha256') .update(query) .digest('hex'); } diff --git a/packages/apollo-server-core/src/utils/createSHA.ts b/packages/apollo-server-core/src/utils/createSHA.ts new file mode 100644 index 00000000000..31102aec3bd --- /dev/null +++ b/packages/apollo-server-core/src/utils/createSHA.ts @@ -0,0 +1,10 @@ +import isNode from './isNode'; + +export default function(kind: string): import('crypto').Hash { + if (isNode) { + // Use module.require instead of just require to avoid bundling whatever + // crypto polyfills a non-Node bundler might fall back to. + return module.require('crypto').createHash(kind); + } + return require('sha.js')(kind); +} diff --git a/packages/apollo-server-core/src/utils/isNode.ts b/packages/apollo-server-core/src/utils/isNode.ts new file mode 100644 index 00000000000..8002526c0ac --- /dev/null +++ b/packages/apollo-server-core/src/utils/isNode.ts @@ -0,0 +1,6 @@ +export default typeof process === 'object' && + process && + process.release && + process.release.name === 'node' && + process.versions && + typeof process.versions.node === 'string'; diff --git a/packages/apollo-server-core/src/utils/runtimeSupportsUploads.ts b/packages/apollo-server-core/src/utils/runtimeSupportsUploads.ts index f1a50344a6d..ef80c3d948e 100644 --- a/packages/apollo-server-core/src/utils/runtimeSupportsUploads.ts +++ b/packages/apollo-server-core/src/utils/runtimeSupportsUploads.ts @@ -1,11 +1,7 @@ +import isNode from './isNode'; + const runtimeSupportsUploads = (() => { - if ( - process && - process.release && - process.release.name === 'node' && - process.versions && - typeof process.versions.node === 'string' - ) { + if (isNode) { const [nodeMajor, nodeMinor] = process.versions.node .split('.', 2) .map(segment => parseInt(segment, 10)); diff --git a/packages/apollo-server-core/src/utils/schemaHash.ts b/packages/apollo-server-core/src/utils/schemaHash.ts index a1b01b3628f..7c30c6e751d 100644 --- a/packages/apollo-server-core/src/utils/schemaHash.ts +++ b/packages/apollo-server-core/src/utils/schemaHash.ts @@ -1,44 +1,44 @@ -import { parse } from 'graphql/language'; -import { execute, ExecutionResult } from 'graphql/execution'; -import { getIntrospectionQuery, IntrospectionSchema } from 'graphql/utilities'; -import stableStringify from 'fast-json-stable-stringify'; -import { GraphQLSchema } from 'graphql/type'; -import { createHash } from 'crypto'; - -export function generateSchemaHash(schema: GraphQLSchema): string { - const introspectionQuery = getIntrospectionQuery(); - const documentAST = parse(introspectionQuery); - const result = execute(schema, documentAST) as ExecutionResult; - - // If the execution of an introspection query results in a then-able, it - // indicates that one or more of its resolvers is behaving in an asynchronous - // manner. This is not the expected behavior of a introspection query - // which does not have any asynchronous resolvers. - if ( - result && - typeof (result as PromiseLike).then === 'function' - ) { - throw new Error( - [ - 'The introspection query is resolving asynchronously; execution of an introspection query is not expected to return a `Promise`.', - '', - 'Wrapped type resolvers should maintain the existing execution dynamics of the resolvers they wrap (i.e. async vs sync) or introspection types should be excluded from wrapping by checking them with `graphql/type`s, `isIntrospectionType` predicate function prior to wrapping.', - ].join('\n'), - ); - } - - if (!result || !result.data || !result.data.__schema) { - throw new Error('Unable to generate server introspection document.'); - } - - const introspectionSchema: IntrospectionSchema = result.data.__schema; - - // It's important that we perform a deterministic stringification here - // since, depending on changes in the underlying `graphql-js` execution - // layer, varying orders of the properties in the introspection - const stringifiedSchema = stableStringify(introspectionSchema); - - return createHash('sha512') - .update(stringifiedSchema) - .digest('hex'); -} +import { parse } from 'graphql/language'; +import { execute, ExecutionResult } from 'graphql/execution'; +import { getIntrospectionQuery, IntrospectionSchema } from 'graphql/utilities'; +import stableStringify from 'fast-json-stable-stringify'; +import { GraphQLSchema } from 'graphql/type'; +import createSHA from './createSHA'; + +export function generateSchemaHash(schema: GraphQLSchema): string { + const introspectionQuery = getIntrospectionQuery(); + const documentAST = parse(introspectionQuery); + const result = execute(schema, documentAST) as ExecutionResult; + + // If the execution of an introspection query results in a then-able, it + // indicates that one or more of its resolvers is behaving in an asynchronous + // manner. This is not the expected behavior of a introspection query + // which does not have any asynchronous resolvers. + if ( + result && + typeof (result as PromiseLike).then === 'function' + ) { + throw new Error( + [ + 'The introspection query is resolving asynchronously; execution of an introspection query is not expected to return a `Promise`.', + '', + 'Wrapped type resolvers should maintain the existing execution dynamics of the resolvers they wrap (i.e. async vs sync) or introspection types should be excluded from wrapping by checking them with `graphql/type`s, `isIntrospectionType` predicate function prior to wrapping.', + ].join('\n'), + ); + } + + if (!result || !result.data || !result.data.__schema) { + throw new Error('Unable to generate server introspection document.'); + } + + const introspectionSchema: IntrospectionSchema = result.data.__schema; + + // It's important that we perform a deterministic stringification here + // since, depending on changes in the underlying `graphql-js` execution + // layer, varying orders of the properties in the introspection + const stringifiedSchema = stableStringify(introspectionSchema); + + return createSHA('sha512') + .update(stringifiedSchema) + .digest('hex'); +}