Подготовил к интеграции с битриксом

This commit is contained in:
NikitaKalitov
2026-07-16 16:23:08 +03:00
parent be2504d60d
commit b09bd6abf7
9 changed files with 374 additions and 13 deletions
+17 -5
View File
@@ -1,7 +1,8 @@
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { DiagramEditor } from './DiagramEditor';
import { createDevAdapter } from './host/devAdapter';
import type { DiagramDocument } from './types';
import { createBitrixAdapter } from './host/bitrixAdapter';
import { EMPTY_DOCUMENT, type DiagramDocument } from './types';
const SAMPLE_DOCUMENT: DiagramDocument = {
nodes: [
@@ -62,16 +63,27 @@ const SAMPLE_DOCUMENT: DiagramDocument = {
],
};
const adapter = createDevAdapter();
const isProd = import.meta.env.PROD;
const adapter = isProd ? createBitrixAdapter() : createDevAdapter();
// In Bitrix, no value means a brand-new element — start blank, not with dev demo content.
const fallbackDocument = isProd ? EMPTY_DOCUMENT : SAMPLE_DOCUMENT;
function App() {
const [initialDoc] = useState<DiagramDocument>(() => adapter.getInitialValue() ?? SAMPLE_DOCUMENT);
const [doc, setDoc] = useState<DiagramDocument | null>(null);
useEffect(() => {
adapter.onReady((initial) => setDoc(initial ?? fallbackDocument));
}, []);
const handleChange = useCallback((next: DiagramDocument) => {
adapter.onChange(next);
}, []);
return <DiagramEditor initialValue={initialDoc} onChange={handleChange} />;
if (!doc) {
return <p style={{ padding: 16, fontFamily: 'system-ui, sans-serif' }}>Загрузка диаграммы…</p>;
}
return <DiagramEditor initialValue={doc} onChange={handleChange} />;
}
export default App;
+4 -4
View File
@@ -2,11 +2,11 @@ import type { DiagramDocument } from '../types';
/**
* Seam between the diagram app and whatever embeds it.
* Dev builds read/write localStorage; the Bitrix build will read the
* initial value from the bridged hidden input and push changes back
* to it (see bitrix-diagram-constructor-summary.md).
* `onReady` is callback-based rather than a synchronous getter because the
* Bitrix bridge only has the initial value after a postMessage round trip
* (see docs/bitrix-integration.md) — dev builds just call back immediately.
*/
export interface HostAdapter {
getInitialValue: () => DiagramDocument | null;
onReady: (callback: (doc: DiagramDocument | null) => void) => void;
onChange: (doc: DiagramDocument) => void;
}
+56
View File
@@ -0,0 +1,56 @@
import type { DiagramDocument } from '../types';
import type { HostAdapter } from './HostAdapter';
/**
* postMessage handshake with the Bitrix bridge script (see
* docs/bitrix-integration.md for the PHP side). `targetOrigin` is always a
* concrete origin, never '*' — matches the security requirement from the
* original design doc.
*/
const READY_MESSAGE = 'diagram:ready';
const INIT_MESSAGE = 'diagram:init';
const CHANGE_MESSAGE = 'diagram:change';
function getBitrixOrigin(): string {
const origin = import.meta.env.VITE_BITRIX_ORIGIN;
if (!origin) {
throw new Error(
'VITE_BITRIX_ORIGIN is not set. The bridge needs a concrete parent origin — postMessage must never target "*".',
);
}
return origin;
}
export function createBitrixAdapter(): HostAdapter {
const targetOrigin = getBitrixOrigin();
return {
onReady(callback) {
let delivered = false;
function onMessage(event: MessageEvent) {
if (event.origin !== targetOrigin || event.source !== window.parent) return;
if (delivered || event.data?.type !== INIT_MESSAGE) return;
delivered = true;
window.removeEventListener('message', onMessage);
const payload = event.data.payload as string | null | undefined;
if (!payload) {
callback(null);
return;
}
try {
callback(JSON.parse(payload) as DiagramDocument);
} catch {
callback(null);
}
}
window.addEventListener('message', onMessage);
window.parent.postMessage({ type: READY_MESSAGE }, targetOrigin);
},
onChange(doc) {
window.parent.postMessage({ type: CHANGE_MESSAGE, payload: JSON.stringify(doc) }, targetOrigin);
},
};
}
+7 -4
View File
@@ -5,13 +5,16 @@ const STORAGE_KEY = 'diagram-builder:dev-doc';
export function createDevAdapter(): HostAdapter {
return {
getInitialValue() {
onReady(callback) {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
if (!raw) {
callback(null);
return;
}
try {
return JSON.parse(raw) as DiagramDocument;
callback(JSON.parse(raw) as DiagramDocument);
} catch {
return null;
callback(null);
}
},
onChange(doc) {
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
/** Bitrix portal origin the postMessage bridge is allowed to talk to (e.g. https://portal.local). Required in production builds. */
readonly VITE_BITRIX_ORIGIN?: string;
}