@thealteroffice/react-native-adgeist 0.0.16 → 0.0.18

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 (34) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/generated/java/com/adgeist/NativeAdgeistSpec.java +83 -0
  3. package/android/generated/jni/CMakeLists.txt +36 -0
  4. package/android/generated/jni/RNAdgeistSpec-generated.cpp +98 -0
  5. package/android/generated/jni/RNAdgeistSpec.h +31 -0
  6. package/android/generated/jni/react/renderer/components/RNAdgeistSpec/RNAdgeistSpecJSI-generated.cpp +149 -0
  7. package/android/generated/jni/react/renderer/components/RNAdgeistSpec/RNAdgeistSpecJSI.h +170 -0
  8. package/android/src/main/java/com/adgeist/implementation/AdgeistModuleImpl.kt +101 -9
  9. package/android/src/newarch/java/com/AdgeistModule.kt +93 -2
  10. package/android/src/oldarch/java/com/AdgeistModule.kt +101 -4
  11. package/ios/generated/RNAdgeistSpec/RNAdgeistSpec-generated.mm +42 -7
  12. package/ios/generated/RNAdgeistSpec/RNAdgeistSpec.h +57 -10
  13. package/ios/generated/RNAdgeistSpecJSI-generated.cpp +81 -14
  14. package/ios/generated/RNAdgeistSpecJSI.h +54 -9
  15. package/lib/module/NativeAdgeist.js.map +1 -1
  16. package/lib/module/components/BannerAd.js +201 -63
  17. package/lib/module/components/BannerAd.js.map +1 -1
  18. package/lib/module/utilities.js +16 -0
  19. package/lib/module/utilities.js.map +1 -0
  20. package/lib/typescript/src/NativeAdgeist.d.ts +6 -1
  21. package/lib/typescript/src/NativeAdgeist.d.ts.map +1 -1
  22. package/lib/typescript/src/components/BannerAd.d.ts.map +1 -1
  23. package/lib/typescript/src/utilities.d.ts +5 -0
  24. package/lib/typescript/src/utilities.d.ts.map +1 -0
  25. package/package.json +8 -2
  26. package/src/NativeAdgeist.ts +61 -9
  27. package/src/components/BannerAd.tsx +283 -77
  28. package/src/utilities.ts +13 -0
  29. package/plugin/build/android/withRNAdgeistMainApplication.d.ts +0 -3
  30. package/plugin/build/android/withRNAdgeistMainApplication.js +0 -65
  31. package/plugin/build/index.d.ts +0 -3
  32. package/plugin/build/index.js +0 -25
  33. package/plugin/build/ios/withRNAdgeistAppDelegate.d.ts +0 -4
  34. package/plugin/build/ios/withRNAdgeistAppDelegate.js +0 -66
@@ -23,24 +23,76 @@ export interface Spec extends TurboModule {
23
23
  isTestEnvironment: boolean
24
24
  ): Promise<Object>;
25
25
 
26
- sendCreativeAnalytic(
26
+ setUserDetails(user: Object): void;
27
+
28
+ logEvent(event: Object): void;
29
+
30
+ getConsentStatus(): Promise<boolean>;
31
+
32
+ updateConsentStatus(consent: boolean): void;
33
+
34
+ trackImpression(
27
35
  campaignId: string,
28
36
  adSpaceId: string,
29
37
  publisherId: string,
30
- eventType: string,
31
- origin: string,
32
38
  apiKey: string,
33
39
  bidId: string,
34
- isTestEnvironment: boolean
35
- ): Promise<Object>;
40
+ isTestEnvironment: boolean,
41
+ renderTime: number
42
+ ): Promise<string>;
36
43
 
37
- setUserDetails(user: Object): void;
44
+ trackView(
45
+ campaignId: string,
46
+ adSpaceId: string,
47
+ publisherId: string,
48
+ apiKey: string,
49
+ bidId: string,
50
+ isTestEnvironment: boolean,
51
+ viewTime: number,
52
+ visibilityRatio: number,
53
+ scrollDepth: number,
54
+ timeToVisible: number
55
+ ): Promise<string>;
38
56
 
