Skip to content

Latest commit

 

History

History
181 lines (136 loc) · 5.81 KB

File metadata and controls

181 lines (136 loc) · 5.81 KB

iframe Embed API

This project supports embedding into any web application via iframe. The recommended pattern is: the parent system handles auth, file fetching, and upload; the iframe handles editing only. Tokens, cookies, and business APIs stay in the parent — the editor never sees them.

A working demo is available at /embed-demo.html (includes sha256 logging for debugging).


Embedding the editor

<iframe
  id="documentEditor"
  src="https://your-deployment/?embed=1"
  style="width: 100%; height: 720px; border: 0"
></iframe>

To restrict messages to a specific origin, add embedOrigin:

<iframe id="documentEditor" src="https://your-deployment/?embed=1&embedOrigin=https://your-system.example.com"></iframe>

Sending commands

Include an id on each command to match it to the response:

const iframe = document.getElementById('documentEditor');
const editorOrigin = 'https://your-deployment';

function sendEditorCommand(type, payload = {}) {
  const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
  iframe.contentWindow.postMessage({ id, type, payload }, editorOrigin);
  return id;
}

window.addEventListener('message', (event) => {
  if (event.origin !== editorOrigin) return;
  const { id, type, payload } = event.data || {};
  if (!type?.startsWith('document:')) return;

  switch (type) {
    case 'document:ready':
      console.log('Editor ready');
      break;
    case 'document:opened':
      console.log('Opened', id, payload);
      break;
    case 'document:saved':
      console.log('Saved', payload.fileName, payload.file);
      break;
    case 'document:error':
      console.error('Error', payload.message);
      break;
  }
});

Opening a document

From URL

sendEditorCommand('document:open-url', {
  url: 'https://example.com/files/demo.xlsx',
  fileName: 'demo.xlsx',
  readonly: false,
});

If the URL requires auth headers, pass fetchOptions. For protected files it is preferable to fetch in the parent system and pass the binary:

sendEditorCommand('document:open-url', {
  url: 'https://example.com/api/files/1',
  fileName: 'demo.xlsx',
  fetchOptions: { headers: { Authorization: `Bearer ${token}` } },
});

From a file picker

const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx,.xls,.csv,.docx,.doc,.pptx,.ppt';
input.onchange = () => {
  sendEditorCommand('document:open-file', { file: input.files[0], readonly: false });
};
input.click();

From an authenticated fetch (recommended for protected files)

const response = await fetch('/api/files/1', {
  headers: { Authorization: `Bearer ${token}` },
});
const buffer = await response.arrayBuffer();
sendEditorCommand('document:open-buffer', { fileName: 'demo.xlsx', buffer, readonly: false });

Read-only mode

Set at open time via the readonly field, or toggle at any time:

sendEditorCommand('document:set-readonly', { readonly: true });

In read-only mode, editing is disabled and document:save returns document:error.


Saving and uploading

The save command exports the current document and returns a File via document:saved. Default format is XLSX; pass targetExt to change it.

sendEditorCommand('document:save', { targetExt: 'XLSX' }); // XLSX, DOCX, PPTX, CSV

By default the command waits for the editor to return the edited file. If it times out, document:error is returned — this prevents accidentally uploading the original unchanged file. To opt in to returning the original on timeout:

sendEditorCommand('document:save', { targetExt: 'XLSX', returnOriginalOnTimeout: true });

Upload from the parent:

window.addEventListener('message', async (event) => {
  if (event.origin !== editorOrigin) return;
  const { type, payload } = event.data || {};
  if (type !== 'document:saved') return;

  await fetch('/api/files/1', {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: payload.file,
  });
});

Note: Do not rely on file.size alone to detect changes. .xlsx is a zip archive — a minor edit can produce the exact same byte count. The built-in /embed-demo.html logs a sha256 hash on every save for easier debugging.


Query current state

sendEditorCommand('document:get-state');
// Response: { type: 'document:state', payload: { readonly: false, hasDocument: true } }

Message reference

Direction Type Description
parent → iframe document:open-url Open document from URL
parent → iframe document:open-file Open document from File / Blob
parent → iframe document:open-buffer Open document from ArrayBuffer / Uint8Array
parent → iframe document:set-readonly Set read-only or editable
parent → iframe document:save Save and return File
parent → iframe document:get-state Query current state
iframe → parent document:ready Editor initialised
iframe → parent document:opened Document opened
iframe → parent document:readonly-changed Read-only state changed
iframe → parent document:saved Save complete, file returned
iframe → parent document:state Current state response
iframe → parent document:error Operation failed