@kontextso/sdk-react-native 3.0.7-rc.2 → 3.0.7-rc.3

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.
@@ -7,17 +7,12 @@ import {
7
7
  } from '@kontextso/sdk-common'
8
8
  import {
9
9
  AdsContext,
10
- type ContextType,
11
- convertParamsToString,
12
- ErrorBoundary,
13
10
  type FormatProps,
14
11
  useBid,
15
- useIframeUrl,
16
12
  } from '@kontextso/sdk-react'
17
- import { useContext, useEffect, useRef, useState } from 'react'
18
- import { Keyboard, Linking, Modal, useWindowDimensions, View } from 'react-native'
19
- import type { WebView, WebViewMessageEvent } from 'react-native-webview'
20
- import FrameWebView from '../frame-webview'
13
+ import { useContext, useRef } from 'react'
14
+ import { View } from 'react-native'
15
+ import { WebView, type WebViewMessageEvent } from 'react-native-webview'
21
16
 
22
17
  const sendMessage = (
23
18
  webViewRef: React.RefObject<WebView>,
@@ -37,82 +32,30 @@ const sendMessage = (
37
32
  `)
38
33
  }
39
34
 
40
- const getCachedContent = (context: ContextType, bidId?: string) => {
41
- if (!bidId) {
35
+ const getUrl = (code: string, messageId: string, bidId?: string) => {
36
+ const context = useContext(AdsContext)
37
+ if (!context || !bidId) {
42
38
  return null
43
39
  }
44
- return context?.cachedContentRef?.current?.get(bidId) ?? null
40
+
41
+ const adServerUrl = context?.adServerUrl
42
+
43
+ const params = new URLSearchParams({
44
+ code,
45
+ messageId,
46
+ sdk: 'sdk-react-native',
47
+ })
48
+
49
+ return `${adServerUrl}/api/frame/${bidId}?${params}`
45
50
  }
46
51
 
47
52
  const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatProps) => {
48
53
  const context = useContext(AdsContext)
49
54
 
50
55
  const bid = useBid({ code, messageId })
51
- const [height, setHeight] = useState<number>(0)
56
+ const iframeUrl = getUrl(code, messageId, bid?.bidId)
52
57
 
53
- const cachedContent = getCachedContent(context, bid?.bidId)
54
-
55
- const iframeUrl = useIframeUrl(bid, code, messageId, 'sdk-react-native', otherParams.theme, cachedContent)
56
- const modalUrl = iframeUrl.replace('/api/frame/', '/api/modal/')
57
-
58
- const [showIframe, setShowIframe] = useState<boolean>(false)
59
- const [iframeLoaded, setIframeLoaded] = useState<boolean>(false)
60
-
61
- const [modalOpen, setModalOpen] = useState<boolean>(false)
62
- const [modalShown, setModalShown] = useState<boolean>(false)
63
- const [modalLoaded, setModalLoaded] = useState<boolean>(false)
64
-
65
- const [containerStyles, setContainerStyles] = useState<any>({})
66
- const [iframeStyles, setIframeStyles] = useState<any>({})
67
-
68
- const containerRef = useRef<View>(null)
69
58
  const webViewRef = useRef<WebView>(null)
70
- const modalWebViewRef = useRef<WebView>(null)
71
- const modalInitTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
72
- const isModalInitRef = useRef<boolean>(false)
73
-
74
- const { height: windowHeight, width: windowWidth } = useWindowDimensions()
75
-
76
- const keyboardHeightRef = useRef(0)
77
-
78
- const isAdViewVisible = showIframe && iframeLoaded
79
-
80
- const reset = () => {
81
- setHeight(0)
82
- setShowIframe(false)
83
- setContainerStyles({})
84
- setIframeStyles({})
85
- setIframeLoaded(false)
86
- resetModal()
87
- context?.resetAll()
88
- context?.captureError(new Error('Processing iframe error'))
89
- }
90
-
91
- const resetModal = () => {
92
- debugModal('Format:resetModal', {
93
- params: {
94
- messageId,
95
- code,
96
- otherParams,
97
- }
98
- })
99
- if (modalInitTimeoutRef.current) {
100
- debugModal('Format:resetModalTimeout', {
101
- params: {
102
- messageId,
103
- code,
104
- otherParams,
105
- }
106
- })
107
- clearTimeout(modalInitTimeoutRef.current)
108
- modalInitTimeoutRef.current = null
109
- }
110
-
111
- isModalInitRef.current = false
112
- setModalOpen(false)
113
- setModalLoaded(false)
114
- setModalShown(false)
115
- }
116
59
 
117
60
  const debug = (name: string, data: any = {}) => {
118
61
  context?.onDebugEventInternal?.(name, {
@@ -121,25 +64,6 @@ const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatPro
121
64
  otherParams,
122
65
  bid,
123
66
  iframeUrl,
124
- iframeLoaded,
125
- showIframe,
126
- height,
127
- containerStyles,
128
- iframeStyles,
129
- ...data,
130
- })
131
- }
132
-
133
- const debugModal = (name: string, data: any = {}) => {
134
- context?.onDebugEventInternal?.(name, {
135
- code,
136
- messageId,
137
- otherParams,
138
- bid,
139
- modalUrl,
140
- modalOpen,
141
- modalShown,
142
- modalLoaded,
143
67
  ...data,
144
68
  })
145
69
  }
@@ -165,7 +89,6 @@ const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatPro
165
89
  (message) => {
166
90
  switch (message.type) {
167
91
  case 'init-iframe':
168
- setIframeLoaded(true)
169
92
  debug('Format:iframePostMessage', {
170
93
  params: {
171
94
  code,
@@ -183,56 +106,6 @@ const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatPro
183
106
  })
184
107
  break
185
108
 
186
- case 'error-iframe':
187
- reset()
188
- break
189
-
190
- case 'resize-iframe':
191
- setHeight(message.data.height)
192
- break
193
-
194
- case 'click-iframe':
195
- if (message.data.url) {
196
- Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch((err) =>
197
- console.error('error opening url', err)
198
- )
199
- }
200
- context?.onAdClickInternal(message.data)
201
- break
202
-
203
- case 'view-iframe':
204
- context?.onAdViewInternal(message.data)
205
- break
206
-
207
- case 'ad-done-iframe':
208
- if (bid?.bidId && message.data.cachedContent) {
209
- context?.cachedContentRef?.current?.set(bid.bidId, message.data.cachedContent)
210
- }
211
- break
212
-
213
- case 'show-iframe':
214
- setShowIframe(true)
215
- break
216
-
217
- case 'hide-iframe':
218
- setShowIframe(false)
219
- break
220
-
221
- case 'set-styles-iframe':
222
- setContainerStyles(message.data.containerStyles)
223
- setIframeStyles(message.data.iframeStyles)
224
- break
225
-
226
- case 'open-component-iframe':
227
- setModalOpen(true)
228
-
229
- modalInitTimeoutRef.current = setTimeout(() => {
230
- if (!isModalInitRef.current) {
231
- resetModal()
232
- }
233
- }, message.data.timeout ?? 5000)
234
- break
235
-
236
109
  case 'event-iframe':
237
110
  onEvent?.(message.data)
238
111
  context?.onAdEventInternal(message.data)
@@ -250,219 +123,9 @@ const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatPro
250
123
  error: e,
251
124
  })
252
125
  console.error('error parsing message from webview', e)
253
- reset()
254
126
  }
255
127
  }
256
128
 
257
- const onModalMessage = (event: WebViewMessageEvent) => {
258
- try {
259
- const data = JSON.parse(event.nativeEvent.data) as IframeMessage
260
-
261
- debugModal('Format:modalIframeMessage', {
262
- params: { data, messageId, code, otherParams },
263
- message: data,
264
- })
265
-
266
- const messageHandler = handleIframeMessage(
267
- (message) => {
268
- switch (message.type) {
269
- case 'close-component-iframe':
270
- resetModal()
271
- break
272
-
273
- case 'init-component-iframe':
274
- // Just clearing the timeoutRef didn't work in Android, so we need to set a flag and check it in the timeout callback
275
- isModalInitRef.current = true
276
-
277
- if (modalInitTimeoutRef.current) {
278
- clearTimeout(modalInitTimeoutRef.current)
279
- modalInitTimeoutRef.current = null
280
- }
281
-
282
- setModalShown(true)
283
- break
284
-
285
- case 'error-component-iframe':
286
- case 'error-iframe':
287
- resetModal()
288
- context?.captureError(new Error('Processing modal iframe error'))
289
- break
290
-
291
- case 'click-iframe':
292
- if (message.data.url) {
293
- Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch((err) =>
294
- console.error('error opening url', err)
295
- )
296
- }
297
- context?.onAdClickInternal(message.data)
298
- break
299
-
300
- case 'event-iframe':
301
- onEvent?.(message.data)
302
- context?.onAdEventInternal(message.data)
303
- break
304
- }
305
- },
306
- {
307
- code,
308
- component: 'modal',
309
- }
310
- )
311
- messageHandler({ data } as IframeMessageEvent)
312
- } catch (e) {
313
- debugModal('Format:modalIframeMessageError', {
314
- params: { error: e, messageId, code, otherParams },
315
- error: e,
316
- })
317
- console.error('error parsing message from webview', e)
318
- resetModal()
319
- }
320
- }
321
-
322
- const paramsString = convertParamsToString(otherParams)
323
-
324
- useEffect(() => {
325
- if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
326
- debug('Format:iframePostMessageNotLoaded', {
327
- params: {
328
- messageId,
329
- iframeLoaded,
330
- contextAdServerUrl: context?.adServerUrl,
331
- bid,
332
- code,
333
- otherParams,
334
- }
335
- })
336
- return
337
- }
338
- debug('Format:iframePostMessage', {
339
- params: {
340
- messageId,
341
- otherParams,
342
- code,
343
- }
344
- })
345
- sendMessage(webViewRef, 'update-iframe', code, {
346
- data: { otherParams },
347
- code,
348
- })
349
- // because we use the rest params, the object is alaways new and useEffect would be called on every render
350
- }, [paramsString, iframeLoaded, context?.adServerUrl, bid, code])
351
-
352
- const checkIfInViewport = () => {
353
- if (!containerRef.current) {
354
- debug('Format:checkIfInViewportNoContainer', {
355
- params: {
356
- messageId,
357
- code,
358
- otherParams,
359
- }
360
- })
361
- return
362
- }
363
-
364
- debug('Format:checkIfInViewportMeasure', {
365
- params: {
366
- windowWidth,
367
- windowHeight,
368
- messageId,
369
- code,
370
- otherParams,
371
- }
372
- })
373
-
374
- containerRef.current.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
375
- sendMessage(webViewRef, 'update-dimensions-iframe', code, {
376
- windowWidth,
377
- windowHeight,
378
- containerWidth,
379
- containerHeight,
380
- containerX,
381
- containerY,
382
- keyboardHeight: keyboardHeightRef.current,
383
- })
384
- debug('Format:checkIfInViewportMeasureSend', {
385
- params: {
386
- messageId,
387
- code,
388
- otherParams,
389
- windowWidth,
390
- windowHeight,
391
- containerWidth,
392
- containerHeight,
393
- containerX,
394
- containerY,
395
- keyboardHeight: keyboardHeightRef.current,
396
- }
397
- })
398
- })
399
- }
400
-
401
- useEffect(() => {
402
- if (!isAdViewVisible) {
403
- debug('Format:checkIfInViewportNotVisible', {
404
- params: {
405
- messageId,
406
- code,
407
- otherParams,
408
- }
409
- })
410
- return
411
- }
412
-
413
- const interval = setInterval(() => {
414
- checkIfInViewport()
415
- }, 250)
416
-
417
- return () => {
418
- clearInterval(interval)
419
- debug('Format:checkIfInViewportCleanup', {
420
- params: {
421
- messageId,
422
- code,
423
- otherParams,
424
- }
425
- })
426
- }
427
- }, [isAdViewVisible])
428
-
429
- useEffect(() => {
430
- const showSubscription = Keyboard.addListener('keyboardDidShow', (e) => {
431
- debug('Format:keyboardDidShow', {
432
- params: {
433
- keyboardHeight: e?.endCoordinates?.height ?? 0,
434
- messageId,
435
- code,
436
- otherParams,
437
- }
438
- })
439
- keyboardHeightRef.current = e?.endCoordinates?.height ?? 0
440
- })
441
- const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
442
- debug('Format:keyboardDidHide', {
443
- params: {
444
- messageId,
445
- code,
446
- otherParams,
447
- }
448
- })
449
- keyboardHeightRef.current = 0
450
- })
451
-
452
- return () => {
453
- showSubscription.remove()
454
- hideSubscription.remove()
455
- keyboardHeightRef.current = 0
456
- debug('Format:keyboardEffectCleanup', {
457
- params: {
458
- messageId,
459
- code,
460
- otherParams,
461
- }
462
- })
463
- }
464
- }, [])
465
-
466
129
  if (!context || !bid || !iframeUrl) {
467
130
  debug('Format:noContextOrBidOrIframeUrl', {
468
131
  params: {
@@ -477,115 +140,117 @@ const Format = ({ code, messageId, wrapper, onEvent, ...otherParams }: FormatPro
477
140
  return null
478
141
  }
479
142
 
480
- const inlineContent = (
481
- <FrameWebView
482
- ref={webViewRef}
483
- iframeUrl={iframeUrl}
484
- onMessage={onMessage}
143
+ return (
144
+ <View
485
145
  style={{
486
- height,
146
+ height: 300,
487
147
  width: '100%',
488
148
  backgroundColor: 'transparent',
489
149
  borderWidth: 0,
490
- ...iframeStyles,
491
- }}
492
- onError={() => {
493
- debug('Format:iframeError', {
494
- params: {
495
- messageId,
496
- code,
497
- otherParams,
498
- }
499
- })
500
- reset()
501
- }}
502
- onLoad={() => {
503
- debug('Format:iframeLoad', {
504
- params: {
505
- messageId,
506
- code,
507
- otherParams,
508
- }
509
- })
510
150
  }}
511
- />
512
- )
513
-
514
- const interstitialContent = (
515
- <Modal
516
- visible={modalOpen}
517
- transparent={true}
518
- onRequestClose={resetModal}
519
- animationType="slide"
520
- statusBarTranslucent={true}
521
151
  >
522
- <View
152
+
153
+ <WebView
154
+ ref={webViewRef}
155
+ source={{
156
+ uri: iframeUrl,
157
+ }}
158
+ onMessage={onMessage}
523
159
  style={{
524
- flex: 1,
525
- // Don't show the modal until the modal page is loaded and sends 'init-component-iframe' message back to SDK
526
- ...(modalShown ? { opacity: 1, pointerEvents: 'auto' } : { opacity: 0, pointerEvents: 'none' }),
160
+ height: 300,
161
+ width: '100%',
162
+ backgroundColor: 'transparent',
163
+ borderWidth: 0,
527
164
  }}
528
- >
529
- <FrameWebView
530
- ref={modalWebViewRef}
531
- iframeUrl={modalUrl}
532
- onMessage={onModalMessage}
533
- style={{
534
- backgroundColor: 'transparent',
535
- height: '100%',
536
- width: '100%',
537
- borderWidth: 0,
538
- }}
539
- onError={() => {
540
- debug('Format:modalError', {
541
- params: {
542
- messageId,
543
- code,
544
- otherParams,
545
- }
546
- })
547
- resetModal()
548
- }}
549
- onLoad={() => {
550
- debug('Format:modalLoad', {
551
- params: {
552
- messageId,
553
- code,
554
- otherParams,
555
- }
556
- })
557
- setModalLoaded(true)
558
- }}
559
- />
560
- </View>
561
- </Modal>
562
- )
563
-
564
- return (
565
- <>
566
- <View
567
- style={
568
- isAdViewVisible
569
- ? containerStyles
570
- : {
571
- height: 0,
572
- overflow: 'hidden',
573
- }
574
- }
575
- ref={containerRef}
576
- >
577
- {wrapper ? wrapper(inlineContent) : inlineContent}
578
- </View>
579
-
580
- {interstitialContent}
581
- </>
165
+ allowsInlineMediaPlayback={true}
166
+ mediaPlaybackRequiresUserAction={false}
167
+ javaScriptEnabled={true}
168
+ domStorageEnabled={true}
169
+ allowsFullscreenVideo={false}
170
+ injectedJavaScript={`
171
+ window.addEventListener("message", function(event) {
172
+ if (window.ReactNativeWebView && event.data) {
173
+ // ReactNativeWebView.postMessage only supports string data
174
+ window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
175
+ }
176
+ }, false);
177
+ `}
178
+ onLoadStart={() => {
179
+ debug('Format:iframeLoadStart', {
180
+ params: {
181
+ messageId,
182
+ code,
183
+ otherParams,
184
+ }
185
+ })
186
+ }}
187
+ onError={() => {
188
+ debug('Format:iframeError', {
189
+ params: {
190
+ messageId,
191
+ code,
192
+ otherParams,
193
+ }
194
+ })
195
+ }}
196
+ onLoad={() => {
197
+ debug('Format:iframeLoad', {
198
+ params: {
199
+ messageId,
200
+ code,
201
+ otherParams,
202
+ }
203
+ })
204
+ }}
205
+ onLoadProgress={() => {
206
+ debug('Format:iframeLoadProgress', {
207
+ params: {
208
+ messageId,
209
+ code,
210
+ otherParams,
211
+ }
212
+ })
213
+ }}
214
+ onHttpError={() => {
215
+ debug('Format:iframeHttpError', {
216
+ params: {
217
+ messageId,
218
+ code,
219
+ otherParams,
220
+ }
221
+ })
222
+ }}
223
+ onRenderProcessGone={() => {
224
+ debug('Format:iframeRenderProcessGone', {
225
+ params: {
226
+ messageId,
227
+ code,
228
+ otherParams,
229
+ }
230
+ })
231
+ }}
232
+ onNavigationStateChange={() => {
233
+ debug('Format:iframeNavigationStateChange', {
234
+ params: {
235
+ messageId,
236
+ code,
237
+ otherParams,
238
+ }
239
+ })
240
+ }}
241
+ onContentProcessDidTerminate={() => {
242
+ debug('Format:iframeContentProcessDidTerminate', {
243
+ params: {
244
+ messageId,
245
+ code,
246
+ otherParams,
247
+ }
248
+ })
249
+ }}
250
+
251
+ />
252
+ </View>
582
253
  )
583
254
  }
584
255
 
585
- const FormatWithErrorBoundary = (props: FormatProps) => (
586
- <ErrorBoundary>
587
- <Format {...props} />
588
- </ErrorBoundary>
589
- )
590
-
591
- export default FormatWithErrorBoundary
256
+ export default Format