@kontextso/sdk-react-native 3.0.7-rc.2 → 4.0.0-rc.0
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/dist/index.d.mts +167 -4
- package/dist/index.d.ts +167 -4
- package/dist/index.js +468 -615
- package/dist/index.mjs +466 -612
- package/package.json +7 -9
- package/src/AbstractStream.ts +141 -0
- package/src/Configuration.ts +129 -0
- package/src/InlineAd.tsx +168 -0
- package/src/KontextAds.ts +40 -0
- package/src/Logger.ts +95 -0
- package/src/NativeStream.ts +20 -0
- package/src/Preload.ts +150 -0
- package/src/Session.ts +104 -0
- package/src/index.ts +2 -4
- package/src/utils/device.ts +89 -0
- package/src/utils/request.ts +73 -0
- package/src/utils/sdk.ts +16 -0
- package/src/utils/validation.ts +59 -0
- package/android/build.gradle +0 -88
- package/android/gradle.properties +0 -5
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/java/so/kontext/react/RNKontextModuleImpl.kt +0 -21
- package/android/src/newarch/java/so/kontext/react/RNKontextModule.kt +0 -22
- package/android/src/newarch/java/so/kontext/react/RNKontextPackage.kt +0 -32
- package/android/src/oldarch/java/so/kontext/react/RNKontextModule.kt +0 -25
- package/android/src/oldarch/java/so/kontext/react/RNKontextPacakge.kt +0 -16
- package/ios/KontextSDK.swift +0 -26
- package/ios/RNKontext.h +0 -13
- package/ios/RNKontext.mm +0 -36
- package/src/__tests__/util.test.ts +0 -9
- package/src/context/AdsProvider.tsx +0 -119
- package/src/formats/Format.tsx +0 -591
- package/src/formats/InlineAd.tsx +0 -8
- package/src/frame-webview.tsx +0 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontextso/sdk-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
4
4
|
"description": "Kontext SDK for React Native",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -10,19 +10,18 @@
|
|
|
10
10
|
"author": "Kontext",
|
|
11
11
|
"homepage": "https://github.com/kontextso",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"dev:js": "tsup --watch",
|
|
13
|
+
"dev:js": "tsup src/index.ts --format cjs,esm --dts --external react react-dom --watch",
|
|
14
14
|
"dev": "npm-run-all2 dev:js",
|
|
15
|
-
"build:js": "tsup",
|
|
16
|
-
"build": "npm run build:js && cross-env NODE_ENV=development
|
|
15
|
+
"build:js": "tsup src/index.ts --format cjs,esm --dts --external react",
|
|
16
|
+
"build": "npm run build:js && cross-env NODE_ENV=development",
|
|
17
17
|
"test:run": "vitest --run",
|
|
18
18
|
"test": "vitest --run",
|
|
19
|
-
"test:watch": "vitest --watch",
|
|
19
|
+
"test:watch": "vitest --watch",
|
|
20
20
|
"format": "biome format --write ."
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@kontextso/sdk-common": "^1.0.0",
|
|
24
24
|
"@kontextso/typescript-config": "*",
|
|
25
|
-
"@react-native-community/netinfo": "11.3.1",
|
|
26
25
|
"@testing-library/dom": "^10.4.0",
|
|
27
26
|
"@testing-library/jest-dom": "^6.4.6",
|
|
28
27
|
"@testing-library/react": "^16.0.0",
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
"react": "^18.3.1",
|
|
40
39
|
"react-dom": "^18.3.1",
|
|
41
40
|
"react-native": "^0.80.1",
|
|
42
|
-
"react-native-device-info": "
|
|
41
|
+
"react-native-device-info": ">=12.0.0",
|
|
43
42
|
"react-native-webview": "^13.15.0",
|
|
44
43
|
"react-test-renderer": "^18.3.1",
|
|
45
44
|
"tsup": "^8.0.2",
|
|
@@ -47,14 +46,13 @@
|
|
|
47
46
|
"vitest": "^2.1.2"
|
|
48
47
|
},
|
|
49
48
|
"peerDependencies": {
|
|
50
|
-
"@react-native-community/netinfo": "^11.0",
|
|
51
49
|
"react": ">=18.0.0",
|
|
52
50
|
"react-native": ">=0.73.0",
|
|
53
51
|
"react-native-device-info": ">=10.0.0 <15.0.0",
|
|
54
52
|
"react-native-webview": "^13.10.0"
|
|
55
53
|
},
|
|
56
54
|
"dependencies": {
|
|
57
|
-
"@kontextso/sdk-
|
|
55
|
+
"@kontextso/sdk-utils": "^1.0.0"
|
|
58
56
|
},
|
|
59
57
|
"files": [
|
|
60
58
|
"dist/*",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { handleIframeMessage, type IframeMessage, makeIframeMessage } from '@kontextso/sdk-common'
|
|
2
|
+
import type { Session } from './Session'
|
|
3
|
+
|
|
4
|
+
export abstract class AbstractStream {
|
|
5
|
+
protected session: Session
|
|
6
|
+
protected element: HTMLIFrameElement | null = null
|
|
7
|
+
|
|
8
|
+
constructor(session: Session) {
|
|
9
|
+
this.session = session
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public getIframeUrl() {
|
|
13
|
+
const bid = this.session.getLastBid()
|
|
14
|
+
if (!bid) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
const messageId = bid.messageId
|
|
18
|
+
const bidId = bid.bidId
|
|
19
|
+
|
|
20
|
+
const params = new URLSearchParams({
|
|
21
|
+
code: 'inlineAd',
|
|
22
|
+
messageId,
|
|
23
|
+
sdk: 'sdk-js',
|
|
24
|
+
})
|
|
25
|
+
const adServerUrl = this.session.config.adServerUrl
|
|
26
|
+
return `${adServerUrl}/api/frame/${bidId}?${params}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected updateHeight(height: number) {
|
|
30
|
+
if (this.element) {
|
|
31
|
+
this.element.style.height = `${height}px`
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected updateDisplay(display: string) {
|
|
36
|
+
if (this.element) {
|
|
37
|
+
this.element.style.display = display
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected sendUpdateMessage() {
|
|
42
|
+
const bid = this.session.getLastBid()
|
|
43
|
+
if (!bid) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
this.element?.contentWindow?.postMessage(
|
|
47
|
+
makeIframeMessage('update-iframe', {
|
|
48
|
+
code: 'inlineAd',
|
|
49
|
+
data: {
|
|
50
|
+
messages: this.session.getMessages(),
|
|
51
|
+
sdk: 'sdk-js',
|
|
52
|
+
messageId: bid.messageId,
|
|
53
|
+
otherParams: {},
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
'*'
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected processInitIframeMessage() {
|
|
61
|
+
this.updateDisplay('block')
|
|
62
|
+
this.sendUpdateMessage()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected processErrorIframeMessage(message: IframeMessage<'error-iframe'>) {
|
|
66
|
+
console.log('error-iframe', message.data)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected processResizeIframeMessage(message: IframeMessage<'resize-iframe'>) {
|
|
70
|
+
this.updateHeight(message.data.height)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected processEventIframeMessage(message: IframeMessage<'event-iframe'>) {
|
|
74
|
+
console.log('event-iframe', message.data)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected processShowIframeMessage() {
|
|
78
|
+
this.updateDisplay('block')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected processHideIframeMessage() {
|
|
82
|
+
this.updateDisplay('none')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected setupMessageListener() {
|
|
86
|
+
const { adServerUrl, placementCode } = this.session.config
|
|
87
|
+
const messageHandler = handleIframeMessage(
|
|
88
|
+
(message) => {
|
|
89
|
+
switch (message.type) {
|
|
90
|
+
case 'init-iframe':
|
|
91
|
+
this.processInitIframeMessage()
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
case 'error-iframe':
|
|
95
|
+
this.processErrorIframeMessage(message)
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
case 'resize-iframe':
|
|
99
|
+
this.processResizeIframeMessage(message)
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
case 'event-iframe':
|
|
103
|
+
this.processEventIframeMessage(message)
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
case 'show-iframe':
|
|
107
|
+
this.processShowIframeMessage()
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
case 'hide-iframe':
|
|
111
|
+
this.processHideIframeMessage()
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
origin: adServerUrl,
|
|
117
|
+
code: placementCode,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
// TODO: remove listener on unmount
|
|
121
|
+
window.addEventListener('message', messageHandler)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected createIframe() {
|
|
125
|
+
const iframeUrl = this.getIframeUrl()
|
|
126
|
+
if (!iframeUrl) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
const iframe = document.createElement('iframe')
|
|
130
|
+
iframe.src = iframeUrl
|
|
131
|
+
iframe.title = 'ad-iframe'
|
|
132
|
+
iframe.dataset.testid = 'ad-iframe'
|
|
133
|
+
iframe.style.display = 'none'
|
|
134
|
+
iframe.style.height = '100%'
|
|
135
|
+
iframe.style.width = '100%'
|
|
136
|
+
iframe.style.background = 'transparent'
|
|
137
|
+
iframe.style.border = '0'
|
|
138
|
+
iframe.style.colorScheme = 'auto'
|
|
139
|
+
this.element = iframe
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const DEFAULT_AD_SERVER_URL = 'https://server.megabrain.co'
|
|
2
|
+
const DEFAULT_PLACEMENT_CODE = 'inlineAd'
|
|
3
|
+
const DEFAULT_LOG_LEVEL = 'info'
|
|
4
|
+
|
|
5
|
+
export type Regulatory = {
|
|
6
|
+
/** Flag that indicates whether or not the request is subject to GDPR regulations 0 = No, 1 = Yes, omission indicates Unknown */
|
|
7
|
+
gdpr?: 0 | 1
|
|
8
|
+
|
|
9
|
+
/** When GDPR regulations are in effect this attribute contains the Transparency and Consent Framework's Consent String data structure */
|
|
10
|
+
gdprConsent?: string
|
|
11
|
+
|
|
12
|
+
/** Flag indicating if this request is subject to the COPPA regulations established by the USA FTC, where 0 = no, 1 = yes, omission indicates Unknown */
|
|
13
|
+
coppa?: 0 | 1
|
|
14
|
+
|
|
15
|
+
/** Contains the Global Privacy Platform's consent string. See IAB-GPP spec for more details */
|
|
16
|
+
gpp?: string
|
|
17
|
+
|
|
18
|
+
/** List of the section(s) of the GPP string which should be applied for this transaction */
|
|
19
|
+
gppSid?: number[]
|
|
20
|
+
|
|
21
|
+
/** Communicates signals regarding consumer privacy under US privacy regulation under CCPA and LSPA */
|
|
22
|
+
usPrivacy?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Character {
|
|
26
|
+
/** Unique ID of the character */
|
|
27
|
+
id: string
|
|
28
|
+
|
|
29
|
+
/** Name of the character */
|
|
30
|
+
name: string
|
|
31
|
+
|
|
32
|
+
/** URL of the character’s avatar */
|
|
33
|
+
avatarUrl?: string
|
|
34
|
+
|
|
35
|
+
/** Greeting of the character */
|
|
36
|
+
greeting?: string
|
|
37
|
+
|
|
38
|
+
/** Description of the character’s personality */
|
|
39
|
+
persona?: string
|
|
40
|
+
|
|
41
|
+
/** Tags of the character */
|
|
42
|
+
tags?: string[]
|
|
43
|
+
|
|
44
|
+
/** Whether the character is NSFW */
|
|
45
|
+
isNsfw?: boolean
|
|
46
|
+
|
|
47
|
+
[key: string]: string | string[] | boolean | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ConfigOptions {
|
|
51
|
+
/** Your publisher token. This token is not secret and is visible in the browser’s developer console. */
|
|
52
|
+
publisherToken: string
|
|
53
|
+
|
|
54
|
+
/** The SDK can be temporarily disabled by setting it to false (no ads will be rendered). */
|
|
55
|
+
userId: string
|
|
56
|
+
|
|
57
|
+
/** Unique identifier of the conversation. */
|
|
58
|
+
conversationId: string
|
|
59
|
+
|
|
60
|
+
/** Character information. */
|
|
61
|
+
character?: Character
|
|
62
|
+
/** A list of allowed placement codes. */
|
|
63
|
+
|
|
64
|
+
placementCode?: string
|
|
65
|
+
/** A variant ID that helps determine which type of ad to render. */
|
|
66
|
+
|
|
67
|
+
variantId?: string
|
|
68
|
+
/** Device-specific identifier provided by the operating systems (IDFA/GAID) */
|
|
69
|
+
|
|
70
|
+
/** Regulatory features. */
|
|
71
|
+
regulatory?: Regulatory
|
|
72
|
+
|
|
73
|
+
/** Local and remote logging settings. */
|
|
74
|
+
logLevel?: 'debug' | 'info' | 'log' | 'warn' | 'error' | 'silent'
|
|
75
|
+
|
|
76
|
+
/** The email of the user. */
|
|
77
|
+
userEmail?: string
|
|
78
|
+
|
|
79
|
+
/** We can set a different server URL (usually used for testing). */
|
|
80
|
+
adServerUrl?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class Configuration {
|
|
84
|
+
private config: ConfigOptions
|
|
85
|
+
|
|
86
|
+
constructor(config: ConfigOptions) {
|
|
87
|
+
this.config = config
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get publisherToken () {
|
|
91
|
+
return this.config.publisherToken
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get userId () {
|
|
95
|
+
return this.config.userId
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get conversationId () {
|
|
99
|
+
return this.config.conversationId
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get character () {
|
|
103
|
+
return this.config.character
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get placementCode () {
|
|
107
|
+
return this.config.placementCode ?? DEFAULT_PLACEMENT_CODE
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get variantId () {
|
|
111
|
+
return this.config.variantId
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get regulatory () {
|
|
115
|
+
return this.config.regulatory
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get logLevel () {
|
|
119
|
+
return this.config.logLevel ?? DEFAULT_LOG_LEVEL
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get userEmail () {
|
|
123
|
+
return this.config.userEmail
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get adServerUrl () {
|
|
127
|
+
return this.config.adServerUrl ?? DEFAULT_AD_SERVER_URL
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/InlineAd.tsx
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleIframeMessage,
|
|
3
|
+
type IframeMessage,
|
|
4
|
+
type IframeMessageEvent,
|
|
5
|
+
type IframeMessageType,
|
|
6
|
+
makeIframeMessage,
|
|
7
|
+
} from '@kontextso/sdk-common'
|
|
8
|
+
import { Session } from './Session'
|
|
9
|
+
import { useRef, useEffect, useState, useCallback } from 'react'
|
|
10
|
+
import { View } from 'react-native'
|
|
11
|
+
import { WebView, type WebViewMessageEvent } from 'react-native-webview'
|
|
12
|
+
|
|
13
|
+
interface InlineAdProps {
|
|
14
|
+
messageId: string
|
|
15
|
+
session: Session
|
|
16
|
+
onDebugEvent: (name: string, data?: any) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getIframeUrl = (bidId: string, messageId: string, adServerUrl: string) => {
|
|
20
|
+
const params = new URLSearchParams({
|
|
21
|
+
code: 'inlineAd',
|
|
22
|
+
messageId,
|
|
23
|
+
sdk: 'sdk-react-native',
|
|
24
|
+
})
|
|
25
|
+
return `${adServerUrl}/api/frame/${bidId}?${params}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const InlineAd = ({ messageId, session, onDebugEvent }: InlineAdProps) => {
|
|
29
|
+
const [bidId, setBidId] = useState<string | null>(null)
|
|
30
|
+
const webViewRef = useRef<WebView>(null)
|
|
31
|
+
const postRef = useRef<{
|
|
32
|
+
[messageId: string]: boolean
|
|
33
|
+
}>({})
|
|
34
|
+
|
|
35
|
+
const sendMessage = (
|
|
36
|
+
webViewRef: React.RefObject<WebView>,
|
|
37
|
+
type: Extract<IframeMessageType, 'update-iframe' | 'update-dimensions-iframe'>,
|
|
38
|
+
code: string,
|
|
39
|
+
data: any
|
|
40
|
+
) => {
|
|
41
|
+
if (postRef.current[messageId]) {
|
|
42
|
+
onDebugEvent('InlineAd: message already posted', { messageId, ref: postRef.current })
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const message = makeIframeMessage(type, {
|
|
46
|
+
data,
|
|
47
|
+
code,
|
|
48
|
+
})
|
|
49
|
+
onDebugEvent('InlineAd: post message', { messageId, message })
|
|
50
|
+
webViewRef.current?.injectJavaScript(`
|
|
51
|
+
window.dispatchEvent(new MessageEvent('message', {
|
|
52
|
+
data: ${JSON.stringify(message)}
|
|
53
|
+
}));
|
|
54
|
+
`)
|
|
55
|
+
postRef.current[messageId] = true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const updateBid = useCallback(() => {
|
|
59
|
+
const bid = session.getLastBid()
|
|
60
|
+
if (!bid || bid.messageId !== messageId) {
|
|
61
|
+
setBidId(null)
|
|
62
|
+
onDebugEvent('InlineAd: no bid', { messageId, bid })
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
setBidId(bid.bidId)
|
|
66
|
+
onDebugEvent('InlineAd: update bid', { messageId, bid })
|
|
67
|
+
}, [session, messageId])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
session.setOnUpdateBids(updateBid)
|
|
71
|
+
return () => {
|
|
72
|
+
session.setOnUpdateBids(undefined as any)
|
|
73
|
+
onDebugEvent('InlineAd: remove update bids callback', { messageId })
|
|
74
|
+
}
|
|
75
|
+
}, [session, updateBid])
|
|
76
|
+
|
|
77
|
+
// still okay to force an initial update when messageId changes
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
updateBid()
|
|
80
|
+
}, [updateBid])
|
|
81
|
+
|
|
82
|
+
if (!bidId) {
|
|
83
|
+
onDebugEvent('InlineAd: no bid id', { messageId, bidId })
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const iframeUrl = getIframeUrl(bidId, messageId, session.config.adServerUrl)
|
|
88
|
+
if (!iframeUrl) {
|
|
89
|
+
onDebugEvent('InlineAd: no iframe url', { messageId, bidId })
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const onMessage = (event: WebViewMessageEvent) => {
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(event.nativeEvent.data) as IframeMessage
|
|
96
|
+
const messageHandler = handleIframeMessage(
|
|
97
|
+
(message) => {
|
|
98
|
+
onDebugEvent('InlineAd: message handler', { messageId, message, bidId })
|
|
99
|
+
switch (message.type) {
|
|
100
|
+
case 'init-iframe':
|
|
101
|
+
sendMessage(webViewRef, 'update-iframe', 'inlineAd', {
|
|
102
|
+
messages: session.getMessages(),
|
|
103
|
+
sdk: 'sdk-react-native',
|
|
104
|
+
otherParams: {},
|
|
105
|
+
messageId,
|
|
106
|
+
})
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
case 'error-iframe':
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
case 'click-iframe':
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
case 'view-iframe':
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
case 'event-iframe':
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
code: 'inlineAd',
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
messageHandler({ data } as IframeMessageEvent)
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error('error parsing message from webview', e)
|
|
129
|
+
onDebugEvent('InlineAd: error parsing message from webview', { messageId, error: e })
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
onDebugEvent('InlineAd: iframe url', { messageId, iframeUrl })
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<WebView
|
|
137
|
+
source={{
|
|
138
|
+
uri: iframeUrl,
|
|
139
|
+
}}
|
|
140
|
+
style={{
|
|
141
|
+
height: 300,
|
|
142
|
+
width: '100%',
|
|
143
|
+
backgroundColor: 'red',
|
|
144
|
+
}}
|
|
145
|
+
ref={webViewRef}
|
|
146
|
+
onMessage={onMessage}
|
|
147
|
+
allowsInlineMediaPlayback={true}
|
|
148
|
+
mediaPlaybackRequiresUserAction={false}
|
|
149
|
+
javaScriptEnabled={true}
|
|
150
|
+
domStorageEnabled={true}
|
|
151
|
+
allowsFullscreenVideo={false}
|
|
152
|
+
injectedJavaScript={`
|
|
153
|
+
window.addEventListener("message", function(event) {
|
|
154
|
+
if (window.ReactNativeWebView && event.data) {
|
|
155
|
+
// ReactNativeWebView.postMessage only supports string data
|
|
156
|
+
window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
|
|
157
|
+
}
|
|
158
|
+
}, false);
|
|
159
|
+
`}
|
|
160
|
+
onError={() => {
|
|
161
|
+
onDebugEvent('InlineAd: error loading iframe', { messageId })
|
|
162
|
+
}}
|
|
163
|
+
onLoad={() => {
|
|
164
|
+
onDebugEvent('InlineAd: iframe loaded', { messageId })
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ConfigOptions, Configuration } from './Configuration'
|
|
2
|
+
import type { Message } from '@kontextso/sdk-common'
|
|
3
|
+
import { Session } from './Session'
|
|
4
|
+
import { Platform } from 'react-native'
|
|
5
|
+
import * as packageJson from '../package.json'
|
|
6
|
+
|
|
7
|
+
type GlobalConfig = Pick<ConfigOptions, 'publisherToken' | 'userId' | 'userEmail' | 'adServerUrl' | 'logLevel' | 'placementCode'>
|
|
8
|
+
|
|
9
|
+
type SessionConfig = Pick<ConfigOptions, 'conversationId' | 'character' | 'variantId' | 'regulatory'>
|
|
10
|
+
|
|
11
|
+
const KontextAds = (config: GlobalConfig) => {
|
|
12
|
+
return {
|
|
13
|
+
createSession: (sessionConfig: SessionConfig) => createSession({
|
|
14
|
+
...config,
|
|
15
|
+
...sessionConfig,
|
|
16
|
+
}),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const createSession = (options: ConfigOptions) => {
|
|
21
|
+
const sdk = {
|
|
22
|
+
name: 'sdk-react-native',
|
|
23
|
+
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
|
24
|
+
version: packageJson.version,
|
|
25
|
+
} as any
|
|
26
|
+
const instance = new Session(new Configuration(options), { sdk })
|
|
27
|
+
return {
|
|
28
|
+
addMessage: (message: Message) => {
|
|
29
|
+
instance.addMessage(message)
|
|
30
|
+
if (message.role === 'user') {
|
|
31
|
+
instance.preload().requestAd()
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
getInstance: () => instance,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
KontextAds
|
|
40
|
+
}
|
package/src/Logger.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error' | 'silent'
|
|
2
|
+
|
|
3
|
+
export class Logger {
|
|
4
|
+
private localLevel: LogLevel = 'log'
|
|
5
|
+
private remoteLevel: LogLevel = 'error'
|
|
6
|
+
private remoteConfig: { url: string; params: Record<string, any> } | null = null
|
|
7
|
+
|
|
8
|
+
private levels: Record<LogLevel, number> = {
|
|
9
|
+
debug: 0,
|
|
10
|
+
info: 1,
|
|
11
|
+
log: 2,
|
|
12
|
+
warn: 3,
|
|
13
|
+
error: 4,
|
|
14
|
+
silent: 5,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getLocalLevel() {
|
|
18
|
+
return this.localLevel
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setLocalLevel(level: LogLevel) {
|
|
22
|
+
this.localLevel = level
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getRemoteLevel() {
|
|
26
|
+
return this.remoteLevel
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setRemoteLevel(level: LogLevel) {
|
|
30
|
+
this.remoteLevel = level
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
configureRemote(url: string, params: Record<string, any>) {
|
|
34
|
+
this.remoteConfig = { url, params }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private shouldLog(level: LogLevel, targetLevel: LogLevel): boolean {
|
|
38
|
+
if (targetLevel === 'silent') {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
return this.levels[level] >= this.levels[targetLevel]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private logToConsole(level: LogLevel, ...args: any[]) {
|
|
45
|
+
if (this.shouldLog(level, this.localLevel)) {
|
|
46
|
+
if (level === 'silent') {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
console[level](...args)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private logToRemote(level: LogLevel, ...args: any[]) {
|
|
54
|
+
if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
|
|
55
|
+
// Simulate sending to remote server
|
|
56
|
+
|
|
57
|
+
fetch(`${this.remoteConfig.url}/log`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
...this.remoteConfig.params,
|
|
61
|
+
level: level,
|
|
62
|
+
message: args,
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
}),
|
|
65
|
+
}).catch((e) => {
|
|
66
|
+
// ignore errors
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
debug(...args: any[]) {
|
|
72
|
+
this.logToConsole('debug', ...args)
|
|
73
|
+
this.logToRemote('debug', ...args)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
info(...args: any[]) {
|
|
77
|
+
this.logToConsole('info', ...args)
|
|
78
|
+
this.logToRemote('info', ...args)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
log(...args: any[]) {
|
|
82
|
+
this.logToConsole('log', ...args)
|
|
83
|
+
this.logToRemote('log', ...args)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
warn(...args: any[]) {
|
|
87
|
+
this.logToConsole('warn', ...args)
|
|
88
|
+
this.logToRemote('warn', ...args)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
error(...args: any[]) {
|
|
92
|
+
this.logToConsole('error', ...args)
|
|
93
|
+
this.logToRemote('error', ...args)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AbstractStream } from "./AbstractStream"
|
|
2
|
+
|
|
3
|
+
export class NativeStream extends AbstractStream {
|
|
4
|
+
|
|
5
|
+
render(messageId: string) {
|
|
6
|
+
const bid = this.session.getLastBid()
|
|
7
|
+
if (!bid) {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
if (bid.messageId !== messageId) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
if (this.element) {
|
|
14
|
+
return this.element
|
|
15
|
+
}
|
|
16
|
+
this.setupMessageListener()
|
|
17
|
+
this.createIframe()
|
|
18
|
+
return this.element
|
|
19
|
+
}
|
|
20
|
+
}
|