diff --git a/examples/demo/src/app/token-staleness/page.tsx b/examples/demo/src/app/token-staleness/page.tsx index 587ef551f5..dba232a47a 100644 --- a/examples/demo/src/app/token-staleness/page.tsx +++ b/examples/demo/src/app/token-staleness/page.tsx @@ -23,7 +23,7 @@ export default function TokenStalenessPage() { const [newDisplayName, setNewDisplayName] = useState(''); // Get partial user from token (can be stale compared to actual user data) - const partialUserFromToken = app.usePartialUser({ from: 'token', or: 'anonymous' }); + const partialUserFromToken = app.usePartialUser({ from: 'token', or: 'anonymous-if-exists' }); // Get raw tokens const tokens = user?.currentSession.useTokens(); diff --git a/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts b/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts index 9e6b3e20d6..628bf107f5 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts @@ -552,17 +552,22 @@ export class _StackClientAppImplIncomplete => { + + const getAnalyticsSession = async (): Promise => { this._ensurePersistentTokenStore(); - return await (await this.getUser({ or: "anonymous" })).getAccessToken(); + const partialUser = await this.getPartialUser({ from: 'token', or: 'anonymous-if-exists' }); + if (partialUser) { + return await this._getSession(); + } + const anonUser = await this.getUser({ or: "anonymous" }); + return anonUser._internalSession; }; if (isBrowserLike() && this._analyticsOptions?.replays?.enabled === true) { this._sessionRecorder = new SessionRecorder({ projectId: this.projectId, - getAccessToken: getAnalyticsAccessToken, sendBatch: async (body, opts) => { - return await this._interface.sendSessionReplayBatch(body, await this._getSession(), opts); + return await this._interface.sendSessionReplayBatch(body, await getAnalyticsSession(), opts); }, }, this._analyticsOptions.replays); this._sessionRecorder.start(); @@ -571,9 +576,8 @@ export class _StackClientAppImplIncomplete { - return await this._interface.sendAnalyticsEventBatch(body, await this._getSession(), opts); + return await this._interface.sendAnalyticsEventBatch(body, await getAnalyticsSession(), opts); }, }); this._eventTracker.start(); @@ -2686,7 +2690,7 @@ export class _StackClientAppImplIncomplete { const sentBodies: string[] = []; const tracker = new EventTracker({ projectId: "internal", - getAccessToken: async () => "access-token", sendBatch: async (body) => { sentBodies.push(body); return Result.ok(new Response()); @@ -56,7 +53,7 @@ describe("EventTracker", () => { clientY: 34, })); - await advancePastAccessTokenRefresh(); + await advancePastFlush(); expect(getSentEventTypes(sentBodies)).toMatchInlineSnapshot(` [ @@ -79,7 +76,6 @@ describe("EventTracker", () => { const sentBodies: string[] = []; const tracker = new EventTracker({ projectId: "internal", - getAccessToken: async () => "access-token", sendBatch: async (body) => { sentBodies.push(body); return Result.ok(new Response()); @@ -90,7 +86,7 @@ describe("EventTracker", () => { tracker.start(); window.history.pushState({}, "", "/projects/test-project"); - await advancePastAccessTokenRefresh(); + await advancePastFlush(); expect(getSentEventTypes(sentBodies)).toMatchInlineSnapshot(` [ diff --git a/packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts b/packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts index c46a652d29..1e2b0a1115 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts @@ -29,7 +29,6 @@ function hasHistoryMethods(value: unknown): value is { pushState: History["pushS export type EventTrackerDeps = { projectId: string, - getAccessToken: () => Promise, sendBatch: (body: string, options: { keepalive: boolean }) => Promise>, }; @@ -46,7 +45,6 @@ export class EventTracker { private _flushTimer: ReturnType | null = null; private _events: TrackedEvent[] = []; private _approxBytes = 0; - private _lastKnownAccessToken: string | null = null; private _lastUrl: string | null = null; private readonly _sessionReplaySegmentId: string; private readonly _deps: EventTrackerDeps; @@ -86,7 +84,7 @@ export class EventTracker { clearInterval(this._flushTimer); this._flushTimer = null; } - runAsynchronously(() => this._flush({ keepalive: true }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: true })); this._teardown(); } @@ -99,7 +97,7 @@ export class EventTracker { this._events.push(event); this._approxBytes += JSON.stringify(event).length; if (this._events.length >= MAX_EVENTS_PER_BATCH || this._approxBytes >= MAX_APPROX_BYTES_PER_BATCH) { - runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: false })); } } @@ -226,7 +224,7 @@ export class EventTracker { } private readonly _onPageHide = () => { - runAsynchronously(() => this._flush({ keepalive: true }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: true })); }; private _setupPageHideListeners() { @@ -265,7 +263,6 @@ export class EventTracker { } private async _flush(options: { keepalive: boolean }) { - if (!this._lastKnownAccessToken) return; if (this._events.length === 0) return; const nowMs = Date.now(); @@ -298,14 +295,8 @@ export class EventTracker { private _tick() { if (this._cancelled) return; - - runAsynchronously(async () => { - this._lastKnownAccessToken = await this._deps.getAccessToken(); - }, { noErrorLogging: true }); - - const hasAuth = !!this._lastKnownAccessToken; - if (hasAuth && this._events.length > 0) { - runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true }); + if (this._events.length > 0) { + runAsynchronously(() => this._flush({ keepalive: false })); } } } diff --git a/packages/template/src/lib/stack-app/apps/implementations/session-replay.ts b/packages/template/src/lib/stack-app/apps/implementations/session-replay.ts index 2eb497bca6..be3cf89d8a 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/session-replay.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/session-replay.ts @@ -130,7 +130,6 @@ export function getOrRotateSession(options: { key: string, nowMs: number }): Sto export type SessionRecorderDeps = { projectId: string, - getAccessToken: () => Promise, sendBatch: (body: string, options: { keepalive: boolean }) => Promise>, }; @@ -148,7 +147,6 @@ export class SessionRecorder { private _lastBrowserSessionId: string | null = null; private _takingSnapshot = false; private _flushInProgress = false; - private _lastKnownAccessToken: string | null = null; private readonly _sessionReplaySegmentId: string; private readonly _storageKey: string; private readonly _deps: SessionRecorderDeps; @@ -172,7 +170,7 @@ export class SessionRecorder { // Kick off rrweb recording runAsynchronously(() => this._startRecording(), { noErrorLogging: true }); - // Periodic flush + token refresh + // Periodic flush this._flushTimer = setInterval(() => this._tick(), FLUSH_INTERVAL_MS); } @@ -183,7 +181,7 @@ export class SessionRecorder { this._flushTimer = null; } // Flush remaining events before cleanup - runAsynchronously(() => this._flush({ keepalive: true }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: true })); this._stopCurrentRecording(); } @@ -202,7 +200,6 @@ export class SessionRecorder { } private async _flush(options: { keepalive: boolean }) { - if (!this._lastKnownAccessToken) return; if (this._events.length === 0) return; // Prevent concurrent in-flight HTTP requests. When a flush is already // in-flight, a second batch could race on the server (both call @@ -287,7 +284,7 @@ export class SessionRecorder { this._events.push(event); this._approxBytes += JSON.stringify(event).length; if (this._events.length >= MAX_EVENTS_PER_BATCH || this._approxBytes >= MAX_APPROX_BYTES_PER_BATCH) { - runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: false })); } }, maskAllInputs: this._replayOptions.maskAllInputs ?? true, @@ -298,7 +295,7 @@ export class SessionRecorder { this._recording = true; const onPageHide = () => { - runAsynchronously(() => this._flush({ keepalive: true }), { noErrorLogging: true }); + runAsynchronously(() => this._flush({ keepalive: true })); }; window.addEventListener("pagehide", onPageHide); document.addEventListener("visibilitychange", onPageHide); @@ -324,15 +321,8 @@ export class SessionRecorder { private _tick() { if (this._cancelled) return; - - // Refresh the cached access token (async, fire-and-forget for this tick) - runAsynchronously(async () => { - this._lastKnownAccessToken = await this._deps.getAccessToken(); - }, { noErrorLogging: true }); - - const hasAuth = !!this._lastKnownAccessToken; - if (hasAuth && this._events.length > 0) { - runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true }); + if (this._events.length > 0) { + runAsynchronously(() => this._flush({ keepalive: false })); } } } diff --git a/packages/template/src/lib/stack-app/common.ts b/packages/template/src/lib/stack-app/common.ts index 841c481b1b..bce4c509a9 100644 --- a/packages/template/src/lib/stack-app/common.ts +++ b/packages/template/src/lib/stack-app/common.ts @@ -61,7 +61,7 @@ export type ConvexCtx = export type GetCurrentPartialUserOptions = & { - or?: 'return-null' | 'anonymous', // note: unlike normal getUser, 'anonymous' still returns null sometimes (eg. if no token is present) + or?: 'return-null' | 'anonymous-if-exists', // note: unlike normal getUser, 'anonymous' still returns null sometimes (eg. if no token is present) tokenStore?: TokenStoreInit, } & (