detach-context.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import {
  2. createContext,
  3. useContext,
  4. useCallback,
  5. useMemo,
  6. useEffect,
  7. useState,
  8. } from 'react'
  9. import PropTypes from 'prop-types'
  10. import getMeta from '../../utils/meta'
  11. import { buildUrlWithDetachRole } from '../utils/url-helper'
  12. import useCallbackHandlers from '../hooks/use-callback-handlers'
  13. export const DetachContext = createContext()
  14. DetachContext.Provider.propTypes = {
  15. value: PropTypes.shape({
  16. role: PropTypes.oneOf(['detacher', 'detached', null]),
  17. setRole: PropTypes.func.isRequired,
  18. broadcastEvent: PropTypes.func.isRequired,
  19. addEventHandler: PropTypes.func.isRequired,
  20. deleteEventHandler: PropTypes.func.isRequired,
  21. }).isRequired,
  22. }
  23. const debugPdfDetach = getMeta('ol-debugPdfDetach')
  24. const projectId = getMeta('ol-project_id')
  25. export const detachChannelId = `detach-${projectId}`
  26. export const detachChannel =
  27. 'BroadcastChannel' in window
  28. ? new BroadcastChannel(detachChannelId)
  29. : undefined
  30. export function DetachProvider({ children }) {
  31. const [lastDetachedConnectedAt, setLastDetachedConnectedAt] = useState()
  32. const [role, setRole] = useState(() => getMeta('ol-detachRole') || null)
  33. const {
  34. addHandler: addEventHandler,
  35. deleteHandler: deleteEventHandler,
  36. callHandlers: callEventHandlers,
  37. } = useCallbackHandlers()
  38. useEffect(() => {
  39. if (debugPdfDetach) {
  40. console.log('Effect', { role })
  41. }
  42. window.history.replaceState({}, '', buildUrlWithDetachRole(role).toString())
  43. }, [role])
  44. useEffect(() => {
  45. if (detachChannel) {
  46. const listener = event => {
  47. if (debugPdfDetach) {
  48. console.log(`Receiving:`, event.data)
  49. }
  50. callEventHandlers(event.data)
  51. }
  52. detachChannel.addEventListener('message', listener)
  53. return () => {
  54. detachChannel.removeEventListener('message', listener)
  55. }
  56. }
  57. }, [callEventHandlers])
  58. const broadcastEvent = useCallback(
  59. (event, data) => {
  60. if (!role) {
  61. if (debugPdfDetach) {
  62. console.log('Not Broadcasting (no role)', {
  63. role,
  64. event,
  65. data,
  66. })
  67. }
  68. return
  69. }
  70. if (debugPdfDetach) {
  71. console.log('Broadcasting', {
  72. role,
  73. event,
  74. data,
  75. })
  76. }
  77. const message = {
  78. role,
  79. event,
  80. }
  81. if (data) {
  82. message.data = data
  83. }
  84. detachChannel?.postMessage(message)
  85. },
  86. [role]
  87. )
  88. useEffect(() => {
  89. broadcastEvent('connected')
  90. }, [broadcastEvent])
  91. useEffect(() => {
  92. const onBeforeUnload = () => broadcastEvent('closed')
  93. window.addEventListener('beforeunload', onBeforeUnload)
  94. return () => window.removeEventListener('beforeunload', onBeforeUnload)
  95. }, [broadcastEvent])
  96. useEffect(() => {
  97. const updateLastDetachedConnectedAt = message => {
  98. if (message.role === 'detached' && message.event === 'connected') {
  99. setLastDetachedConnectedAt(new Date())
  100. }
  101. }
  102. addEventHandler(updateLastDetachedConnectedAt)
  103. return () => deleteEventHandler(updateLastDetachedConnectedAt)
  104. }, [addEventHandler, deleteEventHandler])
  105. const value = useMemo(
  106. () => ({
  107. role,
  108. setRole,
  109. broadcastEvent,
  110. lastDetachedConnectedAt,
  111. addEventHandler,
  112. deleteEventHandler,
  113. }),
  114. [
  115. role,
  116. setRole,
  117. broadcastEvent,
  118. lastDetachedConnectedAt,
  119. addEventHandler,
  120. deleteEventHandler,
  121. ]
  122. )
  123. return (
  124. <DetachContext.Provider value={value}>{children}</DetachContext.Provider>
  125. )
  126. }
  127. DetachProvider.propTypes = {
  128. children: PropTypes.any,
  129. }
  130. export function useDetachContext(propTypes) {
  131. const data = useContext(DetachContext)
  132. if (!data) {
  133. throw new Error('useDetachContext is only available inside DetachProvider')
  134. }
  135. PropTypes.checkPropTypes(propTypes, data, 'data', 'DetachContext.Provider')
  136. return data
  137. }