metaProxy.tsx 2.5 KB

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