From 38faa6e49c1c1e8b6848ed5ad46aad67b2ddf428 Mon Sep 17 00:00:00 2001 From: BilalMir135 Date: Wed, 30 Aug 2023 00:03:22 +0500 Subject: [PATCH 1/5] feat(widget): add campaign widget --- src/BaseAnalytics/index.js | 4 ++ src/WalletConnection/index.js | 4 +- src/Widget/index.js | 87 +++++++++++++++++++++++++++++++++++ src/constants.js | 12 +++++ src/core/index.js | 4 ++ src/index.js | 2 + types/index.d.ts | 6 ++- 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/Widget/index.js diff --git a/src/BaseAnalytics/index.js b/src/BaseAnalytics/index.js index c26ace7..46b1f4a 100644 --- a/src/BaseAnalytics/index.js +++ b/src/BaseAnalytics/index.js @@ -7,6 +7,8 @@ import { getConfig } from '../utils/helpers'; import { log } from '../utils/logs'; import Request from '../utils/request'; +import WidgetController from '../Widget'; + /** * @typedef Config * @type {object} @@ -51,6 +53,8 @@ class BaseAnalytics { testENV: this.testENV, store: this.store, }); + + this.widgetController = WidgetController; } /** diff --git a/src/WalletConnection/index.js b/src/WalletConnection/index.js index daff77a..a16e0bb 100644 --- a/src/WalletConnection/index.js +++ b/src/WalletConnection/index.js @@ -1,6 +1,6 @@ import invariant from 'tiny-invariant'; -import { LOG, WALLET_TYPE, EVENTS, TRACKING_EVENTS } from '../constants'; +import { LOG, WALLET_TYPE, EVENTS, TRACKING_EVENTS, WIDGET_SEND_EVENTS } from '../constants'; import BaseAnalytics from '../BaseAnalytics'; import { txnRejected } from './utils'; import { JSON_Formatter, normalizeChainId } from '../utils/formatting'; @@ -340,6 +340,8 @@ class WalletConnection extends BaseAnalytics { const properties = { walletType }; this.trackEvent({ event: TRACKING_EVENTS.WALLET_CONNECTION, properties, logMessage: 'Wallet connect' }); + + this.widgetController.postMessage(WIDGET_SEND_EVENTS.WALLET_CONNECT, { address: account?.toLowerCase(), chain }); } } } diff --git a/src/Widget/index.js b/src/Widget/index.js new file mode 100644 index 0000000..8f7cfa5 --- /dev/null +++ b/src/Widget/index.js @@ -0,0 +1,87 @@ +import { WIDGET_ENDPOINT, WIDGET_RECEIVE_EVENTS } from '../constants'; +import { addEvent } from '../utils/helpers'; + +const iframwStyles = { + display: 'none', + position: 'fixed', + bottom: '3%', + right: '3%', + borderRadius: '0', + border: 'none', + width: '350px', + zIndex: '2147483647', +}; + +function applyStyles(elemment) { + for (const [cssProperty, value] of Object.entries(iframwStyles)) { + elemment.style[cssProperty] = value; + } +} + +class WidgetController { + constructor() { + this.setUserDefinedOnClick = this.setUserDefinedOnClick.bind(this); + } + + init(appKey) { + const createIframe = () => { + const iframe = document.createElement('iframe'); + iframe.src = WIDGET_ENDPOINT + `/?appKey=${appKey}`; + iframe.title = 'Spock Widget'; + iframe.id = 'spock-widget'; + iframe.dataset.spockIframeLabel = new URL(WIDGET_ENDPOINT).host; + applyStyles(iframe); + document.body.appendChild(iframe); + return iframe; + }; + + this.iframe = createIframe(); + + addEvent(window, 'message', this.eventHandler.bind(this)); + } + + eventHandler(event) { + const { origin, data } = event; + if (origin === WIDGET_ENDPOINT) { + switch (data?.message) { + case WIDGET_RECEIVE_EVENTS.SHOW_POPUP: + this.show(data?.body?.height); + break; + case WIDGET_RECEIVE_EVENTS.HIDE_POPUP: + this.hide(); + break; + case WIDGET_RECEIVE_EVENTS.BUTTON_CLICK: + this.hide(); + if (data?.body?.redirectUrl) { + window.location.href = data?.body?.redirectUrl; + } else if (this.userDefinedClickMethod) { + this.userDefinedClickMethod(data?.body); + } + break; + default: + break; + } + } + } + + postMessage(message, body) { + if (this.iframe && this.iframe.contentWindow) { + this.iframe.contentWindow.postMessage({ message, body }, WIDGET_ENDPOINT); + } + } + + show(height) { + this.iframe.style.height = height; + this.iframe.style.display = 'block'; + } + + hide() { + this.iframe.style.display = 'none'; + } + + setUserDefinedOnClick(method) { + this.userDefinedClickMethod = method; + } +} + +export default new WidgetController(); diff --git a/src/constants.js b/src/constants.js index a4ce0d1..9f25845 100644 --- a/src/constants.js +++ b/src/constants.js @@ -8,6 +8,7 @@ export const SERVER_ENDPOINT = 'https://ingest.spockanalytics.xyz'; */ export const TEST_SERVER_ENDPOINT = 'https://ingest-dev.spockanalytics.xyz'; +export const WIDGET_ENDPOINT = 'http://localhost:3000'; /** * alias to make storage keys unqiue */ @@ -31,11 +32,22 @@ export const withAlias = (key) => `${ALIAS}_${key}`; export const DATA_POINTS = { BROWSER_PROFILE: 'browser_profile', DEMOGRAPHICS: 'demographics', + ENGAGE: 'engage', NAVIGATION: 'navigation', UTM_PARAMS: 'utm_params', WEB3: 'web3', }; +export const WIDGET_SEND_EVENTS = { + WALLET_CONNECT: withAlias('wallet_connect'), +}; + +export const WIDGET_RECEIVE_EVENTS = { + SHOW_POPUP: withAlias('show_popup'), + HIDE_POPUP: withAlias('hide_popup'), + BUTTON_CLICK: withAlias('button_click'), +}; + /** * default values for sdk configuration * DATA_POINTS - data that is allowed to track diff --git a/src/core/index.js b/src/core/index.js index c7cbddf..74ecb88 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,10 @@ class Web3Analytics extends BaseAnalytics { this.log(LOG.INFO, 'Web3 Analytics initialized'); this.userConsent(); + if (this.dataPoints[DATA_POINTS.ENGAGE]) { + this.widgetController.init(this.appKey); + } + this.session.trackSession(); await this.userInfo.getUserInfo(); this.wallet.initialize(); diff --git a/src/index.js b/src/index.js index d784508..cf6e19c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import Web3AnalyticsClass from './core'; import { EVENTS } from './constants'; import { addEvent } from './utils/helpers'; +import WidgetController from './Widget'; function overrideLocalStorage() { Storage.prototype._setItem = Storage.prototype.setItem; @@ -43,6 +44,7 @@ Web3Analytics.init = function (config) { Web3Analytics.trackPageView = web3AnalyticsInstance.trackPageView; Web3Analytics.trackWalletConnection = web3AnalyticsInstance.wallet.trackWalletConnection; Web3Analytics.walletProvider = web3AnalyticsInstance.wallet.walletProvider; + Web3Analytics.widgetOnClick = WidgetController.setUserDefinedOnClick; }; export default Web3Analytics; diff --git a/types/index.d.ts b/types/index.d.ts index c8e63fe..b87de25 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,7 +1,9 @@ +declare type WidgetOnClickMethod = (params: { campaignId: string }) => void; + declare namespace Web3Analytics { export function init(config: { appKey: string; - dataPoints?: ('browser_profile' | 'demographics' | 'navigation' | 'utm_params' | 'web3')[]; + dataPoints?: ('browser_profile' | 'demographics' | 'engage' | 'navigation' | 'utm_params' | 'web3')[]; debug?: boolean; inactivityTimeout?: number; optOut?: boolean; @@ -20,6 +22,8 @@ declare namespace Web3Analytics { export function trackWalletConnection(walletType: string, account: string, chainId: number): void; export function walletProvider(provider: any): void; + + export function widgetOnClick(method: WidgetOnClickMethod): void; } export default Web3Analytics; From 42301ad69ff13701fc097a27fd6ed3248592ed34 Mon Sep 17 00:00:00 2001 From: BilalMir135 Date: Sun, 3 Sep 2023 00:10:12 +0500 Subject: [PATCH 2/5] feat(campaign): add all events support in widget --- src/AnalyticsStorage/index.js | 2 ++ src/BaseAnalytics/index.js | 22 +++++++++++++++++++++- src/Session/index.js | 2 ++ src/WalletConnection/index.js | 8 ++++---- src/Widget/index.js | 20 ++++++++++---------- src/constants.js | 16 ++++++---------- 6 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/AnalyticsStorage/index.js b/src/AnalyticsStorage/index.js index 2a43d44..8cdb16e 100644 --- a/src/AnalyticsStorage/index.js +++ b/src/AnalyticsStorage/index.js @@ -31,6 +31,7 @@ import { notUndefined } from '../utils/validators'; * @property {number} txnReject - total txn rejected in current session * @property {number} txnSubmit - total txn submitted txn in current session * @property {UserInfo|undefined} userInfo - user metadata + * @property {()=>number} sessionDuration - method to calculate current session duration */ /** @type {AnalyticsStore} */ @@ -48,6 +49,7 @@ const initialState = { txnReject: 0, txnSubmit: 0, userInfo: undefined, + sessionDuration: () => 0, }; class AnalyticsStorage { diff --git a/src/BaseAnalytics/index.js b/src/BaseAnalytics/index.js index 46b1f4a..61265f9 100644 --- a/src/BaseAnalytics/index.js +++ b/src/BaseAnalytics/index.js @@ -1,5 +1,5 @@ import AnalyticsStorage from '../AnalyticsStorage'; -import { DEFAULT_CONFIG, LOG, TRACKING_EVENTS, STORAGE, UTM_KEYS, DATA_POINTS } from '../constants'; +import { DEFAULT_CONFIG, LOG, TRACKING_EVENTS, STORAGE, UTM_KEYS, DATA_POINTS, withAlias } from '../constants'; import { cheapGuid, getQueryParams, parseFlowProperties, transformUTMKey } from './utils'; import { setCookie } from '../utils/cookies'; import { JSON_Formatter } from '../utils/formatting'; @@ -121,6 +121,26 @@ class BaseAnalytics { this.dispatch({ trackingQueue: [...this.store.trackingQueue, { event, data }] }); } + if (this.dataPoints[DATA_POINTS.ENGAGE]) { + const { ip, flow, optOut, initialized, txnReject, txnSubmit, sessionDuration } = this.store; + this.widgetController.postMessage(withAlias(event.replace(/-/g, '_')), { + ...data, + store: { + duration: typeof sessionDuration === 'function' ? sessionDuration() : 0, + flow, + initialized, + ip, + optOut, + txnReject, + txnSubmit, + }, + }); + } + + // if (event === TRACKING_EVENTS.WALLET_CONNECTION) { + // this.widgetController.postMessage(WIDGET_SEND_EVENTS.WALLET_CONNECT, data); + // } + logMessage && this.log(LOG.INFO, logMessage, data); } diff --git a/src/Session/index.js b/src/Session/index.js index bc27c4f..194a392 100644 --- a/src/Session/index.js +++ b/src/Session/index.js @@ -19,6 +19,8 @@ class Session extends BaseAnalytics { this.hiddenTime = 0; this.totalHiddenTime = 0; this.hidden = 'hidden'; + + this.dispatch({ sessionDuration: this.sessionDuration.bind(this) }); } trackSession() { diff --git a/src/WalletConnection/index.js b/src/WalletConnection/index.js index a16e0bb..96681f1 100644 --- a/src/WalletConnection/index.js +++ b/src/WalletConnection/index.js @@ -1,6 +1,6 @@ import invariant from 'tiny-invariant'; -import { LOG, WALLET_TYPE, EVENTS, TRACKING_EVENTS, WIDGET_SEND_EVENTS } from '../constants'; +import { LOG, WALLET_TYPE, EVENTS, TRACKING_EVENTS } from '../constants'; import BaseAnalytics from '../BaseAnalytics'; import { txnRejected } from './utils'; import { JSON_Formatter, normalizeChainId } from '../utils/formatting'; @@ -89,13 +89,13 @@ class WalletConnection extends BaseAnalytics { logTransaction(status, txnObj, txnHash) { if (status === 'rejected') { const properties = { ...txnObj, status: 0 }; - this.trackEvent({ event: TRACKING_EVENTS.TRANSACTION, properties, logMessage: 'Transaction rejected' }); this.dispatch({ txnReject: this.store.txnReject + 1 }); + this.trackEvent({ event: TRACKING_EVENTS.TRANSACTION, properties, logMessage: 'Transaction rejected' }); } else if (status === 'submitted' && this.cacheTxnHash !== txnHash) { const properties = { ...txnObj, hash: txnHash, status: 1 }; + this.dispatch({ txnSubmit: this.store.txnSubmit + 1 }); this.trackEvent({ event: TRACKING_EVENTS.TRANSACTION, properties, logMessage: 'Transaction submitted' }); this.cacheTxnHash = txnHash; - this.dispatch({ txnSubmit: this.store.txnSubmit + 1 }); } } @@ -341,7 +341,7 @@ class WalletConnection extends BaseAnalytics { this.trackEvent({ event: TRACKING_EVENTS.WALLET_CONNECTION, properties, logMessage: 'Wallet connect' }); - this.widgetController.postMessage(WIDGET_SEND_EVENTS.WALLET_CONNECT, { address: account?.toLowerCase(), chain }); + // this.widgetController.postMessage(WIDGET_SEND_EVENTS.WALLET_CONNECT, { address: account?.toLowerCase(), chain }); } } } diff --git a/src/Widget/index.js b/src/Widget/index.js index 8f7cfa5..b6aa58d 100644 --- a/src/Widget/index.js +++ b/src/Widget/index.js @@ -4,16 +4,16 @@ import { addEvent } from '../utils/helpers'; const iframwStyles = { display: 'none', position: 'fixed', - bottom: '3%', - right: '3%', + // bottom: '3%', + // right: '3%', borderRadius: '0', border: 'none', - width: '350px', + // width: '350px', zIndex: '2147483647', }; -function applyStyles(elemment) { - for (const [cssProperty, value] of Object.entries(iframwStyles)) { +function applyStyles(elemment, styles) { + for (const [cssProperty, value] of Object.entries(styles)) { elemment.style[cssProperty] = value; } } @@ -30,7 +30,7 @@ class WidgetController { iframe.title = 'Spock Widget'; iframe.id = 'spock-widget'; iframe.dataset.spockIframeLabel = new URL(WIDGET_ENDPOINT).host; - applyStyles(iframe); + applyStyles(iframe, iframwStyles); document.body.appendChild(iframe); return iframe; }; @@ -45,7 +45,7 @@ class WidgetController { if (origin === WIDGET_ENDPOINT) { switch (data?.message) { case WIDGET_RECEIVE_EVENTS.SHOW_POPUP: - this.show(data?.body?.height); + this.show(data?.body); break; case WIDGET_RECEIVE_EVENTS.HIDE_POPUP: this.hide(); @@ -66,13 +66,13 @@ class WidgetController { postMessage(message, body) { if (this.iframe && this.iframe.contentWindow) { + console.log({ message, body }); this.iframe.contentWindow.postMessage({ message, body }, WIDGET_ENDPOINT); } } - show(height) { - this.iframe.style.height = height; - this.iframe.style.display = 'block'; + show(styles) { + applyStyles(this.iframe, styles); } hide() { diff --git a/src/constants.js b/src/constants.js index 9f25845..6efed7f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -38,16 +38,6 @@ export const DATA_POINTS = { WEB3: 'web3', }; -export const WIDGET_SEND_EVENTS = { - WALLET_CONNECT: withAlias('wallet_connect'), -}; - -export const WIDGET_RECEIVE_EVENTS = { - SHOW_POPUP: withAlias('show_popup'), - HIDE_POPUP: withAlias('hide_popup'), - BUTTON_CLICK: withAlias('button_click'), -}; - /** * default values for sdk configuration * DATA_POINTS - data that is allowed to track @@ -162,6 +152,12 @@ export const TRACKING_EVENTS = { WALLET_CONNECTION: 'wallet-connect', }; +export const WIDGET_RECEIVE_EVENTS = { + SHOW_POPUP: withAlias('show_popup'), + HIDE_POPUP: withAlias('hide_popup'), + BUTTON_CLICK: withAlias('button_click'), +}; + /** * constant for representing empty string */ From 8016e665683797a79ec752d745f2448e6eec589c Mon Sep 17 00:00:00 2001 From: BilalMir135 Date: Fri, 8 Sep 2023 19:37:07 +0500 Subject: [PATCH 3/5] fix(widget): widget rendering on different devices --- src/BaseAnalytics/index.js | 7 +++---- src/Widget/index.js | 15 +++++++++++---- src/constants.js | 6 +++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/BaseAnalytics/index.js b/src/BaseAnalytics/index.js index 61265f9..2332685 100644 --- a/src/BaseAnalytics/index.js +++ b/src/BaseAnalytics/index.js @@ -134,13 +134,12 @@ class BaseAnalytics { txnReject, txnSubmit, }, + browserProps: { + innerWidth: window.innerWidth, + }, }); } - // if (event === TRACKING_EVENTS.WALLET_CONNECTION) { - // this.widgetController.postMessage(WIDGET_SEND_EVENTS.WALLET_CONNECT, data); - // } - logMessage && this.log(LOG.INFO, logMessage, data); } diff --git a/src/Widget/index.js b/src/Widget/index.js index b6aa58d..2f29d6a 100644 --- a/src/Widget/index.js +++ b/src/Widget/index.js @@ -4,11 +4,8 @@ import { addEvent } from '../utils/helpers'; const iframwStyles = { display: 'none', position: 'fixed', - // bottom: '3%', - // right: '3%', borderRadius: '0', border: 'none', - // width: '350px', zIndex: '2147483647', }; @@ -64,8 +61,18 @@ class WidgetController { } } - postMessage(message, body) { + async documentLoaded(counter) { + if (document.readyState != 'complete') { + await new Promise((resolve) => setTimeout(resolve, 5 * 1000)); + //counter for protecting from infinite recursion + counter < 5 && (await this.documentLoaded(counter + 1)); + } + } + + async postMessage(message, body) { if (this.iframe && this.iframe.contentWindow) { + //wait util document loading is complete + await this.documentLoaded(0); console.log({ message, body }); this.iframe.contentWindow.postMessage({ message, body }, WIDGET_ENDPOINT); } diff --git a/src/constants.js b/src/constants.js index 6efed7f..4e2b441 100644 --- a/src/constants.js +++ b/src/constants.js @@ -8,7 +8,11 @@ export const SERVER_ENDPOINT = 'https://ingest.spockanalytics.xyz'; */ export const TEST_SERVER_ENDPOINT = 'https://ingest-dev.spockanalytics.xyz'; -export const WIDGET_ENDPOINT = 'http://localhost:3000'; +/** + * spock-widget hosting url + */ +export const WIDGET_ENDPOINT = 'https://widget.spockanalytics.xyz'; + /** * alias to make storage keys unqiue */ From 404c8792a4ba16f378974cfa7f254ada99f8735b Mon Sep 17 00:00:00 2001 From: BilalMir135 Date: Fri, 8 Sep 2023 21:02:41 +0500 Subject: [PATCH 4/5] docs(version): add beta version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85e985b..f5c2c68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "analytics-web3", - "version": "1.1.0", + "version": "1.2.0-beta.0", "description": "Spock analytics SDK analytics-web3 is a js module to collect and log all the data and events of DApp for analytics.", "main": "dist/analytics-web3.min.js", "license": "UNLICENSED", From 69b885e23b1010cd79d206089005cba9c89acba7 Mon Sep 17 00:00:00 2001 From: BilalMir135 Date: Sun, 10 Sep 2023 01:48:18 +0500 Subject: [PATCH 5/5] chore(wdiget): add comments on widget constants --- package.json | 2 +- src/Widget/index.js | 1 - src/constants.js | 6 ++++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f5c2c68..087e5a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "analytics-web3", - "version": "1.2.0-beta.0", + "version": "1.3.0", "description": "Spock analytics SDK analytics-web3 is a js module to collect and log all the data and events of DApp for analytics.", "main": "dist/analytics-web3.min.js", "license": "UNLICENSED", diff --git a/src/Widget/index.js b/src/Widget/index.js index 2f29d6a..dd984b0 100644 --- a/src/Widget/index.js +++ b/src/Widget/index.js @@ -73,7 +73,6 @@ class WidgetController { if (this.iframe && this.iframe.contentWindow) { //wait util document loading is complete await this.documentLoaded(0); - console.log({ message, body }); this.iframe.contentWindow.postMessage({ message, body }, WIDGET_ENDPOINT); } } diff --git a/src/constants.js b/src/constants.js index 4e2b441..0deb3fd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -156,6 +156,12 @@ export const TRACKING_EVENTS = { WALLET_CONNECTION: 'wallet-connect', }; +/** + * events received from widget + * SHOW_POPUP - show the popup to user + * HIDE_POPUP - close popup + * BUTTON_CLICK - onclick action + */ export const WIDGET_RECEIVE_EVENTS = { SHOW_POPUP: withAlias('show_popup'), HIDE_POPUP: withAlias('hide_popup'),