diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 8d244f7980..2c27b4dc68 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -3,12 +3,26 @@ import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps type Props = Record & DocumentProps; function toRunBeforeReactOnClient() { - window.sessionStorage.setItem("calEmbedMode", String(location.search.includes("embed="))); + const calEmbedMode = location.search.includes("embed="); + try { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage + window.sessionStorage.setItem("calEmbedMode", String(calEmbedMode)); + } catch (e) {} + window.isEmbed = () => { - return window.sessionStorage.getItem("calEmbedMode") === "true"; + try { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage + return window.sessionStorage.getItem("calEmbedMode") === "true"; + } catch (e) {} + // If we can't use sessionStorage to retrieve embed mode, just use the variable. It would fail to detect embed if page in iframe reloads without embed query param in it. + return calEmbedMode; }; + window.resetEmbedStatus = () => { - window.sessionStorage.removeItem("calEmbedMode"); + try { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage + window.sessionStorage.removeItem("calEmbedMode"); + } catch (e) {} }; window.getEmbedTheme = () => { diff --git a/apps/web/pages/getting-started.tsx b/apps/web/pages/getting-started.tsx index cf17ef320b..2b88b2b7ab 100644 --- a/apps/web/pages/getting-started.tsx +++ b/apps/web/pages/getting-started.tsx @@ -40,6 +40,9 @@ import { UsernameAvailability } from "@components/ui/UsernameAvailability"; import { TRPCClientErrorLike } from "@trpc/client"; +// Embed isn't applicable to onboarding, so ignore the rule +/* eslint-disable @calcom/eslint/avoid-web-storage */ + type ScheduleFormValues = { schedule: ScheduleType; }; diff --git a/apps/web/pages/settings/profile.tsx b/apps/web/pages/settings/profile.tsx index 2faf5b0159..dd2f1e9140 100644 --- a/apps/web/pages/settings/profile.tsx +++ b/apps/web/pages/settings/profile.tsx @@ -179,6 +179,8 @@ function SettingsView(props: ComponentProps & { localeProp: str const enteredTimeFormat = selectedTimeFormat.value; // Write time format to localStorage if available + // Embed isn't applicable to profile pages. So ignore the rule + // eslint-disable-next-line @calcom/eslint/avoid-web-storage window.localStorage.setItem("timeOption.is24hClock", selectedTimeFormat.value === 12 ? "false" : "true"); // TODO: Add validation diff --git a/apps/web/playwright/onboarding.test.ts b/apps/web/playwright/onboarding.test.ts index 4b523ebbd9..15f6cd0860 100644 --- a/apps/web/playwright/onboarding.test.ts +++ b/apps/web/playwright/onboarding.test.ts @@ -36,6 +36,7 @@ test.describe("Onboarding", () => { */ test.fixme(); await page.addInitScript(() => { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage window.localStorage.setItem("username", "alwaysavailable"); }, {}); // Try to go getting started with a available username diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 22b5ae8ce6..66b87556e8 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -3,6 +3,7 @@ const recommended = { parserOptions: { sourceType: "module" }, rules: { "@calcom/eslint/deprecated-imports": "error", + "@calcom/eslint/avoid-web-storage": "error", }, }; diff --git a/packages/eslint-plugin/src/rules/avoid-web-storage.ts b/packages/eslint-plugin/src/rules/avoid-web-storage.ts new file mode 100644 index 0000000000..57c7d561c7 --- /dev/null +++ b/packages/eslint-plugin/src/rules/avoid-web-storage.ts @@ -0,0 +1,45 @@ +import { ESLintUtils } from "@typescript-eslint/utils"; + +const createRule = ESLintUtils.RuleCreator((name) => `https://developer.cal.com/eslint/rule/${name}`); +const rule = createRule({ + create(context) { + return { + CallExpression(node) { + const webStorages = ["localStorage", "sessionStorage"]; + const callee = node.callee; + if ( + // Can't figure out how to fix this TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + callee.object?.object?.name === "window" && + // Can't figure out how to fix this TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + webStorages.includes(node?.callee?.object?.property?.name) + ) { + return context.report({ + node: node, + loc: node.loc, + messageId: "possible-issue-with-embed", + }); + } + }, + }; + }, + name: "avoid-web-storage", + meta: { + fixable: "code", + docs: { + description: "Avoid deprecated imports", + recommended: "warn", + }, + messages: { + "possible-issue-with-embed": `Be aware that accessing localStorage/sessionStorage throws error in Chrome Incognito mode when embed is in cross domain context. If you know what you are doing, \`import {localStorage, sessionStorage} from "@calcom/lib/webstorage"\` for safe usage. See https://github.com/calcom/cal.com/issues/2618`, + }, + type: "suggestion", + schema: [], + }, + defaultOptions: [], +}); + +export default rule; diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 2ed38d161b..b199eab87e 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -4,4 +4,5 @@ import type { ESLint } from "eslint"; export default { "my-first-rule": require("./my-first-rule").default, "deprecated-imports": require("./deprecated-imports").default, + "avoid-web-storage": require("./avoid-web-storage").default, } as ESLint.Plugin["rules"]; diff --git a/packages/lib/webstorage.ts b/packages/lib/webstorage.ts index 50eeadc57b..47cbd00e3b 100644 --- a/packages/lib/webstorage.ts +++ b/packages/lib/webstorage.ts @@ -2,6 +2,7 @@ export const localStorage = { getItem(key: string) { try { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage return window.localStorage.getItem(key); } catch (e) { // In case storage is restricted. Possible reasons @@ -11,6 +12,7 @@ export const localStorage = { }, setItem(key: string, value: string) { try { + // eslint-disable-next-line @calcom/eslint/avoid-web-storage window.localStorage.setItem(key, value); } catch (e) { // In case storage is restricted. Possible reasons