@osano/osano-cmp-react-native 0.18.6 → 0.19.1

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 (68) hide show
  1. package/lib/commonjs/ConsentManager.js +32 -1
  2. package/lib/commonjs/ConsentManager.js.map +1 -1
  3. package/lib/commonjs/components/PoweredByOsano.js +72 -0
  4. package/lib/commonjs/components/PoweredByOsano.js.map +1 -0
  5. package/lib/commonjs/iab/tcf/iab-storage-utils.js +53 -49
  6. package/lib/commonjs/iab/tcf/iab-storage-utils.js.map +1 -1
  7. package/lib/commonjs/iab/tcf/middleware.js +20 -23
  8. package/lib/commonjs/iab/tcf/middleware.js.map +1 -1
  9. package/lib/commonjs/iab/tcf/reducer.js +0 -22
  10. package/lib/commonjs/iab/tcf/reducer.js.map +1 -1
  11. package/lib/commonjs/index.js +16 -2
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/reducer.js +5 -1
  14. package/lib/commonjs/reducer.js.map +1 -1
  15. package/lib/commonjs/services/consent.js +178 -77
  16. package/lib/commonjs/services/consent.js.map +1 -1
  17. package/lib/commonjs/views/Dialog/index.js +15 -2
  18. package/lib/commonjs/views/Dialog/index.js.map +1 -1
  19. package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js +160 -12
  20. package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
  21. package/lib/module/ConsentManager.js +32 -1
  22. package/lib/module/ConsentManager.js.map +1 -1
  23. package/lib/module/components/PoweredByOsano.js +68 -0
  24. package/lib/module/components/PoweredByOsano.js.map +1 -0
  25. package/lib/module/iab/tcf/iab-storage-utils.js +53 -49
  26. package/lib/module/iab/tcf/iab-storage-utils.js.map +1 -1
  27. package/lib/module/iab/tcf/middleware.js +22 -25
  28. package/lib/module/iab/tcf/middleware.js.map +1 -1
  29. package/lib/module/iab/tcf/reducer.js +1 -23
  30. package/lib/module/iab/tcf/reducer.js.map +1 -1
  31. package/lib/module/index.js +17 -3
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/reducer.js +5 -1
  34. package/lib/module/reducer.js.map +1 -1
  35. package/lib/module/services/consent.js +174 -75
  36. package/lib/module/services/consent.js.map +1 -1
  37. package/lib/module/views/Dialog/index.js +16 -3
  38. package/lib/module/views/Dialog/index.js.map +1 -1
  39. package/lib/module/views/Drawer/Drawer.ConsentCategories.js +162 -14
  40. package/lib/module/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
  41. package/lib/typescript/src/ConsentManager.d.ts +13 -1
  42. package/lib/typescript/src/ConsentManager.d.ts.map +1 -1
  43. package/lib/typescript/src/components/PoweredByOsano.d.ts +5 -0
  44. package/lib/typescript/src/components/PoweredByOsano.d.ts.map +1 -0
  45. package/lib/typescript/src/iab/tcf/iab-storage-utils.d.ts +7 -7
  46. package/lib/typescript/src/iab/tcf/iab-storage-utils.d.ts.map +1 -1
  47. package/lib/typescript/src/iab/tcf/middleware.d.ts.map +1 -1
  48. package/lib/typescript/src/iab/tcf/reducer.d.ts +1 -0
  49. package/lib/typescript/src/iab/tcf/reducer.d.ts.map +1 -1
  50. package/lib/typescript/src/index.d.ts.map +1 -1
  51. package/lib/typescript/src/reducer.d.ts.map +1 -1
  52. package/lib/typescript/src/services/consent.d.ts +28 -0
  53. package/lib/typescript/src/services/consent.d.ts.map +1 -1
  54. package/lib/typescript/src/views/Dialog/index.d.ts.map +1 -1
  55. package/lib/typescript/src/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
  56. package/package.json +5 -4
  57. package/src/ConsentManager.ts +33 -2
  58. package/src/components/PoweredByOsano.tsx +79 -0
  59. package/src/iab/tcf/iab-storage-utils.ts +101 -56
  60. package/src/iab/tcf/middleware.ts +36 -32
  61. package/src/iab/tcf/reducer.ts +2 -29
  62. package/src/index.tsx +15 -1
  63. package/src/reducer.ts +4 -0
  64. package/src/services/consent.ts +220 -86
  65. package/src/types/consent-config.d.ts +11 -1
  66. package/src/types/store.d.ts +5 -0
  67. package/src/views/Dialog/index.tsx +19 -1
  68. package/src/views/Drawer/Drawer.ConsentCategories.tsx +168 -11
@@ -4,13 +4,16 @@ import React, {
4
4
  useEffect,
5
5
  useMemo,
6
6
  useRef,
7
+ useState,
7
8
  } from 'react';
