@zauru-sdk/components 6.4.0 → 6.5.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [6.5.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.4.0...@zauru-sdk/components@6.5.0) (2026-09-16)
7
+
8
+
9
+ ### Features
10
+
11
+ * extraer CloudWatch y observabilidad de Remix al SDK ([425af0d](https://github.com/intuitiva/zauru-typescript-sdk/commit/425af0dcd1955d2739204b3f69f9d77090553141))
12
+
13
+
14
+
15
+
16
+
6
17
  # [6.4.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.3.2...@zauru-sdk/components@6.4.0) (2026-09-16)
7
18
 
8
19
 
@@ -1,6 +1,9 @@
1
1
  import React from "react";
2
2
  type Props = {
3
3
  children: React.ReactNode;
4
+ appVersion?: string;
5
+ /** Default true. Captures window.onerror / unhandledrejection. */
6
+ reportClientErrors?: boolean;
4
7
  };
5
8
  export declare const BodyContainer: (props: Props) => import("react/jsx-runtime").JSX.Element;
6
9
  export {};
@@ -1,8 +1,10 @@
1
- export declare const ErrorLayout: ({ from, isRootLevel, error: parentError, onError, }: {
1
+ export declare const ErrorLayout: ({ from, isRootLevel, error: parentError, onError, reportErrors, }: {
2
2
  from?: string;
3
3
  isRootLevel?: boolean;
4
4
  error?: Error;
5
5
  onError?: (error: Error, meta: {
6
6
  from?: string;
7
7
  }) => void;
8
+ /** Default true. POSTs to `/api/client-errors` unless `onError` is passed. */
9
+ reportErrors?: boolean;
8
10
  }) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,5 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useZauruClientErrorReporting } from "../observability/useZauruClientErrorReporting.js";
2
3
  export const BodyContainer = (props) => {
3
- const { children } = props;
4
+ const { children, appVersion, reportClientErrors = true } = props;
5
+ useZauruClientErrorReporting({
6
+ appVersion,
7
+ enabled: reportClientErrors,
8
+ });
4
9
  return _jsx("body", { className: "flex flex-col min-h-screen m-0", children: children });
5
10
  };
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { isRouteErrorResponse, Links, Meta, Scripts, useRouteError, Link, } from "@remix-run/react";
3
3
  import { useEffect, useState } from "react";
4
- export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, onError, }) => {
4
+ import { sendClientError } from "../../observability/sendClientError.js";
5
+ export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, onError, reportErrors = true, }) => {
5
6
  try {
6
7
  const error = useRouteError();
7
8
  const [showDetails, setShowDetails] = useState(!!parentError);
@@ -10,7 +11,25 @@ export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, onEr
10
11
  if (reportable instanceof Error) {
11
12
  onError?.(reportable, { from });
12
13
  }
13
- }, [error, parentError, from, onError]);
14
+ if (!reportErrors || onError)
15
+ return;
16
+ if (reportable instanceof Error) {
17
+ sendClientError({
18
+ message: reportable.message,
19
+ stack: reportable.stack,
20
+ source: "errorBoundary",
21
+ url: from,
22
+ });
23
+ return;
24
+ }
25
+ if (isRouteErrorResponse(error)) {
26
+ sendClientError({
27
+ message: `Error en request: ${error.status}: ${error.statusText}`,
28
+ source: "errorBoundary",
29
+ url: from,
30
+ });
31
+ }
32
+ }, [error, parentError, from, onError, reportErrors]);
14
33
  const baseError = (_jsxs("div", { className: "min-h-screen flex flex-col items-center justify-center p-4", children: [_jsx("img", { src: "/logo.png", alt: "Zauru Logo", className: "mb-8 h-20" }), _jsx("h1", { className: "text-5xl font-extrabold text-red-500 mb-6", children: "\u00A1Ups!" }), _jsxs("div", { className: "w-full max-w-2xl flex flex-col items-center", children: [_jsx("p", { className: "text-2xl text-gray-300 mb-8 text-center", children: isRouteErrorResponse(error)
15
34
  ? `Error en request: ${error.status}: ${error.statusText} - PATH: ${error.data.path} - MESSAGE: ${error.data.message}`
16
35
  : error instanceof Error
package/dist/esm/index.js CHANGED
@@ -26,3 +26,4 @@ export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
27
  export * from "./Routes/index.js";
28
28
  export * from "./SidePanel/index.js";
29
+ export * from "./observability/index.js";
@@ -0,0 +1,9 @@
1
+ import { useZauruClientErrorReporting } from "./useZauruClientErrorReporting.js";
2
+ /**
3
+ * Optional drop-in for apps that do not use `BodyContainer`.
4
+ * `BodyContainer` already installs the same listeners.
5
+ */
6
+ export function ZauruObservability(options) {
7
+ useZauruClientErrorReporting(options);
8
+ return null;
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./sendClientError.js";
2
+ export * from "./useZauruClientErrorReporting.js";
3
+ export * from "./ZauruObservability.js";
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Client helper to report errors to `/api/client-errors`, which forwards
3
+ * them to CloudWatch Logs (with host logs as fallback).
4
+ *
5
+ * `keepalive: true` keeps the request alive if the page unloads right after.
6
+ */
7
+ const ENDPOINT = "/api/client-errors";
8
+ let lastReportedKey = "";
9
+ let defaultAppVersion;
10
+ export function setClientErrorAppVersion(version) {
11
+ defaultAppVersion = version;
12
+ }
13
+ /**
14
+ * Sends a client error to the server. Best-effort: never throws.
15
+ * Deduplicates identical consecutive reports so re-renders of the same
16
+ * error do not flood CloudWatch.
17
+ */
18
+ export function sendClientError(payload) {
19
+ if (typeof window === "undefined")
20
+ return;
21
+ const body = {
22
+ message: payload.message,
23
+ stack: payload.stack,
24
+ source: payload.source ?? "unknown",
25
+ url: payload.url ?? window.location.href,
26
+ userAgent: payload.userAgent ?? navigator.userAgent,
27
+ appVersion: payload.appVersion ?? defaultAppVersion,
28
+ timestamp: payload.timestamp ?? Date.now(),
29
+ };
30
+ const dedupKey = `${body.source}|${body.message}|${body.url}`;
31
+ if (dedupKey === lastReportedKey)
32
+ return;
33
+ lastReportedKey = dedupKey;
34
+ try {
35
+ fetch(ENDPOINT, {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: JSON.stringify(body),
39
+ keepalive: true,
40
+ credentials: "same-origin",
41
+ }).catch(() => {
42
+ // Silent: reporting must not break UX.
43
+ });
44
+ }
45
+ catch {
46
+ // Silent.
47
+ }
48
+ }
@@ -0,0 +1,41 @@
1
+ import { useEffect } from "react";
2
+ import { sendClientError, setClientErrorAppVersion, } from "./sendClientError.js";
3
+ /**
4
+ * Captures `window.onerror` and `unhandledrejection` and POSTs them to
5
+ * `/api/client-errors`. Used by `BodyContainer` by default.
6
+ */
7
+ export function useZauruClientErrorReporting(options) {
8
+ const enabled = options?.enabled !== false;
9
+ const appVersion = options?.appVersion;
10
+ useEffect(() => {
11
+ if (!enabled || typeof window === "undefined")
12
+ return;
13
+ if (appVersion)
14
+ setClientErrorAppVersion(appVersion);
15
+ const onError = (event) => {
16
+ sendClientError({
17
+ message: event.message || "Error no capturado (window.onerror)",
18
+ stack: event.error instanceof Error ? event.error.stack : undefined,
19
+ source: "window.onerror",
20
+ });
21
+ };
22
+ const onRejection = (event) => {
23
+ const reason = event.reason;
24
+ sendClientError({
25
+ message: reason instanceof Error
26
+ ? reason.message
27
+ : typeof reason === "string"
28
+ ? reason
29
+ : "Promesa rechazada no manejada",
30
+ stack: reason instanceof Error ? reason.stack : undefined,
31
+ source: "unhandledrejection",
32
+ });
33
+ };
34
+ window.addEventListener("error", onError);
35
+ window.addEventListener("unhandledrejection", onRejection);
36
+ return () => {
37
+ window.removeEventListener("error", onError);
38
+ window.removeEventListener("unhandledrejection", onRejection);
39
+ };
40
+ }, [enabled, appVersion]);
41
+ }
package/dist/index.d.ts CHANGED
@@ -26,3 +26,4 @@ export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
27
  export * from "./Routes/index.js";
28
28
  export * from "./SidePanel/index.js";
29
+ export * from "./observability/index.js";
@@ -0,0 +1,6 @@
1
+ import type { ZauruClientErrorReportingOptions } from "./useZauruClientErrorReporting.js";
2
+ /**
3
+ * Optional drop-in for apps that do not use `BodyContainer`.
4
+ * `BodyContainer` already installs the same listeners.
5
+ */
6
+ export declare function ZauruObservability(options: ZauruClientErrorReportingOptions): null;
@@ -0,0 +1,3 @@
1
+ export * from "./sendClientError.js";
2
+ export * from "./useZauruClientErrorReporting.js";
3
+ export * from "./ZauruObservability.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Client helper to report errors to `/api/client-errors`, which forwards
3
+ * them to CloudWatch Logs (with host logs as fallback).
4
+ *
5
+ * `keepalive: true` keeps the request alive if the page unloads right after.
6
+ */
7
+ export interface ClientErrorPayload {
8
+ message: string;
9
+ stack?: string;
10
+ /** Sub-origin: errorBoundary, window.onerror, unhandledrejection. */
11
+ source?: string;
12
+ url?: string;
13
+ userAgent?: string;
14
+ appVersion?: string;
15
+ timestamp?: number;
16
+ [key: string]: unknown;
17
+ }
18
+ export declare function setClientErrorAppVersion(version?: string): void;
19
+ /**
20
+ * Sends a client error to the server. Best-effort: never throws.
21
+ * Deduplicates identical consecutive reports so re-renders of the same
22
+ * error do not flood CloudWatch.
23
+ */
24
+ export declare function sendClientError(payload: ClientErrorPayload): void;
@@ -0,0 +1,9 @@
1
+ export type ZauruClientErrorReportingOptions = {
2
+ appVersion?: string;
3
+ enabled?: boolean;
4
+ };
5
+ /**
6
+ * Captures `window.onerror` and `unhandledrejection` and POSTs them to
7
+ * `/api/client-errors`. Used by `BodyContainer` by default.
8
+ */
9
+ export declare function useZauruClientErrorReporting(options?: ZauruClientErrorReportingOptions): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "6.4.0",
3
+ "version": "6.5.0",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -49,7 +49,7 @@
49
49
  "@zauru-sdk/icons": "^6.0.0",
50
50
  "@zauru-sdk/redux": "^6.5.9",
51
51
  "@zauru-sdk/types": "^6.16.0",
52
- "@zauru-sdk/utils": "^6.15.0",
52
+ "@zauru-sdk/utils": "^6.16.0",
53
53
  "browser-image-compression": "^2.0.2",
54
54
  "framer-motion": "^12.0.0",
55
55
  "jsonwebtoken": "^9.0.2",
@@ -62,5 +62,5 @@
62
62
  "styled-components": "^5.3.5",
63
63
  "zod": "^3.23.8"
64
64
  },
65
- "gitHead": "7ccc6b0a3d1c426f8b62579a64a0bb0be73d2fcd"
65
+ "gitHead": "41f536143a03ddca17bbf467c027ef0179493479"
66
66
  }
@@ -1,10 +1,18 @@
1
1
  import React from "react";
2
+ import { useZauruClientErrorReporting } from "../observability/useZauruClientErrorReporting.js";
2
3
 
3
4
  type Props = {
4
5
  children: React.ReactNode;
6
+ appVersion?: string;
7
+ /** Default true. Captures window.onerror / unhandledrejection. */
8
+ reportClientErrors?: boolean;
5
9
  };
6
10
 
7
11
  export const BodyContainer = (props: Props) => {
8
- const { children } = props;
12
+ const { children, appVersion, reportClientErrors = true } = props;
13
+ useZauruClientErrorReporting({
14
+ appVersion,
15
+ enabled: reportClientErrors,
16
+ });
9
17
  return <body className="flex flex-col min-h-screen m-0">{children}</body>;
10
18
  };
@@ -7,17 +7,21 @@ import {
7
7
  Link,
8
8
  } from "@remix-run/react";
9
9
  import { useEffect, useState } from "react";
10
+ import { sendClientError } from "../../observability/sendClientError.js";
10
11
 
11
12
  export const ErrorLayout = ({
12
13
  from,
13
14
  isRootLevel = true,
14
15
  error: parentError,
15
16
  onError,
17
+ reportErrors = true,
16
18
  }: {
17
19
  from?: string;
18
20
  isRootLevel?: boolean;
19
21
  error?: Error;
20
22
  onError?: (error: Error, meta: { from?: string }) => void;
23
+ /** Default true. POSTs to `/api/client-errors` unless `onError` is passed. */
24
+ reportErrors?: boolean;
21
25
  }) => {
22
26
  try {
23
27
  const error = useRouteError();
@@ -28,7 +32,24 @@ export const ErrorLayout = ({
28
32
  if (reportable instanceof Error) {
29
33
  onError?.(reportable, { from });
30
34
  }
31
- }, [error, parentError, from, onError]);
35
+ if (!reportErrors || onError) return;
36
+ if (reportable instanceof Error) {
37
+ sendClientError({
38
+ message: reportable.message,
39
+ stack: reportable.stack,
40
+ source: "errorBoundary",
41
+ url: from,
42
+ });
43
+ return;
44
+ }
45
+ if (isRouteErrorResponse(error)) {
46
+ sendClientError({
47
+ message: `Error en request: ${error.status}: ${error.statusText}`,
48
+ source: "errorBoundary",
49
+ url: from,
50
+ });
51
+ }
52
+ }, [error, parentError, from, onError, reportErrors]);
32
53
 
33
54
  const baseError = (
34
55
  <div className="min-h-screen flex flex-col items-center justify-center p-4">
package/src/index.ts CHANGED
@@ -26,3 +26,4 @@ export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
27
  export * from "./Routes/index.js";
28
28
  export * from "./SidePanel/index.js";
29
+ export * from "./observability/index.js";
@@ -0,0 +1,11 @@
1
+ import { useZauruClientErrorReporting } from "./useZauruClientErrorReporting.js";
2
+ import type { ZauruClientErrorReportingOptions } from "./useZauruClientErrorReporting.js";
3
+
4
+ /**
5
+ * Optional drop-in for apps that do not use `BodyContainer`.
6
+ * `BodyContainer` already installs the same listeners.
7
+ */
8
+ export function ZauruObservability(options: ZauruClientErrorReportingOptions) {
9
+ useZauruClientErrorReporting(options);
10
+ return null;
11
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./sendClientError.js";
2
+ export * from "./useZauruClientErrorReporting.js";
3
+ export * from "./ZauruObservability.js";
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Client helper to report errors to `/api/client-errors`, which forwards
3
+ * them to CloudWatch Logs (with host logs as fallback).
4
+ *
5
+ * `keepalive: true` keeps the request alive if the page unloads right after.
6
+ */
7
+
8
+ export interface ClientErrorPayload {
9
+ message: string;
10
+ stack?: string;
11
+ /** Sub-origin: errorBoundary, window.onerror, unhandledrejection. */
12
+ source?: string;
13
+ url?: string;
14
+ userAgent?: string;
15
+ appVersion?: string;
16
+ timestamp?: number;
17
+ [key: string]: unknown;
18
+ }
19
+
20
+ const ENDPOINT = "/api/client-errors";
21
+
22
+ let lastReportedKey = "";
23
+ let defaultAppVersion: string | undefined;
24
+
25
+ export function setClientErrorAppVersion(version?: string): void {
26
+ defaultAppVersion = version;
27
+ }
28
+
29
+ /**
30
+ * Sends a client error to the server. Best-effort: never throws.
31
+ * Deduplicates identical consecutive reports so re-renders of the same
32
+ * error do not flood CloudWatch.
33
+ */
34
+ export function sendClientError(payload: ClientErrorPayload): void {
35
+ if (typeof window === "undefined") return;
36
+
37
+ const body = {
38
+ message: payload.message,
39
+ stack: payload.stack,
40
+ source: payload.source ?? "unknown",
41
+ url: payload.url ?? window.location.href,
42
+ userAgent: payload.userAgent ?? navigator.userAgent,
43
+ appVersion: payload.appVersion ?? defaultAppVersion,
44
+ timestamp: payload.timestamp ?? Date.now(),
45
+ };
46
+
47
+ const dedupKey = `${body.source}|${body.message}|${body.url}`;
48
+ if (dedupKey === lastReportedKey) return;
49
+ lastReportedKey = dedupKey;
50
+
51
+ try {
52
+ fetch(ENDPOINT, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify(body),
56
+ keepalive: true,
57
+ credentials: "same-origin",
58
+ }).catch(() => {
59
+ // Silent: reporting must not break UX.
60
+ });
61
+ } catch {
62
+ // Silent.
63
+ }
64
+ }
@@ -0,0 +1,56 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ sendClientError,
4
+ setClientErrorAppVersion,
5
+ } from "./sendClientError.js";
6
+
7
+ export type ZauruClientErrorReportingOptions = {
8
+ appVersion?: string;
9
+ enabled?: boolean;
10
+ };
11
+
12
+ /**
13
+ * Captures `window.onerror` and `unhandledrejection` and POSTs them to
14
+ * `/api/client-errors`. Used by `BodyContainer` by default.
15
+ */
16
+ export function useZauruClientErrorReporting(
17
+ options?: ZauruClientErrorReportingOptions,
18
+ ): void {
19
+ const enabled = options?.enabled !== false;
20
+ const appVersion = options?.appVersion;
21
+
22
+ useEffect(() => {
23
+ if (!enabled || typeof window === "undefined") return;
24
+
25
+ if (appVersion) setClientErrorAppVersion(appVersion);
26
+
27
+ const onError = (event: ErrorEvent) => {
28
+ sendClientError({
29
+ message: event.message || "Error no capturado (window.onerror)",
30
+ stack: event.error instanceof Error ? event.error.stack : undefined,
31
+ source: "window.onerror",
32
+ });
33
+ };
34
+
35
+ const onRejection = (event: PromiseRejectionEvent) => {
36
+ const reason = event.reason;
37
+ sendClientError({
38
+ message:
39
+ reason instanceof Error
40
+ ? reason.message
41
+ : typeof reason === "string"
42
+ ? reason
43
+ : "Promesa rechazada no manejada",
44
+ stack: reason instanceof Error ? reason.stack : undefined,
45
+ source: "unhandledrejection",
46
+ });
47
+ };
48
+
49
+ window.addEventListener("error", onError);
50
+ window.addEventListener("unhandledrejection", onRejection);
51
+ return () => {
52
+ window.removeEventListener("error", onError);
53
+ window.removeEventListener("unhandledrejection", onRejection);
54
+ };
55
+ }, [enabled, appVersion]);
56
+ }