@hopara/iframe 2.4.56 → 2.4.58

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 (38) hide show
  1. package/build/client.js +1 -1
  2. package/build/service-worker.js +1 -1
  3. package/build/types/src/client/index.d.ts +43 -0
  4. package/build/types/src/demo/Demo.d.ts +5 -0
  5. package/build/types/src/demo/index.d.ts +1 -0
  6. package/build/types/src/events/EventEmitter.d.ts +15 -0
  7. package/build/types/src/events/EventReceiver.d.ts +5 -0
  8. package/build/types/src/events/Events.d.ts +69 -0
  9. package/build/types/src/index.d.ts +1 -0
  10. package/build/types/src/service-worker.d.ts +1 -0
  11. package/build/types/src/view/Provider.d.ts +73 -0
  12. package/build/types/src/view/Router.d.ts +1 -0
  13. package/build/types/src/view/View.d.ts +24 -0
  14. package/build/types/src/view/index.d.ts +1 -0
  15. package/package.json +8 -8
  16. package/src/client/index.ts +256 -0
  17. package/src/demo/Demo.tsx +119 -0
  18. package/src/demo/index.tsx +7 -0
  19. package/src/demo/resources/assets-data.json +20552 -0
  20. package/src/demo/resources/buildings-data.json +56 -0
  21. package/src/demo/resources/sensors-data.json +830 -0
  22. package/src/events/EventEmitter.ts +90 -0
  23. package/src/events/EventReceiver.ts +24 -0
  24. package/src/events/Events.ts +86 -0
  25. package/src/index.ts +2 -0
  26. package/src/react-app-env.d.ts +9 -0
  27. package/src/service-worker.ts +1 -0
  28. package/src/view/Provider.tsx +258 -0
  29. package/src/view/Router.tsx +35 -0
  30. package/src/view/View.tsx +159 -0
  31. package/src/view/index.tsx +20 -0
  32. package/tasks/preprocessHTML.js +0 -74
  33. package/tests/golden-images/circle-layer-with-entities.png +0 -0
  34. package/tests/golden-images/circle-layer.png +0 -0
  35. package/tests/golden-images/diff/circle-layer.png +0 -0
  36. package/tests/golden-images/temp/circle-layer.png +0 -0
  37. package/tests/spec/view.system.test.js +0 -57
  38. package/tests/systemTest.js +0 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hopara/iframe",
3
- "version": "2.4.56",
3
+ "version": "2.4.58",
4
4
  "main": "build/client.js",
5
5
  "types": "build/types/src/client/index.d.ts",
