@pallasoft/ps-core-components 2.33.0 → 2.34.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 (67) hide show
  1. package/dist/analytics/index.d.ts +12 -0
  2. package/dist/analytics/index.js +28 -0
  3. package/dist/analytics/report-client-error.d.ts +1 -0
  4. package/dist/analytics/report-client-error.js +6 -0
  5. package/dist/components/ps-analytics-provider.d.ts +14 -0
  6. package/dist/components/ps-analytics-provider.js +72 -0
  7. package/dist/components/ps-client-error-reporter.d.ts +5 -0
  8. package/dist/components/ps-client-error-reporter.js +22 -0
  9. package/dist/components/ps-consent-banner.d.ts +11 -0
  10. package/dist/components/ps-consent-banner.js +21 -0
  11. package/dist/constants/ps-analytics-config.d.ts +19 -0
  12. package/dist/constants/ps-analytics-config.js +22 -0
  13. package/dist/constants/ps-client-error-config.d.ts +23 -0
  14. package/dist/constants/ps-client-error-config.js +17 -0
  15. package/dist/esm/analytics/index.js +12 -0
  16. package/dist/esm/analytics/report-client-error.js +2 -0
  17. package/dist/esm/components/ps-analytics-provider.js +68 -0
  18. package/dist/esm/components/ps-client-error-reporter.js +18 -0
  19. package/dist/esm/components/ps-consent-banner.js +17 -0
  20. package/dist/esm/constants/ps-analytics-config.js +19 -0
  21. package/dist/esm/constants/ps-client-error-config.js +14 -0
  22. package/dist/esm/hooks/use-consent.js +17 -0
  23. package/dist/esm/models/analytics-csp-input.js +1 -0
  24. package/dist/esm/models/analytics-csp-origins.js +1 -0
  25. package/dist/esm/models/ps-client-error-payload.js +1 -0
  26. package/dist/esm/models/ps-client-error-settings.js +1 -0
  27. package/dist/esm/models/ps-consent-api.js +1 -0
  28. package/dist/esm/models/ps-consent-choice.js +1 -0
  29. package/dist/esm/models/ps-consent-state.js +1 -0
  30. package/dist/esm/models/ps-gtag-window.js +1 -0
  31. package/dist/esm/services/analytics-csp-service.js +27 -0
  32. package/dist/esm/services/ps-analytics-key-service.js +8 -0
  33. package/dist/esm/services/ps-client-error-service.js +72 -0
  34. package/dist/esm/services/ps-consent-service.js +57 -0
  35. package/dist/esm/services/ps-google-analytics-service.js +41 -0
  36. package/dist/esm/services/ps-post-hog-service.js +18 -0
  37. package/dist/hooks/use-consent.d.ts +2 -0
  38. package/dist/hooks/use-consent.js +21 -0
  39. package/dist/models/analytics-csp-input.d.ts +7 -0
  40. package/dist/models/analytics-csp-input.js +2 -0
  41. package/dist/models/analytics-csp-origins.d.ts +5 -0
  42. package/dist/models/analytics-csp-origins.js +2 -0
  43. package/dist/models/ps-client-error-payload.d.ts +9 -0
  44. package/dist/models/ps-client-error-payload.js +2 -0
  45. package/dist/models/ps-client-error-settings.d.ts +4 -0
  46. package/dist/models/ps-client-error-settings.js +2 -0
  47. package/dist/models/ps-consent-api.d.ts +9 -0
  48. package/dist/models/ps-consent-api.js +2 -0
  49. package/dist/models/ps-consent-choice.d.ts +1 -0
  50. package/dist/models/ps-consent-choice.js +2 -0
  51. package/dist/models/ps-consent-state.d.ts +5 -0
  52. package/dist/models/ps-consent-state.js +2 -0
  53. package/dist/models/ps-gtag-window.d.ts +4 -0
  54. package/dist/models/ps-gtag-window.js +2 -0
  55. package/dist/services/analytics-csp-service.d.ts +7 -0
  56. package/dist/services/analytics-csp-service.js +31 -0
  57. package/dist/services/ps-analytics-key-service.d.ts +3 -0
  58. package/dist/services/ps-analytics-key-service.js +12 -0
  59. package/dist/services/ps-client-error-service.d.ts +14 -0
  60. package/dist/services/ps-client-error-service.js +76 -0
  61. package/dist/services/ps-consent-service.d.ts +15 -0
  62. package/dist/services/ps-consent-service.js +61 -0
  63. package/dist/services/ps-google-analytics-service.d.ts +6 -0
  64. package/dist/services/ps-google-analytics-service.js +45 -0
  65. package/dist/services/ps-post-hog-service.d.ts +4 -0
  66. package/dist/services/ps-post-hog-service.js +55 -0
  67. package/package.json +15 -1
