@presto1314w/vite-devtools-browser 0.3.3 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,6 @@ export type BrowserSessionState = {
20
20
  context: BrowserContext | null;
21
21
  page: Page | null;
22
22
  framework: BrowserFramework;
23
- extensionModeDisabled: boolean;
24
23
  collectorInstalled: boolean;
25
24
  consoleLogs: string[];
26
25
  hmrEvents: HmrEvent[];
@@ -35,3 +34,16 @@ export declare function ensureBrowserPage(state: BrowserSessionState, onPageRead
35
34
  export declare function closeBrowserSession(state: BrowserSessionState): Promise<void>;
36
35
  export declare function contextUsable(current: Pick<BrowserContext, "pages"> | null): current is Pick<BrowserContext, "pages">;
37
36
  export declare function isClosedTargetError(error: unknown): boolean;
37
+ /**
38
+ * Build platform-aware Chromium launch arguments.
39
+ *
40
+ * On Linux (especially Docker / CI without a display server) we add
41
+ * `--no-sandbox` and `--disable-dev-shm-usage` so that Chromium can
42
+ * start reliably in headless-shell containers.
43
+ *
44
+ * On Windows, `--disable-gpu` is added to work around occasional GPU
45
+ * process crashes on older drivers.
46
+ */
47
+ export declare function platformChromiumArgs(extra?: string[]): string[];
48
+ export declare function resolveChromiumExecutablePath(env?: NodeJS.ProcessEnv): string | undefined;
49
+ export declare function resolveBrowserHeadless(env?: NodeJS.ProcessEnv): boolean;
@@ -1,18 +1,12 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
1
+ import { existsSync } from "node:fs";
3
2
  import { chromium } from "playwright";
4
- const extensionPath = process.env.REACT_DEVTOOLS_EXTENSION ??
5
- resolve(import.meta.dirname, "../../next-browser/extensions/react-devtools-chrome");
6
- const hasReactExtension = existsSync(join(extensionPath, "manifest.json"));
7
- const installHook = hasReactExtension
8
- ? readFileSync(join(extensionPath, "build", "installHook.js"), "utf-8")
9
- : null;
3
+ import { getHookSource } from "./react/hook-manager.js";
4
+ import { isWindows, isLinux } from "./paths.js";
10
5
  export function createBrowserSessionState() {
11
6
  return {
12
7
  context: null,
13
8
  page: null,
14
9
  framework: "unknown",
15
- extensionModeDisabled: false,
16
10
  collectorInstalled: false,
17
11
  consoleLogs: [],
18
12
  hmrEvents: [],
@@ -42,9 +36,7 @@ export function resetBrowserSessionState(state) {
42
36
  export async function ensureBrowserPage(state, onPageReady) {
43
37
  if (!contextUsable(state.context)) {
44
38
  await closeBrowserSession(state);
45
- const launched = await launchBrowserContext(state.extensionModeDisabled);
46
- state.context = launched.context;
47
- state.extensionModeDisabled = launched.extensionModeDisabled;
39
+ state.context = await launchBrowserContext();
48
40
  }
49
41
  if (!state.context)
50
42
  throw new Error("browser not open");
@@ -56,10 +48,7 @@ export async function ensureBrowserPage(state, onPageReady) {
56
48
  if (!isClosedTargetError(error))
57
49
  throw error;
58
50
  await closeBrowserSession(state);
59
- state.extensionModeDisabled = true;
60
- const launched = await launchBrowserContext(state.extensionModeDisabled);
61
- state.context = launched.context;
62
- state.extensionModeDisabled = launched.extensionModeDisabled;
51
+ state.context = await launchBrowserContext();
63
52
  state.page = state.context.pages()[0] ?? (await state.context.newPage());
64
53
  }
65
54
  onPageReady(state.page);
@@ -86,32 +75,60 @@ export function isClosedTargetError(error) {
86
75
  return false;
87
76
  return /Target page, context or browser has been closed/i.test(error.message);
88
77
  }
89
- async function launchBrowserContext(extensionModeDisabled) {
90
- if (hasReactExtension && installHook && !extensionModeDisabled) {
91
- try {
92
- const context = await chromium.launchPersistentContext("", {
93
- headless: false,
94
- viewport: { width: 1280, height: 720 },
95
- args: [
96
- `--disable-extensions-except=${extensionPath}`,
97
- `--load-extension=${extensionPath}`,
98
- "--auto-open-devtools-for-tabs",
99
- ],
100
- });
101
- await context.waitForEvent("serviceworker").catch(() => { });
102
- await context.addInitScript(installHook);
103
- return { context, extensionModeDisabled };
104
- }
105
- catch {
106
- extensionModeDisabled = true;
107
- }
78
+ /**
79
+ * Build platform-aware Chromium launch arguments.
80
+ *
81
+ * On Linux (especially Docker / CI without a display server) we add
82
+ * `--no-sandbox` and `--disable-dev-shm-usage` so that Chromium can
83
+ * start reliably in headless-shell containers.
84
+ *
85
+ * On Windows, `--disable-gpu` is added to work around occasional GPU
86
+ * process crashes on older drivers.
87
+ */
88
+ export function platformChromiumArgs(extra = []) {
89
+ const args = ["--auto-open-devtools-for-tabs", ...extra];
90
+ if (isLinux) {
91
+ args.push("--no-sandbox", "--disable-dev-shm-usage");
108
92
  }
93
+ else if (isWindows) {
94
+ args.push("--disable-gpu");
95
+ }
96
+ return args;
97
+ }
98
+ export function resolveChromiumExecutablePath(env = process.env) {
99
+ const explicit = env.VITE_BROWSER_EXECUTABLE_PATH || env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
100
+ if (explicit && existsSync(explicit))
101
+ return explicit;
102
+ const candidates = isWindows
103
+ ? [
104
+ `${env.PROGRAMFILES ?? "C:\\Program Files"}\\Google\\Chrome\\Application\\chrome.exe`,
105
+ `${env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)"}\\Google\\Chrome\\Application\\chrome.exe`,
106
+ `${env.LOCALAPPDATA ?? ""}\\Google\\Chrome\\Application\\chrome.exe`,
107
+ ]
108
+ : isLinux
109
+ ? [
110
+ "/usr/bin/google-chrome",
111
+ "/usr/bin/chromium",
112
+ "/usr/bin/chromium-browser",
113
+ "/snap/bin/chromium",
114
+ ]
115
+ : [
116
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
117
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
118
+ ];
119
+ return candidates.find((candidate) => Boolean(candidate) && existsSync(candidate));
120
+ }
121
+ export function resolveBrowserHeadless(env = process.env) {
122
+ return /^(1|true|yes)$/i.test(env.VITE_BROWSER_HEADLESS || "");
123
+ }
124
+ async function launchBrowserContext() {
109
125
  const browser = await chromium.launch({
110
- headless: false,
111
- args: ["--auto-open-devtools-for-tabs"],
126
+ headless: resolveBrowserHeadless(),
127
+ executablePath: resolveChromiumExecutablePath(),
128
+ args: platformChromiumArgs(),
112
129
  });
113
- return {
114
- context: await browser.newContext({ viewport: { width: 1280, height: 720 } }),
115
- extensionModeDisabled,
116
- };
130
+ const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
131
+ // Inject bundled React DevTools hook before any page loads
132
+ await context.addInitScript(getHookSource());
133
+ return context;
117
134
  }
package/dist/browser.d.ts CHANGED
@@ -40,6 +40,12 @@ export declare function vueTree(id?: string): Promise<string>;
40
40
  export declare function vuePinia(store?: string): Promise<string>;
41
41
  export declare function vueRouter(): Promise<string>;
42
42
  export declare function reactTree(id?: string): Promise<string>;
43
+ export declare function reactStoreList(): Promise<string>;
44
+ export declare function reactStoreInspect(store: string): Promise<string>;
45
+ export declare function reactHookHealth(): Promise<string>;
46
+ export declare function reactHookInject(): Promise<string>;
47
+ export declare function reactCommits(limit?: number): Promise<string>;
48
+ export declare function reactCommitsClear(): Promise<string>;
43
49
  export declare function svelteTree(id?: string): Promise<string>;
44
50
  export declare function viteRestart(): Promise<string>;
45
51
  export declare function viteHMR(): Promise<string>;
package/dist/browser.js CHANGED
@@ -5,6 +5,9 @@ import { detectBrowserFramework, inspectReactTree, inspectSvelteTree, inspectVue
5
5
  import { closeBrowserSession, createBrowserSessionState, ensureBrowserPage, getCurrentPage as getSessionPage, } from "./browser-session.js";
6
6
  import { collectModuleRows, formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, readOverlayError, readRuntimeSnapshot, } from "./browser-vite.js";
7
7
  import * as networkLog from "./network.js";
8
+ import { checkHookHealth, formatHookHealth, injectHook } from "./react/hook-manager.js";
9
+ import { clearRenderHistory, formatRenderInfo, getRecentRenders, installRenderTracking } from "./react/profiler.js";
10
+ import { formatStoreInspection, formatStoreList, inspectStore, listStores } from "./react/zustand.js";
8
11
  import { resolveViaSourceMap } from "./sourcemap.js";
9
12
  export { contextUsable, isClosedTargetError } from "./browser-session.js";
10
13
  export { formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, normalizeLimit, } from "./browser-vite.js";
@@ -189,6 +192,36 @@ export async function vueRouter() {
189
192
  export async function reactTree(id) {
190
193
  return inspectReactTree(session, requireCurrentPage(), id);
191
194
  }
195
+ export async function reactStoreList() {
196
+ return formatStoreList(await listStores(requireCurrentPage()));
197
+ }
198
+ export async function reactStoreInspect(store) {
199
+ const result = await inspectStore(requireCurrentPage(), store);
200
+ if (!result) {
201
+ throw new Error(`zustand store not found: ${store}`);
202
+ }
203
+ return formatStoreInspection(result);
204
+ }
205
+ export async function reactHookHealth() {
206
+ return formatHookHealth(await checkHookHealth(requireCurrentPage()));
207
+ }
208
+ export async function reactHookInject() {
209
+ const currentPage = requireCurrentPage();
210
+ const injected = await injectHook(currentPage);
211
+ const status = await checkHookHealth(currentPage);
212
+ const prefix = injected ? "React hook injected.\n\n" : "React hook already installed.\n\n";
213
+ return `${prefix}${formatHookHealth(status)}`;
214
+ }
215
+ export async function reactCommits(limit = 20) {
216
+ const currentPage = requireCurrentPage();
217
+ await installRenderTracking(currentPage);
218
+ return formatRenderInfo(await getRecentRenders(currentPage, limit));
219
+ }
220
+ export async function reactCommitsClear() {
221
+ const currentPage = requireCurrentPage();
222
+ await clearRenderHistory(currentPage);
223
+ return "cleared React commit history";
224
+ }
192
225
  export async function svelteTree(id) {
193
226
  return inspectSvelteTree(requireCurrentPage(), id);
194
227
  }
@@ -210,6 +243,10 @@ export async function viteHMR() {
210
243
  }
211
244
  export async function viteRuntimeStatus() {
212
245
  const currentPage = requireCurrentPage();
246
+ if (session.framework === "unknown") {
247
+ const result = await detectBrowserFramework(currentPage);
248
+ session.framework = result.framework;
249
+ }
213
250
  const runtime = await readRuntimeSnapshot(currentPage);
214
251
  return formatRuntimeStatus(runtime, session.framework, session.hmrEvents);
215
252
  }
@@ -334,6 +371,8 @@ async function navigateAndRefreshContext(currentPage, navigate, refreshFramework
334
371
  clearRuntimeErrors();
335
372
  await injectEventCollector(currentPage);
336
373
  if (refreshFramework) {
374
+ await currentPage.waitForLoadState?.("networkidle", { timeout: 1_000 }).catch(() => { });
375
+ await currentPage.waitForTimeout?.(100).catch(() => { });
337
376
  await detectFramework();
338
377
  }
339
378
  }
package/dist/cli.js CHANGED
@@ -97,6 +97,47 @@ export async function runCli(argv, io) {
97
97
  const res = await io.send("react-tree", { id });
98
98
  exit(io, res, res.ok && res.data ? String(res.data) : "");
99
99
  }
100
+ if (cmd === "react" && arg === "store") {
101
+ const sub = args[2];
102
+ if (sub === "list") {
103
+ const res = await io.send("react-store-list");
104
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
105
+ }
106
+ if (sub === "inspect") {
107
+ const store = args[3];
108
+ if (!store) {
109
+ io.stderr("usage: vite-browser react store inspect <name>");
110
+ io.exit(1);
111
+ }
112
+ const res = await io.send("react-store-inspect", { store });
113
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
114
+ }
115
+ io.stderr("usage: vite-browser react store <list|inspect> [name]");
116
+ io.exit(1);
117
+ }
118
+ if (cmd === "react" && arg === "hook") {
119
+ const sub = args[2];
120
+ if (sub === "health") {
121
+ const res = await io.send("react-hook-health");
122
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
123
+ }
124
+ if (sub === "inject") {
125
+ const res = await io.send("react-hook-inject");
126
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
127
+ }
128
+ io.stderr("usage: vite-browser react hook <health|inject>");
129
+ io.exit(1);
130
+ }
131
+ if (cmd === "react" && arg === "commits") {
132
+ const sub = args[2];
133
+ if (sub === "clear") {
134
+ const res = await io.send("react-commits-clear");
135
+ exit(io, res, res.ok && res.data ? String(res.data) : "cleared React commit history");
136
+ }
137
+ const limit = parseNumberFlag(args, "--limit", 20);
138
+ const res = await io.send("react-commits", { limit });
139
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
140
+ }
100
141
  if (cmd === "svelte" && arg === "tree") {
101
142
  const id = args[2];
102
143
  const res = await io.send("svelte-tree", { id });
@@ -205,63 +246,69 @@ export function exit(io, res, msg) {
205
246
  io.exit(0);
206
247
  }
207
248
  export function printUsage() {
208
- return `
209
- vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
210
-
211
- USAGE
212
- vite-browser <command> [options]
213
-
214
- BROWSER CONTROL
215
- open <url> [--cookies-json <file>] Launch browser and navigate
216
- close Close browser and daemon
217
- goto <url> Full-page navigation
218
- back Go back in history
219
- reload Reload current page
220
-
221
- FRAMEWORK DETECTION
222
- detect Detect framework (vue/react/svelte)
223
-
224
- VUE COMMANDS
225
- vue tree [id] Show Vue component tree or inspect component
226
- vue pinia [store] Show Pinia stores or inspect specific store
227
- vue router Show Vue Router information
228
-
229
- REACT COMMANDS
230
- react tree [id] Show React component tree or inspect component
231
-
232
- SVELTE COMMANDS
233
- svelte tree [id] Show Svelte component tree or inspect component
234
-
235
- VITE COMMANDS
236
- vite restart Restart Vite dev server
237
- vite hmr Show HMR summary
238
- vite hmr trace [--limit <n>] Show HMR timeline
239
- vite hmr clear Clear tracked HMR timeline
240
- vite runtime Show Vite runtime status
241
- vite module-graph [--filter <txt>] [--limit <n>]
242
- Show loaded Vite module resources
243
- vite module-graph trace [--filter <txt>] [--limit <n>]
244
- Show module additions/removals since baseline
245
- vite module-graph clear Clear module-graph baseline
246
- errors Show build/runtime errors
247
- errors --mapped Show errors with source-map mapping
248
- errors --mapped --inline-source Include mapped source snippets
249
- correlate errors [--window <ms>] Correlate current errors with recent HMR events
250
- correlate renders [--window <ms>] Summarize recent render/update propagation evidence
251
- correlate errors --mapped Correlate mapped errors with recent HMR events
252
- diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
253
- diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
254
- diagnose propagation [--window <ms>]
255
- Diagnose likely update -> render -> error propagation
256
- logs Show dev server logs
257
-
258
- UTILITIES
259
- screenshot Save screenshot to temp file
260
- eval <script> Evaluate JavaScript in page context
261
- network [idx] List network requests or inspect one
262
-
263
- OPTIONS
264
- -h, --help Show this help message
249
+ return `
250
+ vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
251
+
252
+ USAGE
253
+ vite-browser <command> [options]
254
+
255
+ BROWSER CONTROL
256
+ open <url> [--cookies-json <file>] Launch browser and navigate
257
+ close Close browser and daemon
258
+ goto <url> Full-page navigation
259
+ back Go back in history
260
+ reload Reload current page
261
+
262
+ FRAMEWORK DETECTION
263
+ detect Detect framework (vue/react/svelte)
264
+
265
+ VUE COMMANDS
266
+ vue tree [id] Show Vue component tree or inspect component
267
+ vue pinia [store] Show Pinia stores or inspect specific store
268
+ vue router Show Vue Router information
269
+
270
+ REACT COMMANDS
271
+ react tree [id] Show React component tree or inspect component
272
+ react store list List detected Zustand stores
273
+ react store inspect <name> Show Zustand store state and actions
274
+ react hook health Show bundled React hook status
275
+ react hook inject Inject bundled React hook into current page
276
+ react commits [--limit <n>] Show recent React commit records
277
+ react commits clear Clear recorded React commit history
278
+
279
+ SVELTE COMMANDS
280
+ svelte tree [id] Show Svelte component tree or inspect component
281
+
282
+ VITE COMMANDS
283
+ vite restart Restart Vite dev server
284
+ vite hmr Show HMR summary
285
+ vite hmr trace [--limit <n>] Show HMR timeline
286
+ vite hmr clear Clear tracked HMR timeline
287
+ vite runtime Show Vite runtime status
288
+ vite module-graph [--filter <txt>] [--limit <n>]
289
+ Show loaded Vite module resources
290
+ vite module-graph trace [--filter <txt>] [--limit <n>]
291
+ Show module additions/removals since baseline
292
+ vite module-graph clear Clear module-graph baseline
293
+ errors Show build/runtime errors
294
+ errors --mapped Show errors with source-map mapping
295
+ errors --mapped --inline-source Include mapped source snippets
296
+ correlate errors [--window <ms>] Correlate current errors with recent HMR events
297
+ correlate renders [--window <ms>] Summarize recent render/update propagation evidence
298
+ correlate errors --mapped Correlate mapped errors with recent HMR events
299
+ diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
300
+ diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
301
+ diagnose propagation [--window <ms>]
302
+ Diagnose likely update -> render -> error propagation
303
+ logs Show dev server logs
304
+
305
+ UTILITIES
306
+ screenshot Save screenshot to temp file
307
+ eval <script> Evaluate JavaScript in page context
308
+ network [idx] List network requests or inspect one
309
+
310
+ OPTIONS
311
+ -h, --help Show this help message
265
312
  `;
266
313
  }
267
314
  function isEntrypoint(argv1) {
package/dist/client.js CHANGED
@@ -3,7 +3,7 @@ import { readFileSync, existsSync, rmSync } from "node:fs";
3
3
  import { spawn } from "node:child_process";
4
4
  import { setTimeout as sleep } from "node:timers/promises";
5
5
  import { fileURLToPath } from "node:url";
6
- import { socketPath, pidFile } from "./paths.js";
6
+ import { socketPath, pidFile, isWindows } from "./paths.js";
7
7
  export function createClientDeps() {
8
8
  const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
9
9
  const daemonPath = fileURLToPath(new URL(`./daemon${ext}`, import.meta.url));
@@ -86,7 +86,7 @@ export function no() {
86
86
  return false;
87
87
  }
88
88
  export function removeSocketFile(path, removeFile = rmSync) {
89
- if (process.platform === "win32")
89
+ if (isWindows)
90
90
  return;
91
91
  removeFile(path, { force: true });
92
92
  }
package/dist/daemon.js CHANGED
@@ -8,6 +8,7 @@ import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
8
8
  import { diagnosePropagation, formatPropagationDiagnosisReport } from "./diagnose-propagation.js";
9
9
  import { extractModules } from "./event-analysis.js";
10
10
  import { socketDir, socketPath, pidFile } from "./paths.js";
11
+ import { removeSocketFile } from "./client.js";
11
12
  import { correlateRenderPropagation, formatPropagationTraceReport } from "./trace.js";
12
13
  import { EventQueue } from "./event-queue.js";
13
14
  import * as networkLog from "./network.js";
@@ -70,6 +71,30 @@ export function createRunner(api = browser) {
70
71
  const data = await api.reactTree(cmd.id);
71
72
  return { ok: true, data };
72
73
  }
74
+ if (cmd.action === "react-store-list") {
75
+ const data = await api.reactStoreList();
76
+ return { ok: true, data };
77
+ }
78
+ if (cmd.action === "react-store-inspect") {
79
+ const data = await api.reactStoreInspect(cmd.store);
80
+ return { ok: true, data };
81
+ }
82
+ if (cmd.action === "react-hook-health") {
83
+ const data = await api.reactHookHealth();
84
+ return { ok: true, data };
85
+ }
86
+ if (cmd.action === "react-hook-inject") {
87
+ const data = await api.reactHookInject();
88
+ return { ok: true, data };
89
+ }
90
+ if (cmd.action === "react-commits") {
91
+ const data = await api.reactCommits(cmd.limit ?? 20);
92
+ return { ok: true, data };
93
+ }
94
+ if (cmd.action === "react-commits-clear") {
95
+ const data = await api.reactCommitsClear();
96
+ return { ok: true, data };
97
+ }
73
98
  // Svelte commands
74
99
  if (cmd.action === "svelte-tree") {
75
100
  const data = await api.svelteTree(cmd.id);
@@ -303,7 +328,7 @@ export function startDaemon() {
303
328
  networkLog.setEventQueue(eventQueue);
304
329
  const run = createRunner();
305
330
  mkdirSync(socketDir, { recursive: true, mode: 0o700 });
306
- removeSocketFile();
331
+ removeSocketFile(socketPath);
307
332
  rmSync(pidFile, { force: true });
308
333
  writeFileSync(pidFile, String(process.pid));
309
334
  const server = createServer((socket) => {
@@ -330,16 +355,10 @@ export function startDaemon() {
330
355
  process.exit(0);
331
356
  }
332
357
  function cleanup() {
333
- removeSocketFile();
358
+ removeSocketFile(socketPath);
334
359
  rmSync(pidFile, { force: true });
335
360
  }
336
361
  }
337
- function removeSocketFile() {
338
- // Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
339
- if (process.platform === "win32")
340
- return;
341
- rmSync(socketPath, { force: true });
342
- }
343
362
  if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
344
363
  startDaemon();
345
364
  }
@@ -74,8 +74,15 @@ export declare class EventQueue {
74
74
  private readonly maxSize;
75
75
  constructor(maxSize?: number);
76
76
  push(event: VBEvent): void;
77
+ /** Bubble the element at `idx` leftward to restore sorted order. */
78
+ private _insertionSort;
77
79
  /**
78
- * Return all events within the last `ms` milliseconds before `before`
80
+ * Return all events within the last `ms` milliseconds before `before`.
81
+ *
82
+ * Uses binary search to find the start index (O(log n)) then slices,
83
+ * which is significantly faster than a full linear scan for large queues.
84
+ * Events are maintained in insertion order, which for timestamped pushes
85
+ * is monotonically non-decreasing.
79
86
  */
80
87
  window(ms: number, before?: number): VBEvent[];
81
88
  /**
@@ -83,5 +90,7 @@ export declare class EventQueue {
83
90
  */
84
91
  ofType(type: VBEventType): VBEvent[];
85
92
  all(): VBEvent[];
93
+ /** Number of events currently stored */
94
+ get size(): number;
86
95
  clear(): void;
87
96
  }
@@ -9,13 +9,59 @@ export class EventQueue {
9
9
  if (this.events.length > this.maxSize) {
10
10
  this.events.shift();
11
11
  }
12
+ // Maintain sorted order by timestamp. Events almost always arrive
13
+ // in order, so the fast path (no swap) costs a single comparison.
14
+ const len = this.events.length;
15
+ if (len >= 2 && this.events[len - 1].timestamp < this.events[len - 2].timestamp) {
16
+ this._insertionSort(len - 1);
17
+ }
18
+ }
19
+ /** Bubble the element at `idx` leftward to restore sorted order. */
20
+ _insertionSort(idx) {
21
+ const events = this.events;
22
+ const item = events[idx];
23
+ let j = idx - 1;
24
+ while (j >= 0 && events[j].timestamp > item.timestamp) {
25
+ events[j + 1] = events[j];
26
+ j--;
27
+ }
28
+ events[j + 1] = item;
12
29
  }
13
30
  /**
14
- * Return all events within the last `ms` milliseconds before `before`
31
+ * Return all events within the last `ms` milliseconds before `before`.
32
+ *
33
+ * Uses binary search to find the start index (O(log n)) then slices,
34
+ * which is significantly faster than a full linear scan for large queues.
35
+ * Events are maintained in insertion order, which for timestamped pushes
36
+ * is monotonically non-decreasing.
15
37
  */
16
38
  window(ms, before = Date.now()) {
17
39
  const start = before - ms;
18
- return this.events.filter((e) => e.timestamp >= start && e.timestamp <= before);
40
+ const events = this.events;
41
+ const len = events.length;
42
+ if (len === 0)
43
+ return [];
44
+ // Binary search for the first event with timestamp >= start
45
+ let lo = 0;
46
+ let hi = len;
47
+ while (lo < hi) {
48
+ const mid = (lo + hi) >>> 1;
49
+ if (events[mid].timestamp < start) {
50
+ lo = mid + 1;
51
+ }
52
+ else {
53
+ hi = mid;
54
+ }
55
+ }
56
+ // Linear scan from lo for events <= before
57
+ // (in practice, `before` is usually Date.now() so most events qualify)
58
+ const result = [];
59
+ for (let i = lo; i < len; i++) {
60
+ if (events[i].timestamp > before)
61
+ break;
62
+ result.push(events[i]);
63
+ }
64
+ return result;
19
65
  }
20
66
  /**
21
67
  * Return all events of a given type
@@ -26,6 +72,10 @@ export class EventQueue {
26
72
  all() {
27
73
  return [...this.events];
28
74
  }
75
+ /** Number of events currently stored */
76
+ get size() {
77
+ return this.events.length;
78
+ }
29
79
  clear() {
30
80
  this.events = [];
31
81
  }
package/dist/paths.d.ts CHANGED
@@ -1,3 +1,28 @@
1
+ export declare const isWindows: boolean;
2
+ export declare const isLinux: boolean;
3
+ /**
4
+ * Sanitize a session name for safe use in file paths and pipe names.
5
+ */
6
+ export declare function sanitizeSession(name: string): string;
7
+ /**
8
+ * Resolve the base directory for vite-browser runtime files.
9
+ *
10
+ * Uses `~/.vite-browser` on all platforms.
11
+ * Falls back to `$TMPDIR/vite-browser-<uid>` when the home directory
12
+ * is not writable (e.g. some CI/container environments).
13
+ */
14
+ export declare function resolveSocketDir(): string;
1
15
  export declare const socketDir: string;
16
+ /**
17
+ * Socket path for the daemon.
18
+ *
19
+ * - Windows: uses a named pipe `\\.\pipe\vite-browser-<session>`
20
+ * - Unix: uses a Unix domain socket file in socketDir
21
+ *
22
+ * Note: Unix socket paths have a ~104-char limit on macOS and ~108 on
23
+ * Linux. The `~/.vite-browser/<session>.sock` path is well within
24
+ * that range. The tmpdir fallback may produce longer paths; we keep
25
+ * them short by using numeric uid.
26
+ */
2
27
  export declare const socketPath: string;
3
28
  export declare const pidFile: string;