mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -06:00
Sandbox: Patch history.replaceState to make it work inside the sandbox (#76255)
* Patch history.replaceState api instead of doing a live distortion * Add better patching mechanism * Remove console log
This commit is contained in:
parent
63c7a0e8ca
commit
7107ba0104
@ -528,24 +528,5 @@ async function distortPostMessage(distortions: DistortionMap) {
|
|||||||
*/
|
*/
|
||||||
export function distortLiveApis(originalValue: ProxyTarget): ProxyTarget | undefined {
|
export function distortLiveApis(originalValue: ProxyTarget): ProxyTarget | undefined {
|
||||||
distortMonacoEditor(generalDistortionMap);
|
distortMonacoEditor(generalDistortionMap);
|
||||||
|
|
||||||
// This distorts the `history.replace` function in react-router-dom.
|
|
||||||
// constructed for each browser history and is only accessible within the react context.
|
|
||||||
// Note that this distortion does not affect `String.prototype.replace` calls.
|
|
||||||
// because they don't go through distortions
|
|
||||||
if (
|
|
||||||
originalValue instanceof Function &&
|
|
||||||
originalValue.name === 'replace' &&
|
|
||||||
originalValue.prototype.constructor.length === 2
|
|
||||||
) {
|
|
||||||
return function replace(this: unknown, ...args: unknown[]) {
|
|
||||||
// validate history.replace signature further
|
|
||||||
if (args && args[0] && typeof args[0] === 'string' && args[1] && !(args[1] instanceof Function)) {
|
|
||||||
const newArgs = cloneDeep(args);
|
|
||||||
return Reflect.apply(originalValue, this, newArgs);
|
|
||||||
}
|
|
||||||
return Reflect.apply(originalValue, this, args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { isNearMembraneProxy, ProxyTarget } from '@locker/near-membrane-shared';
|
import { isNearMembraneProxy, ProxyTarget } from '@locker/near-membrane-shared';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
|
|
||||||
import { DataSourceApi } from '@grafana/data';
|
import { DataSourceApi } from '@grafana/data';
|
||||||
@ -160,3 +161,50 @@ export function getSandboxMockBody(): Element {
|
|||||||
}
|
}
|
||||||
return sandboxBody;
|
return sandboxBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nativeAPIsPatched = false;
|
||||||
|
|
||||||
|
export function patchWebAPIs() {
|
||||||
|
if (!nativeAPIsPatched) {
|
||||||
|
nativeAPIsPatched = true;
|
||||||
|
patchHistoryReplaceState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* window.history.replaceState is a native API that won't work with proxies
|
||||||
|
* so we need to patch it to unwrap any possible proxies you pass to it.
|
||||||
|
*
|
||||||
|
* Why can't we directly distord window.history.replaceState calls inside plugins?
|
||||||
|
*
|
||||||
|
* We can. Except that plugins don't call window.history.replaceState directly they
|
||||||
|
* instead use the history object from react-router.
|
||||||
|
*
|
||||||
|
* react-router is a runtime dependency and it is executed in the blue realm
|
||||||
|
* and calls window.history.replaceState directly where the sandbox is not involved at all
|
||||||
|
*
|
||||||
|
* It is most likely this "original" function is not really the native function because
|
||||||
|
* `useLocation` from `react-use` patches this function before the sandbox kicks in.
|
||||||
|
*
|
||||||
|
* Regarding the performance impact of this cloneDeep. The structures passed to history.replaceState
|
||||||
|
* are minimalistic and its impact will be neglegible.
|
||||||
|
*/
|
||||||
|
function patchHistoryReplaceState() {
|
||||||
|
const original = window.history.replaceState;
|
||||||
|
Object.defineProperty(window.history, 'replaceState', {
|
||||||
|
value: function (...args: Parameters<typeof window.history.replaceState>) {
|
||||||
|
let newArgs = args;
|
||||||
|
try {
|
||||||
|
newArgs = cloneDeep(args);
|
||||||
|
} catch (e) {
|
||||||
|
logWarning('Error cloning args in window.history.replaceState', {
|
||||||
|
error: String(e),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Reflect.apply(original, this, newArgs);
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
isLiveTarget,
|
isLiveTarget,
|
||||||
markDomElementStyleAsALiveTarget,
|
markDomElementStyleAsALiveTarget,
|
||||||
patchObjectAsLiveTarget,
|
patchObjectAsLiveTarget,
|
||||||
|
patchWebAPIs,
|
||||||
} from './document_sandbox';
|
} from './document_sandbox';
|
||||||
import { sandboxPluginDependencies } from './plugin_dependencies';
|
import { sandboxPluginDependencies } from './plugin_dependencies';
|
||||||
import { sandboxPluginComponents } from './sandbox_components';
|
import { sandboxPluginComponents } from './sandbox_components';
|
||||||
@ -30,6 +31,7 @@ const pluginImportCache = new Map<string, Promise<System.Module>>();
|
|||||||
const pluginLogCache: Record<string, boolean> = {};
|
const pluginLogCache: Record<string, boolean> = {};
|
||||||
|
|
||||||
export async function importPluginModuleInSandbox({ pluginId }: { pluginId: string }): Promise<System.Module> {
|
export async function importPluginModuleInSandbox({ pluginId }: { pluginId: string }): Promise<System.Module> {
|
||||||
|
patchWebAPIs();
|
||||||
try {
|
try {
|
||||||
const pluginMeta = await getPluginSettings(pluginId);
|
const pluginMeta = await getPluginSettings(pluginId);
|
||||||
if (!pluginImportCache.has(pluginId)) {
|
if (!pluginImportCache.has(pluginId)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user