diff --git a/README.md b/README.md index aee33f08..51faf56c 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ All the options are supported. The callback function also receives these exports ### ConsentManager -The `ConsentManager` React component is a prebuilt consent manager UI (it's the one we use on https://segment.com) that uses the [ConsentManagerBuilder][] component under the hood. To use it, just mount the component where you want the consent banner to appear and pass in your own custom copy. +The `ConsentManager` React component is a prebuilt consent manager UI (it's the one we use on ) that uses the [ConsentManagerBuilder][] component under the hood. To use it, just mount the component where you want the consent banner to appear and pass in your own custom copy. #### Props @@ -475,6 +475,13 @@ Default: the [top most domain][top-domain] and all sub domains The domain the `tracking-preferences` cookie should be scoped to. +##### cookieName + +Type: `string`
+Default: `tracking-preferences` + +The cookie name that should be used to store tracking preferences cookie + #### cookieExpires Type: `number`
@@ -629,7 +636,7 @@ The CDN to fetch list of integrations from To run our storybook locally, simply do: ``` -$ yarn dev +yarn dev ``` and the storybook should be opened in your browser. We recommend adding a new story for new features, and testing against existing stories when making bug fixes. @@ -639,8 +646,8 @@ and the storybook should be opened in your browser. We recommend adding a new st This package follows semantic versioning. To publish a new version: ``` -$ npm version -$ npm publish +npm version +npm publish ``` ## License diff --git a/src/__tests__/consent-manager-builder/preferences.test.ts b/src/__tests__/consent-manager-builder/preferences.test.ts index 209a048f..823fd003 100644 --- a/src/__tests__/consent-manager-builder/preferences.test.ts +++ b/src/__tests__/consent-manager-builder/preferences.test.ts @@ -35,6 +35,20 @@ describe('preferences', () => { }) }) + test('loadPreferences(cookieName) returns preferences when cookie exists', () => { + document.cookie = + 'custom-tracking-preferences={%22version%22:1%2C%22destinations%22:{%22Amplitude%22:true}%2C%22custom%22:{%22functional%22:true}}' + + expect(loadPreferences('custom-tracking-preferences')).toMatchObject({ + destinationPreferences: { + Amplitude: true + }, + customPreferences: { + functional: true + } + }) + }) + test('savePreferences() saves the preferences', () => { const ajsIdentify = sinon.spy() @@ -93,4 +107,30 @@ describe('preferences', () => { // TODO: actually check domain // expect(document.cookie.includes('domain=example.com')).toBe(true) }) + + test('savePreferences() sets the cookie with custom key', () => { + const ajsIdentify = sinon.spy() + // @ts-ignore + window.analytics = { identify: ajsIdentify } + document.cookie = '' + + const destinationPreferences = { + Amplitude: true + } + + savePreferences({ + destinationPreferences, + customPreferences: undefined, + cookieDomain: undefined, + cookieName: 'custom-tracking-preferences' + }) + + expect(ajsIdentify.calledOnce).toBe(true) + expect(ajsIdentify.args[0][0]).toMatchObject({ + destinationTrackingPreferences: destinationPreferences, + customTrackingPreferences: undefined + }) + + expect(document.cookie.includes('custom-tracking-preferences')).toBe(true) + }) }) diff --git a/src/consent-manager-builder/index.tsx b/src/consent-manager-builder/index.tsx index 43022b86..2fcc3e2f 100644 --- a/src/consent-manager-builder/index.tsx +++ b/src/consent-manager-builder/index.tsx @@ -34,6 +34,7 @@ interface Props { otherWriteKeys?: string[] cookieDomain?: string + cookieName?: string /** * Number of days until the preferences cookie should expire @@ -184,11 +185,12 @@ export default class ConsentManagerBuilder extends Component { mapCustomPreferences, defaultDestinationBehavior, cookieDomain, + cookieName, cookieExpires, cdnHost = ConsentManagerBuilder.defaultProps.cdnHost } = this.props // TODO: add option to run mapCustomPreferences on load so that the destination preferences automatically get updated - let { destinationPreferences, customPreferences } = loadPreferences() + let { destinationPreferences, customPreferences } = loadPreferences(cookieName) const [isConsentRequired, destinations] = await Promise.all([ shouldRequireConsent(), @@ -216,7 +218,13 @@ export default class ConsentManagerBuilder extends Component { const mapped = mapCustomPreferences(destinations, preferences) destinationPreferences = mapped.destinationPreferences customPreferences = mapped.customPreferences - savePreferences({ destinationPreferences, customPreferences, cookieDomain, cookieExpires }) + savePreferences({ + destinationPreferences, + customPreferences, + cookieDomain, + cookieName, + cookieExpires + }) } } else { preferences = destinationPreferences || initialPreferences @@ -255,8 +263,8 @@ export default class ConsentManagerBuilder extends Component { } handleResetPreferences = () => { - const { initialPreferences, mapCustomPreferences } = this.props - const { destinationPreferences, customPreferences } = loadPreferences() + const { initialPreferences, mapCustomPreferences, cookieName } = this.props + const { destinationPreferences, customPreferences } = loadPreferences(cookieName) let preferences: CategoryPreferences | undefined if (mapCustomPreferences) { @@ -272,6 +280,7 @@ export default class ConsentManagerBuilder extends Component { const { writeKey, cookieDomain, + cookieName, cookieExpires, mapCustomPreferences, defaultDestinationBehavior @@ -309,7 +318,13 @@ export default class ConsentManagerBuilder extends Component { // If preferences haven't changed, don't reload the page as it's a disruptive experience for end-users if (prevState.havePreferencesChanged || newDestinations.length > 0) { - savePreferences({ destinationPreferences, customPreferences, cookieDomain, cookieExpires }) + savePreferences({ + destinationPreferences, + customPreferences, + cookieDomain, + cookieName, + cookieExpires + }) conditionallyLoadAnalytics({ writeKey, destinations, diff --git a/src/consent-manager-builder/preferences.ts b/src/consent-manager-builder/preferences.ts index 74bdbe9d..e40986ef 100644 --- a/src/consent-manager-builder/preferences.ts +++ b/src/consent-manager-builder/preferences.ts @@ -4,6 +4,7 @@ import topDomain from '@segment/top-domain' import { WindowWithAJS, Preferences, CategoryPreferences } from '../types' import { EventEmitter } from 'events' +const DEFAULT_COOKIE_NAME = 'tracking-preferences' const COOKIE_KEY = 'tracking-preferences' const COOKIE_DEFAULT_EXPIRES = 365 @@ -15,8 +16,8 @@ export interface PreferencesManager { // TODO: harden against invalid cookies // TODO: harden against different versions of cookies -export function loadPreferences(): Preferences { - const preferences = cookies.getJSON(COOKIE_KEY) +export function loadPreferences(cookieName?: string): Preferences { + const preferences = cookies.getJSON(cookieName || DEFAULT_COOKIE_NAME) if (!preferences) { return {} @@ -28,7 +29,11 @@ export function loadPreferences(): Preferences { } } -type SavePreferences = Preferences & { cookieDomain?: string; cookieExpires?: number } +type SavePreferences = Preferences & { + cookieDomain?: string + cookieName?: string + cookieExpires?: number +} const emitter = new EventEmitter() @@ -47,6 +52,7 @@ export function savePreferences({ destinationPreferences, customPreferences, cookieDomain, + cookieName, cookieExpires }: SavePreferences) { const wd = window as WindowWithAJS diff --git a/src/consent-manager/index.tsx b/src/consent-manager/index.tsx index 8f87822c..00b11379 100644 --- a/src/consent-manager/index.tsx +++ b/src/consent-manager/index.tsx @@ -19,6 +19,7 @@ export default class ConsentManager extends PureComponent Promise | boolean implyConsentOnInteraction?: boolean cookieDomain?: string + cookieName?: string cookieExpires?: number bannerContent: React.ReactNode bannerSubContent?: string diff --git a/stories/0.2-consent-manager-custom-cookie-name.stories.tsx b/stories/0.2-consent-manager-custom-cookie-name.stories.tsx new file mode 100644 index 00000000..b16abedd --- /dev/null +++ b/stories/0.2-consent-manager-custom-cookie-name.stories.tsx @@ -0,0 +1,140 @@ +import React from 'react' +import cookies from 'js-cookie' +import { Pane, Heading, Button } from 'evergreen-ui' +import { ConsentManager, openConsentManager, loadPreferences, onPreferencesSaved } from '../src' +import { storiesOf } from '@storybook/react' +import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs' +import SyntaxHighlighter from 'react-syntax-highlighter' +import { Preferences } from '../src/types' +import CookieView from './components/CookieView' + +const bannerContent = ( + + We use cookies (and other similar technologies) to collect data to improve your experience on + our site. By using our website, you’re agreeing to the collection of data as described in our{' '} + + Website Data Collection Policy + + . + +) +const bannerSubContent = 'You can manage your preferences here!' +const preferencesDialogTitle = 'Website Data Collection Preferences' +const preferencesDialogContent = ( +
+

+ Segment uses data collected by cookies and JavaScript libraries to improve your browsing + experience, analyze site traffic, deliver personalized advertisements, and increase the + overall performance of our site. +

+

+ By using our website, you’re agreeing to our{' '} + + Website Data Collection Policy + + . +

+

+ The table below outlines how we use this data by category. To opt out of a category of data + collection, select “No” and save your preferences. +

+
+) +const cancelDialogTitle = 'Are you sure you want to cancel?' +const cancelDialogContent = ( +
+ Your preferences have not been saved. By continuing to use our website, you’re agreeing to our{' '} + + Website Data Collection Policy + + . +
+) + +const ConsentManagerExample = (props: { cookieName: string }) => { + const [prefs, updatePrefs] = React.useState( + loadPreferences('custom-tracking-preferences') + ) + + const cleanup = onPreferencesSaved(preferences => { + updatePrefs(preferences) + }) + + React.useEffect(() => { + return () => { + cleanup() + } + }) + + return ( + + + + + Your website content + +