@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.
- package/README.md +1 -1
- package/RNKontext.podspec +21 -0
- package/android/build.gradle +88 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/so/kontext/react/RNKontextModuleImpl.kt +21 -0
- package/android/src/newarch/java/so/kontext/react/RNKontextModule.kt +22 -0
- package/android/src/newarch/java/so/kontext/react/RNKontextPackage.kt +32 -0
- package/android/src/oldarch/java/so/kontext/react/RNKontextModule.kt +25 -0
- package/android/src/oldarch/java/so/kontext/react/RNKontextPacakge.kt +16 -0
- package/dist/index.js +77 -193
- package/dist/index.mjs +64 -180
- package/ios/KontextSDK.swift +26 -0
- package/ios/RNKontext.h +13 -0
- package/ios/RNKontext.mm +36 -0
- package/package.json +26 -4
- package/src/NativeRNKontext.ts +8 -0
- package/src/context/AdsProvider.tsx +60 -0
- package/src/formats/Format.tsx +263 -0
- package/src/formats/InlineAd.tsx +8 -0
- package/src/index.ts +5 -0
|
@@ -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
|