@@ -0,0 +1,12 @@
1
+ export * from '../components/ps-analytics-provider';
2
+ export * from '../components/ps-client-error-reporter';
3
+ export * from '../components/ps-consent-banner';
4
+ export * from '../constants/ps-analytics-config';
5
+ export * from '../constants/ps-client-error-config';
6
+ export * from '../hooks/use-consent';
7
+ export * from '../models/analytics-csp-input';
8
+ export * from '../models/analytics-csp-origins';
9
+ export * from '../models/ps-consent-api';
10
+ export * from '../models/ps-consent-choice';
11
+ export * from '../services/analytics-csp-service';
12
+ export * from './report-client-error';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../components/ps-analytics-provider"), exports);
18
+ __exportStar(require("../components/ps-client-error-reporter"), exports);
19
+ __exportStar(require("../components/ps-consent-banner"), exports);
20
+ __exportStar(require("../constants/ps-analytics-config"), exports);
21
+ __exportStar(require("../constants/ps-client-error-config"), exports);
22
+ __exportStar(require("../hooks/use-consent"), exports);
23
+ __exportStar(require("../models/analytics-csp-input"), exports);
24
+ __exportStar(require("../models/analytics-csp-origins"), exports);
25
+ __exportStar(require("../models/ps-consent-api"), exports);
26
+ __exportStar(require("../models/ps-consent-choice"), exports);
27
+ __exportStar(require("../services/analytics-csp-service"), exports);
28
+ __exportStar(require("./report-client-error"), exports);
@@ -0,0 +1 @@
1
+ export declare const reportClientError: (error: unknown, context?: string) => void;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportClientError = void 0;
4
+ const ps_client_error_service_1 = require("../services/ps-client-error-service");
5
+ const reportClientError = (error, context) => ps_client_error_service_1.PsClientErrorService.report(error, context);
6
+ exports.reportClientError = reportClientError;
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface PsAnalyticsProviderProps {
3
+ children?: ReactNode;
4
+ googleAnalyticsId?: string;
5
+ googleTagManagerUrl?: string;
6
+ postHogKey?: string;
7
+ postHogApiHost?: string;
8
+ userId?: string | null;
9
+ appVersion?: string;
10
+ nonce?: string;
11
+ consentCookieName?: string;
12
+ pathname?: string;
13
+ }
14
+ export declare const PsAnalyticsProvider: ({ children, googleAnalyticsId, googleTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName, pathname, }: PsAnalyticsProviderProps) => import("react").JSX.Element;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAnalyticsProvider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_analytics_config_1 = require("../constants/ps-analytics-config");
7
+ const use_consent_1 = require("../hooks/use-consent");
8
+ const ps_analytics_key_service_1 = require("../services/ps-analytics-key-service");
9
+ const ps_google_analytics_service_1 = require("../services/ps-google-analytics-service");
10
+ const ps_post_hog_service_1 = require("../services/ps-post-hog-service");
11
+ const PsAnalyticsProvider = ({ children, googleAnalyticsId, googleTagManagerUrl = ps_analytics_config_1.PsAnalyticsConfig.defaultTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName = ps_analytics_config_1.PsAnalyticsConfig.defaultConsentCookieName, pathname, }) => {
12
+ const { isGranted } = (0, use_consent_1.useConsent)(consentCookieName);
13
+ const [postHog, setPostHog] = (0, react_1.useState)(null);
14
+ const identifiedRef = (0, react_1.useRef)(null);
15
+ const isGoogleOn = ps_analytics_key_service_1.PsAnalyticsKeyService.isEnabled(googleAnalyticsId);
16
+ const isPostHogOn = ps_analytics_key_service_1.PsAnalyticsKeyService.isEnabled(postHogKey);
17
+ const currentPath = pathname !== undefined ? pathname : '';
18
+ const normalizedUserId = userId ? userId : null;
19
+ (0, react_1.useEffect)(() => {
20
+ if (!isGoogleOn)
21
+ return;
22
+ if (!isGranted) {
23
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.disable(googleAnalyticsId);
24
+ return;
25
+ }
26
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.load(googleAnalyticsId, googleTagManagerUrl, nonce);
27
+ }, [isGoogleOn, isGranted, googleAnalyticsId, googleTagManagerUrl, nonce]);
28
+ (0, react_1.useEffect)(() => {
29
+ if (!isGoogleOn || !isGranted)
30
+ return;
31
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.configure(googleAnalyticsId, currentPath, normalizedUserId);
32
+ }, [isGoogleOn, isGranted, googleAnalyticsId, currentPath, normalizedUserId]);
33
+ (0, react_1.useEffect)(() => {
34
+ if (!isPostHogOn)
35
+ return;
36
+ if (!isGranted) {
37
+ if (postHog)
38
+ postHog.opt_out_capturing();
39
+ return;
40
+ }
41
+ let isCancelled = false;
42
+ ps_post_hog_service_1.PsPostHogService.load(postHogKey, postHogApiHost).then((client) => {
43
+ if (!isCancelled)
44
+ setPostHog(client);
45
+ });
46
+ return () => {
47
+ isCancelled = true;
48
+ };
49
+ }, [isPostHogOn, isGranted, postHogKey, postHogApiHost]);
50
+ (0, react_1.useEffect)(() => {
51
+ if (!postHog || !isGranted)
52
+ return;
53
+ if (normalizedUserId) {
54
+ postHog.identify(normalizedUserId, appVersion ? { app_version: appVersion } : undefined);
55
+ identifiedRef.current = normalizedUserId;
56
+ return;
57
+ }
58
+ if (identifiedRef.current) {
59
+ postHog.reset();
60
+ identifiedRef.current = null;
61
+ }
62
+ }, [postHog, isGranted, normalizedUserId, appVersion]);
63
+ (0, react_1.useEffect)(() => {
64
+ if (!postHog || !isGranted)
65
+ return;
66
+ postHog.capture(ps_analytics_config_1.PsAnalyticsConfig.postHog.pageviewEvent, {
67
+ $pathname: currentPath,
68
+ });
69
+ }, [postHog, isGranted, currentPath]);
70
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
71
+ };
72
+ exports.PsAnalyticsProvider = PsAnalyticsProvider;
@@ -0,0 +1,5 @@
1
+ export interface PsClientErrorReporterProps {
2
+ endpoint: string;
3
+ appVersion?: string;
4
+ }
5
+ export declare const PsClientErrorReporter: ({ endpoint, appVersion }: PsClientErrorReporterProps) => null;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsClientErrorReporter = void 0;
4
+ const react_1 = require("react");
5
+ const ps_client_error_config_1 = require("../constants/ps-client-error-config");
6
+ const ps_client_error_service_1 = require("../services/ps-client-error-service");
7
+ const PsClientErrorReporter = ({ endpoint, appVersion }) => {
8
+ (0, react_1.useEffect)(() => {
9
+ ps_client_error_service_1.PsClientErrorService.configure({ endpoint, appVersion });
10
+ const onError = (event) => ps_client_error_service_1.PsClientErrorService.report(event.error ? event.error : event.message, '', ps_client_error_config_1.PsClientErrorConfig.sources.error);
11
+ const onRejection = (event) => ps_client_error_service_1.PsClientErrorService.report(event.reason, '', ps_client_error_config_1.PsClientErrorConfig.sources.rejection);
12
+ window.addEventListener('error', onError);
13
+ window.addEventListener('unhandledrejection', onRejection);
14
+ return () => {
15
+ window.removeEventListener('error', onError);
16
+ window.removeEventListener('unhandledrejection', onRejection);
17
+ ps_client_error_service_1.PsClientErrorService.clear();
18
+ };
19
+ }, [endpoint, appVersion]);
20
+ return null;
21
+ };
22
+ exports.PsClientErrorReporter = PsClientErrorReporter;
@@ -0,0 +1,11 @@
1
+ export interface PsConsentBannerProps {
2
+ title: string;
3
+ message: string;
4
+ acceptLabel: string;
5
+ rejectLabel: string;
6
+ privacyPolicyHref: string;
7
+ privacyPolicyLabel: string;
8
+ consentCookieName?: string;
9
+ className?: string;
10
+ }
11
+ export declare const PsConsentBanner: ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName, className, }: PsConsentBannerProps) => import("react").JSX.Element | null;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsConsentBanner = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_analytics_config_1 = require("../constants/ps-analytics-config");
8
+ const use_consent_1 = require("../hooks/use-consent");
9
+ const ps_button_1 = require("./ps-button");
10
+ const PsConsentBanner = ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName = ps_analytics_config_1.PsAnalyticsConfig.defaultConsentCookieName, className, }) => {
11
+ const { isBannerOpen, accept, reject } = (0, use_consent_1.useConsent)(consentCookieName);
12
+ const ref = (0, react_1.useRef)(null);
13
+ (0, react_1.useEffect)(() => {
14
+ if (isBannerOpen)
15
+ ref.current?.focus();
16
+ }, [isBannerOpen]);
17
+ if (!isBannerOpen)
18
+ return null;
19
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, role: 'dialog', "aria-label": title, "aria-live": 'polite', tabIndex: -1, className: `ps-card fixed inset-x-3 bottom-3 flex flex-col gap-3 p-4 shadow-lg outline-none sm:left-auto sm:right-4 sm:max-w-md ${className ? className : ''}`, style: { zIndex: ps_design_system_1.PallasoftZIndex.Toast }, children: [(0, jsx_runtime_1.jsxs)("p", { className: 'text-sm', children: [message, ' ', (0, jsx_runtime_1.jsx)("a", { className: 'underline', href: privacyPolicyHref, children: privacyPolicyLabel })] }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex gap-2', children: [(0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: reject, children: rejectLabel }), (0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: accept, children: acceptLabel })] })] }));
20
+ };
21
+ exports.PsConsentBanner = PsConsentBanner;
@@ -0,0 +1,19 @@
1
+ export declare const PsAnalyticsConfig: {
2
+ readonly offValues: readonly ["", "_"];
3
+ readonly defaultConsentCookieName: "ps_consent";
4
+ readonly consentGranted: "granted";
5
+ readonly consentDenied: "denied";
6
+ readonly consentMaxAgeSeconds: 31536000;
7
+ readonly defaultTagManagerUrl: "https://www.googletagmanager.com";
8
+ readonly tagManagerScriptPath: "/gtag/js?id=";
9
+ readonly scriptAttribute: "data-ps-analytics";
10
+ readonly postHog: {
11
+ readonly defaults: "2026-05-30";
12
+ readonly personProfiles: "identified_only";
13
+ readonly autocapture: true;
14
+ readonly capturePageleave: true;
15
+ readonly captureExceptions: true;
16
+ readonly pageviewEvent: "$pageview";
17
+ };
18
+ readonly listSeparator: ",";
19
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAnalyticsConfig = void 0;
4
+ exports.PsAnalyticsConfig = {
5
+ offValues: ['', '_'],
6
+ defaultConsentCookieName: 'ps_consent',
7
+ consentGranted: 'granted',
8
+ consentDenied: 'denied',
9
+ consentMaxAgeSeconds: 31536000,
10
+ defaultTagManagerUrl: 'https://www.googletagmanager.com',
11
+ tagManagerScriptPath: '/gtag/js?id=',
12
+ scriptAttribute: 'data-ps-analytics',
13
+ postHog: {
14
+ defaults: '2026-05-30',
15
+ personProfiles: 'identified_only',
16
+ autocapture: true,
17
+ capturePageleave: true,
18
+ captureExceptions: true,
19
+ pageviewEvent: '$pageview',
20
+ },
21
+ listSeparator: ',',
22
+ };
@@ -0,0 +1,23 @@
1
+ export declare const PsClientErrorConfig: {
2
+ readonly maxReportsPerPage: 10;
3
+ readonly maxStackLength: 2048;
4
+ readonly maxMessageLength: 500;
5
+ readonly maxContextLength: 200;
6
+ readonly jsonContentType: "application/json";
7
+ readonly emailPattern: RegExp;
8
+ readonly redactedValue: "[redacted]";
9
+ readonly unknownMessage: "Unknown error";
10
+ readonly botPattern: RegExp;
11
+ readonly mobilePattern: RegExp;
12
+ readonly userAgentClasses: {
13
+ readonly bot: "bot";
14
+ readonly mobile: "mobile";
15
+ readonly desktop: "desktop";
16
+ readonly unknown: "unknown";
17
+ };
18
+ readonly sources: {
19
+ readonly error: "error";
20
+ readonly rejection: "unhandledrejection";
21
+ readonly boundary: "boundary";
22
+ };
23
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsClientErrorConfig = void 0;
4
+ exports.PsClientErrorConfig = {
5
+ maxReportsPerPage: 10,
6
+ maxStackLength: 2048,
7
+ maxMessageLength: 500,
8
+ maxContextLength: 200,
9
+ jsonContentType: 'application/json',
10
+ emailPattern: /[^\s@"'()<>]+@[^\s@"'()<>]+\.[^\s@"'()<>]+/g,
11
+ redactedValue: '[redacted]',
12
+ unknownMessage: 'Unknown error',
13
+ botPattern: /bot|crawl|spider|slurp|headless/i,
14
+ mobilePattern: /mobi|android|iphone|ipad/i,
15
+ userAgentClasses: { bot: 'bot', mobile: 'mobile', desktop: 'desktop', unknown: 'unknown' },
16
+ sources: { error: 'error', rejection: 'unhandledrejection', boundary: 'boundary' },
17
+ };
@@ -0,0 +1,12 @@
1
+ export * from '../components/ps-analytics-provider';
2
+ export * from '../components/ps-client-error-reporter';
3
+ export * from '../components/ps-consent-banner';
4
+ export * from '../constants/ps-analytics-config';
5
+ export * from '../constants/ps-client-error-config';
6
+ export * from '../hooks/use-consent';
7
+ export * from '../models/analytics-csp-input';
8
+ export * from '../models/analytics-csp-origins';
9
+ export * from '../models/ps-consent-api';
10
+ export * from '../models/ps-consent-choice';
11
+ export * from '../services/analytics-csp-service';
12
+ export * from './report-client-error';
@@ -0,0 +1,2 @@
1
+ import { PsClientErrorService } from '../services/ps-client-error-service';
2
+ export const reportClientError = (error, context) => PsClientErrorService.report(error, context);
@@ -0,0 +1,68 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config';
4
+ import { useConsent } from '../hooks/use-consent';
5
+ import { PsAnalyticsKeyService } from '../services/ps-analytics-key-service';
6
+ import { PsGoogleAnalyticsService } from '../services/ps-google-analytics-service';
7
+ import { PsPostHogService } from '../services/ps-post-hog-service';
8
+ export const PsAnalyticsProvider = ({ children, googleAnalyticsId, googleTagManagerUrl = PsAnalyticsConfig.defaultTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName = PsAnalyticsConfig.defaultConsentCookieName, pathname, }) => {
9
+ const { isGranted } = useConsent(consentCookieName);
10
+ const [postHog, setPostHog] = useState(null);
11
+ const identifiedRef = useRef(null);
12
+ const isGoogleOn = PsAnalyticsKeyService.isEnabled(googleAnalyticsId);
13
+ const isPostHogOn = PsAnalyticsKeyService.isEnabled(postHogKey);
14
+ const currentPath = pathname !== undefined ? pathname : '';
15
+ const normalizedUserId = userId ? userId : null;
16
+ useEffect(() => {
17
+ if (!isGoogleOn)
18
+ return;
19
+ if (!isGranted) {
20
+ PsGoogleAnalyticsService.disable(googleAnalyticsId);
21
+ return;
22
+ }
23
+ PsGoogleAnalyticsService.load(googleAnalyticsId, googleTagManagerUrl, nonce);
24
+ }, [isGoogleOn, isGranted, googleAnalyticsId, googleTagManagerUrl, nonce]);
25
+ useEffect(() => {
26
+ if (!isGoogleOn || !isGranted)
27
+ return;
28
+ PsGoogleAnalyticsService.configure(googleAnalyticsId, currentPath, normalizedUserId);
29
+ }, [isGoogleOn, isGranted, googleAnalyticsId, currentPath, normalizedUserId]);
30
+ useEffect(() => {
31
+ if (!isPostHogOn)
32
+ return;
33
+ if (!isGranted) {
34
+ if (postHog)
35
+ postHog.opt_out_capturing();
36
+ return;
37
+ }
38
+ let isCancelled = false;
39
+ PsPostHogService.load(postHogKey, postHogApiHost).then((client) => {
40
+ if (!isCancelled)
41
+ setPostHog(client);
42
+ });
43
+ return () => {
44
+ isCancelled = true;
45
+ };
46
+ }, [isPostHogOn, isGranted, postHogKey, postHogApiHost]);
47
+ useEffect(() => {
48
+ if (!postHog || !isGranted)
49
+ return;
50
+ if (normalizedUserId) {
51
+ postHog.identify(normalizedUserId, appVersion ? { app_version: appVersion } : undefined);
52
+ identifiedRef.current = normalizedUserId;
53
+ return;
54
+ }
55
+ if (identifiedRef.current) {
56
+ postHog.reset();
57
+ identifiedRef.current = null;
58
+ }
59
+ }, [postHog, isGranted, normalizedUserId, appVersion]);
60
+ useEffect(() => {
61
+ if (!postHog || !isGranted)
62
+ return;
63
+ postHog.capture(PsAnalyticsConfig.postHog.pageviewEvent, {
64
+ $pathname: currentPath,
65
+ });
66
+ }, [postHog, isGranted, currentPath]);
67
+ return _jsx(_Fragment, { children: children });
68
+ };
@@ -0,0 +1,18 @@
1
+ import { useEffect } from 'react';
2
+ import { PsClientErrorConfig } from '../constants/ps-client-error-config';
3
+ import { PsClientErrorService } from '../services/ps-client-error-service';
4
+ export const PsClientErrorReporter = ({ endpoint, appVersion }) => {
5
+ useEffect(() => {
6
+ PsClientErrorService.configure({ endpoint, appVersion });
7
+ const onError = (event) => PsClientErrorService.report(event.error ? event.error : event.message, '', PsClientErrorConfig.sources.error);
8
+ const onRejection = (event) => PsClientErrorService.report(event.reason, '', PsClientErrorConfig.sources.rejection);
9
+ window.addEventListener('error', onError);
10
+ window.addEventListener('unhandledrejection', onRejection);
11
+ return () => {
12
+ window.removeEventListener('error', onError);
13
+ window.removeEventListener('unhandledrejection', onRejection);
14
+ PsClientErrorService.clear();
15
+ };
16
+ }, [endpoint, appVersion]);
17
+ return null;
18
+ };
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { PallasoftZIndex } from '@pallasoft/ps-design-system';
4
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config';
5
+ import { useConsent } from '../hooks/use-consent';
6
+ import { PsButton } from './ps-button';
7
+ export const PsConsentBanner = ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName = PsAnalyticsConfig.defaultConsentCookieName, className, }) => {
8
+ const { isBannerOpen, accept, reject } = useConsent(consentCookieName);
9
+ const ref = useRef(null);
10
+ useEffect(() => {
11
+ if (isBannerOpen)
12
+ ref.current?.focus();
13
+ }, [isBannerOpen]);
14
+ if (!isBannerOpen)
15
+ return null;
16
+ return (_jsxs("div", { ref: ref, role: 'dialog', "aria-label": title, "aria-live": 'polite', tabIndex: -1, className: `ps-card fixed inset-x-3 bottom-3 flex flex-col gap-3 p-4 shadow-lg outline-none sm:left-auto sm:right-4 sm:max-w-md ${className ? className : ''}`, style: { zIndex: PallasoftZIndex.Toast }, children: [_jsxs("p", { className: 'text-sm', children: [message, ' ', _jsx("a", { className: 'underline', href: privacyPolicyHref, children: privacyPolicyLabel })] }), _jsxs("div", { className: 'flex gap-2', children: [_jsx(PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: reject, children: rejectLabel }), _jsx(PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: accept, children: acceptLabel })] })] }));
17
+ };
@@ -0,0 +1,19 @@
1
+ export const PsAnalyticsConfig = {
2
+ offValues: ['', '_'],
3
+ defaultConsentCookieName: 'ps_consent',
4
+ consentGranted: 'granted',
5
+ consentDenied: 'denied',
6
+ consentMaxAgeSeconds: 31536000,
7
+ defaultTagManagerUrl: 'https://www.googletagmanager.com',
8
+ tagManagerScriptPath: '/gtag/js?id=',
9
+ scriptAttribute: 'data-ps-analytics',
10
+ postHog: {
11
+ defaults: '2026-05-30',
12
+ personProfiles: 'identified_only',
13
+ autocapture: true,
14
+ capturePageleave: true,
15
+ captureExceptions: true,
16
+ pageviewEvent: '$pageview',
17
+ },
18
+ listSeparator: ',',
19
+ };
@@ -0,0 +1,14 @@
1
+ export const PsClientErrorConfig = {
2
+ maxReportsPerPage: 10,
3
+ maxStackLength: 2048,
4
+ maxMessageLength: 500,
5
+ maxContextLength: 200,
6
+ jsonContentType: 'application/json',
7
+ emailPattern: /[^\s@"'()<>]+@[^\s@"'()<>]+\.[^\s@"'()<>]+/g,
8
+ redactedValue: '[redacted]',
9
+ unknownMessage: 'Unknown error',
10
+ botPattern: /bot|crawl|spider|slurp|headless/i,
11
+ mobilePattern: /mobi|android|iphone|ipad/i,
12
+ userAgentClasses: { bot: 'bot', mobile: 'mobile', desktop: 'desktop', unknown: 'unknown' },
13
+ sources: { error: 'error', rejection: 'unhandledrejection', boundary: 'boundary' },
14
+ };
@@ -0,0 +1,17 @@
1
+ import { useCallback, useSyncExternalStore } from 'react';
2
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config';
3
+ import { PsConsentService } from '../services/ps-consent-service';
4
+ export const useConsent = (consentCookieName = PsAnalyticsConfig.defaultConsentCookieName) => {
5
+ const state = useSyncExternalStore((listener) => PsConsentService.subscribe(listener), () => PsConsentService.snapshot(consentCookieName), () => PsConsentService.serverState);
6
+ const accept = useCallback(() => PsConsentService.choose(consentCookieName, PsAnalyticsConfig.consentGranted), [consentCookieName]);
7
+ const reject = useCallback(() => PsConsentService.choose(consentCookieName, PsAnalyticsConfig.consentDenied), [consentCookieName]);
8
+ const reopen = useCallback(() => PsConsentService.reopen(consentCookieName), [consentCookieName]);
9
+ return {
10
+ choice: state.choice,
11
+ isGranted: state.choice === PsAnalyticsConfig.consentGranted,
12
+ isBannerOpen: state === PsConsentService.serverState ? false : state.choice === null || state.isReopened,
13
+ accept,
14
+ reject,
15
+ reopen,
16
+ };
17
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config';
2
+ import { PsAnalyticsKeyService } from './ps-analytics-key-service';
3
+ export class AnalyticsCspService {
4
+ static origins(input) {
5
+ const isGoogleOn = PsAnalyticsKeyService.isEnabled(input.googleAnalyticsId);
6
+ const isPostHogOn = PsAnalyticsKeyService.isEnabled(input.postHogKey);
7
+ const googleUrls = isGoogleOn ? this.parse(input.googleAnalyticsUrls) : [];
8
+ const tagManagerUrls = isGoogleOn ? this.parse(input.googleTagManagerUrls) : [];
9
+ const postHogUrls = isPostHogOn ? this.parse(input.postHogUrls) : [];
10
+ return {
11
+ scriptSrc: this.unique([...tagManagerUrls, ...postHogUrls]),
12
+ connectSrc: this.unique([...googleUrls, ...tagManagerUrls, ...postHogUrls]),
13
+ imgSrc: this.unique([...googleUrls, ...tagManagerUrls]),
14
+ };
15
+ }
16
+ static parse(value) {
17
+ if (!value)
18
+ return [];
19
+ return value
20
+ .split(PsAnalyticsConfig.listSeparator)
21
+ .map((item) => item.trim())
22
+ .filter((item) => PsAnalyticsKeyService.isEnabled(item));
23
+ }
24
+ static unique(values) {
25
+ return Array.from(new Set(values));
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config';
2
+ export class PsAnalyticsKeyService {
3
+ static isEnabled(value) {
4
+ if (value === null || value === undefined)
5
+ return false;
6
+ return !PsAnalyticsConfig.offValues.includes(value.trim());
7
+ }
8
+ }
@@ -0,0 +1,72 @@
1
+ import { PsClientErrorConfig } from '../constants/ps-client-error-config';
2
+ export class PsClientErrorService {
3
+ static configure(settings) {
4
+ this.settings = settings;
5
+ }
6
+ static clear() {
7
+ this.settings = null;
8
+ }
9
+ static reset() {
10
+ this.settings = null;
11
+ this.sentKeys.clear();
12
+ this.sentCount = 0;
13
+ }
14
+ static report(error, context = '', source = PsClientErrorConfig.sources.boundary) {
15
+ if (!this.settings || typeof window === 'undefined')
16
+ return;
17
+ if (this.sentCount >= PsClientErrorConfig.maxReportsPerPage)
18
+ return;
19
+ const payload = this.payload(error, context, source, this.settings);
20
+ const key = `${payload.message}|${payload.context}`;
21
+ if (this.sentKeys.has(key))
22
+ return;
23
+ this.sentKeys.add(key);
24
+ this.sentCount += 1;
25
+ this.send(this.settings.endpoint, JSON.stringify(payload));
26
+ }
27
+ static payload(error, context, source, settings) {
28
+ const isError = error instanceof Error;
29
+ const rawMessage = isError ? error.message : typeof error === 'string' ? error : '';
30
+ return {
31
+ message: this.clean(rawMessage ? rawMessage : PsClientErrorConfig.unknownMessage).slice(0, PsClientErrorConfig.maxMessageLength),
32
+ stack: isError && error.stack
33
+ ? this.clean(error.stack).slice(0, PsClientErrorConfig.maxStackLength)
34
+ : '',
35
+ pathname: window.location.pathname,
36
+ appVersion: settings.appVersion ? settings.appVersion : '',
37
+ userAgentClass: this.userAgentClass(),
38
+ source,
39
+ context: this.clean(context).slice(0, PsClientErrorConfig.maxContextLength),
40
+ };
41
+ }
42
+ static clean(value) {
43
+ return value.replace(PsClientErrorConfig.emailPattern, PsClientErrorConfig.redactedValue);
44
+ }
45
+ static userAgentClass() {
46
+ const agent = typeof navigator === 'undefined' ? '' : navigator.userAgent;
47
+ const classes = PsClientErrorConfig.userAgentClasses;
48
+ if (!agent)
49
+ return classes.unknown;
50
+ if (PsClientErrorConfig.botPattern.test(agent))
51
+ return classes.bot;
52
+ if (PsClientErrorConfig.mobilePattern.test(agent))
53
+ return classes.mobile;
54
+ return classes.desktop;
55
+ }
56
+ static send(endpoint, body) {
57
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
58
+ const blob = new Blob([body], { type: PsClientErrorConfig.jsonContentType });
59
+ if (navigator.sendBeacon(endpoint, blob))
60
+ return;
61
+ }
62
+ fetch(endpoint, {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': PsClientErrorConfig.jsonContentType },
65
+ body,
66
+ keepalive: true,
67
+ }).catch(() => undefined);
68
+ }
69
+ }
70
+ PsClientErrorService.settings = null;
71
+ PsClientErrorService.sentKeys = new Set();
72
+ PsClientErrorService.sentCount = 0;