Merge branch 'llm-mcp-flow' of https://github.com/stack-auth/stack-auth into llm-mcp-flow

This commit is contained in:
Aadesh Kheria 2026-04-13 09:49:36 -07:00
commit a630be1e57
3 changed files with 110 additions and 6 deletions

View File

@ -1 +1 @@
Please compare `dev` to `main` and ensure that all migrations are backwards compatible. In what ways could breakage occur? Report the result to me in detail.
Please compare `dev` to `main` and ensure that all migrations are backwards compatible. In what ways could breakage occur? Report the result to me in detail. Anything else that's scary that could occur, or that we should think about while migrating?

View File

@ -0,0 +1,105 @@
// @vitest-environment jsdom
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { afterEach, describe, expect, it, vi } from "vitest";
import { EventTracker } from "./event-tracker";
async function advancePastAccessTokenRefresh() {
await vi.advanceTimersByTimeAsync(10_000);
await Promise.resolve();
await vi.advanceTimersByTimeAsync(10_000);
await Promise.resolve();
}
function getSentEventTypes(sentBodies: string[]) {
const [body] = sentBodies;
const payload = JSON.parse(body);
if (typeof payload !== "object" || payload === null || !("events" in payload) || !Array.isArray(payload.events)) {
throw new Error("Expected analytics batch payload to include an events array.");
}
return (payload.events as { event_type: string }[]).map((event) => event.event_type);
}
describe("EventTracker", () => {
afterEach(() => {
vi.useRealTimers();
});
it("captures events when browser globals are exposed as accessor descriptors", async () => {
vi.useFakeTimers();
document.body.innerHTML = "<button>Open project</button>";
const screenDescriptor = Object.getOwnPropertyDescriptor(window, "screen");
const historyDescriptor = Object.getOwnPropertyDescriptor(window, "history");
expect(screenDescriptor?.value).toBeUndefined();
expect(historyDescriptor?.value).toBeUndefined();
expect(screenDescriptor?.get).toBeTypeOf("function");
expect(historyDescriptor?.get).toBeTypeOf("function");
const sentBodies: string[] = [];
const tracker = new EventTracker({
projectId: "internal",
getAccessToken: async () => "access-token",
sendBatch: async (body) => {
sentBodies.push(body);
return Result.ok(new Response());
},
});
try {
tracker.start();
document.querySelector("button")?.dispatchEvent(new MouseEvent("click", {
bubbles: true,
clientX: 12,
clientY: 34,
}));
await advancePastAccessTokenRefresh();
expect(getSentEventTypes(sentBodies)).toMatchInlineSnapshot(`
[
"$page-view",
"$click",
]
`);
} finally {
tracker.stop();
}
});
it("captures client-side navigations when history is exposed as an accessor descriptor", async () => {
vi.useFakeTimers();
const historyDescriptor = Object.getOwnPropertyDescriptor(window, "history");
expect(historyDescriptor?.value).toBeUndefined();
expect(historyDescriptor?.get).toBeTypeOf("function");
const sentBodies: string[] = [];
const tracker = new EventTracker({
projectId: "internal",
getAccessToken: async () => "access-token",
sendBatch: async (body) => {
sentBodies.push(body);
return Result.ok(new Response());
},
});
try {
tracker.start();
window.history.pushState({}, "", "/projects/test-project");
await advancePastAccessTokenRefresh();
expect(getSentEventTypes(sentBodies)).toMatchInlineSnapshot(`
[
"$page-view",
"$page-view",
]
`);
} finally {
tracker.stop();
}
});
});

View File

@ -62,13 +62,12 @@ export class EventTracker {
start() {
if (this._started) return;
if (!isBrowserLike()) return;
const screenObject = Object.getOwnPropertyDescriptor(window, "screen")?.value;
if (
typeof window.addEventListener !== "function"
|| typeof window.removeEventListener !== "function"
|| typeof document.addEventListener !== "function"
|| typeof document.removeEventListener !== "function"
|| !hasScreenDimensions(screenObject)
|| !hasScreenDimensions(window.screen)
) {
return;
}
@ -105,7 +104,7 @@ export class EventTracker {
}
private _capturePageView(entryType: "initial" | "push" | "replace" | "pop") {
const screenObject = Object.getOwnPropertyDescriptor(window, "screen")?.value;
const screenObject = window.screen;
if (!hasScreenDimensions(screenObject)) {
return;
}
@ -134,7 +133,7 @@ export class EventTracker {
private _setupPageViewCapture() {
// Fire initial page-view
this._capturePageView("initial");
const historyObject = Object.getOwnPropertyDescriptor(window, "history")?.value;
const historyObject = window.history;
if (!hasHistoryMethods(historyObject)) {
return;
}
@ -246,7 +245,7 @@ export class EventTracker {
}
// Restore history methods
const historyObject = Object.getOwnPropertyDescriptor(window, "history")?.value;
const historyObject = window.history;
if (hasHistoryMethods(historyObject)) {
if (this._originalPushState) {
historyObject.pushState = this._originalPushState;