From 678c6d8d918c72dfef9faeb895be57d39c51805c Mon Sep 17 00:00:00 2001 From: Shraddha <24843989+siddy2181@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:48:50 -0800 Subject: [PATCH] feat(fraudnet): add fraudnet util for shared SDK components (#170) * started test driving data collector util * add tests and todos, break fns down a bit * add fraudnet util * update correct fn url * cleanup * outline of fraudnet work * loadfraudnet exposed but not called * fix up error handling/swalloing * remove jest config and fraudnet from setup file * clean up fraudnet lints * fix loadfraudnet type * explicit response type * fix some flow typing of load fraudnet * fix flow type errors * fix some flow * type the fraudneturl const * fix duplicate param * use env ref in types * actual use constants type * 4.0.178 * update fraudnet urls --------- Co-authored-by: cdibble Co-authored-by: cgdibble --- src/constants.js | 17 ++++++ src/fraudnet.js | 141 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 159 insertions(+) create mode 100644 src/fraudnet.js diff --git a/src/constants.js b/src/constants.js index 6c2c530d..6318b0ff 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,4 +1,5 @@ /* @flow */ +import { ENV } from "@paypal/sdk-constants/src"; export const FPTI_CONTEXT_TYPE = { ORDER_ID: ("EC-Token": "EC-Token"), @@ -13,3 +14,19 @@ export const FPTI_TRANSITION = { export const FPTI_STATE = { PXP: ("PXP_CHECK": "PXP_CHECK"), }; + +type FraudnetUrl = {| + [$Values]: string, +|}; + +export const FRAUDNET_URL: FraudnetUrl = { + [ENV.LOCAL]: + "https://cdn-latest.static.engineering.dev.paypalinc.com/qaRdaAssets/fraudnet/async/fb-raw.js", + [ENV.STAGE]: + "https://cdn-latest.static.engineering.dev.paypalinc.com/qaRdaAssets/fraudnet/async/fb-raw.js", + [ENV.SANDBOX]: "https://c.paypal.com/da/r/fb.js", + [ENV.PRODUCTION]: "https://c.paypal.com/da/r/fb.js", + [ENV.TEST]: "https://c.paypal.com/da/r/fb.js", +}; + +export const FRAUDNET_FNCLS = "fnparams-dede7cc5-15fd-4c75-a9f4-36c430ee3a99"; diff --git a/src/fraudnet.js b/src/fraudnet.js new file mode 100644 index 00000000..1bc74485 --- /dev/null +++ b/src/fraudnet.js @@ -0,0 +1,141 @@ +/* eslint-disable promise/no-native, no-restricted-globals */ +/* @flow */ + +import { ZalgoPromise } from "@krakenjs/zalgo-promise/src"; +import { ENV } from "@paypal/sdk-constants/src"; +import { memoize, type Memoized } from "@krakenjs/belter/src"; + +import { FRAUDNET_FNCLS, FRAUDNET_URL } from "./constants"; +import { getLogger } from "./logger"; + +type FraudnetOptions = {| + env: $Values, + clientMetadataID: string, + cspNonce: string, + appName: string, + queryStringParams?: { [string]: string | boolean }, +|}; + +type FraudnetConfig = {| + f: string, + s: string, + u?: string, + cb1: string, + sandbox?: boolean, + io: boolean, +|}; + +type CreateConfigOptions = {| + env: $Values, + cspNonce: string, + clientMetadataID: string, + appName: string, +|}; + +type CreateFraudnetOptions = {| + cspNonce: string, + env: $Values, + queryStringParams?: { [string]: string | boolean }, +|}; + +export const createConfigScript = ({ + env, + cspNonce = "", + clientMetadataID, + appName, +}: CreateConfigOptions): ZalgoPromise => { + return new ZalgoPromise((resolve) => { + if (__TEST__) { + return resolve(); + } + + const config: FraudnetConfig = { + f: clientMetadataID, + s: appName, + io: true, + cb1: "fnCallback", + }; + + if (env === ENV.SANDBOX) { + config.sandbox = true; + } + + const configScript = document.createElement("script"); + + configScript.setAttribute("nonce", cspNonce); + configScript.setAttribute("type", "application/json"); + configScript.setAttribute("id", "fconfig"); + configScript.setAttribute("fncls", FRAUDNET_FNCLS); + configScript.text = JSON.stringify(config); + // eslint-disable-next-line compat/compat + document.body?.appendChild(configScript); + }); +}; + +export const createFraudnetScript = ({ + cspNonce, + env, + queryStringParams = {}, +}: CreateFraudnetOptions): ZalgoPromise => { + return new ZalgoPromise((resolve, reject) => { + const fraudnetScript = document.createElement("script"); + const queryString = Object.keys(queryStringParams) + .map( + (key) => `${key}=${encodeURIComponent(String(queryStringParams[key]))}` + ) + .join("&"); + + const fraudnetUrl = queryString.length + ? `${FRAUDNET_URL[env]}?${queryString}` + : FRAUDNET_URL[env]; + + fraudnetScript.setAttribute("nonce", cspNonce || ""); + fraudnetScript.setAttribute("src", fraudnetUrl); + fraudnetScript.addEventListener("error", () => resolve()); + + window.fnCallback = resolve; + // eslint-disable-next-line compat/compat + document.body?.appendChild(fraudnetScript); + + fraudnetScript.addEventListener("load", () => { + resolve(); + }); + fraudnetScript.addEventListener("error", () => { + reject(new Error(`Fraudnet failed to load.`)); + }); + fraudnetScript.addEventListener("abort", () => { + reject(new Error(`Fraudnet load was aborted.`)); + }); + }); +}; + +type LoadFraudnetResponse = {| + collect: () => Promise<*> | void, +|}; +type LoadFraudnet = (opts: FraudnetOptions) => LoadFraudnetResponse; + +export const loadFraudnet: Memoized = memoize( + ({ env, clientMetadataID, cspNonce, appName, queryStringParams = {} }) => { + createConfigScript({ env, cspNonce, clientMetadataID, appName }); + + const fraudnetPromise = createFraudnetScript({ + cspNonce, + env, + queryStringParams, + }).catch(() => { + getLogger().warn("ppcp_axo_init_fraudnet_failed"); + }); + + return { + collect: async () => { + try { + await fraudnetPromise; + await window.PAYPAL.asyncData.collect(); + } catch (err) { + getLogger().warn("ppcp_axo_collect_fraudnet_failed"); + } + }, + }; + } +); +/* eslint-enable promise/no-native, no-restricted-globals */ diff --git a/src/index.js b/src/index.js index 3a2da175..dc27de95 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ export * from "./logger"; export * from "./types"; export * from "./global"; export * from "./script"; +export * from "./fraudnet"; export * from "./meta"; export * from "./api"; export * from "./experiment";