From 2517774a8513428f9cd0cd9b8a3538592f3af113 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Wed, 7 Sep 2022 01:42:34 -0700 Subject: [PATCH 1/4] [WIP] React example for do not sell button --- .eslintrc.js | 2 +- src/DoNotSellButton.tsx | 66 ++++++++++++++++++++++++++++++++++++++ src/init.ts | 4 +-- src/settings.ts | 4 +-- src/utils/idle-callback.ts | 3 +- 5 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 src/DoNotSellButton.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 4db72f59..8a4668f8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -237,7 +237,7 @@ module.exports = { '@typescript-eslint/no-this-alias': ['error'], /** Prevent use of global variables */ - 'no-restricted-globals': ['error'], + 'no-restricted-globals': 0, /** No unnecessary async statements on a function */ 'require-await': ['error'], diff --git a/src/DoNotSellButton.tsx b/src/DoNotSellButton.tsx new file mode 100644 index 00000000..73730eb7 --- /dev/null +++ b/src/DoNotSellButton.tsx @@ -0,0 +1,66 @@ +import { AirgapAPI, TranscendAPI } from '@transcend-io/airgap.js-types'; +import { h, FunctionComponent } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; + +/** + * Global window with airgap instance attached + */ +type WindowWithAirgap = typeof self & { + /** Airgap instance should have been initialized on self when script added in header */ + transcend: TranscendAPI; + /** Airgap API */ + airgap: AirgapAPI; +}; + +/** + * A button that implements the logic for Do Not Sell/Do Not Share + * to ensure compliance with laws like CPRA, CPA, CDPA + */ +export const DoNotSellButton: FunctionComponent = () => { + // Ensure airgap was initialized + const selfWithAirgap = self as WindowWithAirgap; + if (!selfWithAirgap.airgap || !selfWithAirgap.transcend) { + throw new Error(`Expected airgap and transcend to be initialized on self!`); + } + + // Check if the user is opted into sale of data + const [isSellingData, setIsSellingData] = useState( + !!selfWithAirgap.airgap.getConsent().purposes.SaleOfInfo, + ); + + // update state variable when consent values change + useEffect(() => { + // Callback to re-calculate consent preferences + const onConsentChange = (): void => { + setIsSellingData( + !!selfWithAirgap.airgap.getConsent().purposes.SaleOfInfo, + ); + }; + + // Add listener on change to re-calculate consent preferences + selfWithAirgap.airgap.addEventListener('consent-change', onConsentChange); + + // Remove event listener when component dismounts + return () => + selfWithAirgap.airgap.removeEventListener( + 'consent-change', + onConsentChange, + ); + }, [selfWithAirgap.airgap]); + + return ( + + ); +}; diff --git a/src/init.ts b/src/init.ts index 46f65cf5..d5328e1a 100644 --- a/src/init.ts +++ b/src/init.ts @@ -19,7 +19,7 @@ import { logger } from './logger'; import { LOG_ENABLED, LOG_LEVELS } from './settings'; import { throwOutside } from './utils/throw-outside'; -// eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any const view = self as any; /** Pre-setup reference to window.transcend */ @@ -42,7 +42,7 @@ if (Array.isArray(readyQueue)) { // Promise which resolves when airgap.js core API is ready export const airgapPromise = new Promise((resolve) => { // Stub self.airgap.ready() queue if it doesn't exist - // eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any ((self as any).airgap ??= { readyQueue: [], /** diff --git a/src/settings.ts b/src/settings.ts index 44e81d99..06978c87 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,9 +13,9 @@ export const apiEventName = 'tcmUIApiEvent'; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function getAirgapSettings(): any { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-restricted-globals + // eslint-disable-next-line @typescript-eslint/no-explicit-any const airgapInit = (self as any)?.airgap; - // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-restricted-globals + // eslint-disable-next-line @typescript-eslint/no-explicit-any const transcendInit = (self as any)?.transcend; // transcend.loadOptions is used to inject settings from our backend // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/utils/idle-callback.ts b/src/utils/idle-callback.ts index 5fa00e22..fe19e1a1 100644 --- a/src/utils/idle-callback.ts +++ b/src/utils/idle-callback.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line no-restricted-globals const { setTimeout, clearTimeout } = self; /** Polyfills for requestIdleCallback and cancelIdleCallback */ @@ -15,5 +14,5 @@ export const { cancelIdleCallback = (callbackId: number) => { clearTimeout(callbackId); }, - // eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any } = self as any; From de1dcec405aaaf47a7939d377f343e05cf37e724 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Wed, 7 Sep 2022 01:44:14 -0700 Subject: [PATCH 2/4] eslint --- .eslintrc.js | 2 +- src/DoNotSellButton.tsx | 3 +++ src/init.ts | 4 ++-- src/settings.ts | 4 ++-- src/utils/idle-callback.ts | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8a4668f8..4db72f59 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -237,7 +237,7 @@ module.exports = { '@typescript-eslint/no-this-alias': ['error'], /** Prevent use of global variables */ - 'no-restricted-globals': 0, + 'no-restricted-globals': ['error'], /** No unnecessary async statements on a function */ 'require-await': ['error'], diff --git a/src/DoNotSellButton.tsx b/src/DoNotSellButton.tsx index 73730eb7..bbc87d87 100644 --- a/src/DoNotSellButton.tsx +++ b/src/DoNotSellButton.tsx @@ -1,3 +1,5 @@ +/* eslint-disable no-restricted-globals */ + import { AirgapAPI, TranscendAPI } from '@transcend-io/airgap.js-types'; import { h, FunctionComponent } from 'preact'; import { useEffect, useState } from 'preact/hooks'; @@ -64,3 +66,4 @@ export const DoNotSellButton: FunctionComponent = () => { ); }; +/* eslint-enable no-restricted-globals */ diff --git a/src/init.ts b/src/init.ts index d5328e1a..46f65cf5 100644 --- a/src/init.ts +++ b/src/init.ts @@ -19,7 +19,7 @@ import { logger } from './logger'; import { LOG_ENABLED, LOG_LEVELS } from './settings'; import { throwOutside } from './utils/throw-outside'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any const view = self as any; /** Pre-setup reference to window.transcend */ @@ -42,7 +42,7 @@ if (Array.isArray(readyQueue)) { // Promise which resolves when airgap.js core API is ready export const airgapPromise = new Promise((resolve) => { // Stub self.airgap.ready() queue if it doesn't exist - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any ((self as any).airgap ??= { readyQueue: [], /** diff --git a/src/settings.ts b/src/settings.ts index 06978c87..44e81d99 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,9 +13,9 @@ export const apiEventName = 'tcmUIApiEvent'; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function getAirgapSettings(): any { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-restricted-globals const airgapInit = (self as any)?.airgap; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-restricted-globals const transcendInit = (self as any)?.transcend; // transcend.loadOptions is used to inject settings from our backend // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/utils/idle-callback.ts b/src/utils/idle-callback.ts index fe19e1a1..5fa00e22 100644 --- a/src/utils/idle-callback.ts +++ b/src/utils/idle-callback.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-globals const { setTimeout, clearTimeout } = self; /** Polyfills for requestIdleCallback and cancelIdleCallback */ @@ -14,5 +15,5 @@ export const { cancelIdleCallback = (callbackId: number) => { clearTimeout(callbackId); }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any } = self as any; From d7fd4f0afcc9496dc52477aa8f88044c6a531da6 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Wed, 7 Sep 2022 01:46:38 -0700 Subject: [PATCH 3/4] conditional --- src/DoNotSellButton.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/DoNotSellButton.tsx b/src/DoNotSellButton.tsx index bbc87d87..85c431e6 100644 --- a/src/DoNotSellButton.tsx +++ b/src/DoNotSellButton.tsx @@ -50,6 +50,11 @@ export const DoNotSellButton: FunctionComponent = () => { ); }, [selfWithAirgap.airgap]); + // Only show the button in california + if (!selfWithAirgap.airgap.getRegimes().has('CPRA')) { + return null; + } + return (