39
- logEvent(event: Object): void;
57
+ trackTotalView(
58
+ campaignId: string,
59
+ adSpaceId: string,
60
+ publisherId: string,
61
+ apiKey: string,
62
+ bidId: string,
63
+ isTestEnvironment: boolean,
64
+ totalViewTime: number,
65
+ visibilityRatio: number
66
+ ): Promise<string>;
40
67
 
41
- getConsentStatus(): Promise<boolean>;
68
+ trackClick(
69
+ campaignId: string,
70
+ adSpaceId: string,
71
+ publisherId: string,
72
+ apiKey: string,
73
+ bidId: string,
74
+ isTestEnvironment: boolean
75
+ ): Promise<string>;
42
76
 
43
- updateConsentStatus(consent: boolean): void;
77
+ trackVideoPlayback(
78
+ campaignId: string,
79
+ adSpaceId: string,
80
+ publisherId: string,
81
+ apiKey: string,
82
+ bidId: string,
83
+ isTestEnvironment: boolean,
84
+ totalPlaybackTime: number
85
+ ): Promise<string>;
86
+
87
+ trackVideoQuartile(
88
+ campaignId: string,
89
+ adSpaceId: string,
90
+ publisherId: string,
91
+ apiKey: string,
92
+ bidId: string,
93
+ isTestEnvironment: boolean,
94
+ quartile: string
95
+ ): Promise<string>;
44
96
  }
45
97
 
46
98
  export default TurboModuleRegistry.getEnforcing<Spec>('Adgeist');
@@ -3,7 +3,7 @@
3
3
  * @description A React Native component for displaying banner and video ads with robust error handling and analytics.
4
4
  */
5
5
 
