asset-interceptor-plugin.ts 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { Asset } from '@vendure/common/lib/generated-types';
  2. import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServiceContext } from 'apollo-server-plugin-base';
  3. import { DocumentNode, GraphQLNamedType, isUnionType } from 'graphql';
  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. export class AssetInterceptorPlugin implements ApolloServerPlugin {
  12. private graphqlValueTransformer: GraphqlValueTransformer;
  13. private readonly toAbsoluteUrl: AssetStorageStrategy['toAbsoluteUrl'] | undefined;
  14. constructor(private configService: ConfigService) {
  15. const { assetOptions } = this.configService;
  16. if (assetOptions.assetStorageStrategy.toAbsoluteUrl) {
  17. this.toAbsoluteUrl = assetOptions.assetStorageStrategy.toAbsoluteUrl.bind(
  18. assetOptions.assetStorageStrategy,
  19. );
  20. }
  21. }
  22. serverWillStart(service: GraphQLServiceContext): Promise<void> | void {
  23. this.graphqlValueTransformer = new GraphqlValueTransformer(service.schema);
  24. }
  25. requestDidStart(): GraphQLRequestListener {
  26. return {
  27. willSendResponse: requestContext => {
  28. const { document } = requestContext;
  29. if (document) {
  30. const data = requestContext.response.data;
  31. const req = requestContext.context.req;
  32. if (data) {
  33. this.prefixAssetUrls(req, document, data);
  34. }
  35. }
  36. },
  37. };
  38. }
  39. private prefixAssetUrls(request: any, document: DocumentNode, data: Record<string, any>) {
  40. const typeTree = this.graphqlValueTransformer.getOutputTypeTree(document);
  41. const toAbsoluteUrl = this.toAbsoluteUrl;
  42. if (!toAbsoluteUrl) {
  43. return;
  44. }
  45. this.graphqlValueTransformer.transformValues(typeTree, data, (value, type) => {
  46. if (!type) {
  47. return value;
  48. }
  49. const isAssetType = this.isAssetType(type);
  50. const isUnionWithAssetType = isUnionType(type) && type.getTypes().find(t => this.isAssetType(t));
  51. if (isAssetType || isUnionWithAssetType) {
  52. if (value && !Array.isArray(value)) {
  53. if (value.preview) {
  54. value.preview = toAbsoluteUrl(request, value.preview);
  55. }
  56. if (value.source) {
  57. value.source = toAbsoluteUrl(request, value.source);
  58. }
  59. }
  60. }
  61. // TODO: This path is deprecated and should be removed in a future version
  62. // once the fields are removed from the GraphQL API
  63. const isSearchResultType = type && type.name === 'SearchResult';
  64. if (isSearchResultType) {
  65. if (value && !Array.isArray(value)) {
  66. if (value.productPreview) {
  67. value.productPreview = toAbsoluteUrl(request, value.productPreview);
  68. }
  69. if (value.productVariantPreview) {
  70. value.productVariantPreview = toAbsoluteUrl(request, value.productVariantPreview);
  71. }
  72. }
  73. }
  74. return value;
  75. });
  76. }
  77. private isAssetType(type: GraphQLNamedType): boolean {
  78. const assetTypeNames = ['Asset', 'SearchResultAsset'];
  79. return assetTypeNames.includes(type.name);
  80. }
  81. }