@dloizides/utils 1.2.0 → 1.4.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
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.0] - 2026-07-19
9
+
10
+ ### Added
11
+
12
+ - `sanitizeHtml(text)` / `removeControlCharacters(text)` / `sanitizeNotificationMessage(message, maxLength?)` /
13
+ `sanitizeText(text, maxLength?)` / `sanitizeUrl(url)` — XSS defence-in-depth for user-provided
14
+ text: HTML-entity escaping, control/zero-width/BOM stripping (homograph + hidden-content
15
+ attacks), length capping, and `javascript:`/`data:`/`vbscript:` URL blocking. Pure string
16
+ work with no imports at all, so it is safe on web and native alike. Also exported as the
17
+ `sanitize` namespace. Promoted from the byte-identical `src/utils/sanitize.ts` pair in
18
+ erevna-web and katalogos-web (de-fork Wave 1); all 35 tests moved with it unchanged.
19
+
20
+ ⚠️ **Naming hazard, inherited — do not "fix" by guessing.** The `sanitizeHtml` exported here
21
+ ESCAPES entities (`<` → `&lt;`) and returns display-safe text. `@dloizides/rn-web-hooks`
22
+ exports a DIFFERENT `sanitizeHtml` — a DOMPurify scrubber that PARSES real DOM and strips
23
+ dangerous nodes. The fork gave two different jobs one name. They are kept in separate
24
+ packages so no single import site can resolve both, and neither was renamed here because
25
+ this wave is a provably-safe move, not a redesign. A follow-up should rename the escaping
26
+ one to `escapeHtml`.
27
+
28
+ ## [1.3.0] - 2026-07-19
29
+
30
+ ### Added
31
+
32
+ - `isChunkLoadError(error)` / `attemptChunkRecovery(ports?)` / `clearChunkRecoveryFlag(ports?)` /
33
+ `reloadPage(ports?)` + the `ChunkRecoveryPorts` type — stale-chunk detection and a one-shot
34
+ guarded reload. After a deploy the browser can hold a stale `index.html` referencing hashed
35
+ chunks that now 404; a single guarded `location.reload()` recovers silently, and the
36
+ sessionStorage one-shot flag stops it becoming a reload LOOP when the reload does not help.
37
+ Detection is a pure predicate; the storage + reload side effects are injectable ports.
38
+ Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in kefi-web,
39
+ erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd copy — the original file's
40
+ own header predicted the move). Also exported as the `chunkRecovery` namespace.
41
+ Pairs with `<AppErrorBoundary>` from `@dloizides/ui-feedback` ≥1.7.0, which consumes these as
42
+ its `retryable` / `onMount` / `recover` injections.
43
+
8
44
  ## [1.2.0] - 2026-07-18
9
45
 
10
46
  ### Added
package/dist/index.d.mts CHANGED
@@ -20,6 +20,68 @@ declare function getErrorMessage(value: unknown, fallback?: string): string;
20
20
  */
21
21
  declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
22
22
 
23
+ /**
24
+ * Text sanitization utilities for preventing XSS and ensuring safe display.
25
+ * These utilities sanitize user-provided text before rendering.
26
+ */
27
+ /**
28
+ * Escapes HTML special characters in a string to prevent XSS attacks.
29
+ * Use this when displaying user-provided content in the UI.
30
+ *
31
+ * @param text - The text to sanitize
32
+ * @returns The sanitized text with HTML entities escaped
33
+ *
34
+ * @example
35
+ * sanitizeHtml('<script>alert("xss")</script>')
36
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
37
+ */
38
+ declare function sanitizeHtml(text: string): string;
39
+ /**
40
+ * Removes control characters and zero-width characters from text.
41
+ * These characters can be used to create misleading text or hide content.
42
+ *
43
+ * @param text - The text to sanitize
44
+ * @returns The text with dangerous characters removed
45
+ */
46
+ declare function removeControlCharacters(text: string): string;
47
+ /**
48
+ * Sanitizes a notification message for safe display.
49
+ * This function:
50
+ * 1. Removes control/zero-width characters
51
+ * 2. Trims whitespace
52
+ * 3. Truncates to a reasonable length
53
+ * 4. Escapes HTML entities (for web platform)
54
+ *
55
+ * Note: React Native's Text component already escapes HTML by default,
56
+ * but this provides defense-in-depth and handles the web platform.
57
+ *
58
+ * @param message - The notification message to sanitize
59
+ * @param maxLength - Maximum length (default: 500)
60
+ * @returns The sanitized message
61
+ *
62
+ * @example
63
+ * sanitizeNotificationMessage(' Hello <b>World</b> ')
64
+ * // Returns: 'Hello &lt;b&gt;World&lt;/b&gt;'
65
+ */
66
+ declare function sanitizeNotificationMessage(message: string, maxLength?: number): string;
67
+ /**
68
+ * Sanitizes text for safe display, without HTML escaping.
69
+ * Use this for React Native components where HTML escaping isn't needed
70
+ * but control character removal and length limits are still important.
71
+ *
72
+ * @param text - The text to sanitize
73
+ * @param maxLength - Maximum length (optional)
74
+ * @returns The sanitized text
75
+ */
76
+ declare function sanitizeText(text: string, maxLength?: number): string;
77
+ /**
78
+ * Validates and sanitizes a URL to prevent javascript: and data: protocol attacks.
79
+ *
80
+ * @param url - The URL to validate
81
+ * @returns The sanitized URL or empty string if invalid
82
+ */
83
+ declare function sanitizeUrl(url: string): string;
84
+
23
85
  /**
24
86
  * Framework-agnostic redirect escape hatch.
25
87
  *
@@ -58,21 +120,80 @@ declare function redirectTo(path: string): void;
58
120
  */
59
121
  declare function resetRedirectHandler(): void;
60
122
 
