@pixagram/sanitizer 0.2.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/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # pixa-content
2
+
3
+ > Secure content processing engine for the PIXA blockchain platform.
4
+ > Rust → WebAssembly module for browser-side rendering of posts, comments, profiles, and metadata.
5
+
6
+ ## Features
7
+
8
+ | Feature | Description |
9
+ |---------|-------------|
10
+ | **Post Rendering** | Markdown/HTML → sanitized HTML with full formatting |
11
+ | **Comment Rendering** | Stricter subset — no headings, tables, iframes |
12
+ | **@Mentions** | `@username` → `<a href="/@username">` with validation |
13
+ | **#Hashtags** | `#tag` → `<a href="/trending/tag">` |
14
+ | **Image Extraction** | Returns image list; render with/without images |
15
+ | **Base64 Images** | Validates `data:image/*` URIs, blocks dangerous MIME types |
16
+ | **External Links** | Marks external links with `data-*` attrs for React dialog |
17
+ | **JSON Sanitizer** | `safeJson` — sanitizes any JSON tree (keys, strings, base64) |
18
+ | **Biography** | HTML → plain text sanitization |
19
+ | **Username** | HIVE-compatible validation (3-16 chars, a-z0-9.-) |
20
+ | **Metadata** | `parseMetadata` — safeJson + known-field extraction |
21
+ | **Plain Text** | Strip all formatting, return clean text |
22
+ | **Summarization** | TF-IDF extractive summarization |
23
+ | **XSS Protection** | Whitelist-based sanitization via ammonia |
24
+
25
+ ## Security Model
26
+
27
+ - **Whitelist-based HTML sanitization** via `ammonia` — only approved tags and attributes pass through
28
+ - **No script execution** — `<script>`, event handlers (`onclick`, `onerror`, etc.), and `javascript:` URIs are all blocked
29
+ - **SVG safety** — Base64 SVGs are decoded and checked for embedded scripts
30
+ - **Link isolation** — External links get `rel="noopener noreferrer"` and `target="_blank"`
31
+ - **Input limits** — Maximum body length, image size, nesting depth, and URL length enforced
32
+ - **Username validation** — Strict HIVE-compatible rules prevent injection via mention links
33
+ - **JSON sanitization** — Every key validated, every string HTML-stripped, embedded JSON-in-strings rejected, dangerous URI schemes blocked, base64 images validated
34
+
35
+ ## Build
36
+
37
+ ### Prerequisites
38
+
39
+ ```bash
40
+ # Rust toolchain
41
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
42
+ rustup target add wasm32-unknown-unknown
43
+
44
+ # wasm-pack
45
+ cargo install wasm-pack
46
+
47
+ # Optional: binaryen for extra ~20-30% size reduction
48
+ npm install -g binaryen
49
+ ```
50
+
51
+ ### Build Commands
52
+
53
+ ```bash
54
+ # Standard release build (optimized for size)
55
+ ./scripts/build.sh release
56
+
57
+ # Smaller build with wee_alloc (trades speed for ~20KB less)
58
+ ./scripts/build.sh release-small
59
+
60
+ # Dev build (fast compile, no optimization)
61
+ ./scripts/build.sh dev
62
+
63
+ # For bundlers (webpack, vite, rollup)
64
+ ./scripts/build.sh bundler
65
+
66
+ # Run all tests
67
+ ./scripts/build.sh test
68
+ ```
69
+
70
+ ### Expected Output Size
71
+
72
+ | Build | Raw WASM | + wasm-opt | Gzip |
73
+ |-------|----------|------------|------|
74
+ | `release` | ~800KB-1.2MB | ~600KB-900KB | ~250KB-350KB |
75
+ | `release-small` | ~750KB-1.1MB | ~550KB-850KB | ~220KB-320KB |
76
+
77
+ > **Why not smaller?** The bulk comes from `ammonia` (HTML sanitizer using `html5ever` parser) and `regex`. These are essential for security. The gzipped transfer size is what matters for users — and **250-350KB gzipped is reasonable** for a full content engine replacing multiple JavaScript libraries.
78
+
79
+ ## Usage
80
+
81
+ ### JavaScript / React
82
+
83
+ ```javascript
84
+ import { PixaContent } from './pixa-content.js';
85
+
86
+ // Initialize once
87
+ const pixa = await PixaContent.init('./pixa_content.js');
88
+
89
+ // ── Render a post ──────────────────────────────
90
+ const result = pixa.sanitizePost(postBody, {
91
+ max_image_count: 0, // 0 = unlimited
92
+ internal_domains: ['pixa.pics', 'custom-domain.com'],
93
+ });
94
+
95
+ console.log(result.html); // Sanitized HTML
96
+ console.log(result.images); // [{ src, alt, is_base64, index }]
97
+ console.log(result.links); // [{ href, text, domain, is_external }]
98
+
99
+ // ── Render a comment ───────────────────────────
100
+ const comment = pixa.sanitizeComment(commentBody);
101
+
102
+ // ── Render a memo ──────────────────────────────
103
+ const memo = pixa.sanitizeMemo(memoBody);
104
+
105
+ // ── Sanitize any JSON (the main entry point) ───
106
+ const safe = pixa.safeJson(rawJsonString);
107
+ // Every key validated, every string HTML-stripped,
108
+ // embedded JSON rejected, base64 images preserved.
109
+
110
+ // ── Sanitize a single string ───────────────────
111
+ const title = pixa.safeString(rawTitle); // '' if unsafe
112
+ const category = pixa.safeString(rawCategory);
113
+
114
+ // ── Parse metadata (convenience wrapper) ───────
115
+ const meta = pixa.parseMetadata(jsonMetadataString);
116
+ console.log(meta.profile); // Sanitized JSON object (whatever was in source)
117
+ console.log(meta.tags); // ['tag1', 'tag2']
118
+ console.log(meta.image); // ['https://...']
119
+ console.log(meta.app); // 'peakd/2024.10.11'
120
+ console.log(meta.extra); // Any unknown fields (already sanitized)
121
+
122
+ // ── Extract plain text ─────────────────────────
123
+ const text = pixa.extractPlainText(postBody);
124
+
125
+ // ── Summarize ──────────────────────────────────
126
+ const summary = pixa.summarizeContent(postBody, 3);
127
+ console.log(summary.summary); // Top 3 sentences joined
128
+ console.log(summary.keywords); // Top keywords with scores
129
+ console.log(summary.sentences); // Scored sentences with positions
130
+
131
+ // ── Sanitize profile data ──────────────────────
132
+ const bio = pixa.sanitizeBiography(rawBio, 256); // max 256 chars
133
+ const name = pixa.sanitizeUsername(rawUsername); // '' if invalid
134
+ ```
135
+
136
+ ### External Link Dialog (React)
137
+
138
+ ```jsx
139
+ import { ExternalLinkDialog } from './ExternalLinkDialog';
140
+
141
+ function PostContent({ html }) {
142
+ return (
143
+ <ExternalLinkDialog
144
+ onNavigate={(href, domain) => {
145
+ console.log('User navigated to:', domain);
146
+ }}
147
+ >
148
+ <div dangerouslySetInnerHTML={{ __html: html }} />
149
+ </ExternalLinkDialog>
150
+ );
151
+ }
152
+ ```
153
+
154
+ Or with a custom dialog:
155
+
156
+ ```jsx
157
+ <ExternalLinkDialog
158
+ renderDialog={({ href, domain, onConfirm, onCancel }) => (
159
+ <YourCustomModal
160
+ title={`Leave Pixa?`}
161
+ message={`Navigate to ${domain}?`}
162
+ onYes={onConfirm}
163
+ onNo={onCancel}
164
+ />
165
+ )}
166
+ >
167
+ <div dangerouslySetInnerHTML={{ __html: html }} />
168
+ </ExternalLinkDialog>
169
+ ```
170
+
171
+ ### Using from Rust (Non-WASM)
172
+
173
+ ```rust
174
+ use pixa_content::{extract_plain_text, sanitize_biography, sanitize_username};
175
+ use pixa_content::sanitizer::{sanitize_post, safe_json, SanitizeOptions};
176
+
177
+ // Sanitize a post
178
+ let opts = SanitizeOptions::default();
179
+ let result = sanitize_post("# Hello @world\n\nCheck #pixelart!", &opts);
180
+ println!("HTML: {}", result.html);
181
+ println!("Images: {:?}", result.images);
182
+ println!("Links: {:?}", result.links);
183
+
184
+ // Sanitize any JSON metadata
185
+ let clean = safe_json(r#"{"tags":["art"],"profile":{"name":"Alice"}}"#).unwrap();
186
+ ```
187
+
188
+ ## Architecture
189
+
190
+ ```
191
+ pixa-content/
192
+ ├── Cargo.toml # Dependencies & WASM optimization config
193
+ ├── src/
194
+ │ ├── lib.rs # Public API & WASM bindings
195
+ │ ├── types.rs # Shared types (ImageInfo, LinkInfo, ParsedMetadata, etc.)
196
+ │ ├── sanitizer.rs # ammonia-based HTML sanitization + safe_json/safe_string/safe_key
197
+ │ ├── mentions.rs # @mention and #hashtag processing
198
+ │ ├── images.rs # Image extraction, base64 validation
199
+ │ ├── links.rs # External link detection & wrapping
200
+ │ ├── text.rs # Plain text extraction, sentence splitting
201
+ │ ├── summarizer.rs # TF-IDF extractive summarization
202
+ │ └── metadata.rs # JSON metadata parsing (thin wrapper over safe_json)
203
+ ├── tests/
204
+ │ └── integration.rs # Full pipeline integration tests
205
+ ├── js/
206
+ │ ├── pixa-content.js # JS wrapper API
207
+ │ └── ExternalLinkDialog.jsx # React external link component
208
+ ├── scripts/
209
+ │ └── build.sh # Build & optimization script
210
+ └── README.md
211
+ ```
212
+
213
+ ## Processing Pipeline
214
+
215
+ ```
216
+ Input (Markdown or HTML)
217
+
218
+ ├──► Detect format (is_predominantly_html)
219
+
220
+ ├──► If Markdown: pulldown-cmark → HTML
221
+
222
+ ├──► Extract images (before sanitization)
223
+
224
+ ├──► Process @mentions and #hashtags
225
+ │ (text nodes only, not inside existing links)
226
+
227
+ ├──► Sanitize HTML (ammonia whitelist)
228
+ │ ├── Post: full formatting
229
+ │ ├── Comment: restricted subset
230
+ │ └── Memo: inline only
231
+
232
+ ├──► Process links (internal vs external)
233
+ │ └── External: add data-* attrs for React
234
+
235
+ ├──► Optionally strip/limit images
236
+
237
+ └──► Return { html, images, links }
238
+ ```
239
+
240
+ ## JSON Sanitization Pipeline
241
+
242
+ ```
243
+ Input (raw JSON string)
244
+
245
+ ├──► Parse JSON
246
+
247
+ ├──► Walk tree recursively:
248
+ │ ├── Keys: safe_key → validate identifier format, drop bad keys
249
+ │ ├── Strings: safe_string →
250
+ │ │ ├── Reject embedded JSON-in-strings
251
+ │ │ ├── data:image/* → validate base64, pass through
252
+ │ │ ├── URLs → strip control chars (preserve & in query strings)
253
+ │ │ ├── Text → strip ALL HTML via ammonia, reject dangerous schemes
254
+ │ │ └── Enforce length limits
255
+ │ ├── Numbers: pass through (must be finite)
256
+ │ ├── Arrays: recurse (max 100 elements)
257
+ │ └── Objects: recurse (max 50 keys, max depth 5)
258
+
259
+ └──► Return sanitized JSON value
260
+ ```
261
+
262
+ ## Metadata Flexibility
263
+
264
+ `parseMetadata` calls `safeJson` first, then extracts known fields for convenience:
265
+
266
+ ```json
267
+ {
268
+ "profile": { "name": "...", "about": "...", "profile_image": "..." },
269
+ "tags": ["tag1", "tag2"],
270
+ "app": "peakd/2024.10.11",
271
+ "format": "markdown",
272
+ "pixa_nft_id": "preserved-in-extra",
273
+ "custom_field": { "also": "preserved" }
274
+ }
275
+ ```
276
+
277
+ All fields are sanitized by `safeJson` before extraction. Unknown fields land in `extra`.
278
+ For full control, use `safeJson` directly — it handles any JSON structure.
279
+
280
+ ## License
281
+
282
+ Proprietary — Pixagram SA. All rights reserved.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@pixagram/sanitizer",
3
+ "type": "module",
4
+ "collaborators": [
5
+ "Pixagram SA"
6
+ ],
7
+ "description": "Secure content processing for PIXA blockchain — Markdown/HTML rendering, sanitization, metadata parsing, summarization",
8
+ "version": "0.2.0",
9
+ "license": "PROPRIETARY",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/nicmusic/pixa-content"
13
+ },
14
+ "files": [
15
+ "pixa_content_bg.wasm",
16
+ "pixa_content.js",
17
+ "pixa_content.d.ts"
18
+ ],
19
+ "main": "pixa_content.js",
20
+ "types": "pixa_content.d.ts",
21
+ "sideEffects": [
22
+ "./snippets/*"
23
+ ]
24
+ }
@@ -0,0 +1,108 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Extract plain text from content.
6
+ */
7
+ export function extractPlainText(body: string): string;
8
+
9
+ /**
10
+ * Parse and sanitize JSON metadata.
11
+ * Internally calls safe_json then extracts known fields.
12
+ */
13
+ export function parseMetadata(json_str: string): any;
14
+
15
+ export function pixaContentInit(): void;
16
+
17
+ /**
18
+ * Sanitize a JSON string. Every key validated, every string HTML-stripped,
19
+ * embedded JSON-in-strings rejected, base64 images allowed if valid.
20
+ *
21
+ * This is the single entry point for ALL metadata sanitization.
22
+ */
23
+ export function safeJson(json_str: string): any;
24
+
25
+ /**
26
+ * Sanitize a single string value. Strips HTML, rejects embedded JSON,
27
+ * rejects dangerous URI schemes, allows validated base64 images.
28
+ *
29
+ * Used by JS pipeline for title, category, and other text fields.
30
+ */
31
+ export function safeString(s: string, max_len: number): string | undefined;
32
+
33
+ /**
34
+ * Sanitize a user biography — returns plain text only (no HTML).
35
+ */
36
+ export function sanitizeBiography(bio: string, max_length: number): string;
37
+
38
+ /**
39
+ * Sanitize a comment body.
40
+ * Everything in memo + lists, blockquotes, code, links.
41
+ * No images, no headings, no tables.
42
+ */
43
+ export function sanitizeComment(body: string, options_json: string): any;
44
+
45
+ /**
46
+ * Sanitize a transaction memo.
47
+ * Bold, italic, @mentions, #hashtags. No images, no lists, no blocks.
48
+ */
49
+ export function sanitizeMemo(body: string, options_json: string): any;
50
+
51
+ /**
52
+ * Sanitize a post body. Full markdown → HTML.
53
+ * Returns sanitized HTML + extracted images (including base64) + links.
54
+ */
55
+ export function sanitizePost(body: string, options_json: string): any;
56
+
57
+ /**
58
+ * Sanitize a username — alphanumeric, dots, hyphens only.
59
+ */
60
+ export function sanitizeUsername(username: string): string;
61
+
62
+ /**
63
+ * TF-IDF summarization.
64
+ */
65
+ export function summarizeContent(body: string, sentence_count: number): any;
66
+
67
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
68
+
69
+ export interface InitOutput {
70
+ readonly memory: WebAssembly.Memory;
71
+ readonly pixaContentInit: () => void;
72
+ readonly sanitizeMemo: (a: number, b: number, c: number, d: number, e: number) => void;
73
+ readonly sanitizeComment: (a: number, b: number, c: number, d: number, e: number) => void;
74
+ readonly sanitizePost: (a: number, b: number, c: number, d: number, e: number) => void;
75
+ readonly safeJson: (a: number, b: number, c: number) => void;
76
+ readonly safeString: (a: number, b: number, c: number, d: number) => void;
77
+ readonly sanitizeBiography: (a: number, b: number, c: number, d: number) => void;
78
+ readonly sanitizeUsername: (a: number, b: number, c: number) => void;
79
+ readonly extractPlainText: (a: number, b: number, c: number) => void;
80
+ readonly summarizeContent: (a: number, b: number, c: number, d: number) => void;
81
+ readonly parseMetadata: (a: number, b: number, c: number) => void;
82
+ readonly __wbindgen_export: (a: number, b: number) => number;
83
+ readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
84
+ readonly __wbindgen_export3: (a: number, b: number, c: number) => void;
85
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
86
+ }
87
+
88
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
89
+
90
+ /**
91
+ * Instantiates the given `module`, which can either be bytes or
92
+ * a precompiled `WebAssembly.Module`.
93
+ *
94
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
95
+ *
96
+ * @returns {InitOutput}
97
+ */
98
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
99
+
100
+ /**
101
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
102
+ * for everything else, calls `WebAssembly.instantiate` directly.
103
+ *
104
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
105
+ *
106
+ * @returns {Promise<InitOutput>}
107
+ */
108
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -0,0 +1,568 @@
1
+ /* @ts-self-types="./pixa_content.d.ts" */
2
+
3
+ /**
4
+ * Extract plain text from content.
5
+ * @param {string} body
6
+ * @returns {string}
7
+ */
8
+ export function extractPlainText(body) {
9
+ let deferred2_0;
10
+ let deferred2_1;
11
+ try {
12
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
13
+ const ptr0 = passStringToWasm0(body, wasm.__wbindgen_export, wasm.__wbindgen_export2);
14
+ const len0 = WASM_VECTOR_LEN;
15
+ wasm.extractPlainText(retptr, ptr0, len0);
16
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
17
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
18
+ deferred2_0 = r0;
19
+ deferred2_1 = r1;
20
+ return getStringFromWasm0(r0, r1);
21
+ } finally {
22
+ wasm.__wbindgen_add_to_stack_pointer(16);
23
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Parse and sanitize JSON metadata.
29
+ * Internally calls safe_json then extracts known fields.
30
+ * @param {string} json_str
31
+ * @returns {any}
32
+ */
33
+ export function parseMetadata(json_str) {
34
+ try {
35
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
36
+ const ptr0 = passStringToWasm0(json_str, wasm.__wbindgen_export, wasm.__wbindgen_export2);
37
+ const len0 = WASM_VECTOR_LEN;
38
+ wasm.parseMetadata(retptr, ptr0, len0);
39
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
40
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
41
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
42
+ if (r2) {
43
+ throw takeObject(r1);
44
+ }
45
+ return takeObject(r0);
46
+ } finally {
47
+ wasm.__wbindgen_add_to_stack_pointer(16);
48
+ }
49
+ }
50
+
51
+ export function pixaContentInit() {
52
+ wasm.pixaContentInit();
53
+ }
54
+
55
+ /**
56
+ * Sanitize a JSON string. Every key validated, every string HTML-stripped,
57
+ * embedded JSON-in-strings rejected, base64 images allowed if valid.
58
+ *
59
+ * This is the single entry point for ALL metadata sanitization.
60
+ * @param {string} json_str
61
+ * @returns {any}
62
+ */
63
+ export function safeJson(json_str) {
64
+ try {
65
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
66
+ const ptr0 = passStringToWasm0(json_str, wasm.__wbindgen_export, wasm.__wbindgen_export2);
67
+ const len0 = WASM_VECTOR_LEN;
68
+ wasm.safeJson(retptr, ptr0, len0);
69
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
70
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
71
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
72
+ if (r2) {
73
+ throw takeObject(r1);
74
+ }
75
+ return takeObject(r0);
76
+ } finally {
77
+ wasm.__wbindgen_add_to_stack_pointer(16);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Sanitize a single string value. Strips HTML, rejects embedded JSON,
83
+ * rejects dangerous URI schemes, allows validated base64 images.
84
+ *
85
+ * Used by JS pipeline for title, category, and other text fields.
86
+ * @param {string} s
87
+ * @param {number} max_len
88
+ * @returns {string | undefined}
89
+ */
90
+ export function safeString(s, max_len) {
91
+ try {
92
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
93
+ const ptr0 = passStringToWasm0(s, wasm.__wbindgen_export, wasm.__wbindgen_export2);
94
+ const len0 = WASM_VECTOR_LEN;
95
+ wasm.safeString(retptr, ptr0, len0, max_len);
96
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
97
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
98
+ let v2;
99
+ if (r0 !== 0) {
100
+ v2 = getStringFromWasm0(r0, r1).slice();
101
+ wasm.__wbindgen_export3(r0, r1 * 1, 1);
102
+ }
103
+ return v2;
104
+ } finally {
105
+ wasm.__wbindgen_add_to_stack_pointer(16);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Sanitize a user biography — returns plain text only (no HTML).
111
+ * @param {string} bio
112
+ * @param {number} max_length
113
+ * @returns {string}
114
+ */
115
+ export function sanitizeBiography(bio, max_length) {
116
+ let deferred2_0;
117
+ let deferred2_1;
118
+ try {
119
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
120
+ const ptr0 = passStringToWasm0(bio, wasm.__wbindgen_export, wasm.__wbindgen_export2);
121
+ const len0 = WASM_VECTOR_LEN;
122
+ wasm.sanitizeBiography(retptr, ptr0, len0, max_length);
123
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
124
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
125
+ deferred2_0 = r0;
126
+ deferred2_1 = r1;
127
+ return getStringFromWasm0(r0, r1);
128
+ } finally {
129
+ wasm.__wbindgen_add_to_stack_pointer(16);
130
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Sanitize a comment body.
136
+ * Everything in memo + lists, blockquotes, code, links.
137
+ * No images, no headings, no tables.
138
+ * @param {string} body
139
+ * @param {string} options_json
140
+ * @returns {any}
141
+ */
142
+ export function sanitizeComment(body, options_json) {
143
+ try {
144
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
145
+ const ptr0 = passStringToWasm0(body, wasm.__wbindgen_export, wasm.__wbindgen_export2);
146
+ const len0 = WASM_VECTOR_LEN;
147
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
148
+ const len1 = WASM_VECTOR_LEN;
149
+ wasm.sanitizeComment(retptr, ptr0, len0, ptr1, len1);
150
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
151
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
152
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
153
+ if (r2) {
154
+ throw takeObject(r1);
155
+ }
156
+ return takeObject(r0);
157
+ } finally {
158
+ wasm.__wbindgen_add_to_stack_pointer(16);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Sanitize a transaction memo.
164
+ * Bold, italic, @mentions, #hashtags. No images, no lists, no blocks.
165
+ * @param {string} body
166
+ * @param {string} options_json
167
+ * @returns {any}
168
+ */
169
+ export function sanitizeMemo(body, options_json) {
170
+ try {
171
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
172
+ const ptr0 = passStringToWasm0(body, wasm.__wbindgen_export, wasm.__wbindgen_export2);
173
+ const len0 = WASM_VECTOR_LEN;
174
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
175
+ const len1 = WASM_VECTOR_LEN;
176
+ wasm.sanitizeMemo(retptr, ptr0, len0, ptr1, len1);
177
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
178
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
179
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
180
+ if (r2) {
181
+ throw takeObject(r1);
182
+ }
183
+ return takeObject(r0);
184
+ } finally {
185
+ wasm.__wbindgen_add_to_stack_pointer(16);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Sanitize a post body. Full markdown → HTML.
191
+ * Returns sanitized HTML + extracted images (including base64) + links.
192
+ * @param {string} body
193
+ * @param {string} options_json
194
+ * @returns {any}
195
+ */
196
+ export function sanitizePost(body, options_json) {
197
+ try {
198
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
199
+ const ptr0 = passStringToWasm0(body, wasm.__wbindgen_export, wasm.__wbindgen_export2);
200
+ const len0 = WASM_VECTOR_LEN;
201
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
202
+ const len1 = WASM_VECTOR_LEN;
203
+ wasm.sanitizePost(retptr, ptr0, len0, ptr1, len1);
204
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
205
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
206
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
207
+ if (r2) {
208
+ throw takeObject(r1);
209
+ }
210
+ return takeObject(r0);
211
+ } finally {
212
+ wasm.__wbindgen_add_to_stack_pointer(16);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Sanitize a username — alphanumeric, dots, hyphens only.
218
+ * @param {string} username
219
+ * @returns {string}
220
+ */
221
+ export function sanitizeUsername(username) {
222
+ let deferred2_0;
223
+ let deferred2_1;
224
+ try {
225
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
226
+ const ptr0 = passStringToWasm0(username, wasm.__wbindgen_export, wasm.__wbindgen_export2);
227
+ const len0 = WASM_VECTOR_LEN;
228
+ wasm.sanitizeUsername(retptr, ptr0, len0);
229
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
230
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
231
+ deferred2_0 = r0;
232
+ deferred2_1 = r1;
233
+ return getStringFromWasm0(r0, r1);
234
+ } finally {
235
+ wasm.__wbindgen_add_to_stack_pointer(16);
236
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * TF-IDF summarization.
242
+ * @param {string} body
243
+ * @param {number} sentence_count
244
+ * @returns {any}
245
+ */
246
+ export function summarizeContent(body, sentence_count) {
247
+ try {
248
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
249
+ const ptr0 = passStringToWasm0(body, wasm.__wbindgen_export, wasm.__wbindgen_export2);
250
+ const len0 = WASM_VECTOR_LEN;
251
+ wasm.summarizeContent(retptr, ptr0, len0, sentence_count);
252
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
253
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
254
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
255
+ if (r2) {
256
+ throw takeObject(r1);
257
+ }
258
+ return takeObject(r0);
259
+ } finally {
260
+ wasm.__wbindgen_add_to_stack_pointer(16);
261
+ }
262
+ }
263
+
264
+ function __wbg_get_imports() {
265
+ const import0 = {
266
+ __proto__: null,
267
+ __wbg_Error_8c4e43fe74559d73: function(arg0, arg1) {
268
+ const ret = Error(getStringFromWasm0(arg0, arg1));
269
+ return addHeapObject(ret);
270
+ },
271
+ __wbg_String_8f0eb39a4a4c2f66: function(arg0, arg1) {
272
+ const ret = String(getObject(arg1));
273
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2);
274
+ const len1 = WASM_VECTOR_LEN;
275
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
276
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
277
+ },
278
+ __wbg___wbindgen_is_string_cd444516edc5b180: function(arg0) {
279
+ const ret = typeof(getObject(arg0)) === 'string';
280
+ return ret;
281
+ },
282
+ __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) {
283
+ throw new Error(getStringFromWasm0(arg0, arg1));
284
+ },
285
+ __wbg_error_7534b8e9a36f1ab4: function(arg0, arg1) {
286
+ let deferred0_0;
287
+ let deferred0_1;
288
+ try {
289
+ deferred0_0 = arg0;
290
+ deferred0_1 = arg1;
291
+ console.error(getStringFromWasm0(arg0, arg1));
292
+ } finally {
293
+ wasm.__wbindgen_export3(deferred0_0, deferred0_1, 1);
294
+ }
295
+ },
296
+ __wbg_new_361308b2356cecd0: function() {
297
+ const ret = new Object();
298
+ return addHeapObject(ret);
299
+ },
300
+ __wbg_new_3eb36ae241fe6f44: function() {
301
+ const ret = new Array();
302
+ return addHeapObject(ret);
303
+ },
304
+ __wbg_new_8a6f238a6ece86ea: function() {
305
+ const ret = new Error();
306
+ return addHeapObject(ret);
307
+ },
308
+ __wbg_new_dca287b076112a51: function() {
309
+ const ret = new Map();
310
+ return addHeapObject(ret);
311
+ },
312
+ __wbg_set_1eb0999cf5d27fc8: function(arg0, arg1, arg2) {
313
+ const ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
314
+ return addHeapObject(ret);
315
+ },
316
+ __wbg_set_3f1d0b984ed272ed: function(arg0, arg1, arg2) {
317
+ getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
318
+ },
319
+ __wbg_set_f43e577aea94465b: function(arg0, arg1, arg2) {
320
+ getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
321
+ },
322
+ __wbg_stack_0ed75d68575b0f3c: function(arg0, arg1) {
323
+ const ret = getObject(arg1).stack;
324
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2);
325
+ const len1 = WASM_VECTOR_LEN;
326
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
327
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
328
+ },
329
+ __wbindgen_cast_0000000000000001: function(arg0) {
330
+ // Cast intrinsic for `F64 -> Externref`.
331
+ const ret = arg0;
332
+ return addHeapObject(ret);
333
+ },
334
+ __wbindgen_cast_0000000000000002: function(arg0) {
335
+ // Cast intrinsic for `I64 -> Externref`.
336
+ const ret = arg0;
337
+ return addHeapObject(ret);
338
+ },
339
+ __wbindgen_cast_0000000000000003: function(arg0, arg1) {
340
+ // Cast intrinsic for `Ref(String) -> Externref`.
341
+ const ret = getStringFromWasm0(arg0, arg1);
342
+ return addHeapObject(ret);
343
+ },
344
+ __wbindgen_cast_0000000000000004: function(arg0) {
345
+ // Cast intrinsic for `U64 -> Externref`.
346
+ const ret = BigInt.asUintN(64, arg0);
347
+ return addHeapObject(ret);
348
+ },
349
+ __wbindgen_object_clone_ref: function(arg0) {
350
+ const ret = getObject(arg0);
351
+ return addHeapObject(ret);
352
+ },
353
+ __wbindgen_object_drop_ref: function(arg0) {
354
+ takeObject(arg0);
355
+ },
356
+ };
357
+ return {
358
+ __proto__: null,
359
+ "./pixa_content_bg.js": import0,
360
+ };
361
+ }
362
+
363
+ function addHeapObject(obj) {
364
+ if (heap_next === heap.length) heap.push(heap.length + 1);
365
+ const idx = heap_next;
366
+ heap_next = heap[idx];
367
+
368
+ heap[idx] = obj;
369
+ return idx;
370
+ }
371
+
372
+ function dropObject(idx) {
373
+ if (idx < 132) return;
374
+ heap[idx] = heap_next;
375
+ heap_next = idx;
376
+ }
377
+
378
+ let cachedDataViewMemory0 = null;
379
+ function getDataViewMemory0() {
380
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
381
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
382
+ }
383
+ return cachedDataViewMemory0;
384
+ }
385
+
386
+ function getStringFromWasm0(ptr, len) {
387
+ ptr = ptr >>> 0;
388
+ return decodeText(ptr, len);
389
+ }
390
+
391
+ let cachedUint8ArrayMemory0 = null;
392
+ function getUint8ArrayMemory0() {
393
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
394
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
395
+ }
396
+ return cachedUint8ArrayMemory0;
397
+ }
398
+
399
+ function getObject(idx) { return heap[idx]; }
400
+
401
+ let heap = new Array(128).fill(undefined);
402
+ heap.push(undefined, null, true, false);
403
+
404
+ let heap_next = heap.length;
405
+
406
+ function passStringToWasm0(arg, malloc, realloc) {
407
+ if (realloc === undefined) {
408
+ const buf = cachedTextEncoder.encode(arg);
409
+ const ptr = malloc(buf.length, 1) >>> 0;
410
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
411
+ WASM_VECTOR_LEN = buf.length;
412
+ return ptr;
413
+ }
414
+
415
+ let len = arg.length;
416
+ let ptr = malloc(len, 1) >>> 0;
417
+
418
+ const mem = getUint8ArrayMemory0();
419
+
420
+ let offset = 0;
421
+
422
+ for (; offset < len; offset++) {
423
+ const code = arg.charCodeAt(offset);
424
+ if (code > 0x7F) break;
425
+ mem[ptr + offset] = code;
426
+ }
427
+ if (offset !== len) {
428
+ if (offset !== 0) {
429
+ arg = arg.slice(offset);
430
+ }
431
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
432
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
433
+ const ret = cachedTextEncoder.encodeInto(arg, view);
434
+
435
+ offset += ret.written;
436
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
437
+ }
438
+
439
+ WASM_VECTOR_LEN = offset;
440
+ return ptr;
441
+ }
442
+
443
+ function takeObject(idx) {
444
+ const ret = getObject(idx);
445
+ dropObject(idx);
446
+ return ret;
447
+ }
448
+
449
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
450
+ cachedTextDecoder.decode();
451
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
452
+ let numBytesDecoded = 0;
453
+ function decodeText(ptr, len) {
454
+ numBytesDecoded += len;
455
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
456
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
457
+ cachedTextDecoder.decode();
458
+ numBytesDecoded = len;
459
+ }
460
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
461
+ }
462
+
463
+ const cachedTextEncoder = new TextEncoder();
464
+
465
+ if (!('encodeInto' in cachedTextEncoder)) {
466
+ cachedTextEncoder.encodeInto = function (arg, view) {
467
+ const buf = cachedTextEncoder.encode(arg);
468
+ view.set(buf);
469
+ return {
470
+ read: arg.length,
471
+ written: buf.length
472
+ };
473
+ };
474
+ }
475
+
476
+ let WASM_VECTOR_LEN = 0;
477
+
478
+ let wasmModule, wasm;
479
+ function __wbg_finalize_init(instance, module) {
480
+ wasm = instance.exports;
481
+ wasmModule = module;
482
+ cachedDataViewMemory0 = null;
483
+ cachedUint8ArrayMemory0 = null;
484
+ return wasm;
485
+ }
486
+
487
+ async function __wbg_load(module, imports) {
488
+ if (typeof Response === 'function' && module instanceof Response) {
489
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
490
+ try {
491
+ return await WebAssembly.instantiateStreaming(module, imports);
492
+ } catch (e) {
493
+ const validResponse = module.ok && expectedResponseType(module.type);
494
+
495
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
496
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
497
+
498
+ } else { throw e; }
499
+ }
500
+ }
501
+
502
+ const bytes = await module.arrayBuffer();
503
+ return await WebAssembly.instantiate(bytes, imports);
504
+ } else {
505
+ const instance = await WebAssembly.instantiate(module, imports);
506
+
507
+ if (instance instanceof WebAssembly.Instance) {
508
+ return { instance, module };
509
+ } else {
510
+ return instance;
511
+ }
512
+ }
513
+
514
+ function expectedResponseType(type) {
515
+ switch (type) {
516
+ case 'basic': case 'cors': case 'default': return true;
517
+ }
518
+ return false;
519
+ }
520
+ }
521
+
522
+ function initSync(module) {
523
+ if (wasm !== undefined) return wasm;
524
+
525
+
526
+ if (module !== undefined) {
527
+ if (Object.getPrototypeOf(module) === Object.prototype) {
528
+ ({module} = module)
529
+ } else {
530
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
531
+ }
532
+ }
533
+
534
+ const imports = __wbg_get_imports();
535
+ if (!(module instanceof WebAssembly.Module)) {
536
+ module = new WebAssembly.Module(module);
537
+ }
538
+ const instance = new WebAssembly.Instance(module, imports);
539
+ return __wbg_finalize_init(instance, module);
540
+ }
541
+
542
+ async function __wbg_init(module_or_path) {
543
+ if (wasm !== undefined) return wasm;
544
+
545
+
546
+ if (module_or_path !== undefined) {
547
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
548
+ ({module_or_path} = module_or_path)
549
+ } else {
550
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
551
+ }
552
+ }
553
+
554
+ if (module_or_path === undefined) {
555
+ module_or_path = new URL('pixa_content_bg.wasm', import.meta.url);
556
+ }
557
+ const imports = __wbg_get_imports();
558
+
559
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
560
+ module_or_path = fetch(module_or_path);
561
+ }
562
+
563
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
564
+
565
+ return __wbg_finalize_init(instance, module);
566
+ }
567
+
568
+ export { initSync, __wbg_init as default };
Binary file