Skip to content

Commit 43d7c4e

Browse files
Shukriclaude
authored andcommitted
feat: frontend Core Web Vitals via browser SDK injection (v1.2.0)
Injects the DevPulse browser UMD bundle on all public WordPress pages so real-user LCP, INP, CLS, TTFB, and page_load are collected automatically — matching the same Lighthouse metric set. All vitals from one page load are sent as a single grouped "Performance vitals" event, identical to the format the browser SDK produces. - Handler: new `$track_vitals` constructor param; enqueue_vitals_script() registers assets/devpulse.umd.js and inlines DevPulse.default.init() with DSN, environment, and release — no manual JS needed - Plugin: reads devpulse_track_vitals option (or DEVPULSE_TRACK_VITALS constant) and passes it to Handler - Admin: new "Frontend Performance Vitals" checkbox in settings; option included in repair_db defaults - assets/devpulse.umd.js: bundled browser SDK (copied from browser SDK dist); ~4 KB, deferred, filter devpulse_enqueue_vitals to disable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 38ab8cb commit 43d7c4e

5 files changed

Lines changed: 136 additions & 24 deletions

File tree

assets/devpulse.umd.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

devpulse.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Plugin Name: DevPulse
55
* Plugin URI: https://github.com/SekolahCode/devpulse-wp
66
* Description: Real-time error tracking for WordPress — self-hosted.
7-
* Version: 1.1.1
7+
* Version: 1.2.0
88
* Requires at least: 6.3
99
* Requires PHP: 7.4
1010
* Tested up to: 6.9
@@ -24,7 +24,7 @@
2424
*
2525
* @since 1.0.0
2626
*/
27-
define( 'DEVPULSE_VERSION', '1.1.1' );
27+
define( 'DEVPULSE_VERSION', '1.2.0' );
2828

2929
/**
3030
* Absolute path to the main plugin file.

src/Admin.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ public function register_settings(): void {
197197
'type' => 'string',
198198
'show_in_rest' => false,
199199
] );
200+
201+
register_setting( self::OPTION_GROUP, 'devpulse_track_vitals', [
202+
'sanitize_callback' => 'absint',
203+
'default' => 1,
204+
'type' => 'boolean',
205+
'show_in_rest' => false,
206+
] );
200207
}
201208

202209
/**
@@ -275,11 +282,12 @@ public function render_page(): void {
275282
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'devpulse' ) );
276283
}
277284

278-
$dsn = get_option( 'devpulse_dsn', '' );
279-
$env = get_option( 'devpulse_env', 'production' );
280-
$enabled = (int) get_option( 'devpulse_enabled', 0 );
281-
$sample_rate = (float) get_option( 'devpulse_sample_rate', 1.0 );
282-
$release = get_option( 'devpulse_release', '' );
285+
$dsn = get_option( 'devpulse_dsn', '' );
286+
$env = get_option( 'devpulse_env', 'production' );
287+
$enabled = (int) get_option( 'devpulse_enabled', 0 );
288+
$sample_rate = (float) get_option( 'devpulse_sample_rate', 1.0 );
289+
$release = get_option( 'devpulse_release', '' );
290+
$track_vitals = (int) get_option( 'devpulse_track_vitals', 1 );
283291

284292
$dsn_via_constant = defined( 'DEVPULSE_DSN' );
285293
// Show an advisory when the DSN (which contains an API key) is stored in
@@ -333,6 +341,33 @@ public function render_page(): void {
333341
</td>
334342
</tr>
335343

344+
<tr>
345+
<th scope="row">
346+
<label for="devpulse_track_vitals">
347+
<?php esc_html_e( 'Frontend Performance Vitals', 'devpulse' ); ?>
348+
</label>
349+
</th>
350+
<td>
351+
<fieldset>
352+
<legend class="screen-reader-text">
353+
<?php esc_html_e( 'Frontend Performance Vitals', 'devpulse' ); ?>
354+
</legend>
355+
<label for="devpulse_track_vitals">
356+
<input
357+
type="checkbox"
358+
id="devpulse_track_vitals"
359+
name="devpulse_track_vitals"
360+
value="1"
361+
<?php checked( $track_vitals, 1 ); ?> />
362+
<?php esc_html_e( 'Collect real-user Core Web Vitals (LCP, INP, CLS, TTFB, page load) on every page view', 'devpulse' ); ?>
363+
</label>
364+
<p class="description">
365+
<?php esc_html_e( 'Injects a lightweight browser script (~4 KB) that measures Lighthouse-equivalent metrics and sends them as a single grouped issue. Override with DEVPULSE_TRACK_VITALS in wp-config.php.', 'devpulse' ); ?>
366+
</p>
367+
</fieldset>
368+
</td>
369+
</tr>
370+
336371
<tr>
337372
<th scope="row">
338373
<label for="devpulse_dsn">
@@ -532,11 +567,12 @@ public function repair_db(): void {
532567

533568
$repairs = [];
534569
$defaults = [
535-
'devpulse_dsn' => '',
536-
'devpulse_env' => 'production',
537-
'devpulse_enabled' => 0,
538-
'devpulse_sample_rate' => 1.0,
539-
'devpulse_release' => '',
570+
'devpulse_dsn' => '',
571+
'devpulse_env' => 'production',
572+
'devpulse_enabled' => 0,
573+
'devpulse_sample_rate' => 1.0,
574+
'devpulse_release' => '',
575+
'devpulse_track_vitals' => 1,
540576
];
541577

542578
// 1. Restore any missing options.

src/Handler.php

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class Handler {
2828
/** @var string|null Release identifier (semver, git SHA, etc.). */
2929
private ?string $release;
3030

