@ably/ui 18.4.1 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +4 -3
  3. package/core/Code.js.map +1 -1
  4. package/core/DropdownMenu.js +1 -1
  5. package/core/DropdownMenu.js.map +1 -1
  6. package/core/Flash.js.map +1 -1
  7. package/core/Loader.js.map +1 -1
  8. package/core/Meganav/MeganavBlog.js +1 -1
  9. package/core/Meganav/MeganavBlog.js.map +1 -1
  10. package/core/Meganav/MeganavCustomerStories.js +1 -1
  11. package/core/Meganav/MeganavCustomerStories.js.map +1 -1
  12. package/core/Meganav/MeganavPanelItemLinks.js +1 -1
  13. package/core/Meganav/MeganavPanelItemLinks.js.map +1 -1
  14. package/core/Meganav/PanelTitle.js +1 -1
  15. package/core/Meganav/PanelTitle.js.map +1 -1
  16. package/core/Meganav/data.js +1 -1
  17. package/core/Meganav/data.js.map +1 -1
  18. package/core/Meganav/images/cust-logo-fin-dark.svg +5 -0
  19. package/core/Meganav/images/cust-logo-fin-light.svg +5 -0
  20. package/core/Notice.js.map +1 -1
  21. package/core/Pricing/data.js +1 -1
  22. package/core/Pricing/data.js.map +1 -1
  23. package/core/Pricing/data.test.js +2 -0
  24. package/core/Pricing/data.test.js.map +1 -0
  25. package/core/SessionData.js.map +1 -1
  26. package/core/hooks/use-themed-scrollpoints.js.map +1 -1
  27. package/core/insights/bot-detection.js +2 -0
  28. package/core/insights/bot-detection.js.map +1 -0
  29. package/core/insights/bot-detection.test.js +2 -0
  30. package/core/insights/bot-detection.test.js.map +1 -0
  31. package/core/insights/command-queue.js +1 -1
  32. package/core/insights/command-queue.js.map +1 -1
  33. package/core/insights/index.test.js +1 -1
  34. package/core/insights/index.test.js.map +1 -1
  35. package/core/insights/mixpanel.js +1 -1
  36. package/core/insights/mixpanel.js.map +1 -1
  37. package/core/insights/mixpanel.test.js +1 -1
  38. package/core/insights/mixpanel.test.js.map +1 -1
  39. package/core/insights/service.js +1 -1
  40. package/core/insights/service.js.map +1 -1
  41. package/core/styles/colors/utils.js +1 -1
  42. package/core/styles/colors/utils.js.map +1 -1
  43. package/index.d.ts +5 -0
  44. package/package.json +29 -31
  45. package/core/images/cust-logo-split-col-pos.png +0 -0
  46. package/core/images/cust-logo-split-col-pos@2x.png +0 -0
  47. package/core/images/cust-logo-split-mono-pos.svg +0 -9
  48. package/core/images/cust-photo-split-pato-echague.jpg +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/Pricing/data.tsx"],"sourcesContent":["import React from \"react\";\nimport { PricingDataFeature } from \"./types\";\nimport Tooltip from \"../Tooltip\";\n\nexport const planData: PricingDataFeature[] = [\n {\n title: {\n content: \"Free\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Build a proof of concept.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$0\" },\n cta: {\n text: \"Start for free\",\n url: \"/sign-up\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"200 concurrent connections\",\n \"500 messages / second\",\n \"6M messages / month\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"Community & email support (best effort)\",\n \"No commitment\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n {\n title: {\n content: \"Standard\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Roll-out into production.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: {\n amount: \"$29\",\n content: (\n <>\n <p\n className=\"ui-text-p3 font-medium\"\n style={{ color: \"currentColor\" }}\n >\n / month\n </p>\n <div className=\"flex -mt-1\">\n <p className=\"ui-text-p3 font-bold text-gui-blue-default-dark leading-[1.4rem]\">\n + usage\n </p>\n <Tooltip interactive={true} iconSize=\"1.25rem\">\n Usage is billed on top of your plan. You&apos;ll be charged\n monthly based on how many messages, channel minutes, and\n connection minutes you use. See rates above.\n </Tooltip>\n </div>\n </>\n ),\n },\n cta: {\n text: \"Get started\",\n url: \"/users/paid_sign_up?package=standard\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"10k concurrent channels\",\n \"10k concurrent connections\",\n \"2.5k messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"1 day email support SLA\",\n \"Uptime SLO\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n {\n title: {\n content: \"Pro\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Scale business critical workloads.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: {\n amount: \"$399\",\n content: (\n <>\n <p\n className=\"ui-text-p3 font-medium\"\n style={{ color: \"currentColor\" }}\n >\n / month\n </p>\n <div className=\"flex -mt-1\">\n <p className=\"ui-text-p3 font-bold text-gui-blue-default-dark leading-[1.4rem]\">\n + usage\n </p>\n <Tooltip interactive={true} iconSize=\"1.25rem\">\n Usage is billed on top of your plan. You&apos;ll be charged\n monthly based on how many messages, channel minutes, and\n connection minutes you use. See rates above.\n </Tooltip>\n </div>\n </>\n ),\n },\n cta: {\n text: \"Get started\",\n url: \"/users/paid_sign_up?package=pro\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"50k concurrent channels\",\n \"50k concurrent connections\",\n \"10k messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"4 hour email support SLA\",\n \"Datadog (lite)\",\n \"Uptime SLO\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n\n {\n title: {\n content: \"Enterprise\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-orange-600\",\n },\n description: {\n content: \"Serious workloads without limits.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"Custom\" },\n cta: {\n text: \"Contact us\",\n url: \"/contact\",\n isPriority: true,\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"Unlimited concurrent channels\",\n \"Unlimited connections\",\n \"Unlimited messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"24/7 mission critical support\",\n \"99.999% uptime SLAs\",\n \"Committed use discounts\",\n \"Datadog\",\n \"CNAME, SSO, & more\",\n ],\n listItemColors: {\n foreground: \"text-orange-700 dark:text-orange-500\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n];\n\nexport const consumptionData: PricingDataFeature[] = [\n {\n title: {\n content: \"Messages\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content:\n \"Messages contain the data that a client is communicating, such as the contents of a chat message.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$2.50\", content: \"/ million\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million msgs\"],\n [\"First 50 million msgs\", \"$2.50\"],\n [\"Next 450 million msgs\", \"$2.25\"],\n [\"Next 4.5 billion msgs\", \"$1.95\"],\n [\"Next 15 billion msgs\", \"$1.65\"],\n [\"Next 30 billion msgs\", \"$1.40\"],\n [\"Over 50 billion msgs\", \"$1.25\"],\n ],\n },\n ],\n subtext: \"As low as $0.50/M with volume discount\",\n },\n {\n title: {\n content: \"Channels\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n tooltip: (\n <p>\n We charge you for the amount of time a channel is active in our\n network by the minute. For example, if ten channels are in use for 45\n minutes, you will be charged a total of 450 channel minutes.\n </p>\n ),\n },\n description: {\n content:\n \"Channels are used to route messages from publishers to subscribers. Channels are billed by the minute when actively being used by a connected client.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$1.00\", content: \"/ million mins\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million mins\"],\n [\"First 10 million mins\", \"$1.00\"],\n [\"Next 90 million mins\", \"$0.95\"],\n [\"Next 900 million mins\", \"$0.85\"],\n [\"Next 4 billion mins\", \"$0.75\"],\n [\"Next 10 billion mins\", \"$0.65\"],\n [\"Over 15 billion mins\", \"$0.60\"],\n ],\n },\n ],\n subtext: \"As low as $0.20/M with volume discount\",\n },\n {\n title: {\n content: \"Connections\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n tooltip: (\n <p>\n We charge you for the amount of time devices are connected to our\n network by the minute. For example, if ten devices are each connected\n for 45 minutes, you will be charged a total of 450 connection minutes.\n </p>\n ),\n },\n description: {\n content:\n \"Clients establish and maintain a connection to the Ably service, typically over WebSockets.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$1.00\", content: \"/ million mins\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million mins\"],\n [\"First 10 million mins\", \"$1.00\"],\n [\"Next 90 million mins\", \"$0.95\"],\n [\"Next 900 million mins\", \"$0.85\"],\n [\"Next 4 billion mins\", \"$0.75\"],\n [\"Next 10 billion mins\", \"$0.65\"],\n [\"Over 15 billion mins\", \"$0.60\"],\n ],\n },\n ],\n subtext: \"As low as $0.20/M with volume discount\",\n },\n];\n"],"names":["React","Tooltip","planData","title","content","className","color","description","price","amount","cta","text","url","iconColor","sections","items","listItemColors","foreground","background","bottomCta","p","style","div","interactive","iconSize","isPriority","consumptionData","subtext","tooltip"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAE1B,QAAOC,YAAa,YAAa,AAEjC,QAAO,MAAMC,SAAiC,CAC5C,CACEC,MAAO,CACLC,QAAS,OACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,4BACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CAAEC,OAAQ,IAAK,EACtBC,IAAK,CACHC,KAAM,iBACNC,IAAK,WACLC,UAAW,wCACb,EACAC,SAAU,CACR,CACEX,MAAO,WACPY,MAAO,CACL,6BACA,wBACA,sBACD,AACH,EACA,CACEZ,MAAO,WACPY,MAAO,CACL,8BACA,0CACA,gBACD,CACDC,eAAgB,CACdC,WAAY,yCACZC,WAAY,yCACd,CACF,EACD,CACDC,UAAW,CACTR,KAAM,mBACNC,IAAK,gBACP,CACF,EACA,CACET,MAAO,CACLC,QAAS,WACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,4BACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CACLC,OAAQ,MACRL,QACE,wCACE,oBAACgB,KACCf,UAAU,yBACVgB,MAAO,CAAEf,MAAO,cAAe,GAChC,WAGD,oBAACgB,OAAIjB,UAAU,cACb,oBAACe,KAAEf,UAAU,oEAAmE,WAGhF,oBAACJ,SAAQsB,YAAa,KAAMC,SAAS,WAAU,iKAQvD,EACAd,IAAK,CACHC,KAAM,cACNC,IAAK,uCACLC,UAAW,wCACb,EACAC,SAAU,CACR,CACEX,MAAO,WACPY,MAAO,CACL,0BACA,6BACA,yBACD,AACH,EACA,CACEZ,MAAO,WACPY,MAAO,CACL,8BACA,0BACA,aACD,CACDC,eAAgB,CACdC,WAAY,yCACZC,WAAY,yCACd,CACF,EACD,CACDC,UAAW,CACTR,KAAM,mBACNC,IAAK,gBACP,CACF,EACA,CACET,MAAO,CACLC,QAAS,MACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,qCACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CACLC,OAAQ,OACRL,QACE,wCACE,oBAACgB,KACCf,UAAU,yBACV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"}
1
+ {"version":3,"sources":["../../../src/core/Pricing/data.tsx"],"sourcesContent":["import React from \"react\";\nimport { PricingDataFeature } from \"./types\";\nimport Tooltip from \"../Tooltip\";\n\nexport const planData: PricingDataFeature[] = [\n {\n title: {\n content: \"Free\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Build a proof of concept.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$0\" },\n cta: {\n text: \"Start for free\",\n url: \"/sign-up\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"200 concurrent connections\",\n \"500 messages / second\",\n \"6M messages / month\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"Community & email support (best effort)\",\n \"No commitment\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n {\n title: {\n content: \"Standard\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Roll-out into production.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: {\n amount: \"$29\",\n content: (\n <>\n <p\n className=\"ui-text-p3 font-medium\"\n style={{ color: \"currentColor\" }}\n >\n / month\n </p>\n <div className=\"flex -mt-1\">\n <p className=\"ui-text-p3 font-bold text-gui-blue-default-dark leading-[1.4rem]\">\n + usage\n </p>\n <Tooltip interactive={true} iconSize=\"1.25rem\">\n Usage is billed on top of your plan. You&apos;ll be charged\n monthly based on how many messages, channel minutes, and\n connection minutes you use. See rates above.\n </Tooltip>\n </div>\n </>\n ),\n },\n cta: {\n text: \"Get started\",\n url: \"/sign-up?next=package\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"10k concurrent channels\",\n \"10k concurrent connections\",\n \"2.5k messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"1 day email support SLA\",\n \"Uptime SLO\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n {\n title: {\n content: \"Pro\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content: \"Scale business critical workloads.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: {\n amount: \"$399\",\n content: (\n <>\n <p\n className=\"ui-text-p3 font-medium\"\n style={{ color: \"currentColor\" }}\n >\n / month\n </p>\n <div className=\"flex -mt-1\">\n <p className=\"ui-text-p3 font-bold text-gui-blue-default-dark leading-[1.4rem]\">\n + usage\n </p>\n <Tooltip interactive={true} iconSize=\"1.25rem\">\n Usage is billed on top of your plan. You&apos;ll be charged\n monthly based on how many messages, channel minutes, and\n connection minutes you use. See rates above.\n </Tooltip>\n </div>\n </>\n ),\n },\n cta: {\n text: \"Get started\",\n url: \"/sign-up?next=package\",\n iconColor: \"text-neutral-600 dark:text-neutral-700\",\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"50k concurrent channels\",\n \"50k concurrent connections\",\n \"10k messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"4 hour email support SLA\",\n \"Datadog (lite)\",\n \"Uptime SLO\",\n ],\n listItemColors: {\n foreground: \"text-neutral-700 dark:text-neutral-600\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n\n {\n title: {\n content: \"Enterprise\",\n className: \"ui-text-h4 tracking-[-0.002rem]\",\n color: \"text-orange-600\",\n },\n description: {\n content: \"Serious workloads without limits.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"Custom\" },\n cta: {\n text: \"Contact us\",\n url: \"/contact\",\n isPriority: true,\n },\n sections: [\n {\n title: \"Capacity\",\n items: [\n \"Unlimited concurrent channels\",\n \"Unlimited connections\",\n \"Unlimited messages / second\",\n ],\n },\n {\n title: \"Includes\",\n items: [\n \"Build with any Ably product\",\n \"24/7 mission critical support\",\n \"99.999% uptime SLAs\",\n \"Committed use discounts\",\n \"Datadog\",\n \"CNAME, SSO, & more\",\n ],\n listItemColors: {\n foreground: \"text-orange-700 dark:text-orange-500\",\n background: \"text-neutral-100 dark:text-neutral-1200\",\n },\n },\n ],\n bottomCta: {\n text: \"See all features\",\n url: \"#pricing-table\",\n },\n },\n];\n\nexport const consumptionData: PricingDataFeature[] = [\n {\n title: {\n content: \"Messages\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n },\n description: {\n content:\n \"Messages contain the data that a client is communicating, such as the contents of a chat message.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$2.50\", content: \"/ million\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million msgs\"],\n [\"First 50 million msgs\", \"$2.50\"],\n [\"Next 450 million msgs\", \"$2.25\"],\n [\"Next 4.5 billion msgs\", \"$1.95\"],\n [\"Next 15 billion msgs\", \"$1.65\"],\n [\"Next 30 billion msgs\", \"$1.40\"],\n [\"Over 50 billion msgs\", \"$1.25\"],\n ],\n },\n ],\n subtext: \"As low as $0.50/M with volume discount\",\n },\n {\n title: {\n content: \"Channels\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n tooltip: (\n <p>\n We charge you for the amount of time a channel is active in our\n network by the minute. For example, if ten channels are in use for 45\n minutes, you will be charged a total of 450 channel minutes.\n </p>\n ),\n },\n description: {\n content:\n \"Channels are used to route messages from publishers to subscribers. Channels are billed by the minute when actively being used by a connected client.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$1.00\", content: \"/ million mins\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million mins\"],\n [\"First 10 million mins\", \"$1.00\"],\n [\"Next 90 million mins\", \"$0.95\"],\n [\"Next 900 million mins\", \"$0.85\"],\n [\"Next 4 billion mins\", \"$0.75\"],\n [\"Next 10 billion mins\", \"$0.65\"],\n [\"Over 15 billion mins\", \"$0.60\"],\n ],\n },\n ],\n subtext: \"As low as $0.20/M with volume discount\",\n },\n {\n title: {\n content: \"Connections\",\n className: \"ui-text-h3\",\n color: \"text-neutral-1300 dark:text-neutral-000\",\n tooltip: (\n <p>\n We charge you for the amount of time devices are connected to our\n network by the minute. For example, if ten devices are each connected\n for 45 minutes, you will be charged a total of 450 connection minutes.\n </p>\n ),\n },\n description: {\n content:\n \"Clients establish and maintain a connection to the Ably service, typically over WebSockets.\",\n className: \"ui-text-p3\",\n color: \"text-neutral-700 dark:text-neutral-600\",\n },\n price: { amount: \"$1.00\", content: \"/ million mins\" },\n sections: [\n {\n title: \"Volume discounts\",\n items: [\n [\"Consumption\", \"$ / million mins\"],\n [\"First 10 million mins\", \"$1.00\"],\n [\"Next 90 million mins\", \"$0.95\"],\n [\"Next 900 million mins\", \"$0.85\"],\n [\"Next 4 billion mins\", \"$0.75\"],\n [\"Next 10 billion mins\", \"$0.65\"],\n [\"Over 15 billion mins\", \"$0.60\"],\n ],\n },\n ],\n subtext: \"As low as $0.20/M with volume discount\",\n },\n];\n"],"names":["React","Tooltip","planData","title","content","className","color","description","price","amount","cta","text","url","iconColor","sections","items","listItemColors","foreground","background","bottomCta","p","style","div","interactive","iconSize","isPriority","consumptionData","subtext","tooltip"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAE1B,QAAOC,YAAa,YAAa,AAEjC,QAAO,MAAMC,SAAiC,CAC5C,CACEC,MAAO,CACLC,QAAS,OACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,4BACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CAAEC,OAAQ,IAAK,EACtBC,IAAK,CACHC,KAAM,iBACNC,IAAK,WACLC,UAAW,wCACb,EACAC,SAAU,CACR,CACEX,MAAO,WACPY,MAAO,CACL,6BACA,wBACA,sBACD,AACH,EACA,CACEZ,MAAO,WACPY,MAAO,CACL,8BACA,0CACA,gBACD,CACDC,eAAgB,CACdC,WAAY,yCACZC,WAAY,yCACd,CACF,EACD,CACDC,UAAW,CACTR,KAAM,mBACNC,IAAK,gBACP,CACF,EACA,CACET,MAAO,CACLC,QAAS,WACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,4BACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CACLC,OAAQ,MACRL,QACE,wCACE,oBAACgB,KACCf,UAAU,yBACVgB,MAAO,CAAEf,MAAO,cAAe,GAChC,WAGD,oBAACgB,OAAIjB,UAAU,cACb,oBAACe,KAAEf,UAAU,oEAAmE,WAGhF,oBAACJ,SAAQsB,YAAa,KAAMC,SAAS,WAAU,iKAQvD,EACAd,IAAK,CACHC,KAAM,cACNC,IAAK,wBACLC,UAAW,wCACb,EACAC,SAAU,CACR,CACEX,MAAO,WACPY,MAAO,CACL,0BACA,6BACA,yBACD,AACH,EACA,CACEZ,MAAO,WACPY,MAAO,CACL,8BACA,0BACA,aACD,CACDC,eAAgB,CACdC,WAAY,yCACZC,WAAY,yCACd,CACF,EACD,CACDC,UAAW,CACTR,KAAM,mBACNC,IAAK,gBACP,CACF,EACA,CACET,MAAO,CACLC,QAAS,MACTC,UAAW,kCACXC,MAAO,yCACT,EACAC,YAAa,CACXH,QAAS,qCACTC,UAAW,aACXC,MAAO,wCACT,EACAE,MAAO,CACLC,OAAQ,OACRL,QACE,wCACE,oBAACgB,KACCf,UAAU,yBACV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"}
@@ -0,0 +1,2 @@
1
+ import{describe,expect,it}from"vitest";import{planData}from"./data";const PAID_SIGNUP_URL="/sign-up?next=package";describe("paid plan CTAs",()=>{const paidCtaUrls=planData.filter(plan=>plan.cta?.text==="Get started").map(plan=>plan.cta?.url);it("sends the paid plans to signup rather than the retired payment form",()=>{expect(paidCtaUrls).toEqual([PAID_SIGNUP_URL,PAID_SIGNUP_URL])});it("leaves no route to the retired signup-and-payment form",()=>{const allUrls=planData.map(plan=>plan.cta?.url);expect(allUrls.filter(url=>url?.includes("paid_sign_up"))).toEqual([])})});
2
+ //# sourceMappingURL=data.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/Pricing/data.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { planData } from \"./data\";\n\nconst PAID_SIGNUP_URL = \"/sign-up?next=package\";\n\ndescribe(\"paid plan CTAs\", () => {\n const paidCtaUrls = planData\n .filter((plan) => plan.cta?.text === \"Get started\")\n .map((plan) => plan.cta?.url);\n\n it(\"sends the paid plans to signup rather than the retired payment form\", () => {\n expect(paidCtaUrls).toEqual([PAID_SIGNUP_URL, PAID_SIGNUP_URL]);\n });\n\n it(\"leaves no route to the retired signup-and-payment form\", () => {\n const allUrls = planData.map((plan) => plan.cta?.url);\n expect(allUrls.filter((url) => url?.includes(\"paid_sign_up\"))).toEqual([]);\n });\n});\n"],"names":["describe","expect","it","planData","PAID_SIGNUP_URL","paidCtaUrls","filter","plan","cta","text","map","url","toEqual","allUrls","includes"],"mappings":"AAAA,OAASA,QAAQ,CAAEC,MAAM,CAAEC,EAAE,KAAQ,QAAS,AAE9C,QAASC,QAAQ,KAAQ,QAAS,CAElC,MAAMC,gBAAkB,wBAExBJ,SAAS,iBAAkB,KACzB,MAAMK,YAAcF,SACjBG,MAAM,CAAC,AAACC,MAASA,KAAKC,GAAG,EAAEC,OAAS,eACpCC,GAAG,CAAC,AAACH,MAASA,KAAKC,GAAG,EAAEG,KAE3BT,GAAG,sEAAuE,KACxED,OAAOI,aAAaO,OAAO,CAAC,CAACR,gBAAiBA,gBAAgB,CAChE,GAEAF,GAAG,yDAA0D,KAC3D,MAAMW,QAAUV,SAASO,GAAG,CAAC,AAACH,MAASA,KAAKC,GAAG,EAAEG,KACjDV,OAAOY,QAAQP,MAAM,CAAC,AAACK,KAAQA,KAAKG,SAAS,kBAAkBF,OAAO,CAAC,EAAE,CAC3E,EACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/SessionData.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n PropsWithChildren,\n useMemo,\n} from \"react\";\nimport useSWR from \"swr\";\n\n// Feature flag for enabling credentials in fetch requests\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare const __ENABLE_FETCH_WITH_CREDENTIALS__: boolean | undefined;\n\ntype SessionUser = {\n firstName?: string;\n lastName?: string;\n email?: string;\n};\n\ntype SessionData = {\n user?: SessionUser;\n} | null;\n\ntype SessionDataContextType = {\n sessionData: SessionData;\n isLoading: boolean;\n error: Error | undefined;\n};\n\nconst SessionDataContext = createContext<SessionDataContextType | undefined>(\n undefined,\n);\n\ntype SessionDataProviderProps = PropsWithChildren<{\n sessionDataUrl?: string;\n}>;\n\n/**\n * fetcher - Retrieves session data from a backend API endpoint.\n *\n * Makes a JSON request to fetch current user session information. Optionally includes\n * credentials (cookies) based on the __ENABLE_FETCH_WITH_CREDENTIALS__ feature flag.\n * Handles \"not-found\" responses gracefully by returning null instead of throwing.\n * Validates response is JSON to prevent parsing errors.\n */\nconst fetcher = async (url: string): Promise<SessionData> => {\n const options: RequestInit = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n // Check if credentials should be included (feature flag)\n if (\n typeof __ENABLE_FETCH_WITH_CREDENTIALS__ !== \"undefined\" &&\n __ENABLE_FETCH_WITH_CREDENTIALS__\n ) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n throw new Error(\"Failed to fetch session data\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const data = await res.json();\n\n // Handle \"not found\" error gracefully\n if (data.error === \"not-found\") {\n return null;\n }\n\n return data;\n};\n\nconst SESSION_SWR_OPTIONS = {\n revalidateOnFocus: false,\n revalidateOnReconnect: false,\n shouldRetryOnError: false,\n onError: (err: Error) => {\n console.warn(\"Could not fetch session data due to error:\", err);\n },\n};\n\n/**\n * SessionDataProvider - Context provider for managing user session data throughout the app.\n *\n * Fetches and caches session data using SWR (stale-while-revalidate) from the provided URL.\n * Exposes session data, loading state, and errors through context. Use this at the app root\n * level and access via useSessionData() hook in child components. Disables auto-revalidation\n * to avoid excessive network requests - session data is fetched once on mount.\n */\nexport const SessionDataProvider = ({\n children,\n sessionDataUrl,\n}: SessionDataProviderProps) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n const contextValue = useMemo(\n () => ({\n sessionData: data ?? null,\n isLoading,\n error,\n }),\n [data, isLoading, error],\n );\n\n return (\n <SessionDataContext.Provider value={contextValue}>\n {children}\n </SessionDataContext.Provider>\n );\n};\n\n/**\n * useSessionData - Hook to access session data from SessionDataProvider context.\n *\n * Returns the current user session data, loading state, and any errors from the provider.\n * Must be used within a SessionDataProvider or will throw an error. Use this hook in\n * components that need to read or display user session information.\n */\nexport const useSessionData = () => {\n const context = useContext(SessionDataContext);\n if (context === undefined) {\n throw new Error(\"useSessionData must be used within SessionDataProvider\");\n }\n return context;\n};\n\n/**\n * useSessionDataDirect - Direct session data hook without requiring a provider (legacy).\n *\n * Fetches session data using SWR directly, bypassing the context provider pattern.\n * Provided for backward compatibility with code that doesn't use SessionDataProvider.\n * For new code, prefer using SessionDataProvider + useSessionData() for better\n * performance and centralized session management.\n */\nexport const useSessionDataDirect = (sessionDataUrl?: string) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n return {\n sessionData: data ?? null,\n isLoading,\n error,\n };\n};\n"],"names":["React","createContext","useContext","useMemo","useSWR","SessionDataContext","undefined","fetcher","url","options","headers","accept","cache","credentials","res","fetch","ok","Error","contentType","get","includes","data","json","error","SESSION_SWR_OPTIONS","revalidateOnFocus","revalidateOnReconnect","shouldRetryOnError","onError","err","console","warn","SessionDataProvider","children","sessionDataUrl","isLoading","contextValue","sessionData","Provider","value","useSessionData","context","useSessionDataDirect"],"mappings":"AAAA,OAAOA,OACLC,aAAa,CACbC,UAAU,CAEVC,OAAO,KACF,OAAQ,AACf,QAAOC,WAAY,KAAM,CAsBzB,MAAMC,mBAAqBJ,cACzBK,WAeF,MAAMC,QAAU,MAAOC,MACrB,MAAMC,QAAuB,CAC3BC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAGA,GACE,OAgGD,QAhG8C,aAgG9C,MA9FC,CACAH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMP,IAAKC,SAE7B,GAAI,CAACK,IAAIE,EAAE,CAAE,CACX,MAAM,IAAIC,MAAM,+BAClB,CAEA,MAAMC,YAAcJ,IAAIJ,OAAO,CAACS,GAAG,CAAC,gBACpC,GAAI,CAACD,aAAaE,SAAS,oBAAqB,CAC9C,MAAM,IAAIH,MAAM,uCAClB,CAEA,MAAMI,KAAO,MAAMP,IAAIQ,IAAI,GAG3B,GAAID,KAAKE,KAAK,GAAK,YAAa,CAC9B,OAAO,IACT,CAEA,OAAOF,IACT,EAEA,MAAMG,oBAAsB,CAC1BC,kBAAmB,MACnBC,sBAAuB,MACvBC,mBAAoB,MACpBC,QAAS,AAACC,MACRC,QAAQC,IAAI,CAAC,6CAA8CF,IAC7D,CACF,CAUA,QAAO,MAAMG,oBAAsB,CAAC,CAClCC,QAAQ,CACRC,cAAc,CACW,IACzB,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAMY,aAAejC,QACnB,IAAO,CAAA,CACLkC,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CAAA,EACA,CAACF,KAAMc,UAAWZ,MAAM,EAG1B,OACE,oBAAClB,mBAAmBiC,QAAQ,EAACC,MAAOH,cACjCH,SAGP,CAAE,AASF,QAAO,MAAMO,eAAiB,KAC5B,MAAMC,QAAUvC,WAAWG,oBAC3B,GAAIoC,UAAYnC,UAAW,CACzB,MAAM,IAAIW,MAAM,yDAClB,CACA,OAAOwB,OACT,CAAE,AAUF,QAAO,MAAMC,qBAAuB,AAACR,iBACnC,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAO,CACLa,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CACF,CAAE"}
1
+ {"version":3,"sources":["../../src/core/SessionData.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n PropsWithChildren,\n useMemo,\n} from \"react\";\nimport useSWR from \"swr\";\n\n// Feature flag for enabling credentials in fetch requests\ndeclare const __ENABLE_FETCH_WITH_CREDENTIALS__: boolean | undefined;\n\ntype SessionUser = {\n firstName?: string;\n lastName?: string;\n email?: string;\n};\n\ntype SessionData = {\n user?: SessionUser;\n} | null;\n\ntype SessionDataContextType = {\n sessionData: SessionData;\n isLoading: boolean;\n error: Error | undefined;\n};\n\nconst SessionDataContext = createContext<SessionDataContextType | undefined>(\n undefined,\n);\n\ntype SessionDataProviderProps = PropsWithChildren<{\n sessionDataUrl?: string;\n}>;\n\n/**\n * fetcher - Retrieves session data from a backend API endpoint.\n *\n * Makes a JSON request to fetch current user session information. Optionally includes\n * credentials (cookies) based on the __ENABLE_FETCH_WITH_CREDENTIALS__ feature flag.\n * Handles \"not-found\" responses gracefully by returning null instead of throwing.\n * Validates response is JSON to prevent parsing errors.\n */\nconst fetcher = async (url: string): Promise<SessionData> => {\n const options: RequestInit = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n // Check if credentials should be included (feature flag)\n if (\n typeof __ENABLE_FETCH_WITH_CREDENTIALS__ !== \"undefined\" &&\n __ENABLE_FETCH_WITH_CREDENTIALS__\n ) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n throw new Error(\"Failed to fetch session data\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const data = await res.json();\n\n // Handle \"not found\" error gracefully\n if (data.error === \"not-found\") {\n return null;\n }\n\n return data;\n};\n\nconst SESSION_SWR_OPTIONS = {\n revalidateOnFocus: false,\n revalidateOnReconnect: false,\n shouldRetryOnError: false,\n onError: (err: Error) => {\n console.warn(\"Could not fetch session data due to error:\", err);\n },\n};\n\n/**\n * SessionDataProvider - Context provider for managing user session data throughout the app.\n *\n * Fetches and caches session data using SWR (stale-while-revalidate) from the provided URL.\n * Exposes session data, loading state, and errors through context. Use this at the app root\n * level and access via useSessionData() hook in child components. Disables auto-revalidation\n * to avoid excessive network requests - session data is fetched once on mount.\n */\nexport const SessionDataProvider = ({\n children,\n sessionDataUrl,\n}: SessionDataProviderProps) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n const contextValue = useMemo(\n () => ({\n sessionData: data ?? null,\n isLoading,\n error,\n }),\n [data, isLoading, error],\n );\n\n return (\n <SessionDataContext.Provider value={contextValue}>\n {children}\n </SessionDataContext.Provider>\n );\n};\n\n/**\n * useSessionData - Hook to access session data from SessionDataProvider context.\n *\n * Returns the current user session data, loading state, and any errors from the provider.\n * Must be used within a SessionDataProvider or will throw an error. Use this hook in\n * components that need to read or display user session information.\n */\nexport const useSessionData = () => {\n const context = useContext(SessionDataContext);\n if (context === undefined) {\n throw new Error(\"useSessionData must be used within SessionDataProvider\");\n }\n return context;\n};\n\n/**\n * useSessionDataDirect - Direct session data hook without requiring a provider (legacy).\n *\n * Fetches session data using SWR directly, bypassing the context provider pattern.\n * Provided for backward compatibility with code that doesn't use SessionDataProvider.\n * For new code, prefer using SessionDataProvider + useSessionData() for better\n * performance and centralized session management.\n */\nexport const useSessionDataDirect = (sessionDataUrl?: string) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n return {\n sessionData: data ?? null,\n isLoading,\n error,\n };\n};\n"],"names":["React","createContext","useContext","useMemo","useSWR","SessionDataContext","undefined","fetcher","url","options","headers","accept","cache","credentials","res","fetch","ok","Error","contentType","get","includes","data","json","error","SESSION_SWR_OPTIONS","revalidateOnFocus","revalidateOnReconnect","shouldRetryOnError","onError","err","console","warn","SessionDataProvider","children","sessionDataUrl","isLoading","contextValue","sessionData","Provider","value","useSessionData","context","useSessionDataDirect"],"mappings":"AAAA,OAAOA,OACLC,aAAa,CACbC,UAAU,CAEVC,OAAO,KACF,OAAQ,AACf,QAAOC,WAAY,KAAM,CAqBzB,MAAMC,mBAAqBJ,cACzBK,WAeF,MAAMC,QAAU,MAAOC,MACrB,MAAMC,QAAuB,CAC3BC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAGA,GACE,OAqGM,QArGuC,aAqGvC,MAnGN,CACAH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMP,IAAKC,SAE7B,GAAI,CAACK,IAAIE,EAAE,CAAE,CACX,MAAM,IAAIC,MAAM,+BAClB,CAEA,MAAMC,YAAcJ,IAAIJ,OAAO,CAACS,GAAG,CAAC,gBACpC,GAAI,CAACD,aAAaE,SAAS,oBAAqB,CAC9C,MAAM,IAAIH,MAAM,uCAClB,CAEA,MAAMI,KAAO,MAAMP,IAAIQ,IAAI,GAG3B,GAAID,KAAKE,KAAK,GAAK,YAAa,CAC9B,OAAO,IACT,CAEA,OAAOF,IACT,EAEA,MAAMG,oBAAsB,CAC1BC,kBAAmB,MACnBC,sBAAuB,MACvBC,mBAAoB,MACpBC,QAAS,AAACC,MACRC,QAAQC,IAAI,CAAC,6CAA8CF,IAC7D,CACF,CAUA,QAAO,MAAMG,oBAAsB,CAAC,CAClCC,QAAQ,CACRC,cAAc,CACW,IACzB,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAMY,aAAejC,QACnB,IAAO,CAAA,CACLkC,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CAAA,EACA,CAACF,KAAMc,UAAWZ,MAAM,EAG1B,OACE,oBAAClB,mBAAmBiC,QAAQ,EAACC,MAAOH,cACjCH,SAGP,CAAE,AASF,QAAO,MAAMO,eAAiB,KAC5B,MAAMC,QAAUvC,WAAWG,oBAC3B,GAAIoC,UAAYnC,UAAW,CACzB,MAAM,IAAIW,MAAM,yDAClB,CACA,OAAOwB,OACT,CAAE,AAUF,QAAO,MAAMC,qBAAuB,AAACR,iBACnC,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAO,CACLa,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CACF,CAAE"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.ts"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { ThemedScrollpoint } from \"../Header/types\";\n\nconst HEADER_HEIGHT = 64;\n\nexport function useThemedScrollpoints(\n scrollpoints: ThemedScrollpoint[],\n): string {\n const [activeClassName, setActiveClassName] = useState<string>(\"\");\n\n const previousClassNameRef = useRef<string>(\"\");\n const observerRef = useRef<IntersectionObserver | null>(null);\n const initialCheckDoneRef = useRef<boolean>(false);\n const rafIdRef = useRef<number | null>(null);\n const intersectingElementsRef = useRef<\n Map<string, IntersectionObserverEntry>\n >(new Map());\n\n useEffect(() => {\n if (scrollpoints.length === 0) {\n // Clear active className when scrollpoints becomes empty\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setActiveClassName(\"\");\n previousClassNameRef.current = \"\";\n return;\n }\n\n let isMounted = true;\n const intersectingElements = intersectingElementsRef.current;\n\n // Function to find and update the best matching scrollpoint\n const updateBestMatch = (useEntryRects = false) => {\n // Find the best match from ALL currently intersecting elements\n // Strategy: Pick the element whose top edge is closest to the header position\n // Use scrollpoints array order as tiebreaker when distances are equal\n let bestMatch: {\n scrollpoint: ThemedScrollpoint;\n distance: number;\n index: number;\n } | null = null;\n\n for (const [id, entry] of intersectingElements) {\n const scrollpointIndex = scrollpoints.findIndex((sp) => sp.id === id);\n if (scrollpointIndex === -1) continue;\n\n const scrollpoint = scrollpoints[scrollpointIndex];\n\n // For observer callbacks, use entry.boundingClientRect (for test compatibility)\n // For scroll handler, get fresh position data\n const rect = useEntryRects\n ? (entry.boundingClientRect ?? entry.target.getBoundingClientRect())\n : entry.target.getBoundingClientRect();\n\n // Only consider elements at or past the header line\n // This prevents selecting elements that are marked as \"intersecting\" by rootMargin\n // but haven't actually reached the header position yet\n if (rect.top > HEADER_HEIGHT) continue;\n\n // Calculate distance from element's top edge to header position\n const distance = Math.abs(rect.top - HEADER_HEIGHT);\n\n // Pick element with smallest distance; if equal, pick earlier in scrollpoints array\n if (\n !bestMatch ||\n distance < bestMatch.distance ||\n (distance === bestMatch.distance &&\n scrollpointIndex < bestMatch.index)\n ) {\n bestMatch = { scrollpoint, distance, index: scrollpointIndex };\n }\n }\n\n if (\n bestMatch &&\n bestMatch.scrollpoint.className !== previousClassNameRef.current\n ) {\n previousClassNameRef.current = bestMatch.scrollpoint.className;\n setActiveClassName(bestMatch.scrollpoint.className);\n }\n };\n\n observerRef.current = new IntersectionObserver(\n (entries) => {\n // Update the map of currently intersecting elements\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id;\n if (entry.isIntersecting) {\n intersectingElements.set(id, entry);\n } else {\n intersectingElements.delete(id);\n }\n }\n\n // Schedule best match calculation using entry rects\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n if (!isMounted) return;\n updateBestMatch(true); // Use entry.boundingClientRect\n });\n },\n {\n rootMargin: `-${HEADER_HEIGHT}px 0px 0px 0px`,\n threshold: 0,\n },\n );\n\n // Lightweight scroll handler to re-evaluate on scroll (gets fresh position data)\n const handleScroll = () => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n if (!isMounted) return;\n updateBestMatch(false); // Get fresh position data\n });\n };\n\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n scrollpoints.forEach(({ id }) => {\n const element = document.getElementById(id);\n if (element) {\n observerRef.current?.observe(element);\n } else {\n console.warn(\n `useThemedScrollpoints: Element with id \"${id}\" not found in DOM`,\n );\n }\n });\n\n // Manually check initial intersection state since IntersectionObserver\n // callbacks only fire on changes, not on initial observation\n // Use a small timeout to ensure DOM is fully laid out\n const timeoutId = setTimeout(() => {\n if (initialCheckDoneRef.current) {\n return;\n }\n initialCheckDoneRef.current = true;\n\n // Manually populate the intersection map for initial check\n // (observer callbacks haven't fired yet)\n for (const scrollpoint of scrollpoints) {\n const element = document.getElementById(scrollpoint.id);\n if (element) {\n // Create a minimal entry with just the target\n intersectingElements.set(scrollpoint.id, {\n target: element,\n } as unknown as IntersectionObserverEntry);\n }\n }\n\n // Run initial best match calculation (gets fresh position data)\n updateBestMatch(false);\n }, 0);\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutId);\n window.removeEventListener(\"scroll\", handleScroll);\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n observerRef.current?.disconnect();\n observerRef.current = null;\n initialCheckDoneRef.current = false;\n intersectingElements.clear();\n };\n }, [scrollpoints]);\n\n return activeClassName;\n}\n"],"names":["useState","useEffect","useRef","HEADER_HEIGHT","useThemedScrollpoints","scrollpoints","activeClassName","setActiveClassName","previousClassNameRef","observerRef","initialCheckDoneRef","rafIdRef","intersectingElementsRef","Map","length","current","isMounted","intersectingElements","updateBestMatch","useEntryRects","bestMatch","id","entry","scrollpointIndex","findIndex","sp","scrollpoint","rect","boundingClientRect","target","getBoundingClientRect","top","distance","Math","abs","index","className","IntersectionObserver","entries","isIntersecting","set","delete","cancelAnimationFrame","requestAnimationFrame","rootMargin","threshold","handleScroll","window","addEventListener","passive","forEach","element","document","getElementById","observe","console","warn","timeoutId","setTimeout","clearTimeout","removeEventListener","disconnect","clear"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.ts"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { ThemedScrollpoint } from \"../Header/types\";\n\nconst HEADER_HEIGHT = 64;\n\nexport function useThemedScrollpoints(\n scrollpoints: ThemedScrollpoint[],\n): string {\n const [activeClassName, setActiveClassName] = useState<string>(\"\");\n\n const previousClassNameRef = useRef<string>(\"\");\n const observerRef = useRef<IntersectionObserver | null>(null);\n const initialCheckDoneRef = useRef<boolean>(false);\n const rafIdRef = useRef<number | null>(null);\n const intersectingElementsRef = useRef<\n Map<string, IntersectionObserverEntry>\n >(new Map());\n\n useEffect(() => {\n if (scrollpoints.length === 0) {\n // Clear active className when scrollpoints becomes empty\n setActiveClassName(\"\");\n previousClassNameRef.current = \"\";\n return;\n }\n\n let isMounted = true;\n const intersectingElements = intersectingElementsRef.current;\n\n // Function to find and update the best matching scrollpoint\n const updateBestMatch = (useEntryRects = false) => {\n // Find the best match from ALL currently intersecting elements\n // Strategy: Pick the element whose top edge is closest to the header position\n // Use scrollpoints array order as tiebreaker when distances are equal\n let bestMatch: {\n scrollpoint: ThemedScrollpoint;\n distance: number;\n index: number;\n } | null = null;\n\n for (const [id, entry] of intersectingElements) {\n const scrollpointIndex = scrollpoints.findIndex((sp) => sp.id === id);\n if (scrollpointIndex === -1) continue;\n\n const scrollpoint = scrollpoints[scrollpointIndex];\n\n // For observer callbacks, use entry.boundingClientRect (for test compatibility)\n // For scroll handler, get fresh position data\n const rect = useEntryRects\n ? (entry.boundingClientRect ?? entry.target.getBoundingClientRect())\n : entry.target.getBoundingClientRect();\n\n // Only consider elements at or past the header line\n // This prevents selecting elements that are marked as \"intersecting\" by rootMargin\n // but haven't actually reached the header position yet\n if (rect.top > HEADER_HEIGHT) continue;\n\n // Calculate distance from element's top edge to header position\n const distance = Math.abs(rect.top - HEADER_HEIGHT);\n\n // Pick element with smallest distance; if equal, pick earlier in scrollpoints array\n if (\n !bestMatch ||\n distance < bestMatch.distance ||\n (distance === bestMatch.distance &&\n scrollpointIndex < bestMatch.index)\n ) {\n bestMatch = { scrollpoint, distance, index: scrollpointIndex };\n }\n }\n\n if (\n bestMatch &&\n bestMatch.scrollpoint.className !== previousClassNameRef.current\n ) {\n previousClassNameRef.current = bestMatch.scrollpoint.className;\n setActiveClassName(bestMatch.scrollpoint.className);\n }\n };\n\n observerRef.current = new IntersectionObserver(\n (entries) => {\n // Update the map of currently intersecting elements\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id;\n if (entry.isIntersecting) {\n intersectingElements.set(id, entry);\n } else {\n intersectingElements.delete(id);\n }\n }\n\n // Schedule best match calculation using entry rects\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n if (!isMounted) return;\n updateBestMatch(true); // Use entry.boundingClientRect\n });\n },\n {\n rootMargin: `-${HEADER_HEIGHT}px 0px 0px 0px`,\n threshold: 0,\n },\n );\n\n // Lightweight scroll handler to re-evaluate on scroll (gets fresh position data)\n const handleScroll = () => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n if (!isMounted) return;\n updateBestMatch(false); // Get fresh position data\n });\n };\n\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n scrollpoints.forEach(({ id }) => {\n const element = document.getElementById(id);\n if (element) {\n observerRef.current?.observe(element);\n } else {\n console.warn(\n `useThemedScrollpoints: Element with id \"${id}\" not found in DOM`,\n );\n }\n });\n\n // Manually check initial intersection state since IntersectionObserver\n // callbacks only fire on changes, not on initial observation\n // Use a small timeout to ensure DOM is fully laid out\n const timeoutId = setTimeout(() => {\n if (initialCheckDoneRef.current) {\n return;\n }\n initialCheckDoneRef.current = true;\n\n // Manually populate the intersection map for initial check\n // (observer callbacks haven't fired yet)\n for (const scrollpoint of scrollpoints) {\n const element = document.getElementById(scrollpoint.id);\n if (element) {\n // Create a minimal entry with just the target\n intersectingElements.set(scrollpoint.id, {\n target: element,\n } as unknown as IntersectionObserverEntry);\n }\n }\n\n // Run initial best match calculation (gets fresh position data)\n updateBestMatch(false);\n }, 0);\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutId);\n window.removeEventListener(\"scroll\", handleScroll);\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n observerRef.current?.disconnect();\n observerRef.current = null;\n initialCheckDoneRef.current = false;\n intersectingElements.clear();\n };\n }, [scrollpoints]);\n\n return activeClassName;\n}\n"],"names":["useState","useEffect","useRef","HEADER_HEIGHT","useThemedScrollpoints","scrollpoints","activeClassName","setActiveClassName","previousClassNameRef","observerRef","initialCheckDoneRef","rafIdRef","intersectingElementsRef","Map","length","current","isMounted","intersectingElements","updateBestMatch","useEntryRects","bestMatch","id","entry","scrollpointIndex","findIndex","sp","scrollpoint","rect","boundingClientRect","target","getBoundingClientRect","top","distance","Math","abs","index","className","IntersectionObserver","entries","isIntersecting","set","delete","cancelAnimationFrame","requestAnimationFrame","rootMargin","threshold","handleScroll","window","addEventListener","passive","forEach","element","document","getElementById","observe","console","warn","timeoutId","setTimeout","clearTimeout","removeEventListener","disconnect","clear"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{isbot}from"isbot";import*as logger from"./logger";const FORGED_UA_PATTERN=/Version\/[\d.]+.*Firefox\//;export const isLikelyAutomated=()=>{if(typeof navigator==="undefined"){return true}if(navigator.webdriver){return true}try{const userAgent=navigator.userAgent??"";return isbot(userAgent)||FORGED_UA_PATTERN.test(userAgent)}catch(e){logger.error("Bot detection failed, failing closed",e);return true}};
2
+ //# sourceMappingURL=bot-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/insights/bot-detection.ts"],"sourcesContent":["import { isbot } from \"isbot\";\n\nimport * as logger from \"./logger\";\n\n// Automated clients (crawlers, headless browsers, scraper fleets) execute our\n// analytics bundles and inflate per-event billing without producing usable\n// insights, so we skip initialization for them entirely.\n\n// Real Firefox never carries a Safari-style \"Version/x\" token; UAs with both\n// are fabricated by scraper UA generators.\nconst FORGED_UA_PATTERN = /Version\\/[\\d.]+.*Firefox\\//;\n\nexport const isLikelyAutomated = (): boolean => {\n // Non-browser environment: nothing worth tracking\n if (typeof navigator === \"undefined\") {\n return true;\n }\n\n // Set by WebDriver-based automation (Puppeteer, Playwright, Selenium)\n // even when the user agent has been disguised\n if (navigator.webdriver) {\n return true;\n }\n\n try {\n const userAgent = navigator.userAgent ?? \"\";\n return isbot(userAgent) || FORGED_UA_PATTERN.test(userAgent);\n } catch (e) {\n logger.error(\"Bot detection failed, failing closed\", e);\n return true;\n }\n};\n"],"names":["isbot","logger","FORGED_UA_PATTERN","isLikelyAutomated","navigator","webdriver","userAgent","test","e","error"],"mappings":"AAAA,OAASA,KAAK,KAAQ,OAAQ,AAE9B,WAAYC,WAAY,UAAW,CAQnC,MAAMC,kBAAoB,4BAE1B,QAAO,MAAMC,kBAAoB,KAE/B,GAAI,OAAOC,YAAc,YAAa,CACpC,OAAO,IACT,CAIA,GAAIA,UAAUC,SAAS,CAAE,CACvB,OAAO,IACT,CAEA,GAAI,CACF,MAAMC,UAAYF,UAAUE,SAAS,EAAI,GACzC,OAAON,MAAMM,YAAcJ,kBAAkBK,IAAI,CAACD,UACpD,CAAE,MAAOE,EAAG,CACVP,OAAOQ,KAAK,CAAC,uCAAwCD,GACrD,OAAO,IACT,CACF,CAAE"}
@@ -0,0 +1,2 @@
1
+ import{describe,expect,afterEach,it,vi}from"vitest";import{isbot}from"isbot";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import{isLikelyAutomated}from"./bot-detection";import{InsightsService}from"./service";import{InsightsCommandQueue}from"./command-queue";vi.mock("isbot",async importOriginal=>{const actual=await importOriginal();return{...actual,isbot:vi.fn(actual.isbot)}});vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));const CHROME_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";const stubNavigator=overrides=>{vi.stubGlobal("navigator",{webdriver:false,userAgent:CHROME_UA,...overrides})};describe("isLikelyAutomated",()=>{afterEach(()=>{vi.unstubAllGlobals()});it("passes a regular Chrome browser",()=>{stubNavigator({});expect(isLikelyAutomated()).toBe(false)});it("passes real Firefox",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0"});expect(isLikelyAutomated()).toBe(false)});it("passes real Safari despite its Version/x token",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"});expect(isLikelyAutomated()).toBe(false)});it("flags webdriver automation regardless of user agent",()=>{stubNavigator({webdriver:true});expect(isLikelyAutomated()).toBe(true)});it("flags headless browsers",()=>{stubNavigator({userAgent:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/147.0.7727.15 Safari/537.36"});expect(isLikelyAutomated()).toBe(true)});it("flags self-identified crawlers",()=>{stubNavigator({userAgent:"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.0; +https://openai.com/gptbot"});expect(isLikelyAutomated()).toBe(true)});it("treats environments without navigator as automated",()=>{vi.stubGlobal("navigator",undefined);expect(isLikelyAutomated()).toBe(true)});it("flags forged Safari/Firefox hybrid user agents",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 12) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Firefox/99.0"});expect(isLikelyAutomated()).toBe(true)});it("flags SEO/monitoring crawlers via isbot",()=>{stubNavigator({userAgent:"Mozilla/5.0 (compatible; ContentKing/1.0; +https://www.contentkingapp.com)"});expect(isLikelyAutomated()).toBe(true)});it("flags self-declared indexing crawlers via isbot",()=>{stubNavigator({userAgent:"ExoPriorsSiteClosure/1.0 (content indexing; contact@exopriors.com)"});expect(isLikelyAutomated()).toBe(true)});it("flags LG webOS smart TVs — a known, accepted isbot false positive",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/STR Large Screen WebAppManager Safari/537.36"});expect(isLikelyAutomated()).toBe(true)});it("fails closed when bot detection itself throws",()=>{stubNavigator({});vi.mocked(isbot).mockImplementationOnce(()=>{throw new Error("pattern explosion")});expect(isLikelyAutomated()).toBe(true)})});describe("InsightsService bot gating",()=>{afterEach(()=>{vi.unstubAllGlobals();vi.clearAllMocks()});const config={mixpanelToken:"test-token",mixpanelAutoCapture:true,posthogApiKey:"test-key",posthogApiHost:"test-host"};it("skips all analytics initialization for automated clients",()=>{stubNavigator({webdriver:true});new InsightsService().initInsights(config);expect(mixpanel.initMixpanel).not.toHaveBeenCalled();expect(posthog.initPosthog).not.toHaveBeenCalled()});it("initializes analytics for regular browsers",()=>{stubNavigator({});new InsightsService().initInsights(config);expect(mixpanel.initMixpanel).toHaveBeenCalled();expect(posthog.initPosthog).toHaveBeenCalled()})});describe("InsightsCommandQueue bot gating",()=>{afterEach(()=>{vi.unstubAllGlobals();vi.clearAllMocks()});const config={mixpanelToken:"test-token",mixpanelAutoCapture:true,posthogApiKey:"test-key",posthogApiHost:"test-host"};it("keeps post-init calls away from the SDKs for automated clients",()=>{stubNavigator({webdriver:true});const queue=new InsightsCommandQueue;queue.initInsights(config);queue.trackPageView();queue.track("some_event",{});expect(mixpanel.initMixpanel).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.initPosthog).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled()});it("dispatches post-init calls normally for regular browsers",()=>{stubNavigator({});const queue=new InsightsCommandQueue;queue.initInsights(config);queue.trackPageView();expect(mixpanel.initMixpanel).toHaveBeenCalled();expect(mixpanel.trackPageView).toHaveBeenCalled()})});
2
+ //# sourceMappingURL=bot-detection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/insights/bot-detection.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, afterEach, it, vi } from \"vitest\";\nimport { isbot } from \"isbot\";\n\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport { isLikelyAutomated } from \"./bot-detection\";\nimport { InsightsService } from \"./service\";\nimport { InsightsCommandQueue } from \"./command-queue\";\n\n// Real implementation by default; individual tests can override to simulate\n// library failure\nvi.mock(\"isbot\", async (importOriginal) => {\n const actual = await importOriginal<typeof import(\"isbot\")>();\n return { ...actual, isbot: vi.fn(actual.isbot) };\n});\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\nconst CHROME_UA =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\";\n\nconst stubNavigator = (overrides: {\n webdriver?: boolean;\n userAgent?: string;\n}) => {\n vi.stubGlobal(\"navigator\", {\n webdriver: false,\n userAgent: CHROME_UA,\n ...overrides,\n });\n};\n\ndescribe(\"isLikelyAutomated\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"passes a regular Chrome browser\", () => {\n stubNavigator({});\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"passes real Firefox\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0\",\n });\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"passes real Safari despite its Version/x token\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15\",\n });\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"flags webdriver automation regardless of user agent\", () => {\n stubNavigator({ webdriver: true });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags headless browsers\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/147.0.7727.15 Safari/537.36\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags self-identified crawlers\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.0; +https://openai.com/gptbot\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"treats environments without navigator as automated\", () => {\n vi.stubGlobal(\"navigator\", undefined);\n expect(isLikelyAutomated()).toBe(true);\n });\n\n // Verbatim UA of the scraper fleet observed hammering ably.com — isbot\n // cannot catch it (browser-shaped), so the forged-UA heuristic must\n // catch it instead\n it(\"flags forged Safari/Firefox hybrid user agents\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 12) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Firefox/99.0\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags SEO/monitoring crawlers via isbot\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (compatible; ContentKing/1.0; +https://www.contentkingapp.com)\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags self-declared indexing crawlers via isbot\", () => {\n stubNavigator({\n userAgent:\n \"ExoPriorsSiteClosure/1.0 (content indexing; contact@exopriors.com)\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags LG webOS smart TVs — a known, accepted isbot false positive\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/STR Large Screen WebAppManager Safari/537.36\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"fails closed when bot detection itself throws\", () => {\n stubNavigator({});\n vi.mocked(isbot).mockImplementationOnce(() => {\n throw new Error(\"pattern explosion\");\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n});\n\ndescribe(\"InsightsService bot gating\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.clearAllMocks();\n });\n\n const config = {\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: true,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n it(\"skips all analytics initialization for automated clients\", () => {\n stubNavigator({ webdriver: true });\n\n new InsightsService().initInsights(config);\n\n expect(mixpanel.initMixpanel).not.toHaveBeenCalled();\n expect(posthog.initPosthog).not.toHaveBeenCalled();\n });\n\n it(\"initializes analytics for regular browsers\", () => {\n stubNavigator({});\n\n new InsightsService().initInsights(config);\n\n expect(mixpanel.initMixpanel).toHaveBeenCalled();\n expect(posthog.initPosthog).toHaveBeenCalled();\n });\n});\n\ndescribe(\"InsightsCommandQueue bot gating\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.clearAllMocks();\n });\n\n const config = {\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: true,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n // Pins the guarantee that gated clients can't reach the SDKs through\n // calls made after init (route-change page views etc.) — we must not\n // depend on Mixpanel/PostHog's own pre-init guards for that\n it(\"keeps post-init calls away from the SDKs for automated clients\", () => {\n stubNavigator({ webdriver: true });\n\n const queue = new InsightsCommandQueue();\n queue.initInsights(config);\n queue.trackPageView();\n queue.track(\"some_event\", {});\n\n expect(mixpanel.initMixpanel).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.initPosthog).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"dispatches post-init calls normally for regular browsers\", () => {\n stubNavigator({});\n\n const queue = new InsightsCommandQueue();\n queue.initInsights(config);\n queue.trackPageView();\n\n expect(mixpanel.initMixpanel).toHaveBeenCalled();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n });\n});\n"],"names":["describe","expect","afterEach","it","vi","isbot","mixpanel","posthog","isLikelyAutomated","InsightsService","InsightsCommandQueue","mock","importOriginal","actual","fn","initMixpanel","trackPageView","track","initPosthog","debug","info","warn","error","CHROME_UA","stubNavigator","overrides","stubGlobal","webdriver","userAgent","unstubAllGlobals","toBe","undefined","mocked","mockImplementationOnce","Error","clearAllMocks","config","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","initInsights","not","toHaveBeenCalled","queue"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{InsightsService}from"./service";import*as logger from"./logger";export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}constructor(){_define_property(this,"isInitialized",false);_define_property(this,"queue",[]);_define_property(this,"debugMode",false);_define_property(this,"realImplementation",null);return new Proxy(this,{get:(target,prop)=>{if(prop in target&&typeof target[prop]!=="function"){return target[prop]}return(...args)=>{if(!target.isInitialized||!target.realImplementation){if(prop==="initInsights"){target.executeInitInsights(args[0]);return}if(target.debugMode){logger.debug(`Queuing method call: ${String(prop)}`,args)}target.queue.push({methodName:prop,args});return}if(typeof target.realImplementation[prop]==="function"){return target.realImplementation[prop](...args)}}}})}}
1
+ function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else obj[key]=value;return obj}import{InsightsService}from"./service";import{isLikelyAutomated}from"./bot-detection";import*as logger from"./logger";class NoopAnalyticsService{initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}}export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(isLikelyAutomated()){if(this.debugMode){logger.debug("Automated client detected, installing no-op analytics")}this.realImplementation=new NoopAnalyticsService;this.isInitialized=true;this.queue=[];return}if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}constructor(){_define_property(this,"isInitialized",false);_define_property(this,"queue",[]);_define_property(this,"debugMode",false);_define_property(this,"realImplementation",null);return new Proxy(this,{get:(target,prop)=>{if(prop in target&&typeof target[prop]!=="function"){return target[prop]}return(...args)=>{if(!target.isInitialized||!target.realImplementation){if(prop==="initInsights"){target.executeInitInsights(args[0]);return}if(target.debugMode){logger.debug(`Queuing method call: ${String(prop)}`,args)}target.queue.push({methodName:prop,args});return}if(typeof target.realImplementation[prop]==="function"){return target.realImplementation[prop](...args)}}}})}}
2
2
  //# sourceMappingURL=command-queue.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport * as logger from \"./logger\";\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: InsightsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n"],"names":["InsightsService","logger","InsightsCommandQueue","executeInitInsights","config","debugMode","debug","realImplementation","initInsights","isInitialized","processQueue","queue","length","cmd","shift","methodName","args","fn","apply","e","error","_config","enableDebugMode","disableDebugMode","identify","_identity","trackPageView","_options","track","_event","_properties","setupObserver","Proxy","get","target","prop","String","push"],"mappings":"oLAOA,OAASA,eAAe,KAAQ,WAAY,AAC5C,WAAYC,WAAY,UAAW,AAGnC,QAAO,MAAMC,qBAkDX,AAAQC,oBAAoBC,MAAsB,CAAQ,CACxD,IAAI,CAACC,SAAS,CAAG,CAAC,CAACD,OAAOE,KAAK,CAE/B,GAAI,IAAI,CAACD,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,wBACf,CAGA,IAAI,CAACC,kBAAkB,CAAG,IAAIP,gBAC9B,IAAI,CAACO,kBAAkB,CAACC,YAAY,CAACJ,OAGrC,CAAA,IAAI,CAACK,aAAa,CAAG,KACrB,IAAI,CAACC,YAAY,EACnB,CAGA,AAAQA,cAAqB,CAC3B,GAAI,IAAI,CAACL,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,CAAC,WAAW,EAAE,IAAI,CAACK,KAAK,CAACC,MAAM,CAAC,gBAAgB,CAAC,CAChE,CAEA,MAAO,IAAI,CAACD,KAAK,CAACC,MAAM,CAAG,EAAG,CAC5B,MAAMC,IAAM,IAAI,CAACF,KAAK,CAACG,KAAK,GAC5B,GACED,KACA,IAAI,CAACN,kBAAkB,EACvB,OAAO,IAAI,CAACA,kBAAkB,CAACM,IAAIE,UAAU,CAAC,GAAK,WACnD,CACA,GAAI,CACF,GAAI,IAAI,CAACV,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CACV,CAAC,0BAA0B,EAAEO,IAAIE,UAAU,CAAC,CAAC,CAC7CF,IAAIG,IAAI,CAEZ,CAEA,MAAMC,GAAK,IAAI,CAACV,kBAAkB,CAACM,IAAIE,UAAU,CAAC,CAKlDE,GAAGC,KAAK,CAAC,IAAI,CAACX,kBAAkB,CAAEM,IAAIG,IAAI,CAC5C,CAAE,MAAOG,EAAG,CACV,GAAI,IAAI,CAACd,SAAS,CAAE,CAClBJ,OAAOmB,KAAK,CACV,CAAC,gCAAgC,EAAEP,IAAIE,UAAU,CAAC,CAAC,CACnDI,EAEJ,CACF,CACF,CACF,CACF,CAIAX,aAAaa,OAAuB,CAAQ,CAAC,CAC7CC,iBAAwB,CAAC,CACzBC,kBAAyB,CAAC,CAC1BC,SAASC,SAA2B,CAAQ,CAAC,CAC7CC,cAAcC,QAA+B,CAAQ,CAAC,CACtDC,MAAMC,MAAc,CAAEC,WAAqC,CAAQ,CAAC,CACpEC,eAA4B,CAC1B,MAAO,KAAO,CAChB,CA7GA,aAAc,CALd,sBAAQtB,gBAAyB,OACjC,sBAAQE,QAAmB,EAAE,EAC7B,sBAAQN,YAAqB,OAC7B,sBAAQE,qBAA6C,MAInD,OAAO,IAAIyB,MAAM,IAAI,CAAE,CACrBC,IAAK,CAACC,OAA8BC,QAElC,GAAIA,QAAQD,QAAU,OAAOA,MAAM,CAACC,KAAK,GAAK,WAAY,CACxD,OAAOD,MAAM,CAACC,KAAK,AACrB,CAGA,MAAO,CAAC,GAAGnB,QACT,GAAI,CAACkB,OAAOzB,aAAa,EAAI,CAACyB,OAAO3B,kBAAkB,CAAE,CAEvD,GAAI4B,OAAS,eAAgB,CAE3BD,OAAO/B,mBAAmB,CAACa,IAAI,CAAC,EAAE,EAClC,MACF,CAGA,GAAIkB,OAAO7B,SAAS,CAAE,CACpBJ,OAAOK,KAAK,CAAC,CAAC,qBAAqB,EAAE8B,OAAOD,MAAM,CAAC,CAAEnB,KACvD,CAEAkB,OAAOvB,KAAK,CAAC0B,IAAI,CAAC,CAChBtB,WAAYoB,KACZnB,IACF,GAEA,MACF,CAGA,GAAI,OAAOkB,OAAO3B,kBAAkB,CAAC4B,KAAK,GAAK,WAAY,CACzD,OAAO,AACLD,OAAO3B,kBAAkB,CAAC4B,KAAK,IAC5BnB,KACP,CACF,CACF,CACF,EACF,CAqEF"}