61
- declare const index$2_getErrorMessage: typeof getErrorMessage;
123
+ /**
124
+ * Detection + one-shot auto-recovery for stale-chunk load failures.
125
+ *
126
+ * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold
127
+ * a stale `index.html` that references hashed JS chunks which now 404 — surfacing
128
+ * as a `ChunkLoadError` / "failed to fetch dynamically imported module". A single
129
+ * guarded `location.reload()` re-fetches the fresh entry + chunks and the app
130
+ * recovers with no user-visible error. The sessionStorage one-shot flag prevents
131
+ * a reload LOOP if the reload does not fix it (then the boundary shows a manual
132
+ * Reload action instead).
133
+ *
134
+ * Pure + port-injectable: detection is a pure predicate; the storage + reload
135
+ * side effects are ports (default to `window`) so unit tests run deterministically
136
+ * in jsdom without a real navigation.
137
+ *
138
+ * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in
139
+ * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).
140
+ * The original file's own header predicted this move. Pairs with
141
+ * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its
142
+ * `retryable` / `onMount` / `recover` injections.
143
+ */
144
+ /** Injectable side-effect ports; default to the real `window`. */
145
+ interface ChunkRecoveryPorts {
146
+ hasFlag: () => boolean;
147
+ setFlag: () => void;
148
+ clearFlag: () => void;
149
+ reload: () => void;
150
+ }
151
+ /** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */
152
+ declare function isChunkLoadError(error: unknown): boolean;
153
+ /**
154
+ * Attempt a one-shot reload to recover from a stale-chunk error.
155
+ * Returns `true` when a reload was triggered, `false` when the one-shot guard was
156
+ * already spent (the caller then shows a manual Reload action, never a loop).
157
+ */
158
+ declare function attemptChunkRecovery(ports?: ChunkRecoveryPorts): boolean;
159
+ /** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */
160
+ declare function clearChunkRecoveryFlag(ports?: ChunkRecoveryPorts): void;
161
+ /** Manually reload the page (the Reload action). */
162
+ declare function reloadPage(ports?: ChunkRecoveryPorts): void;
163
+
164
+ declare const index$4_getErrorMessage: typeof getErrorMessage;
165
+ declare namespace index$4 {
166
+ export { index$4_getErrorMessage as getErrorMessage };
167
+ }
168
+
169
+ declare const index$3_formatDate: typeof formatDate;
170
+ declare namespace index$3 {
171
+ export { index$3_formatDate as formatDate };
172
+ }
173
+
174
+ declare const index$2_redirectTo: typeof redirectTo;
175
+ declare const index$2_resetRedirectHandler: typeof resetRedirectHandler;
176
+ declare const index$2_setRedirectHandler: typeof setRedirectHandler;
62
177
  declare namespace index$2 {
63
- export { index$2_getErrorMessage as getErrorMessage };
178
+ export { index$2_redirectTo as redirectTo, index$2_resetRedirectHandler as resetRedirectHandler, index$2_setRedirectHandler as setRedirectHandler };
64
179
  }
65
180
 
66
- declare const index$1_formatDate: typeof formatDate;
181
+ type index$1_ChunkRecoveryPorts = ChunkRecoveryPorts;
182
+ declare const index$1_attemptChunkRecovery: typeof attemptChunkRecovery;
183
+ declare const index$1_clearChunkRecoveryFlag: typeof clearChunkRecoveryFlag;
184
+ declare const index$1_isChunkLoadError: typeof isChunkLoadError;
185
+ declare const index$1_reloadPage: typeof reloadPage;
67
186
  declare namespace index$1 {
68
- export { index$1_formatDate as formatDate };
187
+ export { type index$1_ChunkRecoveryPorts as ChunkRecoveryPorts, index$1_attemptChunkRecovery as attemptChunkRecovery, index$1_clearChunkRecoveryFlag as clearChunkRecoveryFlag, index$1_isChunkLoadError as isChunkLoadError, index$1_reloadPage as reloadPage };
69
188
  }
70
189
 
71
- declare const index_redirectTo: typeof redirectTo;
72
- declare const index_resetRedirectHandler: typeof resetRedirectHandler;
73
- declare const index_setRedirectHandler: typeof setRedirectHandler;
190
+ declare const index_removeControlCharacters: typeof removeControlCharacters;
191
+ declare const index_sanitizeHtml: typeof sanitizeHtml;
192
+ declare const index_sanitizeNotificationMessage: typeof sanitizeNotificationMessage;
193
+ declare const index_sanitizeText: typeof sanitizeText;
194
+ declare const index_sanitizeUrl: typeof sanitizeUrl;
74
195
  declare namespace index {
75
- export { index_redirectTo as redirectTo, index_resetRedirectHandler as resetRedirectHandler, index_setRedirectHandler as setRedirectHandler };
196
+ export { index_removeControlCharacters as removeControlCharacters, index_sanitizeHtml as sanitizeHtml, index_sanitizeNotificationMessage as sanitizeNotificationMessage, index_sanitizeText as sanitizeText, index_sanitizeUrl as sanitizeUrl };
76
197
  }
77
198
 
