metaProxy.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import isEmpty from 'lodash/isEmpty';
  2. import isNull from 'lodash/isNull';
  3. import memoize from 'lodash/memoize';
  4. import {Meta} from 'sentry/types';
  5. const GET_META = Symbol('GET_META');
  6. const IS_PROXY = Symbol('IS_PROXY');
  7. type SymbolProp = typeof GET_META | typeof IS_PROXY;
  8. function isAnnotated(meta) {
  9. if (isEmpty(meta)) {
  10. return false;
  11. }
  12. return !isEmpty(meta.rem) || !isEmpty(meta.err);
  13. }
  14. type Local = Record<string, any> | undefined;
  15. export class MetaProxy {
  16. private local: Local;
  17. constructor(local: Local) {
  18. this.local = local;
  19. }
  20. get<T extends {}>(
  21. obj: T | Array<T>,
  22. prop: Extract<keyof T, string> | SymbolProp,
  23. receiver: T
  24. ) {
  25. // trap calls to `getMeta` to return meta object
  26. if (prop === GET_META) {
  27. return key => {
  28. if (this.local && this.local[key] && this.local[key]['']) {
  29. // TODO: Error checks
  30. const meta = this.local[key][''];
  31. return isAnnotated(meta) ? meta : undefined;
  32. }
  33. return undefined;
  34. };
  35. }
  36. // this is how we can determine if current `obj` is a proxy
  37. if (prop === IS_PROXY) {
  38. return true;
  39. }
  40. const value = Reflect.get(obj, prop, receiver);
  41. if (!Reflect.has(obj, prop) || typeof value !== 'object' || isNull(value)) {
  42. return value;
  43. }
  44. // This is so we don't create a new Proxy from an object that is
  45. // already a proxy. Otherwise we can get into very deep recursive calls
  46. if (Reflect.get(obj, IS_PROXY, receiver)) {
  47. return value;
  48. }
  49. // Make sure we apply proxy to all children (objects and arrays)
  50. // Do we need to check for annotated inside of objects?
  51. return new Proxy(value, new MetaProxy(this.local && this.local[prop]));
  52. }
  53. }
  54. export const withMeta = memoize(function withMeta<T>(event: T): T {
  55. if (!event) {
  56. return event;
  57. }
  58. // Return unproxied `event` if browser does not support `Proxy`
  59. if (typeof window.Proxy === 'undefined' || typeof window.Reflect === 'undefined') {
  60. return event;
  61. }
  62. // withMeta returns a type that is supposed to be 100% compatible with its
  63. // input type. Proxy typing on typescript is not really functional enough to
  64. // make this work without casting.
  65. //
  66. // https://github.com/microsoft/TypeScript/issues/20846
  67. return new Proxy(event, new MetaProxy((event as any)._meta)) as T;
  68. });
  69. export function getMeta<T extends {}>(
  70. obj: T | undefined,
  71. prop: Extract<keyof T, string>
  72. ): Meta | undefined {
  73. if (!obj || typeof obj[GET_META] !== 'function') {
  74. return undefined;
  75. }
  76. return obj[GET_META](prop);
  77. }