UNPKG

@graphql-yoga/plugin-apq

Version:
77 lines (76 loc) 3.03 kB
import { createGraphQLError, createLRUCache } from 'graphql-yoga'; import { handleMaybePromise } from '@whatwg-node/promise-helpers'; export function hashSHA256(text, api = globalThis) { const inputUint8Array = new api.TextEncoder().encode(text); return handleMaybePromise(() => api.crypto.subtle.digest({ name: 'SHA-256' }, inputUint8Array), arrayBuf => { 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; }); } export function createInMemoryAPQStore(options = {}) { return createLRUCache({ max: options.max ?? 1000, ttl: options.ttl ?? 36_000, }); } function isAPQExtension(input) { return (input != null && typeof input === 'object' && 'version' in input && input?.version === 1 && 'sha256Hash' in input && typeof input?.sha256Hash === 'string'); } function decodeAPQExtension(input) { if (isAPQExtension(input)) { return input; } return null; } export function useAPQ(options = {}) { const { store = createInMemoryAPQStore(), hash = hashSHA256, responseConfig = {} } = options; return { onParams({ params, setParams, fetchAPI }) { const persistedQueryData = decodeAPQExtension(params.extensions?.['persistedQuery']); if (persistedQueryData === null) { return; } if (params.query == null) { return handleMaybePromise(() => store.get(persistedQueryData.sha256Hash), persistedQuery => { if (persistedQuery == null) { throw createGraphQLError('PersistedQueryNotFound', { extensions: { http: { status: responseConfig.forceStatusCodeOk ? 200 : 404, }, code: 'PERSISTED_QUERY_NOT_FOUND', }, }); } setParams({ ...params, query: persistedQuery, }); }); } return handleMaybePromise(() => hash(params.query, fetchAPI), expectedHash => { if (persistedQueryData.sha256Hash !== expectedHash) { throw createGraphQLError('PersistedQueryMismatch', { extensions: { http: { status: responseConfig.forceStatusCodeOk ? 200 : 400, }, code: 'PERSISTED_QUERY_MISMATCH', }, }); } return store.set(persistedQueryData.sha256Hash, params.query); }); }, }; }