@media-suite/reactnative-sdk 0.1.1 → 0.2.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/MediaSuiteAd.js +12 -9
- package/dist/MediaSuiteProvider.js +9 -7
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/MediaSuiteAd.js
CHANGED
|
@@ -14,6 +14,7 @@ export function MediaSuiteAd({ spaceCode, style, screenName, onAdLoaded, onError
|
|
|
14
14
|
const scrollCheckInterval = useRef(null);
|
|
15
15
|
const adsRef = useRef([]);
|
|
16
16
|
const consecutiveFailures = useRef(0);
|
|
17
|
+
const lastClickTime = useRef(0);
|
|
17
18
|
// Keep adsRef in sync for use inside interval callbacks
|
|
18
19
|
useEffect(() => { adsRef.current = ads; }, [ads]);
|
|
19
20
|
// Fetch ads — skip until visitorId is ready to avoid false onError
|
|
@@ -125,23 +126,26 @@ export function MediaSuiteAd({ spaceCode, style, screenName, onAdLoaded, onError
|
|
|
125
126
|
cancelViewabilityCheck();
|
|
126
127
|
}
|
|
127
128
|
}, [ads, index, onError, cancelViewabilityCheck]);
|
|
128
|
-
// Click handler —
|
|
129
|
+
// Click handler — debounced, opens tracking URL via Linking (backend 302 redirects to destination)
|
|
129
130
|
const handlePress = useCallback(async () => {
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
if (now - lastClickTime.current < 1000)
|
|
133
|
+
return; // 1s debounce
|
|
134
|
+
lastClickTime.current = now;
|
|
130
135
|
const ad = ads[index];
|
|
131
136
|
if (!ad)
|
|
132
137
|
return;
|
|
133
138
|
const clickUrl = getClickUrl(ad.placement_id, screenName);
|
|
134
|
-
|
|
135
|
-
fetch(clickUrl).catch(() => { });
|
|
136
|
-
// Open the destination URL directly
|
|
137
|
-
if (ad.click_url) {
|
|
138
|
-
await Linking.openURL(ad.click_url).catch(() => { });
|
|
139
|
-
}
|
|
139
|
+
await Linking.openURL(clickUrl).catch(() => { });
|
|
140
140
|
}, [ads, index, getClickUrl, screenName]);
|
|
141
141
|
if (!ads.length)
|
|
142
142
|
return null;
|
|
143
143
|
const ad = ads[index];
|
|
144
|
-
return (_jsx(TouchableOpacity, { activeOpacity: 0.9, onPress: handlePress, style: [styles.container, style], accessibilityLabel: ad.alt_text || 'Advertisement', accessibilityRole: "link", children: _jsx(View, { ref: viewRef, collapsable: false, children: _jsx(Image, { source: { uri: ad.image_url }, style: [
|
|
144
|
+
return (_jsx(TouchableOpacity, { activeOpacity: 0.9, onPress: handlePress, style: [styles.container, style], accessibilityLabel: ad.alt_text || 'Advertisement', accessibilityRole: "link", children: _jsx(View, { ref: viewRef, collapsable: false, children: _jsx(Image, { source: { uri: ad.image_url }, style: [
|
|
145
|
+
styles.image,
|
|
146
|
+
{ aspectRatio: ad.image_width && ad.image_height ? ad.image_width / ad.image_height : 16 / 9 },
|
|
147
|
+
loaded && styles.imageLoaded,
|
|
148
|
+
], resizeMode: "contain", onLoad: () => { setLoaded(true); consecutiveFailures.current = 0; }, onError: handleImageError }, ad.placement_id) }) }));
|
|
145
149
|
}
|
|
146
150
|
const styles = StyleSheet.create({
|
|
147
151
|
container: {
|
|
@@ -150,7 +154,6 @@ const styles = StyleSheet.create({
|
|
|
150
154
|
},
|
|
151
155
|
image: {
|
|
152
156
|
width: '100%',
|
|
153
|
-
aspectRatio: 16 / 9,
|
|
154
157
|
opacity: 0,
|
|
155
158
|
},
|
|
156
159
|
imageLoaded: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useEffect, useState, useRef, useCallback } from 'react';
|
|
3
|
-
import { useWindowDimensions } from 'react-native';
|
|
3
|
+
import { Platform, useWindowDimensions } from 'react-native';
|
|
4
4
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
5
|
function uuid() {
|
|
6
6
|
// Prefer crypto.randomUUID (available since RN 0.73+ with Hermes)
|
|
@@ -66,13 +66,15 @@ export function MediaSuiteProvider({ config, children, }) {
|
|
|
66
66
|
};
|
|
67
67
|
}, []);
|
|
68
68
|
const fetchSpace = useCallback(async (spaceCode) => {
|
|
69
|
+
const CACHE_TTL = 120000; // 120s — matches backend KVS TTL
|
|
69
70
|
const cacheKey = `${spaceCode}:${device}`;
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const cached = cache.current[cacheKey];
|
|
72
|
+
if (cached && Date.now() - cached.ts < CACHE_TTL)
|
|
73
|
+
return cached.data;
|
|
72
74
|
if (!visitorId)
|
|
73
75
|
return null;
|
|
74
76
|
try {
|
|
75
|
-
const url = `${baseUrl}/sdk/serve/${encodeURIComponent(config.companyId)}?spaces=${encodeURIComponent(spaceCode)}&device=${device}&visitor_id=${encodeURIComponent(visitorId)}`;
|
|
77
|
+
const url = `${baseUrl}/sdk/serve/${encodeURIComponent(config.companyId)}?spaces=${encodeURIComponent(spaceCode)}&device=${device}&visitor_id=${encodeURIComponent(visitorId)}&os=${Platform.OS}`;
|
|
76
78
|
const res = await fetch(url, {
|
|
77
79
|
headers: { 'api-key': config.apiKey },
|
|
78
80
|
});
|
|
@@ -81,7 +83,7 @@ export function MediaSuiteProvider({ config, children, }) {
|
|
|
81
83
|
const json = await res.json();
|
|
82
84
|
const space = json.data.spaces.find((s) => s.space_code === spaceCode) || null;
|
|
83
85
|
if (space)
|
|
84
|
-
cache.current[cacheKey] = space;
|
|
86
|
+
cache.current[cacheKey] = { data: space, ts: Date.now() };
|
|
85
87
|
return space;
|
|
86
88
|
}
|
|
87
89
|
catch {
|
|
@@ -96,7 +98,7 @@ export function MediaSuiteProvider({ config, children, }) {
|
|
|
96
98
|
return;
|
|
97
99
|
pendingImpressions.current = [];
|
|
98
100
|
const pageUrl = encodeURIComponent(screenName || 'app://unknown');
|
|
99
|
-
const url = `${baseUrl}/sdk/impressions?company=${encodeURIComponent(config.companyId)}&placements=${ids.join(',')}&visitor_id=${encodeURIComponent(visitorId)}&device=${device}&page_url=${pageUrl}`;
|
|
101
|
+
const url = `${baseUrl}/sdk/impressions?company=${encodeURIComponent(config.companyId)}&placements=${ids.join(',')}&visitor_id=${encodeURIComponent(visitorId)}&device=${device}&page_url=${pageUrl}&os=${Platform.OS}`;
|
|
100
102
|
fetch(url, { headers: { 'api-key': config.apiKey } }).catch(() => {
|
|
101
103
|
if (retryCount < MAX_RETRIES && isMounted.current) {
|
|
102
104
|
ids.forEach((id) => sentImpressions.current.delete(id));
|
|
@@ -119,7 +121,7 @@ export function MediaSuiteProvider({ config, children, }) {
|
|
|
119
121
|
}, [flushImpressions]);
|
|
120
122
|
const getClickUrl = useCallback((placementId, screenName) => {
|
|
121
123
|
const pageUrl = encodeURIComponent(screenName || 'app://unknown');
|
|
122
|
-
return `${baseUrl}/sdk/click/${encodeURIComponent(placementId)}?vid=${encodeURIComponent(visitorId)}&device=${device}&page_url=${pageUrl}`;
|
|
124
|
+
return `${baseUrl}/sdk/click/${encodeURIComponent(placementId)}?vid=${encodeURIComponent(visitorId)}&device=${device}&page_url=${pageUrl}&os=${Platform.OS}`;
|
|
123
125
|
}, [baseUrl, visitorId, device]);
|
|
124
126
|
// Render children always — ad components handle the "not ready" state
|
|
125
127
|
return (_jsx(Ctx.Provider, { value: { config, visitorId, device, fetchSpace, trackImpression, getClickUrl }, children: children }));
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@media-suite/reactnative-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MediaSuite Native SDK — React Native SDK for rendering and tracking ads in mobile apps. IAB/MRC viewability standards, impression/click tracking, and ad rotation.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|