78
- export { index$2 as error, index$1 as format, formatDate, getErrorMessage, index as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
199
+ export { type ChunkRecoveryPorts, attemptChunkRecovery, index$1 as chunkRecovery, clearChunkRecoveryFlag, index$4 as error, index$3 as format, formatDate, getErrorMessage, isChunkLoadError, index$2 as navigation, redirectTo, reloadPage, removeControlCharacters, resetRedirectHandler, index as sanitize, sanitizeHtml, sanitizeNotificationMessage, sanitizeText, sanitizeUrl, setRedirectHandler };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,68 @@ declare function getErrorMessage(value: unknown, fallback?: string): string;
20
20
  */
21
21
  declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
22
22
 
23
+ /**
24
+ * Text sanitization utilities for preventing XSS and ensuring safe display.
25
+ * These utilities sanitize user-provided text before rendering.
26
+ */
27
+ /**
28
+ * Escapes HTML special characters in a string to prevent XSS attacks.
29
+ * Use this when displaying user-provided content in the UI.
30
+ *
31
+ * @param text - The text to sanitize
32
+ * @returns The sanitized text with HTML entities escaped
33
+ *
34
+ * @example
35
+ * sanitizeHtml('<script>alert("xss")</script>')
36
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
37
+ */
38
+ declare function sanitizeHtml(text: string): string;
39
+ /**
40
+ * Removes control characters and zero-width characters from text.
41
+ * These characters can be used to create misleading text or hide content.
42
+ *
43
+ * @param text - The text to sanitize
44
+ * @returns The text with dangerous characters removed
45
+ */
46
+ declare function removeControlCharacters(text: string): string;
47
+ /**
48
+ * Sanitizes a notification message for safe display.
49
+ * This function:
50
+ * 1. Removes control/zero-width characters
51
+ * 2. Trims whitespace
52
+ * 3. Truncates to a reasonable length
53
+ * 4. Escapes HTML entities (for web platform)
54
+ *
55
+ * Note: React Native's Text component already escapes HTML by default,
56
+ * but this provides defense-in-depth and handles the web platform.
57
+ *
58
+ * @param message - The notification message to sanitize
59
+ * @param maxLength - Maximum length (default: 500)
60
+ * @returns The sanitized message
61
+ *
62
+ * @example
63
+ * sanitizeNotificationMessage(' Hello <b>World</b> ')
64
+ * // Returns: 'Hello &lt;b&gt;World&lt;/b&gt;'
65
+ */
66
+ declare function sanitizeNotificationMessage(message: string, maxLength?: number): string;
67
+ /**
68
+ * Sanitizes text for safe display, without HTML escaping.
69
+ * Use this for React Native components where HTML escaping isn't needed
70
+ * but control character removal and length limits are still important.
71
+ *
72
+ * @param text - The text to sanitize
73
+ * @param maxLength - Maximum length (optional)
74
+ * @returns The sanitized text
75
+ */
76
+ declare function sanitizeText(text: string, maxLength?: number): string;
77
+ /**
78
+ * Validates and sanitizes a URL to prevent javascript: and data: protocol attacks.
79
+ *
80
+ * @param url - The URL to validate
81
+ * @returns The sanitized URL or empty string if invalid
82
+ */
83
+ declare function sanitizeUrl(url: string): string;
84
+
23
85
  /**
24
86
  * Framework-agnostic redirect escape hatch.
25
87
  *
@@ -58,21 +120,80 @@ declare function redirectTo(path: string): void;
58
120
  */
59
121
  declare function resetRedirectHandler(): void;
60
122
 
61
- declare const index$2_getErrorMessage: typeof getErrorMessage;
123
+ /**
124
+ * Detection + one-shot auto-recovery for stale-chunk load failures.
125
+ *
126
+ * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold
127
+ * a stale `index.html` that references hashed JS chunks which now 404 — surfacing
128
+ * as a `ChunkLoadError` / "failed to fetch dynamically imported module". A single
129
+ * guarded `location.reload()` re-fetches the fresh entry + chunks and the app
130
+ * recovers with no user-visible error. The sessionStorage one-shot flag prevents
131
+ * a reload LOOP if the reload does not fix it (then the boundary shows a manual
132
+ * Reload action instead).
133
+ *
134
+ * Pure + port-injectable: detection is a pure predicate; the storage + reload
135
+ * side effects are ports (default to `window`) so unit tests run deterministically
136
+ * in jsdom without a real navigation.
137
+ *
138
+ * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in
139
+ * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).
140
+ * The original file's own header predicted this move. Pairs with
141
+ * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its
142
+ * `retryable` / `onMount` / `recover` injections.
143
+ */
144
+ /** Injectable side-effect ports; default to the real `window`. */
145
+ interface ChunkRecoveryPorts {
146
+ hasFlag: () => boolean;
147
+ setFlag: () => void;
148
+ clearFlag: () => void;
149
+ reload: () => void;
150
+ }
151
+ /** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */
152
+ declare function isChunkLoadError(error: unknown): boolean;
153
+ /**
154
+ * Attempt a one-shot reload to recover from a stale-chunk error.
155
+ * Returns `true` when a reload was triggered, `false` when the one-shot guard was
156
+ * already spent (the caller then shows a manual Reload action, never a loop).
157
+ */
158
+ declare function attemptChunkRecovery(ports?: ChunkRecoveryPorts): boolean;
159
+ /** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */
160
+ declare function clearChunkRecoveryFlag(ports?: ChunkRecoveryPorts): void;
161
+ /** Manually reload the page (the Reload action). */
162
+ declare function reloadPage(ports?: ChunkRecoveryPorts): void;
163
+
164
+ declare const index$4_getErrorMessage: typeof getErrorMessage;
165
+ declare namespace index$4 {
166
+ export { index$4_getErrorMessage as getErrorMessage };
167
+ }
168
+
169
+ declare const index$3_formatDate: typeof formatDate;
170
+ declare namespace index$3 {
171
+ export { index$3_formatDate as formatDate };
172
+ }
173
+
174
+ declare const index$2_redirectTo: typeof redirectTo;
175
+ declare const index$2_resetRedirectHandler: typeof resetRedirectHandler;
176
+ declare const index$2_setRedirectHandler: typeof setRedirectHandler;
62
177
  declare namespace index$2 {
63
- export { index$2_getErrorMessage as getErrorMessage };
178
+ export { index$2_redirectTo as redirectTo, index$2_resetRedirectHandler as resetRedirectHandler, index$2_setRedirectHandler as setRedirectHandler };
64
179
  }
65
180
 
66
- declare const index$1_formatDate: typeof formatDate;
181
+ type index$1_ChunkRecoveryPorts = ChunkRecoveryPorts;
182
+ declare const index$1_attemptChunkRecovery: typeof attemptChunkRecovery;
183
+ declare const index$1_clearChunkRecoveryFlag: typeof clearChunkRecoveryFlag;
184
+ declare const index$1_isChunkLoadError: typeof isChunkLoadError;
185
+ declare const index$1_reloadPage: typeof reloadPage;
67
186
  declare namespace index$1 {
68
- export { index$1_formatDate as formatDate };
187
+ export { type index$1_ChunkRecoveryPorts as ChunkRecoveryPorts, index$1_attemptChunkRecovery as attemptChunkRecovery, index$1_clearChunkRecoveryFlag as clearChunkRecoveryFlag, index$1_isChunkLoadError as isChunkLoadError, index$1_reloadPage as reloadPage };
69
188
  }
70
189
 
71
- declare const index_redirectTo: typeof redirectTo;
72
- declare const index_resetRedirectHandler: typeof resetRedirectHandler;
73
- declare const index_setRedirectHandler: typeof setRedirectHandler;
190
+ declare const index_removeControlCharacters: typeof removeControlCharacters;
191
+ declare const index_sanitizeHtml: typeof sanitizeHtml;
192
+ declare const index_sanitizeNotificationMessage: typeof sanitizeNotificationMessage;
193
+ declare const index_sanitizeText: typeof sanitizeText;
194
+ declare const index_sanitizeUrl: typeof sanitizeUrl;
74
195
  declare namespace index {
75
- export { index_redirectTo as redirectTo, index_resetRedirectHandler as resetRedirectHandler, index_setRedirectHandler as setRedirectHandler };
196
+ export { index_removeControlCharacters as removeControlCharacters, index_sanitizeHtml as sanitizeHtml, index_sanitizeNotificationMessage as sanitizeNotificationMessage, index_sanitizeText as sanitizeText, index_sanitizeUrl as sanitizeUrl };
76
197
  }
77
198
 
78
- export { index$2 as error, index$1 as format, formatDate, getErrorMessage, index as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
199
+ export { type ChunkRecoveryPorts, attemptChunkRecovery, index$1 as chunkRecovery, clearChunkRecoveryFlag, index$4 as error, index$3 as format, formatDate, getErrorMessage, isChunkLoadError, index$2 as navigation, redirectTo, reloadPage, removeControlCharacters, resetRedirectHandler, index as sanitize, sanitizeHtml, sanitizeNotificationMessage, sanitizeText, sanitizeUrl, setRedirectHandler };
package/dist/index.js CHANGED
@@ -76,6 +76,94 @@ function formatDate(date, locale, options) {
76
76
  }
77
77
  }
78
78
 
79
+ // src/sanitize/sanitize.ts
80
+ var HTML_ENTITIES = {
81
+ "&": "&amp;",
82
+ "<": "&lt;",
83
+ ">": "&gt;",
84
+ '"': "&quot;",
85
+ "'": "&#x27;",
86
+ "/": "&#x2F;",
87
+ "`": "&#x60;",
88
+ "=": "&#x3D;"
89
+ };
90
+ var HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
91
+ var C0_CONTROL_START = 0;
92
+ var C0_CONTROL_END = 31;
93
+ var C1_CONTROL_START = 127;
94
+ var C1_CONTROL_END = 159;
95
+ var ZERO_WIDTH_START = 8203;
96
+ var ZERO_WIDTH_END = 8207;
97
+ var FORMAT_CHARS_START = 8232;
98
+ var FORMAT_CHARS_END = 8239;
99
+ var BYTE_ORDER_MARK = 65279;
100
+ var MAX_NOTIFICATION_LENGTH = 500;
101
+ var ELLIPSIS_LENGTH = 3;
102
+ function sanitizeHtml(text) {
103
+ if (text.length === 0) {
104
+ return "";
105
+ }
106
+ return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ENTITIES[char] ?? char);
107
+ }
108
+ function isDangerousCharCode(code) {
109
+ const isC0Control = code >= C0_CONTROL_START && code <= C0_CONTROL_END;
110
+ const isC1Control = code >= C1_CONTROL_START && code <= C1_CONTROL_END;
111
+ const isZeroWidth = code >= ZERO_WIDTH_START && code <= ZERO_WIDTH_END;
112
+ const isFormatChar = code >= FORMAT_CHARS_START && code <= FORMAT_CHARS_END;
113
+ const isBom = code === BYTE_ORDER_MARK;
114
+ return isC0Control || isC1Control || isZeroWidth || isFormatChar || isBom;
115
+ }
116
+ function removeControlCharacters(text) {
117
+ if (text.length === 0) {
118
+ return "";
119
+ }
120
+ let out = "";
121
+ for (let i = 0; i < text.length; i++) {
122
+ const code = text.charCodeAt(i);
123
+ if (!isDangerousCharCode(code)) {
124
+ out += text[i];
125
+ }
126
+ }
127
+ return out;
128
+ }
129
+ function sanitizeNotificationMessage(message, maxLength = MAX_NOTIFICATION_LENGTH) {
130
+ if (message.length === 0) {
131
+ return "";
132
+ }
133
+ let sanitized = removeControlCharacters(message);
134
+ sanitized = sanitized.trim();
135
+ if (sanitized.length > maxLength) {
136
+ sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;
137
+ }
138
+ sanitized = sanitizeHtml(sanitized);
139
+ return sanitized;
140
+ }
141
+ function sanitizeText(text, maxLength) {
142
+ if (text.length === 0) {
143
+ return "";
144
+ }
145
+ let sanitized = removeControlCharacters(text);
146
+ sanitized = sanitized.trim();
147
+ const hasValidMaxLength = typeof maxLength === "number" && maxLength > 0;
148
+ const exceedsMaxLength = hasValidMaxLength && sanitized.length > maxLength;
149
+ if (exceedsMaxLength) {
150
+ sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;
151
+ }
152
+ return sanitized;
153
+ }
154
+ function sanitizeUrl(url) {
155
+ if (url.length === 0) {
156
+ return "";
157
+ }
158
+ const trimmedLower = url.trim().toLowerCase();
159
+ const protocol = trimmedLower.split(":")[0];
160
+ const isDangerousProtocol = protocol === "javascript" || protocol === "data" || protocol === "vbscript";
161
+ if (isDangerousProtocol) {
162
+ return "";
163
+ }
164
+ return url.trim();
165
+ }
166
+
79
167
  // src/navigation/redirect.ts
80
168
  var NAVIGATION_CHECK_DELAY_MS = 150;
81
169
  var redirectHandler = null;
@@ -147,6 +235,70 @@ function resetRedirectHandler() {
147
235
  queuedRedirects.length = 0;
148
236
  }
149
237
 
238
+ // src/chunkRecovery/chunkRecovery.ts
239
+ var CHUNK_RELOAD_FLAG = "ui.chunkReload.attempted";
240
+ var CHUNK_ERROR_PATTERNS = [
241
+ /ChunkLoadError/i,
242
+ /Loading chunk [\w-]+ failed/i,
243
+ /Loading CSS chunk/i,
244
+ /Failed to fetch dynamically imported module/i,
245
+ /error loading dynamically imported module/i,
246
+ /Importing a module script failed/i,
247
+ /is not a valid JavaScript MIME type/i
248
+ ];
249
+ function isChunkLoadError(error) {
250
+ if (!(error instanceof Error)) {
251
+ return false;
252
+ }
253
+ if (error.name === "ChunkLoadError") {
254
+ return true;
255
+ }
256
+ const haystack = `${error.name} ${error.message}`;
257
+ return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));
258
+ }
259
+ function resolveStorage() {
260
+ if (typeof window === "undefined") {
261
+ return void 0;
262
+ }
263
+ try {
264
+ return window.sessionStorage;
265
+ } catch {
266
+ return void 0;
267
+ }
268
+ }
269
+ function defaultPorts() {
270
+ const storage = resolveStorage();
271
+ const hasWindow = typeof window !== "undefined";
272
+ return {
273
+ hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),
274
+ setFlag: () => {
275
+ storage?.setItem(CHUNK_RELOAD_FLAG, "1");
276
+ },
277
+ clearFlag: () => {
278
+ storage?.removeItem(CHUNK_RELOAD_FLAG);
279
+ },
280
+ reload: () => {
281
+ if (hasWindow) {
282
+ window.location.reload();
283
+ }
284
+ }
285
+ };
286
+ }
287
+ function attemptChunkRecovery(ports = defaultPorts()) {
288
+ if (ports.hasFlag()) {
289
+ return false;
290
+ }
291
+ ports.setFlag();
292
+ ports.reload();
293
+ return true;
294
+ }
295
+ function clearChunkRecoveryFlag(ports = defaultPorts()) {
296
+ ports.clearFlag();
297
+ }
298
+ function reloadPage(ports = defaultPorts()) {
299
+ ports.reload();
300
+ }
301
+
150
302
  // src/guards/index.ts
151
303
  var guards_exports = {};