6
- import React, { useCallback, useEffect, useState } from 'react';
6
+ import React, { useCallback, useEffect, useState, useRef } from 'react';
7
7
  import {
8
8
  Image,
9
9
  Linking,
@@ -12,10 +12,12 @@ import {
12
12
  View,
13
13
  TouchableWithoutFeedback,
14
14
  ActivityIndicator,
15
+ Dimensions,
15
16
  } from 'react-native';
16
17
  import Video from 'react-native-video';
17
18
  import Adgeist from '../NativeAdgeist';
18
19
  import { useAdgeistContext } from './AdgeistProvider';
20
+ import { normalizeUrl } from '../utilities';
19
21
 
20
22
  /**
21
23
  * Interface for ad data structure
@@ -84,22 +86,37 @@ export const BannerAd: React.FC<AdBannerProps> = ({
84
86
  width = 0,
85
87
  height = 0,
86
88
  isResponsive = false,
87
- // responsiveType = 'SQUARE',
88
89
  onAdLoadError,
89
90
  onAdLoadSuccess,
90
91
  }) => {
91
92
  const [adData, setAdData] = useState<AdData | null>(null);
92
- const [isMuted, setIsMuted] = useState<boolean>(false);
93
+ const [isMuted, setIsMuted] = useState<boolean>(true);
93
94
  const [error, setError] = useState<Error | null>(null);
94
95
  const [isLoading, setIsLoading] = useState<boolean>(false);
96
+ const [hasImpression, setHasImpression] = useState<boolean>(false);
97
+ const [hasView, setHasView] = useState<boolean>(false);
98
+ const [isPaused, setIsPaused] = useState<boolean>(true);
99
+ const [isManuallyControlled, setIsManuallyControlled] =
100
+ useState<boolean>(false);
101
+ const renderStartTime = useRef(Date.now());
102
+ const adRef = useRef<View>(null);
103
+ const visibilityStartTime = useRef<number | null>(null);
104
+ const timeToVisible = useRef<number | null>(null);
105
+ const viewTime = useRef<number>(0);
106
+ const lastCheckTime = useRef<number>(Date.now());
107
+ const currentVisibilityRatio = useRef<number>(0);
108
+ const videoRef = useRef<any>(null);
95
109
 
96
110
  const { isInitialized, publisherId, apiKey, domain, isTestEnvironment } =
97
111
  useAdgeistContext();
98
112
 
99
113
  const creativeData = adData?.seatBid?.[0]?.bid?.[0]?.ext as BidExtension;
114
+ const bidId = adData?.id;
115
+ const campaignId = adData?.seatBid?.[0]?.bid?.[0]?.id;
116
+ const adSpaceId = dataAdSlot;
100
117
 
101
118
  /**
102
- * Fetches ad creative and sends impression analytics
119
+ * Fetches ad creative data (without tracking impression here)
103
120
  */
104
121
  const fetchAd = useCallback(async () => {
105
122
  if (!isInitialized) return;
@@ -107,6 +124,8 @@ export const BannerAd: React.FC<AdBannerProps> = ({
107
124
  try {
108
125
  setIsLoading(true);
109
126
  setError(null);
127
+ setHasImpression(false);
128
+ setHasView(false);
110
129
 
111
130
  const response = await Adgeist.fetchCreative(
112
131
  apiKey,
@@ -119,24 +138,12 @@ export const BannerAd: React.FC<AdBannerProps> = ({
119
138
  const creative: { data: AdData } = response as { data: AdData };
120
139
  setAdData(creative.data);
121
140
  onAdLoadSuccess?.(creative.data);
122
-
123
- // if (creative.data.seatBid.length > 0) {
124
- // await Adgeist.sendCreativeAnalytic(
125
- // creative.data.seatBid?.[0]?.bid?.[0]?.id || '',
126
- // dataAdSlot,
127
- // publisherId,
128
- // 'IMPRESSION',
129
- // domain,
130
- // apiKey,
131
- // creative.data.id,
132
- // isTestEnvironment
133
- // );
134
- // }
135
141
  } catch (err) {
136
142
  const error = err instanceof Error ? err : new Error('Ad load failed');
137
143
  setError(error);
144
+ setHasImpression(false);
145
+ setHasView(false);
138
146
  onAdLoadError?.(error);
139
- console.error('Ad load failed:', error);
140
147
  } finally {
141
148
  setIsLoading(false);
142
149
  }
@@ -152,43 +159,204 @@ export const BannerAd: React.FC<AdBannerProps> = ({
152
159
  ]);
153
160
 
154
161
  /**
155
- * Handles ad click and sends click analytics
162
+ * Tracks impression when media (image/video) is fully loaded
156
163
  */
157
- const handleClick = useCallback(async () => {
158
- if (adData && adData.seatBid.length > 0) {
159
- const bidId = adData.seatBid[0]?.bid[0]?.id;
160
- if (!bidId) return;
164
+ const trackImpressionOnMediaLoad = useCallback(async () => {
165
+ if (hasImpression || !bidId || !campaignId) return;
161
166
 
162
- try {
163
- await Adgeist.sendCreativeAnalytic(
164
- bidId,
165
- dataAdSlot,
167
+ try {
168
+ const renderTime = Date.now() - renderStartTime.current;
169
+
170
+ await Adgeist.trackImpression(
171
+ campaignId,
172
+ adSpaceId,
173
+ publisherId,
174
+ apiKey,
175
+ bidId,
176
+ isTestEnvironment,
177
+ renderTime
178
+ );
179
+
180
+ setHasImpression(true);
181
+ } catch (err) {
182
+ console.error('Failed to track impression:', err);
183
+ }
184
+ }, [
185
+ hasImpression,
186
+ bidId,
187
+ campaignId,
188
+ adSpaceId,
189
+ publisherId,
190
+ apiKey,
191
+ isTestEnvironment,
192
+ ]);
193
+
194
+ /**
195
+ * Tracks view event for ads when visible for >=1s (banner) or >=2s (video) and >=50% in viewport
196
+ */
197
+ const trackView = useCallback(async () => {
198
+ if (hasView || !hasImpression || !bidId || !campaignId) return;
199
+
200
+ try {
201
+ const currentViewTime = viewTime.current;
202
+ const currentTimeToVisible = timeToVisible.current || 0;
203
+ const visibility = currentVisibilityRatio.current;
204
+
205
+ if (
206
+ currentViewTime >= (dataSlotType === 'video' ? 2000 : 1000) &&
207
+ visibility >= 0.5 &&
208
+ timeToVisible.current !== null
209
+ ) {
210
+ await Adgeist.trackView(
211
+ campaignId,
212
+ adSpaceId,
166
213
  publisherId,
167
- 'CLICK',
168
- domain,
169
214
  apiKey,
170
- adData.id,
171
- isTestEnvironment
215
+ bidId,
216
+ isTestEnvironment,
217
+ currentViewTime,
218
+ visibility,
219
+ 0,
220
+ currentTimeToVisible
172
221
  );
173
- await Linking.openURL(creativeData.ctaUrl);
174
- } catch (err) {
175
- console.error('Failed to handle ad click:', err);
222
+
223
+ setHasView(true);
224
+ }
225
+ } catch (err) {
226
+ console.error('Failed to track view:', err);
227
+ }
228
+ }, [
229
+ hasView,
230
+ hasImpression,
231
+ bidId,
232
+ campaignId,
233
+ adSpaceId,
234
+ publisherId,
235
+ apiKey,
236
+ isTestEnvironment,
237
+ dataSlotType,
238
+ ]);
239
+
240
+ /**
241
+ * Toggles play/pause state manually
242
+ */
243
+ const togglePlayPause = useCallback(() => {
244
+ setIsManuallyControlled(true);
245
+ setIsPaused((prev) => !prev);
246
+ }, []);
247
+
248
+ /**
249
+ * Calculates visibility ratio, updates view metrics, and controls video playback
250
+ */
251
+ const checkVisibility = useCallback(() => {
252
+ if (!adRef.current || !hasImpression) return;
253
+
254
+ adRef.current.measure((_x, _y, _width, height, _pageX, pageY) => {
255
+ const window = Dimensions.get('window');
256
+ const windowHeight = window.height;
257
+
258
+ const adTop = pageY;
259
+ const adBottom = pageY + height;
260
+ const windowTop = 0;
261
+ const windowBottom = windowHeight;
262
+
263
+ const visibleTop = Math.max(adTop, windowTop);
264
+ const visibleBottom = Math.min(adBottom, windowBottom);
265
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
266
+ const visibilityRatio = height > 0 ? visibleHeight / height : 0;
267
+ currentVisibilityRatio.current = visibilityRatio;
268
+
269
+ const currentTime = Date.now();
270
+ const deltaTime = currentTime - lastCheckTime.current;
271
+ lastCheckTime.current = currentTime;
272
+
273
+ if (visibilityRatio >= 0.5 && timeToVisible.current === null) {
274
+ timeToVisible.current = currentTime - renderStartTime.current;
275
+ }
276
+
277
+ if (visibilityRatio >= 0.5) {
278
+ viewTime.current += deltaTime;
279
+ if (!visibilityStartTime.current) {
280
+ visibilityStartTime.current = currentTime;
281
+ }
282
+ if (dataSlotType === 'video' && isPaused && !isManuallyControlled) {
283
+ setIsPaused(false);
284
+ }
285
+ } else {
286
+ visibilityStartTime.current = null;
287
+ if (dataSlotType === 'video' && !isPaused) {
288
+ setIsPaused(true);
289
+ setIsManuallyControlled(false);
290
+ }
176
291
  }
292
+
293
+ // Only check for view tracking if view event hasn't been tracked yet
294
+ if (
295
+ !hasView &&
296
+ viewTime.current >= (dataSlotType === 'video' ? 2000 : 1000) &&
297
+ visibilityRatio >= 0.5
298
+ ) {
299
+ trackView();
300
+ }
301
+ });
302
+ }, [
303
+ hasImpression,
304
+ trackView,
305
+ dataSlotType,
306
+ isPaused,
307
+ hasView,
308
+ isManuallyControlled,
309
+ ]);
310
+
311
+ /**
312
+ * Handles ad click and sends click analytics
313
+ */
314
+ const handleClick = useCallback(async () => {
315
+ if (!adData || !adData.seatBid.length || !bidId || !campaignId) return;
316
+
317
+ try {
318
+ Adgeist.trackClick(
319
+ campaignId,
320
+ adSpaceId,
321
+ publisherId,
322
+ apiKey,
323
+ bidId,
324
+ isTestEnvironment
325
+ );
326
+
327
+ await Linking.openURL(normalizeUrl(creativeData.ctaUrl));
328
+ } catch (err) {
329
+ console.error('Failed to handle ad click:', err);
177
330
  }
178
331
  }, [
179
332
  adData,
180
- dataAdSlot,
333
+ bidId,
334
+ campaignId,
335
+ adSpaceId,
181
336
  publisherId,
182
- domain,
183
337
  apiKey,
184
338
  isTestEnvironment,
185
339
  creativeData,
186
340
  ]);
187
341
 
188
342
  useEffect(() => {
343
+ renderStartTime.current = Date.now();
344
+ lastCheckTime.current = Date.now();
189
345
  fetchAd();
190
346
  }, [fetchAd]);
191
347
 
348
+ useEffect(() => {
349
+ if (!hasImpression) return;
350
+
351
+ if (dataSlotType === 'banner' && hasView) return;
352
+
353
+ const intervalId = setInterval(checkVisibility, 200);
354
+
355
+ return () => {
356
+ clearInterval(intervalId);
357
+ };
358
+ }, [hasImpression, checkVisibility, dataSlotType, hasView]);
359
+
192
360
  if (isLoading) {
193
361
  return (
194
362
  <View
@@ -211,43 +379,69 @@ export const BannerAd: React.FC<AdBannerProps> = ({
211
379
  style={{ width: '100%', height: '100%' }}
212
380
  >
213
381
  <View
382
+ ref={adRef}
214
383
  style={[
215
384
  styles.adContainer,
216
385
  !isResponsive && { width: width, height: height },
217
386
  ]}
218
387
  >
219
388
  {dataSlotType === 'banner' ? (
220
- <Image
221
- style={styles.creative}
222
- source={{ uri: creativeData.creativeUrl }}
223
- accessibilityLabel="Ad Creative"
224
- resizeMode="contain"
225
- onError={(e) =>
226
- console.error('Image load error:', e.nativeEvent.error)
227
- }
228
- />
229
- ) : (
230
- <View style={styles.videoCreative}>
231
- <Video
389
+ <TouchableWithoutFeedback onPress={handleClick}>
390
+ <Image
391
+ style={styles.creative}
232
392
  source={{ uri: creativeData.creativeUrl }}
393
+ accessibilityLabel="Ad Creative"
233
394
  resizeMode="contain"
234
- style={{ width: '100%', height: '100%' }}
235
- repeat={true}
236
- muted={isMuted}
237
- onError={(e) => console.error('Video load error:', e)}
395
+ onLoad={trackImpressionOnMediaLoad}
396
+ onError={(e) => {
397
+ console.error('Image load error:', e.nativeEvent.error);
398
+ setError(new Error('Failed to load ad image'));
399
+ }}
238
400
  />
239
-
240
- <TouchableWithoutFeedback onPress={() => setIsMuted(!isMuted)}>
241
- <Image
242
- style={styles.soundIcon}
243
- source={{
244
- uri: isMuted
245
- ? 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Muted.png'
246
- : 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Unmuted.png',
401
+ </TouchableWithoutFeedback>
402
+ ) : (
403
+ <View style={styles.videoCreative}>
404
+ <TouchableWithoutFeedback onPress={handleClick}>
405
+ <Video
406
+ ref={videoRef}
407
+ source={{ uri: creativeData.creativeUrl }}
408
+ resizeMode="contain"
409
+ style={{ width: '100%', height: '100%' }}
410
+ repeat={true}
411
+ muted={isMuted}
412
+ paused={isPaused}
413
+ onLoad={trackImpressionOnMediaLoad}
414
+ onError={() => {
415
+ setError(new Error('Failed to load ad video'));
247
416
  }}
248
- accessibilityLabel={isMuted ? 'Unmute video' : 'Mute video'}
249
417
  />
250
418
  </TouchableWithoutFeedback>
419
+ <View style={styles.videoControls}>
420
+ <TouchableWithoutFeedback onPress={togglePlayPause}>
421
+ <Image
422
+ style={styles.playPauseIcon}
423
+ source={{
424
+ uri: isPaused
425
+ ? 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Play.png'
426
+ : 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Pause.png',
427
+ }}
428
+ accessibilityLabel={
429
+ isPaused ? 'Paused video' : 'Playing video'
430
+ }
431
+ />
432
+ </TouchableWithoutFeedback>
433
+ <TouchableWithoutFeedback onPress={() => setIsMuted(!isMuted)}>
434
+ <Image
435
+ style={styles.soundIcon}
436
+ source={{
437
+ uri: isMuted
438
+ ? 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Muted.png'
439
+ : 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Unmuted.png',
440
+ }}
441
+ accessibilityLabel={isMuted ? 'Unmute video' : 'Mute video'}
442
+ />
443
+ </TouchableWithoutFeedback>
444
+ </View>
251
445
  </View>
252
446
  )}
253
447
 
@@ -256,7 +450,6 @@ export const BannerAd: React.FC<AdBannerProps> = ({
256
450
  <Text style={styles.title} numberOfLines={1} ellipsizeMode="tail">
257
451
  {creativeData.creativeTitle}
258
452
  </Text>
259
-
260
453
  <Text
261
454
  style={styles.description}
262
455
  numberOfLines={1}
@@ -264,20 +457,21 @@ export const BannerAd: React.FC<AdBannerProps> = ({
264
457
  >
265
458
  {creativeData.creativeDescription}
266
459
  </Text>
267
-
268
- <Text
269
- style={styles.brandName}
270
- numberOfLines={1}
271
- ellipsizeMode="tail"
272
- >
273
- {creativeData?.creativeBrandName || 'Brand Name'}
274
- </Text>
460
+ {creativeData?.creativeBrandName && (
461
+ <Text
462
+ style={styles.brandName}
463
+ numberOfLines={1}
464
+ ellipsizeMode="tail"
465
+ >
466
+ {creativeData?.creativeBrandName || 'The Brand Name'}
467
+ </Text>
468
+ )}
275
469
  </View>
276
470
  <TouchableWithoutFeedback onPress={handleClick}>
277
471
  <Image
278
472
  style={styles.linkButton}
279
473
  source={{
280
- uri: 'https://d2cfeg6k9cklz9.cloudfront.net/onboarding-icons/Button.png',
474
+ uri: 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Button.png',
281
475
  }}
282
476
  accessibilityLabel="Visit Advertiser Site"
283
477
  />
@@ -310,6 +504,14 @@ const styles = StyleSheet.create({
310
504
  width: '100%',
311
505
  height: '70%',
312
506
  },
507
+ videoControls: {},
508
+ playPauseIcon: {
509
+ position: 'absolute',
510
+ bottom: 8,
511
+ left: 10,
512
+ width: 30,
513
+ height: 30,
514
+ },
313
515
  soundIcon: {
314
516
  position: 'absolute',
315
517
  bottom: 10,
@@ -324,31 +526,35 @@ const styles = StyleSheet.create({
324
526
  flexDirection: 'row',
325
527
  justifyContent: 'space-between',
326
528
  paddingVertical: 10,
327
- paddingHorizontal: 20,
529
+ paddingRight: 10,
530
+ paddingLeft: 20,
328
531
  alignItems: 'center',
329
532
  borderBottomLeftRadius: 5,
330
533
  borderBottomRightRadius: 5,
331
534
  },
332
535
  contentContainer: {
333
- width: '80%',
536
+ flex: 1,
537
+ marginRight: 10,
334
538
  },
335
539
  title: {
336
540
  color: '#1A1A1A',
337
- fontSize: 18,
541
+ fontSize: 16,
338
542
  fontWeight: '600',
339
543
  },
340
544
  description: {
341
545
  color: '#4A4A4A',
342
- fontSize: 16,
546
+ fontSize: 15,
343
547
  marginBottom: 4,
344
548
  },
345
549
  brandName: {
346
550
  color: '#6B7280',
347
551
  fontSize: 14,
348
- textTransform: 'uppercase',
552
+ textTransform: 'capitalize',
553
+ opacity: 0.8,
349
554
  },
350
555
  linkButton: {
351
- width: 40,
556
+ width: 80,
352
557
  height: 40,
558
+ objectFit: 'contain',
353
559
  },
354
560
  });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Normalizes a URL to ensure it has a valid protocol
3
+ */
4
+ export const normalizeUrl = (url: string) => {
5
+ if (!url) return url;
6
+ if (url.startsWith('www.')) {
7
+ return `https://${url}`;
8
+ }
9
+ if (!url.match(/^[a-zA-Z]+:\/\//)) {
10
+ return `https://${url}`;
11
+ }
12
+ return url;
13
+ };
@@ -1,3 +0,0 @@
1
- import { type ConfigPlugin } from '@expo/config-plugins';
2
- export declare const withRNAdgeistMainApplication: ConfigPlugin;
3
- export declare function ktFileUpdater(originalContents: string): string;
@@ -1,65 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withRNAdgeistMainApplication = void 0;
4
- exports.ktFileUpdater = ktFileUpdater;
5
- const config_plugins_1 = require("@expo/config-plugins");
6
- const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
7
- const withRNAdgeistMainApplication = (config) => {
8
- return (0, config_plugins_1.withAppBuildGradle)((0, config_plugins_1.withMainApplication)(config, readMainApplicationFileAndUpdateContents), readBuildGradleFileAndUpdateContents);
9
- };
10
- exports.withRNAdgeistMainApplication = withRNAdgeistMainApplication;
11
- // 1. MainActivity Modifications
12
- async function readMainApplicationFileAndUpdateContents(config) {
13
- const { modResults: mainApplicationFile } = config;
14
- const worker = getCompatibleFileUpdater(mainApplicationFile.language);
15
- mainApplicationFile.contents = worker(mainApplicationFile.contents);
16
- return config;
17
- }
18
- function readBuildGradleFileAndUpdateContents(config) {
19
- const { modResults } = config;
20
- if (!modResults.contents.includes('implementation "ai.adgeist:adgeistkit:')) {
21
- modResults.contents = modResults.contents.replace(/dependencies\s*{/, `dependencies {
22
- implementation "ai.adgeist:adgeistkit:0.0.1" // AdgeistKit Dependency`);
23
- }
24
- return config;
25
- }
26
- function getCompatibleFileUpdater(language) {
27
- switch (language) {
28
- case 'kt':
29
- return ktFileUpdater;
30
- default:
31
- throw new Error(`Cannot add React Native Orientation Director code to MainActivity of language "${language}"`);
32
- }
33
- }
34
- function ktFileUpdater(originalContents) {
35
- // Safer anchor detection
36
- const anchors = [
37
- /super\.onCreate\(/,
38
- /@Override\s+fun onCreate\(/,
39
- /class \w+ : ReactActivity/,
40
- ].find((anchor) => anchor.test(originalContents));
41
- if (!anchors) {
42
- throw new Error('Could not find suitable insertion point in MainActivity');
43
- }
44
- const packageImportCodeBlock = 'import com.adgeist.AdgeistPackage';
45
- const rightBeforeClassDeclaration = /import com.facebook.react.ReactPackage/;
46
- const importMergeResults = (0, generateCode_1.mergeContents)({
47
- tag: '@react-native-adgeist/package-import',
48
- src: originalContents,
49
- newSrc: packageImportCodeBlock,
50
- anchor: rightBeforeClassDeclaration,
51
- offset: 0,
52
- comment: '// React Native Adgeist',
53
- });
54
- const onConfigurationChangedCodeBlock = `packages.add(AdgeistPackage())`;
55
- const rightBeforeOnReturnStatement = /return packages/;
56
- const implementationMergeResults = (0, generateCode_1.mergeContents)({
57
- tag: '@react-native-adgeist/package-initialization',
58
- src: importMergeResults.contents,
59
- newSrc: onConfigurationChangedCodeBlock,
60
- anchor: rightBeforeOnReturnStatement,
61
- offset: 0,
62
- comment: '// Package Initialization',
63
- });
64
- return implementationMergeResults.contents;
65
- }
@@ -1,3 +0,0 @@
1
- import { type ConfigPlugin } from '@expo/config-plugins';
2
- declare const _default: ConfigPlugin<void>;
3
- export default _default;