Revert "fix: handle null/undefined tracking IDs in analytics schemas (#27625)"
This reverts commit 04e828358a.
This commit is contained in:
@@ -1,15 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Helper to create schemas that accept nullish values but always output string.
|
||||
* Used for tracking IDs that may be null/undefined in the database.
|
||||
*/
|
||||
const nullishString = () =>
|
||||
z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim() : ""));
|
||||
|
||||
// URL schema: string input, validates http/https (kept as original to maintain type compatibility)
|
||||
export const safeUrlSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
@@ -26,19 +16,23 @@ export const safeUrlSchema = z
|
||||
{ message: "Invalid URL format. Must be a valid http or https URL" }
|
||||
);
|
||||
|
||||
// Alphanumeric ID schema - accepts nullish for tracking IDs
|
||||
export const alphanumericIdSchema = nullishString().refine(
|
||||
(val) => !val || /^[A-Za-z0-9_-]+$/.test(val),
|
||||
{ message: "Invalid ID format. Expected alphanumeric characters, underscores, or hyphens" }
|
||||
);
|
||||
// Schema for tracking IDs that should only contain letters, numbers, underscores, and hyphens
|
||||
export const alphanumericIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9_-]+$/.test(val), {
|
||||
message: "Invalid ID format. Expected alphanumeric characters, underscores, or hyphens",
|
||||
});
|
||||
|
||||
// Numeric ID schema - accepts nullish for tracking IDs
|
||||
export const numericIdSchema = nullishString().refine(
|
||||
(val) => !val || /^[0-9]+$/.test(val),
|
||||
{ message: "Invalid ID format. Expected a numeric ID" }
|
||||
);
|
||||
// Schema for tracking IDs that should only contain digits
|
||||
export const numericIdSchema = z
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[0-9]+$/.test(val), {
|
||||
message: "Invalid ID format. Expected a numeric ID",
|
||||
});
|
||||
|
||||
// Factory for prefixed ID schemas (GTM-, G-, etc.)
|
||||
// Factory for creating prefixed ID schemas (GTM, GA4, Fathom, etc.)
|
||||
export const createPrefixedIdSchema = (options: {
|
||||
prefix?: string;
|
||||
addPrefixIfMissing?: boolean;
|
||||
@@ -47,11 +41,9 @@ export const createPrefixedIdSchema = (options: {
|
||||
const { prefix = "", addPrefixIfMissing = false, allowEmpty = true } = options;
|
||||
|
||||
return z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => {
|
||||
if (typeof val !== "string") return "";
|
||||
.string()
|
||||
.transform((val) => {
|
||||
let result = val.trim().toUpperCase();
|
||||
if (!result) return result;
|
||||
if (prefix && addPrefixIfMissing) {
|
||||
const clean = result.replace(new RegExp(`^${prefix}`, "i"), "");
|
||||
result = `${prefix}${clean}`;
|
||||
|
||||
@@ -252,99 +252,4 @@ describe("Analytics Apps - Input Validation", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Null/undefined handling for embed endpoint (PR 26976)
|
||||
// Schemas accept nullish input and always output string
|
||||
describe("Null/Undefined Handling", () => {
|
||||
it("GTM converts null to empty string", () => {
|
||||
expect(gtmSchema.parse({ trackingId: null }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("GA4 converts null to empty string", () => {
|
||||
expect(ga4Schema.parse({ trackingId: null }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("Meta Pixel converts null to empty string", () => {
|
||||
expect(metapixelSchema.parse({ trackingId: null }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("PostHog converts null to empty string", () => {
|
||||
expect(posthogSchema.parse({ TRACKING_ID: null }).TRACKING_ID).toBe("");
|
||||
});
|
||||
|
||||
it("Fathom converts null to empty string", () => {
|
||||
expect(fathomSchema.parse({ trackingId: null }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("Plausible converts null to empty string", () => {
|
||||
expect(plausibleSchema.parse({ trackingId: null }).trackingId).toBe("");
|
||||
});
|
||||
|
||||
it("Matomo converts null to empty string", () => {
|
||||
expect(matomoSchema.parse({ SITE_ID: null }).SITE_ID).toBe("");
|
||||
});
|
||||
|
||||
it("Umami converts null to empty string", () => {
|
||||
expect(umamiSchema.parse({ SITE_ID: null }).SITE_ID).toBe("");
|
||||
});
|
||||
|
||||
it("Twipla converts null to empty string", () => {
|
||||
expect(twiplaSchema.parse({ SITE_ID: null }).SITE_ID).toBe("");
|
||||
});
|
||||
|
||||
it("Insihts converts null to empty string", () => {
|
||||
expect(insihtsSchema.parse({ SITE_ID: null }).SITE_ID).toBe("");
|
||||
});
|
||||
|
||||
it("Databuddy converts null to empty string", () => {
|
||||
expect(databuddySchema.parse({ CLIENT_ID: null }).CLIENT_ID).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// Missing key handling - fields should be optional
|
||||
describe("Missing Key Handling", () => {
|
||||
it("GA4 accepts missing trackingId", () => {
|
||||
expect(() => ga4Schema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("GTM accepts missing trackingId", () => {
|
||||
expect(() => gtmSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Meta Pixel accepts missing trackingId", () => {
|
||||
expect(() => metapixelSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Fathom accepts missing trackingId", () => {
|
||||
expect(() => fathomSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Plausible accepts missing trackingId", () => {
|
||||
expect(() => plausibleSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("PostHog accepts missing TRACKING_ID", () => {
|
||||
expect(() => posthogSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Matomo accepts missing fields", () => {
|
||||
expect(() => matomoSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Umami accepts missing SITE_ID", () => {
|
||||
expect(() => umamiSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Twipla accepts missing SITE_ID", () => {
|
||||
expect(() => twiplaSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Insihts accepts missing fields", () => {
|
||||
expect(() => insihtsSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("Databuddy accepts missing CLIENT_ID", () => {
|
||||
expect(() => databuddySchema.parse({})).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { createPrefixedIdSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: createPrefixedIdSchema({ prefix: "GTM-", addPrefixIfMissing: true, allowEmpty: true }),
|
||||
trackingId: createPrefixedIdSchema({ prefix: "GTM-", addPrefixIfMissing: true, allowEmpty: false }),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { alphanumericIdSchema, safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { numericIdSchema, safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
|
||||
@@ -4,8 +4,8 @@ import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
// Meta Pixel IDs are numeric strings of 15-16 digits
|
||||
const metaPixelIdSchema = z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim() : ""))
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => val === "" || /^[0-9]{15,16}$/.test(val), {
|
||||
message: "Invalid Meta Pixel ID format. Expected a numeric ID (e.g., 1234567890123456)",
|
||||
})
|
||||
|
||||
@@ -5,12 +5,14 @@ import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// Domain schema for Plausible tracking (e.g., example.com, sub.example.com)
|
||||
const domainSchema = z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim().toLowerCase() : ""))
|
||||
.string()
|
||||
.transform((val) => val.trim().toLowerCase())
|
||||
.refine(
|
||||
(val) =>
|
||||
val === "" || /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(val),
|
||||
{ message: "Invalid domain format. Expected format: example.com" }
|
||||
{
|
||||
message: "Invalid domain format. Expected format: example.com",
|
||||
}
|
||||
)
|
||||
.optional();
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// PostHog Project API Keys (typically start with phc_) - allow alphanumeric to not break legacy data
|
||||
const posthogIdSchema = z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim() : ""))
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9_]+$/.test(val), {
|
||||
message: "Invalid PostHog Project API Key format. Expected alphanumeric characters or underscores",
|
||||
})
|
||||
|
||||
@@ -4,8 +4,8 @@ import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
// Twipla Site IDs can be UUID or alphanumeric strings
|
||||
const twiplaSiteIdSchema = z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim() : ""))
|
||||
.string()
|
||||
.transform((val) => val.trim())
|
||||
.refine((val) => !val || /^[A-Za-z0-9-]+$/.test(val), {
|
||||
message: "Invalid Twipla Site ID format. Expected alphanumeric characters or UUID",
|
||||
})
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
import { safeUrlSchema } from "@calcom/app-store/_lib/analytics-schemas";
|
||||
|
||||
// Umami Website IDs: UUID in v2 (e.g., 4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b) or numeric in v1
|
||||
const umamiSiteIdSchema = z
|
||||
.union([z.string(), z.null(), z.undefined()])
|
||||
.transform((val): string => (typeof val === "string" ? val.trim().toLowerCase() : ""))
|
||||
.string()
|
||||
.transform((val) => val.trim().toLowerCase())
|
||||
.refine(
|
||||
(val) =>
|
||||
!val ||
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(val) ||
|
||||
/^[0-9]+$/.test(val),
|
||||
{ message: "Invalid Umami Website ID format. Expected UUID or numeric ID" }
|
||||
{
|
||||
message: "Invalid Umami Website ID format. Expected UUID or numeric ID",
|
||||
}
|
||||
)
|
||||
.optional();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user