152
304
  __export(guards_exports, {
@@ -184,13 +336,36 @@ __export(navigation_exports, {
184
336
  setRedirectHandler: () => setRedirectHandler
185
337
  });
186
338
 
339
+ // src/chunkRecovery/index.ts
340
+ var chunkRecovery_exports = {};
341
+ __export(chunkRecovery_exports, {
342
+ attemptChunkRecovery: () => attemptChunkRecovery,
343
+ clearChunkRecoveryFlag: () => clearChunkRecoveryFlag,
344
+ isChunkLoadError: () => isChunkLoadError,
345
+ reloadPage: () => reloadPage
346
+ });
347
+
348
+ // src/sanitize/index.ts
349
+ var sanitize_exports = {};
350
+ __export(sanitize_exports, {
351
+ removeControlCharacters: () => removeControlCharacters,
352
+ sanitizeHtml: () => sanitizeHtml,
353
+ sanitizeNotificationMessage: () => sanitizeNotificationMessage,
354
+ sanitizeText: () => sanitizeText,
355
+ sanitizeUrl: () => sanitizeUrl
356
+ });
357
+
187
358
  exports.assertDefined = assertDefined;
188
359
  exports.assertions = assertions_exports;
360
+ exports.attemptChunkRecovery = attemptChunkRecovery;
361
+ exports.chunkRecovery = chunkRecovery_exports;
362
+ exports.clearChunkRecoveryFlag = clearChunkRecoveryFlag;
189
363
  exports.error = error_exports;
190
364
  exports.format = format_exports;
191
365
  exports.formatDate = formatDate;
192
366
  exports.getErrorMessage = getErrorMessage;
193
367
  exports.guards = guards_exports;
368
+ exports.isChunkLoadError = isChunkLoadError;
194
369
  exports.isEmptyArray = isEmptyArray;
195
370
  exports.isEmptyString = isEmptyString;
196
371
  exports.isNotEmptyArray = isNotEmptyArray;
@@ -199,7 +374,14 @@ exports.isNullOrUndefined = isNullOrUndefined;
199
374
  exports.isValueDefined = isValueDefined;
200
375
  exports.navigation = navigation_exports;
201
376
  exports.redirectTo = redirectTo;
377
+ exports.reloadPage = reloadPage;
378
+ exports.removeControlCharacters = removeControlCharacters;
202
379
  exports.resetRedirectHandler = resetRedirectHandler;
380
+ exports.sanitize = sanitize_exports;
381
+ exports.sanitizeHtml = sanitizeHtml;
382
+ exports.sanitizeNotificationMessage = sanitizeNotificationMessage;
383
+ exports.sanitizeText = sanitizeText;
384
+ exports.sanitizeUrl = sanitizeUrl;
203
385
  exports.setRedirectHandler = setRedirectHandler;
204
386
  //# sourceMappingURL=index.js.map
205
387
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/navigation/redirect.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n"]}
1
+ {"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/sanitize/sanitize.ts","../src/navigation/redirect.ts","../src/chunkRecovery/chunkRecovery.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts","../src/chunkRecovery/index.ts","../src/sanitize/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Text sanitization utilities for preventing XSS and ensuring safe display.\n * These utilities sanitize user-provided text before rendering.\n */\n\n/**\n * HTML entities that should be escaped to prevent XSS attacks.\n */\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;',\n};\n\n/**\n * Regular expression matching characters that need escaping.\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`=/]/g;\n\n/**\n * Unicode code point ranges for dangerous characters.\n * These are control characters and zero-width characters that can be\n * used in homograph attacks or to hide malicious content.\n */\n\n/** C0 control characters range start (NULL) */\nconst C0_CONTROL_START = 0x0000;\n/** C0 control characters range end (Unit Separator) */\nconst C0_CONTROL_END = 0x001f;\n/** C1 control characters range start (DEL and beyond) */\nconst C1_CONTROL_START = 0x007f;\n/** C1 control characters range end */\nconst C1_CONTROL_END = 0x009f;\n/** Zero-width characters range start (Zero Width Space) */\nconst ZERO_WIDTH_START = 0x200b;\n/** Zero-width characters range end (Right-to-Left Mark) */\nconst ZERO_WIDTH_END = 0x200f;\n/** Format characters range start (Line Separator) */\nconst FORMAT_CHARS_START = 0x2028;\n/** Format characters range end (Narrow No-Break Space) */\nconst FORMAT_CHARS_END = 0x202f;\n/** Byte Order Mark code point */\nconst BYTE_ORDER_MARK = 0xfeff;\n\n/**\n * Maximum reasonable length for notification messages.\n */\nconst MAX_NOTIFICATION_LENGTH = 500;\n\n/**\n * Length of ellipsis suffix (\"...\") used when truncating text.\n */\nconst ELLIPSIS_LENGTH = 3;\n\n/**\n * Escapes HTML special characters in a string to prevent XSS attacks.\n * Use this when displaying user-provided content in the UI.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text with HTML entities escaped\n *\n * @example\n * sanitizeHtml('<script>alert(\"xss\")</script>')\n * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'\n */\nexport function sanitizeHtml(text: string): string {\n if (text.length === 0) {return '';}\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ENTITIES[char] ?? char);\n}\n\n/**\n * Control characters and zero-width characters that should be removed.\n * These can be used in homograph attacks or to hide malicious content.\n */\nfunction isDangerousCharCode(code: number): boolean {\n const isC0Control = code >= C0_CONTROL_START && code <= C0_CONTROL_END;\n const isC1Control = code >= C1_CONTROL_START && code <= C1_CONTROL_END;\n const isZeroWidth = code >= ZERO_WIDTH_START && code <= ZERO_WIDTH_END;\n const isFormatChar = code >= FORMAT_CHARS_START && code <= FORMAT_CHARS_END;\n const isBom = code === BYTE_ORDER_MARK;\n return isC0Control || isC1Control || isZeroWidth || isFormatChar || isBom;\n}\n\n/**\n * Removes control characters and zero-width characters from text.\n * These characters can be used to create misleading text or hide content.\n *\n * @param text - The text to sanitize\n * @returns The text with dangerous characters removed\n */\nexport function removeControlCharacters(text: string): string {\n if (text.length === 0) {return '';}\n let out = '';\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (!isDangerousCharCode(code)) {out += text[i];}\n }\n return out;\n}\n\n/**\n * Sanitizes a notification message for safe display.\n * This function:\n * 1. Removes control/zero-width characters\n * 2. Trims whitespace\n * 3. Truncates to a reasonable length\n * 4. Escapes HTML entities (for web platform)\n *\n * Note: React Native's Text component already escapes HTML by default,\n * but this provides defense-in-depth and handles the web platform.\n *\n * @param message - The notification message to sanitize\n * @param maxLength - Maximum length (default: 500)\n * @returns The sanitized message\n *\n * @example\n * sanitizeNotificationMessage(' Hello <b>World</b> ')\n * // Returns: 'Hello &lt;b&gt;World&lt;/b&gt;'\n */\nexport function sanitizeNotificationMessage(\n message: string,\n maxLength: number = MAX_NOTIFICATION_LENGTH\n): string {\n if (message.length === 0) {return '';}\n\n // Remove dangerous characters first\n let sanitized = removeControlCharacters(message);\n\n // Trim whitespace\n sanitized = sanitized.trim();\n\n // Truncate if too long\n if (sanitized.length > maxLength) \n {sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;}\n \n\n // Escape HTML entities\n sanitized = sanitizeHtml(sanitized);\n\n return sanitized;\n}\n\n/**\n * Sanitizes text for safe display, without HTML escaping.\n * Use this for React Native components where HTML escaping isn't needed\n * but control character removal and length limits are still important.\n *\n * @param text - The text to sanitize\n * @param maxLength - Maximum length (optional)\n * @returns The sanitized text\n */\nexport function sanitizeText(text: string, maxLength?: number): string {\n if (text.length === 0) {return '';}\n\n let sanitized = removeControlCharacters(text);\n sanitized = sanitized.trim();\n\n const hasValidMaxLength = typeof maxLength === 'number' && maxLength > 0;\n const exceedsMaxLength = hasValidMaxLength && sanitized.length > maxLength;\n if (exceedsMaxLength) \n {sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;}\n \n\n return sanitized;\n}\n\n/**\n * Validates and sanitizes a URL to prevent javascript: and data: protocol attacks.\n *\n * @param url - The URL to validate\n * @returns The sanitized URL or empty string if invalid\n */\nexport function sanitizeUrl(url: string): string {\n if (url.length === 0) {return '';}\n\n const trimmedLower = url.trim().toLowerCase();\n const protocol = trimmedLower.split(':')[0];\n\n // Block dangerous protocols\n const isDangerousProtocol = protocol === 'javascript' || protocol === 'data' || protocol === 'vbscript';\n if (isDangerousProtocol) \n {return '';}\n \n\n return url.trim();\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","/**\n * Detection + one-shot auto-recovery for stale-chunk load failures.\n *\n * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold\n * a stale `index.html` that references hashed JS chunks which now 404 — surfacing\n * as a `ChunkLoadError` / \"failed to fetch dynamically imported module\". A single\n * guarded `location.reload()` re-fetches the fresh entry + chunks and the app\n * recovers with no user-visible error. The sessionStorage one-shot flag prevents\n * a reload LOOP if the reload does not fix it (then the boundary shows a manual\n * Reload action instead).\n *\n * Pure + port-injectable: detection is a pure predicate; the storage + reload\n * side effects are ports (default to `window`) so unit tests run deterministically\n * in jsdom without a real navigation.\n *\n * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in\n * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).\n * The original file's own header predicted this move. Pairs with\n * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its\n * `retryable` / `onMount` / `recover` injections.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020` (see `navigation/redirect.ts`, which\n * makes the same call). Declaring the few properties used here keeps every other\n * util in the package from reaching for browser globals absent on the server.\n */\ninterface ChunkRecoveryStorage {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n}\n\ninterface BrowserWindow {\n sessionStorage: ChunkRecoveryStorage;\n location: { reload: () => void };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** sessionStorage key for the one-shot auto-reload guard. */\nconst CHUNK_RELOAD_FLAG = 'ui.chunkReload.attempted';\n\n/** Messages/names emitted by webpack/metro/vite/Safari for a stale-chunk failure. */\nconst CHUNK_ERROR_PATTERNS: readonly RegExp[] = [\n /ChunkLoadError/i,\n /Loading chunk [\\w-]+ failed/i,\n /Loading CSS chunk/i,\n /Failed to fetch dynamically imported module/i,\n /error loading dynamically imported module/i,\n /Importing a module script failed/i,\n /is not a valid JavaScript MIME type/i,\n];\n\n/** Injectable side-effect ports; default to the real `window`. */\nexport interface ChunkRecoveryPorts {\n hasFlag: () => boolean;\n setFlag: () => void;\n clearFlag: () => void;\n reload: () => void;\n}\n\n/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n if (error.name === 'ChunkLoadError') {\n return true;\n }\n const haystack = `${error.name} ${error.message}`;\n return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));\n}\n\nfunction resolveStorage(): ChunkRecoveryStorage | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n try {\n return window.sessionStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction defaultPorts(): ChunkRecoveryPorts {\n const storage = resolveStorage();\n const hasWindow = typeof window !== 'undefined';\n return {\n hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),\n setFlag: (): void => {\n storage?.setItem(CHUNK_RELOAD_FLAG, '1');\n },\n clearFlag: (): void => {\n storage?.removeItem(CHUNK_RELOAD_FLAG);\n },\n reload: (): void => {\n if (hasWindow) {\n window.location.reload();\n }\n },\n };\n}\n\n/**\n * Attempt a one-shot reload to recover from a stale-chunk error.\n * Returns `true` when a reload was triggered, `false` when the one-shot guard was\n * already spent (the caller then shows a manual Reload action, never a loop).\n */\nexport function attemptChunkRecovery(ports: ChunkRecoveryPorts = defaultPorts()): boolean {\n if (ports.hasFlag()) {\n return false;\n }\n ports.setFlag();\n ports.reload();\n return true;\n}\n\n/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */\nexport function clearChunkRecoveryFlag(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.clearFlag();\n}\n\n/** Manually reload the page (the Reload action). */\nexport function reloadPage(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.reload();\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n","export {\n isChunkLoadError,\n attemptChunkRecovery,\n clearChunkRecoveryFlag,\n reloadPage,\n} from './chunkRecovery';\nexport type { ChunkRecoveryPorts } from './chunkRecovery';\n","export {\n sanitizeHtml,\n removeControlCharacters,\n sanitizeNotificationMessage,\n sanitizeText,\n sanitizeUrl,\n} from './sanitize';\n"]}
package/dist/index.mjs CHANGED
@@ -74,6 +74,94 @@ function formatDate(date, locale, options) {
74
74
  }
75
75
  }
