From dbd1720aeb99e75a1fe5a171895576af22eda9f9 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sat, 26 Sep 2020 22:33:20 +0530 Subject: [PATCH] chore: Add a check for data diff in setUser call (#1272) --- app/javascript/packs/sdk.js | 57 ++++++++++++++++++++++---- app/javascript/specs/packs/sdk.spec.js | 44 ++++++++++++++++++++ package.json | 1 + yarn.lock | 21 +++++++++- 4 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 app/javascript/specs/packs/sdk.spec.js diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index 429a2ddf9..08be2bacf 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -1,6 +1,31 @@ import Cookies from 'js-cookie'; import { IFrameHelper } from '../sdk/IFrameHelper'; import { getBubbleView } from '../sdk/bubbleHelpers'; +import md5 from 'md5'; + +const ALLOWED_LIST_OF_SET_USER_ATTRIBUTES = ['avatar_url', 'email', 'name']; + +export const getUserCookieName = () => { + const SET_USER_COOKIE_PREFIX = 'cw_user_'; + const { websiteToken: websiteIdentifier } = window.$chatwoot; + return `${SET_USER_COOKIE_PREFIX}${websiteIdentifier}`; +}; + +export const getUserString = ({ identifier = '', user }) => { + const userStringWithSortedKeys = ALLOWED_LIST_OF_SET_USER_ATTRIBUTES.reduce( + (acc, key) => `${acc}${key}${user[key] || ''}`, + '' + ); + return `${userStringWithSortedKeys}identifier${identifier}`; +}; + +const computeHashForUserData = (...args) => md5(getUserString(...args)); + +export const hasUserKeys = user => + ALLOWED_LIST_OF_SET_USER_ATTRIBUTES.reduce( + (acc, key) => acc || !!user[key], + false + ); const runSDK = ({ baseUrl, websiteToken }) => { const chatwootSettings = window.chatwootSettings || {}; @@ -21,16 +46,30 @@ const runSDK = ({ baseUrl, websiteToken }) => { }, setUser(identifier, user) { - if (typeof identifier === 'string' || typeof identifier === 'number') { - window.$chatwoot.identifier = identifier; - window.$chatwoot.user = user || {}; - IFrameHelper.sendMessage('set-user', { - identifier, - user: window.$chatwoot.user, - }); - } else { + if (typeof identifier !== 'string' && typeof identifier !== 'number') { throw new Error('Identifier should be a string or a number'); } + + if (!hasUserKeys()) { + throw new Error( + 'User object should have one of the keys [avatar_url, email, name]' + ); + } + + const userCookieName = getUserCookieName(); + const existingCookieValue = Cookies.get(userCookieName); + const hashToBeStored = computeHashForUserData({ identifier, user }); + if (hashToBeStored === existingCookieValue) { + return; + } + + window.$chatwoot.identifier = identifier; + window.$chatwoot.user = user; + IFrameHelper.sendMessage('set-user', { identifier, user }); + Cookies.set(userCookieName, hashToBeStored, { + expires: 365, + sameSite: 'Lax', + }); }, setCustomAttributes(customAttributes = {}) { @@ -69,6 +108,8 @@ const runSDK = ({ baseUrl, websiteToken }) => { } Cookies.remove('cw_conversation'); + Cookies.remove(getUserCookieName()); + const iframe = IFrameHelper.getAppFrame(); iframe.src = IFrameHelper.getUrl({ baseUrl: window.$chatwoot.baseUrl, diff --git a/app/javascript/specs/packs/sdk.spec.js b/app/javascript/specs/packs/sdk.spec.js new file mode 100644 index 000000000..62e267e43 --- /dev/null +++ b/app/javascript/specs/packs/sdk.spec.js @@ -0,0 +1,44 @@ +import { getUserCookieName, getUserString, hasUserKeys } from '../../packs/sdk'; + +describe('#getUserCookieName', () => { + it('returns correct cookie name', () => { + global.$chatwoot = { websiteToken: '123456' }; + expect(getUserCookieName()).toBe('cw_user_123456'); + }); +}); + +describe('#getUserString', () => { + it('returns correct user string', () => { + expect( + getUserString({ + user: { + name: 'Pranav', + email: 'pranav@example.com', + avatar_url: 'https://images.chatwoot.com/placeholder', + }, + identifier: '12345', + }) + ).toBe( + 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnamePranavidentifier12345' + ); + + expect( + getUserString({ + user: { + email: 'pranav@example.com', + avatar_url: 'https://images.chatwoot.com/placeholder', + }, + }) + ).toBe( + 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnameidentifier' + ); + }); +}); + +describe('#hasUserKeys', () => { + it('checks whether the allowed list of keys are present', () => { + expect(hasUserKeys({})).toBe(false); + expect(hasUserKeys({ randomKey: 'randomValue' })).toBe(false); + expect(hasUserKeys({ avatar_url: 'randomValue' })).toBe(true); + }); +}); diff --git a/package.json b/package.json index a6a26ceb4..f8daa6fc6 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "ionicons": "~2.0.1", "js-cookie": "^2.2.1", "lodash.groupby": "^4.6.0", + "md5": "^2.3.0", "query-string": "5", "spinkit": "~1.2.5", "tween.js": "~16.6.0", diff --git a/yarn.lock b/yarn.lock index 48cb5462b..6a5710510 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2644,6 +2644,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chart.js@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.5.0.tgz#fe6e751a893769f56e72bee5ad91207e1c592957" @@ -3200,6 +3205,11 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -5560,7 +5570,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -6931,6 +6941,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"