asset-interceptor.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
  2. import { GqlExecutionContext } from '@nestjs/graphql';
  3. import { Type } from '@vendure/common/lib/shared-types';
  4. import { Observable } from 'rxjs';
  5. import { map } from 'rxjs/operators';
  6. import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
  7. import { ConfigService } from '../../config/config.service';
  8. import { Asset } from '../../entity/asset/asset.entity';
  9. /**
  10. * Transforms outputs so that any Asset instances are run through the {@link AssetStorageStrategy.toAbsoluteUrl}
  11. * method before being returned in the response.
  12. */
  13. @Injectable()
  14. export class AssetInterceptor implements NestInterceptor {
  15. private readonly toAbsoluteUrl: AssetStorageStrategy['toAbsoluteUrl'] | undefined;
  16. constructor(private configService: ConfigService) {
  17. const { assetOptions } = this.configService;
  18. if (assetOptions.assetStorageStrategy.toAbsoluteUrl) {
  19. this.toAbsoluteUrl = assetOptions.assetStorageStrategy.toAbsoluteUrl.bind(
  20. assetOptions.assetStorageStrategy,
  21. );
  22. }
  23. }
  24. intercept<T = any>(context: ExecutionContext, next: CallHandler<T>): Observable<T> {
  25. const toAbsoluteUrl = this.toAbsoluteUrl;
  26. if (toAbsoluteUrl === undefined) {
  27. return next.handle();
  28. }
  29. const ctx = GqlExecutionContext.create(context).getContext();
  30. const request = ctx.req;
  31. return next.handle().pipe(
  32. map(data => {
  33. if (data instanceof Asset) {
  34. data.preview = toAbsoluteUrl(request, data.preview);
  35. data.source = toAbsoluteUrl(request, data.source);
  36. } else {
  37. visitType(data, [Asset, 'productPreview', 'productVariantPreview'], asset => {
  38. if (asset instanceof Asset) {
  39. asset.preview = toAbsoluteUrl(request, asset.preview);
  40. asset.source = toAbsoluteUrl(request, asset.source);
  41. } else {
  42. asset = toAbsoluteUrl(request, asset);
  43. }
  44. return asset;
  45. });
  46. }
  47. return data;
  48. }),
  49. );
  50. }
  51. }
  52. /**
  53. * Traverses the object and when encountering a property with a value which
  54. * is an instance of class T, invokes the visitor function on that value.
  55. */
  56. function visitType<T>(obj: any, types: Array<Type<T> | string>, visit: (instance: T | string) => T | string, seen: Set<any> = new Set()) {
  57. const keys = Object.keys(obj || {});
  58. for (const key of keys) {
  59. const value = obj[key];
  60. for (const type of types) {
  61. if (typeof type === 'string') {
  62. if (type === key) {
  63. obj[key] = visit(value);
  64. }
  65. } else if (value instanceof type) {
  66. visit(value);
  67. }
  68. }
  69. if (typeof value === 'object' && !seen.has(value)) {
  70. // add this object to the set of "seen" objects,
  71. // which prevents us getting stuck in the case of a
  72. // cyclic graph.
  73. seen.add(value);
  74. visitType(value, types, visit, seen);
  75. }
  76. }
  77. }