mirror of https://github.com/grafana/grafana
Sandbox: Add support for webpack dynamic imports (#71714)
parent
47f70bdb00
commit
e8e3f81e38
@ -0,0 +1,88 @@ |
||||
import { PluginMeta } from '@grafana/data'; |
||||
|
||||
import { getPluginCdnResourceUrl, extractPluginIdVersionFromUrl, transformPluginSourceForCDN } from '../cdn/utils'; |
||||
import { PLUGIN_CDN_URL_KEY } from '../constants'; |
||||
|
||||
import { SandboxEnvironment } from './types'; |
||||
|
||||
function isSameDomainAsHost(url: string): boolean { |
||||
const locationUrl = new URL(window.location.href); |
||||
const paramUrl = new URL(url); |
||||
return locationUrl.host === paramUrl.host; |
||||
} |
||||
|
||||
export async function loadScriptIntoSandbox(url: string, meta: PluginMeta, sandboxEnv: SandboxEnvironment) { |
||||
let scriptCode = ''; |
||||
|
||||
// same-domain
|
||||
if (isSameDomainAsHost(url)) { |
||||
const response = await fetch(url); |
||||
scriptCode = await response.text(); |
||||
scriptCode = patchPluginSourceMap(meta, scriptCode); |
||||
|
||||
// cdn loaded
|
||||
} else if (url.includes(PLUGIN_CDN_URL_KEY)) { |
||||
const response = await fetch(url); |
||||
scriptCode = await response.text(); |
||||
const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js'; |
||||
const { version } = extractPluginIdVersionFromUrl(pluginUrl); |
||||
scriptCode = transformPluginSourceForCDN({ |
||||
pluginId: meta.id, |
||||
version, |
||||
source: scriptCode, |
||||
}); |
||||
} |
||||
|
||||
if (scriptCode.length === 0) { |
||||
throw new Error('Only same domain scripts are allowed in sandboxed plugins'); |
||||
} |
||||
|
||||
sandboxEnv.evaluate(scriptCode); |
||||
} |
||||
|
||||
export async function getPluginCode(meta: PluginMeta): Promise<string> { |
||||
if (meta.module.includes(`${PLUGIN_CDN_URL_KEY}/`)) { |
||||
// should load plugin from a CDN
|
||||
const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js'; |
||||
const response = await fetch(pluginUrl); |
||||
let pluginCode = await response.text(); |
||||
const { version } = extractPluginIdVersionFromUrl(pluginUrl); |
||||
pluginCode = transformPluginSourceForCDN({ |
||||
pluginId: meta.id, |
||||
version, |
||||
source: pluginCode, |
||||
}); |
||||
return pluginCode; |
||||
} else { |
||||
//local plugin loading
|
||||
const response = await fetch('public/' + meta.module + '.js'); |
||||
let pluginCode = await response.text(); |
||||
pluginCode = patchPluginSourceMap(meta, pluginCode); |
||||
return pluginCode; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Patches the plugin's module.js source code references to sourcemaps to include the full url |
||||
* of the module.js file instead of the regular relative reference. |
||||
* |
||||
* Because the plugin module.js code is loaded via fetch and then "eval" as a string |
||||
* it can't find the references to the module.js.map directly and we need to patch it |
||||
* to point to the correct location |
||||
*/ |
||||
function patchPluginSourceMap(meta: PluginMeta, pluginCode: string): string { |
||||
// skips inlined and files without source maps
|
||||
if (pluginCode.includes('//# sourceMappingURL=module.js.map')) { |
||||
let replaceWith = ''; |
||||
// make sure we don't add the sourceURL twice
|
||||
if (!pluginCode.includes('//# sourceURL') || !pluginCode.includes('//@ sourceUrl')) { |
||||
replaceWith += `//# sourceURL=module.js\n`; |
||||
} |
||||
// modify the source map url to point to the correct location
|
||||
const sourceCodeMapUrl = `/public/${meta.module}.js.map`; |
||||
replaceWith += `//# sourceMappingURL=${sourceCodeMapUrl}`; |
||||
|
||||
return pluginCode.replace('//# sourceMappingURL=module.js.map', replaceWith); |
||||
} |
||||
return pluginCode; |
||||
} |
@ -1 +1 @@ |
||||
export const forbiddenElements = ['script', 'iframe']; |
||||
export const forbiddenElements = ['iframe']; |
||||
|
Loading…
Reference in new issue