@shopify/ui-extensions-server-kit 0.0.0-experimental-20260902222805

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +75 -0
  3. package/dist/ExtensionServerClient/ExtensionServerClient.d.ts +27 -0
  4. package/dist/ExtensionServerClient/ExtensionServerClient.test.d.ts +9 -0
  5. package/dist/ExtensionServerClient/index.d.ts +2 -0
  6. package/dist/ExtensionServerClient/server-types.d.ts +66 -0
  7. package/dist/ExtensionServerClient/types.d.ts +124 -0
  8. package/dist/_virtual/jsx-runtime.cjs.js +1 -0
  9. package/dist/_virtual/jsx-runtime.cjs2.js +1 -0
  10. package/dist/_virtual/jsx-runtime.es.js +5 -0
  11. package/dist/_virtual/jsx-runtime.es2.js +4 -0
  12. package/dist/_virtual/react-jsx-runtime.development.cjs.js +1 -0
  13. package/dist/_virtual/react-jsx-runtime.development.es.js +4 -0
  14. package/dist/_virtual/react-jsx-runtime.production.min.cjs.js +1 -0
  15. package/dist/_virtual/react-jsx-runtime.production.min.es.js +4 -0
  16. package/dist/context/ExtensionServerProvider.d.ts +3 -0
  17. package/dist/context/ExtensionServerProvider.test.d.ts +1 -0
  18. package/dist/context/constants.d.ts +2 -0
  19. package/dist/context/index.d.ts +3 -0
  20. package/dist/context/types.d.ts +12 -0
  21. package/dist/hooks/index.d.ts +3 -0
  22. package/dist/hooks/useExtensionServerContext.d.ts +1 -0
  23. package/dist/hooks/useExtensionServerState.d.ts +1 -0
  24. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -0
  25. package/dist/i18n.d.ts +68 -0
  26. package/dist/i18n.test.d.ts +1 -0
  27. package/dist/index.cjs.js +1 -0
  28. package/dist/index.cjs2.js +1 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.es.js +46 -0
  31. package/dist/index.es2.js +8 -0
  32. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.cjs.js +22 -0
  33. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.es.js +604 -0
  34. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.cjs.js +9 -0
  35. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.es.js +28 -0
  36. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.cjs.js +1 -0
  37. package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.es.js +10 -0
  38. package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/ExtensionServerClient.cjs.js +1 -0
  39. package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/ExtensionServerClient.es.js +136 -0
  40. package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/types.cjs.js +1 -0
  41. package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/types.es.js +4 -0
  42. package/dist/packages/ui-extensions-server-kit/src/context/ExtensionServerProvider.cjs.js +1 -0
  43. package/dist/packages/ui-extensions-server-kit/src/context/ExtensionServerProvider.es.js +30 -0
  44. package/dist/packages/ui-extensions-server-kit/src/context/constants.cjs.js +1 -0
  45. package/dist/packages/ui-extensions-server-kit/src/context/constants.es.js +13 -0
  46. package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerContext.cjs.js +1 -0
  47. package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerContext.es.js +7 -0
  48. package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerState.cjs.js +1 -0
  49. package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerState.es.js +9 -0
  50. package/dist/packages/ui-extensions-server-kit/src/hooks/useIsomorphicLayoutEffect.cjs.js +1 -0
  51. package/dist/packages/ui-extensions-server-kit/src/hooks/useIsomorphicLayoutEffect.es.js +5 -0
  52. package/dist/packages/ui-extensions-server-kit/src/i18n.cjs.js +1 -0
  53. package/dist/packages/ui-extensions-server-kit/src/i18n.es.js +60 -0
  54. package/dist/packages/ui-extensions-server-kit/src/state/actions/actions.cjs.js +1 -0
  55. package/dist/packages/ui-extensions-server-kit/src/state/actions/actions.es.js +37 -0
  56. package/dist/packages/ui-extensions-server-kit/src/state/reducers/constants.cjs.js +1 -0
  57. package/dist/packages/ui-extensions-server-kit/src/state/reducers/constants.es.js +7 -0
  58. package/dist/packages/ui-extensions-server-kit/src/state/reducers/extensionServerReducer.cjs.js +1 -0
  59. package/dist/packages/ui-extensions-server-kit/src/state/reducers/extensionServerReducer.es.js +57 -0
  60. package/dist/packages/ui-extensions-server-kit/src/testing/MockExtensionServerProvider.cjs.js +1 -0
  61. package/dist/packages/ui-extensions-server-kit/src/testing/MockExtensionServerProvider.es.js +25 -0
  62. package/dist/packages/ui-extensions-server-kit/src/testing/app.cjs.js +1 -0
  63. package/dist/packages/ui-extensions-server-kit/src/testing/app.es.js +16 -0
  64. package/dist/packages/ui-extensions-server-kit/src/testing/extensions.cjs.js +1 -0
  65. package/dist/packages/ui-extensions-server-kit/src/testing/extensions.es.js +66 -0
  66. package/dist/packages/ui-extensions-server-kit/src/types.cjs.js +1 -0
  67. package/dist/packages/ui-extensions-server-kit/src/types.es.js +4 -0
  68. package/dist/packages/ui-extensions-server-kit/src/utilities/assetToString.cjs.js +1 -0
  69. package/dist/packages/ui-extensions-server-kit/src/utilities/assetToString.es.js +7 -0
  70. package/dist/packages/ui-extensions-server-kit/src/utilities/isUIExtension.cjs.js +1 -0
  71. package/dist/packages/ui-extensions-server-kit/src/utilities/isUIExtension.es.js +7 -0
  72. package/dist/packages/ui-extensions-server-kit/src/utilities/isValidSurface.cjs.js +1 -0
  73. package/dist/packages/ui-extensions-server-kit/src/utilities/isValidSurface.es.js +7 -0
  74. package/dist/packages/ui-extensions-server-kit/src/utilities/noop.cjs.js +1 -0
  75. package/dist/packages/ui-extensions-server-kit/src/utilities/noop.es.js +5 -0
  76. package/dist/packages/ui-extensions-server-kit/src/utilities/replaceUpdated.cjs.js +1 -0
  77. package/dist/packages/ui-extensions-server-kit/src/utilities/replaceUpdated.es.js +14 -0
  78. package/dist/packages/ui-extensions-server-kit/src/utilities/set.cjs.js +1 -0
  79. package/dist/packages/ui-extensions-server-kit/src/utilities/set.es.js +18 -0
  80. package/dist/state/actions/actions.d.ts +6 -0
  81. package/dist/state/actions/index.d.ts +2 -0
  82. package/dist/state/actions/types.d.ts +21 -0
  83. package/dist/state/index.d.ts +2 -0
  84. package/dist/state/reducers/constants.d.ts +2 -0
  85. package/dist/state/reducers/extensionServerReducer.d.ts +3 -0
  86. package/dist/state/reducers/extensionServerReducer.test.d.ts +1 -0
  87. package/dist/state/reducers/index.d.ts +3 -0
  88. package/dist/state/reducers/types.d.ts +6 -0
  89. package/dist/testing/MockExtensionServerProvider.d.ts +8 -0
  90. package/dist/testing/app.d.ts +2 -0
  91. package/dist/testing/extensions.d.ts +6 -0
  92. package/dist/testing/index.d.ts +3 -0
  93. package/dist/types.d.ts +196 -0
  94. package/dist/utilities/assetToString.d.ts +2 -0
  95. package/dist/utilities/assetToString.test.d.ts +1 -0
  96. package/dist/utilities/index.d.ts +6 -0
  97. package/dist/utilities/isUIExtension.d.ts +1 -0
  98. package/dist/utilities/isValidSurface.d.ts +2 -0
  99. package/dist/utilities/noop.d.ts +1 -0
  100. package/dist/utilities/replaceUpdated.d.ts +1 -0
  101. package/dist/utilities/replaceUpdated.test.d.ts +1 -0
  102. package/dist/utilities/set.d.ts +4 -0
  103. package/dist/utilities/set.test.d.ts +1 -0
  104. package/index.d.ts +1 -0
  105. package/index.js +1 -0
  106. package/index.mjs +1 -0
  107. package/node_modules/@testing-library/react/LICENSE +20 -0
  108. package/node_modules/@testing-library/react/README.md +692 -0
  109. package/node_modules/@testing-library/react/dist/@testing-library/react.cjs.js +537 -0
  110. package/node_modules/@testing-library/react/dist/@testing-library/react.esm.js +497 -0
  111. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.cjs.js +501 -0
  112. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.esm.js +461 -0
  113. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.js +502 -0
  114. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.js.map +1 -0
  115. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.min.js +2 -0
  116. package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.min.js.map +1 -0
  117. package/node_modules/@testing-library/react/dist/@testing-library/react.umd.js +538 -0
  118. package/node_modules/@testing-library/react/dist/@testing-library/react.umd.js.map +1 -0
  119. package/node_modules/@testing-library/react/dist/@testing-library/react.umd.min.js +2 -0
  120. package/node_modules/@testing-library/react/dist/@testing-library/react.umd.min.js.map +1 -0
  121. package/node_modules/@testing-library/react/dist/act-compat.js +80 -0
  122. package/node_modules/@testing-library/react/dist/config.js +33 -0
  123. package/node_modules/@testing-library/react/dist/fire-event.js +70 -0
  124. package/node_modules/@testing-library/react/dist/index.js +52 -0
  125. package/node_modules/@testing-library/react/dist/pure.js +356 -0
  126. package/node_modules/@testing-library/react/dont-cleanup-after-each.js +1 -0
  127. package/node_modules/@testing-library/react/package.json +116 -0
  128. package/node_modules/@testing-library/react/pure.d.ts +1 -0
  129. package/node_modules/@testing-library/react/pure.js +2 -0
  130. package/node_modules/@testing-library/react/types/index.d.ts +287 -0
  131. package/node_modules/@testing-library/react/types/pure.d.ts +1 -0
  132. package/node_modules/@types/react/LICENSE +21 -0
  133. package/node_modules/@types/react/README.md +15 -0
  134. package/node_modules/@types/react/canary.d.ts +166 -0
  135. package/node_modules/@types/react/experimental.d.ts +127 -0
  136. package/node_modules/@types/react/global.d.ts +160 -0
  137. package/node_modules/@types/react/index.d.ts +4543 -0
  138. package/node_modules/@types/react/jsx-dev-runtime.d.ts +45 -0
  139. package/node_modules/@types/react/jsx-runtime.d.ts +36 -0
  140. package/node_modules/@types/react/package.json +206 -0
  141. package/node_modules/@types/react/ts5.0/canary.d.ts +166 -0
  142. package/node_modules/@types/react/ts5.0/experimental.d.ts +127 -0
  143. package/node_modules/@types/react/ts5.0/global.d.ts +160 -0
  144. package/node_modules/@types/react/ts5.0/index.d.ts +4530 -0
  145. package/node_modules/@types/react/ts5.0/jsx-dev-runtime.d.ts +44 -0
  146. package/node_modules/@types/react/ts5.0/jsx-runtime.d.ts +35 -0
  147. package/node_modules/@vitejs/plugin-react/LICENSE +21 -0
  148. package/node_modules/@vitejs/plugin-react/README.md +159 -0
  149. package/node_modules/@vitejs/plugin-react/dist/index.d.ts +75 -0
  150. package/node_modules/@vitejs/plugin-react/dist/index.js +401 -0
  151. package/node_modules/@vitejs/plugin-react/dist/refresh-runtime.js +663 -0
  152. package/node_modules/@vitejs/plugin-react/package.json +77 -0
  153. package/node_modules/@vitejs/plugin-react/types/preamble.d.ts +1 -0
  154. package/package.json +64 -0
  155. package/project.json +40 -0
  156. package/scripts/create-entry-files.ts +44 -0
  157. package/src/ExtensionServerClient/ExtensionServerClient.test.ts +1238 -0
  158. package/src/ExtensionServerClient/ExtensionServerClient.ts +320 -0
  159. package/src/ExtensionServerClient/index.ts +2 -0
  160. package/src/ExtensionServerClient/server-types.ts +65 -0
  161. package/src/ExtensionServerClient/types.ts +159 -0
  162. package/src/context/ExtensionServerProvider.test.tsx +318 -0
  163. package/src/context/ExtensionServerProvider.tsx +48 -0
  164. package/src/context/constants.ts +16 -0
  165. package/src/context/index.ts +3 -0
  166. package/src/context/types.ts +14 -0
  167. package/src/hooks/index.ts +3 -0
  168. package/src/hooks/useExtensionServerContext.ts +4 -0
  169. package/src/hooks/useExtensionServerState.ts +6 -0
  170. package/src/hooks/useIsomorphicLayoutEffect.ts +6 -0
  171. package/src/i18n.test.ts +417 -0
  172. package/src/i18n.ts +205 -0
  173. package/src/index.ts +7 -0
  174. package/src/state/actions/actions.ts +36 -0
  175. package/src/state/actions/index.ts +2 -0
  176. package/src/state/actions/types.ts +26 -0
  177. package/src/state/index.ts +2 -0
  178. package/src/state/reducers/constants.ts +6 -0
  179. package/src/state/reducers/extensionServerReducer.test.ts +165 -0
  180. package/src/state/reducers/extensionServerReducer.ts +87 -0
  181. package/src/state/reducers/index.ts +3 -0
  182. package/src/state/reducers/types.ts +7 -0
  183. package/src/testing/MockExtensionServerProvider.tsx +36 -0
  184. package/src/testing/app.ts +15 -0
  185. package/src/testing/extensions.ts +71 -0
  186. package/src/testing/index.ts +3 -0
  187. package/src/types.ts +204 -0
  188. package/src/utilities/assetToString.test.ts +16 -0
  189. package/src/utilities/assetToString.ts +7 -0
  190. package/src/utilities/index.ts +6 -0
  191. package/src/utilities/isUIExtension.ts +8 -0
  192. package/src/utilities/isValidSurface.ts +6 -0
  193. package/src/utilities/noop.ts +1 -0
  194. package/src/utilities/replaceUpdated.test.ts +26 -0
  195. package/src/utilities/replaceUpdated.ts +15 -0
  196. package/src/utilities/set.test.ts +19 -0
  197. package/src/utilities/set.ts +35 -0
  198. package/testing.d.ts +1 -0
  199. package/testing.js +1 -0
  200. package/testing.mjs +1 -0
  201. package/tests/setup.ts +3 -0
  202. package/vite.config.mts +57 -0
