All files / ext.wikilambda.app/store/stores zhtml.js

100% Statements 94/94
92.85% Branches 13/14
100% Functions 3/3
100% Lines 94/94

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 126x 12x 1x 1x 11x 11x 11x 11x 11x 1x 1x 10x 10x 11x 1x 1x 9x 9x 9x 9x 7x 7x 7x 7x 7x 7x 9x 9x 2x 2x 2x 2x 2x 9x 9x 9x 9x 9x 11x 126x 126x 126x 126x 126x 126x 126x 1x 1x 1x 126x 126x  
/*!
 * WikiLambda Vue editor: HTML Fragment sanitization store
 *
 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
 * @license MIT
 */
'use strict';
 
const apiUtils = require( '../../utils/apiUtils.js' );
const miscUtils = require( '../../utils/miscUtils.js' );
 
module.exports = {
	state: {
		/**
		 * Cache for sanitized HTML fragments.
		 * Key: SHA-256 hash of raw HTML string
		 * Value: sanitized HTML string
		 *
		 * @type {Map<string, string>}
		 */
		sanitizationCache: new Map(),
 
		/**
		 * Map of in-flight sanitization promises.
		 * Key: SHA-256 hash of raw HTML string
		 * Value: Promise resolving to sanitized HTML string
		 *
		 * @type {Map<string, Promise<string>>}
		 */
		pendingPromises: new Map()
	},
 
	getters: {},
 
	actions: {
		/**
		 * Sanitises HTML fragment with caching.
		 * Returns cached result if available, otherwise calls API and caches result.
		 *
		 * @param {string} html - The raw HTML to sanitise
		 * @param {AbortSignal} [signal] - Optional AbortSignal to cancel the request
		 * @return {Promise<string>} Promise resolving to the sanitised HTML string
		 */
		sanitiseHtml: function ( html, signal ) {
			if ( !html ) {
				return Promise.resolve( '' );
			}
 
			// Hash the HTML to use as cache key
			return miscUtils.sha256( html ).then( ( hash ) => {
				// Check if we already have a cached sanitized result
				if ( this.sanitizationCache.has( hash ) ) {
					return this.sanitizationCache.get( hash );
				}
 
				// Check if there's already an in-flight sanitization request for this hash
				if ( this.pendingPromises.has( hash ) ) {
					return this.pendingPromises.get( hash );
				}
 
				// Not in cache or pending, create new API request
				const requestPromise = apiUtils.sanitiseHtmlFragment( { html, signal } )
					.then( ( data ) => {
						const sanitised = data.html || '';
						// Cache the result
						this.sanitizationCache.set( hash, sanitised );
						// Remove from pending promises
						this.pendingPromises.delete( hash );
						return sanitised;
					} )
					.catch( () => {
						// Do not cache failures: Map.has() is key-existence, so a cached ''
						// would short-circuit every subsequent call for the same hash and
						// turn any transient failure into a permanent session failure.
						this.pendingPromises.delete( hash );
						return '';
					} );
 
				// Store the pending promise
				this.pendingPromises.set( hash, requestPromise );
				return requestPromise;
			} );
		},
 
		/**
		 * Clears the sanitization cache and pending promises.
		 * Useful for testing or when cache needs to be invalidated.
		 */
		clearSanitizationCache: function () {
			this.sanitizationCache.clear();
			this.pendingPromises.clear();
		}
	}
};