Browser Beacon
Beacon is the browser-only SDK surface for inspecting and controlling a PHPSandbox preview inside an iframe. Import it from @phpsandbox/sdk/beacon; do not place the PHPSandbox API token in the browser bundle.
Connect to a preview
Create the iframe in the controlling page, restrict messages to the preview origin, and wait for the injected preview bridge:
import { connectBeacon } from '@phpsandbox/sdk/beacon';
const iframe = document.querySelector<HTMLIFrameElement>('#sandbox-preview');
if (!iframe) {
throw new Error('Missing preview iframe');
}
const previewUrl = 'https://example.phpsandbox.run';
iframe.src = previewUrl;
const beacon = await connectBeacon(iframe, {
targetOrigin: new URL(previewUrl).origin,
timeout: 30_000,
});
console.log(await beacon.ping());
Use a preview URL or protected preview-session URL issued by a trusted server. targetOrigin should be the exact preview origin rather than *.
Inspect and diagnose
Beacon can inspect DOM state, execute diagnostic JavaScript, collect captured console and error events, make requests from the preview context, and capture screenshots:
const element = await beacon.inspectElement('#checkout-form');
const execution = await beacon.executeCode('document.title');
const response = await beacon.fetch<{ status: string }>({
url: new URL('/health', beacon.url).toString(),
});
const screenshot = await beacon.captureScreenshot({
output: { maxWidth: 1280, maxHeight: 720 },
type: 'image/png',
});
console.log({ element, execution, response, screenshot });
Executed code runs in the preview page. Treat it with the same care as browser developer tools and never interpolate untrusted input into it.
Navigate the preview
Navigation creates a new preview document and communication channel. Wait for readiness again before issuing another command:
const nextUrl = new URL('/orders/123', beacon.url).toString();
beacon.navigate(nextUrl);
await beacon.ready();
beacon.on('historyChange', (event) => {
console.log(event.url, event.direction);
});
Recreate the Beacon connection if the iframe element itself is replaced.