import type { Manifest, Scripting } from '~/browser'; import { ContentScriptEntrypoint } from '~/types'; /** * Returns a unique and consistent string hash based on a content scripts options. * * It is able to recognize default values, */ export function hashContentScriptOptions( options: ContentScriptEntrypoint['options'], ): string { const simplifiedOptions = mapWxtOptionsToContentScript( options, undefined, undefined, ); // Remove undefined fields and use defaults to generate hash Object.keys(simplifiedOptions).forEach((key) => { // @ts-expect-error: key not typed as keyof ... if (simplifiedOptions[key] == null) delete simplifiedOptions[key]; }); const withDefaults: Manifest.ContentScript = { exclude_globs: [], exclude_matches: [], include_globs: [], match_about_blank: false, run_at: 'document_idle', all_frames: false, // @ts-expect-error - not in type match_origin_as_fallback: false, world: 'ISOLATED', ...simplifiedOptions, }; return JSON.stringify( Object.entries(withDefaults) // Sort any arrays so their values are consistent .map<[string, unknown]>(([key, value]) => { if (Array.isArray(value)) return [key, value.sort()]; else return [key, value]; }) // Sort all the fields alphabetically .sort((l, r) => l[0].localeCompare(r[0])), ); } export function mapWxtOptionsToContentScript( options: ContentScriptEntrypoint['options'], js: string[] | undefined, css: string[] | undefined, ): Manifest.ContentScript { return { matches: options.matches, all_frames: options.allFrames, match_about_blank: options.matchAboutBlank, exclude_globs: options.excludeGlobs, exclude_matches: options.excludeMatches, include_globs: options.includeGlobs, run_at: options.runAt, css, js, // @ts-expect-error: untyped chrome options match_origin_as_fallback: options.matchOriginAsFallback, world: options.world, }; } export function mapWxtOptionsToRegisteredContentScript( options: ContentScriptEntrypoint['options'], js: string[] | undefined, css: string[] | undefined, ): Omit { return { allFrames: options.allFrames, excludeMatches: options.excludeMatches, matches: options.matches, runAt: options.runAt, js, css, // @ts-expect-error: Chrome accepts this, not typed in webextension-polyfill (https://developer.chrome.com/docs/extensions/reference/scripting/#type-RegisteredContentScript) world: options.world, }; }