diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9d7ec7e1c..7923f2db3 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -50,7 +50,6 @@ module.exports = { }, // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined globals: { - DOMPurify: 'readonly', droll: 'readonly', Handlebars: 'readonly', hljs: 'readonly', diff --git a/package-lock.json b/package-lock.json index 3507bb196..fd106e984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "cookie-session": "^2.1.0", "cors": "^2.8.5", "csrf-csrf": "^2.2.3", + "dompurify": "^3.1.7", "express": "^4.21.0", "form-data": "^4.0.0", "fuse.js": "^7.0.0", @@ -68,7 +69,6 @@ "@types/cookie-parser": "^1.4.7", "@types/cookie-session": "^2.0.49", "@types/cors": "^2.8.17", - "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", "@types/jquery": "^3.5.29", "@types/lodash": "^4.17.10", @@ -1174,16 +1174,6 @@ "@types/node": "*" } }, - "node_modules/@types/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/trusted-types": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1427,13 +1417,6 @@ "@types/jquery": "*" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/write-file-atomic": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz", @@ -3221,6 +3204,12 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", + "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", diff --git a/package.json b/package.json index 16bde9f05..d9d1158ab 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "cookie-session": "^2.1.0", "cors": "^2.8.5", "csrf-csrf": "^2.2.3", + "dompurify": "^3.1.7", "express": "^4.21.0", "form-data": "^4.0.0", "fuse.js": "^7.0.0", @@ -94,7 +95,6 @@ "@types/cookie-parser": "^1.4.7", "@types/cookie-session": "^2.0.49", "@types/cors": "^2.8.17", - "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", "@types/jquery": "^3.5.29", "@types/lodash": "^4.17.10", diff --git a/public/global.d.ts b/public/global.d.ts index 4b6c85705..41a5881cc 100644 --- a/public/global.d.ts +++ b/public/global.d.ts @@ -16,6 +16,7 @@ declare var ai; declare var SillyTavern: { getContext(): any; llm: any; + libs: any; }; // Jquery plugins diff --git a/public/lib.js b/public/lib.js index c07c76f44..55245b283 100644 --- a/public/lib.js +++ b/public/lib.js @@ -3,7 +3,33 @@ * They are bundled and exposed by Webpack in the /lib.js file. */ import Fuse from 'fuse.js'; +import DOMPurify from 'dompurify'; + +/** + * Expose the libraries to the 'window' object. + * Needed for compatibility with old extensions. + * Note: New extensions are encouraged to import the libraries directly from lib.js. + */ +export function initLibraryShims() { + if (!window) { + return; + } + if (!('Fuse' in window)) { + // @ts-ignore + window.Fuse = Fuse; + } + if (!('DOMPurify' in window)) { + // @ts-ignore + window.DOMPurify = DOMPurify; + } +} + +export default { + Fuse, + DOMPurify, +}; export { Fuse, + DOMPurify, }; diff --git a/public/script.js b/public/script.js index c8e4e71d1..00503cdbb 100644 --- a/public/script.js +++ b/public/script.js @@ -1,4 +1,4 @@ -import { Fuse } from './lib.js'; +import { Fuse, DOMPurify, initLibraryShims, default as libs } from './lib.js'; import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter, addSafariPatch } from './scripts/RossAscends-mods.js'; import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js'; @@ -410,6 +410,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { // API OBJECT FOR EXTERNAL WIRING window['SillyTavern'] = {}; +window['SillyTavern'].libs = libs; // Event source init export const event_types = { @@ -940,6 +941,7 @@ async function firstLoadInit() { throw new Error('Initialization failed'); } + initLibraryShims(); addSafariPatch(); await getClientVersion(); await readSecretState(); @@ -2058,8 +2060,15 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san mes = mes.replace(new RegExp(`(^|\n)${escapeRegex(ch_name)}:`, 'g'), '$1'); } - /** @type {any} */ - const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'], ...sanitizerOverrides }; + /** @type {import('dompurify').Config & { RETURN_DOM_FRAGMENT: false; RETURN_DOM: false }} */ + const config = { + RETURN_DOM: false, + RETURN_DOM_FRAGMENT: false, + RETURN_TRUSTED_TYPE: false, + MESSAGE_SANITIZE: true, + ADD_TAGS: ['custom-style'], + ...sanitizerOverrides, + }; mes = encodeStyleTags(mes); mes = DOMPurify.sanitize(mes, config); mes = decodeStyleTags(mes); diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js index eb39c5b3a..4dda05e05 100644 --- a/public/scripts/PromptManager.js +++ b/public/scripts/PromptManager.js @@ -1,5 +1,7 @@ 'use strict'; +import { DOMPurify } from '../lib.js'; + import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js'; import { is_group_generating } from './group-chats.js'; import { Message, TokenHandler } from './openai.js'; diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 69bd7c7a0..e980b4605 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -1,3 +1,5 @@ +import { DOMPurify } from '../lib.js'; + import { characters, online_status, diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 8b49c5ec5..3ea160421 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -1,3 +1,5 @@ +import { DOMPurify } from '../lib.js'; + import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js'; import { showLoader } from './loader.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index 97881c16b..d491e8aaf 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -3,6 +3,7 @@ TODO: */ //const DEBUG_TONY_SAMA_FORK_MODE = true +import { DOMPurify } from '../../../lib.js'; import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 0e82c9d03..68a03eb17 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -3,7 +3,7 @@ * By CncAnon (@CncAnon1) * https://github.com/CncAnon1/TavernAITurbo */ -import { Fuse } from '../lib.js'; +import { Fuse, DOMPurify } from '../lib.js'; import { abortStatusCheck, diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js index 9a2c23d03..8d0190187 100644 --- a/public/scripts/secrets.js +++ b/public/scripts/secrets.js @@ -1,3 +1,4 @@ +import { DOMPurify } from '../lib.js'; import { callPopup, getRequestHeaders } from '../script.js'; export const SECRET_KEYS = { diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 1b281b673..e4a72dd4a 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1,4 +1,4 @@ -import { Fuse } from '../lib.js'; +import { Fuse, DOMPurify } from '../lib.js'; import { Generate, diff --git a/public/scripts/slash-commands/SlashCommandReturnHelper.js b/public/scripts/slash-commands/SlashCommandReturnHelper.js index 947e92cd3..c26729ac5 100644 --- a/public/scripts/slash-commands/SlashCommandReturnHelper.js +++ b/public/scripts/slash-commands/SlashCommandReturnHelper.js @@ -1,3 +1,4 @@ +import { DOMPurify } from '../../lib.js'; import { sendSystemMessage, system_message_types } from '../../script.js'; import { callGenericPopup, POPUP_TYPE } from '../popup.js'; import { escapeHtml } from '../utils.js'; diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 9a1950d0a..32f8b5d02 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -1,3 +1,5 @@ +import { DOMPurify } from '../lib.js'; + import { characters, saveSettingsDebounced, diff --git a/public/scripts/templates.js b/public/scripts/templates.js index b6d935681..baaa0d75f 100644 --- a/public/scripts/templates.js +++ b/public/scripts/templates.js @@ -1,3 +1,4 @@ +import { DOMPurify } from '../lib.js'; import { applyLocale } from './i18n.js'; /** diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index 32b288b39..9cf50d4fc 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -1,3 +1,4 @@ +import { DOMPurify } from '../lib.js'; import { isMobile } from './RossAscends-mods.js'; import { amount_gen, callPopup, eventSource, event_types, getRequestHeaders, max_context, online_status, setGenerationParamsFromPreset } from '../script.js'; import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js'; diff --git a/public/scripts/tool-calling.js b/public/scripts/tool-calling.js index 11dc65c5d..b2a682840 100644 --- a/public/scripts/tool-calling.js +++ b/public/scripts/tool-calling.js @@ -1,3 +1,5 @@ +import { DOMPurify } from '../lib.js'; + import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js'; import { chat_completion_sources, oai_settings } from './openai.js'; import { Popup } from './popup.js'; diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 6cf92f5c0..11b64e4b6 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -1,3 +1,5 @@ +import { DOMPurify } from '../lib.js'; + import { getContext } from './extensions.js'; import { characters, getRequestHeaders, this_chid } from '../script.js'; import { isMobile } from './RossAscends-mods.js'; diff --git a/webpack.config.js b/webpack.config.js index b844425db..0f0154a35 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,12 +4,7 @@ export const publicLibConfig = { entry: './public/lib.js', cache: true, devtool: 'source-map', - module: { - rules: [{ - test: /\.js$/, - exclude: /node_modules/, - }], - }, + module: {}, experiments: { outputModule: true, },