@octovise/react-native-sdk 1.0.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +160 -0
  4. package/android/src/main/java/com/octovise/sdk/OctoFirebaseMessagingService.kt +72 -0
  5. package/android/src/main/java/com/octovise/sdk/OctoPushDismissReceiver.kt +54 -0
  6. package/android/src/main/java/com/octovise/sdk/OctoPushModule.kt +26 -0
  7. package/android/src/main/java/com/octovise/sdk/OctoPushNotificationBuilder.kt +142 -0
  8. package/dist/api.d.ts +38 -0
  9. package/dist/api.js +210 -0
  10. package/dist/carouselStart.d.ts +1 -0
  11. package/dist/carouselStart.js +6 -0
  12. package/dist/components/OctoErrorBoundary.d.ts +15 -0
  13. package/dist/components/OctoErrorBoundary.js +28 -0
  14. package/dist/components/OctoImage.d.ts +23 -0
  15. package/dist/components/OctoImage.js +69 -0
  16. package/dist/components/OctoMessage.d.ts +9 -0
  17. package/dist/components/OctoMessage.js +189 -0
  18. package/dist/components/OctoModal.d.ts +13 -0
  19. package/dist/components/OctoModal.js +179 -0
  20. package/dist/components/OctoProvider.d.ts +14 -0
  21. package/dist/components/OctoProvider.js +92 -0
  22. package/dist/components/OctoToast.d.ts +13 -0
  23. package/dist/components/OctoToast.js +392 -0
  24. package/dist/debug.d.ts +2 -0
  25. package/dist/debug.js +15 -0
  26. package/dist/imageFit.d.ts +8 -0
  27. package/dist/imageFit.js +31 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.js +195 -0
  30. package/dist/latency.d.ts +17 -0
  31. package/dist/latency.js +11 -0
  32. package/dist/native.d.ts +1 -0
  33. package/dist/native.js +14 -0
  34. package/dist/navigation/reactNavigation.d.ts +12 -0
  35. package/dist/navigation/reactNavigation.js +24 -0
  36. package/dist/navigation/resolveCTA.d.ts +12 -0
  37. package/dist/navigation/resolveCTA.js +74 -0
  38. package/dist/push.d.ts +3 -0
  39. package/dist/push.js +129 -0
  40. package/dist/safeNavUrl.d.ts +2 -0
  41. package/dist/safeNavUrl.js +18 -0
  42. package/dist/screenStream.d.ts +17 -0
  43. package/dist/screenStream.js +126 -0
  44. package/dist/screenTargeting.d.ts +37 -0
  45. package/dist/screenTargeting.js +154 -0
  46. package/dist/session.d.ts +2 -0
  47. package/dist/session.js +22 -0
  48. package/dist/state.d.ts +8 -0
  49. package/dist/state.js +53 -0
  50. package/dist/telemetry.d.ts +6 -0
  51. package/dist/telemetry.js +65 -0
  52. package/dist/toastLayout.d.ts +2 -0
  53. package/dist/toastLayout.js +11 -0
  54. package/dist/types.d.ts +78 -0
  55. package/dist/types.js +7 -0
  56. package/dist/version.d.ts +1 -0
  57. package/dist/version.js +4 -0
  58. package/ios/OctoviseNotificationExtension/NotificationService.swift +128 -0
  59. package/ios/OctoviseSDK/OctoPushModule.m +6 -0
  60. package/ios/OctoviseSDK/OctoPushModule.swift +27 -0
  61. package/package.json +57 -0
  62. package/react-native.config.js +16 -0
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = OctoToast;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const react_native_safe_area_context_1 = require("react-native-safe-area-context");
40
+ const OctoImage_1 = require("./OctoImage");
41
+ const toastLayout_1 = require("../toastLayout");
42
+ const carouselStart_1 = require("../carouselStart");
43
+ const { width: SCREEN_WIDTH } = react_native_1.Dimensions.get('window');
44
+ const INSETS = react_native_safe_area_context_1.initialWindowMetrics?.insets ?? { top: 0, right: 0, bottom: 0, left: 0 };
45
+ const AUTO_ADVANCE_MS = 4000;
46
+ const SLIDE_DURATION_MS = 1500;
47
+ const SLIDE_EASING = react_native_1.Easing.bezier(0.16, 1, 0.3, 1);
48
+ const SWIPE_THRESHOLD_RATIO = 0.3;
49
+ function getScale(size) {
50
+ if (size === 'small')
51
+ return 0.85;
52
+ if (size === 'large')
53
+ return 1.15;
54
+ return 1;
55
+ }
56
+ function getToastWidth(size) {
57
+ if (size === 'small')
58
+ return Math.round(SCREEN_WIDTH * 0.70);
59
+ if (size === 'large')
60
+ return Math.round(SCREEN_WIDTH * 0.98);
61
+ return Math.round(SCREEN_WIDTH * 0.85);
62
+ }
63
+ function OctoToast({ message, visible, onImpression, onCTAPress, onNoneEngagement, onDismiss, onImageError }) {
64
+ const scale = getScale(message.size);
65
+ const textScale = getScale(message.text_size);
66
+ const colors = message.colors || {};
67
+ const primary = colors.primary || '#6200ee';
68
+ const bg = colors.background || '#ffffff';
69
+ const textColor = colors.text || '#333333';
70
+ const position = message.mobile_position || 'bottom';
71
+ const clickMode = message.click_mode || 'cta';
72
+ const toastWidth = getToastWidth(message.size);
73
+ const imgHeight = Math.round(toastWidth * 9 / 16);
74
+ const hasText = !!(message.title || message.body);
75
+ const layout = (0, toastLayout_1.toastLayout)(!!message.image, hasText, message.image_position);
76
+ const imagePos = message.image_position === 'bottom' ? 'bottom' : 'top';
77
+ const entryAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(position === 'top' ? -200 : 200)).current;
78
+ const opacityAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
79
+ (0, react_1.useEffect)(() => {
80
+ if (visible) {
81
+ onImpression();
82
+ react_native_1.Animated.parallel([
83
+ react_native_1.Animated.spring(entryAnim, {
84
+ toValue: 0,
85
+ useNativeDriver: true,
86
+ damping: 20,
87
+ stiffness: 200,
88
+ }),
89
+ react_native_1.Animated.timing(opacityAnim, {
90
+ toValue: 1,
91
+ duration: 250,
92
+ useNativeDriver: true,
93
+ }),
94
+ ]).start();
95
+ }
96
+ else {
97
+ react_native_1.Animated.parallel([
98
+ react_native_1.Animated.timing(entryAnim, {
99
+ toValue: position === 'top' ? -200 : 200,
100
+ duration: 300,
101
+ useNativeDriver: true,
102
+ }),
103
+ react_native_1.Animated.timing(opacityAnim, {
104
+ toValue: 0,
105
+ duration: 200,
106
+ useNativeDriver: true,
107
+ }),
108
+ ]).start();
109
+ }
110
+ }, [visible]);
111
+ const bottomGap = react_native_1.Platform.OS === 'ios' ? INSETS.bottom + 24 : Math.min(INSETS.bottom + 24, 24);
112
+ const positionStyle = position === 'top'
113
+ ? { top: INSETS.top + 12 }
114
+ : position === 'center'
115
+ ? { top: '40%' }
116
+ : { bottom: bottomGap };
117
+ const wrapperStyle = [
118
+ styles.wrapper,
119
+ positionStyle,
120
+ {
121
+ width: toastWidth,
122
+ opacity: opacityAnim,
123
+ transform: [{ translateY: entryAnim }],
124
+ },
125
+ ];
126
+ const handlePress = () => {
127
+ if (clickMode === 'full')
128
+ onCTAPress();
129
+ else if (clickMode === 'none')
130
+ onNoneEngagement();
131
+ };
132
+ const tappable = clickMode === 'full' || clickMode === 'none';
133
+ if (layout === 'imageOnly' && message.image) {
134
+ const onImagePress = clickMode === 'none'
135
+ ? onNoneEngagement
136
+ : (message.cta ? onCTAPress : undefined);
137
+ return (<react_native_1.Animated.View style={wrapperStyle} pointerEvents="box-none">
138
+ <react_native_1.TouchableOpacity activeOpacity={onImagePress ? 0.95 : 1} disabled={!onImagePress} onPress={onImagePress} style={[styles.card, { borderRadius: 12 * scale, overflow: 'hidden' }]}>
139
+ <CloseButton scale={scale} onPress={() => onDismiss('close_button')}/>
140
+ <OctoImage_1.OctoImage message={message} width={toastWidth} height={imgHeight} corners={{
141
+ topLeft: 12 * scale,
142
+ topRight: 12 * scale,
143
+ bottomLeft: 12 * scale,
144
+ bottomRight: 12 * scale,
145
+ }} bleed={{ top: 1, right: 1, bottom: 1, left: 1 }} onError={onImageError} placeholderColor={textColor}/>
146
+ </react_native_1.TouchableOpacity>
147
+ </react_native_1.Animated.View>);
148
+ }
149
+ if (layout === 'stacked' && message.image) {
150
+ return (<react_native_1.Animated.View style={wrapperStyle} pointerEvents="box-none">
151
+ <react_native_1.TouchableOpacity activeOpacity={tappable ? 0.95 : 1} onPress={tappable ? handlePress : undefined} style={[styles.card, { backgroundColor: bg, borderRadius: 12 * scale, overflow: 'hidden' }]}>
152
+ <CloseButton scale={scale} onPress={() => onDismiss('close_button')}/>
153
+ <react_native_1.View style={{ flexDirection: imagePos === 'bottom' ? 'column-reverse' : 'column' }}>
154
+ <OctoImage_1.OctoImage message={message} width={toastWidth} height={imgHeight} corners={imagePos === 'bottom'
155
+ ? { bottomLeft: 12 * scale, bottomRight: 12 * scale }
156
+ : { topLeft: 12 * scale, topRight: 12 * scale }} bleed={imagePos === 'bottom'
157
+ ? { bottom: 1, left: 1, right: 1 }
158
+ : { top: 1, left: 1, right: 1 }} onError={onImageError} placeholderColor={textColor}/>
159
+ <react_native_1.View style={{ padding: 14 * scale }}>
160
+ <ContentBlock message={message} textColor={textColor} primary={primary} textScale={textScale} scale={scale} onCTAPress={onCTAPress} clickMode={clickMode}/>
161
+ </react_native_1.View>
162
+ </react_native_1.View>
163
+ </react_native_1.TouchableOpacity>
164
+ </react_native_1.Animated.View>);
165
+ }
166
+ if (layout === 'textOnly') {
167
+ return (<react_native_1.Animated.View style={wrapperStyle} pointerEvents="box-none">
168
+ <react_native_1.TouchableOpacity activeOpacity={tappable ? 0.95 : 1} onPress={tappable ? handlePress : undefined} style={[
169
+ styles.card,
170
+ { backgroundColor: bg, borderRadius: 12 * scale, padding: 14 * scale },
171
+ ]}>
172
+ <CloseButton scale={scale} onPress={() => onDismiss('close_button')}/>
173
+ <ContentBlock message={message} textColor={textColor} primary={primary} textScale={textScale} scale={scale} onCTAPress={onCTAPress} clickMode={clickMode}/>
174
+ </react_native_1.TouchableOpacity>
175
+ </react_native_1.Animated.View>);
176
+ }
177
+ return (<react_native_1.Animated.View style={wrapperStyle} pointerEvents="box-none">
178
+ <Carousel message={message} toastWidth={toastWidth} scale={scale} textScale={textScale} primary={primary} bg={bg} textColor={textColor} clickMode={clickMode} onCTAPress={onCTAPress} onNoneEngagement={onNoneEngagement} onDismiss={onDismiss} onImageError={onImageError}/>
179
+ </react_native_1.Animated.View>);
180
+ }
181
+ function Carousel({ message, toastWidth, scale, textScale, primary, bg, textColor, clickMode, onCTAPress, onNoneEngagement, onDismiss, onImageError, }) {
182
+ const initialCell = (0, carouselStart_1.carouselInitialCell)(message.image_position);
183
+ const cellRef = (0, react_1.useRef)(initialCell);
184
+ const [currentDot, setCurrentDot] = (0, react_1.useState)(initialCell === 2 ? 1 : 0);
185
+ const trackX = (0, react_1.useRef)(new react_native_1.Animated.Value(-initialCell * toastWidth)).current;
186
+ const imgHeight = Math.round(toastWidth * 9 / 16);
187
+ const autoTimerRef = (0, react_1.useRef)(null);
188
+ const dotForCell = (c) => (c === 1 || c === 3 ? 0 : 1);
189
+ const goToCell = (c) => {
190
+ cellRef.current = c;
191
+ setCurrentDot(dotForCell(c));
192
+ react_native_1.Animated.timing(trackX, {
193
+ toValue: -c * toastWidth,
194
+ duration: SLIDE_DURATION_MS,
195
+ easing: SLIDE_EASING,
196
+ useNativeDriver: false,
197
+ }).start(({ finished }) => {
198
+ if (!finished)
199
+ return;
200
+ const real = c === 0 ? 2 : c === 3 ? 1 : c;
201
+ if (real !== c) {
202
+ cellRef.current = real;
203
+ trackX.setValue(-real * toastWidth);
204
+ }
205
+ });
206
+ };
207
+ const next = () => goToCell(cellRef.current + 1);
208
+ const prev = () => goToCell(cellRef.current - 1);
209
+ const startAutoTimer = () => {
210
+ if (autoTimerRef.current)
211
+ clearInterval(autoTimerRef.current);
212
+ autoTimerRef.current = setInterval(next, AUTO_ADVANCE_MS);
213
+ };
214
+ (0, react_1.useEffect)(() => {
215
+ startAutoTimer();
216
+ return () => {
217
+ if (autoTimerRef.current)
218
+ clearInterval(autoTimerRef.current);
219
+ };
220
+ }, []);
221
+ const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
222
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 5 && Math.abs(g.dx) > Math.abs(g.dy),
223
+ onPanResponderGrant: () => {
224
+ if (autoTimerRef.current)
225
+ clearInterval(autoTimerRef.current);
226
+ trackX.stopAnimation();
227
+ const c = cellRef.current;
228
+ const real = c === 0 ? 2 : c === 3 ? 1 : c;
229
+ if (real !== c) {
230
+ cellRef.current = real;
231
+ trackX.setValue(-real * toastWidth);
232
+ }
233
+ },
234
+ onPanResponderMove: (_, g) => {
235
+ const base = -cellRef.current * toastWidth;
236
+ trackX.setValue(base + g.dx);
237
+ },
238
+ onPanResponderRelease: (_, g) => {
239
+ const threshold = toastWidth * SWIPE_THRESHOLD_RATIO;
240
+ if (g.dx < -threshold)
241
+ next();
242
+ else if (g.dx > threshold)
243
+ prev();
244
+ else
245
+ goToCell(cellRef.current);
246
+ startAutoTimer();
247
+ },
248
+ })).current;
249
+ const onDotPress = (idx) => {
250
+ goToCell(idx === 0 ? 1 : 2);
251
+ startAutoTimer();
252
+ };
253
+ const handlePress = () => {
254
+ if (clickMode === 'full')
255
+ onCTAPress();
256
+ else if (clickMode === 'none')
257
+ onNoneEngagement();
258
+ };
259
+ const tappable = clickMode === 'full' || clickMode === 'none';
260
+ const onImagePress = clickMode === 'none'
261
+ ? onNoneEngagement
262
+ : (message.cta ? onCTAPress : undefined);
263
+ const imageCell = (key) => (<react_native_1.TouchableOpacity key={key} activeOpacity={onImagePress ? 0.95 : 1} disabled={!onImagePress} onPress={onImagePress} style={{ width: toastWidth }}>
264
+ <OctoImage_1.OctoImage message={message} width={toastWidth} height={imgHeight} onError={onImageError} placeholderColor={textColor}/>
265
+ </react_native_1.TouchableOpacity>);
266
+ const contentCell = (key) => (<react_native_1.TouchableOpacity key={key} activeOpacity={tappable ? 0.95 : 1} onPress={tappable ? handlePress : undefined} style={{
267
+ width: toastWidth,
268
+ paddingVertical: 14 * scale,
269
+ paddingHorizontal: 21 * scale,
270
+ flexDirection: 'column',
271
+ justifyContent: 'space-between',
272
+ }}>
273
+ <ContentBlock message={message} textColor={textColor} primary={primary} textScale={textScale} scale={scale} onCTAPress={onCTAPress} clickMode={clickMode}/>
274
+ </react_native_1.TouchableOpacity>);
275
+ return (<react_native_1.View style={[styles.card, { backgroundColor: bg, borderRadius: 12 * scale, overflow: 'hidden' }]}>
276
+ <CloseButton scale={scale} onPress={() => onDismiss('close_button')}/>
277
+
278
+ <react_native_1.View style={[styles.dots, { top: 10 * scale, left: 12 * scale }]}>
279
+ {[0, 1].map((i) => (<react_native_1.TouchableOpacity key={i} onPress={() => onDotPress(i)} hitSlop={{ top: 10, right: 6, bottom: 10, left: 6 }}>
280
+ <react_native_1.View style={[
281
+ styles.dot,
282
+ { backgroundColor: textColor, opacity: currentDot === i ? 0.4 : 0.15 },
283
+ ]}/>
284
+ </react_native_1.TouchableOpacity>))}
285
+ </react_native_1.View>
286
+
287
+
288
+ <react_native_1.View {...panResponder.panHandlers} style={{ overflow: 'hidden' }}>
289
+ <react_native_1.Animated.View style={{
290
+ flexDirection: 'row',
291
+ width: toastWidth * 4,
292
+ transform: [{ translateX: trackX }],
293
+ }}>
294
+ {contentCell('c0')}
295
+ {imageCell('i1')}
296
+ {contentCell('c2')}
297
+ {imageCell('i3')}
298
+ </react_native_1.Animated.View>
299
+ </react_native_1.View>
300
+ </react_native_1.View>);
301
+ }
302
+ function CloseButton({ scale, onPress }) {
303
+ return (<react_native_1.TouchableOpacity style={[
304
+ styles.closeBtn,
305
+ { width: 24 * scale, height: 24 * scale, borderRadius: 12 * scale },
306
+ ]} onPress={onPress} activeOpacity={0.7}>
307
+ <react_native_1.Text style={styles.closeBtnText}>×</react_native_1.Text>
308
+ </react_native_1.TouchableOpacity>);
309
+ }
310
+ function ContentBlock({ message, textColor, primary, textScale, scale, onCTAPress, clickMode, }) {
311
+ const ctaAlign = message.cta?.align || 'left';
312
+ const alignItems = ctaAlign === 'center' ? 'center' : ctaAlign === 'right' ? 'flex-end' : 'flex-start';
313
+ const textAlign = message.text_align || 'left';
314
+ return (<>
315
+ <react_native_1.View style={{ overflow: 'hidden' }}>
316
+ {message.title ? (<react_native_1.Text style={[styles.title, { fontSize: 15 * textScale, color: textColor, textAlign }]} numberOfLines={2}>
317
+ {message.title}
318
+ </react_native_1.Text>) : null}
319
+ {message.body ? (<react_native_1.Text style={[styles.body, { fontSize: 13 * textScale, color: textColor, textAlign }]} numberOfLines={3}>
320
+ {message.body}
321
+ </react_native_1.Text>) : null}
322
+ </react_native_1.View>
323
+ {clickMode === 'cta' && message.cta?.label ? (<react_native_1.View style={{ marginTop: 10 * scale, alignItems }}>
324
+ <react_native_1.TouchableOpacity style={{
325
+ backgroundColor: primary,
326
+ borderRadius: 6 * scale,
327
+ paddingVertical: 6 * scale,
328
+ paddingHorizontal: 16 * scale,
329
+ }} onPress={onCTAPress} activeOpacity={0.9}>
330
+ <react_native_1.Text style={[styles.ctaText, { fontSize: 13 * textScale }]}>{message.cta.label}</react_native_1.Text>
331
+ </react_native_1.TouchableOpacity>
332
+ </react_native_1.View>) : null}
333
+ </>);
334
+ }
335
+ const styles = react_native_1.StyleSheet.create({
336
+ wrapper: {
337
+ position: 'absolute',
338
+ alignSelf: 'center',
339
+ zIndex: 999999,
340
+ },
341
+ card: {
342
+ elevation: 8,
343
+ shadowColor: '#000',
344
+ shadowOffset: { width: 0, height: 8 },
345
+ shadowOpacity: 0.15,
346
+ shadowRadius: 30,
347
+ position: 'relative',
348
+ },
349
+ closeBtn: {
350
+ position: 'absolute',
351
+ top: 8,
352
+ right: 8,
353
+ zIndex: 10,
354
+ backgroundColor: 'rgba(0,0,0,0.5)',
355
+ alignItems: 'center',
356
+ justifyContent: 'center',
357
+ shadowColor: '#000',
358
+ shadowOffset: { width: 0, height: 1 },
359
+ shadowOpacity: 0.25,
360
+ shadowRadius: 3,
361
+ elevation: 4,
362
+ },
363
+ closeBtnText: {
364
+ color: '#fff',
365
+ fontSize: 14,
366
+ lineHeight: 16,
367
+ },
368
+ dots: {
369
+ position: 'absolute',
370
+ flexDirection: 'row',
371
+ zIndex: 11,
372
+ gap: 5,
373
+ },
374
+ dot: {
375
+ width: 6,
376
+ height: 6,
377
+ borderRadius: 3,
378
+ },
379
+ title: {
380
+ fontWeight: '700',
381
+ marginBottom: 4,
382
+ lineHeight: 20,
383
+ },
384
+ body: {
385
+ opacity: 0.7,
386
+ lineHeight: 18,
387
+ },
388
+ ctaText: {
389
+ color: '#fff',
390
+ fontWeight: '600',
391
+ },
392
+ });
@@ -0,0 +1,2 @@
1
+ export declare function isDebug(): boolean;
2
+ export declare function debugLog(label: string, payload?: unknown): void;
package/dist/debug.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDebug = isDebug;
4
+ exports.debugLog = debugLog;
5
+ function isDebug() {
6
+ return globalThis.__OCTO_DEBUG__ === true;
7
+ }
8
+ function debugLog(label, payload) {
9
+ if (!isDebug())
10
+ return;
11
+ if (payload === undefined)
12
+ console.log(label);
13
+ else
14
+ console.log(label, payload);
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { ImageStyle, ViewStyle } from 'react-native';
2
+ import type { InAppMessage } from './types';
3
+ export declare const LOGO_WIDTH = 120;
4
+ export interface ImageRenderStyles {
5
+ container: ViewStyle;
6
+ image: ImageStyle;
7
+ }
8
+ export declare function imageRenderStyles(message: Pick<InAppMessage, 'image_fit' | 'image_bg'>, width: number, height: number): ImageRenderStyles;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOGO_WIDTH = void 0;
4
+ exports.imageRenderStyles = imageRenderStyles;
5
+ const HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
6
+ exports.LOGO_WIDTH = 120;
7
+ function imageRenderStyles(message, width, height) {
8
+ if (message.image_fit === 'contain') {
9
+ const raw = message.image_bg?.trim();
10
+ const backgroundColor = raw && HEX_RE.test(raw) ? raw : undefined;
11
+ return {
12
+ container: {
13
+ width,
14
+ height,
15
+ backgroundColor,
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ overflow: 'hidden',
19
+ },
20
+ image: {
21
+ width: Math.min(exports.LOGO_WIDTH, width),
22
+ height: Math.min(exports.LOGO_WIDTH, height),
23
+ resizeMode: 'contain',
24
+ },
25
+ };
26
+ }
27
+ return {
28
+ container: { width, height, overflow: 'hidden' },
29
+ image: { width, height, resizeMode: 'cover' },
30
+ };
31
+ }
@@ -0,0 +1,11 @@
1
+ import type { OctoRNConfig, InAppMessage, EntitySignal } from './types';
2
+ export { default as OctoProvider } from './components/OctoProvider';
3
+ export { makeReactNavigationAdapter } from './navigation/reactNavigation';
4
+ export type { OctoRNConfig, InAppMessage, NavAdapter, EntitySignal } from './types';
5
+ export declare function init(config: OctoRNConfig): void;
6
+ export declare function close(): void;
7
+ export declare function setEntity(entity: EntitySignal | null): void;
8
+ export declare function forceCheck(opts?: {
9
+ ignoreDedup?: boolean;
10
+ }): Promise<InAppMessage | null>;
11
+ export declare function triggerTestInApp(): Promise<boolean>;
package/dist/index.js ADDED
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.makeReactNavigationAdapter = exports.OctoProvider = void 0;
40
+ exports.init = init;
41
+ exports.close = close;
42
+ exports.setEntity = setEntity;
43
+ exports.forceCheck = forceCheck;
44
+ exports.triggerTestInApp = triggerTestInApp;
45
+ const api = __importStar(require("./api"));
46
+ const state = __importStar(require("./state"));
47
+ const telemetry_1 = require("./telemetry");
48
+ const screenTargeting = __importStar(require("./screenTargeting"));
49
+ const screenStream = __importStar(require("./screenStream"));
50
+ const session = __importStar(require("./session"));
51
+ const OctoProvider_1 = require("./components/OctoProvider");
52
+ const resolveCTA_1 = require("./navigation/resolveCTA");
53
+ var OctoProvider_2 = require("./components/OctoProvider");
54
+ Object.defineProperty(exports, "OctoProvider", { enumerable: true, get: function () { return __importDefault(OctoProvider_2).default; } });
55
+ var reactNavigation_1 = require("./navigation/reactNavigation");
56
+ Object.defineProperty(exports, "makeReactNavigationAdapter", { enumerable: true, get: function () { return reactNavigation_1.makeReactNavigationAdapter; } });
57
+ let currentConfig = null;
58
+ let screenUnsub = null;
59
+ const handledTargetSends = new Set();
60
+ function ctaHandler() {
61
+ return (url) => (0, resolveCTA_1.handleCTA)(url, {
62
+ nav: currentConfig?.nav,
63
+ onCTA: currentConfig?.onCTA,
64
+ resolveRoute: (token) => screenTargeting.resolveScreenForRoute(token),
65
+ });
66
+ }
67
+ function showMessage(msg, timing) {
68
+ if (state.isDuplicate(msg.id))
69
+ return;
70
+ state.setCurrentMessage(msg.id);
71
+ (0, OctoProvider_1.getGlobalShow)()(timing ? { ...msg, __timing: timing } : msg, ctaHandler());
72
+ }
73
+ function doCheck() {
74
+ if (!currentConfig)
75
+ return;
76
+ const t0 = Date.now();
77
+ api.check().then((msg) => {
78
+ const tCheck = Date.now();
79
+ if (msg)
80
+ showMessage(msg, { t0, tCheck });
81
+ });
82
+ }
83
+ function evaluateTargeting(screen) {
84
+ if (!currentConfig)
85
+ return;
86
+ const rule = screenTargeting.matchRuleForScreen(screen);
87
+ if (!rule)
88
+ return;
89
+ if (handledTargetSends.has(rule.send_id))
90
+ return;
91
+ if (state.isDuplicate(rule.id))
92
+ return;
93
+ handledTargetSends.add(rule.send_id);
94
+ const t0 = Date.now();
95
+ api.check(rule.send_id).then((msg) => {
96
+ const tCheck = Date.now();
97
+ if (msg)
98
+ showMessage(msg, { t0, tCheck });
99
+ });
100
+ }
101
+ function handleScreen(screen) {
102
+ if (!currentConfig)
103
+ return;
104
+ evaluateTargeting(screen);
105
+ screenStream.record(screen);
106
+ if (state.canInit()) {
107
+ state.markInit();
108
+ doCheck();
109
+ }
110
+ }
111
+ function handleForeground() {
112
+ if (!currentConfig)
113
+ return;
114
+ doCheck();
115
+ screenTargeting.sync().then((ok) => {
116
+ if (!ok)
117
+ return;
118
+ const current = currentConfig?.nav?.getCurrentScreen?.();
119
+ if (current)
120
+ evaluateTargeting(current);
121
+ });
122
+ }
123
+ function init(config) {
124
+ api.configure(config.apiKey, config.userId, config.userHash, config.baseUrl, config.appVersion);
125
+ api.setEventObserver(config.onEvent);
126
+ (0, telemetry_1.configureTelemetry)(config.userId);
127
+ currentConfig = config;
128
+ try {
129
+ initInternal(config);
130
+ }
131
+ catch (err) {
132
+ (0, telemetry_1.reportError)('init_failed', { message: err instanceof Error ? err.message : String(err) });
133
+ }
134
+ }
135
+ function initInternal(config) {
136
+ if (!state.canInit()) {
137
+ console.warn('[OctoSDK] init() ignored — cooldown active');
138
+ return;
139
+ }
140
+ state.markInit();
141
+ doCheck();
142
+ state.startForegroundListener(handleForeground);
143
+ const nav = config.nav;
144
+ if (nav?.subscribeScreenChange) {
145
+ screenStream.start();
146
+ screenUnsub = nav.subscribeScreenChange((screen) => handleScreen(screen));
147
+ }
148
+ screenTargeting.sync().then((ok) => {
149
+ if (!ok)
150
+ return;
151
+ const current = config.nav?.getCurrentScreen?.();
152
+ if (current)
153
+ handleScreen(current);
154
+ });
155
+ }
156
+ function close() {
157
+ state.reset();
158
+ api.setEventObserver(undefined);
159
+ screenUnsub?.();
160
+ screenUnsub = null;
161
+ screenTargeting.reset();
162
+ handledTargetSends.clear();
163
+ screenStream.stop();
164
+ session.reset();
165
+ currentConfig = null;
166
+ }
167
+ function setEntity(entity) {
168
+ if (!currentConfig)
169
+ return;
170
+ screenStream.setEntity(entity);
171
+ }
172
+ async function forceCheck(opts) {
173
+ if (!currentConfig)
174
+ return null;
175
+ const t0 = Date.now();
176
+ const msg = await api.check();
177
+ const tCheck = Date.now();
178
+ if (!msg)
179
+ return null;
180
+ if (!opts?.ignoreDedup && state.isDuplicate(msg.id))
181
+ return null;
182
+ state.setCurrentMessage(msg.id);
183
+ (0, OctoProvider_1.getGlobalShow)()({ ...msg, __timing: { t0, tCheck } }, ctaHandler());
184
+ return msg;
185
+ }
186
+ async function triggerTestInApp() {
187
+ if (!currentConfig)
188
+ return false;
189
+ const queued = await api.testInApp();
190
+ if (!queued)
191
+ return false;
192
+ state.setCurrentMessage(undefined);
193
+ const msg = await forceCheck();
194
+ return msg !== null;
195
+ }
@@ -0,0 +1,17 @@
1
+ export interface LatencyTiming {
2
+ t0: number;
3
+ tCheck: number;
4
+ }
5
+ export interface LatencyMarks {
6
+ showStart: number;
7
+ shown: number;
8
+ impression: number;
9
+ hasImage: boolean;
10
+ }
11
+ export interface LatencyBreakdown {
12
+ total_ms: number;
13
+ network_ms: number;
14
+ image_ms: number;
15
+ render_ms: number;
16
+ }
17
+ export declare function buildLatencyBreakdown(t: LatencyTiming, m: LatencyMarks): LatencyBreakdown;