8
9
  import {
9
10
  ActivityIndicator,
10
11
  FlatList,
11
12
  type LayoutChangeEvent,
13
+ Linking,
12
14
  type ListRenderItemInfo,
13
15
  Platform,
16
+ Pressable,
14
17
  type StyleProp,
15
18
  StyleSheet,
16
19
  Text,
@@ -37,6 +40,7 @@ import { Divider } from '../../components/Divider';
37
40
  import { ExpansionPanel } from '../../components/ExpansionPanel';
38
41
  import { Link } from '../../components/Link';
39
42
  import Locale from '../../components/Locale';
43
+ import PoweredByOsano from '../../components/PoweredByOsano';
40
44
  import StoragePolicyLink from '../../components/StoragePolicyLink';
41
45
  import Toggle from '../../components/Toggle';
42
46
  import { ConsentCategory, type ConsentCategoryType } from '../../constants';
@@ -56,7 +60,7 @@ import type { Disclosure } from '../../types/store';
56
60
  import DrawerHeader from './Drawer.Header';
57
61
 
58
62
  type ConsentCategoryItem = {
59
- type: 'info' | 'category' | 'specialFeature';
63
+ type: 'info' | 'category' | 'specialFeature' | 'uuid';
60
64
  id: string;
61
65
  headerHeight?: number;
62
66
  };
@@ -512,21 +516,144 @@ const CategoryListHeader: React.FC<{ style: StyleProp<ViewStyle> }> = ({
512
516
  );
513
517
  };
514
518
 
519
+ const OSANO_URL =
520
+ 'https://www.osano.com/?utm_campaign=cmp&utm_source=cmp-dialog&utm_medium=drawer';
521
+
515
522
  const CategoryListFooter: React.FC = () => {
516
- const { cmp, dispatch } = useContext(OsanoPrivateContext);
523
+ const { cmp, dispatch, translate } = useContext(OsanoPrivateContext);
517
524
 
518
525
  const handleSave = useCallback(() => {
519
526
  cmp && dispatch(saveConsent(cmp));
520
527
  dispatch(close());
521
- }, [cmp]);
528
+ }, [cmp, dispatch]);
522
529
 
