Skip to content

✨ feat: Support ga4 provider - #5

Merged
sudongyuer merged 1 commit into
masterfrom
feat/ga4-provider
Aug 5, 2025
Merged

✨ feat: Support ga4 provider#5
sudongyuer merged 1 commit into
masterfrom
feat/ga4-provider

Conversation

@sudongyuer

@sudongyuer sudongyuer commented Jul 30, 2025

Copy link
Copy Markdown
Collaborator

💻 变更类型 | Change Type

  • ✨ feat
  • 🐛 fix
  • ♻️ refactor
  • 💄 style
  • 🔨 chore
  • 📝 docs

🔀 变更说明 | 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:

  • Support Google Analytics 4 provider with measurementId and gtagConfig options
  • Introduce GoogleAnalyticsProvider class implementing initialize, track, identify, page view, reset, and native instance methods

Enhancements:

  • Register GA4 provider in createAnalytics when enabled
  • Export GoogleAnalyticsProvider and update type mappings for GA4

Documentation:

  • Add examples/ga4.ts demonstrating GA4 initialization, event tracking, identification, and advanced usage

@lobehubbot

Copy link
Copy Markdown
Member

👍 @sudongyuer


Thank you for raising your pull request and contributing to our Community
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
If you encounter any problems, please feel free to connect with us.
非常感谢您提出拉取请求并为我们的社区做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。
如果您遇到任何问题,请随时与我们联系。

@sourcery-ai

sourcery-ai Bot commented Jul 30, 2025

Copy link
Copy Markdown

Reviewer's Guide

This 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 config

erDiagram
  AnalyticsConfig {
    string business
    boolean debug
  }
  GoogleAnalyticsProviderConfig {
    boolean enabled
    string measurementId
    object gtagConfig
  }
  AnalyticsConfig ||--|{ GoogleAnalyticsProviderConfig : ga4
Loading

Class diagram for the new GoogleAnalyticsProvider

classDiagram
  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
Loading

File-Level Changes

Change Details Files
Integrate GA4 provider into analytics configuration
  • Add GA4 settings example and business field in config comments
  • Extend createAnalytics to register GA4 provider when enabled
src/config.ts
Extend type definitions for GA4 support
  • Rename and enhance the GoogleAnalyticsProviderConfig interface with gtagConfig
  • Replace ga provider entry with ga4 in AnalyticsConfig
  • Add ga4 to ProviderTypeMap
src/types.ts
Expose GA4 provider in public API exports
  • Export GoogleAnalyticsProvider from index
  • Update export of GoogleAnalyticsProviderConfig type
src/index.ts
Implement GoogleAnalyticsProvider class for GA4
  • Initialize gtag script and dataLayer
  • Implement track, identify, trackPageView, reset and utility methods
  • Handle browser-only environment and error logging
src/providers/ga4.ts
Add GA4 usage example
  • Provide basic, advanced, and error-handling example functions
  • Demonstrate initialization, tracking, identification, and direct gtag access
examples/ga4.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2025

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/lobehub/lobe-analytics/@lobehub/analytics@5

commit: 59a9dbb

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/providers/ga4.ts
Comment on lines +67 to +73
// 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,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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,
+ };

Comment thread src/providers/ga4.ts
Comment on lines +142 to +144
gtag('event', 'login', {
user_id: userId,
...this.enrichProperties(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  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.

Comment thread src/providers/ga4.ts
Comment on lines +162 to +164
await this.track({
name: 'page_view',
properties: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/types.ts
Comment on lines +84 to 92
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;
}

Comment thread src/providers/ga4.ts
}

try {
const gtag = (window as any).gtag;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const gtag = (window as any).gtag;
const {gtag} = window as any;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Comment thread src/providers/ga4.ts
}

try {
const gtag = (window as any).gtag;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const gtag = (window as any).gtag;
const {gtag} = window as any;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Comment thread src/providers/ga4.ts
}

try {
const gtag = (window as any).gtag;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const gtag = (window as any).gtag;
const {gtag} = window as any;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Comment thread src/providers/ga4.ts
return null;
}

const gtag = (window as any).gtag;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const gtag = (window as any).gtag;
const {gtag} = window as any;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

@sudongyuer
sudongyuer merged commit bd69e71 into master Aug 5, 2025
5 checks passed
@lobehubbot

Copy link
Copy Markdown
Member

❤️ Great PR @sudongyuer ❤️


The growth of project is inseparable from user feedback and contribution, thanks for your contribution!
项目的成长离不开用户反馈和贡献,感谢您的贡献!

github-actions Bot pushed a commit that referenced this pull request Aug 5, 2025
## [Version&nbsp;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">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
@lobehubbot

Copy link
Copy Markdown
Member

🎉 This PR is included in version 1.6.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants