# @mirrorapp/iframe-bridge

A tiny bridge to help you:

- Auto-initialize iframe resizing via `@open-iframe-resizer/core`
- Expose a global message handler so child iframes can request parent viewport info
- Use a simple API: `iFrameSetup(iframe: HTMLIFrameElement)` and `iFrameTeardown(iframe: HTMLIFrameElement)`

UMD build exposes `window.IframeBridge.iFrameSetup` and also `window.iFrameSetup`.

## Installation

```bash
npm install @mirrorapp/iframe-bridge
```

Alternative:

```bash
yarn add @mirrorapp/iframe-bridge
```

```bash
pnpm add @mirrorapp/iframe-bridge
```

## Quick Start (ES Modules)

```ts
import { iFrameSetup } from "@mirrorapp/iframe-bridge";

const iframe = document.getElementById("my-iframe") as HTMLIFrameElement;

await iFrameSetup(iframe);
```

## Browser Usage (UMD, no bundler)

Load the UMD bundle and call the global API:

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>IframeBridge UMD</title>
  </head>
  <body>
    <iframe
      id="my-iframe"
      src="https://example.com"
      style="width:100%;border:0;"
    ></iframe>

    <script src="https://unpkg.com/@mirrorapp/iframe-bridge/dist/index.umd.js"></script>
    <script>
      const iframe = document.getElementById("my-iframe");

      // Either global object...
      window.IframeBridge.iFrameSetup(iframe);

      // ...or the convenience alias:
      // window.iFrameSetup(iframe);
    </script>
  </body>
</html>
```

## UMD setup with an existing `onload` handler

The existing `onload="iFrameSetup(this)"` handler is supported; no iframe markup change is required:

```html
<iframe
  onload="iFrameSetup(this)"
  src="https://example.com"
  style="width:100%;border:0;"
></iframe>

<script src="https://cdn.jsdelivr.net/npm/@mirrorapp/iframe-bridge/dist/index.umd.js"></script>
```

If the bridge script loads first, the inline handler initializes the iframe on load. If the iframe loads first, its handler may log `ReferenceError`; when the bridge script loads, it scans for existing iframes whose `onload` handler calls `iFrameSetup` and initializes them then. In either order, the iframe resizer is initialized. The browser bundle also watches for matching iframes added later.

If you control the markup for new integrations, `onload="window.iFrameSetup?.(this)"` avoids the early `ReferenceError` while retaining the `onload` setup flow.

## React Example

Initialize after mount using a `ref`:

```tsx
import { useEffect, useRef } from "react";
import { iFrameSetup, iFrameTeardown } from "@mirrorapp/iframe-bridge";

export default function App() {
  const iframeRef = useRef<HTMLIFrameElement | null>(null);

  useEffect(() => {
    if (iframeRef.current) {
      const iframe = iframeRef.current;
      void iFrameSetup(iframe);

      return () => {
        iFrameTeardown(iframe);
      };
    }
  }, []);

  return (
    <iframe
      ref={iframeRef}
      id="my-iframe"
      src="https://example.com"
      style={{ width: "100%", border: 0 }}
    />
  );
}
```

## Communicating from Inside the Iframe

From the child iframe page, request viewport info from the parent:

```js
// Request parent viewport info
const requestId = "req-" + Date.now();
window.parent.postMessage(
  { type: "get_viewport_info", data: { requestId } },
  "*",
);

// Listen for the response
window.addEventListener("message", (event) => {
  if (
    event.data?.type === "viewport_info_response" &&
    event.data.requestId === requestId
  ) {
    const {
      iframeTopInViewport,
      iframeLeftInViewport,
      viewportHeight,
      viewportWidth,
      scrollTop,
      scrollLeft,
    } = event.data;

    console.log("Viewport info:", event.data);
  }
});
```

The parent responds with:

- `type: "viewport_info_response"`
- `iframeTopInViewport`, `iframeLeftInViewport` — iframe position within the parent viewport
- `viewportHeight`, `viewportWidth` — parent viewport dimensions
- `scrollTop`, `scrollLeft` — parent scroll positions
- `requestId` — echo from your request

## API

- `iFrameSetup(iframe: HTMLIFrameElement): Promise<void>`
  - Ensures the iframe has an `id` (adds one if missing)
  - Initializes `@open-iframe-resizer/core` with `{ checkOrigin: false }` for that iframe
  - Registers a single global message handler for viewport info requests
- `iFrameTeardown(iframe: HTMLIFrameElement): void`
  - Removes the resizer listeners for the iframe and clears its registration

UMD global:

- `window.IframeBridge.iFrameSetup(iframe)`
- Alias: `window.iFrameSetup(iframe)`
- `window.IframeBridge.iFrameTeardown(iframe)`
- Alias: `window.iFrameTeardown(iframe)`

## Notes

- SSR: Call `iFrameSetup` only in the browser (client-side). The package checks for `window`.
- Security: Resizer uses `checkOrigin: false`. Ensure the parent and child iframe content are trusted. Prefer restricting `postMessage` target origins to a known domain instead of `"*"`.
- Types: TypeScript definitions are included in the package.

## Module Formats

Exports include:

- ESM: `dist/index.js` (default `import`)
- CJS: `dist/index.cjs` (`require`)
- UMD: `dist/index.umd.js` (global `IframeBridge`)

```js
// CommonJS example
const { iFrameSetup } = require("@mirrorapp/iframe-bridge");
```

## Development

```bash
npm ci
```

```bash
npm run build
```

Build outputs to `dist/` with `esm`, `cjs`, and `iife (UMD)` formats, source maps, and type declarations.

Run the browser integration checks with Chromium:

```bash
pnpm --filter @mirrorapp/iframe-bridge exec playwright install chromium
pnpm --filter @mirrorapp/iframe-bridge test:browser
```
