@stream-mdx/core 0.0.0 → 0.0.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/index.cjs +94 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +93 -1
- package/dist/index.mjs.map +1 -1
- package/dist/security.cjs +1 -1
- package/dist/security.cjs.map +1 -1
- package/dist/security.d.cts +1 -1
- package/dist/security.d.ts +1 -1
- package/dist/security.mjs +1 -1
- package/dist/security.mjs.map +1 -1
- package/dist/streaming/custom-matcher.cjs +119 -0
- package/dist/streaming/custom-matcher.cjs.map +1 -0
- package/dist/streaming/custom-matcher.d.cts +22 -0
- package/dist/streaming/custom-matcher.d.ts +22 -0
- package/dist/streaming/custom-matcher.mjs +94 -0
- package/dist/streaming/custom-matcher.mjs.map +1 -0
- package/package.json +10 -2
package/dist/security.cjs
CHANGED
|
@@ -154,7 +154,7 @@ function createTrustedHTML(html) {
|
|
|
154
154
|
if (trustedTypesPolicy) {
|
|
155
155
|
return trustedTypesPolicy.createHTML(html);
|
|
156
156
|
}
|
|
157
|
-
return
|
|
157
|
+
return sanitizeHTML(html);
|
|
158
158
|
}
|
|
159
159
|
function sanitizeHTML(html) {
|
|
160
160
|
const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());
|
package/dist/security.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/security.ts"],"sourcesContent":["// Security layer with sanitization and Trusted Types\n// Handles HTML sanitization for safe rendering\n\nimport createDOMPurify, { type Config as DOMPurifyConfig } from \"dompurify\";\n\ntype DOMPurifyInstance = {\n sanitize: (html: string, config: DOMPurifyConfig) => unknown;\n addHook?: (name: string, hook: (node: unknown) => unknown) => void;\n};\n\nlet domPurifyInstance: DOMPurifyInstance | null = null;\n\nfunction resolveDOMPurify(): DOMPurifyInstance {\n const maybeInstance = createDOMPurify as unknown as DOMPurifyInstance;\n if (maybeInstance && typeof maybeInstance.sanitize === \"function\") {\n return maybeInstance;\n }\n if (domPurifyInstance && typeof domPurifyInstance.sanitize === \"function\") {\n return domPurifyInstance;\n }\n if (typeof window === \"undefined\") {\n throw new Error(\"[markdown-v2] DOMPurify requires a DOM `window` to sanitize HTML. Provide a DOM (e.g. via jsdom) before calling sanitize helpers.\");\n }\n domPurifyInstance = (createDOMPurify as unknown as (win: Window) => DOMPurifyInstance)(window);\n return domPurifyInstance;\n}\n\n/**\n * Trusted Types policy for safe HTML rendering\n */\nlet trustedTypesPolicy: TrustedTypePolicy | undefined;\n\n/**\n * Initialize Trusted Types policy\n */\nexport function initializeTrustedTypesPolicy(): void {\n if (typeof window === \"undefined\" || trustedTypesPolicy) {\n return;\n }\n const trustedWindow = window as typeof window & { trustedTypes?: TrustedTypePolicyFactory };\n const factory = trustedWindow.trustedTypes;\n if (!factory || trustedTypesPolicy) {\n return;\n }\n trustedTypesPolicy = factory.createPolicy(\"markdown-renderer-v2\", {\n createHTML: (input: string) => {\n const out = resolveDOMPurify().sanitize(input, {\n ALLOWED_TAGS: [\n // Block elements\n \"div\",\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"hr\",\n \"br\",\n\n // Inline elements\n \"span\",\n \"strong\",\n \"em\",\n \"a\",\n \"img\",\n\n // Code highlighting\n 'span[class^=\"token\"]',\n\n // Math rendering (KaTeX)\n 'span[class^=\"katex\"]',\n 'span[class^=\"mord\"]',\n 'span[class^=\"mopen\"]',\n 'span[class^=\"mclose\"]',\n 'span[class^=\"mop\"]',\n 'span[class^=\"mbin\"]',\n 'span[class^=\"mrel\"]',\n 'span[class^=\"mpunct\"]',\n 'span[class^=\"minner\"]',\n 'span[class^=\"mspace\"]',\n 'span[class^=\"sizing\"]',\n 'span[class^=\"reset-size\"]',\n \"div[class^='katex-block-wrapper']\",\n\n // Custom components\n \"div[data-component]\",\n \"span[data-component]\",\n ],\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"type\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"style\", // Limited style for math rendering\n ],\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n });\n return typeof out === \"string\" ? out : String(out);\n },\n createScript: (input: string) => input,\n createScriptURL: (input: string) => input,\n });\n}\n\n/**\n * Create trusted HTML using DOMPurify and Trusted Types\n */\nexport function createTrustedHTML(html: string): TrustedHTML | string {\n // Initialize policy if not already done\n initializeTrustedTypesPolicy();\n\n if (trustedTypesPolicy) {\n return trustedTypesPolicy.createHTML(html);\n }\n\n // Fallback to DOMPurify without Trusted Types\n return resolveDOMPurify().sanitize(html, getSanitizationConfig()) as unknown as string | TrustedHTML;\n}\n\n/**\n * Sanitize HTML content for safe rendering\n */\nexport function sanitizeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Get DOMPurify configuration\n */\nfunction getSanitizationConfig(): DOMPurifyConfig {\n return {\n ALLOWED_TAGS: [\n // Standard HTML elements\n \"div\",\n \"span\",\n \"p\",\n \"br\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"strong\",\n \"em\",\n \"u\",\n \"s\",\n \"del\",\n \"ins\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"tr\",\n \"th\",\n \"td\",\n \"a\",\n \"img\",\n\n // Code highlighting (Shiki/Prism classes)\n \"span\",\n \"div\",\n\n // Math rendering (KaTeX)\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n\n // Custom extension points\n \"section\",\n \"article\",\n \"aside\",\n \"nav\",\n \"header\",\n \"footer\",\n \"main\",\n ],\n\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"width\",\n \"height\",\n \"type\",\n \"value\",\n \"placeholder\",\n \"disabled\",\n \"readonly\",\n \"role\",\n \"aria-*\",\n \"style\", // Limited for math/highlighting\n \"target\",\n \"rel\",\n ],\n\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n\n // Remove scripts and potentially dangerous content\n FORBID_TAGS: [\"script\", \"object\", \"embed\", \"form\", \"input\", \"button\"],\n FORBID_ATTR: [\"onerror\", \"onload\", \"onclick\", \"onmouseover\"],\n\n // Keep data attributes for component identification\n KEEP_CONTENT: true,\n\n // Return configuration\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n\n // Additional security\n SANITIZE_DOM: true,\n WHOLE_DOCUMENT: false,\n\n // Hook for custom processing\n SANITIZE_NAMED_PROPS: true,\n\n // Custom hooks\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n attributeNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n allowCustomizedBuiltInElements: false,\n },\n };\n}\n\n/**\n * Sanitization policy for different content types\n */\nexport interface SanitizationPolicy {\n allowMath: boolean;\n allowSyntaxHighlighting: boolean;\n allowCustomComponents: boolean;\n allowInlineStyles: boolean;\n allowExternalLinks: boolean;\n customTags?: string[];\n customAttributes?: string[];\n}\n\n/**\n * Create custom sanitization config based on policy\n */\nexport function createSanitizationConfig(policy: SanitizationPolicy): DOMPurifyConfig {\n const baseConfig = getSanitizationConfig();\n\n // Extend allowed tags based on policy\n const allowedTags = [...(baseConfig.ALLOWED_TAGS || [])];\n const allowedAttr = [...(baseConfig.ALLOWED_ATTR || [])];\n\n if (!policy.allowMath) {\n // Remove math-related tags\n const mathTags = [\"annotation\", \"semantics\", \"mtext\", \"mn\", \"mo\", \"mi\", \"mspace\"];\n allowedTags.splice(0, allowedTags.length, ...allowedTags.filter((tag) => !mathTags.includes(tag)));\n }\n\n if (!policy.allowSyntaxHighlighting) {\n const classIndex = allowedAttr.indexOf(\"class\");\n if (classIndex > -1) {\n allowedAttr.splice(classIndex, 1);\n }\n }\n\n if (!policy.allowInlineStyles) {\n // Remove style attribute\n const styleIndex = allowedAttr.indexOf(\"style\");\n if (styleIndex > -1) {\n allowedAttr.splice(styleIndex, 1);\n }\n }\n\n if (!policy.allowExternalLinks) {\n // Restrict URI pattern to relative links only\n baseConfig.ALLOWED_URI_REGEXP = /^(?:[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i;\n }\n\n // Add custom tags and attributes\n if (policy.customTags) {\n allowedTags.push(...policy.customTags);\n }\n\n if (policy.customAttributes) {\n allowedAttr.push(...policy.customAttributes);\n }\n\n return {\n ...baseConfig,\n ALLOWED_TAGS: allowedTags,\n ALLOWED_ATTR: allowedAttr,\n };\n}\n\n/**\n * Sanitize code block HTML from syntax highlighters\n */\nexport function sanitizeCodeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\"pre\", \"code\", \"span\", \"div\", \"br\"],\n ALLOWED_ATTR: [\"class\", \"data-*\", \"style\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Sanitize math HTML from KaTeX\n */\nexport function sanitizeMathHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\n \"span\",\n \"div\",\n \"br\",\n // KaTeX specific tags\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n ],\n ALLOWED_ATTR: [\"class\", \"style\", \"data-*\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Validate and sanitize URLs\n */\nexport function sanitizeURL(url: string): string | null {\n // Basic URL validation and sanitization\n try {\n const parsed = new URL(url, window.location.origin);\n\n // Allow http, https, mailto, and relative URLs\n if (![\"http:\", \"https:\", \"mailto:\", \"tel:\"].includes(parsed.protocol)) {\n return null;\n }\n\n return parsed.toString();\n } catch {\n // If URL parsing fails, treat as relative\n if (url.startsWith(\"/\") || url.startsWith(\"./\") || url.startsWith(\"../\")) {\n return url;\n }\n return null;\n }\n}\n\n/**\n * Content Security Policy utilities\n */\nexport const CSP_HEADERS = {\n // Strict CSP for markdown rendering\n strict: [\n \"default-src 'self'\",\n \"script-src 'self'\",\n \"style-src 'self' 'unsafe-inline'\", // Required for KaTeX\n \"img-src 'self' data: https:\",\n \"connect-src 'self'\",\n \"font-src 'self' data:\",\n \"require-trusted-types-for 'script'\",\n \"trusted-types markdown-renderer-v2\",\n ].join(\"; \"),\n\n // Relaxed CSP for development\n development: [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-eval'\",\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data: https:\",\n \"connect-src 'self' ws:\",\n \"font-src 'self' data:\",\n ].join(\"; \"),\n};\n\n/**\n * Initialize security features\n */\nexport function initializeSecurity(): void {\n // Initialize Trusted Types\n initializeTrustedTypesPolicy();\n\n // Set up DOMPurify hooks if needed\n try {\n const purifier = resolveDOMPurify();\n if (typeof purifier.addHook !== \"function\") {\n return;\n }\n\n purifier.addHook(\"beforeSanitizeElements\", (node: unknown) => node);\n purifier.addHook(\"afterSanitizeElements\", (node: unknown) => node);\n } catch {\n // ignore missing DOM environment\n }\n}\n"],"mappings":"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","names":["createDOMPurify"]}
|
|
1
|
+
{"version":3,"sources":["../src/security.ts"],"sourcesContent":["// Security layer with sanitization and Trusted Types\n// Handles HTML sanitization for safe rendering\n\nimport createDOMPurify, { type Config as DOMPurifyConfig } from \"dompurify\";\n\ntype DOMPurifyInstance = {\n sanitize: (html: string, config: DOMPurifyConfig) => unknown;\n addHook?: (name: string, hook: (node: unknown) => unknown) => void;\n};\n\ntype TrustedTypePolicyLike = {\n createHTML: (input: string) => string;\n};\n\ntype TrustedTypePolicyFactoryLike = {\n createPolicy: (\n name: string,\n rules: {\n createHTML: (input: string) => string;\n createScript?: (input: string) => string;\n createScriptURL?: (input: string) => string;\n },\n ) => TrustedTypePolicyLike;\n};\n\nlet domPurifyInstance: DOMPurifyInstance | null = null;\n\nfunction resolveDOMPurify(): DOMPurifyInstance {\n const maybeInstance = createDOMPurify as unknown as DOMPurifyInstance;\n if (maybeInstance && typeof maybeInstance.sanitize === \"function\") {\n return maybeInstance;\n }\n if (domPurifyInstance && typeof domPurifyInstance.sanitize === \"function\") {\n return domPurifyInstance;\n }\n if (typeof window === \"undefined\") {\n throw new Error(\"[markdown-v2] DOMPurify requires a DOM `window` to sanitize HTML. Provide a DOM (e.g. via jsdom) before calling sanitize helpers.\");\n }\n domPurifyInstance = (createDOMPurify as unknown as (win: Window) => DOMPurifyInstance)(window);\n return domPurifyInstance;\n}\n\n/**\n * Trusted Types policy for safe HTML rendering\n */\nlet trustedTypesPolicy: TrustedTypePolicyLike | undefined;\n\n/**\n * Initialize Trusted Types policy\n */\nexport function initializeTrustedTypesPolicy(): void {\n if (typeof window === \"undefined\" || trustedTypesPolicy) {\n return;\n }\n const trustedWindow = window as typeof window & { trustedTypes?: TrustedTypePolicyFactoryLike };\n const factory = trustedWindow.trustedTypes;\n if (!factory || trustedTypesPolicy) {\n return;\n }\n trustedTypesPolicy = factory.createPolicy(\"markdown-renderer-v2\", {\n createHTML: (input: string) => {\n const out = resolveDOMPurify().sanitize(input, {\n ALLOWED_TAGS: [\n // Block elements\n \"div\",\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"hr\",\n \"br\",\n\n // Inline elements\n \"span\",\n \"strong\",\n \"em\",\n \"a\",\n \"img\",\n\n // Code highlighting\n 'span[class^=\"token\"]',\n\n // Math rendering (KaTeX)\n 'span[class^=\"katex\"]',\n 'span[class^=\"mord\"]',\n 'span[class^=\"mopen\"]',\n 'span[class^=\"mclose\"]',\n 'span[class^=\"mop\"]',\n 'span[class^=\"mbin\"]',\n 'span[class^=\"mrel\"]',\n 'span[class^=\"mpunct\"]',\n 'span[class^=\"minner\"]',\n 'span[class^=\"mspace\"]',\n 'span[class^=\"sizing\"]',\n 'span[class^=\"reset-size\"]',\n \"div[class^='katex-block-wrapper']\",\n\n // Custom components\n \"div[data-component]\",\n \"span[data-component]\",\n ],\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"type\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"style\", // Limited style for math rendering\n ],\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n });\n return typeof out === \"string\" ? out : String(out);\n },\n createScript: (input: string) => input,\n createScriptURL: (input: string) => input,\n });\n}\n\n/**\n * Create trusted HTML using DOMPurify and Trusted Types\n */\nexport function createTrustedHTML(html: string): string {\n // Initialize policy if not already done\n initializeTrustedTypesPolicy();\n\n if (trustedTypesPolicy) {\n return trustedTypesPolicy.createHTML(html);\n }\n\n // Fallback to DOMPurify without Trusted Types\n return sanitizeHTML(html);\n}\n\n/**\n * Sanitize HTML content for safe rendering\n */\nexport function sanitizeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Get DOMPurify configuration\n */\nfunction getSanitizationConfig(): DOMPurifyConfig {\n return {\n ALLOWED_TAGS: [\n // Standard HTML elements\n \"div\",\n \"span\",\n \"p\",\n \"br\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"strong\",\n \"em\",\n \"u\",\n \"s\",\n \"del\",\n \"ins\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"tr\",\n \"th\",\n \"td\",\n \"a\",\n \"img\",\n\n // Code highlighting (Shiki/Prism classes)\n \"span\",\n \"div\",\n\n // Math rendering (KaTeX)\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n\n // Custom extension points\n \"section\",\n \"article\",\n \"aside\",\n \"nav\",\n \"header\",\n \"footer\",\n \"main\",\n ],\n\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"width\",\n \"height\",\n \"type\",\n \"value\",\n \"placeholder\",\n \"disabled\",\n \"readonly\",\n \"role\",\n \"aria-*\",\n \"style\", // Limited for math/highlighting\n \"target\",\n \"rel\",\n ],\n\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n\n // Remove scripts and potentially dangerous content\n FORBID_TAGS: [\"script\", \"object\", \"embed\", \"form\", \"input\", \"button\"],\n FORBID_ATTR: [\"onerror\", \"onload\", \"onclick\", \"onmouseover\"],\n\n // Keep data attributes for component identification\n KEEP_CONTENT: true,\n\n // Return configuration\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n\n // Additional security\n SANITIZE_DOM: true,\n WHOLE_DOCUMENT: false,\n\n // Hook for custom processing\n SANITIZE_NAMED_PROPS: true,\n\n // Custom hooks\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n attributeNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n allowCustomizedBuiltInElements: false,\n },\n };\n}\n\n/**\n * Sanitization policy for different content types\n */\nexport interface SanitizationPolicy {\n allowMath: boolean;\n allowSyntaxHighlighting: boolean;\n allowCustomComponents: boolean;\n allowInlineStyles: boolean;\n allowExternalLinks: boolean;\n customTags?: string[];\n customAttributes?: string[];\n}\n\n/**\n * Create custom sanitization config based on policy\n */\nexport function createSanitizationConfig(policy: SanitizationPolicy): DOMPurifyConfig {\n const baseConfig = getSanitizationConfig();\n\n // Extend allowed tags based on policy\n const allowedTags = [...(baseConfig.ALLOWED_TAGS || [])];\n const allowedAttr = [...(baseConfig.ALLOWED_ATTR || [])];\n\n if (!policy.allowMath) {\n // Remove math-related tags\n const mathTags = [\"annotation\", \"semantics\", \"mtext\", \"mn\", \"mo\", \"mi\", \"mspace\"];\n allowedTags.splice(0, allowedTags.length, ...allowedTags.filter((tag) => !mathTags.includes(tag)));\n }\n\n if (!policy.allowSyntaxHighlighting) {\n const classIndex = allowedAttr.indexOf(\"class\");\n if (classIndex > -1) {\n allowedAttr.splice(classIndex, 1);\n }\n }\n\n if (!policy.allowInlineStyles) {\n // Remove style attribute\n const styleIndex = allowedAttr.indexOf(\"style\");\n if (styleIndex > -1) {\n allowedAttr.splice(styleIndex, 1);\n }\n }\n\n if (!policy.allowExternalLinks) {\n // Restrict URI pattern to relative links only\n baseConfig.ALLOWED_URI_REGEXP = /^(?:[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i;\n }\n\n // Add custom tags and attributes\n if (policy.customTags) {\n allowedTags.push(...policy.customTags);\n }\n\n if (policy.customAttributes) {\n allowedAttr.push(...policy.customAttributes);\n }\n\n return {\n ...baseConfig,\n ALLOWED_TAGS: allowedTags,\n ALLOWED_ATTR: allowedAttr,\n };\n}\n\n/**\n * Sanitize code block HTML from syntax highlighters\n */\nexport function sanitizeCodeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\"pre\", \"code\", \"span\", \"div\", \"br\"],\n ALLOWED_ATTR: [\"class\", \"data-*\", \"style\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Sanitize math HTML from KaTeX\n */\nexport function sanitizeMathHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\n \"span\",\n \"div\",\n \"br\",\n // KaTeX specific tags\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n ],\n ALLOWED_ATTR: [\"class\", \"style\", \"data-*\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Validate and sanitize URLs\n */\nexport function sanitizeURL(url: string): string | null {\n // Basic URL validation and sanitization\n try {\n const parsed = new URL(url, window.location.origin);\n\n // Allow http, https, mailto, and relative URLs\n if (![\"http:\", \"https:\", \"mailto:\", \"tel:\"].includes(parsed.protocol)) {\n return null;\n }\n\n return parsed.toString();\n } catch {\n // If URL parsing fails, treat as relative\n if (url.startsWith(\"/\") || url.startsWith(\"./\") || url.startsWith(\"../\")) {\n return url;\n }\n return null;\n }\n}\n\n/**\n * Content Security Policy utilities\n */\nexport const CSP_HEADERS = {\n // Strict CSP for markdown rendering\n strict: [\n \"default-src 'self'\",\n \"script-src 'self'\",\n \"style-src 'self' 'unsafe-inline'\", // Required for KaTeX\n \"img-src 'self' data: https:\",\n \"connect-src 'self'\",\n \"font-src 'self' data:\",\n \"require-trusted-types-for 'script'\",\n \"trusted-types markdown-renderer-v2\",\n ].join(\"; \"),\n\n // Relaxed CSP for development\n development: [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-eval'\",\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data: https:\",\n \"connect-src 'self' ws:\",\n \"font-src 'self' data:\",\n ].join(\"; \"),\n};\n\n/**\n * Initialize security features\n */\nexport function initializeSecurity(): void {\n // Initialize Trusted Types\n initializeTrustedTypesPolicy();\n\n // Set up DOMPurify hooks if needed\n try {\n const purifier = resolveDOMPurify();\n if (typeof purifier.addHook !== \"function\") {\n return;\n }\n\n purifier.addHook(\"beforeSanitizeElements\", (node: unknown) => node);\n purifier.addHook(\"afterSanitizeElements\", (node: unknown) => node);\n } catch {\n // ignore missing DOM environment\n }\n}\n"],"mappings":"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","names":["createDOMPurify"]}
|
package/dist/security.d.cts
CHANGED
|
@@ -7,7 +7,7 @@ declare function initializeTrustedTypesPolicy(): void;
|
|
|
7
7
|
/**
|
|
8
8
|
* Create trusted HTML using DOMPurify and Trusted Types
|
|
9
9
|
*/
|
|
10
|
-
declare function createTrustedHTML(html: string):
|
|
10
|
+
declare function createTrustedHTML(html: string): string;
|
|
11
11
|
/**
|
|
12
12
|
* Sanitize HTML content for safe rendering
|
|
13
13
|
*/
|
package/dist/security.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare function initializeTrustedTypesPolicy(): void;
|
|
|
7
7
|
/**
|
|
8
8
|
* Create trusted HTML using DOMPurify and Trusted Types
|
|
9
9
|
*/
|
|
10
|
-
declare function createTrustedHTML(html: string):
|
|
10
|
+
declare function createTrustedHTML(html: string): string;
|
|
11
11
|
/**
|
|
12
12
|
* Sanitize HTML content for safe rendering
|
|
13
13
|
*/
|
package/dist/security.mjs
CHANGED
|
@@ -112,7 +112,7 @@ function createTrustedHTML(html) {
|
|
|
112
112
|
if (trustedTypesPolicy) {
|
|
113
113
|
return trustedTypesPolicy.createHTML(html);
|
|
114
114
|
}
|
|
115
|
-
return
|
|
115
|
+
return sanitizeHTML(html);
|
|
116
116
|
}
|
|
117
117
|
function sanitizeHTML(html) {
|
|
118
118
|
const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());
|
package/dist/security.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/security.ts"],"sourcesContent":["// Security layer with sanitization and Trusted Types\n// Handles HTML sanitization for safe rendering\n\nimport createDOMPurify, { type Config as DOMPurifyConfig } from \"dompurify\";\n\ntype DOMPurifyInstance = {\n sanitize: (html: string, config: DOMPurifyConfig) => unknown;\n addHook?: (name: string, hook: (node: unknown) => unknown) => void;\n};\n\nlet domPurifyInstance: DOMPurifyInstance | null = null;\n\nfunction resolveDOMPurify(): DOMPurifyInstance {\n const maybeInstance = createDOMPurify as unknown as DOMPurifyInstance;\n if (maybeInstance && typeof maybeInstance.sanitize === \"function\") {\n return maybeInstance;\n }\n if (domPurifyInstance && typeof domPurifyInstance.sanitize === \"function\") {\n return domPurifyInstance;\n }\n if (typeof window === \"undefined\") {\n throw new Error(\"[markdown-v2] DOMPurify requires a DOM `window` to sanitize HTML. Provide a DOM (e.g. via jsdom) before calling sanitize helpers.\");\n }\n domPurifyInstance = (createDOMPurify as unknown as (win: Window) => DOMPurifyInstance)(window);\n return domPurifyInstance;\n}\n\n/**\n * Trusted Types policy for safe HTML rendering\n */\nlet trustedTypesPolicy: TrustedTypePolicy | undefined;\n\n/**\n * Initialize Trusted Types policy\n */\nexport function initializeTrustedTypesPolicy(): void {\n if (typeof window === \"undefined\" || trustedTypesPolicy) {\n return;\n }\n const trustedWindow = window as typeof window & { trustedTypes?: TrustedTypePolicyFactory };\n const factory = trustedWindow.trustedTypes;\n if (!factory || trustedTypesPolicy) {\n return;\n }\n trustedTypesPolicy = factory.createPolicy(\"markdown-renderer-v2\", {\n createHTML: (input: string) => {\n const out = resolveDOMPurify().sanitize(input, {\n ALLOWED_TAGS: [\n // Block elements\n \"div\",\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"hr\",\n \"br\",\n\n // Inline elements\n \"span\",\n \"strong\",\n \"em\",\n \"a\",\n \"img\",\n\n // Code highlighting\n 'span[class^=\"token\"]',\n\n // Math rendering (KaTeX)\n 'span[class^=\"katex\"]',\n 'span[class^=\"mord\"]',\n 'span[class^=\"mopen\"]',\n 'span[class^=\"mclose\"]',\n 'span[class^=\"mop\"]',\n 'span[class^=\"mbin\"]',\n 'span[class^=\"mrel\"]',\n 'span[class^=\"mpunct\"]',\n 'span[class^=\"minner\"]',\n 'span[class^=\"mspace\"]',\n 'span[class^=\"sizing\"]',\n 'span[class^=\"reset-size\"]',\n \"div[class^='katex-block-wrapper']\",\n\n // Custom components\n \"div[data-component]\",\n \"span[data-component]\",\n ],\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"type\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"style\", // Limited style for math rendering\n ],\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n });\n return typeof out === \"string\" ? out : String(out);\n },\n createScript: (input: string) => input,\n createScriptURL: (input: string) => input,\n });\n}\n\n/**\n * Create trusted HTML using DOMPurify and Trusted Types\n */\nexport function createTrustedHTML(html: string): TrustedHTML | string {\n // Initialize policy if not already done\n initializeTrustedTypesPolicy();\n\n if (trustedTypesPolicy) {\n return trustedTypesPolicy.createHTML(html);\n }\n\n // Fallback to DOMPurify without Trusted Types\n return resolveDOMPurify().sanitize(html, getSanitizationConfig()) as unknown as string | TrustedHTML;\n}\n\n/**\n * Sanitize HTML content for safe rendering\n */\nexport function sanitizeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Get DOMPurify configuration\n */\nfunction getSanitizationConfig(): DOMPurifyConfig {\n return {\n ALLOWED_TAGS: [\n // Standard HTML elements\n \"div\",\n \"span\",\n \"p\",\n \"br\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"strong\",\n \"em\",\n \"u\",\n \"s\",\n \"del\",\n \"ins\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"tr\",\n \"th\",\n \"td\",\n \"a\",\n \"img\",\n\n // Code highlighting (Shiki/Prism classes)\n \"span\",\n \"div\",\n\n // Math rendering (KaTeX)\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n\n // Custom extension points\n \"section\",\n \"article\",\n \"aside\",\n \"nav\",\n \"header\",\n \"footer\",\n \"main\",\n ],\n\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"width\",\n \"height\",\n \"type\",\n \"value\",\n \"placeholder\",\n \"disabled\",\n \"readonly\",\n \"role\",\n \"aria-*\",\n \"style\", // Limited for math/highlighting\n \"target\",\n \"rel\",\n ],\n\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n\n // Remove scripts and potentially dangerous content\n FORBID_TAGS: [\"script\", \"object\", \"embed\", \"form\", \"input\", \"button\"],\n FORBID_ATTR: [\"onerror\", \"onload\", \"onclick\", \"onmouseover\"],\n\n // Keep data attributes for component identification\n KEEP_CONTENT: true,\n\n // Return configuration\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n\n // Additional security\n SANITIZE_DOM: true,\n WHOLE_DOCUMENT: false,\n\n // Hook for custom processing\n SANITIZE_NAMED_PROPS: true,\n\n // Custom hooks\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n attributeNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n allowCustomizedBuiltInElements: false,\n },\n };\n}\n\n/**\n * Sanitization policy for different content types\n */\nexport interface SanitizationPolicy {\n allowMath: boolean;\n allowSyntaxHighlighting: boolean;\n allowCustomComponents: boolean;\n allowInlineStyles: boolean;\n allowExternalLinks: boolean;\n customTags?: string[];\n customAttributes?: string[];\n}\n\n/**\n * Create custom sanitization config based on policy\n */\nexport function createSanitizationConfig(policy: SanitizationPolicy): DOMPurifyConfig {\n const baseConfig = getSanitizationConfig();\n\n // Extend allowed tags based on policy\n const allowedTags = [...(baseConfig.ALLOWED_TAGS || [])];\n const allowedAttr = [...(baseConfig.ALLOWED_ATTR || [])];\n\n if (!policy.allowMath) {\n // Remove math-related tags\n const mathTags = [\"annotation\", \"semantics\", \"mtext\", \"mn\", \"mo\", \"mi\", \"mspace\"];\n allowedTags.splice(0, allowedTags.length, ...allowedTags.filter((tag) => !mathTags.includes(tag)));\n }\n\n if (!policy.allowSyntaxHighlighting) {\n const classIndex = allowedAttr.indexOf(\"class\");\n if (classIndex > -1) {\n allowedAttr.splice(classIndex, 1);\n }\n }\n\n if (!policy.allowInlineStyles) {\n // Remove style attribute\n const styleIndex = allowedAttr.indexOf(\"style\");\n if (styleIndex > -1) {\n allowedAttr.splice(styleIndex, 1);\n }\n }\n\n if (!policy.allowExternalLinks) {\n // Restrict URI pattern to relative links only\n baseConfig.ALLOWED_URI_REGEXP = /^(?:[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i;\n }\n\n // Add custom tags and attributes\n if (policy.customTags) {\n allowedTags.push(...policy.customTags);\n }\n\n if (policy.customAttributes) {\n allowedAttr.push(...policy.customAttributes);\n }\n\n return {\n ...baseConfig,\n ALLOWED_TAGS: allowedTags,\n ALLOWED_ATTR: allowedAttr,\n };\n}\n\n/**\n * Sanitize code block HTML from syntax highlighters\n */\nexport function sanitizeCodeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\"pre\", \"code\", \"span\", \"div\", \"br\"],\n ALLOWED_ATTR: [\"class\", \"data-*\", \"style\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Sanitize math HTML from KaTeX\n */\nexport function sanitizeMathHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\n \"span\",\n \"div\",\n \"br\",\n // KaTeX specific tags\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n ],\n ALLOWED_ATTR: [\"class\", \"style\", \"data-*\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Validate and sanitize URLs\n */\nexport function sanitizeURL(url: string): string | null {\n // Basic URL validation and sanitization\n try {\n const parsed = new URL(url, window.location.origin);\n\n // Allow http, https, mailto, and relative URLs\n if (![\"http:\", \"https:\", \"mailto:\", \"tel:\"].includes(parsed.protocol)) {\n return null;\n }\n\n return parsed.toString();\n } catch {\n // If URL parsing fails, treat as relative\n if (url.startsWith(\"/\") || url.startsWith(\"./\") || url.startsWith(\"../\")) {\n return url;\n }\n return null;\n }\n}\n\n/**\n * Content Security Policy utilities\n */\nexport const CSP_HEADERS = {\n // Strict CSP for markdown rendering\n strict: [\n \"default-src 'self'\",\n \"script-src 'self'\",\n \"style-src 'self' 'unsafe-inline'\", // Required for KaTeX\n \"img-src 'self' data: https:\",\n \"connect-src 'self'\",\n \"font-src 'self' data:\",\n \"require-trusted-types-for 'script'\",\n \"trusted-types markdown-renderer-v2\",\n ].join(\"; \"),\n\n // Relaxed CSP for development\n development: [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-eval'\",\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data: https:\",\n \"connect-src 'self' ws:\",\n \"font-src 'self' data:\",\n ].join(\"; \"),\n};\n\n/**\n * Initialize security features\n */\nexport function initializeSecurity(): void {\n // Initialize Trusted Types\n initializeTrustedTypesPolicy();\n\n // Set up DOMPurify hooks if needed\n try {\n const purifier = resolveDOMPurify();\n if (typeof purifier.addHook !== \"function\") {\n return;\n }\n\n purifier.addHook(\"beforeSanitizeElements\", (node: unknown) => node);\n purifier.addHook(\"afterSanitizeElements\", (node: unknown) => node);\n } catch {\n // ignore missing DOM environment\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/security.ts"],"sourcesContent":["// Security layer with sanitization and Trusted Types\n// Handles HTML sanitization for safe rendering\n\nimport createDOMPurify, { type Config as DOMPurifyConfig } from \"dompurify\";\n\ntype DOMPurifyInstance = {\n sanitize: (html: string, config: DOMPurifyConfig) => unknown;\n addHook?: (name: string, hook: (node: unknown) => unknown) => void;\n};\n\ntype TrustedTypePolicyLike = {\n createHTML: (input: string) => string;\n};\n\ntype TrustedTypePolicyFactoryLike = {\n createPolicy: (\n name: string,\n rules: {\n createHTML: (input: string) => string;\n createScript?: (input: string) => string;\n createScriptURL?: (input: string) => string;\n },\n ) => TrustedTypePolicyLike;\n};\n\nlet domPurifyInstance: DOMPurifyInstance | null = null;\n\nfunction resolveDOMPurify(): DOMPurifyInstance {\n const maybeInstance = createDOMPurify as unknown as DOMPurifyInstance;\n if (maybeInstance && typeof maybeInstance.sanitize === \"function\") {\n return maybeInstance;\n }\n if (domPurifyInstance && typeof domPurifyInstance.sanitize === \"function\") {\n return domPurifyInstance;\n }\n if (typeof window === \"undefined\") {\n throw new Error(\"[markdown-v2] DOMPurify requires a DOM `window` to sanitize HTML. Provide a DOM (e.g. via jsdom) before calling sanitize helpers.\");\n }\n domPurifyInstance = (createDOMPurify as unknown as (win: Window) => DOMPurifyInstance)(window);\n return domPurifyInstance;\n}\n\n/**\n * Trusted Types policy for safe HTML rendering\n */\nlet trustedTypesPolicy: TrustedTypePolicyLike | undefined;\n\n/**\n * Initialize Trusted Types policy\n */\nexport function initializeTrustedTypesPolicy(): void {\n if (typeof window === \"undefined\" || trustedTypesPolicy) {\n return;\n }\n const trustedWindow = window as typeof window & { trustedTypes?: TrustedTypePolicyFactoryLike };\n const factory = trustedWindow.trustedTypes;\n if (!factory || trustedTypesPolicy) {\n return;\n }\n trustedTypesPolicy = factory.createPolicy(\"markdown-renderer-v2\", {\n createHTML: (input: string) => {\n const out = resolveDOMPurify().sanitize(input, {\n ALLOWED_TAGS: [\n // Block elements\n \"div\",\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tr\",\n \"th\",\n \"td\",\n \"hr\",\n \"br\",\n\n // Inline elements\n \"span\",\n \"strong\",\n \"em\",\n \"a\",\n \"img\",\n\n // Code highlighting\n 'span[class^=\"token\"]',\n\n // Math rendering (KaTeX)\n 'span[class^=\"katex\"]',\n 'span[class^=\"mord\"]',\n 'span[class^=\"mopen\"]',\n 'span[class^=\"mclose\"]',\n 'span[class^=\"mop\"]',\n 'span[class^=\"mbin\"]',\n 'span[class^=\"mrel\"]',\n 'span[class^=\"mpunct\"]',\n 'span[class^=\"minner\"]',\n 'span[class^=\"mspace\"]',\n 'span[class^=\"sizing\"]',\n 'span[class^=\"reset-size\"]',\n \"div[class^='katex-block-wrapper']\",\n\n // Custom components\n \"div[data-component]\",\n \"span[data-component]\",\n ],\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"type\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"style\", // Limited style for math rendering\n ],\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n });\n return typeof out === \"string\" ? out : String(out);\n },\n createScript: (input: string) => input,\n createScriptURL: (input: string) => input,\n });\n}\n\n/**\n * Create trusted HTML using DOMPurify and Trusted Types\n */\nexport function createTrustedHTML(html: string): string {\n // Initialize policy if not already done\n initializeTrustedTypesPolicy();\n\n if (trustedTypesPolicy) {\n return trustedTypesPolicy.createHTML(html);\n }\n\n // Fallback to DOMPurify without Trusted Types\n return sanitizeHTML(html);\n}\n\n/**\n * Sanitize HTML content for safe rendering\n */\nexport function sanitizeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, getSanitizationConfig());\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Get DOMPurify configuration\n */\nfunction getSanitizationConfig(): DOMPurifyConfig {\n return {\n ALLOWED_TAGS: [\n // Standard HTML elements\n \"div\",\n \"span\",\n \"p\",\n \"br\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"strong\",\n \"em\",\n \"u\",\n \"s\",\n \"del\",\n \"ins\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"ul\",\n \"ol\",\n \"li\",\n \"table\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"tr\",\n \"th\",\n \"td\",\n \"a\",\n \"img\",\n\n // Code highlighting (Shiki/Prism classes)\n \"span\",\n \"div\",\n\n // Math rendering (KaTeX)\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n\n // Custom extension points\n \"section\",\n \"article\",\n \"aside\",\n \"nav\",\n \"header\",\n \"footer\",\n \"main\",\n ],\n\n ALLOWED_ATTR: [\n \"class\",\n \"id\",\n \"data-*\",\n \"href\",\n \"src\",\n \"alt\",\n \"title\",\n \"width\",\n \"height\",\n \"type\",\n \"value\",\n \"placeholder\",\n \"disabled\",\n \"readonly\",\n \"role\",\n \"aria-*\",\n \"style\", // Limited for math/highlighting\n \"target\",\n \"rel\",\n ],\n\n ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i,\n\n // Remove scripts and potentially dangerous content\n FORBID_TAGS: [\"script\", \"object\", \"embed\", \"form\", \"input\", \"button\"],\n FORBID_ATTR: [\"onerror\", \"onload\", \"onclick\", \"onmouseover\"],\n\n // Keep data attributes for component identification\n KEEP_CONTENT: true,\n\n // Return configuration\n RETURN_DOM: false,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n\n // Additional security\n SANITIZE_DOM: true,\n WHOLE_DOCUMENT: false,\n\n // Hook for custom processing\n SANITIZE_NAMED_PROPS: true,\n\n // Custom hooks\n CUSTOM_ELEMENT_HANDLING: {\n tagNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n attributeNameCheck: /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,\n allowCustomizedBuiltInElements: false,\n },\n };\n}\n\n/**\n * Sanitization policy for different content types\n */\nexport interface SanitizationPolicy {\n allowMath: boolean;\n allowSyntaxHighlighting: boolean;\n allowCustomComponents: boolean;\n allowInlineStyles: boolean;\n allowExternalLinks: boolean;\n customTags?: string[];\n customAttributes?: string[];\n}\n\n/**\n * Create custom sanitization config based on policy\n */\nexport function createSanitizationConfig(policy: SanitizationPolicy): DOMPurifyConfig {\n const baseConfig = getSanitizationConfig();\n\n // Extend allowed tags based on policy\n const allowedTags = [...(baseConfig.ALLOWED_TAGS || [])];\n const allowedAttr = [...(baseConfig.ALLOWED_ATTR || [])];\n\n if (!policy.allowMath) {\n // Remove math-related tags\n const mathTags = [\"annotation\", \"semantics\", \"mtext\", \"mn\", \"mo\", \"mi\", \"mspace\"];\n allowedTags.splice(0, allowedTags.length, ...allowedTags.filter((tag) => !mathTags.includes(tag)));\n }\n\n if (!policy.allowSyntaxHighlighting) {\n const classIndex = allowedAttr.indexOf(\"class\");\n if (classIndex > -1) {\n allowedAttr.splice(classIndex, 1);\n }\n }\n\n if (!policy.allowInlineStyles) {\n // Remove style attribute\n const styleIndex = allowedAttr.indexOf(\"style\");\n if (styleIndex > -1) {\n allowedAttr.splice(styleIndex, 1);\n }\n }\n\n if (!policy.allowExternalLinks) {\n // Restrict URI pattern to relative links only\n baseConfig.ALLOWED_URI_REGEXP = /^(?:[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i;\n }\n\n // Add custom tags and attributes\n if (policy.customTags) {\n allowedTags.push(...policy.customTags);\n }\n\n if (policy.customAttributes) {\n allowedAttr.push(...policy.customAttributes);\n }\n\n return {\n ...baseConfig,\n ALLOWED_TAGS: allowedTags,\n ALLOWED_ATTR: allowedAttr,\n };\n}\n\n/**\n * Sanitize code block HTML from syntax highlighters\n */\nexport function sanitizeCodeHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\"pre\", \"code\", \"span\", \"div\", \"br\"],\n ALLOWED_ATTR: [\"class\", \"data-*\", \"style\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Sanitize math HTML from KaTeX\n */\nexport function sanitizeMathHTML(html: string): string {\n const out = resolveDOMPurify().sanitize(html, {\n ALLOWED_TAGS: [\n \"span\",\n \"div\",\n \"br\",\n // KaTeX specific tags\n \"annotation\",\n \"semantics\",\n \"mtext\",\n \"mn\",\n \"mo\",\n \"mi\",\n \"mspace\",\n \"mrow\",\n \"mfrac\",\n \"msup\",\n \"msub\",\n \"msubsup\",\n \"munder\",\n \"mover\",\n \"munderover\",\n \"msqrt\",\n \"mroot\",\n \"mtable\",\n \"mtr\",\n \"mtd\",\n ],\n ALLOWED_ATTR: [\"class\", \"style\", \"data-*\"],\n KEEP_CONTENT: true,\n RETURN_DOM: false,\n });\n return typeof out === \"string\" ? out : String(out);\n}\n\n/**\n * Validate and sanitize URLs\n */\nexport function sanitizeURL(url: string): string | null {\n // Basic URL validation and sanitization\n try {\n const parsed = new URL(url, window.location.origin);\n\n // Allow http, https, mailto, and relative URLs\n if (![\"http:\", \"https:\", \"mailto:\", \"tel:\"].includes(parsed.protocol)) {\n return null;\n }\n\n return parsed.toString();\n } catch {\n // If URL parsing fails, treat as relative\n if (url.startsWith(\"/\") || url.startsWith(\"./\") || url.startsWith(\"../\")) {\n return url;\n }\n return null;\n }\n}\n\n/**\n * Content Security Policy utilities\n */\nexport const CSP_HEADERS = {\n // Strict CSP for markdown rendering\n strict: [\n \"default-src 'self'\",\n \"script-src 'self'\",\n \"style-src 'self' 'unsafe-inline'\", // Required for KaTeX\n \"img-src 'self' data: https:\",\n \"connect-src 'self'\",\n \"font-src 'self' data:\",\n \"require-trusted-types-for 'script'\",\n \"trusted-types markdown-renderer-v2\",\n ].join(\"; \"),\n\n // Relaxed CSP for development\n development: [\n \"default-src 'self'\",\n \"script-src 'self' 'unsafe-eval'\",\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data: https:\",\n \"connect-src 'self' ws:\",\n \"font-src 'self' data:\",\n ].join(\"; \"),\n};\n\n/**\n * Initialize security features\n */\nexport function initializeSecurity(): void {\n // Initialize Trusted Types\n initializeTrustedTypesPolicy();\n\n // Set up DOMPurify hooks if needed\n try {\n const purifier = resolveDOMPurify();\n if (typeof purifier.addHook !== \"function\") {\n return;\n }\n\n purifier.addHook(\"beforeSanitizeElements\", (node: unknown) => node);\n purifier.addHook(\"afterSanitizeElements\", (node: unknown) => node);\n } catch {\n // ignore missing DOM environment\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/streaming/custom-matcher.ts
|
|
21
|
+
var custom_matcher_exports = {};
|
|
22
|
+
__export(custom_matcher_exports, {
|
|
23
|
+
CustomStreamingMatcher: () => CustomStreamingMatcher
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(custom_matcher_exports);
|
|
26
|
+
var CustomStreamingMatcher = class {
|
|
27
|
+
constructor(pattern) {
|
|
28
|
+
this.buffer = "";
|
|
29
|
+
this.possibleMatches = [];
|
|
30
|
+
this.pattern = pattern;
|
|
31
|
+
}
|
|
32
|
+
addCharacter(char) {
|
|
33
|
+
this.buffer += char;
|
|
34
|
+
const fullMatch = this.buffer.match(this.pattern);
|
|
35
|
+
if (fullMatch && fullMatch.index === 0) {
|
|
36
|
+
const match = {
|
|
37
|
+
matched: true,
|
|
38
|
+
content: fullMatch[0],
|
|
39
|
+
length: fullMatch[0].length,
|
|
40
|
+
isComplete: true
|
|
41
|
+
};
|
|
42
|
+
this.buffer = this.buffer.slice(fullMatch[0].length);
|
|
43
|
+
this.possibleMatches = [];
|
|
44
|
+
return match;
|
|
45
|
+
}
|
|
46
|
+
const confidence = this.calculatePartialConfidence();
|
|
47
|
+
return {
|
|
48
|
+
matched: false,
|
|
49
|
+
content: this.buffer,
|
|
50
|
+
length: this.buffer.length,
|
|
51
|
+
isComplete: false,
|
|
52
|
+
confidence
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
addString(str) {
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const char of str) {
|
|
58
|
+
const result = this.addCharacter(char);
|
|
59
|
+
if (result.matched) {
|
|
60
|
+
results.push(result);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (results.length === 0 && this.buffer.length > 0) {
|
|
64
|
+
results.push({
|
|
65
|
+
matched: false,
|
|
66
|
+
content: this.buffer,
|
|
67
|
+
length: this.buffer.length,
|
|
68
|
+
isComplete: false,
|
|
69
|
+
confidence: this.calculatePartialConfidence()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
couldMatch() {
|
|
75
|
+
if (this.buffer.length === 0) return true;
|
|
76
|
+
const patternSource = this.pattern.source;
|
|
77
|
+
const flags = this.pattern.flags;
|
|
78
|
+
try {
|
|
79
|
+
const partialPattern = new RegExp(`^${patternSource.replace(/\\$$/, "")}`, flags);
|
|
80
|
+
const testString = this.buffer + "X".repeat(100);
|
|
81
|
+
return partialPattern.test(testString);
|
|
82
|
+
} catch {
|
|
83
|
+
return this.isValidPrefix();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
reset() {
|
|
87
|
+
this.buffer = "";
|
|
88
|
+
this.possibleMatches = [];
|
|
89
|
+
}
|
|
90
|
+
getBuffer() {
|
|
91
|
+
return this.buffer;
|
|
92
|
+
}
|
|
93
|
+
calculatePartialConfidence() {
|
|
94
|
+
if (this.buffer.length === 0) return 0;
|
|
95
|
+
let confidence = 0.1;
|
|
96
|
+
confidence += Math.min(0.4, this.buffer.length * 0.1);
|
|
97
|
+
if (this.couldMatch()) {
|
|
98
|
+
confidence += 0.3;
|
|
99
|
+
}
|
|
100
|
+
if (this.buffer.startsWith("$")) confidence += 0.2;
|
|
101
|
+
if (this.buffer.startsWith("$$")) confidence += 0.3;
|
|
102
|
+
return Math.min(1, confidence);
|
|
103
|
+
}
|
|
104
|
+
isValidPrefix() {
|
|
105
|
+
const patternStr = this.pattern.source;
|
|
106
|
+
if (patternStr.includes("\\$\\$") && (this.buffer === "$" || this.buffer === "$$")) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (patternStr.includes("\\$") && this.buffer === "$") {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
116
|
+
0 && (module.exports = {
|
|
117
|
+
CustomStreamingMatcher
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=custom-matcher.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/custom-matcher.ts"],"sourcesContent":["// Custom streaming regex matcher to replace problematic incr-regex-package\n\nexport class CustomStreamingMatcher {\n private pattern: RegExp;\n private buffer = \"\";\n private possibleMatches: PossibleMatch[] = [];\n\n constructor(pattern: RegExp) {\n this.pattern = pattern;\n }\n\n addCharacter(char: string): MatchResult {\n this.buffer += char;\n\n const fullMatch = this.buffer.match(this.pattern);\n if (fullMatch && fullMatch.index === 0) {\n const match = {\n matched: true,\n content: fullMatch[0],\n length: fullMatch[0].length,\n isComplete: true,\n };\n\n this.buffer = this.buffer.slice(fullMatch[0].length);\n this.possibleMatches = [];\n\n return match;\n }\n\n const confidence = this.calculatePartialConfidence();\n\n return {\n matched: false,\n content: this.buffer,\n length: this.buffer.length,\n isComplete: false,\n confidence,\n };\n }\n\n addString(str: string): MatchResult[] {\n const results: MatchResult[] = [];\n\n for (const char of str) {\n const result = this.addCharacter(char);\n if (result.matched) {\n results.push(result);\n }\n }\n\n if (results.length === 0 && this.buffer.length > 0) {\n results.push({\n matched: false,\n content: this.buffer,\n length: this.buffer.length,\n isComplete: false,\n confidence: this.calculatePartialConfidence(),\n });\n }\n\n return results;\n }\n\n couldMatch(): boolean {\n if (this.buffer.length === 0) return true;\n\n const patternSource = this.pattern.source;\n const flags = this.pattern.flags;\n\n try {\n const partialPattern = new RegExp(`^${patternSource.replace(/\\\\$$/, \"\")}`, flags);\n const testString = this.buffer + \"X\".repeat(100);\n return partialPattern.test(testString);\n } catch {\n return this.isValidPrefix();\n }\n }\n\n reset(): void {\n this.buffer = \"\";\n this.possibleMatches = [];\n }\n\n getBuffer(): string {\n return this.buffer;\n }\n\n private calculatePartialConfidence(): number {\n if (this.buffer.length === 0) return 0;\n\n let confidence = 0.1;\n confidence += Math.min(0.4, this.buffer.length * 0.1);\n\n if (this.couldMatch()) {\n confidence += 0.3;\n }\n\n if (this.buffer.startsWith(\"$\")) confidence += 0.2;\n if (this.buffer.startsWith(\"$$\")) confidence += 0.3;\n\n return Math.min(1, confidence);\n }\n\n private isValidPrefix(): boolean {\n const patternStr = this.pattern.source;\n\n if (patternStr.includes(\"\\\\$\\\\$\") && (this.buffer === \"$\" || this.buffer === \"$$\")) {\n return true;\n }\n\n if (patternStr.includes(\"\\\\$\") && this.buffer === \"$\") {\n return true;\n }\n\n return false;\n }\n}\n\nexport interface MatchResult {\n matched: boolean;\n content: string;\n length: number;\n isComplete: boolean;\n confidence?: number;\n}\n\ninterface PossibleMatch {\n startIndex: number;\n pattern: RegExp;\n confidence: number;\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,yBAAN,MAA6B;AAAA,EAKlC,YAAY,SAAiB;AAH7B,SAAQ,SAAS;AACjB,SAAQ,kBAAmC,CAAC;AAG1C,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,aAAa,MAA2B;AACtC,SAAK,UAAU;AAEf,UAAM,YAAY,KAAK,OAAO,MAAM,KAAK,OAAO;AAChD,QAAI,aAAa,UAAU,UAAU,GAAG;AACtC,YAAM,QAAQ;AAAA,QACZ,SAAS;AAAA,QACT,SAAS,UAAU,CAAC;AAAA,QACpB,QAAQ,UAAU,CAAC,EAAE;AAAA,QACrB,YAAY;AAAA,MACd;AAEA,WAAK,SAAS,KAAK,OAAO,MAAM,UAAU,CAAC,EAAE,MAAM;AACnD,WAAK,kBAAkB,CAAC;AAExB,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,2BAA2B;AAEnD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK,OAAO;AAAA,MACpB,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,KAA4B;AACpC,UAAM,UAAyB,CAAC;AAEhC,eAAW,QAAQ,KAAK;AACtB,YAAM,SAAS,KAAK,aAAa,IAAI;AACrC,UAAI,OAAO,SAAS;AAClB,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,KAAK,KAAK,OAAO,SAAS,GAAG;AAClD,cAAQ,KAAK;AAAA,QACX,SAAS;AAAA,QACT,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,OAAO;AAAA,QACpB,YAAY;AAAA,QACZ,YAAY,KAAK,2BAA2B;AAAA,MAC9C,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,aAAsB;AACpB,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AAErC,UAAM,gBAAgB,KAAK,QAAQ;AACnC,UAAM,QAAQ,KAAK,QAAQ;AAE3B,QAAI;AACF,YAAM,iBAAiB,IAAI,OAAO,IAAI,cAAc,QAAQ,QAAQ,EAAE,CAAC,IAAI,KAAK;AAChF,YAAM,aAAa,KAAK,SAAS,IAAI,OAAO,GAAG;AAC/C,aAAO,eAAe,KAAK,UAAU;AAAA,IACvC,QAAQ;AACN,aAAO,KAAK,cAAc;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS;AACd,SAAK,kBAAkB,CAAC;AAAA,EAC1B;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,6BAAqC;AAC3C,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AAErC,QAAI,aAAa;AACjB,kBAAc,KAAK,IAAI,KAAK,KAAK,OAAO,SAAS,GAAG;AAEpD,QAAI,KAAK,WAAW,GAAG;AACrB,oBAAc;AAAA,IAChB;AAEA,QAAI,KAAK,OAAO,WAAW,GAAG,EAAG,eAAc;AAC/C,QAAI,KAAK,OAAO,WAAW,IAAI,EAAG,eAAc;AAEhD,WAAO,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AAAA,EAEQ,gBAAyB;AAC/B,UAAM,aAAa,KAAK,QAAQ;AAEhC,QAAI,WAAW,SAAS,QAAQ,MAAM,KAAK,WAAW,OAAO,KAAK,WAAW,OAAO;AAClF,aAAO;AAAA,IACT;AAEA,QAAI,WAAW,SAAS,KAAK,KAAK,KAAK,WAAW,KAAK;AACrD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare class CustomStreamingMatcher {
|
|
2
|
+
private pattern;
|
|
3
|
+
private buffer;
|
|
4
|
+
private possibleMatches;
|
|
5
|
+
constructor(pattern: RegExp);
|
|
6
|
+
addCharacter(char: string): MatchResult;
|
|
7
|
+
addString(str: string): MatchResult[];
|
|
8
|
+
couldMatch(): boolean;
|
|
9
|
+
reset(): void;
|
|
10
|
+
getBuffer(): string;
|
|
11
|
+
private calculatePartialConfidence;
|
|
12
|
+
private isValidPrefix;
|
|
13
|
+
}
|
|
14
|
+
interface MatchResult {
|
|
15
|
+
matched: boolean;
|
|
16
|
+
content: string;
|
|
17
|
+
length: number;
|
|
18
|
+
isComplete: boolean;
|
|
19
|
+
confidence?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { CustomStreamingMatcher, type MatchResult };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare class CustomStreamingMatcher {
|
|
2
|
+
private pattern;
|
|
3
|
+
private buffer;
|
|
4
|
+
private possibleMatches;
|
|
5
|
+
constructor(pattern: RegExp);
|
|
6
|
+
addCharacter(char: string): MatchResult;
|
|
7
|
+
addString(str: string): MatchResult[];
|
|
8
|
+
couldMatch(): boolean;
|
|
9
|
+
reset(): void;
|
|
10
|
+
getBuffer(): string;
|
|
11
|
+
private calculatePartialConfidence;
|
|
12
|
+
private isValidPrefix;
|
|
13
|
+
}
|
|
14
|
+
interface MatchResult {
|
|
15
|
+
matched: boolean;
|
|
16
|
+
content: string;
|
|
17
|
+
length: number;
|
|
18
|
+
isComplete: boolean;
|
|
19
|
+
confidence?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { CustomStreamingMatcher, type MatchResult };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/streaming/custom-matcher.ts
|
|
2
|
+
var CustomStreamingMatcher = class {
|
|
3
|
+
constructor(pattern) {
|
|
4
|
+
this.buffer = "";
|
|
5
|
+
this.possibleMatches = [];
|
|
6
|
+
this.pattern = pattern;
|
|
7
|
+
}
|
|
8
|
+
addCharacter(char) {
|
|
9
|
+
this.buffer += char;
|
|
10
|
+
const fullMatch = this.buffer.match(this.pattern);
|
|
11
|
+
if (fullMatch && fullMatch.index === 0) {
|
|
12
|
+
const match = {
|
|
13
|
+
matched: true,
|
|
14
|
+
content: fullMatch[0],
|
|
15
|
+
length: fullMatch[0].length,
|
|
16
|
+
isComplete: true
|
|
17
|
+
};
|
|
18
|
+
this.buffer = this.buffer.slice(fullMatch[0].length);
|
|
19
|
+
this.possibleMatches = [];
|
|
20
|
+
return match;
|
|
21
|
+
}
|
|
22
|
+
const confidence = this.calculatePartialConfidence();
|
|
23
|
+
return {
|
|
24
|
+
matched: false,
|
|
25
|
+
content: this.buffer,
|
|
26
|
+
length: this.buffer.length,
|
|
27
|
+
isComplete: false,
|
|
28
|
+
confidence
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
addString(str) {
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const char of str) {
|
|
34
|
+
const result = this.addCharacter(char);
|
|
35
|
+
if (result.matched) {
|
|
36
|
+
results.push(result);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (results.length === 0 && this.buffer.length > 0) {
|
|
40
|
+
results.push({
|
|
41
|
+
matched: false,
|
|
42
|
+
content: this.buffer,
|
|
43
|
+
length: this.buffer.length,
|
|
44
|
+
isComplete: false,
|
|
45
|
+
confidence: this.calculatePartialConfidence()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
couldMatch() {
|
|
51
|
+
if (this.buffer.length === 0) return true;
|
|
52
|
+
const patternSource = this.pattern.source;
|
|
53
|
+
const flags = this.pattern.flags;
|
|
54
|
+
try {
|
|
55
|
+
const partialPattern = new RegExp(`^${patternSource.replace(/\\$$/, "")}`, flags);
|
|
56
|
+
const testString = this.buffer + "X".repeat(100);
|
|
57
|
+
return partialPattern.test(testString);
|
|
58
|
+
} catch {
|
|
59
|
+
return this.isValidPrefix();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
reset() {
|
|
63
|
+
this.buffer = "";
|
|
64
|
+
this.possibleMatches = [];
|
|
65
|
+
}
|
|
66
|
+
getBuffer() {
|
|
67
|
+
return this.buffer;
|
|
68
|
+
}
|
|
69
|
+
calculatePartialConfidence() {
|
|
70
|
+
if (this.buffer.length === 0) return 0;
|
|
71
|
+
let confidence = 0.1;
|
|
72
|
+
confidence += Math.min(0.4, this.buffer.length * 0.1);
|
|
73
|
+
if (this.couldMatch()) {
|
|
74
|
+
confidence += 0.3;
|
|
75
|
+
}
|
|
76
|
+
if (this.buffer.startsWith("$")) confidence += 0.2;
|
|
77
|
+
if (this.buffer.startsWith("$$")) confidence += 0.3;
|
|
78
|
+
return Math.min(1, confidence);
|
|
79
|
+
}
|
|
80
|
+
isValidPrefix() {
|
|
81
|
+
const patternStr = this.pattern.source;
|
|
82
|
+
if (patternStr.includes("\\$\\$") && (this.buffer === "$" || this.buffer === "$$")) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (patternStr.includes("\\$") && this.buffer === "$") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export {
|
|
92
|
+
CustomStreamingMatcher
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=custom-matcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/custom-matcher.ts"],"sourcesContent":["// Custom streaming regex matcher to replace problematic incr-regex-package\n\nexport class CustomStreamingMatcher {\n private pattern: RegExp;\n private buffer = \"\";\n private possibleMatches: PossibleMatch[] = [];\n\n constructor(pattern: RegExp) {\n this.pattern = pattern;\n }\n\n addCharacter(char: string): MatchResult {\n this.buffer += char;\n\n const fullMatch = this.buffer.match(this.pattern);\n if (fullMatch && fullMatch.index === 0) {\n const match = {\n matched: true,\n content: fullMatch[0],\n length: fullMatch[0].length,\n isComplete: true,\n };\n\n this.buffer = this.buffer.slice(fullMatch[0].length);\n this.possibleMatches = [];\n\n return match;\n }\n\n const confidence = this.calculatePartialConfidence();\n\n return {\n matched: false,\n content: this.buffer,\n length: this.buffer.length,\n isComplete: false,\n confidence,\n };\n }\n\n addString(str: string): MatchResult[] {\n const results: MatchResult[] = [];\n\n for (const char of str) {\n const result = this.addCharacter(char);\n if (result.matched) {\n results.push(result);\n }\n }\n\n if (results.length === 0 && this.buffer.length > 0) {\n results.push({\n matched: false,\n content: this.buffer,\n length: this.buffer.length,\n isComplete: false,\n confidence: this.calculatePartialConfidence(),\n });\n }\n\n return results;\n }\n\n couldMatch(): boolean {\n if (this.buffer.length === 0) return true;\n\n const patternSource = this.pattern.source;\n const flags = this.pattern.flags;\n\n try {\n const partialPattern = new RegExp(`^${patternSource.replace(/\\\\$$/, \"\")}`, flags);\n const testString = this.buffer + \"X\".repeat(100);\n return partialPattern.test(testString);\n } catch {\n return this.isValidPrefix();\n }\n }\n\n reset(): void {\n this.buffer = \"\";\n this.possibleMatches = [];\n }\n\n getBuffer(): string {\n return this.buffer;\n }\n\n private calculatePartialConfidence(): number {\n if (this.buffer.length === 0) return 0;\n\n let confidence = 0.1;\n confidence += Math.min(0.4, this.buffer.length * 0.1);\n\n if (this.couldMatch()) {\n confidence += 0.3;\n }\n\n if (this.buffer.startsWith(\"$\")) confidence += 0.2;\n if (this.buffer.startsWith(\"$$\")) confidence += 0.3;\n\n return Math.min(1, confidence);\n }\n\n private isValidPrefix(): boolean {\n const patternStr = this.pattern.source;\n\n if (patternStr.includes(\"\\\\$\\\\$\") && (this.buffer === \"$\" || this.buffer === \"$$\")) {\n return true;\n }\n\n if (patternStr.includes(\"\\\\$\") && this.buffer === \"$\") {\n return true;\n }\n\n return false;\n }\n}\n\nexport interface MatchResult {\n matched: boolean;\n content: string;\n length: number;\n isComplete: boolean;\n confidence?: number;\n}\n\ninterface PossibleMatch {\n startIndex: number;\n pattern: RegExp;\n confidence: number;\n}\n\n"],"mappings":";AAEO,IAAM,yBAAN,MAA6B;AAAA,EAKlC,YAAY,SAAiB;AAH7B,SAAQ,SAAS;AACjB,SAAQ,kBAAmC,CAAC;AAG1C,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,aAAa,MAA2B;AACtC,SAAK,UAAU;AAEf,UAAM,YAAY,KAAK,OAAO,MAAM,KAAK,OAAO;AAChD,QAAI,aAAa,UAAU,UAAU,GAAG;AACtC,YAAM,QAAQ;AAAA,QACZ,SAAS;AAAA,QACT,SAAS,UAAU,CAAC;AAAA,QACpB,QAAQ,UAAU,CAAC,EAAE;AAAA,QACrB,YAAY;AAAA,MACd;AAEA,WAAK,SAAS,KAAK,OAAO,MAAM,UAAU,CAAC,EAAE,MAAM;AACnD,WAAK,kBAAkB,CAAC;AAExB,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,2BAA2B;AAEnD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK,OAAO;AAAA,MACpB,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,KAA4B;AACpC,UAAM,UAAyB,CAAC;AAEhC,eAAW,QAAQ,KAAK;AACtB,YAAM,SAAS,KAAK,aAAa,IAAI;AACrC,UAAI,OAAO,SAAS;AAClB,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,KAAK,KAAK,OAAO,SAAS,GAAG;AAClD,cAAQ,KAAK;AAAA,QACX,SAAS;AAAA,QACT,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,OAAO;AAAA,QACpB,YAAY;AAAA,QACZ,YAAY,KAAK,2BAA2B;AAAA,MAC9C,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,aAAsB;AACpB,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AAErC,UAAM,gBAAgB,KAAK,QAAQ;AACnC,UAAM,QAAQ,KAAK,QAAQ;AAE3B,QAAI;AACF,YAAM,iBAAiB,IAAI,OAAO,IAAI,cAAc,QAAQ,QAAQ,EAAE,CAAC,IAAI,KAAK;AAChF,YAAM,aAAa,KAAK,SAAS,IAAI,OAAO,GAAG;AAC/C,aAAO,eAAe,KAAK,UAAU;AAAA,IACvC,QAAQ;AACN,aAAO,KAAK,cAAc;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS;AACd,SAAK,kBAAkB,CAAC;AAAA,EAC1B;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,6BAAqC;AAC3C,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AAErC,QAAI,aAAa;AACjB,kBAAc,KAAK,IAAI,KAAK,KAAK,OAAO,SAAS,GAAG;AAEpD,QAAI,KAAK,WAAW,GAAG;AACrB,oBAAc;AAAA,IAChB;AAEA,QAAI,KAAK,OAAO,WAAW,GAAG,EAAG,eAAc;AAC/C,QAAI,KAAK,OAAO,WAAW,IAAI,EAAG,eAAc;AAEhD,WAAO,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AAAA,EAEQ,gBAAyB;AAC/B,UAAM,aAAa,KAAK,QAAQ;AAEhC,QAAI,WAAW,SAAS,QAAQ,MAAM,KAAK,WAAW,OAAO,KAAK,WAAW,OAAO;AAClF,aAAO;AAAA,IACT;AAEA,QAAI,WAAW,SAAS,KAAK,KAAK,KAAK,WAAW,KAAK;AACrD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-mdx/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Core types, snapshot utilities, and perf helpers for the Streaming Markdown V2 stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,9 +70,16 @@
|
|
|
70
70
|
"types": "./dist/security.d.ts",
|
|
71
71
|
"import": "./dist/security.mjs",
|
|
72
72
|
"require": "./dist/security.cjs"
|
|
73
|
+
},
|
|
74
|
+
"./streaming/custom-matcher": {
|
|
75
|
+
"types": "./dist/streaming/custom-matcher.d.ts",
|
|
76
|
+
"import": "./dist/streaming/custom-matcher.mjs",
|
|
77
|
+
"require": "./dist/streaming/custom-matcher.cjs"
|
|
73
78
|
}
|
|
74
79
|
},
|
|
75
|
-
"files": [
|
|
80
|
+
"files": [
|
|
81
|
+
"dist"
|
|
82
|
+
],
|
|
76
83
|
"sideEffects": false,
|
|
77
84
|
"scripts": {
|
|
78
85
|
"build": "tsup",
|
|
@@ -81,6 +88,7 @@
|
|
|
81
88
|
"prepack": "npm run build"
|
|
82
89
|
},
|
|
83
90
|
"dependencies": {
|
|
91
|
+
"@lezer/common": "^1.2.3",
|
|
84
92
|
"@lezer/markdown": "^1.3.0",
|
|
85
93
|
"dompurify": "^3.1.6",
|
|
86
94
|
"rehype-parse": "^9.0.0",
|