├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc.yml ├── .travis.yml ├── LICENSE ├── README.md ├── _dev └── steem-content-renderer.code-workspace ├── browser-test ├── browser-test.js └── index.html ├── package-lock.json ├── package.json ├── sample ├── demo-lib.js ├── demo-local.js └── live-demo.html ├── src ├── Log.ts ├── RendererError.ts ├── index.ts ├── renderers │ └── default │ │ ├── DefaultRenderer.spec.test.ts │ │ ├── DefaultRenderer.ts │ │ ├── DefaultRendererLocalization.ts │ │ ├── StaticConfig.ts │ │ ├── _test │ │ └── example1.mock.test.ts │ │ ├── embedder │ │ ├── AssetEmbedder.ts │ │ ├── AssetEmbedderOptions.ts │ │ ├── HtmlDOMParser.spec.test.ts │ │ ├── HtmlDOMParser.ts │ │ ├── utils │ │ │ ├── AccountNameValidator.ts │ │ │ ├── BadActorList.ts │ │ │ └── Links.ts │ │ └── videoembedders │ │ │ ├── AbstractVideoEmbedder.ts │ │ │ ├── TwitchEmbedder.ts │ │ │ ├── VideoEmbedders.ts │ │ │ ├── VimeoEmbedder.ts │ │ │ └── YoutubeEmbedder.ts │ │ └── sanitization │ │ ├── PreliminarySanitizer.ts │ │ └── TagTransformingSanitizer.ts └── security │ ├── LinkSanitizer.spec.test.ts │ ├── LinkSanitizer.ts │ ├── Phishing.ts │ ├── SecurityChecker.spec.test.ts │ └── SecurityChecker.ts ├── tsconfig.json ├── tsconfig.lint.json ├── tslint.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /.DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /_dev 2 | /.travis.yml 3 | /.prettierrc.yml 4 | /TODO 5 | /README.md 6 | /LICENSE 7 | /src 8 | /.DS_Store 9 | /sample 10 | /tslint.json 11 | /tsconfig.lint.json 12 | *.test.ts 13 | /dist/browser/statistics.html -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10 -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | trailingComma: all 2 | tabWidth: 4 3 | semi: true 4 | printWidth: 120 5 | singleQuote: false 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: node_js 3 | 4 | branches: 5 | except: 6 | - /^v[0-9]/ 7 | 8 | before_install: 9 | - npm i -g npm 10 | 11 | install: 12 | - npm ci 13 | 14 | script: 15 | - NODE_ENV=production npm run build 16 | - npm run test 17 | - node sample/demo-local.js 18 | # Test installation: 19 | - (mkdir ~/libtest && cp ./sample/demo-lib.js ~/libtest/demo-lib.js && cd ~/libtest && npm install $TRAVIS_BUILD_DIR && node demo-lib.js) 20 | 21 | deploy: 22 | - provider: script 23 | script: npm run semantic-release 24 | skip_cleanup: true 25 | on: 26 | tags: false 27 | all_branches: true 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Wise Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # steem-content-renderer 2 | 3 | [![npm](https://img.shields.io/npm/v/steem-content-renderer.svg?style=flat-square)](https://www.npmjs.com/package/steem-content-renderer) [![License](https://img.shields.io/github/license/wise-team/steem-content-renderer.svg?style=flat-square)](https://github.com/wise-team/steem-content-renderer/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Chat](https://img.shields.io/badge/chat%20on%20discord-6b11ff.svg?style=flat-square)](https://discordapp.com/invite/CwxQDbG) 4 | 5 | 👉 **[Online demo](https://wise-team.github.io/steem-content-renderer/sample/live-demo.html)** 6 | 7 | Portable library that renders steem posts and comments to string. It supports markdown and html and mimics the behaviour of condenser frontend. 8 | 9 | Features: 10 | 11 | - supports markdown and html 12 | 13 | - sanitizes html and protects from XSS 14 | 15 | **Credit**: this library is based on the code from condenser. It's aim is to allow other projects display steem content the right way without porting the same code over and over. 16 | 17 | ## Server side usage 18 | 19 | Installation: 20 | 21 | ```bash 22 | $ npm install --save steem-content-renderer 23 | ``` 24 | 25 | **Typescript:** 26 | 27 | ```typescript 28 | import { DefaultRenderer } from "steem-content-renderer"; 29 | 30 | const renderer = new DefaultRenderer({ 31 | baseUrl: "https://steemit.com/", 32 | breaks: true, 33 | skipSanitization: false, 34 | allowInsecureScriptTags: false, 35 | addNofollowToLinks: true, 36 | doNotShowImages: false, 37 | ipfsPrefix: "", 38 | assetsWidth: 640, 39 | assetsHeight: 480, 40 | imageProxyFn: (url: string) => url, 41 | usertagUrlFn: (account: string) => "/@" + account, 42 | hashtagUrlFn: (hashtag: string) => "/trending/" + hashtag, 43 | isLinkSafeFn: (url: string) => true, 44 | }); 45 | 46 | const safeHtmlStr = renderer.render(postContent); 47 | ``` 48 | 49 | ## Browser usage: 50 | 51 | See [demo](https://wise-team.github.io/steem-content-renderer/sample/live-demo.html) and [its source](https://github.com/wise-team/steem-content-renderer/blob/master/sample/live-demo.html). 52 | 53 | ```html 54 | 55 | 88 | 89 | 90 | ``` 91 | -------------------------------------------------------------------------------- /_dev/steem-content-renderer.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [{ 3 | "path": ".." 4 | }], 5 | "settings": { 6 | "editor.rulers": [110, 120] 7 | } 8 | } -------------------------------------------------------------------------------- /browser-test/browser-test.js: -------------------------------------------------------------------------------- 1 | import {ClientFunction, Selector} from 'testcafe'; 2 | 3 | fixture`Getting Started` 4 | .page`./index.html`; 5 | 6 | const defaultOptions = { 7 | baseUrl: "https://steemit.com/", 8 | breaks: true, 9 | skipSanitization: false, 10 | allowInsecureScriptTags: false, 11 | addNofollowToLinks: true, 12 | doNotShowImages: false, 13 | ipfsPrefix: "", 14 | assetsWidth: 640, 15 | assetsHeight: 480, 16 | imageProxyFn: (url) => url, 17 | usertagUrlFn: (account) => `https://steemit.com/@${account}`, 18 | hashtagUrlFn: (hashtag) => `/trending/${hashtag}`, 19 | isLinkSafeFn: (url) => true, // !!url.match(/^(\/(?!\/)|https:\/\/steemit.com)/), 20 | }; 21 | 22 | const renderInBrowser = ClientFunction((options, markup) => { 23 | const renderer = new SteemContentRenderer.DefaultRenderer(options); 24 | return renderer.render(markup); 25 | }); 26 | 27 | test('Renders properly simple markup', async t => { 28 | const markup = "# H1" 29 | 30 | await t.click(Selector('#awaiter')) 31 | .expect(renderInBrowser({ ...defaultOptions }, markup)).eql('

