From 753bb658ae68c21f6b2e538eb6aa25540d326950 Mon Sep 17 00:00:00 2001 From: adrienne-rio Date: Tue, 24 Sep 2024 12:40:40 +0800 Subject: [PATCH 01/22] feat: added analytics to smarttrader --- build/webpack/plugins.js | 6 ++ package-lock.json | 94 ++++++++++++++++++++++++++--- package.json | 1 + src/javascript/_common/analytics.js | 44 ++++++++++++++ src/javascript/app/base/page.js | 2 + 5 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 src/javascript/_common/analytics.js diff --git a/build/webpack/plugins.js b/build/webpack/plugins.js index 8131a8c4973..b7108750190 100644 --- a/build/webpack/plugins.js +++ b/build/webpack/plugins.js @@ -54,6 +54,9 @@ const getPlugins = (app, grunt) => ([ 'process.env': { BUILD_HASH: JSON.stringify(CryptoJS.MD5(Date.now().toString()).toString()), NODE_ENV : JSON.stringify('production'), + GROWTHBOOK_CLIENT_KEY: JSON.stringify(process.env.GROWTHBOOK_CLIENT_KEY), + GROWTHBOOK_DECRYPTION_KEY: JSON.stringify(process.env.GROWTHBOOK_DECRYPTION_KEY), + RUDDERSTACK_KEY: JSON.stringify(process.env.RUDDERSTACK_KEY), }, }), ] @@ -69,6 +72,9 @@ const getPlugins = (app, grunt) => ([ new webpack.DefinePlugin({ 'process.env': { BUILD_HASH: JSON.stringify(CryptoJS.MD5(Date.now().toString()).toString()), + GROWTHBOOK_CLIENT_KEY: JSON.stringify(process.env.GROWTHBOOK_CLIENT_KEY), + GROWTHBOOK_DECRYPTION_KEY: JSON.stringify(process.env.GROWTHBOOK_DECRYPTION_KEY), + RUDDERSTACK_KEY: JSON.stringify(process.env.RUDDERSTACK_KEY), }, }), ] diff --git a/package-lock.json b/package-lock.json index 2a002b23897..d79a17831f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@binary-com/binary-document-uploader": "^2.4.4", "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.2", + "@deriv-com/analytics": "^1.18.0", "@deriv-com/quill-ui": "^1.16.2", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-icons": "^1.23.1", @@ -2487,6 +2488,41 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@deriv-com/analytics": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@deriv-com/analytics/-/analytics-1.18.0.tgz", + "integrity": "sha512-n1nwd5imrZJEWw4CRvYTE0/tmO+baCuc9EpQa46JuSPFVUjLUhMwILJ5XPg5Eu24bPUjatkkVygEm+dsQBE7fg==", + "dependencies": { + "@growthbook/growthbook": "^1.1.0", + "@rudderstack/analytics-js": "^3.5.1", + "js-cookie": "^3.0.5", + "uuid": "^10.0.0" + }, + "engines": { + "node": "18.x", + "npm": "9.x" + } + }, + "node_modules/@deriv-com/analytics/node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@deriv-com/analytics/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@deriv-com/quill-tokens": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@deriv-com/quill-tokens/-/quill-tokens-2.0.10.tgz", @@ -2659,6 +2695,17 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@growthbook/growthbook": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@growthbook/growthbook/-/growthbook-1.2.0.tgz", + "integrity": "sha512-lL47UKvzUaUq2QLwiePrUbM/p+NPv6WUGrGs2bjw1Zc51dtNtxAD+s1hXEU9wpAF913kbiOW2+yRa67UhxSkuw==", + "dependencies": { + "dom-mutator": "^0.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@headlessui/react": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.18.tgz", @@ -3154,6 +3201,11 @@ "linux" ] }, + "node_modules/@rudderstack/analytics-js": { + "version": "3.7.13", + "resolved": "https://registry.npmjs.org/@rudderstack/analytics-js/-/analytics-js-3.7.13.tgz", + "integrity": "sha512-8nIWn2gv3x2Vj1m7xa8fwXz8aqKo+HdOfYitsNaSTPDM2he0W8jsE8ydx1Q010hf77WT75v9C9VqYNlZe7mHDQ==" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -4391,7 +4443,8 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -5138,7 +5191,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base": { "version": "0.11.2", @@ -5286,6 +5340,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6079,7 +6134,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/config-chain": { "version": "1.1.13", @@ -6940,6 +6996,14 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/dom-mutator": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/dom-mutator/-/dom-mutator-0.6.0.tgz", + "integrity": "sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg==", + "engines": { + "node": ">=10" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -8805,6 +8869,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, "engines": { "node": ">= 4.9.1" } @@ -9161,7 +9226,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -9406,6 +9472,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9440,6 +9507,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -9448,6 +9516,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -11129,6 +11198,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -11156,6 +11226,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -13012,6 +13083,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13363,7 +13435,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/multimatch": { "version": "4.0.0", @@ -13597,6 +13670,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, "dependencies": { "abbrev": "1" }, @@ -16288,6 +16362,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -18600,6 +18675,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -20598,7 +20674,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thenify": { "version": "3.3.1", @@ -21886,6 +21963,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -22020,7 +22098,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/wrench": { "version": "1.4.4", @@ -22036,6 +22115,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" diff --git a/package.json b/package.json index 8e914e4fe5c..cb3dafd289d 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "@binary-com/binary-document-uploader": "^2.4.4", "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.2", + "@deriv-com/analytics": "^1.18.0", "@deriv-com/quill-ui": "^1.16.2", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-icons": "^1.23.1", diff --git a/src/javascript/_common/analytics.js b/src/javascript/_common/analytics.js new file mode 100644 index 00000000000..f475c1b5bb8 --- /dev/null +++ b/src/javascript/_common/analytics.js @@ -0,0 +1,44 @@ +const DerivAnalytics = require('@deriv-com/analytics'); + +const Analytics = (() => { + const init = () => { + if (process.env.RUDDERSTACK_KEY && process.env.GROWTHBOOK_CLIENT_KEY && process.env.GROWTHBOOK_DECRYPTION_KEY) { + DerivAnalytics.Analytics.initialise({ + growthbookKey : process.env.GROWTHBOOK_CLIENT_KEY, // optional key to enable A/B tests + growthbookDecryptionKey: process.env.GROWTHBOOK_DECRYPTION_KEY, // optional key to enable A/B tests + rudderstackKey : process.env.RUDDERSTACK_KEY, + }); + } + }; + + const isGrowthbookLoaded = () => Boolean(DerivAnalytics.Analytics?.getInstances()?.ab); + + const getGrowthbookFeatureValue = ({ defaultValue, featureFlag }) => { + const resolvedDefaultValue = defaultValue !== undefined ? defaultValue : false; + const isGBLoaded = isGrowthbookLoaded(); + + if (!isGBLoaded) return null; + if (DerivAnalytics.Analytics?.getInstances()?.ab) { + return DerivAnalytics.Analytics?.getFeatureValue(featureFlag, resolvedDefaultValue); + } + }; + + const setGrowthbookOnChange = onChange => { + const isGBLoaded = isGrowthbookLoaded(); + if (!isGBLoaded) return null; + if (DerivAnalytics.Analytics?.getInstances()?.ab) { + DerivAnalytics.Analytics?.getInstances()?.ab?.GrowthBook?.setRenderer(() => { + onChange?.(); + }); + } + }; + + return { + init, + isGrowthbookLoaded, + getGrowthbookFeatureValue, + setGrowthbookOnChange, + }; +})(); + +module.exports = Analytics; diff --git a/src/javascript/app/base/page.js b/src/javascript/app/base/page.js index aecb5603af1..dd2d4489820 100644 --- a/src/javascript/app/base/page.js +++ b/src/javascript/app/base/page.js @@ -21,6 +21,7 @@ const State = require('../../_common/storage').State; const scrollToTop = require('../../_common/scroll').scrollToTop; const toISOFormat = require('../../_common/string_util').toISOFormat; const Url = require('../../_common/url'); +const Analytics = require('../../_common/analytics'); const createElement = require('../../_common/utility').createElement; const isLoginPages = require('../../_common/utility').isLoginPages; const isProduction = require('../../config').isProduction; @@ -35,6 +36,7 @@ const Page = (() => { Elevio.init(); onDocumentReady(); Crowdin.init(); + Analytics.init(); }; const onDocumentReady = () => { From de69d69cb11dbac8a3066259019613151eff1a32 Mon Sep 17 00:00:00 2001 From: adrienne-rio Date: Tue, 24 Sep 2024 13:40:23 +0800 Subject: [PATCH 02/22] feat: added auth-client library --- package-lock.json | 53 ++++++++++++++++ package.json | 2 + src/javascript/_common/auth.js | 85 ++++++++++++++++++++++++++ src/javascript/app/base/header.js | 10 ++- src/javascript/app/hooks/growthbook.js | 57 +++++++++++++++++ 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/javascript/_common/auth.js create mode 100644 src/javascript/app/hooks/growthbook.js diff --git a/package-lock.json b/package-lock.json index d79a17831f5..5965d9cb2fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.2", "@deriv-com/analytics": "^1.18.0", + "@deriv-com/auth-client": "^1.0.15", "@deriv-com/quill-ui": "^1.16.2", + "@deriv-com/utils": "^0.0.34", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-icons": "^1.23.1", "@livechat/customer-sdk": "4.0.2", @@ -2523,6 +2525,52 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@deriv-com/auth-client": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.0.15.tgz", + "integrity": "sha512-2G6IUtPIX2TmlpW2khDUeltqfsxJtxZgiFdoHidW19rjZYDaj4aHDEh/RXArbiUUKF0i0FGgupzAuW4Id/o7SA==", + "dependencies": { + "@deriv-com/utils": "^0.0.33", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } + }, + "node_modules/@deriv-com/auth-client/node_modules/@deriv-com/utils": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@deriv-com/utils/-/utils-0.0.33.tgz", + "integrity": "sha512-LzIpzMvfWhK9y06Qpe/HOB4pFCizk2wAyhv9I0s48Romq+d5MM1mmsuh5CvS4SnzzdLyuBy4rgXrOO3394HB7w==" + }, + "node_modules/@deriv-com/auth-client/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@deriv-com/auth-client/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/@deriv-com/auth-client/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@deriv-com/quill-tokens": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@deriv-com/quill-tokens/-/quill-tokens-2.0.10.tgz", @@ -2570,6 +2618,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@deriv-com/utils": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@deriv-com/utils/-/utils-0.0.34.tgz", + "integrity": "sha512-Hq2DWVKiSR4dTnVTy1lMwAt1F58DPAP0VGB2Nfa9mtPWOfOBGO6ZIeU7+fLGrATukc55ZfHDmfqwgnAi6Nqczg==" + }, "node_modules/@deriv/deriv-api": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/@deriv/deriv-api/-/deriv-api-1.0.15.tgz", diff --git a/package.json b/package.json index cb3dafd289d..d06ab1f3b3a 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,9 @@ "@binary-com/binary-style": "^0.2.26", "@binary-com/webtrader-charts": "^0.6.2", "@deriv-com/analytics": "^1.18.0", + "@deriv-com/auth-client": "^1.0.15", "@deriv-com/quill-ui": "^1.16.2", + "@deriv-com/utils": "^0.0.34", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-icons": "^1.23.1", "@livechat/customer-sdk": "4.0.2", diff --git a/src/javascript/_common/auth.js b/src/javascript/_common/auth.js new file mode 100644 index 00000000000..b9889b374e4 --- /dev/null +++ b/src/javascript/_common/auth.js @@ -0,0 +1,85 @@ +const Utils = require('@deriv-com/utils'); + +export const DEFAULT_OAUTH_LOGOUT_URL = 'https://oauth.deriv.com/oauth2/sessions/logout'; + +export const DEFAULT_OAUTH_ORIGIN_URL = 'https://oauth.deriv.com'; + +export const getOAuthLogoutUrl = () => { + const { appId, serverUrl } = Utils.WebSocketUtils.getServerInfo(); + + const oauthUrl = appId && serverUrl ? `https://${serverUrl}/oauth2/sessions/logout` : DEFAULT_OAUTH_LOGOUT_URL; + + return oauthUrl; +}; + +export const getOAuthOrigin = () => { + const { appId, serverUrl } = Utils.WebSocketUtils.getServerInfo(); + + const oauthUrl = appId && serverUrl ? `https://${serverUrl}` : DEFAULT_OAUTH_ORIGIN_URL; + + return oauthUrl; +}; + +export const AuthClient = (() => { + const isOAuth2Enabled = oAuth2GrowthbookConfig => { + const { OAuth2EnabledApps, OAuth2EnabledAppsInitialised } = oAuth2GrowthbookConfig; + const appId = Utils.WebSocketUtils.getAppId(); + + if (OAuth2EnabledAppsInitialised) { + const FEHydraAppIds = OAuth2EnabledApps?.length + ? OAuth2EnabledApps[OAuth2EnabledApps.length - 1]?.enabled_for ?? [] + : []; + return FEHydraAppIds.includes(+appId); + } + + return false; + }; + const getLogoutHandler = (oAuth2GrowthbookConfig, onWSLogoutAndRedirect) => { + const isAuthEnabled = isOAuth2Enabled(oAuth2GrowthbookConfig); + + if (!isAuthEnabled) { + console.log('lol auth2 is not enabled'); + return onWSLogoutAndRedirect; + } + + const onMessage = async event => { + const allowedOrigin = getOAuthOrigin(); + if (allowedOrigin === event.origin) { + if (event.data === 'logout_complete') { + console.log('logout_complete calledd!'); + onWSLogoutAndRedirect(); + } + } + }; + + window.addEventListener('message', onMessage); + + const oAuth2Logout = async () => { + if (!isAuthEnabled) { + console.log('lol auth2 is not enabled'); + onWSLogoutAndRedirect(); + return; + } + + let iframe = document.getElementById('logout-iframe'); + if (!iframe) { + iframe = document.createElement('iframe'); + iframe.id = 'logout-iframe'; + iframe.style.display = 'none'; + document.body.appendChild(iframe); + + setTimeout(() => { + onWSLogoutAndRedirect(); + }, 10000); + } + console.log('auth2 doneee'); + }; + + return oAuth2Logout; + }; + + return { + getLogoutHandler, + isOAuth2Enabled, + }; +})(); diff --git a/src/javascript/app/base/header.js b/src/javascript/app/base/header.js index 812fb94554c..567a7d79971 100644 --- a/src/javascript/app/base/header.js +++ b/src/javascript/app/base/header.js @@ -1,4 +1,6 @@ // const BinaryPjax = require('./binary_pjax'); +const AuthClient = require('../../_common/auth'); +const Analytics = require('../../_common/analytics'); const Client = require('./client'); const BinarySocket = require('./socket'); const showHidePulser = require('../common/account_opening').showHidePulser; @@ -303,7 +305,7 @@ const Header = (() => { el.removeEventListener('click', logoutOnClick); el.addEventListener('click', logoutOnClick); }); - + // Mobile menu const mobile_menu_overlay = getElementById('mobile__container'); const mobile_menu = getElementById('mobile__menu'); @@ -625,7 +627,11 @@ const Header = (() => { }; const logoutOnClick = () => { - Client.sendLogoutRequest(); + const value = Analytics.getGrowthbookFeatureValue({ + featureFlag: 'hydra_be', + }); + const onLogoutWithHydra = AuthClient.AuthClient.getLogoutHandler(value, () => Client.sendLogoutRequest()); + onLogoutWithHydra(); }; const populateWalletAccounts = () => { diff --git a/src/javascript/app/hooks/growthbook.js b/src/javascript/app/hooks/growthbook.js new file mode 100644 index 00000000000..da4b018f78c --- /dev/null +++ b/src/javascript/app/hooks/growthbook.js @@ -0,0 +1,57 @@ +// growthbook.js +import { useState, useEffect } from 'react'; + +import { Analytics } from '@deriv-com/analytics'; + +export const useIsGrowthbookIsLoaded = () => { + const [isGBLoaded, setIsGBLoaded] = useState(false); + + useEffect(() => { + let checksCounter = 0; + const analyticsInterval = setInterval(() => { + // Check if the analytics instance is available for 10 seconds before setting the feature flag value + if (checksCounter > 20) { + // If the analytics instance is not available after 10 seconds, clear the interval + clearInterval(analyticsInterval); + return; + } + checksCounter += 1; + if (Analytics?.getInstances()?.ab) { + setIsGBLoaded(true); + clearInterval(analyticsInterval); + } + }, 500); + + return () => { + clearInterval(analyticsInterval); + }; + }, []); + + return isGBLoaded; +}; + +export const useGrowthbookGetFeatureValue = ({ defaultValue, featureFlag }) => { + const resolvedDefaultValue = defaultValue !== undefined ? defaultValue : false; + const [featureFlagValue, setFeatureFlagValue] = useState( + Analytics?.getFeatureValue(featureFlag, resolvedDefaultValue) ?? resolvedDefaultValue + ); + const isGBLoaded = useIsGrowthbookIsLoaded(); + + useEffect(() => { + if (isGBLoaded) { + if (Analytics?.getInstances()?.ab) { + const setFeatureValue = () => { + const value = Analytics?.getFeatureValue(featureFlag, resolvedDefaultValue); + setFeatureFlagValue(value); + }; + setFeatureValue(); + Analytics?.getInstances()?.ab?.GrowthBook?.setRenderer(() => { + // this will be called whenever the feature flag value changes and acts as a event listener + setFeatureValue(); + }); + } + } + }, [isGBLoaded, resolvedDefaultValue, featureFlag]); + + return [featureFlagValue, isGBLoaded]; +}; From be1724ff15f30d769300b372e4de661fc6f8ec21 Mon Sep 17 00:00:00 2001 From: adrienne-rio Date: Tue, 24 Sep 2024 15:53:09 +0800 Subject: [PATCH 03/22] chore: generate test link --- src/javascript/_common/analytics.js | 17 +- src/javascript/_common/auth.js | 141 +++++---- src/javascript/_common/base/client_base.js | 268 ++++++++++-------- src/javascript/app/base/header.js | 12 +- src/javascript/app/base/logged_in.js | 2 + src/javascript/app/pages/deriv_iframe.jsx | 19 ++ src/javascript/app/pages/trade/tradepage.js | 46 +-- src/templates/_common/_layout/layout.jsx | 22 +- .../_common/includes/deriv-iframe.jsx | 8 +- src/templates/app/logged_in.jsx | 8 +- 10 files changed, 316 insertions(+), 227 deletions(-) create mode 100644 src/javascript/app/pages/deriv_iframe.jsx diff --git a/src/javascript/_common/analytics.js b/src/javascript/_common/analytics.js index f475c1b5bb8..6539482af83 100644 --- a/src/javascript/_common/analytics.js +++ b/src/javascript/_common/analytics.js @@ -4,9 +4,9 @@ const Analytics = (() => { const init = () => { if (process.env.RUDDERSTACK_KEY && process.env.GROWTHBOOK_CLIENT_KEY && process.env.GROWTHBOOK_DECRYPTION_KEY) { DerivAnalytics.Analytics.initialise({ - growthbookKey : process.env.GROWTHBOOK_CLIENT_KEY, // optional key to enable A/B tests + growthbookKey: process.env.GROWTHBOOK_CLIENT_KEY, // optional key to enable A/B tests growthbookDecryptionKey: process.env.GROWTHBOOK_DECRYPTION_KEY, // optional key to enable A/B tests - rudderstackKey : process.env.RUDDERSTACK_KEY, + rudderstackKey: process.env.RUDDERSTACK_KEY, }); } }; @@ -17,20 +17,19 @@ const Analytics = (() => { const resolvedDefaultValue = defaultValue !== undefined ? defaultValue : false; const isGBLoaded = isGrowthbookLoaded(); - if (!isGBLoaded) return null; + if (!isGBLoaded) return [null, false]; if (DerivAnalytics.Analytics?.getInstances()?.ab) { - return DerivAnalytics.Analytics?.getFeatureValue(featureFlag, resolvedDefaultValue); + return [DerivAnalytics.Analytics?.getFeatureValue(featureFlag, resolvedDefaultValue), true]; } }; const setGrowthbookOnChange = onChange => { const isGBLoaded = isGrowthbookLoaded(); if (!isGBLoaded) return null; - if (DerivAnalytics.Analytics?.getInstances()?.ab) { - DerivAnalytics.Analytics?.getInstances()?.ab?.GrowthBook?.setRenderer(() => { - onChange?.(); - }); - } + + DerivAnalytics.Analytics?.getInstances().ab.GrowthBook?.setRenderer(() => { + onChange?.(); + }); }; return { diff --git a/src/javascript/_common/auth.js b/src/javascript/_common/auth.js index b9889b374e4..c80a57e2e73 100644 --- a/src/javascript/_common/auth.js +++ b/src/javascript/_common/auth.js @@ -1,11 +1,50 @@ -const Utils = require('@deriv-com/utils'); +const { + AppIDConstants, + LocalStorageConstants, + LocalStorageUtils, + URLConstants, + WebSocketUtils, +} = require('@deriv-com/utils'); +const Analytics = require('./analytics'); export const DEFAULT_OAUTH_LOGOUT_URL = 'https://oauth.deriv.com/oauth2/sessions/logout'; export const DEFAULT_OAUTH_ORIGIN_URL = 'https://oauth.deriv.com'; +const SocketURL = { + [URLConstants.derivP2pProduction]: 'blue.derivws.com', + [URLConstants.derivP2pStaging] : 'red.derivws.com', +}; + +export const getServerInfo = () => { + const origin = window.location.origin; + const hostname = window.location.hostname; + + const existingAppId = LocalStorageUtils.getValue(LocalStorageConstants.configAppId); + const existingServerUrl = LocalStorageUtils.getValue(LocalStorageConstants.configServerURL); + // since we don't have official app_id for staging, + // we will use the red server with app_id=62019 for the staging-p2p.deriv.com for now + // to fix the login issue + if (origin === URLConstants.derivP2pStaging && (!existingAppId || !existingServerUrl)) { + LocalStorageUtils.setValue(LocalStorageConstants.configServerURL, SocketURL[origin]); + LocalStorageUtils.setValue(LocalStorageConstants.configAppId, `${AppIDConstants.domainAppId[hostname]}`); + } + + // const storedServerUrl = LocalStorageUtils.getValue(LocalStorageConstants.configServerURL); + const serverUrl = 'qa101.deriv.dev'; + + const appId = LocalStorageUtils.getValue(LocalStorageConstants.configAppId); + const lang = LocalStorageUtils.getValue(LocalStorageConstants.i18nLanguage); + + return { + appId, + lang, + serverUrl, + }; +}; + export const getOAuthLogoutUrl = () => { - const { appId, serverUrl } = Utils.WebSocketUtils.getServerInfo(); + const { appId, serverUrl } = getServerInfo(); const oauthUrl = appId && serverUrl ? `https://${serverUrl}/oauth2/sessions/logout` : DEFAULT_OAUTH_LOGOUT_URL; @@ -13,73 +52,67 @@ export const getOAuthLogoutUrl = () => { }; export const getOAuthOrigin = () => { - const { appId, serverUrl } = Utils.WebSocketUtils.getServerInfo(); + const { appId, serverUrl } = getServerInfo(); const oauthUrl = appId && serverUrl ? `https://${serverUrl}` : DEFAULT_OAUTH_ORIGIN_URL; return oauthUrl; }; -export const AuthClient = (() => { - const isOAuth2Enabled = oAuth2GrowthbookConfig => { - const { OAuth2EnabledApps, OAuth2EnabledAppsInitialised } = oAuth2GrowthbookConfig; - const appId = Utils.WebSocketUtils.getAppId(); +export const isOAuth2Enabled = () => { + const [OAuth2EnabledApps, OAuth2EnabledAppsInitialised] = Analytics.getGrowthbookFeatureValue({ + featureFlag: 'hydra_be', + }); + const appId = WebSocketUtils.getAppId(); - if (OAuth2EnabledAppsInitialised) { - const FEHydraAppIds = OAuth2EnabledApps?.length - ? OAuth2EnabledApps[OAuth2EnabledApps.length - 1]?.enabled_for ?? [] - : []; - return FEHydraAppIds.includes(+appId); - } + if (OAuth2EnabledAppsInitialised) { + const FEHydraAppIds = OAuth2EnabledApps?.length + ? OAuth2EnabledApps[OAuth2EnabledApps.length - 1]?.enabled_for ?? [] + : []; + return FEHydraAppIds.includes(+appId); + } - return false; - }; - const getLogoutHandler = (oAuth2GrowthbookConfig, onWSLogoutAndRedirect) => { - const isAuthEnabled = isOAuth2Enabled(oAuth2GrowthbookConfig); + return false; +}; - if (!isAuthEnabled) { - console.log('lol auth2 is not enabled'); - return onWSLogoutAndRedirect; - } +export const getLogoutHandler = onWSLogoutAndRedirect => { + const isAuthEnabled = isOAuth2Enabled(); + + if (!isAuthEnabled) { + return onWSLogoutAndRedirect; + } - const onMessage = async event => { - const allowedOrigin = getOAuthOrigin(); - if (allowedOrigin === event.origin) { - if (event.data === 'logout_complete') { - console.log('logout_complete calledd!'); - onWSLogoutAndRedirect(); - } + const onMessage = async event => { + const allowedOrigin = getOAuthOrigin(); + if (allowedOrigin === event.origin) { + if (event.data === 'logout_complete') { + await onWSLogoutAndRedirect(); } - }; + } + }; - window.addEventListener('message', onMessage); + window.addEventListener('message', onMessage); - const oAuth2Logout = async () => { - if (!isAuthEnabled) { - console.log('lol auth2 is not enabled'); - onWSLogoutAndRedirect(); - return; - } + const oAuth2Logout = () => { + if (!isAuthEnabled) { + onWSLogoutAndRedirect(); + return; + } - let iframe = document.getElementById('logout-iframe'); - if (!iframe) { - iframe = document.createElement('iframe'); - iframe.id = 'logout-iframe'; - iframe.style.display = 'none'; - document.body.appendChild(iframe); + let iframe = document.getElementById('logout-iframe'); + if (!iframe) { + iframe = document.createElement('iframe'); + iframe.id = 'logout-iframe'; + iframe.style.display = 'none'; + document.body.appendChild(iframe); - setTimeout(() => { - onWSLogoutAndRedirect(); - }, 10000); - } - console.log('auth2 doneee'); - }; + setTimeout(() => { + onWSLogoutAndRedirect(); + }, 10000); + } - return oAuth2Logout; + iframe.src = getOAuthLogoutUrl(); }; - return { - getLogoutHandler, - isOAuth2Enabled, - }; -})(); + return oAuth2Logout; +}; diff --git a/src/javascript/_common/base/client_base.js b/src/javascript/_common/base/client_base.js index b0e45ed7a8a..731a7597cdb 100644 --- a/src/javascript/_common/base/client_base.js +++ b/src/javascript/_common/base/client_base.js @@ -1,11 +1,12 @@ -const moment = require('moment'); -const isCryptocurrency = require('./currency_base').isCryptocurrency; -const SocketCache = require('./socket_cache'); -const localize = require('../localize').localize; -const LocalStore = require('../storage').LocalStore; -const State = require('../storage').State; -const getPropertyValue = require('../utility').getPropertyValue; -const isEmptyObject = require('../utility').isEmptyObject; +const moment = require('moment'); +const isCryptocurrency = require('./currency_base').isCryptocurrency; +const SocketCache = require('./socket_cache'); +const AuthClient = require('../auth'); +const localize = require('../localize').localize; +const LocalStore = require('../storage').LocalStore; +const State = require('../storage').State; +const getPropertyValue = require('../utility').getPropertyValue; +const isEmptyObject = require('../utility').isEmptyObject; const getAllowedLocalStorageOrigin = require('../url').getAllowedLocalStorageOrigin; const ClientBase = (() => { @@ -17,14 +18,10 @@ const ClientBase = (() => { const init = () => { current_loginid = LocalStore.get('active_loginid'); - client_object = getAllAccountsObject(); + client_object = getAllAccountsObject(); }; - const isLoggedIn = () => ( - !isEmptyObject(getAllAccountsObject()) && - get('loginid') && - get('token') - ); + const isLoggedIn = () => !isEmptyObject(getAllAccountsObject()) && get('loginid') && get('token'); const isValidLoginid = () => { if (!isLoggedIn()) return true; @@ -78,7 +75,7 @@ const ClientBase = (() => { return value; }; - const setTotalBalance = (amount, currency) => total_balance = { amount, currency }; + const setTotalBalance = (amount, currency) => (total_balance = { amount, currency }); const getTotalBalance = () => total_balance; @@ -88,19 +85,20 @@ const ClientBase = (() => { const getAccountType = (loginid = current_loginid) => { let account_type; - if (/^VR/.test(loginid)) account_type = 'virtual'; - else if (/^MF/.test(loginid)) account_type = 'financial'; + if (/^VR/.test(loginid)) account_type = 'virtual'; + else if (/^MF/.test(loginid)) account_type = 'financial'; else if (/^MLT|MX/.test(loginid)) account_type = 'gaming'; return account_type; }; const isAccountOfType = (type, loginid = current_loginid, only_enabled = false) => { - const this_type = getAccountType(loginid); - return (( - (type === 'virtual' && this_type === 'virtual') || - (type === 'real' && this_type !== 'virtual') || - type === this_type) && - (only_enabled ? !get('is_disabled', loginid) : true)); + const this_type = getAccountType(loginid); + return ( + ((type === 'virtual' && this_type === 'virtual') || + (type === 'real' && this_type !== 'virtual') || + type === this_type) && + (only_enabled ? !get('is_disabled', loginid) : true) + ); }; const getAccountOfType = (type, only_enabled) => { @@ -112,22 +110,20 @@ const ClientBase = (() => { // only considers currency of real money accounts // @param {String} type = crypto|fiat - const hasCurrencyType = (type) => { + const hasCurrencyType = type => { const loginids = getAllLoginids(); if (type === 'crypto') { // find if has crypto currency account - return loginids.find(loginid => - !get('is_virtual', loginid) && isCryptocurrency(get('currency', loginid))); + return loginids.find(loginid => !get('is_virtual', loginid) && isCryptocurrency(get('currency', loginid))); } // else find if have fiat currency account - return loginids.find(loginid => - !get('is_virtual', loginid) && !isCryptocurrency(get('currency', loginid))); + return loginids.find(loginid => !get('is_virtual', loginid) && !isCryptocurrency(get('currency', loginid))); }; const hasOnlyCurrencyType = (type = 'fiat') => { const loginids = getAllLoginids(); const real_loginid = /^(MX|MF|MLT|CR|FOG)[0-9]+$/i; - const only_real_loginids = loginids.filter((loginid) => real_loginid.test(loginid)); + const only_real_loginids = loginids.filter(loginid => real_loginid.test(loginid)); if (type === 'crypto') { return only_real_loginids.every(loginid => isCryptocurrency(get('currency', loginid))); } @@ -135,10 +131,12 @@ const ClientBase = (() => { return only_real_loginids.every(loginid => !get('currency', loginid)); } - return only_real_loginids.every(loginid => get('currency', loginid) && !isCryptocurrency(get('currency', loginid))); + return only_real_loginids.every( + loginid => get('currency', loginid) && !isCryptocurrency(get('currency', loginid)) + ); }; - const isWalletsAccount = (loginid) => { + const isWalletsAccount = loginid => { if (typeof loginid === 'undefined') { return false; } @@ -148,17 +146,18 @@ const ClientBase = (() => { } return account_object.account_category === 'wallet'; }; - - const hasWalletsAccount = () => Object.values(getAllAccountsObject()).some(account => account.account_category === 'wallet'); + + const hasWalletsAccount = () => + Object.values(getAllAccountsObject()).some(account => account.account_category === 'wallet'); const TypesMapConfig = (() => { let types_map_config; const initTypesMap = () => ({ - default : localize('Real'), + default: localize('Real'), financial: localize('Investment'), - gaming : localize('Gaming'), - virtual : localize('Virtual'), + gaming: localize('Gaming'), + virtual: localize('Virtual'), }); return { @@ -173,10 +172,10 @@ const ClientBase = (() => { const getAccountTitle = loginid => { const types_map = TypesMapConfig.get(); - return (types_map[getAccountType(loginid)] || types_map.default); + return types_map[getAccountType(loginid)] || types_map.default; }; - const responseAuthorize = (response) => { + const responseAuthorize = response => { const authorize = response.authorize; const local_currency_config = {}; const local_currencies = Object.keys(authorize.local_currencies); @@ -185,9 +184,9 @@ const ClientBase = (() => { local_currency_config.decimal_places = +authorize.local_currencies[local_currency_config.currency].fractional_digits; } - set('email', authorize.email); - set('country', authorize.country); - set('currency', authorize.currency); + set('email', authorize.email); + set('country', authorize.country); + set('currency', authorize.currency); set('is_virtual', +authorize.is_virtual); set('session_start', parseInt(moment().valueOf() / 1000)); set('landing_company_shortcode', authorize.landing_company_name); @@ -196,10 +195,10 @@ const ClientBase = (() => { updateAccountList(authorize.account_list); }; - const updateAccountList = (account_list) => { - account_list.forEach((account) => { + const updateAccountList = account_list => { + account_list.forEach(account => { set('excluded_until', account.excluded_until || '', account.loginid); - Object.keys(account).forEach((param) => { + Object.keys(account).forEach(param => { const param_to_set = param === 'country' ? 'residence' : param; const value_to_set = typeof account[param] === 'undefined' ? '' : account[param]; if (param_to_set !== 'loginid') { @@ -212,17 +211,17 @@ const ClientBase = (() => { const shouldAcceptTnc = () => { if (get('is_virtual')) return false; const website_tnc_version = State.getResponse('website_status.terms_conditions_version'); - const client_tnc_status = State.getResponse('get_settings.client_tnc_status'); + const client_tnc_status = State.getResponse('get_settings.client_tnc_status'); return typeof client_tnc_status !== 'undefined' && client_tnc_status !== website_tnc_version; }; const clearAllAccounts = () => { current_loginid = undefined; - client_object = {}; + client_object = {}; LocalStore.setObject(storage_key, client_object); }; - const setNewAccount = (options) => { + const setNewAccount = options => { if (!options.email || !options.loginid || !options.token) { return false; } @@ -230,24 +229,25 @@ const ClientBase = (() => { SocketCache.clear(); localStorage.setItem('GTM_new_account', '1'); - set('token', options.token, options.loginid); - set('email', options.email, options.loginid); + set('token', options.token, options.loginid); + set('email', options.email, options.loginid); set('is_virtual', +options.is_virtual, options.loginid); - set('loginid', options.loginid); + set('loginid', options.loginid); return true; }; const currentLandingCompany = () => { const landing_company_response = State.getResponse('landing_company') || {}; - const this_shortcode = get('landing_company_shortcode'); - const landing_company_prop = Object.keys(landing_company_response).find((key) => ( - this_shortcode === landing_company_response[key].shortcode - )); + const this_shortcode = get('landing_company_shortcode'); + const landing_company_prop = Object.keys(landing_company_response).find( + key => this_shortcode === landing_company_response[key].shortcode + ); return landing_company_response[landing_company_prop] || {}; }; - const shouldCompleteTax = () => isAccountOfType('financial') && + const shouldCompleteTax = () => + isAccountOfType('financial') && !/crs_tin_information/.test((State.getResponse('get_account_status') || {}).status); const isAuthenticationAllowed = () => { @@ -270,17 +270,17 @@ const ClientBase = (() => { gaming: { financial: { short: localize('Synthetic'), - full : is_demo ? localize('Demo Synthetic') : localize('Real Synthetic'), + full: is_demo ? localize('Demo Synthetic') : localize('Real Synthetic'), }, }, financial: { financial: { short: localize('Financial'), - full : is_demo ? localize('Demo Financial') : localize('Real Financial'), + full: is_demo ? localize('Demo Financial') : localize('Real Financial'), }, financial_stp: { short: localize('Financial STP'), - full : is_demo ? localize('Demo Financial STP') : localize('Real Financial STP'), + full: is_demo ? localize('Demo Financial STP') : localize('Real Financial STP'), }, }, }; @@ -300,26 +300,34 @@ const ClientBase = (() => { const current_landing_company = get('landing_company_shortcode'); let allowed_currencies = []; if (current_loginid) { - allowed_currencies = getLandingCompanyValue(current_loginid, landing_company_obj, 'legal_allowed_currencies'); + allowed_currencies = getLandingCompanyValue( + current_loginid, + landing_company_obj, + 'legal_allowed_currencies' + ); } // create multiple accounts only available for landing companies with legal_allowed_currencies - can_open_multi = !!(upgradeable_landing_companies.indexOf(current_landing_company) !== -1 && - (allowed_currencies && allowed_currencies.length)); + can_open_multi = !!( + upgradeable_landing_companies.indexOf(current_landing_company) !== -1 && + allowed_currencies && + allowed_currencies.length + ); // only show upgrade message to landing companies other than current const canUpgrade = (...landing_companies) => { - const result = landing_companies.filter(landing_company => ( - landing_company !== current_landing_company && - upgradeable_landing_companies.indexOf(landing_company) !== -1 - )); + const result = landing_companies.filter( + landing_company => + landing_company !== current_landing_company && + upgradeable_landing_companies.indexOf(landing_company) !== -1 + ); return result.length ? result : []; }; can_upgrade_to = canUpgrade('iom', 'svg', 'malta', 'maltainvest'); if (can_upgrade_to.length) { - type = can_upgrade_to.map( - landing_company_shortcode => landing_company_shortcode === 'maltainvest' ? 'financial' : 'real', + type = can_upgrade_to.map(landing_company_shortcode => + landing_company_shortcode === 'maltainvest' ? 'financial' : 'real' ); } } @@ -345,12 +353,11 @@ const ClientBase = (() => { } } else { const financial_company = (getPropertyValue(landing_company, 'financial_company') || {})[key] || []; - const gaming_company = (getPropertyValue(landing_company, 'gaming_company') || {})[key] || []; + const gaming_company = (getPropertyValue(landing_company, 'gaming_company') || {})[key] || []; - landing_company_object = Array.isArray(financial_company) ? - financial_company.concat(gaming_company) - : - $.extend({}, financial_company, gaming_company); + landing_company_object = Array.isArray(financial_company) + ? financial_company.concat(gaming_company) + : $.extend({}, financial_company, gaming_company); return landing_company_object; } @@ -360,15 +367,13 @@ const ClientBase = (() => { const getRiskAssessment = () => { const status = State.getResponse('get_account_status.status'); - return ( - isAccountOfType('financial') ? - /(financial_assessment|trading_experience)_not_complete/.test(status) : - /financial_assessment_not_complete/.test(status) - ); + return isAccountOfType('financial') + ? /(financial_assessment|trading_experience)_not_complete/.test(status) + : /financial_assessment_not_complete/.test(status); }; // API_V3: send a list of accounts the client can transfer to - const canTransferFunds = (account) => { + const canTransferFunds = account => { if (account) { // this specific account can be used to transfer funds to return canTransferFundsTo(account.loginid); @@ -377,13 +382,17 @@ const ClientBase = (() => { return Object.keys(client_object).some(loginid => canTransferFundsTo(loginid)); }; - const canTransferFundsTo = (to_loginid) => { - if (to_loginid === current_loginid || get('is_virtual', to_loginid) || get('is_virtual') || - get('is_disabled', to_loginid)) { + const canTransferFundsTo = to_loginid => { + if ( + to_loginid === current_loginid || + get('is_virtual', to_loginid) || + get('is_virtual') || + get('is_disabled', to_loginid) + ) { return false; } const from_currency = get('currency'); - const to_currency = get('currency', to_loginid); + const to_currency = get('currency', to_loginid); if (!from_currency || !to_currency) { return false; } @@ -392,26 +401,26 @@ const ClientBase = (() => { // these landing companies are allowed to transfer funds to each other if they have the same currency const same_cur_allowed = { maltainvest: 'malta', - malta : 'maltainvest', + malta: 'maltainvest', }; const from_landing_company = get('landing_company_shortcode'); - const to_landing_company = get('landing_company_shortcode', to_loginid); + const to_landing_company = get('landing_company_shortcode', to_loginid); // if same_cur_allowed[from_landing_company] is undefined and to_landing_company is also undefined, it will return true // so we should compare '' === undefined instead return (same_cur_allowed[from_landing_company] || '') === to_landing_company; } // or for other clients if current account is cryptocurrency it should only transfer to fiat currencies and vice versa const is_from_crypto = isCryptocurrency(from_currency); - const is_to_crypto = isCryptocurrency(to_currency); - return (is_from_crypto ? !is_to_crypto : is_to_crypto); + const is_to_crypto = isCryptocurrency(to_currency); + return is_from_crypto ? !is_to_crypto : is_to_crypto; }; - const hasSvgAccount = () => !!(getAllLoginids().find(loginid => /^CR/.test(loginid))); + const hasSvgAccount = () => !!getAllLoginids().find(loginid => /^CR/.test(loginid)); const canChangeCurrency = (statement, mt5_login_list, is_current = true) => { - const currency = get('currency'); - const has_no_mt5 = !mt5_login_list || !mt5_login_list.length; - const has_no_transaction = (statement.count === 0 && statement.transactions.length === 0); + const currency = get('currency'); + const has_no_mt5 = !mt5_login_list || !mt5_login_list.length; + const has_no_transaction = statement.count === 0 && statement.transactions.length === 0; const has_account_criteria = has_no_transaction && has_no_mt5; // Current API requirements for currently logged-in user successfully changing their account's currency: @@ -419,11 +428,14 @@ const ClientBase = (() => { // 2. User must not have any MT5 account // 3. Not be a crypto account // 4. Not be a virtual account - return is_current ? currency && !get('is_virtual') && has_account_criteria && !isCryptocurrency(currency) : has_account_criteria; + return is_current + ? currency && !get('is_virtual') && has_account_criteria && !isCryptocurrency(currency) + : has_account_criteria; }; const isMF = () => { - const landing_company_shortcode = get('landing_company_shortcode') || State.getResponse('landing_company.gaming_company.shortcode'); + const landing_company_shortcode = + get('landing_company_shortcode') || State.getResponse('landing_company.gaming_company.shortcode'); return landing_company_shortcode === 'maltainvest'; }; @@ -460,13 +472,19 @@ const ClientBase = (() => { const financial_restricted_countries = financial_company_shortcode === 'svg' && !gaming_company_shortcode; const CFDs_restricted_countries = gaming_company_shortcode === 'svg' && !financial_company_shortcode; - + const restricted_countries = financial_company_shortcode === 'svg' || (gaming_company_shortcode === 'svg' && financial_company_shortcode !== 'maltainvest'); - + const high_risk = financial_company_shortcode === 'svg' && gaming_company_shortcode === 'svg'; - return high_risk || restricted_countries || risk_classification === 'high' || financial_restricted_countries || CFDs_restricted_countries; + return ( + high_risk || + restricted_countries || + risk_classification === 'high' || + financial_restricted_countries || + CFDs_restricted_countries + ); } return false; @@ -481,52 +499,72 @@ const ClientBase = (() => { if (landing_companies.gaming_company) { gaming_company_shortcode = landing_companies.gaming_company.shortcode; } - const low_risk_landing_company = financial_company_shortcode === 'maltainvest' && gaming_company_shortcode === 'svg'; - return low_risk_landing_company || (upgradeable_landing_companies.include('svg') && upgradeable_landing_companies.include('maltainvest')); + const low_risk_landing_company = + financial_company_shortcode === 'maltainvest' && gaming_company_shortcode === 'svg'; + return ( + low_risk_landing_company || + (upgradeable_landing_companies.include('svg') && upgradeable_landing_companies.include('maltainvest')) + ); } return false; }; const syncWithDerivApp = (active_loginid, client_accounts) => { + const isHydraEnabled = AuthClient.isOAuth2Enabled(); + + if (isHydraEnabled) return; + const iframe_window = document.getElementById('localstorage-sync'); const origin = getAllowedLocalStorageOrigin(); if (!iframe_window || !origin) return; - if (document.readyState === 'complete'){ + if (document.readyState === 'complete') { iframe_window.onload = () => { // Keep client.accounts in sync (in case user wasn't logged in). if (iframe_window.src === `${origin}/localstorage-sync.html`) { - iframe_window.contentWindow.postMessage({ - key : 'client.accounts', - value: JSON.stringify(client_accounts), - }, origin); - iframe_window.contentWindow.postMessage({ - key : 'active_loginid', - value: active_loginid, - }, origin); + iframe_window.contentWindow.postMessage( + { + key: 'client.accounts', + value: JSON.stringify(client_accounts), + }, + origin + ); + iframe_window.contentWindow.postMessage( + { + key: 'active_loginid', + value: active_loginid, + }, + origin + ); } }; return; } - if (!has_readystate_listener){ + if (!has_readystate_listener) { has_readystate_listener = true; document.addEventListener('readystatechange', () => { iframe_window.onload = () => { // Keep client.accounts in sync (in case user wasn't logged in). if (iframe_window.src === `${origin}/localstorage-sync.html`) { - iframe_window.contentWindow.postMessage({ - key : 'client.accounts', - value: JSON.stringify(client_accounts), - }, origin); - iframe_window.contentWindow.postMessage({ - key : 'active_loginid', - value: active_loginid, - }, origin); + iframe_window.contentWindow.postMessage( + { + key: 'client.accounts', + value: JSON.stringify(client_accounts), + }, + origin + ); + iframe_window.contentWindow.postMessage( + { + key: 'active_loginid', + value: active_loginid, + }, + origin + ); } }; }); diff --git a/src/javascript/app/base/header.js b/src/javascript/app/base/header.js index 567a7d79971..dd6f3073fb2 100644 --- a/src/javascript/app/base/header.js +++ b/src/javascript/app/base/header.js @@ -1,8 +1,7 @@ // const BinaryPjax = require('./binary_pjax'); -const AuthClient = require('../../_common/auth'); -const Analytics = require('../../_common/analytics'); const Client = require('./client'); const BinarySocket = require('./socket'); +const AuthClient = require('../../_common/auth'); const showHidePulser = require('../common/account_opening').showHidePulser; const updateTotal = require('../pages/user/update_total'); const isAuthenticationAllowed = require('../../_common/base/client_base').isAuthenticationAllowed; @@ -25,6 +24,7 @@ const template = require('../../_common/utility').template; const Language = require('../../_common/language'); const mapCurrencyName = require('../../_common/base/currency_base').mapCurrencyName; const isEuCountry = require('../common/country_base').isEuCountry; +const DerivIFrame = require('../pages/deriv_iframe.jsx'); const header_icon_base_path = '/images/pages/header/'; const wallet_header_icon_base_path = '/images/pages/header/wallets/'; @@ -46,6 +46,7 @@ const Header = (() => { populateWalletAccounts(); bindSvg(); switchHeaders(); + DerivIFrame.init(); BinarySocket.wait('authorize','landing_company').then(() => { setHeaderUrls(); bindPlatform(); @@ -626,11 +627,8 @@ const Header = (() => { Login.redirectToLogin(); }; - const logoutOnClick = () => { - const value = Analytics.getGrowthbookFeatureValue({ - featureFlag: 'hydra_be', - }); - const onLogoutWithHydra = AuthClient.AuthClient.getLogoutHandler(value, () => Client.sendLogoutRequest()); + const logoutOnClick = async () => { + const onLogoutWithHydra = await AuthClient.getLogoutHandler(() => Client.sendLogoutRequest()); onLogoutWithHydra(); }; diff --git a/src/javascript/app/base/logged_in.js b/src/javascript/app/base/logged_in.js index 0c081b0e3b2..2a250d20dcd 100644 --- a/src/javascript/app/base/logged_in.js +++ b/src/javascript/app/base/logged_in.js @@ -12,6 +12,7 @@ const removeCookies = require('../../_common/storage').removeCookies; const paramsHash = require('../../_common/url').paramsHash; const urlFor = require('../../_common/url').urlFor; const getPropertyValue = require('../../_common/utility').getPropertyValue; +const DerivIFrame = require('../pages/deriv_iframe.jsx'); const LoggedInHandler = (() => { const onLoad = () => { @@ -19,6 +20,7 @@ const LoggedInHandler = (() => { parent.window.is_logging_in = 1; // this flag is used in base.js to prevent auto-reloading this page let redirect_url; const params = paramsHash(window.location.href); + DerivIFrame.init(); BinarySocket.send({ authorize: params.token1 }).then((response) => { const account_list = getPropertyValue(response, ['authorize', 'account_list']); if (isStorageSupported(localStorage) && isStorageSupported(sessionStorage) && account_list) { diff --git a/src/javascript/app/pages/deriv_iframe.jsx b/src/javascript/app/pages/deriv_iframe.jsx new file mode 100644 index 00000000000..82781c14f79 --- /dev/null +++ b/src/javascript/app/pages/deriv_iframe.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { isOAuth2Enabled } from '../../_common/auth'; + +const DerivIFrame = () => ( +