detach-context.jsx 3.9 KB

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