asset-interceptor-plugin.ts 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServerContext } from '@apollo/server';
  2. import { DocumentNode, GraphQLNamedType, isUnionType } from 'graphql';
  3. import { Instrument } from '../../common/instrument-decorator';
  4. import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
  5. import { ConfigService } from '../../config/config.service';
  6. import { GraphqlValueTransformer } from '../common/graphql-value-transformer';
  7. /**
  8. * Transforms outputs so that any Asset instances are run through the {@link AssetStorageStrategy.toAbsoluteUrl}
  9. * method before being returned in the response.
  10. */
  11. @Instrument()
  12. export class AssetInterceptorPlugin implements ApolloServerPlugin {
  13. private graphqlValueTransformer: GraphqlValueTransformer;
  14. private readonly toAbsoluteUrl: AssetStorageStrategy['toAbsoluteUrl'] | undefined;
  15. constructor(private configService: ConfigService) {
  16. const { assetOptions } = this.configService;
  17. if (assetOptions.assetStorageStrategy.toAbsoluteUrl) {
  18. this.toAbsoluteUrl = assetOptions.assetStorageStrategy.toAbsoluteUrl.bind(
  19. assetOptions.assetStorageStrategy,
  20. );
  21. }
  22. }
  23. async serverWillStart(service: GraphQLServerContext): Promise<void> {
  24. this.graphqlValueTransformer = new GraphqlValueTransformer(service.schema);
  25. }
  26. async requestDidStart(): Promise<GraphQLRequestListener<any>> {
  27. return {
  28. willSendResponse: async requestContext => {
  29. const { document } = requestContext;
  30. if (document) {
  31. const { body } = requestContext.response;
  32. const req = requestContext.contextValue.req;
  33. if (body.kind === 'single') {
  34. this.prefixAssetUrls(req, document, body.singleResult.data);
  35. }
  36. }
  37. },
  38. };
  39. }
  40. private prefixAssetUrls(request: any, document: DocumentNode, data?: Record<string, unknown> | null) {
  41. const typeTree = this.graphqlValueTransformer.getOutputTypeTree(document);
  42. const toAbsoluteUrl = this.toAbsoluteUrl;
  43. if (!toAbsoluteUrl || !data) {
  44. return;
  45. }
  46. this.graphqlValueTransformer.transformValues(typeTree, data, (value, type) => {
  47. if (!type) {
  48. return value;
  49. }
  50. const isAssetType = this.isAssetType(type);
  51. const isUnionWithAssetType = isUnionType(type) && type.getTypes().find(t => this.isAssetType(t));
  52. if (isAssetType || isUnionWithAssetType) {
  53. if (value && !Array.isArray(value)) {
  54. if (value.preview) {
  55. value.preview = toAbsoluteUrl(request, value.preview);
  56. }
  57. if (value.source) {
  58. value.source = toAbsoluteUrl(request, value.source);
  59. }
  60. }
  61. }
  62. return value;
  63. });
  64. }
  65. private isAssetType(type: GraphQLNamedType): boolean {
  66. const assetTypeNames = ['Asset', 'SearchResultAsset'];
  67. return assetTypeNames.includes(type.name);
  68. }
  69. }