UNPKG

@graphql-yoga/plugin-apollo-usage-report

Version:

Apollo's GraphOS usage report plugin for GraphQL Yoga.

152 lines (150 loc) • 6.35 kB
const require_reporter = require('./reporter.cjs'); let graphql = require("graphql"); let graphql_yoga = require("graphql-yoga"); let _apollo_utils_usagereporting = require("@apollo/utils.usagereporting"); let _graphql_tools_utils = require("@graphql-tools/utils"); let _graphql_yoga_plugin_apollo_inline_trace = require("@graphql-yoga/plugin-apollo-inline-trace"); //#region src/index.ts function useApolloUsageReport(options = {}) { const [instrumentation, ctxForReq] = (0, _graphql_yoga_plugin_apollo_inline_trace.useApolloInstrumentation)(options); const makeReporter = options.reporter ?? ((...args) => new require_reporter.Reporter(...args)); let schemaIdSet$; let currentSchema; let yoga; let reporter; const setCurrentSchema = async (schema) => { try { currentSchema = { id: await hashSHA256((0, _graphql_tools_utils.printSchemaWithDirectives)(schema), yoga.fetchAPI), schema }; } catch (error) { logger.error("Failed to calculate schema hash: ", error); } schemaIdSet$ = void 0; }; const logger = Object.fromEntries([ "error", "warn", "info", "debug" ].map((level) => [level, (...messages) => yoga.logger[level]("[ApolloUsageReport]", ...messages)])); let clientNameFactory = (req) => req.headers.get("apollographql-client-name"); if (typeof options.clientName === "function") clientNameFactory = options.clientName; let clientVersionFactory = (req) => req.headers.get("apollographql-client-version"); if (typeof options.clientVersion === "function") clientVersionFactory = options.clientVersion; return { onPluginInit({ addPlugin }) { addPlugin(instrumentation); addPlugin({ onYogaInit(args) { yoga = args.yoga; reporter = makeReporter(options, yoga, logger); if (!require_reporter.getEnvVar("APOLLO_KEY", options.apiKey)) throw new Error(`[ApolloUsageReport] Missing API key. Please provide one in plugin options or with 'APOLLO_KEY' environment variable.`); if (!require_reporter.getEnvVar("APOLLO_GRAPH_REF", options.graphRef)) throw new Error(`[ApolloUsageReport] Missing Graph Ref. Please provide one in plugin options or with 'APOLLO_GRAPH_REF' environment variable.`); if (!schemaIdSet$ && !currentSchema) { const { schema } = yoga.getEnveloped(); if (schema) schemaIdSet$ = setCurrentSchema(schema); } }, onSchemaChange({ schema }) { if (schema && yoga) schemaIdSet$ = setCurrentSchema(schema); }, onRequestParse() { return schemaIdSet$; }, onParse() { return function onParseEnd({ result, context }) { const ctx = ctxForReq.get(context.request)?.traces.get(context); if (!ctx) { logger.debug("operation tracing context not found, this operation will not be traced."); return; } ctx.schemaId = currentSchema.id; if (isDocumentNode(result)) { if ((0, graphql.getOperationAST)(result, context.params.operationName)) return; ctx.operationKey = `## GraphQLUnknownOperationName\n`; } else ctx.operationKey = "## GraphQLParseFailure\n"; if (!options.sendUnexecutableOperationDocuments) { ctxForReq.delete(context.request); return; } ctx.trace.unexecutedOperationName = context.params.operationName || ""; ctx.trace.unexecutedOperationBody = context.params.query || ""; }; }, onValidate({ params: { documentAST: document } }) { return ({ valid, context }) => { const ctx = ctxForReq.get(context.request)?.traces.get(context); if (!ctx) { logger.debug("operation tracing context not found, this operation will not be traced."); return; } if (valid) { if (!currentSchema) throw new Error("should not happen: schema doesn't exists"); const opName = (0, graphql.getOperationAST)(document, context.params.operationName)?.name?.value; ctx.referencedFieldsByType = (0, _apollo_utils_usagereporting.calculateReferencedFieldsByType)({ document, schema: currentSchema.schema, resolvedOperationName: opName ?? null }); ctx.operationKey = `# ${opName || "-"}\n${(0, _apollo_utils_usagereporting.usageReportingSignature)(document, opName ?? "")}`; } else if (options.sendUnexecutableOperationDocuments) { ctx.operationKey = "## GraphQLValidationFailure\n"; ctx.trace.unexecutedOperationName = context.params.operationName ?? ""; ctx.trace.unexecutedOperationBody = context.params.query ?? ""; } else ctxForReq.delete(context.request); }; }, onResultProcess({ request, result, serverContext }) { if ((0, graphql_yoga.isAsyncIterable)(result)) { logger.debug("async iterable results not implemented for now"); return; } const reqCtx = ctxForReq.get(request); if (!reqCtx) { logger.debug("operation tracing context not found, this operation will not be traced."); return; } for (const trace of reqCtx.traces.values()) { if (!trace.schemaId || !trace.operationKey) { logger.debug("Misformed trace, missing operation key or schema id"); continue; } const clientName = clientNameFactory(request); if (clientName) trace.trace.clientName = clientName; const clientVersion = clientVersionFactory(request); if (clientVersion) trace.trace.clientVersion = clientVersion; serverContext.waitUntil(reporter.addTrace(currentSchema.id, { statsReportKey: trace.operationKey, trace: trace.trace, referencedFieldsByType: trace.referencedFieldsByType ?? {}, asTrace: true, nonFtv1ErrorPaths: [], maxTraceBytes: options.maxTraceSize })); } }, async onDispose() { await reporter?.flush(); } }); } }; } async function hashSHA256(text, api = globalThis) { const inputUint8Array = new api.TextEncoder().encode(text); const arrayBuf = await api.crypto.subtle.digest({ name: "SHA-256" }, inputUint8Array); const outputUint8Array = new Uint8Array(arrayBuf); let hash = ""; for (const byte of outputUint8Array) { const hex = byte.toString(16); hash += "00".slice(0, Math.max(0, 2 - hex.length)) + hex; } return hash; } function isDocumentNode(data) { const isObject = (data) => !!data && typeof data === "object"; return isObject(data) && data["kind"] === graphql.Kind.DOCUMENT; } //#endregion exports.hashSHA256 = hashSHA256; exports.useApolloUsageReport = useApolloUsageReport;