76
76
 
77
+ // src/sanitize/sanitize.ts
78
+ var HTML_ENTITIES = {
79
+ "&": "&amp;",
80
+ "<": "&lt;",
81
+ ">": "&gt;",
82
+ '"': "&quot;",
83
+ "'": "&#x27;",
84
+ "/": "&#x2F;",
85
+ "`": "&#x60;",
86
+ "=": "&#x3D;"
87
+ };
88
+ var HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
89
+ var C0_CONTROL_START = 0;
90
+ var C0_CONTROL_END = 31;
91
+ var C1_CONTROL_START = 127;
92
+ var C1_CONTROL_END = 159;
93
+ var ZERO_WIDTH_START = 8203;
94
+ var ZERO_WIDTH_END = 8207;
95
+ var FORMAT_CHARS_START = 8232;
96
+ var FORMAT_CHARS_END = 8239;
97
+ var BYTE_ORDER_MARK = 65279;
98
+ var MAX_NOTIFICATION_LENGTH = 500;
99
+ var ELLIPSIS_LENGTH = 3;
100
+ function sanitizeHtml(text) {
101
+ if (text.length === 0) {
102
+ return "";
103
+ }
104
+ return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ENTITIES[char] ?? char);
105
+ }
106
+ function isDangerousCharCode(code) {
107
+ const isC0Control = code >= C0_CONTROL_START && code <= C0_CONTROL_END;
108
+ const isC1Control = code >= C1_CONTROL_START && code <= C1_CONTROL_END;
109
+ const isZeroWidth = code >= ZERO_WIDTH_START && code <= ZERO_WIDTH_END;
110
+ const isFormatChar = code >= FORMAT_CHARS_START && code <= FORMAT_CHARS_END;
111
+ const isBom = code === BYTE_ORDER_MARK;
112
+ return isC0Control || isC1Control || isZeroWidth || isFormatChar || isBom;
113
+ }
114
+ function removeControlCharacters(text) {
115
+ if (text.length === 0) {
116
+ return "";
117
+ }
118
+ let out = "";
119
+ for (let i = 0; i < text.length; i++) {
120
+ const code = text.charCodeAt(i);
121
+ if (!isDangerousCharCode(code)) {
122
+ out += text[i];
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ function sanitizeNotificationMessage(message, maxLength = MAX_NOTIFICATION_LENGTH) {
128
+ if (message.length === 0) {
129
+ return "";
130
+ }
131
+ let sanitized = removeControlCharacters(message);
132
+ sanitized = sanitized.trim();
133
+ if (sanitized.length > maxLength) {
134
+ sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;
135
+ }
136
+ sanitized = sanitizeHtml(sanitized);
137
+ return sanitized;
138
+ }
139
+ function sanitizeText(text, maxLength) {
140
+ if (text.length === 0) {
141
+ return "";
142
+ }
143
+ let sanitized = removeControlCharacters(text);
144
+ sanitized = sanitized.trim();
145
+ const hasValidMaxLength = typeof maxLength === "number" && maxLength > 0;
146
+ const exceedsMaxLength = hasValidMaxLength && sanitized.length > maxLength;
147
+ if (exceedsMaxLength) {
148
+ sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;
149
+ }
150
+ return sanitized;
151
+ }
152
+ function sanitizeUrl(url) {
153
+ if (url.length === 0) {
154
+ return "";
155
+ }
156
+ const trimmedLower = url.trim().toLowerCase();
157
+ const protocol = trimmedLower.split(":")[0];
158
+ const isDangerousProtocol = protocol === "javascript" || protocol === "data" || protocol === "vbscript";
159
+ if (isDangerousProtocol) {
160
+ return "";
161
+ }
162
+ return url.trim();
163
+ }
164
+
77
165
  // src/navigation/redirect.ts
78
166
  var NAVIGATION_CHECK_DELAY_MS = 150;
79
167
  var redirectHandler = null;
@@ -145,6 +233,70 @@ function resetRedirectHandler() {
145
233
  queuedRedirects.length = 0;
146
234
  }
147
235
 
236
+ // src/chunkRecovery/chunkRecovery.ts
237
+ var CHUNK_RELOAD_FLAG = "ui.chunkReload.attempted";
238
+ var CHUNK_ERROR_PATTERNS = [
239
+ /ChunkLoadError/i,
240
+ /Loading chunk [\w-]+ failed/i,
241
+ /Loading CSS chunk/i,
242
+ /Failed to fetch dynamically imported module/i,
243
+ /error loading dynamically imported module/i,
244
+ /Importing a module script failed/i,
245
+ /is not a valid JavaScript MIME type/i
246
+ ];
247
+ function isChunkLoadError(error) {
248
+ if (!(error instanceof Error)) {
249
+ return false;
250
+ }
251
+ if (error.name === "ChunkLoadError") {
252
+ return true;
253
+ }
254
+ const haystack = `${error.name} ${error.message}`;
255
+ return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));
256
+ }
257
+ function resolveStorage() {
258
+ if (typeof window === "undefined") {
259
+ return void 0;
260
+ }
261
+ try {
262
+ return window.sessionStorage;
263
+ } catch {
264
+ return void 0;
265
+ }
266
+ }
267
+ function defaultPorts() {
268
+ const storage = resolveStorage();
269
+ const hasWindow = typeof window !== "undefined";
270
+ return {
271
+ hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),
272
+ setFlag: () => {
273
+ storage?.setItem(CHUNK_RELOAD_FLAG, "1");
274
+ },
275
+ clearFlag: () => {
276
+ storage?.removeItem(CHUNK_RELOAD_FLAG);
277
+ },
278
+ reload: () => {
279
+ if (hasWindow) {
280
+ window.location.reload();
281
+ }
282
+ }
283
+ };
284
+ }
285
+ function attemptChunkRecovery(ports = defaultPorts()) {
286
+ if (ports.hasFlag()) {
287
+ return false;
288
+ }
289
+ ports.setFlag();
290
+ ports.reload();
291
+ return true;
292
+ }
293
+ function clearChunkRecoveryFlag(ports = defaultPorts()) {
294
+ ports.clearFlag();
295
+ }
296
+ function reloadPage(ports = defaultPorts()) {
297
+ ports.reload();
298
+ }
299
+
148
300
  // src/guards/index.ts