H1

\n'); 32 | }); 33 | 34 | test('Does not crash on mixed-img markup', async t => { 35 | const markup = ``; 36 | const expected = `

\n`; 37 | 38 | await t.click(Selector('#awaiter')) 39 | .expect(renderInBrowser({ ...defaultOptions }, markup)).eql(expected); 40 | }); -------------------------------------------------------------------------------- /browser-test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Browser test of clean-stack 6 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steem-content-renderer", 3 | "version": "0.0.0-development", 4 | "description": "Content renderer for Steem posts and comments. Markdown + HTML", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "unpkg": "dist/browser/steem-content-renderer.min.js", 8 | "engines": { 9 | "node": ">=8" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "build:cleanbefore": "rm -rf dist", 16 | "build:lint": "tslint -c tslint.json -p tsconfig.lint.json", 17 | "build:node": "tsc", 18 | "build:browser": "rm -rf dist/browser/ && NODE_ENV=production webpack -p --config webpack.config.js", 19 | "build": "npm run build:cleanbefore && npm run build:node && npm run build:lint && npm run build:browser", 20 | "prepare": "NODE_ENV=production npm run build", 21 | "test": "find src -name '*.spec.test.ts' | TS_NODE_FILES=true TS_NODE_CACHE=false TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' xargs mocha -r ts-node/register --require source-map-support/register", 22 | "verify": "find src -name '*.integration.test.ts' | TS_NODE_FILES=true TS_NODE_CACHE=false TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' xargs mocha -r ts-node/register --require source-map-support/register", 23 | "verify:browser":" testcafe --app-init-timeout 4000 --selector-timeout 2000 --assertion-timeout 2000 chrome browser-test/browser-test.js", 24 | "lint-fix": "tslint --fix -c tslint.json -p tsconfig.lint.json", 25 | "semantic-release": "semantic-release" 26 | }, 27 | "dependencies": { 28 | "ow": "^0.13.2", 29 | "remarkable": "^1.7.1", 30 | "sanitize-html": "^1.20.1", 31 | "typescript-chained-error": "^1.3.2", 32 | "universe-log": "^2.1.0", 33 | "xmldom": "^0.1.27" 34 | }, 35 | "devDependencies": { 36 | "@commitlint/cli": "^8.1.0", 37 | "@commitlint/config-conventional": "^8.1.0", 38 | "@types/bluebird": "^3.5.27", 39 | "@types/chai": "^4.2.0", 40 | "@types/chai-as-promised": "^7.1.2", 41 | "@types/jsdom": "^12.2.4", 42 | "@types/lodash": "^4.14.136", 43 | "@types/mocha": "^5.2.7", 44 | "@types/node": "^12.7.1", 45 | "@types/remarkable": "1.7.3", 46 | "@types/sanitize-html": "^1.20.1", 47 | "@types/sinon": "^7.0.13", 48 | "@types/uuid": "^3.4.5", 49 | "@types/xmldom": "^0.1.29", 50 | "chai": "^4.2.0", 51 | "chai-as-promised": "^7.1.1", 52 | "husky": "^3.0.3", 53 | "jsdom": "^15.1.1", 54 | "lodash": "^4.17.15", 55 | "mocha": "^6.2.0", 56 | "semantic-release": "^16.0.0-beta.22", 57 | "sinon": "^7.4.1", 58 | "testcafe": "^1.4.3", 59 | "ts-node": "^8.3.0", 60 | "tslint": "^5.18.0", 61 | "typescript": "^3.5.3", 62 | "uuid": "^3.3.2", 63 | "webpack": "^4.39.1", 64 | "webpack-cli": "^3.3.6", 65 | "webpack-visualizer-plugin": "^0.1.11", 66 | "wise-tslint-configuration": "^0.2.0" 67 | }, 68 | "commitlint": { 69 | "extends": [ 70 | "@commitlint/config-conventional" 71 | ], 72 | "rules": { 73 | "header-max-length": [ 74 | 0 75 | ], 76 | "scope-case": [ 77 | 0 78 | ] 79 | } 80 | }, 81 | "husky": { 82 | "hooks": { 83 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 84 | } 85 | }, 86 | "repository": { 87 | "type": "git", 88 | "url": "https://github.com/wise-team/steem-content-renderer" 89 | }, 90 | "keywords": [ 91 | "steem", 92 | "markdown", 93 | "renderer", 94 | "blockchain" 95 | ], 96 | "author": "The Wise Team (https://wise-team.io/)", 97 | "contributors": [ 98 | "Jędrzej Lewandowski (https://jedrzej.lewandowski.doctor/)" 99 | ], 100 | "license": "MIT", 101 | "bugs": { 102 | "url": "https://github.com/wise-team/steem-content-renderer/issues" 103 | }, 104 | "homepage": "https://engrave.website/" 105 | } 106 | -------------------------------------------------------------------------------- /sample/demo-lib.js: -------------------------------------------------------------------------------- 1 | const SteemContentRenderer = require("steem-content-renderer"); 2 | 3 | const renderer = new SteemContentRenderer.DefaultRenderer({ 4 | baseUrl: "https://steemit.com/", 5 | breaks: true, 6 | skipSanitization: false, 7 | allowInsecureScriptTags: false, 8 | addNofollowToLinks: true, 9 | doNotShowImages: false, 10 | ipfsPrefix: "", 11 | assetsWidth: 640, 12 | assetsHeight: 480, 13 | imageProxyFn: (url) => url, 14 | usertagUrlFn: (account) => "/@" + account, 15 | hashtagUrlFn: (hashtag) => "/trending/" + hashtag, 16 | isLinkSafeFn: (url) => true, 17 | }); 18 | 19 | const input = ` 20 | # Sample post 21 | 22 | and some content 23 | 24 | Lets mention @ned #stark. 25 | `; 26 | 27 | const output = renderer.render(input); 28 | 29 | console.log(); 30 | console.log("+-----------------------------+"); 31 | console.log("| Steem-content-renderer demo |"); 32 | console.log("+-----------------------------+"); 33 | console.log(); 34 | console.log(output); 35 | console.log(); 36 | console.log(); -------------------------------------------------------------------------------- /sample/demo-local.js: -------------------------------------------------------------------------------- 1 | const SteemContentRenderer = require("../dist/index"); 2 | 3 | const renderer = new SteemContentRenderer.DefaultRenderer({ 4 | baseUrl: "https://steemit.com/", 5 | breaks: true, 6 | skipSanitization: false, 7 | allowInsecureScriptTags: false, 8 | addNofollowToLinks: true, 9 | doNotShowImages: false, 10 | ipfsPrefix: "", 11 | assetsWidth: 640, 12 | assetsHeight: 480, 13 | imageProxyFn: (url) => url, 14 | usertagUrlFn: (account) => "/@" + account, 15 | hashtagUrlFn: (hashtag) => "/trending/" + hashtag, 16 | isLinkSafeFn: (url) => true, 17 | }); 18 | 19 | const input = ` 20 | # Sample post 21 | 22 | and some content 23 | 24 | Lets mention @ned #stark. 25 | `; 26 | 27 | const output = renderer.render(input); 28 | 29 | console.log(); 30 | console.log("+-----------------------------+"); 31 | console.log("| Steem-content-renderer demo |"); 32 | console.log("+-----------------------------+"); 33 | console.log(); 34 | console.log(output); 35 | console.log(); 36 | console.log(); -------------------------------------------------------------------------------- /sample/live-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Steem-content-renderer live demo 7 | 46 | 47 | 48 |
49 |

