✨ feat: Support ga4 provider - #5
Conversation
|
👍 @sudongyuer |
Reviewer's GuideThis PR introduces a Google Analytics 4 provider by adding a new provider implementation, updating configuration, types, and exports to register and support GA4, and includes an example usage file. Entity relationship diagram for AnalyticsConfig and GA4 provider configerDiagram
AnalyticsConfig {
string business
boolean debug
}
GoogleAnalyticsProviderConfig {
boolean enabled
string measurementId
object gtagConfig
}
AnalyticsConfig ||--|{ GoogleAnalyticsProviderConfig : ga4
Class diagram for the new GoogleAnalyticsProviderclassDiagram
class BaseAnalytics {
- business: string
- debug: boolean
- enabled: boolean
+ isEnabled(): boolean
+ log(...): void
+ logError(...): void
+ enrichProperties(...): Record<string, any>
+ validateEvent(...): boolean
}
class GoogleAnalyticsProvider {
- config: GoogleAnalyticsProviderConfig
- initialized: boolean
+ constructor(config: GoogleAnalyticsProviderConfig, business: string)
+ getProviderName(): string
+ initialize(): Promise<void>
+ track(event: AnalyticsEvent): Promise<void>
+ identify(userId: string, properties?): Promise<void>
+ trackPageView(page: string, properties?): Promise<void>
+ reset(): Promise<void>
+ isFeatureEnabled(flag: string): boolean
+ getNativeInstance(): ((...args: any[]) => void) | null
+ getMeasurementId(): string
+ getCurrentBusiness(): string
}
BaseAnalytics <|-- GoogleAnalyticsProvider
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
commit: |
There was a problem hiding this comment.
Hey @sudongyuer - I've reviewed your changes - here's some feedback:
- You forgot to import GoogleAnalyticsProvider in config.ts before using it—add
import { GoogleAnalyticsProvider } from './providers/ga4'. - The gtagConfig typing currently uses a loose
[key: string]: any; consider leveraging official @types/gtag.js definitions for stronger type safety. - The GA4 example file is quite verbose—consider trimming it to just the core usage patterns and moving advanced scenarios to separate docs or examples.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You forgot to import GoogleAnalyticsProvider in config.ts before using it—add `import { GoogleAnalyticsProvider } from './providers/ga4'`.
- The gtagConfig typing currently uses a loose `[key: string]: any`; consider leveraging official @types/gtag.js definitions for stronger type safety.
- The GA4 example file is quite verbose—consider trimming it to just the core usage patterns and moving advanced scenarios to separate docs or examples.
## Individual Comments
### Comment 1
<location> `src/providers/ga4.ts:68` </location>
<code_context>
+ gtag('js', new Date());
+
+ // Configure GA4 with user config and our defaults
+ const configOptions = {
+ // User's gtag config options
+ ...this.config.gtagConfig,
+ // Our internal config (these override user config for consistency)
+ debug_mode: this.debug || this.config.gtagConfig?.debug_mode,
+ };
+
</code_context>
<issue_to_address>
Overriding debug_mode may not respect user intent.
The current logic allows the internal debug flag to override a user's explicit debug_mode setting. Please clarify the intended precedence or document this behavior to avoid confusion.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
// Configure GA4 with user config and our defaults
+ const configOptions = {
+ // User's gtag config options
+ ...this.config.gtagConfig,
+ // Our internal config (these override user config for consistency)
+ debug_mode: this.debug || this.config.gtagConfig?.debug_mode,
+ };
=======
// Configure GA4 with user config and our defaults
+ // Precedence: If user explicitly sets debug_mode in gtagConfig, respect it.
+ // Otherwise, use the internal debug flag.
+ const configOptions = {
+ // User's gtag config options
+ ...this.config.gtagConfig,
+ // Respect user-supplied debug_mode if present, otherwise use internal debug flag
+ debug_mode:
+ typeof this.config.gtagConfig?.debug_mode !== "undefined"
+ ? this.config.gtagConfig.debug_mode
+ : this.debug,
+ };
>>>>>>> REPLACE
</suggested_fix>
### Comment 2
<location> `src/providers/ga4.ts:142` </location>
<code_context>
+ }
+
+ // 3. Track login event (GA4 recommended practice)
+ gtag('event', 'login', {
+ user_id: userId,
+ ...this.enrichProperties(),
+ });
+
</code_context>
<issue_to_address>
Sending a 'login' event on identify may not always be appropriate.
If 'identify' is used outside of login scenarios, this could generate inaccurate login events. Consider making this optional or clearly documenting the behavior.
Suggested implementation:
```typescript
// 3. Track login event (GA4 recommended practice)
// Optionally track login event if requested
if (trackLoginEvent) {
gtag('event', 'login', {
user_id: userId,
...this.enrichProperties(),
});
}
```
```typescript
/**
* Google Analytics 4 Analytics Provider
* Uses gtag.js for tracking events, page views, and user identification
*
* Note: The `identify` method supports an optional `trackLoginEvent` parameter.
* If set to true, a 'login' event will be sent to GA4. This should only be used
* when the identify call is triggered by an actual login event.
*/
export class GoogleAnalyticsProvider extends BaseAnalytics {
```
You will also need to:
1. Update the signature of the `identify` method to accept an optional `trackLoginEvent: boolean = false` parameter.
2. Update all calls to `identify` where a login event should be tracked to pass `trackLoginEvent: true`.
3. Update the method's JSDoc to document the new parameter.
</issue_to_address>
### Comment 3
<location> `src/providers/ga4.ts:162` </location>
<code_context>
+ const enrichedProperties = this.enrichProperties(properties);
+
+ // Use the track method to send page_view event
+ await this.track({
+ name: 'page_view',
+ properties: {
+ page_location: page,
+ page_title: page,
</code_context>
<issue_to_address>
Page view event uses page as both location and title.
Consider allowing a separate page_title parameter or extracting the document title if not provided, as page_location and page_title may differ.
</issue_to_address>
### Comment 4
<location> `src/types.ts:84` </location>
<code_context>
}
-export interface GoogleProviderAnalyticsConfig extends ProviderConfig {
+export interface GoogleAnalyticsProviderConfig extends ProviderConfig {
+ // GA4 gtag config options - integrates with official gtag types when available
+ gtagConfig?: {
+ // Allow additional gtag config options
+ [key: string]: any;
+ debug_mode?: boolean;
+ };
measurementId: string;
</code_context>
<issue_to_address>
gtagConfig property allows arbitrary keys with any type.
Consider restricting the type of gtagConfig keys or using a union of known options to improve type safety and catch misconfigurations at compile time.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
export interface GoogleAnalyticsProviderConfig extends ProviderConfig {
// GA4 gtag config options - integrates with official gtag types when available
gtagConfig?: {
// Allow additional gtag config options
[key: string]: any;
debug_mode?: boolean;
};
measurementId: string;
}
=======
/**
* Known GA4 gtag config options.
* Extend this type as needed to include more official gtag config options.
*/
export type GtagConfigOptions = {
debug_mode?: boolean;
// Add more known gtag config options here as needed, e.g.:
// send_page_view?: boolean;
// allow_ad_personalization_signals?: boolean;
// etc.
[key: string]: string | number | boolean | undefined;
};
export interface GoogleAnalyticsProviderConfig extends ProviderConfig {
// GA4 gtag config options - integrates with official gtag types when available
gtagConfig?: GtagConfigOptions;
measurementId: string;
}
>>>>>>> REPLACE
</suggested_fix>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| // Configure GA4 with user config and our defaults | ||
| const configOptions = { | ||
| // User's gtag config options | ||
| ...this.config.gtagConfig, | ||
| // Our internal config (these override user config for consistency) | ||
| debug_mode: this.debug || this.config.gtagConfig?.debug_mode, | ||
| }; |
There was a problem hiding this comment.
suggestion: Overriding debug_mode may not respect user intent.
The current logic allows the internal debug flag to override a user's explicit debug_mode setting. Please clarify the intended precedence or document this behavior to avoid confusion.
| // Configure GA4 with user config and our defaults | |
| const configOptions = { | |
| // User's gtag config options | |
| ...this.config.gtagConfig, | |
| // Our internal config (these override user config for consistency) | |
| debug_mode: this.debug || this.config.gtagConfig?.debug_mode, | |
| }; | |
| // Configure GA4 with user config and our defaults | |
| + // Precedence: If user explicitly sets debug_mode in gtagConfig, respect it. | |
| + // Otherwise, use the internal debug flag. | |
| + const configOptions = { | |
| + // User's gtag config options | |
| + ...this.config.gtagConfig, | |
| + // Respect user-supplied debug_mode if present, otherwise use internal debug flag | |
| + debug_mode: | |
| + typeof this.config.gtagConfig?.debug_mode !== "undefined" | |
| + ? this.config.gtagConfig.debug_mode | |
| + : this.debug, | |
| + }; |
| gtag('event', 'login', { | ||
| user_id: userId, | ||
| ...this.enrichProperties(), |
There was a problem hiding this comment.
suggestion (bug_risk): Sending a 'login' event on identify may not always be appropriate.
If 'identify' is used outside of login scenarios, this could generate inaccurate login events. Consider making this optional or clearly documenting the behavior.
Suggested implementation:
// 3. Track login event (GA4 recommended practice)
// Optionally track login event if requested
if (trackLoginEvent) {
gtag('event', 'login', {
user_id: userId,
...this.enrichProperties(),
});
}/**
* Google Analytics 4 Analytics Provider
* Uses gtag.js for tracking events, page views, and user identification
*
* Note: The `identify` method supports an optional `trackLoginEvent` parameter.
* If set to true, a 'login' event will be sent to GA4. This should only be used
* when the identify call is triggered by an actual login event.
*/
export class GoogleAnalyticsProvider extends BaseAnalytics {You will also need to:
- Update the signature of the
identifymethod to accept an optionaltrackLoginEvent: boolean = falseparameter. - Update all calls to
identifywhere a login event should be tracked to passtrackLoginEvent: true. - Update the method's JSDoc to document the new parameter.
| await this.track({ | ||
| name: 'page_view', | ||
| properties: { |
There was a problem hiding this comment.
suggestion: Page view event uses page as both location and title.
Consider allowing a separate page_title parameter or extracting the document title if not provided, as page_location and page_title may differ.
| export interface GoogleAnalyticsProviderConfig extends ProviderConfig { | ||
| // GA4 gtag config options - integrates with official gtag types when available | ||
| gtagConfig?: { | ||
| // Allow additional gtag config options | ||
| [key: string]: any; | ||
| debug_mode?: boolean; | ||
| }; | ||
| measurementId: string; | ||
| } |
There was a problem hiding this comment.
suggestion: gtagConfig property allows arbitrary keys with any type.
Consider restricting the type of gtagConfig keys or using a union of known options to improve type safety and catch misconfigurations at compile time.
| export interface GoogleAnalyticsProviderConfig extends ProviderConfig { | |
| // GA4 gtag config options - integrates with official gtag types when available | |
| gtagConfig?: { | |
| // Allow additional gtag config options | |
| [key: string]: any; | |
| debug_mode?: boolean; | |
| }; | |
| measurementId: string; | |
| } | |
| /** | |
| * Known GA4 gtag config options. | |
| * Extend this type as needed to include more official gtag config options. | |
| */ | |
| export type GtagConfigOptions = { | |
| debug_mode?: boolean; | |
| // Add more known gtag config options here as needed, e.g.: | |
| // send_page_view?: boolean; | |
| // allow_ad_personalization_signals?: boolean; | |
| // etc. | |
| [key: string]: string | number | boolean | undefined; | |
| }; | |
| export interface GoogleAnalyticsProviderConfig extends ProviderConfig { | |
| // GA4 gtag config options - integrates with official gtag types when available | |
| gtagConfig?: GtagConfigOptions; | |
| measurementId: string; | |
| } |
| } | ||
|
|
||
| try { | ||
| const gtag = (window as any).gtag; |
There was a problem hiding this comment.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)
| const gtag = (window as any).gtag; | |
| const {gtag} = window as any; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
| } | ||
|
|
||
| try { | ||
| const gtag = (window as any).gtag; |
There was a problem hiding this comment.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)
| const gtag = (window as any).gtag; | |
| const {gtag} = window as any; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
| } | ||
|
|
||
| try { | ||
| const gtag = (window as any).gtag; |
There was a problem hiding this comment.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)
| const gtag = (window as any).gtag; | |
| const {gtag} = window as any; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
| return null; | ||
| } | ||
|
|
||
| const gtag = (window as any).gtag; |
There was a problem hiding this comment.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)
| const gtag = (window as any).gtag; | |
| const {gtag} = window as any; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
|
❤️ Great PR @sudongyuer ❤️ |
## [Version 1.6.0](v1.5.1...v1.6.0) <sup>Released on **2025-08-05**</sup> #### ✨ Features - **misc**: Support ga4 provider. <br/> <details> <summary><kbd>Improvements and Fixes</kbd></summary> #### What's improved * **misc**: Support ga4 provider, closes [#5](#5) ([bd69e71](bd69e71)) </details> <div align="right"> [](#readme-top) </div>
|
🎉 This PR is included in version 1.6.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
💻 变更类型 | Change Type
🔀 变更说明 | Description of Change
GA4 Provider
📝 补充信息 | Additional Information
Summary by Sourcery
Add support for Google Analytics 4 (GA4) provider by extending configuration, types, and exports, implementing a new GoogleAnalyticsProvider class with full gtag.js integration, and providing an example usage script.
New Features:
Enhancements:
Documentation: