@makolabs/ripple 3.8.0 → 3.9.1
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/dist/ai/MessageBox.svelte +2 -1
- package/dist/button/Button.svelte +89 -7
- package/dist/elements/file-viewer/FilePreviewModal.svelte +102 -0
- package/dist/elements/file-viewer/FilePreviewModal.svelte.d.ts +4 -0
- package/dist/elements/file-viewer/FileViewer.svelte +96 -0
- package/dist/elements/file-viewer/FileViewer.svelte.d.ts +4 -0
- package/dist/elements/file-viewer/file-preview-modal-types.d.ts +62 -0
- package/dist/elements/file-viewer/file-preview-modal-types.js +1 -0
- package/dist/elements/file-viewer/file-viewer-types.d.ts +51 -0
- package/dist/elements/file-viewer/file-viewer-types.js +127 -0
- package/dist/elements/image-viewer/ImageViewer.svelte +133 -0
- package/dist/elements/image-viewer/ImageViewer.svelte.d.ts +4 -0
- package/dist/elements/image-viewer/image-viewer-types.d.ts +16 -0
- package/dist/elements/image-viewer/image-viewer-types.js +1 -0
- package/dist/elements/pdf-viewer/PdfViewer.svelte +220 -34
- package/dist/elements/pdf-viewer/pdf-viewer-types.d.ts +13 -4
- package/dist/elements/text-viewer/TextViewer.svelte +141 -0
- package/dist/elements/text-viewer/TextViewer.svelte.d.ts +4 -0
- package/dist/elements/text-viewer/text-viewer-types.d.ts +10 -0
- package/dist/elements/text-viewer/text-viewer-types.js +1 -0
- package/dist/elements/viewer-shared/UnsupportedPreview.svelte +84 -0
- package/dist/elements/viewer-shared/UnsupportedPreview.svelte.d.ts +4 -0
- package/dist/elements/viewer-shared/ViewerToolbar.svelte +128 -0
- package/dist/elements/viewer-shared/ViewerToolbar.svelte.d.ts +4 -0
- package/dist/elements/viewer-shared/unsupported-preview-types.d.ts +17 -0
- package/dist/elements/viewer-shared/unsupported-preview-types.js +1 -0
- package/dist/elements/viewer-shared/viewer-toolbar-types.d.ts +14 -0
- package/dist/elements/viewer-shared/viewer-toolbar-types.js +1 -0
- package/dist/funcs/clerk-requests.d.ts +34 -0
- package/dist/funcs/clerk-requests.js +38 -0
- package/dist/funcs/user-management.remote.js +34 -43
- package/dist/helper/is-safe-http-url.d.ts +33 -0
- package/dist/helper/is-safe-http-url.js +214 -0
- package/dist/helper/pdf-safety.d.ts +38 -0
- package/dist/helper/pdf-safety.js +85 -0
- package/dist/helper/resource-limits.d.ts +13 -0
- package/dist/helper/resource-limits.js +16 -0
- package/dist/helper/sanitize-html.d.ts +5 -0
- package/dist/helper/sanitize-html.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/package.json +4 -2
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL safety helpers for the file-preview viewers. See spec §4 / threat
|
|
3
|
+
* register T0.1, T1.3, T1.4. `isSafeHttpUrl` is a strict allowlist; do not
|
|
4
|
+
* convert it to a denylist.
|
|
5
|
+
*/
|
|
6
|
+
/** Parse a hostname into a 32-bit IPv4 int, or null if it is not an IPv4 form. */
|
|
7
|
+
function toIpv4Int(hostname) {
|
|
8
|
+
const host = hostname.toLowerCase();
|
|
9
|
+
// Dotted forms: a, a.b, a.b.c, a.b.c.d where each part may be dec/hex/octal.
|
|
10
|
+
const parts = host.split('.');
|
|
11
|
+
if (parts.length >= 1 && parts.length <= 4 && parts.every((p) => p.length > 0)) {
|
|
12
|
+
const nums = [];
|
|
13
|
+
for (const part of parts) {
|
|
14
|
+
let n;
|
|
15
|
+
if (/^0x[0-9a-f]+$/.test(part))
|
|
16
|
+
n = parseInt(part, 16);
|
|
17
|
+
else if (/^0[0-7]+$/.test(part))
|
|
18
|
+
n = parseInt(part, 8);
|
|
19
|
+
else if (/^[0-9]+$/.test(part))
|
|
20
|
+
n = parseInt(part, 10);
|
|
21
|
+
else
|
|
22
|
+
return null;
|
|
23
|
+
if (!Number.isFinite(n) || n < 0)
|
|
24
|
+
return null;
|
|
25
|
+
nums.push(n);
|
|
26
|
+
}
|
|
27
|
+
// Single number (e.g. 2130706433) => whole 32-bit address.
|
|
28
|
+
if (nums.length === 1) {
|
|
29
|
+
const n = nums[0];
|
|
30
|
+
return n <= 0xffffffff ? n >>> 0 : null;
|
|
31
|
+
}
|
|
32
|
+
// Last part fills remaining bytes; leading parts must be single bytes.
|
|
33
|
+
const last = nums[nums.length - 1];
|
|
34
|
+
const lead = nums.slice(0, -1);
|
|
35
|
+
if (lead.some((n) => n > 255))
|
|
36
|
+
return null;
|
|
37
|
+
const remaining = 4 - lead.length;
|
|
38
|
+
if (last > 0xffffffff || last >= 256 ** remaining)
|
|
39
|
+
return null;
|
|
40
|
+
let value = 0;
|
|
41
|
+
for (const n of lead)
|
|
42
|
+
value = (value << 8) | n;
|
|
43
|
+
value = (value * 256 ** remaining + last) >>> 0;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function ipv4IntInBlockedRange(ip) {
|
|
49
|
+
const a = (ip >>> 24) & 0xff;
|
|
50
|
+
const b = (ip >>> 16) & 0xff;
|
|
51
|
+
if (a === 0)
|
|
52
|
+
return true; // 0.0.0.0/8
|
|
53
|
+
if (a === 10)
|
|
54
|
+
return true; // 10/8
|
|
55
|
+
if (a === 127)
|
|
56
|
+
return true; // 127/8 loopback
|
|
57
|
+
if (a === 169 && b === 254)
|
|
58
|
+
return true; // 169.254/16 link-local
|
|
59
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
60
|
+
return true; // 172.16/12
|
|
61
|
+
if (a === 192 && b === 168)
|
|
62
|
+
return true; // 192.168/16
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/** True for loopback / link-local / private / metadata / encoded-IP hosts. */
|
|
66
|
+
export function isBlockedHost(hostname) {
|
|
67
|
+
if (!hostname)
|
|
68
|
+
return true;
|
|
69
|
+
let host = hostname.toLowerCase().trim();
|
|
70
|
+
// Strip IPv6 brackets if present.
|
|
71
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
72
|
+
host = host.slice(1, -1);
|
|
73
|
+
if (host === 'localhost' || host.endsWith('.localhost'))
|
|
74
|
+
return true;
|
|
75
|
+
if (host.endsWith('.internal') || host.startsWith('metadata.'))
|
|
76
|
+
return true;
|
|
77
|
+
// IPv6 loopback + unique-local (fc00::/7 => fc.. or fd..) + link-local (fe80).
|
|
78
|
+
if (host === '::1')
|
|
79
|
+
return true;
|
|
80
|
+
if (host.includes(':')) {
|
|
81
|
+
// IPv4-mapped IPv6 (::ffff:127.0.0.1, normalized by URL parser to
|
|
82
|
+
// ::ffff:7f00:1) tunnels a v4 address through a v6 host — unwrap and
|
|
83
|
+
// re-check against the v4 ranges so mapped loopback/private stays blocked.
|
|
84
|
+
const mapped = /^::ffff:(.+)$/.exec(host);
|
|
85
|
+
if (mapped) {
|
|
86
|
+
const tail = mapped[1];
|
|
87
|
+
const dotted = toIpv4Int(tail);
|
|
88
|
+
if (dotted !== null)
|
|
89
|
+
return ipv4IntInBlockedRange(dotted);
|
|
90
|
+
const groups = tail.split(':');
|
|
91
|
+
if (groups.length === 2 && groups.every((g) => /^[0-9a-f]{1,4}$/.test(g))) {
|
|
92
|
+
const v4 = ((parseInt(groups[0], 16) << 16) | parseInt(groups[1], 16)) >>> 0;
|
|
93
|
+
return ipv4IntInBlockedRange(v4);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (host.startsWith('fc') || host.startsWith('fd'))
|
|
97
|
+
return true;
|
|
98
|
+
if (host.startsWith('fe80'))
|
|
99
|
+
return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const ip = toIpv4Int(host);
|
|
103
|
+
if (ip !== null)
|
|
104
|
+
return ipv4IntInBlockedRange(ip);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
// Control chars + space; WHATWG URL strips \t\n\r, so reject before parsing.
|
|
108
|
+
// Matches U+0000–U+0020 (includes tab, newline, CR, space) and U+007F (DEL).
|
|
109
|
+
// eslint-disable-next-line no-control-regex -- detecting control chars is the point
|
|
110
|
+
const CONTROL_OR_SPACE = /[\u0000-\u0020\u007f]/;
|
|
111
|
+
function defaultBase(opts) {
|
|
112
|
+
if (opts.base)
|
|
113
|
+
return opts.base;
|
|
114
|
+
if (typeof document !== 'undefined')
|
|
115
|
+
return document.baseURI;
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
function originOf(value) {
|
|
119
|
+
try {
|
|
120
|
+
return new URL(value).origin;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Strict allowlist: http/https (+ same-origin blob when allowBlob). */
|
|
127
|
+
export function isSafeHttpUrl(url, opts = {}) {
|
|
128
|
+
if (typeof url !== 'string' || url.length === 0)
|
|
129
|
+
return false;
|
|
130
|
+
if (CONTROL_OR_SPACE.test(url))
|
|
131
|
+
return false;
|
|
132
|
+
const base = defaultBase(opts);
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = new URL(url, base);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
if (parsed.username || parsed.password)
|
|
141
|
+
return false;
|
|
142
|
+
const proto = parsed.protocol;
|
|
143
|
+
if (proto === 'blob:') {
|
|
144
|
+
if (!opts.allowBlob)
|
|
145
|
+
return false;
|
|
146
|
+
// blob: origin is embedded in the URL; require it to match the base origin.
|
|
147
|
+
const baseOrigin = base ? originOf(base) : undefined;
|
|
148
|
+
if (!baseOrigin || parsed.origin !== baseOrigin)
|
|
149
|
+
return false;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (proto !== 'http:' && proto !== 'https:')
|
|
153
|
+
return false;
|
|
154
|
+
const baseOrigin = base
|
|
155
|
+
? originOf(base)
|
|
156
|
+
: typeof location !== 'undefined'
|
|
157
|
+
? location.origin
|
|
158
|
+
: undefined;
|
|
159
|
+
if (isBlockedHost(parsed.hostname)) {
|
|
160
|
+
// allowPrivateHosts only waives host-blocking for the base (same-origin)
|
|
161
|
+
// origin or an explicitly allowed origin — never a blanket SSRF bypass to
|
|
162
|
+
// arbitrary loopback/LAN/metadata hosts.
|
|
163
|
+
const privateOk = !!opts.allowPrivateHosts &&
|
|
164
|
+
((!!baseOrigin && parsed.origin === baseOrigin) ||
|
|
165
|
+
(opts.allowedOrigins?.includes(parsed.origin) ?? false));
|
|
166
|
+
if (!privateOk)
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (opts.sameOrigin || opts.allowedOrigins) {
|
|
170
|
+
const allowed = new Set(opts.allowedOrigins ?? []);
|
|
171
|
+
if (opts.sameOrigin && baseOrigin)
|
|
172
|
+
allowed.add(baseOrigin);
|
|
173
|
+
// Fail closed: a same-origin / allowlist requirement with no resolvable
|
|
174
|
+
// allowed origin (e.g. sameOrigin in a non-browser with no base) must not
|
|
175
|
+
// silently permit every origin.
|
|
176
|
+
if (allowed.size === 0)
|
|
177
|
+
return false;
|
|
178
|
+
if (!allowed.has(parsed.origin))
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
/** Strip query string + hash for safe logging (signed-URL tokens live there). */
|
|
184
|
+
export function redactUrl(url, base) {
|
|
185
|
+
try {
|
|
186
|
+
const resolved = base ?? (typeof document !== 'undefined' ? document.baseURI : undefined);
|
|
187
|
+
const u = new URL(url, resolved);
|
|
188
|
+
return u.origin + u.pathname;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return '[unparseable url]';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Schemes that can execute script or read local files — never valid for a link. */
|
|
195
|
+
const DANGEROUS_LINK_SCHEMES = new Set(['javascript:', 'data:', 'vbscript:', 'file:']);
|
|
196
|
+
/**
|
|
197
|
+
* Link-href guard for navigation (`<a href>`). Broader than isSafeHttpUrl:
|
|
198
|
+
* allows http/https/mailto/tel/relative/hash, blocks only executable schemes.
|
|
199
|
+
*/
|
|
200
|
+
export function isSafeLinkHref(href, base) {
|
|
201
|
+
if (typeof href !== 'string' || href.length === 0)
|
|
202
|
+
return false;
|
|
203
|
+
if (CONTROL_OR_SPACE.test(href))
|
|
204
|
+
return false;
|
|
205
|
+
const resolved = base ?? (typeof document !== 'undefined' ? document.baseURI : undefined);
|
|
206
|
+
try {
|
|
207
|
+
const parsed = new URL(href, resolved);
|
|
208
|
+
return !DANGEROUS_LINK_SCHEMES.has(parsed.protocol);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// No usable base + relative href: safe only if it carries no scheme.
|
|
212
|
+
return !/^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Options passed to pdfjs getDocument to neutralise eval/XFA and silence logging. */
|
|
2
|
+
export declare const PDFJS_DOCUMENT_OPTIONS: {
|
|
3
|
+
readonly isEvalSupported: false;
|
|
4
|
+
readonly enableXfa: false;
|
|
5
|
+
readonly verbosity: 0;
|
|
6
|
+
};
|
|
7
|
+
/** True when a pdfjs-dist version is at/above the CVE-2024-4367 fix (4.2.67). */
|
|
8
|
+
export declare function isPdfJsVersionSafe(version: string | undefined): boolean;
|
|
9
|
+
export type CanvasDimensions = {
|
|
10
|
+
cssWidth: number;
|
|
11
|
+
cssHeight: number;
|
|
12
|
+
pixelWidth: number;
|
|
13
|
+
pixelHeight: number;
|
|
14
|
+
effectivePixelRatio: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Compute canvas backing-store + CSS dimensions for a page, clamping the
|
|
18
|
+
* backing store to MAX_CANVAS_SIDE/MAX_CANVAS_AREA so a giant MediaBox can't
|
|
19
|
+
* OOM the tab. CSS (display) size is preserved; only resolution is reduced.
|
|
20
|
+
*/
|
|
21
|
+
export declare function computeCanvasDimensions(baseWidth: number, baseHeight: number, scale: number, rawPixelRatio: number): CanvasDimensions;
|
|
22
|
+
/** Number of pages to eagerly render: min(numPages, maxPages); 0 if invalid. */
|
|
23
|
+
export declare function pagesToRender(numPages: number, maxPages: number): number;
|
|
24
|
+
/** How the viewer auto-sizes a page to its container. `fit-width` spans the
|
|
25
|
+
* available width (scroll vertically); `fit-page` fits the whole first page. */
|
|
26
|
+
export type FitMode = 'fit-width' | 'fit-page';
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a `fit-width`/`fit-page` request into a concrete render scale for the
|
|
29
|
+
* given page and container, clamped to [minScale, maxScale]. `padding` is the
|
|
30
|
+
* total horizontal (and, for `fit-page`, vertical) chrome to subtract from the
|
|
31
|
+
* container before fitting. Invalid geometry falls back to a clamped scale of 1
|
|
32
|
+
* so the caller always gets a usable number.
|
|
33
|
+
*/
|
|
34
|
+
export declare function computeFitScale(pageWidth: number, pageHeight: number, containerWidth: number, containerHeight: number, mode: FitMode, opts: {
|
|
35
|
+
minScale: number;
|
|
36
|
+
maxScale: number;
|
|
37
|
+
padding?: number;
|
|
38
|
+
}): number;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Pure pdfjs hardening helpers — testable without a browser/canvas. See spec §5.2. */
|
|
2
|
+
import { MAX_CANVAS_AREA, MAX_CANVAS_SIDE, clampPixelRatio } from './resource-limits.js';
|
|
3
|
+
/** Options passed to pdfjs getDocument to neutralise eval/XFA and silence logging. */
|
|
4
|
+
export const PDFJS_DOCUMENT_OPTIONS = {
|
|
5
|
+
isEvalSupported: false,
|
|
6
|
+
enableXfa: false,
|
|
7
|
+
verbosity: 0
|
|
8
|
+
};
|
|
9
|
+
/** True when a pdfjs-dist version is at/above the CVE-2024-4367 fix (4.2.67). */
|
|
10
|
+
export function isPdfJsVersionSafe(version) {
|
|
11
|
+
if (!version || typeof version !== 'string')
|
|
12
|
+
return false;
|
|
13
|
+
const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
14
|
+
if (!m)
|
|
15
|
+
return false;
|
|
16
|
+
const major = Number(m[1]);
|
|
17
|
+
const minor = Number(m[2]);
|
|
18
|
+
const patch = Number(m[3]);
|
|
19
|
+
if (major > 4)
|
|
20
|
+
return true;
|
|
21
|
+
if (major < 4)
|
|
22
|
+
return false;
|
|
23
|
+
if (minor > 2)
|
|
24
|
+
return true;
|
|
25
|
+
if (minor < 2)
|
|
26
|
+
return false;
|
|
27
|
+
return patch >= 67;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compute canvas backing-store + CSS dimensions for a page, clamping the
|
|
31
|
+
* backing store to MAX_CANVAS_SIDE/MAX_CANVAS_AREA so a giant MediaBox can't
|
|
32
|
+
* OOM the tab. CSS (display) size is preserved; only resolution is reduced.
|
|
33
|
+
*/
|
|
34
|
+
export function computeCanvasDimensions(baseWidth, baseHeight, scale, rawPixelRatio) {
|
|
35
|
+
const pr = clampPixelRatio(rawPixelRatio);
|
|
36
|
+
const cssWidth = baseWidth * scale;
|
|
37
|
+
const cssHeight = baseHeight * scale;
|
|
38
|
+
let pixelWidth = cssWidth * pr;
|
|
39
|
+
let pixelHeight = cssHeight * pr;
|
|
40
|
+
const sideScale = Math.min(1, MAX_CANVAS_SIDE / pixelWidth, MAX_CANVAS_SIDE / pixelHeight);
|
|
41
|
+
pixelWidth *= sideScale;
|
|
42
|
+
pixelHeight *= sideScale;
|
|
43
|
+
const area = pixelWidth * pixelHeight;
|
|
44
|
+
const areaScale = area > MAX_CANVAS_AREA ? Math.sqrt(MAX_CANVAS_AREA / area) : 1;
|
|
45
|
+
pixelWidth *= areaScale;
|
|
46
|
+
pixelHeight *= areaScale;
|
|
47
|
+
return {
|
|
48
|
+
cssWidth,
|
|
49
|
+
cssHeight,
|
|
50
|
+
pixelWidth: Math.max(1, Math.floor(pixelWidth)),
|
|
51
|
+
pixelHeight: Math.max(1, Math.floor(pixelHeight)),
|
|
52
|
+
effectivePixelRatio: pr * sideScale * areaScale
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Number of pages to eagerly render: min(numPages, maxPages); 0 if invalid. */
|
|
56
|
+
export function pagesToRender(numPages, maxPages) {
|
|
57
|
+
if (!Number.isFinite(numPages) || numPages <= 0)
|
|
58
|
+
return 0;
|
|
59
|
+
return Math.min(numPages, Math.max(0, maxPages));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a `fit-width`/`fit-page` request into a concrete render scale for the
|
|
63
|
+
* given page and container, clamped to [minScale, maxScale]. `padding` is the
|
|
64
|
+
* total horizontal (and, for `fit-page`, vertical) chrome to subtract from the
|
|
65
|
+
* container before fitting. Invalid geometry falls back to a clamped scale of 1
|
|
66
|
+
* so the caller always gets a usable number.
|
|
67
|
+
*/
|
|
68
|
+
export function computeFitScale(pageWidth, pageHeight, containerWidth, containerHeight, mode, opts) {
|
|
69
|
+
const { minScale, maxScale, padding = 0 } = opts;
|
|
70
|
+
const clamp = (value) => Math.min(Math.max(value, minScale), maxScale);
|
|
71
|
+
const finiteAndPositive = (n) => Number.isFinite(n) && n > 0;
|
|
72
|
+
if (!finiteAndPositive(pageWidth) ||
|
|
73
|
+
!finiteAndPositive(pageHeight) ||
|
|
74
|
+
!finiteAndPositive(containerWidth) ||
|
|
75
|
+
!finiteAndPositive(containerHeight)) {
|
|
76
|
+
return clamp(1);
|
|
77
|
+
}
|
|
78
|
+
const availWidth = Math.max(0, containerWidth - padding);
|
|
79
|
+
const widthScale = availWidth / pageWidth;
|
|
80
|
+
if (mode === 'fit-page') {
|
|
81
|
+
const availHeight = Math.max(0, containerHeight - padding);
|
|
82
|
+
return clamp(Math.min(widthScale, availHeight / pageHeight));
|
|
83
|
+
}
|
|
84
|
+
return clamp(widthScale);
|
|
85
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Shared resource caps for the file-preview viewers. See spec §4. */
|
|
2
|
+
/** Max PDF pages PdfViewer will eagerly render before showing a "first N" banner. */
|
|
3
|
+
export declare const MAX_PAGES = 200;
|
|
4
|
+
/** Max canvas area (px²) — 16 MP is safe across Chrome/Safari/Firefox. */
|
|
5
|
+
export declare const MAX_CANVAS_AREA = 16000000;
|
|
6
|
+
/** Max canvas side length (px). */
|
|
7
|
+
export declare const MAX_CANVAS_SIDE = 8192;
|
|
8
|
+
/** Max decoded image pixels (w×h) before falling back to download. */
|
|
9
|
+
export declare const MAX_IMAGE_PIXELS = 40000000;
|
|
10
|
+
/** Max decoded text bytes TextViewer will buffer before truncating. */
|
|
11
|
+
export declare const MAX_TEXT_BYTES: number;
|
|
12
|
+
/** Clamp a devicePixelRatio into [1, 2]; invalid input falls back to 1. */
|
|
13
|
+
export declare function clampPixelRatio(dpr?: number): number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Shared resource caps for the file-preview viewers. See spec §4. */
|
|
2
|
+
/** Max PDF pages PdfViewer will eagerly render before showing a "first N" banner. */
|
|
3
|
+
export const MAX_PAGES = 200;
|
|
4
|
+
/** Max canvas area (px²) — 16 MP is safe across Chrome/Safari/Firefox. */
|
|
5
|
+
export const MAX_CANVAS_AREA = 16_000_000;
|
|
6
|
+
/** Max canvas side length (px). */
|
|
7
|
+
export const MAX_CANVAS_SIDE = 8192;
|
|
8
|
+
/** Max decoded image pixels (w×h) before falling back to download. */
|
|
9
|
+
export const MAX_IMAGE_PIXELS = 40_000_000;
|
|
10
|
+
/** Max decoded text bytes TextViewer will buffer before truncating. */
|
|
11
|
+
export const MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
/** Clamp a devicePixelRatio into [1, 2]; invalid input falls back to 1. */
|
|
13
|
+
export function clampPixelRatio(dpr) {
|
|
14
|
+
const value = typeof dpr === 'number' && Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
|
|
15
|
+
return Math.min(value, 2);
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
/**
|
|
3
|
+
* Sanitise an untrusted HTML string with DOMPurify. Returns '' when no DOM is
|
|
4
|
+
* available (SSR) so unsanitised HTML can never reach the page. See spec §5.9.
|
|
5
|
+
*/
|
|
6
|
+
export function sanitizeHtml(dirty) {
|
|
7
|
+
if (typeof dirty !== 'string')
|
|
8
|
+
return '';
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return '';
|
|
11
|
+
return DOMPurify.sanitize(dirty);
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ export type { SpinnerProps } from './elements/spinner/spinner-types.js';
|
|
|
47
47
|
export type { SkeletonProps, SkeletonVariant } from './elements/skeleton/skeleton-types.js';
|
|
48
48
|
export type { EmptyStateProps, EmptyStateSize } from './elements/empty-state/empty-state-types.js';
|
|
49
49
|
export type { PdfViewerProps } from './elements/pdf-viewer/pdf-viewer-types.js';
|
|
50
|
+
export type { FileViewerProps, FileKindValue } from './elements/file-viewer/file-viewer-types.js';
|
|
51
|
+
export { FileKind, fileKindFromMime, fileKindFromFileName } from './elements/file-viewer/file-viewer-types.js';
|
|
52
|
+
export type { FilePreviewModalProps } from './elements/file-viewer/file-preview-modal-types.js';
|
|
50
53
|
export type { TooltipProps, TooltipPlacement, TooltipSize, TooltipVariant } from './elements/tooltip/tooltip-types.js';
|
|
51
54
|
export type { PopoverProps, PopoverPlacement, PopoverTrigger } from './elements/popover/popover-types.js';
|
|
52
55
|
export type { CalendarProps, CalendarMode } from './forms/calendar/calendar-types.js';
|
|
@@ -65,6 +68,10 @@ export type { ChatMessageType, StreamingCallback, ChatAction, ChatMessage, ChatR
|
|
|
65
68
|
export type { GetUsersOptions, GetUsersResult, UserEmail, UserPhone, User, Permission, Role, UserTableProps, UserModalProps, UserModalSavePayload, UserViewModalProps, UserApproveModalProps, UserManagementAdapter, UserManagementProps, FormErrors } from './user-management/user-management-types.js';
|
|
66
69
|
export { tv, cn } from './helper/cls.js';
|
|
67
70
|
export { isRouteActive } from './helper/nav.svelte.js';
|
|
71
|
+
export * from './helper/resource-limits.js';
|
|
72
|
+
export * from './helper/is-safe-http-url.js';
|
|
73
|
+
export * from './helper/pdf-safety.js';
|
|
74
|
+
export * from './helper/sanitize-html.js';
|
|
68
75
|
export { default as Button } from './button/Button.svelte';
|
|
69
76
|
export { default as Modal } from './modal/Modal.svelte';
|
|
70
77
|
export { default as ConfirmDialog } from './modal/ConfirmDialog.svelte';
|
|
@@ -101,6 +108,8 @@ export { spinner as spinnerVariants } from './elements/spinner/spinner.js';
|
|
|
101
108
|
export { default as EmptyState } from './elements/empty-state/EmptyState.svelte';
|
|
102
109
|
export { emptyState as emptyStateVariants } from './elements/empty-state/empty-state.js';
|
|
103
110
|
export { default as PdfViewer } from './elements/pdf-viewer/PdfViewer.svelte';
|
|
111
|
+
export { default as FileViewer } from './elements/file-viewer/FileViewer.svelte';
|
|
112
|
+
export { default as FilePreviewModal } from './elements/file-viewer/FilePreviewModal.svelte';
|
|
104
113
|
export { default as Tooltip } from './elements/tooltip/Tooltip.svelte';
|
|
105
114
|
export { default as Popover } from './elements/popover/Popover.svelte';
|
|
106
115
|
export { default as ComboBox } from './elements/combobox/ComboBox.svelte';
|
package/dist/index.js
CHANGED
|
@@ -12,12 +12,17 @@ export { buildTestId } from './helper/testid.js';
|
|
|
12
12
|
export { ALL_COUNTRY_CODES, COUNTRY_NAMES } from './forms/market/country-data.js';
|
|
13
13
|
export { Market } from './forms/market/market.js';
|
|
14
14
|
export { countryCodeToFlagEmoji } from './forms/market/flag-emoji.js';
|
|
15
|
+
export { FileKind, fileKindFromMime, fileKindFromFileName } from './elements/file-viewer/file-viewer-types.js';
|
|
15
16
|
export { defaultDatePresets, toIsoDate, fromIsoDate } from './filters/date-presets.js';
|
|
16
17
|
// ============================================================================
|
|
17
18
|
// Helper utilities
|
|
18
19
|
// ============================================================================
|
|
19
20
|
export { tv, cn } from './helper/cls.js';
|
|
20
21
|
export { isRouteActive } from './helper/nav.svelte.js';
|
|
22
|
+
export * from './helper/resource-limits.js';
|
|
23
|
+
export * from './helper/is-safe-http-url.js';
|
|
24
|
+
export * from './helper/pdf-safety.js';
|
|
25
|
+
export * from './helper/sanitize-html.js';
|
|
21
26
|
// ============================================================================
|
|
22
27
|
// Direct Component Exports
|
|
23
28
|
// ============================================================================
|
|
@@ -78,6 +83,9 @@ export { default as EmptyState } from './elements/empty-state/EmptyState.svelte'
|
|
|
78
83
|
export { emptyState as emptyStateVariants } from './elements/empty-state/empty-state.js';
|
|
79
84
|
// Elements - PdfViewer
|
|
80
85
|
export { default as PdfViewer } from './elements/pdf-viewer/PdfViewer.svelte';
|
|
86
|
+
// Elements - File viewers
|
|
87
|
+
export { default as FileViewer } from './elements/file-viewer/FileViewer.svelte';
|
|
88
|
+
export { default as FilePreviewModal } from './elements/file-viewer/FilePreviewModal.svelte';
|
|
81
89
|
// Elements - Tooltip
|
|
82
90
|
export { default as Tooltip } from './elements/tooltip/Tooltip.svelte';
|
|
83
91
|
// Elements - Popover
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makolabs/ripple",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.1",
|
|
4
4
|
"description": "Simple Svelte 5 powered component library ✨",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@sveltejs/kit": "^2.0.0",
|
|
58
|
-
"pdfjs-dist": "^4.
|
|
58
|
+
"pdfjs-dist": "^4.2.67 || ^5.0.0",
|
|
59
59
|
"svelte": "^5.0.0 || ^6.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"@testing-library/jest-dom": "^6.9.1",
|
|
80
80
|
"@testing-library/svelte": "^5.2.4",
|
|
81
81
|
"@types/node": "^22.19.1",
|
|
82
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
82
83
|
"eslint": "^9.39.1",
|
|
83
84
|
"eslint-config-prettier": "^10.0.1",
|
|
84
85
|
"eslint-plugin-storybook": "^10.0.7",
|
|
@@ -147,6 +148,7 @@
|
|
|
147
148
|
"@aws-sdk/s3-request-presigner": "^3.1029.0",
|
|
148
149
|
"@friendofsvelte/mermaid": "^0.0.4",
|
|
149
150
|
"dayjs": "^1.11.19",
|
|
151
|
+
"dompurify": "^3.4.11",
|
|
150
152
|
"echarts": "^6.0.0",
|
|
151
153
|
"marked": "^17.0.0",
|
|
152
154
|
"svelte-highlight": "^7.9.0",
|