import { App, Messages } from '@openreplay/tracker'; import type { Middleware, RelayRequest } from './relaytypes'; import { Sanitizer } from './types'; import Observable from 'zen-observable'; interface GraphQLOperation { name: string; operationKind: string; text?: string | null; } interface GraphQLVariables { [key: string]: any; } interface GraphQLCacheConfig { [key: string]: any; } interface FetchFunction { ( operation: GraphQLOperation, variables: GraphQLVariables, cacheConfig: GraphQLCacheConfig, uploadables?: any ): Observable; } function safeStringify(value: unknown) { try { return JSON.stringify(value); } catch { // If we can’t stringify (e.g., cyclic object), return a placeholder return '"[unserializable]"'; } } function createRelayObserver(sanitizer?: Sanitizer>) { return (app: App | null) => { return (originalFetch: FetchFunction) => (operation: GraphQLOperation, variables: GraphQLVariables, cacheConfig: GraphQLCacheConfig, uploadables?: any): Observable => { const startTime = Date.now(); const observable = originalFetch(operation, variables, cacheConfig, uploadables); if (!app || !app.active()) { return observable; } return new Observable(observer => observable.subscribe({ next: (data: any) => { const duration = Date.now() - startTime; const opName = operation.name; const opKind = operation.operationKind; const vars = JSON.stringify(sanitizer ? sanitizer(variables) : variables); if (data.errors && data.errors.length > 0) { const opResp = safeStringify(sanitizer ? sanitizer(data.errors) : data.errors); app.send(Messages.GraphQL( opKind, `ERROR: ${opName}`, vars, opResp, duration )); } else { const opResp = safeStringify(sanitizer ? sanitizer(data) : data); app.send(Messages.GraphQL( opKind, opName, vars, opResp, duration )); } observer.next(data); }, error: err => { const duration = Date.now() - startTime; const opName = 'ERROR: ' + operation.name; const opKind = operation.operationKind; const vars = safeStringify(sanitizer ? sanitizer(variables) : variables); const opResp = safeStringify(err); app.send(Messages.GraphQL(opKind, opName, vars, opResp, duration)); observer.error(err); }, complete: () => { observer.complete(); } }) ) } } } const createRelayMiddleware = (sanitizer?: Sanitizer>) => { return (app: App | null): Middleware => { if (!app) { return (next) => async (req) => await next(req); } return (next) => async (req) => { const start = app.timestamp(); const resp = await next(req); const end = app.timestamp(); if ('requests' in req) { req.requests.forEach((request) => { app.send( getMessage( request, resp.json as Record, end - start, sanitizer, ), ); }); } else { app.send( getMessage( req, resp.json as Record, end - start, sanitizer, ), ); } return resp; }; }; }; function getMessage( request: RelayRequest, json: Record, duration: number, sanitizer?: Sanitizer>, ) { const opKind = request.operation.kind; const opName = request.operation.name; const vars = JSON.stringify( sanitizer ? sanitizer(request.variables) : request.variables, ); const opResp = JSON.stringify(sanitizer ? sanitizer(json) : json); return Messages.GraphQL(opKind, opName, vars, opResp, duration); } export { createRelayMiddleware, createRelayObserver };