@kontextso/sdk-react-native 2.1.0 → 2.1.1-rc.1

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.
@@ -0,0 +1,263 @@
1
+ import { useContext, useEffect, useRef, useState } from 'react'
2
+ import {
3
+ AdsContext,
4
+ convertParamsToString,
5
+ ErrorBoundary,
6
+ useBid,
7
+ useIframeUrl,
8
+ type FormatProps,
9
+ } from '@kontextso/sdk-react'
10
+ import { WebView, type WebViewMessageEvent } from 'react-native-webview'
11
+ import { Linking, View, useWindowDimensions } from 'react-native'
12
+ import {
13
+ handleIframeMessage,
14
+ IframeMessageEvent,
15
+ IframeMessageType,
16
+ makeIframeMessage,
17
+ type IframeMessage,
18
+ } from '@kontextso/sdk-common'
19
+
20
+ const sendMessage = (
21
+ webViewRef: React.RefObject<WebView>,
22
+ type: Extract<IframeMessageType, 'update-iframe' | 'update-dimensions-iframe'>,
23
+ code: string,
24
+ data: any
25
+ ) => {
26
+ const message = makeIframeMessage(type, {
27
+ data,
28
+ code,
29
+ })
30
+
31
+ webViewRef.current?.injectJavaScript(`
32
+ window.dispatchEvent(new MessageEvent('message', {
33
+ data: ${JSON.stringify(message)}
34
+ }));
35
+ `)
36
+ }
37
+
38
+ const Format = ({ code, messageId, wrapper, ...otherParams }: FormatProps) => {
39
+ const context = useContext(AdsContext)
40
+
41
+ const bid = useBid({ code, messageId })
42
+ const [height, setHeight] = useState<number>(0)
43
+ const iframeUrl = useIframeUrl(context, bid, code, messageId)
44
+
45
+ const [showIframe, setShowIframe] = useState<boolean>(false)
46
+ const [iframeLoaded, setIframeLoaded] = useState<boolean>(false)
47
+
48
+ const [containerStyles, setContainerStyles] = useState<any>({})
49
+ const [iframeStyles, setIframeStyles] = useState<any>({})
50
+
51
+ const containerRef = useRef<View>(null)
52
+ const webViewRef = useRef<WebView>(null)
53
+
54
+ const { height: windowHeight, width: windowWidth } = useWindowDimensions()
55
+
56
+ const reset = () => {
57
+ setHeight(0)
58
+ setShowIframe(false)
59
+ setContainerStyles({})
60
+ setIframeStyles({})
61
+ setIframeLoaded(false)
62
+ context?.resetAll()
63
+ context?.captureError(new Error('Processing iframe error'))
64
+ }
65
+
66
+ const debug = (name: string, data: any = {}) => {
67
+ context?.onDebugEventInternal?.(name, {
68
+ code,
69
+ messageId,
70
+ otherParams,
71
+ bid,
72
+ iframeUrl,
73
+ iframeLoaded,
74
+ showIframe,
75
+ height,
76
+ containerStyles,
77
+ iframeStyles,
78
+ ...data,
79
+ })
80
+ }
81
+
82
+ debug('format-update-state')
83
+
84
+ const onMessage = (event: WebViewMessageEvent) => {
85
+ try {
86
+ const data = JSON.parse(event.nativeEvent.data) as IframeMessage
87
+
88
+ debug('iframe-message', {
89
+ message: data,
90
+ })
91
+
92
+ const messageHandler = handleIframeMessage(
93
+ (message) => {
94
+ switch (message.type) {
95
+ case 'init-iframe':
96
+ setIframeLoaded(true)
97
+ debug('iframe-post-message')
98
+ sendMessage(webViewRef, 'update-iframe', code, {
99
+ messages: context?.messages,
100
+ sdk: 'sdk-react-native',
101
+ otherParams,
102
+ messageId,
103
+ })
104
+ break
105
+
106
+ case 'error-iframe':
107
+ reset()
108
+ break
109
+
110
+ case 'resize-iframe':
111
+ setHeight(message.data.height)
112
+ break
113
+
114
+ case 'click-iframe':
115
+ if (message.data.url) {
116
+ Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch((err) =>
117
+ console.error('error opening url', err)
118
+ )
119
+ }
120
+ context?.onAdClickInternal(message.data)
121
+ break
122
+
123
+ case 'view-iframe':
124
+ context?.onAdViewInternal(message.data)
125
+ break
126
+
127
+ case 'show-iframe':
128
+ setShowIframe(true)
129
+ break
130
+
131
+ case 'hide-iframe':
132
+ setShowIframe(false)
133
+ break
134
+
135
+ case 'set-styles-iframe':
136
+ setContainerStyles(message.data.containerStyles)
137
+ setIframeStyles(message.data.iframeStyles)
138
+ break
139
+ }
140
+ },
141
+ {
142
+ code,
143
+ }
144
+ )
145
+ messageHandler({ data } as IframeMessageEvent)
146
+ } catch (e) {
147
+ debug('iframe-message-error', {
148
+ error: e,
149
+ })
150
+ console.error('error parsing message from webview', e)
151
+ reset()
152
+ }
153
+ }
154
+
155
+ const paramsString = convertParamsToString(otherParams)
156
+
157
+ useEffect(() => {
158
+ if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
159
+ return
160
+ }
161
+ debug('iframe-post-message')
162
+ sendMessage(webViewRef, 'update-iframe', code, {
163
+ data: { otherParams },
164
+ code,
165
+ })
166
+ // because we use the rest params, the object is alaways new and useEffect would be called on every render
167
+ }, [paramsString, iframeLoaded, context?.adServerUrl, bid, code])
168
+
169
+ const checkIfInViewport = () => {
170
+ if (!containerRef.current) return
171
+
172
+ containerRef.current.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
173
+ sendMessage(webViewRef, 'update-dimensions-iframe', code, {
174
+ windowWidth,
175
+ windowHeight,
176
+ containerWidth,
177
+ containerHeight,
178
+ containerX,
179
+ containerY,
180
+ })
181
+ })
182
+ }
183
+
184
+ useEffect(() => {
185
+ const interval = setInterval(() => {
186
+ checkIfInViewport()
187
+ }, 250)
188
+
189
+ return () => clearInterval(interval)
190
+ }, [])
191
+
192
+ if (!context || !bid || !iframeUrl) {
193
+ return null
194
+ }
195
+
196
+ const getWidth = () => {
197
+ if (showIframe && iframeLoaded) {
198
+ return '100%'
199
+ }
200
+ return 0
201
+ }
202
+
203
+ const getHeight = () => {
204
+ if (showIframe && iframeLoaded) {
205
+ return height
206
+ }
207
+ return 0
208
+ }
209
+
210
+ const content = (
211
+ <View style={containerStyles} ref={containerRef}>
212
+ <WebView
213
+ ref={webViewRef}
214
+ source={{
215
+ uri: iframeUrl,
216
+ }}
217
+ onMessage={onMessage}
218
+ style={{
219
+ height: getHeight(),
220
+ width: getWidth(),
221
+ ...iframeStyles,
222
+ }}
223
+ allowsInlineMediaPlayback={true}
224
+ mediaPlaybackRequiresUserAction={false}
225
+ javaScriptEnabled={true}
226
+ domStorageEnabled={true}
227
+ allowsFullscreenVideo={false}
228
+ injectedJavaScript={`
229
+ function sendToLog(data) {
230
+ window.ReactNativeWebView.postMessage(JSON.stringify({
231
+ type: 'log-iframe',
232
+ data: data
233
+ }));
234
+ }
235
+
236
+ window.addEventListener("message", function(event) {
237
+ if (window.ReactNativeWebView && event.data) {
238
+ // ReactNativeWebView.postMessage only supports string data
239
+ window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
240
+ }
241
+ }, false);
242
+ `}
243
+ onError={() => {
244
+ debug('iframe-error')
245
+ reset()
246
+ }}
247
+ onLoad={() => {
248
+ debug('iframe-load')
249
+ }}
250
+ />
251
+ </View>
252
+ )
253
+
254
+ return wrapper ? wrapper(content) : content
255
+ }
256
+
257
+ const FormatWithErrorBoundary = (props: FormatProps) => (
258
+ <ErrorBoundary>
259
+ <Format {...props} />
260
+ </ErrorBoundary>
261
+ )
262
+
263
+ export default FormatWithErrorBoundary
@@ -0,0 +1,8 @@
1
+ import Format from './Format'
2
+ import { type FormatProps } from '@kontextso/sdk-react'
3
+
4
+ const InlineAd = ({ code, messageId, wrapper, ...props }: FormatProps) => {
5
+ return <Format code={code} messageId={messageId} wrapper={wrapper} {...props} />
6
+ }
7
+
8
+ export default InlineAd
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import InlineAd from './formats/InlineAd'
2
+
3
+ export * from './context/AdsProvider'
4
+
5
+ export { InlineAd }