Files
broswer-automation/test-inject-script.js
nasir@endelospay.com d97cad1736 first commit
2025-08-12 02:54:17 +05:00

315 lines
8.7 KiB
JavaScript

(() => {
const SCRIPT_ID = 'excalidraw-control-script';
if (window[SCRIPT_ID]) {
return;
}
function getExcalidrawAPIFromDOM(domElement) {
if (!domElement) {
return null;
}
const reactFiberKey = Object.keys(domElement).find(
(key) => key.startsWith('__reactFiber$') || key.startsWith('__reactInternalInstance$'),
);
if (!reactFiberKey) {
return null;
}
let fiberNode = domElement[reactFiberKey];
if (!fiberNode) {
return null;
}
function isExcalidrawAPI(obj) {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.updateScene === 'function' &&
typeof obj.getSceneElements === 'function' &&
typeof obj.getAppState === 'function'
);
}
function findApiInObject(objToSearch) {
if (isExcalidrawAPI(objToSearch)) {
return objToSearch;
}
if (typeof objToSearch === 'object' && objToSearch !== null) {
for (const key in objToSearch) {
if (Object.prototype.hasOwnProperty.call(objToSearch, key)) {
const found = findApiInObject(objToSearch[key]);
if (found) {
return found;
}
}
}
}
return null;
}
let excalidrawApiInstance = null;
let attempts = 0;
const MAX_TRAVERSAL_ATTEMPTS = 25;
while (fiberNode && attempts < MAX_TRAVERSAL_ATTEMPTS) {
if (fiberNode.stateNode && fiberNode.stateNode.props) {
const api = findApiInObject(fiberNode.stateNode.props);
if (api) {
excalidrawApiInstance = api;
break;
}
if (isExcalidrawAPI(fiberNode.stateNode.props.excalidrawAPI)) {
excalidrawApiInstance = fiberNode.stateNode.props.excalidrawAPI;
break;
}
}
if (fiberNode.memoizedProps) {
const api = findApiInObject(fiberNode.memoizedProps);
if (api) {
excalidrawApiInstance = api;
break;
}
if (isExcalidrawAPI(fiberNode.memoizedProps.excalidrawAPI)) {
excalidrawApiInstance = fiberNode.memoizedProps.excalidrawAPI;
break;
}
}
if (fiberNode.tag === 1 && fiberNode.stateNode && fiberNode.stateNode.state) {
const api = findApiInObject(fiberNode.stateNode.state);
if (api) {
excalidrawApiInstance = api;
break;
}
}
if (
fiberNode.tag === 0 ||
fiberNode.tag === 2 ||
fiberNode.tag === 14 ||
fiberNode.tag === 15 ||
fiberNode.tag === 11
) {
if (fiberNode.memoizedState) {
let currentHook = fiberNode.memoizedState;
let hookAttempts = 0;
const MAX_HOOK_ATTEMPTS = 15;
while (currentHook && hookAttempts < MAX_HOOK_ATTEMPTS) {
const api = findApiInObject(currentHook.memoizedState);
if (api) {
excalidrawApiInstance = api;
break;
}
currentHook = currentHook.next;
hookAttempts++;
}
if (excalidrawApiInstance) break;
}
}
if (fiberNode.stateNode) {
const api = findApiInObject(fiberNode.stateNode);
if (api && api !== fiberNode.stateNode.props && api !== fiberNode.stateNode.state) {
excalidrawApiInstance = api;
break;
}
}
if (
fiberNode.tag === 9 &&
fiberNode.memoizedProps &&
typeof fiberNode.memoizedProps.value !== 'undefined'
) {
const api = findApiInObject(fiberNode.memoizedProps.value);
if (api) {
excalidrawApiInstance = api;
break;
}
}
if (fiberNode.return) {
fiberNode = fiberNode.return;
} else {
break;
}
attempts++;
}
if (excalidrawApiInstance) {
window.excalidrawAPI = excalidrawApiInstance;
console.log('现在您可以通过 `window.foundExcalidrawAPI` 在控制台访问它。');
} else {
console.error('在检查组件树后未能找到 excalidrawAPI。');
}
return excalidrawApiInstance;
}
function createFullExcalidrawElement(skeleton) {
const id = Math.random().toString(36).substring(2, 9);
const seed = Math.floor(Math.random() * 2 ** 31);
const versionNonce = Math.floor(Math.random() * 2 ** 31);
const defaults = {
isDeleted: false,
fillStyle: 'hachure',
strokeWidth: 1,
strokeStyle: 'solid',
roughness: 1,
opacity: 100,
angle: 0,
groupIds: [],
strokeColor: '#000000',
backgroundColor: 'transparent',
version: 1,
locked: false,
};
const fullElement = {
id: id,
seed: seed,
versionNonce: versionNonce,
updated: Date.now(),
...defaults,
...skeleton,
};
return fullElement;
}
let targetElementForAPI = document.querySelector('.excalidraw-app');
if (targetElementForAPI) {
getExcalidrawAPIFromDOM(targetElementForAPI);
}
const eventHandler = {
getSceneElements: () => {
try {
return window.excalidrawAPI.getSceneElements();
} catch (error) {
return {
error: true,
msg: JSON.stringify(error),
};
}
},
addElement: (param) => {
try {
const existingElements = window.excalidrawAPI.getSceneElements();
const newElements = [...existingElements];
param.eles.forEach((ele, idx) => {
const newEle = createFullExcalidrawElement(ele);
newEle.index = `a${existingElements.length + idx + 1}`;
newElements.push(newEle);
});
console.log('newElements ==>', newElements);
const appState = window.excalidrawAPI.getAppState();
window.excalidrawAPI.updateScene({
elements: newElements,
appState: appState,
commitToHistory: true,
});
return {
success: true,
};
} catch (error) {
return {
error: true,
msg: JSON.stringify(error),
};
}
},
deleteElement: (param) => {
try {
const existingElements = window.excalidrawAPI.getSceneElements();
const newElements = [...existingElements];
const idx = newElements.findIndex((e) => e.id === param.id);
if (idx >= 0) {
newElements.splice(idx, 1);
const appState = window.excalidrawAPI.getAppState();
window.excalidrawAPI.updateScene({
elements: newElements,
appState: appState,
commitToHistory: true,
});
return {
success: true,
};
} else {
return {
error: true,
msg: 'element not found',
};
}
} catch (error) {
return {
error: true,
msg: JSON.stringify(error),
};
}
},
updateElement: (param) => {
try {
const existingElements = window.excalidrawAPI.getSceneElements();
const resIds = [];
for (let i = 0; i < param.length; i++) {
const idx = existingElements.findIndex((e) => e.id === param[i].id);
if (idx >= 0) {
resIds.push[idx];
window.excalidrawAPI.mutateElement(existingElements[idx], { ...param[i] });
}
}
return {
success: true,
msg: `已更新元素:${resIds.join(',')}`,
};
} catch (error) {
return {
error: true,
msg: JSON.stringify(error),
};
}
},
cleanup: () => {
try {
window.excalidrawAPI.resetScene();
return {
success: true,
};
} catch (error) {
return {
error: true,
msg: JSON.stringify(error),
};
}
},
};
const handleExecution = (event) => {
const { action, payload, requestId } = event.detail;
const param = JSON.parse(payload || '{}');
let data, error;
try {
const handler = eventHandler[action];
if (!handler) {
error = 'event name not found';
}
data = handler(param);
} catch (e) {
error = e.message;
}
window.dispatchEvent(
new CustomEvent('chrome-mcp:response', { detail: { requestId, data, error } }),
);
};
// --- Lifecycle Functions ---
const initialize = () => {
window.addEventListener('chrome-mcp:execute', handleExecution);
window.addEventListener('chrome-mcp:cleanup', cleanup);
window[SCRIPT_ID] = true;
};
const cleanup = () => {
window.removeEventListener('chrome-mcp:execute', handleExecution);
window.removeEventListener('chrome-mcp:cleanup', cleanup);
delete window[SCRIPT_ID];
delete window.excalidrawAPI;
};
initialize();
})();