openreplay/tracker/tracker-graphql/src/relayMiddleware.ts

144 lines
4.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<unknown>;
}
function safeStringify(value: unknown) {
try {
return JSON.stringify(value);
} catch {
// If we cant stringify (e.g., cyclic object), return a placeholder
return '"[unserializable]"';
}
}
function createRelayObserver(sanitizer?: Sanitizer<Record<string, any>>) {
return (app: App | null) => {
return (originalFetch: FetchFunction) => (operation: GraphQLOperation, variables: GraphQLVariables, cacheConfig: GraphQLCacheConfig, uploadables?: any): Observable<any> => {
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<Record<string, any>>) => {
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<string, any>,
end - start,
sanitizer,
),
);
});
} else {
app.send(
getMessage(
req,
resp.json as Record<string, any>,
end - start,
sanitizer,
),
);
}
return resp;
};
};
};
function getMessage(
request: RelayRequest,
json: Record<string, any>,
duration: number,
sanitizer?: Sanitizer<Record<string, any>>,
) {
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 };