Skip to content

Commit ff2f051

Browse files
committed
Analytics: add sdk_initialization events
1 parent 7d66e14 commit ff2f051

File tree

7 files changed

+269
-132
lines changed

7 files changed

+269
-132
lines changed

packages/modal/src/modalManager.ts

Lines changed: 93 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AuthConnectionConfigItem, serializeError } from "@web3auth/auth";
22
import {
3-
Analytics,
3+
ANALYTICS_EVENTS,
4+
ANALYTICS_SDK_TYPE,
45
type AUTH_CONNECTION_TYPE,
56
type AuthLoginParams,
67
type BaseConnectorConfig,
@@ -23,6 +24,7 @@ import {
2324
WALLET_CONNECTORS,
2425
WalletInitializationError,
2526
type WalletRegistry,
27+
Web3AuthError,
2628
Web3AuthNoModal,
2729
withAbort,
2830
} from "@web3auth/no-modal";
@@ -62,64 +64,105 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
6264
}
6365

6466
public async initModal(options?: { signal?: AbortSignal }): Promise<void> {
65-
const { signal } = options || {};
66-
67-
super.checkInitRequirements();
68-
// get project config and wallet registry
69-
const { projectConfig, walletRegistry } = await this.getProjectAndWalletConfig();
70-
71-
// init config
72-
this.initUIConfig(projectConfig);
73-
super.initAccountAbstractionConfig(projectConfig);
74-
super.initChainsConfig(projectConfig);
75-
super.initCachedConnectorAndChainId();
76-
7767
// init analytics
78-
Analytics.init();
79-
Analytics.identify(window.location.hostname, {
68+
const startTime = Date.now();
69+
this.analytics.init();
70+
this.analytics.identify(this.options.clientId, {
8071
web3auth_client_id: this.options.clientId,
8172
web3auth_network: this.options.web3AuthNetwork,
8273
});
74+
this.analytics.setGlobalProperties({
75+
dapp_url: window.location.origin,
76+
sdk_type: ANALYTICS_SDK_TYPE.WEB_MODAL,
77+
});
78+
let trackData: Record<string, unknown> = {};
8379

84-
// init login modal
85-
const { filteredWalletRegistry, disabledExternalWallets } = this.filterWalletRegistry(walletRegistry, projectConfig);
86-
this.loginModal = new LoginModal(
87-
{
88-
...this.options.uiConfig,
89-
connectorListener: this,
90-
web3authClientId: this.options.clientId,
91-
web3authNetwork: this.options.web3AuthNetwork,
92-
authBuildEnv: this.options.authBuildEnv,
93-
chainNamespaces: this.getChainNamespaces(),
94-
walletRegistry: filteredWalletRegistry,
95-
},
96-
{
97-
onInitExternalWallets: this.onInitExternalWallets,
98-
onSocialLogin: this.onSocialLogin,
99-
onExternalWalletLogin: this.onExternalWalletLogin,
100-
onModalVisibility: this.onModalVisibility,
101-
}
102-
);
103-
await withAbort(() => this.loginModal.initModal(), signal);
104-
105-
// setup common JRPC provider
106-
await withAbort(() => this.setupCommonJRPCProvider(), signal);
80+
try {
81+
const { signal } = options || {};
82+
83+
super.checkInitRequirements();
84+
// get project config and wallet registry
85+
const { projectConfig, walletRegistry } = await this.getProjectAndWalletConfig();
86+
87+
// init config
88+
this.initUIConfig(projectConfig);
89+
super.initAccountAbstractionConfig(projectConfig);
90+
super.initChainsConfig(projectConfig);
91+
super.initCachedConnectorAndChainId();
92+
trackData = {
93+
chains: this.coreOptions.chains.map((chain) => chain.chainId),
94+
rpc_urls: this.coreOptions.chains.map((chain) => chain.rpcTarget),
95+
storage_type: this.coreOptions.storageType,
96+
session_time: this.coreOptions.sessionTime,
97+
is_core_kit_key_used: this.coreOptions.useCoreKitKey,
98+
whitelabel: this.coreOptions.uiConfig, // TODO: flatten this
99+
account_abstraction: this.coreOptions.accountAbstractionConfig, // TODO: flatten this
100+
is_aa_enabled_for_external_wallets: this.coreOptions.useAAWithExternalWallet,
101+
is_mipd_enabled: this.coreOptions.multiInjectedProviderDiscovery,
102+
wallet_services: this.coreOptions.walletServicesConfig, // TODO: flatten this
103+
};
107104

108-
// initialize connectors
109-
this.on(CONNECTOR_EVENTS.CONNECTORS_UPDATED, ({ connectors: newConnectors }) => {
110-
const onAbortHandler = () => {
111-
log.debug("init aborted");
112-
if (this.connectors?.length > 0) {
113-
super.cleanup();
105+
// init login modal
106+
const { filteredWalletRegistry, disabledExternalWallets } = this.filterWalletRegistry(walletRegistry, projectConfig);
107+
this.loginModal = new LoginModal(
108+
{
109+
...this.options.uiConfig,
110+
connectorListener: this,
111+
web3authClientId: this.options.clientId,
112+
web3authNetwork: this.options.web3AuthNetwork,
113+
authBuildEnv: this.options.authBuildEnv,
114+
chainNamespaces: this.getChainNamespaces(),
115+
walletRegistry: filteredWalletRegistry,
116+
},
117+
{
118+
onInitExternalWallets: this.onInitExternalWallets,
119+
onSocialLogin: this.onSocialLogin,
120+
onExternalWalletLogin: this.onExternalWalletLogin,
121+
onModalVisibility: this.onModalVisibility,
114122
}
115-
};
116-
withAbort(() => this.initConnectors({ connectors: newConnectors, projectConfig, disabledExternalWallets }), signal, onAbortHandler);
117-
});
123+
);
124+
await withAbort(() => this.loginModal.initModal(), signal);
125+
126+
// setup common JRPC provider
127+
await withAbort(() => this.setupCommonJRPCProvider(), signal);
118128

119-
await withAbort(() => super.loadConnectors({ projectConfig, modalMode: true }), signal);
129+
// initialize connectors
130+
this.on(CONNECTOR_EVENTS.CONNECTORS_UPDATED, ({ connectors: newConnectors }) => {
131+
const onAbortHandler = () => {
132+
log.debug("init aborted");
133+
if (this.connectors?.length > 0) {
134+
super.cleanup();
135+
}
136+
};
137+
withAbort(() => this.initConnectors({ connectors: newConnectors, projectConfig, disabledExternalWallets }), signal, onAbortHandler);
138+
});
120139

121-
// initialize plugins
122-
await withAbort(() => super.initPlugins(), signal);
140+
await withAbort(() => super.loadConnectors({ projectConfig, modalMode: true }), signal);
141+
142+
// initialize plugins
143+
await withAbort(() => super.initPlugins(), signal);
144+
145+
// track completion event
146+
trackData = {
147+
...trackData,
148+
connectors: this.connectors.map((connector) => connector.name),
149+
};
150+
this.analytics.track(ANALYTICS_EVENTS.SDK_INITIALIZATION_COMPLETED, {
151+
...trackData,
152+
duration: Date.now() - startTime,
153+
});
154+
} catch (error) {
155+
if (error instanceof DOMException && error.name === "AbortError") return;
156+
157+
// track failure event
158+
this.analytics.track(ANALYTICS_EVENTS.SDK_INITIALIZATION_FAILED, {
159+
...trackData,
160+
duration: Date.now() - startTime,
161+
error_code: error instanceof Web3AuthError ? error.code : undefined,
162+
error_message: serializeError(error),
163+
});
164+
log.error("Failed to initialize modal", error);
165+
}
123166
}
124167

125168
public async connect(): Promise<IProvider | null> {

packages/modal/src/react/context/Web3AuthInnerContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ANALYTICS_INTEGRATION_TYPE,
23
type CONNECTED_EVENT_DATA,
34
CONNECTOR_EVENTS,
45
CONNECTOR_STATUS,
@@ -43,6 +44,9 @@ export function Web3AuthInnerProvider(params: PropsWithChildren<Web3AuthProvider
4344
resetHookState();
4445
const { web3AuthOptions } = config;
4546
const web3AuthInstance = new Web3Auth(web3AuthOptions);
47+
web3AuthInstance.setAnalyticsProperties({
48+
integration_type: ANALYTICS_INTEGRATION_TYPE.REACT_HOOKS,
49+
});
4650
setWeb3Auth(web3AuthInstance);
4751
}, [config]);
4852

packages/modal/src/vue/Web3AuthProvider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ANALYTICS_INTEGRATION_TYPE,
23
CONNECTOR_EVENTS,
34
CONNECTOR_STATUS,
45
type CONNECTOR_STATUS_TYPE,
@@ -55,6 +56,9 @@ export const Web3AuthProvider = defineComponent({
5556
resetHookState();
5657
const { web3AuthOptions } = newConfig;
5758
const web3AuthInstance = new Web3Auth(web3AuthOptions);
59+
web3AuthInstance.setAnalyticsProperties({
60+
integration_type: ANALYTICS_INTEGRATION_TYPE.VUE_COMPOSABLES,
61+
});
5862
web3Auth.value = web3AuthInstance;
5963
},
6064
{ immediate: true }

packages/no-modal/src/base/analytics.ts

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,45 @@ type EventProperties = {
1414
const SEGMENT_WRITE_KEY = "gGjtk5XxaH2OAIlErcBgydrHpoRZ2hkZ"; // TODO: use the production key
1515

1616
export class Analytics {
17-
private static instance: Analytics;
17+
private segment: AnalyticsBrowser;
1818

19-
private analytics: AnalyticsBrowser;
19+
private globalProperties: Record<string, unknown> = {};
2020

21-
private constructor() {
22-
this.analytics = new AnalyticsBrowser();
23-
this.analytics.load({ writeKey: SEGMENT_WRITE_KEY }).catch((error) => {
24-
log.error("Failed to initialize Analytics", error);
25-
});
26-
log.info("Analytics initialized", { sdkVersion });
27-
}
28-
29-
public static init(): void {
30-
if (Analytics.isDisabled()) {
31-
return;
21+
public init(): void {
22+
if (this.segment) {
23+
throw new Error("Analytics already initialized");
3224
}
3325

34-
if (!Analytics.instance) {
35-
Analytics.instance = new Analytics();
36-
}
26+
this.segment = new AnalyticsBrowser();
27+
this.segment
28+
.load({ writeKey: SEGMENT_WRITE_KEY })
29+
.then(() => {
30+
log.debug("Analytics initialized", { sdkVersion });
31+
return true;
32+
})
33+
.catch((error) => {
34+
log.error("Failed to initialize Analytics", error);
35+
});
3736
}
3837

39-
public static async identify(userId: string, traits?: UserTraits) {
40-
if (Analytics.isDisabled()) {
41-
return;
42-
}
38+
public setGlobalProperties(properties: Record<string, unknown>) {
39+
this.globalProperties = { ...this.globalProperties, ...properties };
40+
}
4341

42+
public async identify(userId: string, traits?: UserTraits) {
4443
try {
45-
const instance = Analytics.getInstance();
46-
return instance.analytics.identify(userId, {
44+
return this.getSegment().identify(userId, {
4745
...traits,
4846
});
4947
} catch (error) {
50-
log.error(`Failed to identify user ${userId}`, error);
48+
log.error(`Failed to identify user ${userId} in analytics`, error);
5149
}
5250
}
5351

54-
public static async track(event: string, properties?: EventProperties) {
55-
if (Analytics.isDisabled()) {
56-
return;
57-
}
58-
52+
public async track(event: string, properties?: EventProperties) {
5953
try {
60-
const instance = Analytics.getInstance();
61-
return instance.analytics.track(event, {
54+
return this.getSegment().track(event, {
55+
...this.globalProperties,
6256
...properties,
6357
sdk_version: sdkVersion,
6458
});
@@ -67,16 +61,29 @@ export class Analytics {
6761
}
6862
}
6963

70-
private static getInstance() {
71-
if (!Analytics.instance) {
64+
private getSegment() {
65+
if (!this.segment) {
7266
log.error("Analytics not initialized. Call Analytics.init() first.");
7367
throw new Error("Analytics not initialized. Call Analytics.init() first.");
7468
}
75-
return Analytics.instance;
76-
}
77-
78-
private static isDisabled() {
79-
// disable analytics if the origin is not https
80-
return window.location && window.location.protocol !== "https:";
69+
return this.segment;
8170
}
8271
}
72+
73+
export const ANALYTICS_EVENTS = {
74+
SDK_INITIALIZATION_COMPLETED: "sdk_initialization_completed",
75+
SDK_INITIALIZATION_FAILED: "sdk_initialization_failed",
76+
CONNECT_STARTED: "connect_started",
77+
CONNECT_COMPLETED: "connect_completed",
78+
CONNECT_FAILED: "connect_failed",
79+
};
80+
81+
export const ANALYTICS_INTEGRATION_TYPE = {
82+
REACT_HOOKS: "React Hooks",
83+
VUE_COMPOSABLES: "Vue Composables",
84+
};
85+
86+
export const ANALYTICS_SDK_TYPE = {
87+
WEB_NO_MODAL: "Web NoModal",
88+
WEB_MODAL: "Web Modal",
89+
};

0 commit comments

Comments
 (0)