6
6
  "scripts": {
@@ -34,13 +34,13 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@babel/register": "^7.15.3",
37
- "@hopara/components": "2.4.56",
38
- "@hopara/config": "2.4.56",
39
- "@hopara/design-system": "2.4.56",
40
- "@hopara/internals": "2.4.56",
41
- "@hopara/rum": "2.4.56",
42
- "@hopara/service-worker": "2.4.56",
43
- "@hopara/system-test": "2.4.56",
37
+ "@hopara/components": "2.4.58",
38
+ "@hopara/config": "2.4.58",
39
+ "@hopara/design-system": "2.4.58",
40
+ "@hopara/internals": "2.4.58",
41
+ "@hopara/rum": "2.4.58",
42
+ "@hopara/service-worker": "2.4.58",
43
+ "@hopara/system-test": "2.4.58",
44
44
  "babel-loader": "8.1.0",
45
45
  "customize-cra": "^1.0.0",
46
46
  "lodash": "~4.17.21",
@@ -0,0 +1,256 @@
1
+ import { EventEmitter } from '../events/EventEmitter'
2
+ import { EventReceiver } from '../events/EventReceiver'
3
+ import { CallbackFunctionData, InitData, LoadDataRequestData, PostMessageEvent, isCallbackFunctionEvent, isLoadDataEvent, isReadyEvent } from '../events/Events'
4
+ import packageJSON from '../../package.json'
5
+ import { DataLoader } from '@hopara/dataset'
6
+ import { Logger } from '@hopara/internals'
7
+
8
+ interface CallbackAction {
9
+ name: string
10
+ callback: (data: any) => void
11
+ }
12
+
13
+ export interface HoparaIframeConfig extends InitData {
14
+ targetElementId?: string
15
+ targetElement?: HTMLElement | null
16
+ version?: string
17
+ debug?: boolean
18
+ embeddedUrl?: string
19
+ callbacks?: CallbackAction[]
20
+ }
21
+
22
+ const embeddedEnvUrl = {
23
+ 'test': 'https://statics.test.hopara.app/embedded',
24
+ 'production': 'https://statics.hopara.app/embedded',
25
+ }
26
+
27
+ export class Hopara {
28
+ private static _version = packageJSON.version
29
+ config: HoparaIframeConfig
30
+ private iframe?: HTMLIFrameElement
31
+ private readyEventTimeoutId?: number
32
+ private readonly readyEventTimeoutMs = 5000
33
+
34
+ constructor(config: HoparaIframeConfig) {
35
+ this.config = config
36
+ }
37
+
38
+ private getIframeSrc() {
39
+ const envUrl = embeddedEnvUrl[this.config.env ?? 'production'] ?? embeddedEnvUrl.production
40
+ const url = this.config.embeddedUrl ? this.config.embeddedUrl : `${envUrl}/${this.config.version ? this.config.version : 'latest'}`
41
+ return `${url}?v=${Date.now()}${this.config.debug ? '&debug=true' : ''}`
42
+ }
43
+
44
+ private getIframeStyle() {
45
+ return 'background-color: transparent; border: 0px none transparent; padding: 0px; overflow: hidden; width: 100%; height: 100%;'
46
+ }
47
+
48
+ private createIframe(): HTMLIFrameElement {
49
+ const iframe = document.createElement('iframe')
50
+
51
+ iframe.setAttribute('src', this.getIframeSrc())
52
+ iframe.setAttribute('allow', 'geolocation; fullscreen')
53
+ iframe.setAttribute('style', this.getIframeStyle())
54
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation allow-modals allow-downloads allow-top-navigation')
55
+
56
+ return iframe
57
+ }
58
+
59
+ private getEventData(): InitData {
60
+ const eventData = {
61
+ ...this.config,
62
+ visualizationId: this.config.visualizationId ?? (this.config as any).visualization ?? (this.config as any).app,
63
+ dataLoaders: this.config.dataLoaders?.map((dataLoader) => {
64
+ return {
65
+ query: dataLoader.query ?? (dataLoader as any).name,
66
+ source: dataLoader.source,
67
+ cache: dataLoader.cache,
68
+ } as DataLoader
69
+ }),
70
+ callbackNames: this.config.callbacks?.map((callback) => callback.name),
71
+
72
+ // remove uneccessary properties from the event data to prevent postMessage errors and reduce the payload size
73
+ callbacks: undefined,
74
+ targetElementId: undefined,
75
+ targetElement: undefined,
76
+ }
77
+
78
+ return eventData
79
+ }
80
+
81
+ private handleReadyEvent(targetWindow: Window | MessageEventSource) {
82
+ return EventEmitter.init(this.getEventData(), targetWindow as Window)
83
+ }
84
+
85
+ private handleLoadDataEvent(event: PostMessageEvent<LoadDataRequestData>, targetWindow: Window | MessageEventSource) {
86
+ const dataLoader = this.config.dataLoaders?.find((dataLoader) => {
87
+ return (dataLoader.query === event.data.data.query || (dataLoader as any).name === event.data.data.query) &&
88
+ dataLoader.source === event.data.data.source
89
+ })
90
+
91
+ return dataLoader?.loader(event.data.filterSet)
92
+ .then((data) => EventEmitter.loadDataResponse(
93
+ this.getEventData(),
94
+ dataLoader,
95
+ data,
96
+ undefined,
97
+ targetWindow as Window,
98
+ ))
99
+ .catch((error) => EventEmitter.loadDataResponse(
100
+ this.getEventData(),
101
+ dataLoader,
102
+ [],
103
+ error,
104
+ targetWindow as Window,
105
+ ))
106
+ }
107
+
108
+ handleCallbackFunctionEvent(event: PostMessageEvent<CallbackFunctionData>) {
109
+ const callbackFunc = this.config.callbacks?.find((callback) => callback.name === event.data.name)
110
+ if (!callbackFunc) return
111
+ return callbackFunc.callback(event.data.row)
112
+ }
113
+
114
+ listenerFunction(event: PostMessageEvent<any>) {
115
+ const targetWindow = this.iframe?.contentWindow ?? event.source
116
+ if (!targetWindow && EventReceiver.isHoparaMessage(event)) {
117
+ throw new Error('Hopara: targetWindow is not available')
118
+ }
119
+
120
+ if (isReadyEvent(event)) {
121
+ this.clearReadyEventTimeout()
122
+ return this.handleReadyEvent(targetWindow!)
123
+ }
124
+
125
+ if (isLoadDataEvent(event)) {
126
+ return this.handleLoadDataEvent(event, targetWindow!)
127
+ }
128
+
129
+ if (isCallbackFunctionEvent(event)) {
130
+ return this.handleCallbackFunctionEvent(event)
131
+ }
132
+ }
133
+
134
+ private cachedListenerFunction
135
+
136
+ private createListeners() {
137
+ this.cachedListenerFunction = this.listenerFunction.bind(this)
138
+ window.addEventListener('message', this.cachedListenerFunction, true)
139
+ }
140
+
141
+ private removeListeners() {
142
+ window.removeEventListener('message', this.cachedListenerFunction, true)
143
+ }
144
+
145
+ private scheduleReadyEventTimeout(targetWindow?: Window | MessageEventSource | null) {
146
+ if (typeof window === 'undefined') return
147
+
148
+ const windowToWatch = targetWindow ?? this.iframe?.contentWindow ?? null
149
+ if (!windowToWatch) return
150
+
151
+ this.clearReadyEventTimeout()
152
+ this.readyEventTimeoutId = window.setTimeout(() => {
153
+ Logger.warn('Hopara: ready event not received within 5s, reloading target window')
154
+ this.readyEventTimeoutId = undefined
155
+ this.reloadTargetWindow(windowToWatch)
156
+ }, this.readyEventTimeoutMs)
157
+ }
158
+
159
+ private clearReadyEventTimeout() {
160
+ if (!this.readyEventTimeoutId) return
161
+
162
+ clearTimeout(this.readyEventTimeoutId)
163
+ this.readyEventTimeoutId = undefined
164
+ }
165
+
166
+ private reloadTargetWindow(targetWindow?: Window | MessageEventSource | null) {
167
+ const windowToReload = targetWindow ?? this.iframe?.contentWindow ?? null
168
+ if (!windowToReload && !this.iframe) return
169
+
170
+ let reloaded = false
171
+
172
+ if (windowToReload) {
173
+ try {
174
+ const location = (windowToReload as Window).location
175
+ if (location && typeof location.reload === 'function') {
176
+ location.reload()
177
+ reloaded = true
178
+ }
179
+ } catch (error) {
180
+ Logger.warn('Hopara: unable to reload target window directly', error as Error)
181
+ }
182
+ }
183
+
184
+ if (!reloaded && this.iframe) {
185
+ this.iframe.setAttribute('src', this.getIframeSrc())
186
+ reloaded = true
187
+ }
188
+
189
+ if (reloaded) {
190
+ this.scheduleReadyEventTimeout()
191
+ }
192
+ }
193
+
194
+ private doInit = () => {
195
+ const targetElement = this.config.targetElementId ? document.getElementById(this.config.targetElementId) : this.config.targetElement
196
+ if (!targetElement) {
197
+ Logger.warn('Hopara: targetElement not found')
198
+ return this
199
+ }
200
+
201
+ const iframe = this.createIframe()
202
+ targetElement.appendChild(iframe)
203
+ this.iframe = iframe
204
+
205
+ this.createListeners()
206
+ this.scheduleReadyEventTimeout()
207
+ return this
208
+ }
209
+
210
+ refresh(): void {
211
+ if (!this.iframe) throw new Error('Hopara: iframe is not available')
212
+
213
+ if (this.iframe.contentWindow) {
214
+ EventEmitter.refresh(this.getEventData(), this.iframe.contentWindow)
215
+ }
216
+ }
217
+
218
+ update(config: Partial<HoparaIframeConfig>): void {
219
+ this.config = Object.assign({}, this.config, config)
220
+
221
+ if (this.iframe?.contentWindow) {
222
+ EventEmitter.update(this.getEventData(), this.iframe.contentWindow)
223
+ }
224
+ }
225
+
226
+ destroy(): void {
227
+ if (!this.iframe) throw new Error('Hopara: iframe is not available')
228
+
229
+ this.iframe.remove()
230
+ this.iframe = undefined
231
+ this.clearReadyEventTimeout()
232
+ this.removeListeners()
233
+ }
234
+
235
+ static init(config: HoparaIframeConfig) {
236
+ if (!config) {
237
+ Logger.warn('Hopara: init config not present'); return
238
+ }
239
+ if ((!window || !window.document)) {
240
+ Logger.warn('Hopara: window is not available'); return
241
+ }
242
+
243
+ const client = new Hopara(config)
244
+ return client.doInit()
245
+ }
246
+
247
+ static moduleVersion(): string {
248
+ return this._version
249
+ }
250
+
251
+ moduleVersion(): string {
252
+ return Hopara._version
253
+ }
254
+ }
255
+
256
+ export default Hopara
@@ -0,0 +1,119 @@
1
+ import React from 'react'
2
+ import Hopara from '../client'
3
+ import buildingsData from './resources/buildings-data.json'
4
+ import sensorsData from './resources/sensors-data.json'
5
+ import assetsData from './resources/assets-data.json'
6
+ import { PureComponent } from '@hopara/design-system'
7
+
8
+ export class DemoPage extends PureComponent<any> {
9
+ componentDidMount(): void {
10
+ const hopara = Hopara.init({
11
+ debug: true,
12
+ visualizationId: '',
13
+ fallbackVisualizationId: '',
14
+ tenant: '',
15
+ accessToken: '',
16
+ // refreshToken: '',
17
+ targetElementId: 'embedded-target-element',
18
+ darkMode: true,
19
+ // filters: [{
20
+ // field: 'type',
21
+ // values: ['REFRIGERATOR', 'FREEZER'],
22
+ // }],
23
+ // initialRow: {
24
+ // layerId: 'hopara-floors',
25
+ // rowId: '1',
26
+ // },
27
+ embeddedUrl: 'http://localhost:3000/',
28
+ dataLoaders: [
29
+ // ...ibbxMockLoaders,
30
+ {
31
+ query: 'labs_floorplans',
32
+ source: 'sample',
33
+ loader: async () => {
34
+ return buildingsData
35
+ },
36
+ } as any,
37
+ {
38
+ query: 'sensors',
39
+ source: 'sample',
40
+ loader: async () => {
41
+ return sensorsData
42
+ },
43
+ } as any,
44
+ {
45
+ query: 'labs_assets',
46
+ source: 'sample',
47
+ loader: async () => {
48
+ return assetsData
49
+ },
50
+ } as any,
51
+ ],
52
+ callbacks: [
53
+ {
54
+ name: 'CALLBACK_TEST',
55
+ callback: (data) => {
56
+ alert(`CALLBACK_TEST 4: ${data._id}`)
57
+ },
58
+ },
59
+ {
60
+ name: 'goTo3D',
61
+ callback: () => {
62
+ hopara?.update({
63
+ visualizationId: '78-ativo-3d-2',
64
+ fallbackVisualizationId: 'ativo-3d-2',
65
+ })
66
+ },
67
+ },
68
+ {
69
+ name: 'goTo2D',
70
+ callback: () => {
71
+ hopara?.update({
72
+ visualizationId: '78-ativo-2d-2',
73
+ fallbackVisualizationId: 'ativo-2d-2',
74
+ })
75
+ },
76
+ },
77
+ ],
78
+ })
79
+
80
+ // setInterval(() => {
81
+ // hopara?.update({
82
+ // visualizationId: '78-ativo-3d-2',
83
+ // fallbackVisualizationId: 'ativo-3d-2',
84
+ // })
85
+ // }, 10000)
86
+ }
87
+
88
+ render() {
89
+ return (
90
+ <div style={{
91
+ display: 'grid',
92
+ gridTemplateRows: '70px 1fr',
93
+ width: '100vw',
94
+ height: '100vh',
95
+ overflow: 'hidden',
96
+ }}>
97
+ <h1 style={{
98
+ alignItems: 'center',
99
+ background: '#efefef',
100
+ display: 'flex',
101
+ font: '22px sans-serif',
102
+ justifyContent: 'center',
103
+ margin: 0,
104
+ padding: '0.5em',
105
+ textAlign: 'center',
106
+ }}>
107
+ Hopara embedded demo page!
108
+ </h1>
109
+ <div
110
+ id="embedded-target-element"
111
+ style={{
112
+ width: '100%',
113
+ height: '100%',
114
+ background: '#fefefe',
115
+ }}></div>
116
+ </div>
117
+ )
118
+ }
119
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react'
2
+ import {createRoot} from 'react-dom/client'
3
+ import { DemoPage } from './Demo'
4
+
5
+ createRoot(
6
+ document.getElementById('root') as Element)
7
+ .render(<DemoPage />)