1
+ {"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport { isLikelyAutomated } from \"./bot-detection\";\nimport * as logger from \"./logger\";\n\n// Inert implementation installed for automated clients: the analytics SDKs\n// are never initialized and no call can ever reach them — a guarantee we own,\n// rather than relying on the SDKs' own pre-init behaviour.\nclass NoopAnalyticsService implements AnalyticsService {\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: AnalyticsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (isLikelyAutomated()) {\n if (this.debugMode) {\n logger.debug(\"Automated client detected, installing no-op analytics\");\n }\n\n this.realImplementation = new NoopAnalyticsService();\n this.isInitialized = true;\n this.queue = [];\n return;\n }\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n"],"names":["InsightsService","isLikelyAutomated","logger","NoopAnalyticsService","initInsights","_config","enableDebugMode","disableDebugMode","identify","_identity","trackPageView","_options","track","_event","_properties","setupObserver","InsightsCommandQueue","executeInitInsights","config","debugMode","debug","realImplementation","isInitialized","queue","processQueue","length","cmd","shift","methodName","args","fn","apply","e","error","Proxy","get","target","prop","String","push"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,undefined);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
1
+ import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));vi.mock("./bot-detection",()=>({isLikelyAutomated:vi.fn().mockReturnValue(false)}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,undefined);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
2
2
  //# sourceMappingURL=index.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/index.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, afterEach, it, vi, Mock } from \"vitest\";\n\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\nimport * as insights from \"./index\";\n\n// Mock the dependencies\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\ndescribe(\"Insights Command Queue\", () => {\n const testConfig = {\n debug: true,\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: false,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n const testIdentity = {\n userId: \"user-123\",\n accountId: \"account-456\",\n organisationId: \"org-789\",\n email: \"test@example.com\",\n name: \"Test User\",\n };\n\n beforeEach(() => {\n // Clear all mocks before each test\n vi.clearAllMocks();\n\n // Reset the module to clear any internal state\n vi.resetModules();\n });\n\n afterEach(() => {\n // Cleanup document event listeners\n document.body.replaceWith(document.body.cloneNode(true));\n });\n\n describe(\"Pre-initialization Queueing\", () => {\n it(\"should queue methods called before initialization\", async () => {\n // Call methods before initialization\n insights.track(\"early_event\", { early: true });\n insights.identify(testIdentity);\n insights.trackPageView();\n\n // Verify nothing has been called yet on the underlying services\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.track).not.toHaveBeenCalled();\n expect(datalayer.track).not.toHaveBeenCalled();\n expect(mixpanel.identify).not.toHaveBeenCalled();\n expect(posthog.identify).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Initialize should be called immediately\n expect(mixpanel.initMixpanel).toHaveBeenCalledWith(\n testConfig.mixpanelToken,\n testConfig.mixpanelAutoCapture,\n testConfig.debug,\n undefined,\n );\n expect(posthog.initPosthog).toHaveBeenCalledWith(\n testConfig.posthogApiKey,\n testConfig.posthogApiHost,\n );\n\n // Queued methods should now be called in the correct order\n expect(mixpanel.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should handle errors in queued methods gracefully\", async () => {\n // Setup an error for one of the methods\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel error\");\n });\n\n // Call methods before initialization\n insights.track(\"error_event\", { error: true });\n insights.trackPageView();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Should have logged the error but continued processing the queue\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n // The other methods should still be called\n expect(posthog.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Post-initialization Direct Execution\", () => {\n beforeEach(() => {\n // Initialize first\n insights.initInsights(testConfig);\n // Clear the mocks to focus on post-init behavior\n vi.clearAllMocks();\n });\n\n it(\"should directly call methods after initialization\", () => {\n // Call methods after initialization\n insights.track(\"post_init_event\", { post: true });\n\n // Should be called immediately\n expect(mixpanel.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n });\n\n it(\"should handle all exported methods correctly\", () => {\n // Test each exported method\n insights.identify(testIdentity);\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n insights.trackPageView();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n insights.enableDebugMode();\n expect(mixpanel.enableDebugMode).toHaveBeenCalled();\n expect(posthog.enableDebugMode).toHaveBeenCalled();\n\n insights.disableDebugMode();\n expect(mixpanel.disableDebugMode).toHaveBeenCalled();\n expect(posthog.disableDebugMode).toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Observer Setup\", () => {\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should set up click event observer and track clicks\", () => {\n // Setup observer\n const cleanup = insights.setupObserver();\n\n // Create a test element with insight attributes\n const testElement = document.createElement(\"button\");\n testElement.setAttribute(\"data-insight-event\", \"button_clicked\");\n testElement.setAttribute(\"data-insight-button-id\", \"test-123\");\n document.body.appendChild(testElement);\n\n // Simulate click\n testElement.click();\n\n // Should track the event\n expect(mixpanel.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(posthog.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n\n // Test cleanup\n cleanup();\n\n // Reset tracking mocks\n vi.clearAllMocks();\n\n // Click again - should not track\n testElement.click();\n expect(mixpanel.track).not.toHaveBeenCalled();\n });\n\n it(\"should handle nested elements correctly\", () => {\n // Setup observer\n insights.setupObserver();\n\n // Create parent element with insight attributes\n const parentElement = document.createElement(\"div\");\n parentElement.setAttribute(\"data-insight-event\", \"container_clicked\");\n parentElement.setAttribute(\n \"data-insight-container-id\",\n \"parent-container\",\n );\n\n // Create child element without insights\n const childElement = document.createElement(\"span\");\n childElement.textContent = \"Click me\";\n\n // Nest elements\n parentElement.appendChild(childElement);\n document.body.appendChild(parentElement);\n\n // Click the child element\n childElement.click();\n\n // Should find and use the parent's insight attributes\n expect(mixpanel.track).toHaveBeenCalledWith(\"container_clicked\", {\n containerId: \"parent-container\",\n });\n });\n });\n\n describe(\"Error Handling\", () => {\n it(\"should handle initialization errors gracefully\", () => {\n // Setup an error in initialization\n (mixpanel.initMixpanel as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel init error\");\n });\n\n // Should not throw when initializing\n expect(() => {\n insights.initInsights(testConfig);\n }).not.toThrow();\n\n // Should log the error\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to initialize Mixpanel\"),\n expect.any(Error),\n );\n });\n\n it(\"should handle runtime errors in methods\", () => {\n // Initialize first\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n\n // Setup errors in tracking\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel track error\");\n });\n\n (posthog.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Posthog track error\");\n });\n\n // Should not throw when tracking\n expect(() => {\n insights.track(\"error_test\", { test: true });\n }).not.toThrow();\n\n // Should log the errors\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Posthog\"),\n expect.any(Error),\n );\n\n // Should still try to track with datalayer\n expect(datalayer.track).toHaveBeenCalledWith(\"error_test\", {\n test: true,\n });\n });\n });\n\n describe(\"Debug Mode\", () => {\n it(\"should respect debug flag in config\", () => {\n // Initialize with debug: true\n insights.initInsights(testConfig);\n\n // Should log debug messages\n expect(logger.debug).toHaveBeenCalledWith(\n expect.stringContaining(\"Initializing insights\"),\n );\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"debug_test\", { debug: true });\n\n // Should log info about tracking\n expect(logger.info).toHaveBeenCalledWith(\n expect.stringContaining(\"Tracking event\"),\n expect.objectContaining({\n event: \"debug_test\",\n properties: { debug: true },\n }),\n );\n });\n\n it(\"should not log debug info when debug is false\", () => {\n // Initialize with debug: false\n insights.initInsights({\n ...testConfig,\n debug: false,\n });\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"no_debug_test\", { debug: false });\n\n // Should not log info about tracking\n expect(logger.info).not.toHaveBeenCalled();\n });\n });\n\n describe(\"Arbitrary Properties\", () => {\n const customPageViewProperties = {\n customProperty: \"custom-value\",\n anotherProperty: 123,\n nestedObject: { foo: \"bar\" },\n };\n\n const customPageViewPropertiesWithDataLayer = {\n customProperty: \"custom-value\",\n anotherProperty: 456,\n };\n\n const customPageViewPropertiesWithExcludeIds = {\n customProperty: \"test\",\n count: 789,\n };\n\n const customIdentityProperties = {\n customProperty: \"user-custom-value\",\n userSegment: \"premium\",\n signupDate: \"2024-01-01\",\n };\n\n const customMinimalIdentityProperties = {\n customField1: \"value1\",\n customField2: 999,\n customField3: { nested: true },\n };\n\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should pass arbitrary properties through trackPageView\", () => {\n // Call trackPageView with custom properties\n insights.trackPageView(customPageViewProperties);\n\n // Verify mixpanel received all properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify posthog received all properties\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify datalayer did not receive anything (includeDataLayer not set)\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should pass arbitrary properties through trackPageView with includeDataLayer\", () => {\n // Call trackPageView with custom properties and includeDataLayer\n insights.trackPageView({\n includeDataLayer: true,\n ...customPageViewPropertiesWithDataLayer,\n });\n\n // Verify mixpanel received custom properties (includeDataLayer filtered out)\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify posthog received custom properties (includeDataLayer filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify datalayer received custom properties (includeDataLayer filtered out)\n expect(datalayer.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n });\n\n it(\"should pass arbitrary properties through trackPageView with excludeIds\", () => {\n const excludeIds = [\"id1\", \"id2\"];\n\n // Call trackPageView with custom properties and excludeIds\n insights.trackPageView({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify mixpanel received excludeIds and custom properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify posthog received only custom properties (excludeIds filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithExcludeIds,\n );\n });\n\n it(\"should pass arbitrary properties through identify\", () => {\n const identityWithCustomProps = {\n ...testIdentity,\n ...customIdentityProperties,\n };\n\n // Call identify with custom properties\n insights.identify(identityWithCustomProps);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps);\n });\n\n it(\"should pass arbitrary properties through identify with minimal identity\", () => {\n const minimalIdentity = {\n userId: \"minimal-user\",\n accountId: \"minimal-account\",\n ...customMinimalIdentityProperties,\n };\n\n // Call identify with only required fields plus custom properties\n insights.identify(minimalIdentity);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity);\n });\n });\n});\n"],"names":["describe","expect","beforeEach","afterEach","it","vi","datalayer","mixpanel","posthog","logger","insights","mock","track","fn","trackPageView","initMixpanel","enableDebugMode","disableDebugMode","identify","initPosthog","debug","info","warn","error","testConfig","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","testIdentity","userId","accountId","organisationId","email","name","clearAllMocks","resetModules","document","body","replaceWith","cloneNode","early","not","toHaveBeenCalled","initInsights","toHaveBeenCalledWith","undefined","mockImplementationOnce","Error","stringContaining","any","includeDataLayer","post","cleanup","setupObserver","testElement","createElement","setAttribute","appendChild","click","buttonId","parentElement","childElement","textContent","containerId","toThrow","test","objectContaining","event","properties","customPageViewProperties","customProperty","anotherProperty","nestedObject","foo","customPageViewPropertiesWithDataLayer","customPageViewPropertiesWithExcludeIds","count","customIdentityProperties","userSegment","signupDate","customMinimalIdentityProperties","customField1","customField2","customField3","nested","excludeIds","identityWithCustomProps","minimalIdentity"],"mappings":"AAIA,OAASA,QAAQ,CAAEC,MAAM,CAAEC,UAAU,CAAEC,SAAS,CAAEC,EAAE,CAAEC,EAAE,KAAc,QAAS,AAE/E,WAAYC,cAAe,aAAc,AACzC,WAAYC,aAAc,YAAa,AACvC,WAAYC,YAAa,WAAY,AACrC,WAAYC,WAAY,UAAW,AACnC,WAAYC,aAAc,SAAU,CAGpCL,GAAGM,IAAI,CAAC,cAAe,IAAO,CAAA,CAC5BC,MAAOP,GAAGQ,EAAE,GACZC,cAAeT,GAAGQ,EAAE,EACtB,CAAA,GAEAR,GAAGM,IAAI,CAAC,aAAc,IAAO,CAAA,CAC3BI,aAAcV,GAAGQ,EAAE,GACnBG,gBAAiBX,GAAGQ,EAAE,GACtBI,iBAAkBZ,GAAGQ,EAAE,GACvBK,SAAUb,GAAGQ,EAAE,GACfC,cAAeT,GAAGQ,EAAE,GACpBD,MAAOP,GAAGQ,EAAE,EACd,CAAA,GAEAR,GAAGM,IAAI,CAAC,YAAa,IAAO,CAAA,CAC1BQ,YAAad,GAAGQ,EAAE,GAClBG,gBAAiBX,GAAGQ,EAAE,GACtBI,iBAAkBZ,GAAGQ,EAAE,GACvBK,SAAUb,GAAGQ,EAAE,GACfC,cAAeT,GAAGQ,EAAE,GACpBD,MAAOP,GAAGQ,EAAE,EACd,CAAA,GAEAR,GAAGM,IAAI,CAAC,WAAY,IAAO,CAAA,CACzBS,MAAOf,GAAGQ,EAAE,GACZQ,KAAMhB,GAAGQ,EAAE,GACXS,KAAMjB,GAAGQ,EAAE,GACXU,MAAOlB,GAAGQ,EAAE,EACd,CAAA,GAEAb,SAAS,yBAA0B,KACjC,MAAMwB,WAAa,CACjBJ,MAAO,KACPK,cAAe,aACfC,oBAAqB,MACrBC,cAAe,WACfC,eAAgB,WAClB,EAEA,MAAMC,aAAe,CACnBC,OAAQ,WACRC,UAAW,cACXC,eAAgB,UAChBC,MAAO,mBACPC,KAAM,WACR,EAEAhC,WAAW,KAETG,GAAG8B,aAAa,GAGhB9B,GAAG+B,YAAY,EACjB,GAEAjC,UAAU,KAERkC,SAASC,IAAI,CAACC,WAAW,CAACF,SAASC,IAAI,CAACE,SAAS,CAAC,MACpD,GAEAxC,SAAS,8BAA+B,KACtCI,GAAG,oDAAqD,UAEtDM,SAASE,KAAK,CAAC,cAAe,CAAE6B,MAAO,IAAK,GAC5C/B,SAASQ,QAAQ,CAACW,cAClBnB,SAASI,aAAa,GAGtBb,OAAOM,SAASK,KAAK,EAAE8B,GAAG,CAACC,gBAAgB,GAC3C1C,OAAOO,QAAQI,KAAK,EAAE8B,GAAG,CAACC,gBAAgB,GAC1C1C,OAAOK,UAAUM,KAAK,EAAE8B,GAAG,CAACC,gBAAgB,GAC5C1C,OAAOM,SAASW,QAAQ,EAAEwB,GAAG,CAACC,gBAAgB,GAC9C1C,OAAOO,QAAQU,QAAQ,EAAEwB,GAAG,CAACC,gBAAgB,GAC7C1C,OAAOM,SAASO,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,GACnD1C,OAAOO,QAAQM,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,GAClD1C,OAAOK,UAAUQ,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,GAGpDjC,SAASkC,YAAY,CAACpB,YAGtBvB,OAAOM,SAASQ,YAAY,EAAE8B,oBAAoB,CAChDrB,WAAWC,aAAa,CACxBD,WAAWE,mBAAmB,CAC9BF,WAAWJ,KAAK,CAChB0B,WAEF7C,OAAOO,QAAQW,WAAW,EAAE0B,oBAAoB,CAC9CrB,WAAWG,aAAa,CACxBH,WAAWI,cAAc,EAI3B3B,OAAOM,SAASK,KAAK,EAAEiC,oBAAoB,CAAC,cAAe,CACzDJ,MAAO,IACT,GACAxC,OAAOO,QAAQI,KAAK,EAAEiC,oBAAoB,CAAC,cAAe,CACxDJ,MAAO,IACT,GACAxC,OAAOK,UAAUM,KAAK,EAAEiC,oBAAoB,CAAC,cAAe,CAC1DJ,MAAO,IACT,GAEAxC,OAAOM,SAASW,QAAQ,EAAE2B,oBAAoB,CAAChB,cAC/C5B,OAAOO,QAAQU,QAAQ,EAAE2B,oBAAoB,CAAChB,cAE9C5B,OAAOM,SAASO,aAAa,EAAE6B,gBAAgB,GAC/C1C,OAAOO,QAAQM,aAAa,EAAE6B,gBAAgB,GAC9C1C,OAAOK,UAAUQ,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,EACtD,GAEAvC,GAAG,oDAAqD,UAEtD,AAACG,SAASK,KAAK,CAAUmC,sBAAsB,CAAC,KAC9C,MAAM,IAAIC,MAAM,iBAClB,GAGAtC,SAASE,KAAK,CAAC,cAAe,CAAEW,MAAO,IAAK,GAC5Cb,SAASI,aAAa,GAGtBJ,SAASkC,YAAY,CAACpB,YAGtBvB,OAAOQ,OAAOc,KAAK,EAAEsB,oBAAoB,CACvC5C,OAAOgD,gBAAgB,CAAC,qCACxBhD,OAAOiD,GAAG,CAACF,QAIb/C,OAAOO,QAAQI,KAAK,EAAEiC,oBAAoB,CAAC,cAAe,CACxDtB,MAAO,IACT,GACAtB,OAAOK,UAAUM,KAAK,EAAEiC,oBAAoB,CAAC,cAAe,CAC1DtB,MAAO,IACT,GACAtB,OAAOM,SAASO,aAAa,EAAE6B,gBAAgB,GAC/C1C,OAAOO,QAAQM,aAAa,EAAE6B,gBAAgB,GAC9C1C,OAAOK,UAAUQ,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,EACtD,GAEAvC,GAAG,uEAAwE,KACzEM,SAASI,aAAa,CAAC,CAAEqC,iBAAkB,IAAK,GAChDlD,OAAOM,SAASO,aAAa,EAAE6B,gBAAgB,GAC/C1C,OAAOO,QAAQM,aAAa,EAAE6B,gBAAgB,GAC9C1C,OAAOK,UAAUQ,aAAa,EAAE6B,gBAAgB,EAClD,EACF,GAEA3C,SAAS,uCAAwC,KAC/CE,WAAW,KAETQ,SAASkC,YAAY,CAACpB,YAEtBnB,GAAG8B,aAAa,EAClB,GAEA/B,GAAG,oDAAqD,KAEtDM,SAASE,KAAK,CAAC,kBAAmB,CAAEwC,KAAM,IAAK,GAG/CnD,OAAOM,SAASK,KAAK,EAAEiC,oBAAoB,CAAC,kBAAmB,CAC7DO,KAAM,IACR,GACAnD,OAAOO,QAAQI,KAAK,EAAEiC,oBAAoB,CAAC,kBAAmB,CAC5DO,KAAM,IACR,GACAnD,OAAOK,UAAUM,KAAK,EAAEiC,oBAAoB,CAAC,kBAAmB,CAC9DO,KAAM,IACR,EACF,GAEAhD,GAAG,+CAAgD,KAEjDM,SAASQ,QAAQ,CAACW,cAClB5B,OAAOM,SAASW,QAAQ,EAAE2B,oBAAoB,CAAChB,cAC/C5B,OAAOO,QAAQU,QAAQ,EAAE2B,oBAAoB,CAAChB,cAE9CnB,SAASI,aAAa,GACtBb,OAAOM,SAASO,aAAa,EAAE6B,gBAAgB,GAC/C1C,OAAOO,QAAQM,aAAa,EAAE6B,gBAAgB,GAC9C1C,OAAOK,UAAUQ,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,GAEpDjC,SAASM,eAAe,GACxBf,OAAOM,SAASS,eAAe,EAAE2B,gBAAgB,GACjD1C,OAAOO,QAAQQ,eAAe,EAAE2B,gBAAgB,GAEhDjC,SAASO,gBAAgB,GACzBhB,OAAOM,SAASU,gBAAgB,EAAE0B,gBAAgB,GAClD1C,OAAOO,QAAQS,gBAAgB,EAAE0B,gBAAgB,EACnD,GAEAvC,GAAG,uEAAwE,KACzEM,SAASI,aAAa,CAAC,CAAEqC,iBAAkB,IAAK,GAChDlD,OAAOM,SAASO,aAAa,EAAE6B,gBAAgB,GAC/C1C,OAAOO,QAAQM,aAAa,EAAE6B,gBAAgB,GAC9C1C,OAAOK,UAAUQ,aAAa,EAAE6B,gBAAgB,EAClD,EACF,GAEA3C,SAAS,iBAAkB,KACzBE,WAAW,KACTQ,SAASkC,YAAY,CAACpB,YACtBnB,GAAG8B,aAAa,EAClB,GAEA/B,GAAG,sDAAuD,KAExD,MAAMiD,QAAU3C,SAAS4C,aAAa,GAGtC,MAAMC,YAAclB,SAASmB,aAAa,CAAC,UAC3CD,YAAYE,YAAY,CAAC,qBAAsB,kBAC/CF,YAAYE,YAAY,CAAC,yBAA0B,YACnDpB,SAASC,IAAI,CAACoB,WAAW,CAACH,aAG1BA,YAAYI,KAAK,GAGjB1D,OAAOM,SAASK,KAAK,EAAEiC,oBAAoB,CAAC,iBAAkB,CAC5De,SAAU,UACZ,GACA3D,OAAOO,QAAQI,KAAK,EAAEiC,oBAAoB,CAAC,iBAAkB,CAC3De,SAAU,UACZ,GACA3D,OAAOK,UAAUM,KAAK,EAAEiC,oBAAoB,CAAC,iBAAkB,CAC7De,SAAU,UACZ,GAGAP,UAGAhD,GAAG8B,aAAa,GAGhBoB,YAAYI,KAAK,GACjB1D,OAAOM,SAASK,KAAK,EAAE8B,GAAG,CAACC,gBAAgB,EAC7C,GAEAvC,GAAG,0CAA2C,KAE5CM,SAAS4C,aAAa,GAGtB,MAAMO,cAAgBxB,SAASmB,aAAa,CAAC,OAC7CK,cAAcJ,YAAY,CAAC,qBAAsB,qBACjDI,cAAcJ,YAAY,CACxB,4BACA,oBAIF,MAAMK,aAAezB,SAASmB,aAAa,CAAC,OAC5CM,CAAAA,aAAaC,WAAW,CAAG,WAG3BF,cAAcH,WAAW,CAACI,cAC1BzB,SAASC,IAAI,CAACoB,WAAW,CAACG,eAG1BC,aAAaH,KAAK,GAGlB1D,OAAOM,SAASK,KAAK,EAAEiC,oBAAoB,CAAC,oBAAqB,CAC/DmB,YAAa,kBACf,EACF,EACF,GAEAhE,SAAS,iBAAkB,KACzBI,GAAG,iDAAkD,KAEnD,AAACG,SAASQ,YAAY,CAAUgC,sBAAsB,CAAC,KACrD,MAAM,IAAIC,MAAM,sBAClB,GAGA/C,OAAO,KACLS,SAASkC,YAAY,CAACpB,WACxB,GAAGkB,GAAG,CAACuB,OAAO,GAGdhE,OAAOQ,OAAOc,KAAK,EAAEsB,oBAAoB,CACvC5C,OAAOgD,gBAAgB,CAAC,iCACxBhD,OAAOiD,GAAG,CAACF,OAEf,GAEA5C,GAAG,0CAA2C,KAE5CM,SAASkC,YAAY,CAACpB,YACtBnB,GAAG8B,aAAa,GAGhB,AAAC5B,SAASK,KAAK,CAAUmC,sBAAsB,CAAC,KAC9C,MAAM,IAAIC,MAAM,uBAClB,GAEA,AAACxC,QAAQI,KAAK,CAAUmC,sBAAsB,CAAC,KAC7C,MAAM,IAAIC,MAAM,sBAClB,GAGA/C,OAAO,KACLS,SAASE,KAAK,CAAC,aAAc,CAAEsD,KAAM,IAAK,EAC5C,GAAGxB,GAAG,CAACuB,OAAO,GAGdhE,OAAOQ,OAAOc,KAAK,EAAEsB,oBAAoB,CACvC5C,OAAOgD,gBAAgB,CAAC,qCACxBhD,OAAOiD,GAAG,CAACF,QAGb/C,OAAOQ,OAAOc,KAAK,EAAEsB,oBAAoB,CACvC5C,OAAOgD,gBAAgB,CAAC,oCACxBhD,OAAOiD,GAAG,CAACF,QAIb/C,OAAOK,UAAUM,KAAK,EAAEiC,oBAAoB,CAAC,aAAc,CACzDqB,KAAM,IACR,EACF,EACF,GAEAlE,SAAS,aAAc,KACrBI,GAAG,sCAAuC,KAExCM,SAASkC,YAAY,CAACpB,YAGtBvB,OAAOQ,OAAOW,KAAK,EAAEyB,oBAAoB,CACvC5C,OAAOgD,gBAAgB,CAAC,0BAI1B5C,GAAG8B,aAAa,GAChBzB,SAASE,KAAK,CAAC,aAAc,CAAEQ,MAAO,IAAK,GAG3CnB,OAAOQ,OAAOY,IAAI,EAAEwB,oBAAoB,CACtC5C,OAAOgD,gBAAgB,CAAC,kBACxBhD,OAAOkE,gBAAgB,CAAC,CACtBC,MAAO,aACPC,WAAY,CAAEjD,MAAO,IAAK,CAC5B,GAEJ,GAEAhB,GAAG,gDAAiD,KAElDM,SAASkC,YAAY,CAAC,CACpB,GAAGpB,UAAU,CACbJ,MAAO,KACT,GAGAf,GAAG8B,aAAa,GAChBzB,SAASE,KAAK,CAAC,gBAAiB,CAAEQ,MAAO,KAAM,GAG/CnB,OAAOQ,OAAOY,IAAI,EAAEqB,GAAG,CAACC,gBAAgB,EAC1C,EACF,GAEA3C,SAAS,uBAAwB,KAC/B,MAAMsE,yBAA2B,CAC/BC,eAAgB,eAChBC,gBAAiB,IACjBC,aAAc,CAAEC,IAAK,KAAM,CAC7B,EAEA,MAAMC,sCAAwC,CAC5CJ,eAAgB,eAChBC,gBAAiB,GACnB,EAEA,MAAMI,uCAAyC,CAC7CL,eAAgB,OAChBM,MAAO,GACT,EAEA,MAAMC,yBAA2B,CAC/BP,eAAgB,oBAChBQ,YAAa,UACbC,WAAY,YACd,EAEA,MAAMC,gCAAkC,CACtCC,aAAc,SACdC,aAAc,IACdC,aAAc,CAAEC,OAAQ,IAAK,CAC/B,EAEAnF,WAAW,KACTQ,SAASkC,YAAY,CAACpB,YACtBnB,GAAG8B,aAAa,EAClB,GAEA/B,GAAG,yDAA0D,KAE3DM,SAASI,aAAa,CAACwD,0BAGvBrE,OAAOM,SAASO,aAAa,EAAE+B,oBAAoB,CACjDyB,0BAIFrE,OAAOO,QAAQM,aAAa,EAAE+B,oBAAoB,CAChDyB,0BAIFrE,OAAOK,UAAUQ,aAAa,EAAE4B,GAAG,CAACC,gBAAgB,EACtD,GAEAvC,GAAG,+EAAgF,KAEjFM,SAASI,aAAa,CAAC,CACrBqC,iBAAkB,KAClB,GAAGwB,qCAAqC,AAC1C,GAGA1E,OAAOM,SAASO,aAAa,EAAE+B,oBAAoB,CACjD8B,uCAIF1E,OAAOO,QAAQM,aAAa,EAAE+B,oBAAoB,CAChD8B,uCAIF1E,OAAOK,UAAUQ,aAAa,EAAE+B,oBAAoB,CAClD8B,sCAEJ,GAEAvE,GAAG,yEAA0E,KAC3E,MAAMkF,WAAa,CAAC,MAAO,MAAM,CAGjC5E,SAASI,aAAa,CAAC,CACrBwE,WACA,GAAGV,sCAAsC,AAC3C,GAGA3E,OAAOM,SAASO,aAAa,EAAE+B,oBAAoB,CAAC,CAClDyC,WACA,GAAGV,sCAAsC,AAC3C,GAGA3E,OAAOO,QAAQM,aAAa,EAAE+B,oBAAoB,CAChD+B,uCAEJ,GAEAxE,GAAG,oDAAqD,KACtD,MAAMmF,wBAA0B,CAC9B,GAAG1D,YAAY,CACf,GAAGiD,wBAAwB,AAC7B,EAGApE,SAASQ,QAAQ,CAACqE,yBAGlBtF,OAAOM,SAASW,QAAQ,EAAE2B,oBAAoB,CAAC0C,yBAG/CtF,OAAOO,QAAQU,QAAQ,EAAE2B,oBAAoB,CAAC0C,wBAChD,GAEAnF,GAAG,0EAA2E,KAC5E,MAAMoF,gBAAkB,CACtB1D,OAAQ,eACRC,UAAW,kBACX,GAAGkD,+BAA+B,AACpC,EAGAvE,SAASQ,QAAQ,CAACsE,iBAGlBvF,OAAOM,SAASW,QAAQ,EAAE2B,oBAAoB,CAAC2C,iBAG/CvF,OAAOO,QAAQU,QAAQ,EAAE2B,oBAAoB,CAAC2C,gBAChD,EACF,EACF"}
1
+ {"version":3,"sources":["../../../src/core/insights/index.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, afterEach, it, vi, Mock } from \"vitest\";\n\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\nimport * as insights from \"./index\";\n\n// Mock the dependencies\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\n// Isolate these tests from the real isbot pattern list — a future isbot\n// update matching the test environment's UA would otherwise gate every init\n// here and silently invert the call-expectation assertions\nvi.mock(\"./bot-detection\", () => ({\n isLikelyAutomated: vi.fn().mockReturnValue(false),\n}));\n\ndescribe(\"Insights Command Queue\", () => {\n const testConfig = {\n debug: true,\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: false,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n const testIdentity = {\n userId: \"user-123\",\n accountId: \"account-456\",\n organisationId: \"org-789\",\n email: \"test@example.com\",\n name: \"Test User\",\n };\n\n beforeEach(() => {\n // Clear all mocks before each test\n vi.clearAllMocks();\n\n // Reset the module to clear any internal state\n vi.resetModules();\n });\n\n afterEach(() => {\n // Cleanup document event listeners\n document.body.replaceWith(document.body.cloneNode(true));\n });\n\n describe(\"Pre-initialization Queueing\", () => {\n it(\"should queue methods called before initialization\", async () => {\n // Call methods before initialization\n insights.track(\"early_event\", { early: true });\n insights.identify(testIdentity);\n insights.trackPageView();\n\n // Verify nothing has been called yet on the underlying services\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.track).not.toHaveBeenCalled();\n expect(datalayer.track).not.toHaveBeenCalled();\n expect(mixpanel.identify).not.toHaveBeenCalled();\n expect(posthog.identify).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Initialize should be called immediately\n expect(mixpanel.initMixpanel).toHaveBeenCalledWith(\n testConfig.mixpanelToken,\n testConfig.mixpanelAutoCapture,\n testConfig.debug,\n undefined,\n );\n expect(posthog.initPosthog).toHaveBeenCalledWith(\n testConfig.posthogApiKey,\n testConfig.posthogApiHost,\n );\n\n // Queued methods should now be called in the correct order\n expect(mixpanel.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should handle errors in queued methods gracefully\", async () => {\n // Setup an error for one of the methods\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel error\");\n });\n\n // Call methods before initialization\n insights.track(\"error_event\", { error: true });\n insights.trackPageView();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Should have logged the error but continued processing the queue\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n // The other methods should still be called\n expect(posthog.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Post-initialization Direct Execution\", () => {\n beforeEach(() => {\n // Initialize first\n insights.initInsights(testConfig);\n // Clear the mocks to focus on post-init behavior\n vi.clearAllMocks();\n });\n\n it(\"should directly call methods after initialization\", () => {\n // Call methods after initialization\n insights.track(\"post_init_event\", { post: true });\n\n // Should be called immediately\n expect(mixpanel.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n });\n\n it(\"should handle all exported methods correctly\", () => {\n // Test each exported method\n insights.identify(testIdentity);\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n insights.trackPageView();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n insights.enableDebugMode();\n expect(mixpanel.enableDebugMode).toHaveBeenCalled();\n expect(posthog.enableDebugMode).toHaveBeenCalled();\n\n insights.disableDebugMode();\n expect(mixpanel.disableDebugMode).toHaveBeenCalled();\n expect(posthog.disableDebugMode).toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Observer Setup\", () => {\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should set up click event observer and track clicks\", () => {\n // Setup observer\n const cleanup = insights.setupObserver();\n\n // Create a test element with insight attributes\n const testElement = document.createElement(\"button\");\n testElement.setAttribute(\"data-insight-event\", \"button_clicked\");\n testElement.setAttribute(\"data-insight-button-id\", \"test-123\");\n document.body.appendChild(testElement);\n\n // Simulate click\n testElement.click();\n\n // Should track the event\n expect(mixpanel.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(posthog.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n\n // Test cleanup\n cleanup();\n\n // Reset tracking mocks\n vi.clearAllMocks();\n\n // Click again - should not track\n testElement.click();\n expect(mixpanel.track).not.toHaveBeenCalled();\n });\n\n it(\"should handle nested elements correctly\", () => {\n // Setup observer\n insights.setupObserver();\n\n // Create parent element with insight attributes\n const parentElement = document.createElement(\"div\");\n parentElement.setAttribute(\"data-insight-event\", \"container_clicked\");\n parentElement.setAttribute(\n \"data-insight-container-id\",\n \"parent-container\",\n );\n\n // Create child element without insights\n const childElement = document.createElement(\"span\");\n childElement.textContent = \"Click me\";\n\n // Nest elements\n parentElement.appendChild(childElement);\n document.body.appendChild(parentElement);\n\n // Click the child element\n childElement.click();\n\n // Should find and use the parent's insight attributes\n expect(mixpanel.track).toHaveBeenCalledWith(\"container_clicked\", {\n containerId: \"parent-container\",\n });\n });\n });\n\n describe(\"Error Handling\", () => {\n it(\"should handle initialization errors gracefully\", () => {\n // Setup an error in initialization\n (mixpanel.initMixpanel as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel init error\");\n });\n\n // Should not throw when initializing\n expect(() => {\n insights.initInsights(testConfig);\n }).not.toThrow();\n\n // Should log the error\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to initialize Mixpanel\"),\n expect.any(Error),\n );\n });\n\n it(\"should handle runtime errors in methods\", () => {\n // Initialize first\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n\n // Setup errors in tracking\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel track error\");\n });\n\n (posthog.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Posthog track error\");\n });\n\n // Should not throw when tracking\n expect(() => {\n insights.track(\"error_test\", { test: true });\n }).not.toThrow();\n\n // Should log the errors\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Posthog\"),\n expect.any(Error),\n );\n\n // Should still try to track with datalayer\n expect(datalayer.track).toHaveBeenCalledWith(\"error_test\", {\n test: true,\n });\n });\n });\n\n describe(\"Debug Mode\", () => {\n it(\"should respect debug flag in config\", () => {\n // Initialize with debug: true\n insights.initInsights(testConfig);\n\n // Should log debug messages\n expect(logger.debug).toHaveBeenCalledWith(\n expect.stringContaining(\"Initializing insights\"),\n );\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"debug_test\", { debug: true });\n\n // Should log info about tracking\n expect(logger.info).toHaveBeenCalledWith(\n expect.stringContaining(\"Tracking event\"),\n expect.objectContaining({\n event: \"debug_test\",\n properties: { debug: true },\n }),\n );\n });\n\n it(\"should not log debug info when debug is false\", () => {\n // Initialize with debug: false\n insights.initInsights({\n ...testConfig,\n debug: false,\n });\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"no_debug_test\", { debug: false });\n\n // Should not log info about tracking\n expect(logger.info).not.toHaveBeenCalled();\n });\n });\n\n describe(\"Arbitrary Properties\", () => {\n const customPageViewProperties = {\n customProperty: \"custom-value\",\n anotherProperty: 123,\n nestedObject: { foo: \"bar\" },\n };\n\n const customPageViewPropertiesWithDataLayer = {\n customProperty: \"custom-value\",\n anotherProperty: 456,\n };\n\n const customPageViewPropertiesWithExcludeIds = {\n customProperty: \"test\",\n count: 789,\n };\n\n const customIdentityProperties = {\n customProperty: \"user-custom-value\",\n userSegment: \"premium\",\n signupDate: \"2024-01-01\",\n };\n\n const customMinimalIdentityProperties = {\n customField1: \"value1\",\n customField2: 999,\n customField3: { nested: true },\n };\n\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should pass arbitrary properties through trackPageView\", () => {\n // Call trackPageView with custom properties\n insights.trackPageView(customPageViewProperties);\n\n // Verify mixpanel received all properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify posthog received all properties\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify datalayer did not receive anything (includeDataLayer not set)\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should pass arbitrary properties through trackPageView with includeDataLayer\", () => {\n // Call trackPageView with custom properties and includeDataLayer\n insights.trackPageView({\n includeDataLayer: true,\n ...customPageViewPropertiesWithDataLayer,\n });\n\n // Verify mixpanel received custom properties (includeDataLayer filtered out)\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify posthog received custom properties (includeDataLayer filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify datalayer received custom properties (includeDataLayer filtered out)\n expect(datalayer.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n });\n\n it(\"should pass arbitrary properties through trackPageView with excludeIds\", () => {\n const excludeIds = [\"id1\", \"id2\"];\n\n // Call trackPageView with custom properties and excludeIds\n insights.trackPageView({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify mixpanel received excludeIds and custom properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify posthog received only custom properties (excludeIds filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithExcludeIds,\n );\n });\n\n it(\"should pass arbitrary properties through identify\", () => {\n const identityWithCustomProps = {\n ...testIdentity,\n ...customIdentityProperties,\n };\n\n // Call identify with custom properties\n insights.identify(identityWithCustomProps);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps);\n });\n\n it(\"should pass arbitrary properties through identify with minimal identity\", () => {\n const minimalIdentity = {\n userId: \"minimal-user\",\n accountId: \"minimal-account\",\n ...customMinimalIdentityProperties,\n };\n\n // Call identify with only required fields plus custom properties\n insights.identify(minimalIdentity);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity);\n });\n });\n});\n"],"names":["describe","expect","beforeEach","afterEach","it","vi","datalayer","mixpanel","posthog","logger","insights","mock","track","fn","trackPageView","initMixpanel","enableDebugMode","disableDebugMode","identify","initPosthog","debug","info","warn","error","isLikelyAutomated","mockReturnValue","testConfig","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","testIdentity","userId","accountId","organisationId","email","name","clearAllMocks","resetModules","document","body","replaceWith","cloneNode","early","not","toHaveBeenCalled","initInsights","toHaveBeenCalledWith","undefined","mockImplementationOnce","Error","stringContaining","any","includeDataLayer","post","cleanup","setupObserver","testElement","createElement","setAttribute","appendChild","click","buttonId","parentElement","childElement","textContent","containerId","toThrow","test","objectContaining","event","properties","customPageViewProperties","customProperty","anotherProperty","nestedObject","foo","customPageViewPropertiesWithDataLayer","customPageViewPropertiesWithExcludeIds","count","customIdentityProperties","userSegment","signupDate","customMinimalIdentityProperties","customField1","customField2","customField3","nested","excludeIds","identityWithCustomProps","minimalIdentity"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import mixpanel from"mixpanel-browser";export const MIXPANEL_EU_API_HOST="https://api-eu.mixpanel.com";export const initMixpanel=(token,autoCapture=false,debug=false,apiHost=MIXPANEL_EU_API_HOST)=>{const blockSelectors=["[data-mask]"];if(!token){console.warn("Mixpanel token not provided, skipping initialization");return}mixpanel.init(token,{api_host:apiHost,debug:debug,persistence:"localStorage",autocapture:autoCapture?{block_selectors:blockSelectors,capture_text_content:true,pageview:false}:false,track_pageview:false,record_sessions_percent:0})};export const enableDebugMode=()=>{mixpanel.set_config({debug:true})};export const disableDebugMode=()=>{mixpanel.set_config({debug:false})};export const identify=({userId,accountId,organisationId,email,name,...properties})=>{if(!userId){return}mixpanel.identify(userId.toString());const peopleProperties={...properties};if(email||name){peopleProperties.$email=email;peopleProperties.$name=name}if(organisationId){peopleProperties.organization_id=[organisationId.toString()]}if(Object.keys(peopleProperties).length>0){mixpanel.people.set(peopleProperties)}if(accountId){mixpanel.people.union({accounts:[accountId.toString()]})}};const redactUrlSegments=excludeIds=>{const pathSegments=window.location.pathname.split("/");const redactedSegments=pathSegments.map(segment=>{if(/^\d+$/.test(segment)||excludeIds.some(id=>id&&id!==""&&segment===id)){return"{redacted}"}return segment});const url=new URL(window.location.href);url.pathname=redactedSegments.join("/");return decodeURI(url.toString()).toLowerCase()};export const trackPageView=properties=>{const{excludeIds,...rest}=properties??{};mixpanel.track_pageview({redacted_path:redactUrlSegments(excludeIds??[]),...rest})};export const track=(event,properties)=>{mixpanel.track(event,properties)};
1
+ import mixpanel from"mixpanel-browser";export const MIXPANEL_EU_API_HOST="https://api-eu.mixpanel.com";export const initMixpanel=(token,autoCapture=false,debug=false,apiHost=MIXPANEL_EU_API_HOST)=>{const blockSelectors=["[data-mask]"];if(!token){console.warn("Mixpanel token not provided, skipping initialization");return}mixpanel.init(token,{api_host:apiHost,debug:debug,persistence:"localStorage",autocapture:autoCapture?{block_selectors:blockSelectors,capture_text_content:true,pageview:false,scroll:false,input:false,dead_click:false,rage_click:false}:false,track_pageview:false,record_sessions_percent:0})};export const enableDebugMode=()=>{mixpanel.set_config({debug:true})};export const disableDebugMode=()=>{mixpanel.set_config({debug:false})};export const identify=({userId,accountId,organisationId,email,name,...properties})=>{if(!userId){return}mixpanel.identify(userId.toString());const peopleProperties={...properties};if(email||name){peopleProperties.$email=email;peopleProperties.$name=name}if(organisationId){peopleProperties.organization_id=[organisationId.toString()]}if(Object.keys(peopleProperties).length>0){mixpanel.people.set(peopleProperties)}if(accountId){mixpanel.people.union({accounts:[accountId.toString()]})}};const redactUrlSegments=excludeIds=>{const pathSegments=window.location.pathname.split("/");const redactedSegments=pathSegments.map(segment=>{if(/^\d+$/.test(segment)||excludeIds.some(id=>id&&id!==""&&segment===id)){return"{redacted}"}return segment});const url=new URL(window.location.href);url.pathname=redactedSegments.join("/");return decodeURI(url.toString()).toLowerCase()};export const trackPageView=properties=>{const{excludeIds,...rest}=properties??{};mixpanel.track_pageview({redacted_path:redactUrlSegments(excludeIds??[]),...rest})};export const track=(event,properties)=>{mixpanel.track(event,properties)};
2
2
  //# sourceMappingURL=mixpanel.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/mixpanel.ts"],"sourcesContent":["import mixpanel from \"mixpanel-browser\";\n\nimport { InsightsIdentity, TrackPageViewOptions } from \"./types\";\n\nexport const MIXPANEL_EU_API_HOST = \"https://api-eu.mixpanel.com\";\n\nexport const initMixpanel = (\n token: string,\n autoCapture: boolean = false,\n debug: boolean = false,\n apiHost: string = MIXPANEL_EU_API_HOST,\n) => {\n const blockSelectors = [\"[data-mask]\"];\n if (!token) {\n console.warn(\"Mixpanel token not provided, skipping initialization\");\n return;\n }\n\n mixpanel.init(token, {\n api_host: apiHost,\n debug: debug,\n persistence: \"localStorage\",\n autocapture: autoCapture\n ? {\n block_selectors: blockSelectors,\n capture_text_content: true,\n pageview: false, // We'll track page views manually\n }\n : false,\n track_pageview: false, // We'll track page views manually\n record_sessions_percent: 0,\n });\n};\n\nexport const enableDebugMode = () => {\n mixpanel.set_config({ debug: true });\n};\n\nexport const disableDebugMode = () => {\n mixpanel.set_config({ debug: false });\n};\n\nexport const identify = ({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties\n}: InsightsIdentity) => {\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n return;\n }\n\n mixpanel.identify(userId.toString());\n\n const peopleProperties: Record<string, unknown> = { ...properties };\n\n if (email || name) {\n peopleProperties.$email = email;\n peopleProperties.$name = name;\n }\n\n if (organisationId) {\n peopleProperties.organization_id = [organisationId.toString()];\n }\n\n if (Object.keys(peopleProperties).length > 0) {\n mixpanel.people.set(peopleProperties);\n }\n\n if (accountId) {\n mixpanel.people.union({ accounts: [accountId.toString()] });\n }\n};\n\n// Simple function to replace all digits and IDs in a URL path with {redacted},\n// purely to make reporting based on aggregates easier\nconst redactUrlSegments = (excludeIds: string[]) => {\n const pathSegments = window.location.pathname.split(\"/\");\n\n const redactedSegments = pathSegments.map((segment) => {\n // Redact if the segment contains only digits or matches any of the excluded IDs\n if (\n /^\\d+$/.test(segment) ||\n excludeIds.some((id) => id && id !== \"\" && segment === id)\n ) {\n return \"{redacted}\";\n }\n\n return segment;\n });\n\n // Join the segments back together\n const url = new URL(window.location.href);\n url.pathname = redactedSegments.join(\"/\");\n\n return decodeURI(url.toString()).toLowerCase();\n};\n\nexport const trackPageView = (properties?: TrackPageViewOptions) => {\n const { excludeIds, ...rest } = properties ?? {};\n\n // Add the redacted URL to the page view event for reporting\n mixpanel.track_pageview({\n redacted_path: redactUrlSegments(excludeIds ?? []),\n ...rest,\n });\n};\n\nexport const track = (event: string, properties?: Record<string, unknown>) => {\n mixpanel.track(event, properties);\n};\n"],"names":["mixpanel","MIXPANEL_EU_API_HOST","initMixpanel","token","autoCapture","debug","apiHost","blockSelectors","console","warn","init","api_host","persistence","autocapture","block_selectors","capture_text_content","pageview","track_pageview","record_sessions_percent","enableDebugMode","set_config","disableDebugMode","identify","userId","accountId","organisationId","email","name","properties","toString","peopleProperties","$email","$name","organization_id","Object","keys","length","people","set","union","accounts","redactUrlSegments","excludeIds","pathSegments","window","location","pathname","split","redactedSegments","map","segment","test","some","id","url","URL","href","join","decodeURI","toLowerCase","trackPageView","rest","redacted_path","track","event"],"mappings":"AAAA,OAAOA,aAAc,kBAAmB,AAIxC,QAAO,MAAMC,qBAAuB,6BAA8B,AAElE,QAAO,MAAMC,aAAe,CAC1BC,MACAC,YAAuB,KAAK,CAC5BC,MAAiB,KAAK,CACtBC,QAAkBL,oBAAoB,IAEtC,MAAMM,eAAiB,CAAC,cAAc,CACtC,GAAI,CAACJ,MAAO,CACVK,QAAQC,IAAI,CAAC,wDACb,MACF,CAEAT,SAASU,IAAI,CAACP,MAAO,CACnBQ,SAAUL,QACVD,MAAOA,MACPO,YAAa,eACbC,YAAaT,YACT,CACEU,gBAAiBP,eACjBQ,qBAAsB,KACtBC,SAAU,KACZ,EACA,MACJC,eAAgB,MAChBC,wBAAyB,CAC3B,EACF,CAAE,AAEF,QAAO,MAAMC,gBAAkB,KAC7BnB,SAASoB,UAAU,CAAC,CAAEf,MAAO,IAAK,EACpC,CAAE,AAEF,QAAO,MAAMgB,iBAAmB,KAC9BrB,SAASoB,UAAU,CAAC,CAAEf,MAAO,KAAM,EACrC,CAAE,AAEF,QAAO,MAAMiB,SAAW,CAAC,CACvBC,MAAM,CACNC,SAAS,CACTC,cAAc,CACdC,KAAK,CACLC,IAAI,CACJ,GAAGC,WACc,IAGjB,GAAI,CAACL,OAAQ,CACX,MACF,CAEAvB,SAASsB,QAAQ,CAACC,OAAOM,QAAQ,IAEjC,MAAMC,iBAA4C,CAAE,GAAGF,UAAU,AAAC,EAElE,GAAIF,OAASC,KAAM,CACjBG,iBAAiBC,MAAM,CAAGL,KAC1BI,CAAAA,iBAAiBE,KAAK,CAAGL,IAC3B,CAEA,GAAIF,eAAgB,CAClBK,iBAAiBG,eAAe,CAAG,CAACR,eAAeI,QAAQ,GAAG,AAChE,CAEA,GAAIK,OAAOC,IAAI,CAACL,kBAAkBM,MAAM,CAAG,EAAG,CAC5CpC,SAASqC,MAAM,CAACC,GAAG,CAACR,iBACtB,CAEA,GAAIN,UAAW,CACbxB,SAASqC,MAAM,CAACE,KAAK,CAAC,CAAEC,SAAU,CAAChB,UAAUK,QAAQ,GAAG,AAAC,EAC3D,CACF,CAAE,CAIF,MAAMY,kBAAoB,AAACC,aACzB,MAAMC,aAAeC,OAAOC,QAAQ,CAACC,QAAQ,CAACC,KAAK,CAAC,KAEpD,MAAMC,iBAAmBL,aAAaM,GAAG,CAAC,AAACC,UAEzC,GACE,QAAQC,IAAI,CAACD,UACbR,WAAWU,IAAI,CAAC,AAACC,IAAOA,IAAMA,KAAO,IAAMH,UAAYG,IACvD,CACA,MAAO,YACT,CAEA,OAAOH,OACT,GAGA,MAAMI,IAAM,IAAIC,IAAIX,OAAOC,QAAQ,CAACW,IAAI,CACxCF,CAAAA,IAAIR,QAAQ,CAAGE,iBAAiBS,IAAI,CAAC,KAErC,OAAOC,UAAUJ,IAAIzB,QAAQ,IAAI8B,WAAW,EAC9C,CAEA,QAAO,MAAMC,cAAgB,AAAChC,aAC5B,KAAM,CAAEc,UAAU,CAAE,GAAGmB,KAAM,CAAGjC,YAAc,CAAC,EAG/C5B,SAASiB,cAAc,CAAC,CACtB6C,cAAerB,kBAAkBC,YAAc,EAAE,EACjD,GAAGmB,IAAI,AACT,EACF,CAAE,AAEF,QAAO,MAAME,MAAQ,CAACC,MAAepC,cACnC5B,SAAS+D,KAAK,CAACC,MAAOpC,WACxB,CAAE"}
1
+ {"version":3,"sources":["../../../src/core/insights/mixpanel.ts"],"sourcesContent":["import mixpanel from \"mixpanel-browser\";\n\nimport { InsightsIdentity, TrackPageViewOptions } from \"./types\";\n\nexport const MIXPANEL_EU_API_HOST = \"https://api-eu.mixpanel.com\";\n\nexport const initMixpanel = (\n token: string,\n autoCapture: boolean = false,\n debug: boolean = false,\n apiHost: string = MIXPANEL_EU_API_HOST,\n) => {\n const blockSelectors = [\"[data-mask]\"];\n if (!token) {\n console.warn(\"Mixpanel token not provided, skipping initialization\");\n return;\n }\n\n mixpanel.init(token, {\n api_host: apiHost,\n debug: debug,\n persistence: \"localStorage\",\n autocapture: autoCapture\n ? {\n block_selectors: blockSelectors,\n capture_text_content: true,\n pageview: false, // We'll track page views manually\n // Keep clicks and submits: the other interaction events are noise\n // that dominates our Mixpanel event volume (and therefore billing)\n scroll: false,\n input: false,\n dead_click: false,\n rage_click: false,\n }\n : false,\n track_pageview: false, // We'll track page views manually\n record_sessions_percent: 0,\n });\n};\n\nexport const enableDebugMode = () => {\n mixpanel.set_config({ debug: true });\n};\n\nexport const disableDebugMode = () => {\n mixpanel.set_config({ debug: false });\n};\n\nexport const identify = ({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties\n}: InsightsIdentity) => {\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n return;\n }\n\n mixpanel.identify(userId.toString());\n\n const peopleProperties: Record<string, unknown> = { ...properties };\n\n if (email || name) {\n peopleProperties.$email = email;\n peopleProperties.$name = name;\n }\n\n if (organisationId) {\n peopleProperties.organization_id = [organisationId.toString()];\n }\n\n if (Object.keys(peopleProperties).length > 0) {\n mixpanel.people.set(peopleProperties);\n }\n\n if (accountId) {\n mixpanel.people.union({ accounts: [accountId.toString()] });\n }\n};\n\n// Simple function to replace all digits and IDs in a URL path with {redacted},\n// purely to make reporting based on aggregates easier\nconst redactUrlSegments = (excludeIds: string[]) => {\n const pathSegments = window.location.pathname.split(\"/\");\n\n const redactedSegments = pathSegments.map((segment) => {\n // Redact if the segment contains only digits or matches any of the excluded IDs\n if (\n /^\\d+$/.test(segment) ||\n excludeIds.some((id) => id && id !== \"\" && segment === id)\n ) {\n return \"{redacted}\";\n }\n\n return segment;\n });\n\n // Join the segments back together\n const url = new URL(window.location.href);\n url.pathname = redactedSegments.join(\"/\");\n\n return decodeURI(url.toString()).toLowerCase();\n};\n\nexport const trackPageView = (properties?: TrackPageViewOptions) => {\n const { excludeIds, ...rest } = properties ?? {};\n\n // Add the redacted URL to the page view event for reporting\n mixpanel.track_pageview({\n redacted_path: redactUrlSegments(excludeIds ?? []),\n ...rest,\n });\n};\n\nexport const track = (event: string, properties?: Record<string, unknown>) => {\n mixpanel.track(event, properties);\n};\n"],"names":["mixpanel","MIXPANEL_EU_API_HOST","initMixpanel","token","autoCapture","debug","apiHost","blockSelectors","console","warn","init","api_host","persistence","autocapture","block_selectors","capture_text_content","pageview","scroll","input","dead_click","rage_click","track_pageview","record_sessions_percent","enableDebugMode","set_config","disableDebugMode","identify","userId","accountId","organisationId","email","name","properties","toString","peopleProperties","$email","$name","organization_id","Object","keys","length","people","set","union","accounts","redactUrlSegments","excludeIds","pathSegments","window","location","pathname","split","redactedSegments","map","segment","test","some","id","url","URL","href","join","decodeURI","toLowerCase","trackPageView","rest","redacted_path","track","event"],"mappings":"AAAA,OAAOA,aAAc,kBAAmB,AAIxC,QAAO,MAAMC,qBAAuB,6BAA8B,AAElE,QAAO,MAAMC,aAAe,CAC1BC,MACAC,YAAuB,KAAK,CAC5BC,MAAiB,KAAK,CACtBC,QAAkBL,oBAAoB,IAEtC,MAAMM,eAAiB,CAAC,cAAc,CACtC,GAAI,CAACJ,MAAO,CACVK,QAAQC,IAAI,CAAC,wDACb,MACF,CAEAT,SAASU,IAAI,CAACP,MAAO,CACnBQ,SAAUL,QACVD,MAAOA,MACPO,YAAa,eACbC,YAAaT,YACT,CACEU,gBAAiBP,eACjBQ,qBAAsB,KACtBC,SAAU,MAGVC,OAAQ,MACRC,MAAO,MACPC,WAAY,MACZC,WAAY,KACd,EACA,MACJC,eAAgB,MAChBC,wBAAyB,CAC3B,EACF,CAAE,AAEF,QAAO,MAAMC,gBAAkB,KAC7BvB,SAASwB,UAAU,CAAC,CAAEnB,MAAO,IAAK,EACpC,CAAE,AAEF,QAAO,MAAMoB,iBAAmB,KAC9BzB,SAASwB,UAAU,CAAC,CAAEnB,MAAO,KAAM,EACrC,CAAE,AAEF,QAAO,MAAMqB,SAAW,CAAC,CACvBC,MAAM,CACNC,SAAS,CACTC,cAAc,CACdC,KAAK,CACLC,IAAI,CACJ,GAAGC,WACc,IAGjB,GAAI,CAACL,OAAQ,CACX,MACF,CAEA3B,SAAS0B,QAAQ,CAACC,OAAOM,QAAQ,IAEjC,MAAMC,iBAA4C,CAAE,GAAGF,UAAU,AAAC,EAElE,GAAIF,OAASC,KAAM,CACjBG,iBAAiBC,MAAM,CAAGL,KAC1BI,CAAAA,iBAAiBE,KAAK,CAAGL,IAC3B,CAEA,GAAIF,eAAgB,CAClBK,iBAAiBG,eAAe,CAAG,CAACR,eAAeI,QAAQ,GAAG,AAChE,CAEA,GAAIK,OAAOC,IAAI,CAACL,kBAAkBM,MAAM,CAAG,EAAG,CAC5CxC,SAASyC,MAAM,CAACC,GAAG,CAACR,iBACtB,CAEA,GAAIN,UAAW,CACb5B,SAASyC,MAAM,CAACE,KAAK,CAAC,CAAEC,SAAU,CAAChB,UAAUK,QAAQ,GAAG,AAAC,EAC3D,CACF,CAAE,CAIF,MAAMY,kBAAoB,AAACC,aACzB,MAAMC,aAAeC,OAAOC,QAAQ,CAACC,QAAQ,CAACC,KAAK,CAAC,KAEpD,MAAMC,iBAAmBL,aAAaM,GAAG,CAAC,AAACC,UAEzC,GACE,QAAQC,IAAI,CAACD,UACbR,WAAWU,IAAI,CAAC,AAACC,IAAOA,IAAMA,KAAO,IAAMH,UAAYG,IACvD,CACA,MAAO,YACT,CAEA,OAAOH,OACT,GAGA,MAAMI,IAAM,IAAIC,IAAIX,OAAOC,QAAQ,CAACW,IAAI,CACxCF,CAAAA,IAAIR,QAAQ,CAAGE,iBAAiBS,IAAI,CAAC,KAErC,OAAOC,UAAUJ,IAAIzB,QAAQ,IAAI8B,WAAW,EAC9C,CAEA,QAAO,MAAMC,cAAgB,AAAChC,aAC5B,KAAM,CAAEc,UAAU,CAAE,GAAGmB,KAAM,CAAGjC,YAAc,CAAC,EAG/ChC,SAASqB,cAAc,CAAC,CACtB6C,cAAerB,kBAAkBC,YAAc,EAAE,EACjD,GAAGmB,IAAI,AACT,EACF,CAAE,AAEF,QAAO,MAAME,MAAQ,CAACC,MAAepC,cACnChC,SAASmE,KAAK,CAACC,MAAOpC,WACxB,CAAE"}
@@ -1,2 +1,2 @@
1
- import{describe,expect,beforeEach,it,vi}from"vitest";import mixpanel from"mixpanel-browser";import{initMixpanel,MIXPANEL_EU_API_HOST}from"./mixpanel";vi.mock("mixpanel-browser",()=>({default:{init:vi.fn()}}));describe("initMixpanel",()=>{beforeEach(()=>{vi.clearAllMocks()});it("does not initialize without a token",()=>{const consoleSpy=vi.spyOn(console,"warn").mockImplementation(()=>{});initMixpanel("");expect(mixpanel.init).not.toHaveBeenCalled();consoleSpy.mockRestore()});it("blocks [data-mask] elements in autocapture",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture.block_selectors).toEqual(["[data-mask]"])});it("does not record sessions",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.record_sessions_percent).toBe(0)});it("defaults api_host to the EU ingestion endpoint",()=>{initMixpanel("test-token");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);expect(MIXPANEL_EU_API_HOST).toBe("https://api-eu.mixpanel.com")});it("allows overriding api_host",()=>{initMixpanel("test-token",false,false,"https://api.mixpanel.com");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe("https://api.mixpanel.com")});it("falls back to the EU default when api_host is undefined",()=>{initMixpanel("test-token",false,false,undefined);const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST)})});
1
+ import{describe,expect,beforeEach,it,vi}from"vitest";import mixpanel from"mixpanel-browser";import{initMixpanel,MIXPANEL_EU_API_HOST}from"./mixpanel";vi.mock("mixpanel-browser",()=>({default:{init:vi.fn()}}));describe("initMixpanel",()=>{beforeEach(()=>{vi.clearAllMocks()});it("does not initialize without a token",()=>{const consoleSpy=vi.spyOn(console,"warn").mockImplementation(()=>{});initMixpanel("");expect(mixpanel.init).not.toHaveBeenCalled();consoleSpy.mockRestore()});it("blocks [data-mask] elements in autocapture",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture.block_selectors).toEqual(["[data-mask]"])});it("captures clicks and submits, with noisy interaction events disabled",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture).toMatchObject({scroll:false,input:false,dead_click:false,rage_click:false});expect(config.autocapture.click).not.toBe(false);expect(config.autocapture.submit).not.toBe(false)});it("does not record sessions",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.record_sessions_percent).toBe(0)});it("defaults api_host to the EU ingestion endpoint",()=>{initMixpanel("test-token");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);expect(MIXPANEL_EU_API_HOST).toBe("https://api-eu.mixpanel.com")});it("allows overriding api_host",()=>{initMixpanel("test-token",false,false,"https://api.mixpanel.com");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe("https://api.mixpanel.com")});it("falls back to the EU default when api_host is undefined",()=>{initMixpanel("test-token",false,false,undefined);const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST)})});
2
2
  //# sourceMappingURL=mixpanel.test.js.map