Steem-content-renderer example

50 | 63 |
64 | 65 |

Render markdown:

66 |
67 | Link to post (busy or steemit): 68 |
69 | 79 |

80 | 81 |

Output:

82 |

83 | ...press the button... 84 |

85 |
86 | 87 |

Generated HTML markup

88 |
 89 |             ...press the button...
 90 |         
91 | 92 | 93 | 94 | 95 | 96 | 177 | 178 | -------------------------------------------------------------------------------- /src/Log.ts: -------------------------------------------------------------------------------- 1 | import { AbstractUniverseLog } from "universe-log"; 2 | 3 | export class Log extends AbstractUniverseLog { 4 | public static log(): Log { 5 | return Log.INSTANCE; 6 | } 7 | private static INSTANCE: Log = new Log(); 8 | 9 | private constructor() { 10 | super({ 11 | levelEnvs: ["STEEM_CONTENT_RENDERER_LOG_LEVEL", "ENGRAVE_LOG_LEVEL"], 12 | metadata: { 13 | library: "steem-content-renderer", 14 | }, 15 | }); 16 | } 17 | 18 | public initialize() { 19 | super.init(); 20 | } 21 | 22 | public init() { 23 | throw new Error("Instead of #init() please call #initialize() which indirectly overrides init"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/RendererError.ts: -------------------------------------------------------------------------------- 1 | import ChainedError from "typescript-chained-error"; 2 | 3 | export class RendererError extends ChainedError { 4 | public constructor(message?: string, cause?: Error) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { DefaultRenderer } from "./renderers/default/DefaultRenderer"; 2 | export { DefaultRenderer } from "./renderers/default/DefaultRenderer"; 3 | 4 | export const SteemContentRenderer = { 5 | DefaultRenderer, 6 | }; 7 | 8 | export default SteemContentRenderer; 9 | -------------------------------------------------------------------------------- /src/renderers/default/DefaultRenderer.spec.test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable max-line-length quotemark 2 | 3 | import { expect } from "chai"; 4 | import { JSDOM } from "jsdom"; 5 | import "mocha"; 6 | 7 | import { Log } from "../../Log"; 8 | 9 | import { DefaultRenderer } from "./DefaultRenderer"; 10 | 11 | describe("DefaultRender", () => { 12 | const defaultOptions: DefaultRenderer.Options = { 13 | baseUrl: "https://steemit.com/", 14 | breaks: true, 15 | skipSanitization: false, 16 | allowInsecureScriptTags: false, 17 | addNofollowToLinks: true, 18 | doNotShowImages: false, 19 | ipfsPrefix: "", 20 | assetsWidth: 640, 21 | assetsHeight: 480, 22 | imageProxyFn: (url: string) => url, 23 | usertagUrlFn: (account: string) => `https://steemit.com/@${account}`, 24 | hashtagUrlFn: (hashtag: string) => `/trending/${hashtag}`, 25 | isLinkSafeFn: (url: string) => true, // !!url.match(/^(\/(?!\/)|https:\/\/steemit.com)/), 26 | }; 27 | 28 | const tests = [ 29 | { name: "Renders H1 headers correctly", raw: `# Header H1`, expected: "

Header H1

" }, 30 | { name: "Renders H4 headers correctly", raw: `#### Header H4`, expected: "

Header H4

" }, 31 | { 32 | name: "Renders headers and paragraphs correctly", 33 | raw: "# Header H1\n\nSome paragraph\n\n## Header H2\n\nAnother paragraph", 34 | expected: "

Header H1

\n

Some paragraph

\n

Header H2

\n

Another paragraph

", 35 | }, 36 | { 37 | name: "Renders steem mentions correctly", 38 | raw: "Content @noisy another content", 39 | expected: '

Content @noisy another content

', 40 | }, 41 | { 42 | name: "Renders steem hashtags correctly", 43 | raw: "Content #pl-nuda another content", 44 | expected: '

Content #pl-nuda another content

', 45 | }, 46 | { 47 | name: "Embeds correctly vimeo video via paste", 48 | raw: 49 | '', 50 | expected: 51 | '
', 52 | }, 53 | { 54 | name: "Embeds correctly youtube video via paste", 55 | raw: 56 | '', 57 | expected: 58 | '
', 59 | }, 60 | { 61 | name: "Embeds correctly youtube video via youtube.com link", 62 | raw: "https://www.youtube.com/embed/0nFkmd-A7jA", 63 | expected: 64 | '

', 65 | }, 66 | { 67 | name: "Embeds correctly youtube video via youtu.be link", 68 | raw: "https://www.youtu.be/0nFkmd-A7jA", 69 | expected: 70 | '

', 71 | }, 72 | { 73 | name: "Allows links embedded via tags", 74 | raw: 75 | "Drugwars - revenue and transaction analysis", 76 | expected: 77 | '

Drugwars - revenue and transaction analysis

', 78 | }, 79 | 80 | { 81 | name: "Allows links embedded via tags inside of markdown headers", 82 | raw: 83 | "## Drugwars - revenue and transaction analysis", 84 | expected: 85 | '

Drugwars - revenue and transaction analysis

', 86 | }, 87 | ]; 88 | 89 | tests.forEach(test => 90 | it(test.name, () => { 91 | const renderer = new DefaultRenderer(defaultOptions); 92 | const rendered = renderer.render(test.raw).trim(); 93 | 94 | const renderedNode = JSDOM.fragment(rendered); 95 | const comparisonNode = JSDOM.fragment(test.expected); 96 | 97 | Log.log().debug("rendered", rendered); 98 | Log.log().debug("expected", test.expected); 99 | 100 | expect(renderedNode.isEqualNode(comparisonNode)).to.be.equal(true); 101 | }), 102 | ); 103 | 104 | it("Allows insecure script tags when allowInsecureScriptTags = true", () => { 105 | const renderer = new DefaultRenderer({ ...defaultOptions, allowInsecureScriptTags: true }); 106 | const insecureContent = '