@media-suite/reactnative-sdk 0.1.0 → 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/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # @mediasuite/react-native-sdk
1
+ # @media-suite/reactnative-sdk
2
2
 
3
3
  React Native SDK for MediaSuite ad platform. Renders ads, tracks impressions/clicks with IAB/MRC viewability compliance.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @mediasuite/react-native-sdk @react-native-async-storage/async-storage
8
+ npm install @media-suite/reactnative-sdk @react-native-async-storage/async-storage
9
9
  ```
10
10
 
11
11
  ## Quick Start
@@ -13,7 +13,7 @@ npm install @mediasuite/react-native-sdk @react-native-async-storage/async-stora
13
13
  ### 1. Wrap your app with the Provider
14
14
 
15
15
  ```tsx
16
- import { MediaSuiteProvider } from '@mediasuite/react-native-sdk';
16
+ import { MediaSuiteProvider } from '@media-suite/reactnative-sdk';
17
17
 
18
18
  export default function App() {
19
19
  return (
@@ -35,7 +35,7 @@ export default function App() {
35
35
  ### 2. Render ads anywhere
36
36
 
37
37
  ```tsx
38
- import { MediaSuiteAd } from '@mediasuite/react-native-sdk';
38
+ import { MediaSuiteAd } from '@media-suite/reactnative-sdk';
39
39
 
40
40
  function HomeScreen() {
41
41
  return (
@@ -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 — fire-and-forget tracking + open destination URL
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
- // Fire tracking call (don't wait for response)
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: [styles.image, loaded && styles.imageLoaded], resizeMode: "contain", onLoad: () => { setLoaded(true); consecutiveFailures.current = 0; }, onError: handleImageError }, ad.placement_id) }) }));
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
- if (cache.current[cacheKey])
71
- return cache.current[cacheKey];
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
@@ -9,6 +9,8 @@ export interface MediaSuiteConfig {
9
9
  export interface Ad {
10
10
  placement_id: string;
11
11
  image_url: string;
12
+ image_width?: number;
13
+ image_height?: number;
12
14
  alt_text?: string;
13
15
  click_url?: string;
14
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@media-suite/reactnative-sdk",
3
- "version": "0.1.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",