mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Sandbox: Fix monaco editor custom languages not working correctly inside the sandbox (#72911)
This commit is contained in:
parent
51a67b99f2
commit
84181eb613
@ -2863,6 +2863,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "8"]
|
||||
],
|
||||
"public/app/features/plugins/sandbox/distortion_map.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/plugins/sandbox/sandbox_components.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
@ -2,11 +2,12 @@ import { cloneDeep, isFunction } from 'lodash';
|
||||
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Monaco } from '@grafana/ui';
|
||||
|
||||
import { loadScriptIntoSandbox } from './code_loader';
|
||||
import { forbiddenElements } from './constants';
|
||||
import { SandboxEnvironment } from './types';
|
||||
import { logWarning } from './utils';
|
||||
import { logWarning, unboxRegexesFromMembraneProxy } from './utils';
|
||||
|
||||
/**
|
||||
* Distortions are near-membrane mechanisms to altert JS instrics and DOM APIs.
|
||||
@ -67,6 +68,8 @@ const generalDistortionMap: DistortionMap = new Map();
|
||||
|
||||
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
||||
|
||||
const SANDBOX_LIVE_API_PATCHED = Symbol.for('@SANDBOX_LIVE_API_PATCHED');
|
||||
|
||||
export function getGeneralSandboxDistortionMap() {
|
||||
if (generalDistortionMap.size === 0) {
|
||||
// initialize the distortion map
|
||||
@ -79,6 +82,7 @@ export function getGeneralSandboxDistortionMap() {
|
||||
distortCreateElement(generalDistortionMap);
|
||||
distortWorkers(generalDistortionMap);
|
||||
distortDocument(generalDistortionMap);
|
||||
distortMonacoEditor(generalDistortionMap);
|
||||
}
|
||||
return generalDistortionMap;
|
||||
}
|
||||
@ -456,3 +460,48 @@ function distortDocument(distortions: DistortionMap) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function distortMonacoEditor(distortions: DistortionMap) {
|
||||
// We rely on `monaco` being instanciated inside `window.monaco`.
|
||||
// this is the same object passed down to plugins using monaco editor for their editors
|
||||
// this `window.monaco` is an instance of monaco but not the same as if we
|
||||
// import `monaco-editor` directly in this file.
|
||||
// Short of abusing the `window.monaco` object we would have to modify grafana-ui to export
|
||||
// the monaco instance directly in the ReactMonacoEditor component
|
||||
const monacoEditor: Monaco = Reflect.get(window, 'monaco');
|
||||
|
||||
// do not double patch
|
||||
if (!monacoEditor || Object.hasOwn(monacoEditor, SANDBOX_LIVE_API_PATCHED)) {
|
||||
return;
|
||||
}
|
||||
const originalSetMonarchTokensProvider = monacoEditor.languages.setMonarchTokensProvider;
|
||||
|
||||
// NOTE: this function in particular is called only once per intialized custom language inside a plugin which is a
|
||||
// rare ocurrance but if not patched it'll break the syntax highlighting for the custom language.
|
||||
function getSetMonarchTokensProvider() {
|
||||
return function (...args: Parameters<typeof originalSetMonarchTokensProvider>) {
|
||||
if (args.length !== 2) {
|
||||
return originalSetMonarchTokensProvider.apply(monacoEditor, args);
|
||||
}
|
||||
return originalSetMonarchTokensProvider.call(
|
||||
monacoEditor,
|
||||
args[0],
|
||||
unboxRegexesFromMembraneProxy(args[1]) as (typeof args)[1]
|
||||
);
|
||||
};
|
||||
}
|
||||
distortions.set(monacoEditor.languages.setMonarchTokensProvider, getSetMonarchTokensProvider);
|
||||
Reflect.set(monacoEditor, SANDBOX_LIVE_API_PATCHED, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* We define "live" APIs as APIs that can only be distorted in runtime on-the-fly and not at initialization
|
||||
* time like other distortions do.
|
||||
*
|
||||
* This could be because the objects we want to patch only become available after specific states are reached
|
||||
* or because the libraries we want to patch are lazy-loaded and we don't have access to their definitions
|
||||
*
|
||||
*/
|
||||
export async function distortLiveApis() {
|
||||
distortMonacoEditor(generalDistortionMap);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { PluginMeta } from '@grafana/data';
|
||||
import { getPluginSettings } from '../pluginSettings';
|
||||
|
||||
import { getPluginCode } from './code_loader';
|
||||
import { getGeneralSandboxDistortionMap } from './distortion_map';
|
||||
import { getGeneralSandboxDistortionMap, distortLiveApis } from './distortion_map';
|
||||
import {
|
||||
getSafeSandboxDomElement,
|
||||
isDomElement,
|
||||
@ -60,6 +60,7 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown>
|
||||
} else {
|
||||
patchObjectAsLiveTarget(originalValue);
|
||||
}
|
||||
distortLiveApis();
|
||||
const distortion = generalDistortionMap.get(originalValue);
|
||||
if (distortion) {
|
||||
return distortion(originalValue, meta, sandboxEnvironment) as ProxyTarget;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { isNearMembraneProxy } from '@locker/near-membrane-shared';
|
||||
import React from 'react';
|
||||
|
||||
import { LogContext } from '@grafana/faro-web-sdk';
|
||||
@ -36,3 +37,36 @@ export function logError(error: Error, context?: LogContext) {
|
||||
};
|
||||
logErrorRuntime(error, context);
|
||||
}
|
||||
|
||||
function isRegex(value: unknown): value is RegExp {
|
||||
return value?.constructor?.name === 'RegExp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Near membrane regex proxy objects behave just exactly the same as regular RegExp objects
|
||||
* with only one difference: they are not `instanceof RegExp`.
|
||||
* This function takes a structure and makes sure any regex that is a nearmembraneproxy
|
||||
* and returns the same regex but in the bluerealm
|
||||
*/
|
||||
export function unboxRegexesFromMembraneProxy(structure: unknown): unknown {
|
||||
if (!structure) {
|
||||
return structure;
|
||||
}
|
||||
|
||||
// Proxy regexes loook and behave like proxies but they
|
||||
// are not instanceof RegExp
|
||||
if (isRegex(structure) && isNearMembraneProxy(structure)) {
|
||||
return new RegExp(structure);
|
||||
}
|
||||
|
||||
if (Array.isArray(structure)) {
|
||||
return structure.map(unboxRegexesFromMembraneProxy);
|
||||
}
|
||||
if (typeof structure === 'object') {
|
||||
return Object.keys(structure).reduce((acc, key) => {
|
||||
Reflect.set(acc, key, unboxRegexesFromMembraneProxy(Reflect.get(structure, key)));
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return structure;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user