31+
/** @var bool Whether to inject the JS bundle for frontend vitals. */
32+
private bool $track_vitals;
33+
3134
/** @var bool Recursive-send guard. */
3235
private bool $sending = false;
3336

@@ -61,14 +64,16 @@ class Handler {
6164
private const RATE_LIMIT_TTL = 300;
6265

6366
/**
64-
* @param string $dsn Ingest URL.
65-
* @param string $env Environment name.
66-
* @param string|null $release Release identifier.
67+
* @param string $dsn Ingest URL.
68+
* @param string $env Environment name.
69+
* @param string|null $release Release identifier.
70+
* @param bool $track_vitals Whether to inject the browser vitals JS bundle.
6771
*/
68-
public function __construct( string $dsn, string $env = 'production', ?string $release = null ) {
69-
$this->dsn = $dsn;
70-
$this->env = $env;
71-
$this->release = $release;
72+
public function __construct( string $dsn, string $env = 'production', ?string $release = null, bool $track_vitals = true ) {
73+
$this->dsn = $dsn;
74+
$this->env = $env;
75+
$this->release = $release;
76+
$this->track_vitals = $track_vitals;
7277

7378
/**
7479
* Filter: PHP error levels that DevPulse should ignore.
@@ -140,6 +145,10 @@ public function boot(): void {
140145
add_filter( 'wp_die_handler', [ $this, 'wp_die_handler' ] );
141146
add_action( 'shutdown', [ $this, 'capture_db_errors' ] );
142147

148+
if ( $this->track_vitals ) {
149+
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_vitals_script' ] );
150+
}
151+
143152
/**
144153
* Action: Fires after DevPulse handler is initialised.
145154
*
@@ -148,6 +157,68 @@ public function boot(): void {
148157
do_action( 'devpulse_loaded' );
149158
}
150159

160+
// ── Frontend Vitals ───────────────────────────────────────────────────
161+
162+
/**
163+
* Enqueue the DevPulse browser bundle and auto-initialise it.
164+
*
165+
* Injects the UMD bundle on all public-facing pages and calls
166+
* DevPulse.default.init() with the site's DSN, environment, and release
167+
* so LCP, INP, CLS, TTFB and page_load are collected automatically —
168+
* exactly the same metrics as Lighthouse / Core Web Vitals.
169+
*
170+
* @since 1.2.0
171+
*/
172+
public function enqueue_vitals_script(): void {
173+
/**
174+
* Filter: Disable the browser vitals bundle on specific pages.
175+
*
176+
* Return false to skip enqueueing (e.g., logged-in admins, maintenance mode).
177+
*
178+
* @since 1.2.0
179+
* @param bool $enqueue
180+
*/
181+
if ( ! apply_filters( 'devpulse_enqueue_vitals', true ) ) {
182+
return;
183+
}
184+
185+
$script_path = 'assets/devpulse.umd.js';
186+
$script_abs = DEVPULSE_DIR . $script_path;
187+
$script_version = file_exists( $script_abs )
188+
? (string) filemtime( $script_abs )
189+
: DEVPULSE_VERSION;
190+
191+
wp_register_script(
192+
'devpulse-vitals',
193+
plugins_url( $script_path, DEVPULSE_FILE ),
194+
[],
195+
$script_version,
196+
[ 'in_footer' => true, 'strategy' => 'defer' ]
197+
);
198+
199+
/**
200+
* Filter: Override vitals tracking options passed to DevPulse.init().
201+
*
202+
* @since 1.2.0
203+
* @param array $options
204+
*/
205+
$options = apply_filters( 'devpulse_vitals_options', [
206+
'dsn' => $this->dsn,
207+
'environment' => $this->env,
208+
'release' => $this->release,
209+
'trackVitals' => true,
210+
] );
211+
212+
// wp_json_encode already escapes for JS string context.
213+
$init_js = sprintf(
214+
'(function(w){var dp=w.DevPulse;if(dp&&(dp.default||dp.DevPulse)){(dp.default||dp.DevPulse).init(%s);}})(window);',
215+
wp_json_encode( $options )
216+
);
217+
218+
wp_add_inline_script( 'devpulse-vitals', $init_js, 'after' );
219+
wp_enqueue_script( 'devpulse-vitals' );
220+
}
221+
151222
// ── Public API ────────────────────────────────────────────────────────
152223

153224
/** @since 1.0.0 */

src/Plugin.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,17 @@ public function init_handler(): void {
7070
return;
7171
}
7272

73-
$dsn = defined( 'DEVPULSE_DSN' ) ? DEVPULSE_DSN : get_option( 'devpulse_dsn', '' );
74-
$env = defined( 'DEVPULSE_ENV' ) ? DEVPULSE_ENV : get_option( 'devpulse_env', 'production' );
75-
$enabled = defined( 'DEVPULSE_ENABLED' ) ? (bool) DEVPULSE_ENABLED : (bool) get_option( 'devpulse_enabled', 0 );
76-
$release = defined( 'DEVPULSE_RELEASE' ) ? DEVPULSE_RELEASE : get_option( 'devpulse_release', '' );
73+
$dsn = defined( 'DEVPULSE_DSN' ) ? DEVPULSE_DSN : get_option( 'devpulse_dsn', '' );
74+
$env = defined( 'DEVPULSE_ENV' ) ? DEVPULSE_ENV : get_option( 'devpulse_env', 'production' );
75+
$enabled = defined( 'DEVPULSE_ENABLED' ) ? (bool) DEVPULSE_ENABLED : (bool) get_option( 'devpulse_enabled', 0 );
76+
$release = defined( 'DEVPULSE_RELEASE' ) ? DEVPULSE_RELEASE : get_option( 'devpulse_release', '' );
77+
$track_vitals = defined( 'DEVPULSE_TRACK_VITALS' ) ? (bool) DEVPULSE_TRACK_VITALS : (bool) get_option( 'devpulse_track_vitals', 1 );
7778

7879
if ( ! $enabled || empty( $dsn ) ) {
7980
return;
8081
}
8182

82-
$this->handler = new Handler( $dsn, $env, $release ?: null );
83+
$this->handler = new Handler( $dsn, $env, $release ?: null, $track_vitals );
8384
$this->handler->boot();
8485
}
8586

0 commit comments

Comments
 (0)