530
+ // Always shown here; showBannerLogo only gates the logo on the consent banner.
523
531
  return (
524
532
  <View style={[styles.listItem, styles.listFooter]}>
525
533
  <SaveButton onPress={handleSave} />
526
- <View style={styles.poweredBy}>
527
- <Link href="https://www.osano.com/?utm_campaign=cmp&utm_source=cmp-dialog&utm_medium=drawer">
528
- <Locale path="messaging.poweredBy" />
529
- </Link>
534
+ <Pressable
535
+ accessibilityRole="link"
536
+ accessibilityLabel={`${translate('messaging.poweredBy')}`}
537
+ onPress={() => Linking.openURL(OSANO_URL)}
538
+ style={styles.poweredBy}
539
+ >
540
+ <PoweredByOsano testID="drawer-banner-logo" />
541
+ </Pressable>
542
+ </View>
543
+ );
544
+ };
545
+
546
+ // Falls back to translucent black when hex is missing or malformed.
547
+ const hexToRgba = (hex: string | undefined, alpha: number): string => {
548
+ if (!hex || !hex.startsWith('#')) {
549
+ return `rgba(0, 0, 0, ${alpha})`;
550
+ }
551
+ let h = hex.slice(1);
552
+ if (h.length === 3) {
553
+ h = h
554
+ .split('')
555
+ .map((c) => c + c)
556
+ .join('');
557
+ }
558
+ // Reject any non-hex input up front so parseInt can't yield NaN channels.
559
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) {
560
+ return `rgba(0, 0, 0, ${alpha})`;
561
+ }
562
+ const r = parseInt(h.slice(0, 2), 16);
563
+ const g = parseInt(h.slice(2, 4), 16);
564
+ const b = parseInt(h.slice(4, 6), 16);
565
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
566
+ };
567
+
568
+ // Resolve the clipboard native module lazily. The require is guarded so the
569
+ // drawer still renders where the module is not linked (e.g. Expo Go), instead
570
+ // of throwing at import time; the Copy button is hidden when it is unavailable.
571
+ const getClipboard = (): { setString: (value: string) => void } | null => {
572
+ try {
573
+ // Guarded require (not a static import) so an unlinked module degrades
574
+ // instead of throwing when this file loads.
575
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
576
+ return require('@react-native-clipboard/clipboard').default;
577
+ } catch {
578
+ return null;
579
+ }
580
+ };
581
+
582
+ // Shows the visitor's consent UUID with a Copy button that writes it to the
583
+ // clipboard. Renders nothing when no UUID is available.
584
+ const ConsentUuidRow: React.FC<{ style: StyleProp<ViewStyle> }> = ({
585
+ style,
586
+ }) => {
587
+ const { cmp, config, translate } = useContext(OsanoPrivateContext);
588
+ const uuid = cmp?.deviceId ?? '';
589
+ const palette = config?.palette;
590
+ const foregroundColor = palette?.infoDialogForegroundColor;
591
+ const buttonBackgroundColor = palette?.buttonBackgroundColor;
592
+ const buttonForegroundColor = palette?.buttonForegroundColor;
593
+ const clipboard = useMemo(() => getClipboard(), []);
594
+ const [copied, setCopied] = useState(false);
595
+ const copiedTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
596
+
597
+ useEffect(
598
+ () => () => {
599
+ if (copiedTimeout.current) {
600
+ clearTimeout(copiedTimeout.current);
601
+ }
602
+ },
603
+ [],
604
+ );
605
+
606
+ const handleCopy = useCallback(() => {
607
+ if (!uuid || !clipboard) {
608
+ return;
609
+ }
610
+ clipboard.setString(uuid);
611
+ setCopied(true);
612
+ if (copiedTimeout.current) {
613
+ clearTimeout(copiedTimeout.current);
614
+ }
615
+ // Revert the label back to "Copy" so the button is reusable.
616
+ copiedTimeout.current = setTimeout(() => setCopied(false), 2000);
617
+ }, [uuid, clipboard]);
618
+
619
+ if (!uuid) {
620
+ return null;
621
+ }
622
+ const copyLabel = `${translate('drawer.copy')}`;
623
+ const copiedLabel = `${translate('drawer.copied')}`;
624
+ return (
625
+ <View style={[styles.listItem, styles.uuidRow, style]}>
626
+ <View
627
+ style={[
628
+ styles.uuidBox,
629
+ { backgroundColor: hexToRgba(foregroundColor, 0.05) },
630
+ ]}
631
+ >
632
+ <Text
633
+ selectable
634
+ numberOfLines={1}
635
+ ellipsizeMode="tail"
636
+ style={[styles.uuidText, { color: foregroundColor }]}
637
+ testID="drawer-consent-uuid"
638
+ >
639
+ {`UUID: ${uuid}`}
640
+ </Text>
641
+ {clipboard ? (
642
+ <Pressable
643
+ accessibilityRole="button"
644
+ accessibilityLabel={copyLabel}
645
+ onPress={handleCopy}
646
+ style={[
647
+ styles.copyButton,
648
+ { backgroundColor: buttonBackgroundColor },
649
+ ]}
650
+ testID="drawer-consent-uuid-copy"
651
+ >
652
+ <Text style={[styles.copyText, { color: buttonForegroundColor }]}>
653
+ {copied ? copiedLabel : copyLabel}
654
+ </Text>
655
+ </Pressable>
656
+ ) : null}
530
657
  </View>
531
658
  </View>
532
659
  );
@@ -551,6 +678,8 @@ const ListItemRenderer = ({
551
678
  );
552
679
  case 'specialFeature':
553
680
  return <SpecialFeatureListItem style={style} specialFeatureId={id} />;
681
+ case 'uuid':
682
+ return <ConsentUuidRow style={style} />;
554
683
  default:
555
684
  return null;
556
685
  }
@@ -627,6 +756,9 @@ export const ConsentCategoriesDrawer: React.FC = () => {
627
756
  }),
628
757
  );
629
758
  }
759
+ if (config?.showConsentUuid) {
760
+ newCategories.push({ type: 'uuid', id: 'uuid', headerHeight });
761
+ }
630
762
  newCategories.unshift({
631
763
  type: 'info',
632
764
  id: 'info',
@@ -675,9 +807,8 @@ const styles = StyleSheet.create({
675
807
  poweredBy: {
676
808
  alignItems: 'center',
677
809
  justifyContent: 'center',
678
- flexDirection: 'column',
679
- fontWeight: '700',
680
- marginVertical: 16,
810
+ marginTop: 24,
811
+ marginBottom: 16,
681
812
  },
682
813
  switch: {
683
814
  marginBottom: 8,
@@ -698,12 +829,38 @@ const styles = StyleSheet.create({
698
829
  paddingVertical: 32,
699
830
  },
700
831
  listFooter: {
701
- paddingVertical: 32,
832
+ paddingTop: 16,
833
+ paddingBottom: 8,
702
834
  },
703
835
  listItem: {
704
836
  fontSize: 12,
705
837
  paddingHorizontal: 24,
706
838
  },
839
+ uuidRow: {
840
+ paddingVertical: 8,
841
+ },
842
+ uuidBox: {
843
+ borderRadius: 8,
844
+ paddingHorizontal: 12,
845
+ paddingVertical: 8,
846
+ flexDirection: 'row',
847
+ alignItems: 'center',
848
+ justifyContent: 'space-between',
849
+ gap: 12,
850
+ },
851
+ uuidText: {
852
+ fontSize: 14,
853
+ flex: 1,
854
+ },
855
+ copyButton: {
856
+ borderRadius: 8,
857
+ paddingHorizontal: 16,
858
+ paddingVertical: 8,
859
+ },
860
+ copyText: {
861
+ fontSize: 14,
862
+ fontWeight: '700',
863
+ },
707
864
  paragraph: {
708
865
  marginBottom: 24,
709
866
  },