fix: global error handling js package (#2553)

This commit is contained in:
Anshuman Pandey
2024-04-29 18:41:11 +05:30
committed by GitHub
parent 686031e61a
commit 8ef6dc0a07
2 changed files with 132 additions and 79 deletions

View File

@@ -1,37 +1,57 @@
import { TFormbricksApp } from "@formbricks/js-core/app";
import { TFormbricksWebsite } from "@formbricks/js-core/website";
import { Result, wrapThrowsAsync } from "../../types/errorHandlers";
declare global {
interface Window {
formbricks: TFormbricksApp | TFormbricksWebsite;
}
}
let sdkLoadingPromise: Promise<void> | null = null;
let isErrorLoadingSdk = false;
async function loadSDK(apiHost: string) {
// load the sdk, return the result
async function loadFormbricksAppSDK(apiHost: string): Promise<Result<void>> {
if (!window.formbricks) {
const res = await fetch(`${apiHost}/api/packages/app`);
if (!res.ok) throw new Error("Failed to load Formbricks App SDK");
// failed to fetch the app package
if (!res.ok) {
return { ok: false, error: new Error("Failed to load Formbricks App SDK") };
}
const sdkScript = await res.text();
const scriptTag = document.createElement("script");
scriptTag.innerHTML = sdkScript;
document.head.appendChild(scriptTag);
return new Promise<void>((resolve, reject) => {
const checkInterval = setInterval(() => {
if (window.formbricks) {
const getFormbricks = async () =>
new Promise<void>((resolve, reject) => {
const checkInterval = setInterval(() => {
if (window.formbricks) {
clearInterval(checkInterval);
resolve();
}
}, 100);
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}
}, 100);
setTimeout(() => {
clearInterval(checkInterval);
reject(new Error("Formbricks SDK loading timed out"));
}, 10000);
});
reject(new Error("Formbricks App SDK loading timed out"));
}, 10000);
});
try {
await getFormbricks();
return { ok: true, data: undefined };
} catch (error: any) {
// formbricks loading failed, return the error
return {
ok: false,
error: new Error(error.message ?? "Failed to load Formbricks App SDK"),
};
}
}
return { ok: true, data: undefined };
}
type FormbricksAppMethods = {
@@ -41,31 +61,34 @@ type FormbricksAppMethods = {
const formbricksProxyHandler: ProxyHandler<TFormbricksApp> = {
get(_target, prop, _receiver) {
return async (...args: any[]) => {
if (!window.formbricks && !sdkLoadingPromise && !isErrorLoadingSdk) {
const { apiHost } = args[0];
sdkLoadingPromise = loadSDK(apiHost).catch((error) => {
console.error(`🧱 Formbricks - Error loading SDK: ${error}`);
sdkLoadingPromise = null;
isErrorLoadingSdk = true;
return;
});
}
if (isErrorLoadingSdk) {
return;
}
if (sdkLoadingPromise) {
await sdkLoadingPromise;
}
if (!window.formbricks) {
throw new Error("Formbricks App SDK is not available");
if (prop !== "init") {
console.error(
"🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
);
return;
}
// still need to check if the apiHost is passed
if (!args[0]) {
console.error("🧱 Formbricks - Global error: You need to pass the apiHost as the first argument");
return;
}
const { apiHost } = args[0];
const loadSDKResult = await wrapThrowsAsync(loadFormbricksAppSDK)(apiHost);
if (!loadSDKResult.ok) {
console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
return;
}
}
// @ts-expect-error
if (typeof window.formbricks[prop as FormbricksAppMethods] !== "function") {
console.error(`🧱 Formbricks App SDK does not support method ${String(prop)}`);
if (window.formbricks && typeof window.formbricks[prop as FormbricksAppMethods] !== "function") {
console.error(
`🧱 Formbricks - Global error: Formbricks App SDK does not support method ${String(prop)}`
);
return;
}
@@ -73,8 +96,8 @@ const formbricksProxyHandler: ProxyHandler<TFormbricksApp> = {
// @ts-expect-error
return (window.formbricks[prop as FormbricksAppMethods] as Function)(...args);
} catch (error) {
console.error(error);
throw error;
console.error(`Something went wrong: ${error}`);
return;
}
};
},

View File

@@ -1,30 +1,57 @@
import { TFormbricksApp } from "@formbricks/js-core/app";
import { TFormbricksWebsite } from "@formbricks/js-core/website";
let sdkLoadingPromise: Promise<void> | null = null;
let isErrorLoadingSdk = false;
import { Result, wrapThrowsAsync } from "../../types/errorHandlers";
async function loadSDK(apiHost: string) {
declare global {
interface Window {
formbricks: TFormbricksApp | TFormbricksWebsite;
}
}
// load the sdk, return the result
async function loadFormbricksWebsiteSDK(apiHost: string): Promise<Result<void>> {
if (!window.formbricks) {
const res = await fetch(`${apiHost}/api/packages/website`);
if (!res.ok) throw new Error("Failed to load Formbricks Website SDK");
// failed to fetch the app package
if (!res.ok) {
return { ok: false, error: new Error("Failed to load Formbricks Website SDK") };
}
const sdkScript = await res.text();
const scriptTag = document.createElement("script");
scriptTag.innerHTML = sdkScript;
document.head.appendChild(scriptTag);
return new Promise<void>((resolve, reject) => {
const checkInterval = setInterval(() => {
if (window.formbricks) {
const getFormbricks = async () =>
new Promise<void>((resolve, reject) => {
const checkInterval = setInterval(() => {
if (window.formbricks) {
clearInterval(checkInterval);
resolve();
}
}, 100);
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}
}, 100);
setTimeout(() => {
clearInterval(checkInterval);
reject(new Error("Formbricks SDK loading timed out"));
}, 10000);
});
reject(new Error("Formbricks Website SDK loading timed out"));
}, 10000);
});
try {
await getFormbricks();
return { ok: true, data: undefined };
} catch (error: any) {
// formbricks loading failed, return the error
return {
ok: false,
error: new Error(error.message ?? "Failed to load Formbricks Website SDK"),
};
}
}
return { ok: true, data: undefined };
}
type FormbricksWebsiteMethods = {
@@ -34,42 +61,45 @@ type FormbricksWebsiteMethods = {
const formbricksProxyHandler: ProxyHandler<TFormbricksWebsite> = {
get(_target, prop, _receiver) {
return async (...args: any[]) => {
if (!window.formbricks && !sdkLoadingPromise && !isErrorLoadingSdk) {
const { apiHost } = args[0];
sdkLoadingPromise = loadSDK(apiHost).catch((error) => {
console.error(`🧱 Formbricks - Error loading SDK: ${error}`);
sdkLoadingPromise = null;
isErrorLoadingSdk = true;
return;
});
}
if (isErrorLoadingSdk) {
return;
}
if (sdkLoadingPromise) {
await sdkLoadingPromise;
}
if (!window.formbricks) {
throw new Error("Formbricks Website SDK is not available");
if (prop !== "init") {
console.error(
"🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
);
return;
}
// still need to check if the apiHost is passed
if (!args[0]) {
console.error("🧱 Formbricks - Global error: You need to pass the apiHost as the first argument");
return;
}
const { apiHost } = args[0];
const loadSDKResult = await wrapThrowsAsync(loadFormbricksWebsiteSDK)(apiHost);
if (!loadSDKResult.ok) {
console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
return;
}
}
if (typeof window.formbricks[prop as FormbricksWebsiteMethods] !== "function") {
console.error(`🧱 Formbricks Website SDK does not support method ${String(prop)}`);
if (window.formbricks && typeof window.formbricks[prop as FormbricksWebsiteMethods] !== "function") {
console.error(
`🧱 Formbricks - Global error: Formbricks Website SDK does not support method ${String(prop)}`
);
return;
}
try {
return (window.formbricks[prop as FormbricksWebsiteMethods] as Function)(...args);
} catch (error) {
console.error(error);
throw error;
console.error(`🧱 Formbricks - Global error: Something went wrong: ${error}`);
return;
}
};
},
};
const formbricksWebsite: TFormbricksWebsite = new Proxy({} as TFormbricksWebsite, formbricksProxyHandler);
export default formbricksWebsite;
const formbricksApp: TFormbricksWebsite = new Proxy({} as TFormbricksWebsite, formbricksProxyHandler);
export default formbricksApp;