mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-21 14:29:20 -06:00
fix: survey timeout during routing (#4515)
This commit is contained in:
@@ -108,9 +108,6 @@ const AppPage = ({}) => {
|
||||
Look at the logs to understand how the widget works.{" "}
|
||||
<strong className="dark:text-white">Open your browser console</strong> to see the logs.
|
||||
</p>
|
||||
{/* <div className="max-h-[40vh] overflow-y-auto py-4">
|
||||
<LogsContainer />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,17 +3,21 @@ import { trackNoCodeAction } from "./actions";
|
||||
import { Config } from "./config";
|
||||
import { ErrorHandler, NetworkError, Result, err, match, okVoid } from "./errors";
|
||||
import { Logger } from "./logger";
|
||||
import { TimeoutStack } from "./timeout-stack";
|
||||
import { evaluateNoCodeConfigClick, handleUrlFilters } from "./utils";
|
||||
import { setIsSurveyRunning } from "./widget";
|
||||
|
||||
const appConfig = Config.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
const errorHandler = ErrorHandler.getInstance();
|
||||
const timeoutStack = TimeoutStack.getInstance();
|
||||
|
||||
// Event types for various listeners
|
||||
const events = ["hashchange", "popstate", "pushstate", "replacestate", "load"];
|
||||
|
||||
// Page URL Event Handlers
|
||||
let arePageUrlEventListenersAdded = false;
|
||||
let isHistoryPatched = false;
|
||||
|
||||
export const checkPageUrl = async (): Promise<Result<void, NetworkError>> => {
|
||||
logger.debug(`Checking page url: ${window.location.href}`);
|
||||
@@ -27,19 +31,48 @@ export const checkPageUrl = async (): Promise<Result<void, NetworkError>> => {
|
||||
const urlFilters = event.noCodeConfig?.urlFilters ?? [];
|
||||
const isValidUrl = handleUrlFilters(urlFilters);
|
||||
|
||||
if (!isValidUrl) continue;
|
||||
if (isValidUrl) {
|
||||
const trackResult = await trackNoCodeAction(event.name);
|
||||
|
||||
const trackResult = await trackNoCodeAction(event.name);
|
||||
if (trackResult.ok !== true) return err(trackResult.error);
|
||||
if (trackResult.ok !== true) {
|
||||
return err(trackResult.error);
|
||||
}
|
||||
} else {
|
||||
const scheduledTimeouts = timeoutStack.getTimeouts();
|
||||
|
||||
const scheduledTimeout = scheduledTimeouts.find((timeout) => timeout.event === event.name);
|
||||
// If invalid, clear if it's scheduled
|
||||
if (scheduledTimeout) {
|
||||
timeoutStack.remove(scheduledTimeout.timeoutId);
|
||||
setIsSurveyRunning(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return okVoid();
|
||||
};
|
||||
|
||||
const checkPageUrlWrapper = () => checkPageUrl();
|
||||
const checkPageUrlWrapper = () => {
|
||||
checkPageUrl();
|
||||
};
|
||||
|
||||
export const addPageUrlEventListeners = (): void => {
|
||||
if (typeof window === "undefined" || arePageUrlEventListenersAdded) return;
|
||||
|
||||
// Monkey patch history methods if not already done
|
||||
if (!isHistoryPatched) {
|
||||
const originalPushState = history.pushState;
|
||||
|
||||
history.pushState = function (...args) {
|
||||
const returnValue = originalPushState.apply(this, args);
|
||||
const event = new Event("pushstate");
|
||||
window.dispatchEvent(event);
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
isHistoryPatched = true;
|
||||
}
|
||||
|
||||
events.forEach((event) => window.addEventListener(event, checkPageUrlWrapper));
|
||||
arePageUrlEventListenersAdded = true;
|
||||
};
|
||||
|
||||
43
packages/js-core/src/lib/timeout-stack.ts
Normal file
43
packages/js-core/src/lib/timeout-stack.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export class TimeoutStack {
|
||||
private static instance: TimeoutStack;
|
||||
// private timeouts: number[] = [];
|
||||
private timeouts: { event: string; timeoutId: number }[] = [];
|
||||
|
||||
// Private constructor to prevent direct instantiation
|
||||
private constructor() {}
|
||||
|
||||
// Retrieve the singleton instance of TimeoutStack
|
||||
public static getInstance(): TimeoutStack {
|
||||
if (!TimeoutStack.instance) {
|
||||
TimeoutStack.instance = new TimeoutStack();
|
||||
}
|
||||
return TimeoutStack.instance;
|
||||
}
|
||||
|
||||
// Add a new timeout ID to the stack
|
||||
public add(event: string, timeoutId: number): void {
|
||||
this.timeouts.push({ event, timeoutId });
|
||||
}
|
||||
|
||||
// Clear a specific timeout and remove it from the stack
|
||||
public remove(timeoutId: number): void {
|
||||
clearTimeout(timeoutId);
|
||||
this.timeouts = this.timeouts.filter((timeout) => timeout.timeoutId !== timeoutId);
|
||||
}
|
||||
|
||||
// Clear all timeouts and reset the stack
|
||||
public clear(): void {
|
||||
for (const timeout of this.timeouts) {
|
||||
clearTimeout(timeout.timeoutId);
|
||||
}
|
||||
this.timeouts = [];
|
||||
}
|
||||
|
||||
// Get the current stack of timeout IDs
|
||||
public getTimeouts(): {
|
||||
event: string;
|
||||
timeoutId: number;
|
||||
}[] {
|
||||
return this.timeouts;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
import { Config } from "./config";
|
||||
import { CONTAINER_ID } from "./constants";
|
||||
import { Logger } from "./logger";
|
||||
import { TimeoutStack } from "./timeout-stack";
|
||||
import {
|
||||
filterSurveys,
|
||||
getDefaultLanguageCode,
|
||||
@@ -23,6 +24,8 @@ import {
|
||||
|
||||
const config = Config.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
const timeoutStack = TimeoutStack.getInstance();
|
||||
|
||||
let isSurveyRunning = false;
|
||||
let setIsError = (_: boolean) => {};
|
||||
let setIsResponseSendingFinished = (_: boolean) => {};
|
||||
@@ -62,6 +65,7 @@ const renderWidget = async (
|
||||
logger.debug("A survey is already running. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSurveyRunning(true);
|
||||
|
||||
if (survey.delay) {
|
||||
@@ -108,7 +112,7 @@ const renderWidget = async (
|
||||
const isBrandingEnabled = project.inAppSurveyBranding;
|
||||
const formbricksSurveys = await loadFormbricksSurveysExternally();
|
||||
|
||||
setTimeout(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
formbricksSurveys.renderSurveyModal({
|
||||
survey,
|
||||
isBrandingEnabled,
|
||||
@@ -236,6 +240,10 @@ const renderWidget = async (
|
||||
hiddenFieldsRecord: hiddenFields,
|
||||
});
|
||||
}, survey.delay * 1000);
|
||||
|
||||
if (action) {
|
||||
timeoutStack.add(action, timeoutId as unknown as number);
|
||||
}
|
||||
};
|
||||
|
||||
export const closeSurvey = async (): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user