devkit-client-api.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import {
  2. BaseExtensionMessage,
  3. ExtensionMesssage,
  4. MessageResponse,
  5. NotificationMessage,
  6. WatchQueryFetchPolicy,
  7. } from '@vendure/common/lib/extension-host-types';
  8. import { Observable } from 'rxjs';
  9. import { take } from 'rxjs/operators';
  10. let targetOrigin = 'http://localhost:3000';
  11. /**
  12. * @description
  13. * Set the [window.postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
  14. * `targetOrigin`. The Vendure ui-devkit uses the postMessage API to
  15. * enable cross-frame and cross-origin communication between the ui extension code and the Admin UI
  16. * app. The `targetOrigin` is a security feature intended to provide control over where messages are sent.
  17. *
  18. * @docsCategory ui-devkit
  19. * @docsPage UiDevkitClient
  20. */
  21. export function setTargetOrigin(value: string) {
  22. targetOrigin = value;
  23. }
  24. /**
  25. * @description
  26. * Perform a GraphQL query and returns either an Observable or a Promise of the result.
  27. *
  28. * @docsCategory ui-devkit
  29. * @docsPage UiDevkitClient
  30. */
  31. export function graphQlQuery<T, V extends { [key: string]: any }>(
  32. document: string,
  33. variables?: { [key: string]: any },
  34. fetchPolicy?: WatchQueryFetchPolicy,
  35. ): {
  36. then: Promise<T>['then'];
  37. stream: Observable<T>;
  38. } {
  39. const result$ = sendMessage('graphql-query', { document, variables, fetchPolicy });
  40. return {
  41. then: (...args: any[]) =>
  42. result$
  43. .pipe(take(1))
  44. .toPromise()
  45. .then(...args),
  46. stream: result$,
  47. };
  48. }
  49. /**
  50. * @description
  51. * Perform a GraphQL mutation and returns either an Observable or a Promise of the result.
  52. *
  53. * @docsCategory ui-devkit
  54. * @docsPage UiDevkitClient
  55. */
  56. export function graphQlMutation<T, V extends { [key: string]: any }>(
  57. document: string,
  58. variables?: { [key: string]: any },
  59. ): {
  60. then: Promise<T>['then'];
  61. stream: Observable<T>;
  62. } {
  63. const result$ = sendMessage('graphql-mutation', { document, variables });
  64. return {
  65. then: (...args: any[]) =>
  66. result$
  67. .pipe(take(1))
  68. .toPromise()
  69. .then(...args),
  70. stream: result$,
  71. };
  72. }
  73. /**
  74. * @description
  75. * Display a toast notification.
  76. *
  77. * @docsCategory ui-devkit
  78. * @docsPage UiDevkitClient
  79. */
  80. export function notify(options: NotificationMessage['data']) {
  81. sendMessage('notification', options).toPromise();
  82. }
  83. function sendMessage<T extends ExtensionMesssage>(type: T['type'], data: T['data']): Observable<any> {
  84. const requestId =
  85. type +
  86. '__' +
  87. Math.random()
  88. .toString(36)
  89. .substr(3);
  90. const message: BaseExtensionMessage = {
  91. requestId,
  92. type,
  93. data,
  94. };
  95. return new Observable<any>(subscriber => {
  96. const hostWindow = window.opener || window.parent;
  97. const handleReply = (event: MessageEvent) => {
  98. const response: MessageResponse = event.data;
  99. if (response && response.requestId === requestId) {
  100. if (response.complete) {
  101. subscriber.complete();
  102. tearDown();
  103. return;
  104. }
  105. if (response.error) {
  106. subscriber.error(response.data);
  107. tearDown();
  108. return;
  109. }
  110. subscriber.next(response.data);
  111. }
  112. };
  113. const tearDown = () => {
  114. hostWindow.postMessage({ requestId, type: 'cancellation', data: null }, targetOrigin);
  115. };
  116. window.addEventListener('message', handleReply);
  117. hostWindow.postMessage(message, targetOrigin);
  118. return tearDown;
  119. });
  120. }