149
301
  var guards_exports = {};
150
302
  __export(guards_exports, {
@@ -182,6 +334,25 @@ __export(navigation_exports, {
182
334
  setRedirectHandler: () => setRedirectHandler
183
335
  });
184
336
 
185
- export { assertDefined, assertions_exports as assertions, error_exports as error, format_exports as format, formatDate, getErrorMessage, guards_exports as guards, isEmptyArray, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isValueDefined, navigation_exports as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
337
+ // src/chunkRecovery/index.ts
338
+ var chunkRecovery_exports = {};
339
+ __export(chunkRecovery_exports, {
340
+ attemptChunkRecovery: () => attemptChunkRecovery,
341
+ clearChunkRecoveryFlag: () => clearChunkRecoveryFlag,
342
+ isChunkLoadError: () => isChunkLoadError,
343
+ reloadPage: () => reloadPage
344
+ });
345
+
346
+ // src/sanitize/index.ts
347
+ var sanitize_exports = {};
348
+ __export(sanitize_exports, {
349
+ removeControlCharacters: () => removeControlCharacters,
350
+ sanitizeHtml: () => sanitizeHtml,
351
+ sanitizeNotificationMessage: () => sanitizeNotificationMessage,
352
+ sanitizeText: () => sanitizeText,
353
+ sanitizeUrl: () => sanitizeUrl
354
+ });
355
+
356
+ export { assertDefined, assertions_exports as assertions, attemptChunkRecovery, chunkRecovery_exports as chunkRecovery, clearChunkRecoveryFlag, error_exports as error, format_exports as format, formatDate, getErrorMessage, guards_exports as guards, isChunkLoadError, isEmptyArray, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isValueDefined, navigation_exports as navigation, redirectTo, reloadPage, removeControlCharacters, resetRedirectHandler, sanitize_exports as sanitize, sanitizeHtml, sanitizeNotificationMessage, sanitizeText, sanitizeUrl, setRedirectHandler };
186
357
  //# sourceMappingURL=index.mjs.map
187
358
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/navigation/redirect.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n"]}
1
+ {"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/sanitize/sanitize.ts","../src/navigation/redirect.ts","../src/chunkRecovery/chunkRecovery.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts","../src/chunkRecovery/index.ts","../src/sanitize/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Text sanitization utilities for preventing XSS and ensuring safe display.\n * These utilities sanitize user-provided text before rendering.\n */\n\n/**\n * HTML entities that should be escaped to prevent XSS attacks.\n */\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;',\n};\n\n/**\n * Regular expression matching characters that need escaping.\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`=/]/g;\n\n/**\n * Unicode code point ranges for dangerous characters.\n * These are control characters and zero-width characters that can be\n * used in homograph attacks or to hide malicious content.\n */\n\n/** C0 control characters range start (NULL) */\nconst C0_CONTROL_START = 0x0000;\n/** C0 control characters range end (Unit Separator) */\nconst C0_CONTROL_END = 0x001f;\n/** C1 control characters range start (DEL and beyond) */\nconst C1_CONTROL_START = 0x007f;\n/** C1 control characters range end */\nconst C1_CONTROL_END = 0x009f;\n/** Zero-width characters range start (Zero Width Space) */\nconst ZERO_WIDTH_START = 0x200b;\n/** Zero-width characters range end (Right-to-Left Mark) */\nconst ZERO_WIDTH_END = 0x200f;\n/** Format characters range start (Line Separator) */\nconst FORMAT_CHARS_START = 0x2028;\n/** Format characters range end (Narrow No-Break Space) */\nconst FORMAT_CHARS_END = 0x202f;\n/** Byte Order Mark code point */\nconst BYTE_ORDER_MARK = 0xfeff;\n\n/**\n * Maximum reasonable length for notification messages.\n */\nconst MAX_NOTIFICATION_LENGTH = 500;\n\n/**\n * Length of ellipsis suffix (\"...\") used when truncating text.\n */\nconst ELLIPSIS_LENGTH = 3;\n\n/**\n * Escapes HTML special characters in a string to prevent XSS attacks.\n * Use this when displaying user-provided content in the UI.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text with HTML entities escaped\n *\n * @example\n * sanitizeHtml('<script>alert(\"xss\")</script>')\n * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'\n */\nexport function sanitizeHtml(text: string): string {\n if (text.length === 0) {return '';}\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ENTITIES[char] ?? char);\n}\n\n/**\n * Control characters and zero-width characters that should be removed.\n * These can be used in homograph attacks or to hide malicious content.\n */\nfunction isDangerousCharCode(code: number): boolean {\n const isC0Control = code >= C0_CONTROL_START && code <= C0_CONTROL_END;\n const isC1Control = code >= C1_CONTROL_START && code <= C1_CONTROL_END;\n const isZeroWidth = code >= ZERO_WIDTH_START && code <= ZERO_WIDTH_END;\n const isFormatChar = code >= FORMAT_CHARS_START && code <= FORMAT_CHARS_END;\n const isBom = code === BYTE_ORDER_MARK;\n return isC0Control || isC1Control || isZeroWidth || isFormatChar || isBom;\n}\n\n/**\n * Removes control characters and zero-width characters from text.\n * These characters can be used to create misleading text or hide content.\n *\n * @param text - The text to sanitize\n * @returns The text with dangerous characters removed\n */\nexport function removeControlCharacters(text: string): string {\n if (text.length === 0) {return '';}\n let out = '';\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (!isDangerousCharCode(code)) {out += text[i];}\n }\n return out;\n}\n\n/**\n * Sanitizes a notification message for safe display.\n * This function:\n * 1. Removes control/zero-width characters\n * 2. Trims whitespace\n * 3. Truncates to a reasonable length\n * 4. Escapes HTML entities (for web platform)\n *\n * Note: React Native's Text component already escapes HTML by default,\n * but this provides defense-in-depth and handles the web platform.\n *\n * @param message - The notification message to sanitize\n * @param maxLength - Maximum length (default: 500)\n * @returns The sanitized message\n *\n * @example\n * sanitizeNotificationMessage(' Hello <b>World</b> ')\n * // Returns: 'Hello &lt;b&gt;World&lt;/b&gt;'\n */\nexport function sanitizeNotificationMessage(\n message: string,\n maxLength: number = MAX_NOTIFICATION_LENGTH\n): string {\n if (message.length === 0) {return '';}\n\n // Remove dangerous characters first\n let sanitized = removeControlCharacters(message);\n\n // Trim whitespace\n sanitized = sanitized.trim();\n\n // Truncate if too long\n if (sanitized.length > maxLength) \n {sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;}\n \n\n // Escape HTML entities\n sanitized = sanitizeHtml(sanitized);\n\n return sanitized;\n}\n\n/**\n * Sanitizes text for safe display, without HTML escaping.\n * Use this for React Native components where HTML escaping isn't needed\n * but control character removal and length limits are still important.\n *\n * @param text - The text to sanitize\n * @param maxLength - Maximum length (optional)\n * @returns The sanitized text\n */\nexport function sanitizeText(text: string, maxLength?: number): string {\n if (text.length === 0) {return '';}\n\n let sanitized = removeControlCharacters(text);\n sanitized = sanitized.trim();\n\n const hasValidMaxLength = typeof maxLength === 'number' && maxLength > 0;\n const exceedsMaxLength = hasValidMaxLength && sanitized.length > maxLength;\n if (exceedsMaxLength) \n {sanitized = `${sanitized.substring(0, maxLength - ELLIPSIS_LENGTH)}...`;}\n \n\n return sanitized;\n}\n\n/**\n * Validates and sanitizes a URL to prevent javascript: and data: protocol attacks.\n *\n * @param url - The URL to validate\n * @returns The sanitized URL or empty string if invalid\n */\nexport function sanitizeUrl(url: string): string {\n if (url.length === 0) {return '';}\n\n const trimmedLower = url.trim().toLowerCase();\n const protocol = trimmedLower.split(':')[0];\n\n // Block dangerous protocols\n const isDangerousProtocol = protocol === 'javascript' || protocol === 'data' || protocol === 'vbscript';\n if (isDangerousProtocol) \n {return '';}\n \n\n return url.trim();\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","/**\n * Detection + one-shot auto-recovery for stale-chunk load failures.\n *\n * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold\n * a stale `index.html` that references hashed JS chunks which now 404 — surfacing\n * as a `ChunkLoadError` / \"failed to fetch dynamically imported module\". A single\n * guarded `location.reload()` re-fetches the fresh entry + chunks and the app\n * recovers with no user-visible error. The sessionStorage one-shot flag prevents\n * a reload LOOP if the reload does not fix it (then the boundary shows a manual\n * Reload action instead).\n *\n * Pure + port-injectable: detection is a pure predicate; the storage + reload\n * side effects are ports (default to `window`) so unit tests run deterministically\n * in jsdom without a real navigation.\n *\n * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in\n * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).\n * The original file's own header predicted this move. Pairs with\n * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its\n * `retryable` / `onMount` / `recover` injections.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020` (see `navigation/redirect.ts`, which\n * makes the same call). Declaring the few properties used here keeps every other\n * util in the package from reaching for browser globals absent on the server.\n */\ninterface ChunkRecoveryStorage {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n}\n\ninterface BrowserWindow {\n sessionStorage: ChunkRecoveryStorage;\n location: { reload: () => void };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** sessionStorage key for the one-shot auto-reload guard. */\nconst CHUNK_RELOAD_FLAG = 'ui.chunkReload.attempted';\n\n/** Messages/names emitted by webpack/metro/vite/Safari for a stale-chunk failure. */\nconst CHUNK_ERROR_PATTERNS: readonly RegExp[] = [\n /ChunkLoadError/i,\n /Loading chunk [\\w-]+ failed/i,\n /Loading CSS chunk/i,\n /Failed to fetch dynamically imported module/i,\n /error loading dynamically imported module/i,\n /Importing a module script failed/i,\n /is not a valid JavaScript MIME type/i,\n];\n\n/** Injectable side-effect ports; default to the real `window`. */\nexport interface ChunkRecoveryPorts {\n hasFlag: () => boolean;\n setFlag: () => void;\n clearFlag: () => void;\n reload: () => void;\n}\n\n/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n if (error.name === 'ChunkLoadError') {\n return true;\n }\n const haystack = `${error.name} ${error.message}`;\n return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));\n}\n\nfunction resolveStorage(): ChunkRecoveryStorage | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n try {\n return window.sessionStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction defaultPorts(): ChunkRecoveryPorts {\n const storage = resolveStorage();\n const hasWindow = typeof window !== 'undefined';\n return {\n hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),\n setFlag: (): void => {\n storage?.setItem(CHUNK_RELOAD_FLAG, '1');\n },\n clearFlag: (): void => {\n storage?.removeItem(CHUNK_RELOAD_FLAG);\n },\n reload: (): void => {\n if (hasWindow) {\n window.location.reload();\n }\n },\n };\n}\n\n/**\n * Attempt a one-shot reload to recover from a stale-chunk error.\n * Returns `true` when a reload was triggered, `false` when the one-shot guard was\n * already spent (the caller then shows a manual Reload action, never a loop).\n */\nexport function attemptChunkRecovery(ports: ChunkRecoveryPorts = defaultPorts()): boolean {\n if (ports.hasFlag()) {\n return false;\n }\n ports.setFlag();\n ports.reload();\n return true;\n}\n\n/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */\nexport function clearChunkRecoveryFlag(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.clearFlag();\n}\n\n/** Manually reload the page (the Reload action). */\nexport function reloadPage(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.reload();\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n","export {\n isChunkLoadError,\n attemptChunkRecovery,\n clearChunkRecoveryFlag,\n reloadPage,\n} from './chunkRecovery';\nexport type { ChunkRecoveryPorts } from './chunkRecovery';\n","export {\n sanitizeHtml,\n removeControlCharacters,\n sanitizeNotificationMessage,\n sanitizeText,\n sanitizeUrl,\n} from './sanitize';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dloizides/utils",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Type-safe utility functions for TypeScript projects",
5
5
  "keywords": [
6
6
  "typescript",