@@ -0,0 +1,318 @@
1
+ import {ExtensionServerProvider} from './ExtensionServerProvider'
2
+ import {useExtensionServerContext} from '../hooks'
3
+ import {createConnectedAction} from '../state'
4
+ import {mockApp, mockExtension} from '../testing'
5
+ import React from 'react'
6
+ import {beforeEach, afterEach, expect} from 'vitest'
7
+ import {renderHook, act} from '@testing-library/react'
8
+
9
+ class MockWebSocketServer {
10
+ clients: MockWebSocket[] = []
11
+ messages: any[] = []
12
+
13
+ connect(socket: MockWebSocket) {
14
+ this.clients.push(socket)
15
+ socket.readyState = 1
16
+ socket.onopen?.({} as Event)
17
+ }
18
+
19
+ send(data: any) {
20
+ this.messages.push(data)
21
+ this.clients.forEach((client) => {
22
+ const event = new MessageEvent('message', {
23
+ data: typeof data === 'string' ? data : JSON.stringify(data),
24
+ })
25
+ client.onmessage?.(event)
26
+ })
27
+ }
28
+
29
+ close() {
30
+ this.clients.forEach((client) => {
31
+ client.readyState = 3
32
+ client.onclose?.({} as CloseEvent)
33
+ })
34
+ this.clients = []
35
+ this.messages = []
36
+ }
37
+ }
38
+
39
+ class MockWebSocket implements Partial<WebSocket> {
40
+ url: string
41
+ readyState = 0
42
+ onopen: ((ev: Event) => any) | null = null
43
+ onmessage: ((ev: MessageEvent) => any) | null = null
44
+ onclose: ((ev: CloseEvent) => any) | null = null
45
+ server: MockWebSocketServer
46
+ private eventListeners: Record<string, Set<EventListener>> = {
47
+ open: new Set(),
48
+ message: new Set(),
49
+ close: new Set(),
50
+ error: new Set(),
51
+ }
52
+
53
+ constructor(url: string, server: MockWebSocketServer) {
54
+ this.url = url
55
+ this.server = server
56
+ }
57
+
58
+ addEventListener(type: string, listener: EventListener): void {
59
+ if (!this.eventListeners[type]) {
60
+ this.eventListeners[type] = new Set()
61
+ }
62
+ this.eventListeners[type].add(listener)
63
+
64
+ if (type === 'open' && this.onopen === null) {
65
+ this.onopen = (event) => {
66
+ this.eventListeners.open.forEach((listener) => listener(event))
67
+ }
68
+ } else if (type === 'message' && this.onmessage === null) {
69
+ this.onmessage = (event) => {
70
+ this.eventListeners.message.forEach((listener) => listener(event))
71
+ }
72
+ } else if (type === 'close' && this.onclose === null) {
73
+ this.onclose = (event) => {
74
+ this.eventListeners.close.forEach((listener) => listener(event))
75
+ }
76
+ }
77
+ }
78
+
79
+ removeEventListener(type: string, listener: EventListener): void {
80
+ if (this.eventListeners[type]) {
81
+ this.eventListeners[type].delete(listener)
82
+ }
83
+ }
84
+
85
+ dispatchEvent(event: Event): boolean {
86
+ const type = event.type
87
+
88
+ if (type === 'open' && this.onopen) {
89
+ this.onopen(event)
90
+ } else if (type === 'message' && this.onmessage) {
91
+ this.onmessage(event as MessageEvent)
92
+ } else if (type === 'close' && this.onclose) {
93
+ this.onclose(event as CloseEvent)
94
+ }
95
+
96
+ if (this.eventListeners[type]) {
97
+ this.eventListeners[type].forEach((listener) => listener(event))
98
+ }
99
+
100
+ return true
101
+ }
102
+
103
+ send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
104
+ this.server.messages.push(data)
105
+ }
106
+
107
+ close() {
108
+ this.readyState = 3
109
+ this.onclose?.({} as CloseEvent)
110
+ }
111
+ }
112
+
113
+ let mockSocketServer: MockWebSocketServer
114
+ let originalWebSocket: typeof WebSocket
115
+
116
+ function createWrapper(options: {connection: {url?: string}}) {
117
+ return function Wrapper({children}: {children: React.ReactNode}) {
118
+ return <ExtensionServerProvider options={options}>{children}</ExtensionServerProvider>
119
+ }
120
+ }
121
+
122
+ beforeEach(() => {
123
+ mockSocketServer = new MockWebSocketServer()
124
+
125
+ originalWebSocket = globalThis.WebSocket
126
+
127
+ globalThis.WebSocket = function (url: string) {
128
+ const socket = new MockWebSocket(url, mockSocketServer)
129
+ return socket as unknown as WebSocket
130
+ } as unknown as typeof WebSocket
131
+ })
132
+
133
+ afterEach(() => {
134
+ globalThis.WebSocket = originalWebSocket
135
+ })
136
+
137
+ describe('ExtensionServerProvider tests', () => {
138
+ describe('client tests', () => {
139
+ test('creates a new ExtensionServerClient instance', async () => {
140
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
141
+
142
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
143
+
144
+ expect(result.current.client).toBeDefined()
145
+ })
146
+
147
+ test('does not start a new connection if an empty url is passed', async () => {
148
+ const options = {connection: {}}
149
+
150
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
151
+
152
+ expect(result.current.client.connection).toBeUndefined()
153
+ })
154
+ })
155
+
156
+ describe('connect tests', () => {
157
+ test('starts a new connection by calling connect', async () => {
158
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
159
+
160
+ const {result} = renderHook(useExtensionServerContext, {
161
+ wrapper: createWrapper({connection: {url: ''}}),
162
+ })
163
+
164
+ act(() => result.current.connect(options))
165
+
166
+ expect(result.current.client.connection).toBeDefined()
167
+ })
168
+ })
169
+
170
+ describe('dispatch tests', () => {
171
+ test('updates the state', async () => {
172
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
173
+ const app = mockApp()
174
+ const extension = mockExtension()
175
+ const payload = {app, extensions: [extension], store: 'test-store.com'}
176
+
177
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
178
+
179
+ act(() => {
180
+ result.current.dispatch({type: 'connected', payload})
181
+ })
182
+
183
+ expect(result.current.state).toStrictEqual({
184
+ app,
185
+ extensions: [extension],
186
+ store: 'test-store.com',
187
+ })
188
+ })
189
+ })
190
+
191
+ describe('state tests', () => {
192
+ test('persists connection data to the state', async () => {
193
+ const app = mockApp()
194
+ const extension = mockExtension()
195
+ const data = {app, store: 'test-store.com', extensions: [extension]}
196
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
197
+
198
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
199
+
200
+ act(() => {
201
+ result.current.dispatch(createConnectedAction(data))
202
+ })
203
+
204
+ expect(result.current.state).toEqual({
205
+ app,
206
+ extensions: [extension],
207
+ store: 'test-store.com',
208
+ })
209
+ })
210
+
211
+ test('persists update data to the state', async () => {
212
+ const app = mockApp()
213
+ const extension = mockExtension()
214
+ const update = {...extension, version: 'v2'}
215
+ const data = {app, store: 'test-store.com', extensions: [extension]}
216
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
217
+
218
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
219
+
220
+ act(() => {
221
+ result.current.dispatch(createConnectedAction(data))
222
+ })
223
+
224
+ act(() => {
225
+ result.current.dispatch(createConnectedAction({...data, extensions: [update]}))
226
+ })
227
+
228
+ expect(result.current.state).toEqual({
229
+ app,
230
+ extensions: [update],
231
+ store: 'test-store.com',
232
+ })
233
+ })
234
+
235
+ test('persists refresh data to the state', async () => {
236
+ const app = mockApp()
237
+ const extension = mockExtension()
238
+ const data = {app, store: 'test-store.com', extensions: [extension]}
239
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
240
+
241
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
242
+
243
+ act(() => {
244
+ result.current.dispatch(createConnectedAction(data))
245
+ })
246
+
247
+ expect(result.current.state.extensions.length).toBe(1)
248
+ expect(result.current.state.extensions[0].uuid).toBe(extension.uuid)
249
+ })
250
+
251
+ test('persists focus data to the state', async () => {
252
+ const app = mockApp()
253
+ const extension = {
254
+ ...mockExtension(),
255
+ development: {
256
+ ...mockExtension().development,
257
+ focused: false,
258
+ },
259
+ }
260
+ const data = {app, store: 'test-store.com', extensions: [extension]}
261
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
262
+
263
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
264
+
265
+ act(() => {
266
+ result.current.dispatch(createConnectedAction(data))
267
+ })
268
+
269
+ act(() => {
270
+ const focusedExtension = {
271
+ ...extension,
272
+ development: {
273
+ ...extension.development,
274
+ focused: true,
275
+ },
276
+ }
277
+ result.current.dispatch(createConnectedAction({...data, extensions: [focusedExtension]}))
278
+ })
279
+
280
+ const [updatedExtension] = result.current.state.extensions
281
+ expect(updatedExtension.development.focused).toBe(true)
282
+ })
283
+
284
+ test('persists unfocus data to the state', async () => {
285
+ const app = mockApp()
286
+ const extension = {
287
+ ...mockExtension(),
288
+ development: {
289
+ ...mockExtension().development,
290
+ focused: true,
291
+ },
292
+ }
293
+
294
+ const data = {app, store: 'test-store.com', extensions: [extension]}
295
+ const options = {connection: {url: 'ws://example-host.com:8000/extensions/'}}
296
+
297
+ const {result} = renderHook(useExtensionServerContext, {wrapper: createWrapper(options)})
298
+
299
+ act(() => {
300
+ result.current.dispatch(createConnectedAction(data))
301
+ })
302
+
303
+ act(() => {
304
+ const unfocusedExtension = {
305
+ ...extension,
306
+ development: {
307
+ ...extension.development,
308
+ focused: false,
309
+ },
310
+ }
311
+ result.current.dispatch(createConnectedAction({...data, extensions: [unfocusedExtension]}))
312
+ })
313
+
314
+ const [updatedExtension] = result.current.state.extensions
315
+ expect(updatedExtension.development.focused).toBe(false)
316
+ })
317
+ })
318
+ })
@@ -0,0 +1,48 @@
1
+ import {extensionServerContext} from './constants'
2
+ import {
3
+ createConnectedAction,
4
+ createUpdateAction,
5
+ createRefreshAction,
6
+ createFocusAction,
7
+ createUnfocusAction,
8
+ } from '../state'
9
+
10
+ import {ExtensionServerClient} from '../ExtensionServerClient'
11
+ import {ExtensionServer} from '../ExtensionServerClient/server-types.js'
12
+ import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect'
13
+ import {useExtensionServerState} from '../hooks/useExtensionServerState'
14
+
15
+ import React, {useCallback, useMemo, useState} from 'react'
16
+
17
+ import type {ExtensionServerProviderProps} from './types'
18
+
19
+ export function ExtensionServerProvider({children, options: defaultOptions}: ExtensionServerProviderProps) {
20
+ const [state, dispatch] = useExtensionServerState()
21
+ const [options, setOptions] = useState(defaultOptions)
22
+ const [client] = useState<ExtensionServer.Client>(() => new ExtensionServerClient())
23
+
24
+ const connect = useCallback(
25
+ (newOptions: ExtensionServer.Options = options) => {
26
+ setOptions(newOptions)
27
+ },
28
+ [options],
29
+ )
30
+
31
+ useIsomorphicLayoutEffect(() => client.connect(options), [client, options])
32
+
33
+ useIsomorphicLayoutEffect(() => {
34
+ const listeners = [
35
+ client.on('update', (payload) => dispatch(createUpdateAction(payload))),
36
+ client.on('connected', (payload) => dispatch(createConnectedAction(payload))),
37
+ client.on('refresh', (payload) => dispatch(createRefreshAction(payload))),
38
+ client.on('focus', (payload) => dispatch(createFocusAction(payload))),
39
+ client.on('unfocus', (payload) => dispatch(createUnfocusAction(payload))),
40
+ ]
41
+
42
+ return () => listeners.forEach((unsubscribe) => unsubscribe())
43
+ }, [dispatch])
44
+
45
+ const context = useMemo(() => ({dispatch, state, connect, client}), [dispatch, connect, state, client])
46
+
47
+ return <extensionServerContext.Provider value={context}>{children}</extensionServerContext.Provider>
48
+ }
@@ -0,0 +1,16 @@
1
+ import {ExtensionServerClient} from '../ExtensionServerClient'
2
+ import {INITIAL_STATE} from '../state'
3
+ import {noop} from '../utilities'
4
+ import {ExtensionServer} from '../ExtensionServerClient/server-types.js'
5
+ import {createContext} from 'react'
6
+
7
+ import type {ExtensionServerContext} from './types'
8
+
9
+ const DEFAULT_VALUE: ExtensionServerContext = {
10
+ connect: noop,
11
+ dispatch: noop,
12
+ state: INITIAL_STATE,
13
+ client: new ExtensionServerClient() as ExtensionServer.Client,
14
+ }
15
+
16
+ export const extensionServerContext = createContext<ExtensionServerContext>(DEFAULT_VALUE)
@@ -0,0 +1,3 @@
1
+ export * from './ExtensionServerProvider'
2
+ export * from './constants'
3
+ export * from './types'
@@ -0,0 +1,14 @@
1
+ import {ExtensionServer} from '../ExtensionServerClient/server-types.js'
2
+ import type {ExtensionServerState, ExtensionServerActions} from '../state'
3
+
4
+ export interface ExtensionServerContext {
5
+ client: ExtensionServer.Client
6
+ state: ExtensionServerState
7
+ connect(options?: ExtensionServer.Options): void
8
+ dispatch(action: ExtensionServerActions): void
9
+ }
10
+
11
+ export interface ExtensionServerProviderProps {
12
+ children?: React.ReactNode
13
+ options: ExtensionServer.Options
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from './useExtensionServerContext'
2
+ export * from './useExtensionServerState'
3
+ export * from './useIsomorphicLayoutEffect'
@@ -0,0 +1,4 @@
1
+ import {extensionServerContext} from '../context'
2
+ import {useContext} from 'react'
3
+
4
+ export const useExtensionServerContext = () => useContext(extensionServerContext)
@@ -0,0 +1,6 @@
1
+ import {extensionServerReducer, INITIAL_STATE} from '../state'
2
+ import {useReducer} from 'react'
3
+
4
+ export function useExtensionServerState() {
5
+ return useReducer(extensionServerReducer, INITIAL_STATE)
6
+ }
@@ -0,0 +1,6 @@
1
+ import {useEffect, useLayoutEffect} from 'react'
2
+
3
+ const isSSR =
4
+ typeof window === 'undefined' || !window.navigator || /ServerSideRendering|^Deno\//.test(window.navigator.userAgent)
5
+
6
+ export const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect