Embedding Apps

Two integration modes — pick the one that matches your product:

Mode Best for Isolation Entry point
Iframe Embedding one specific app with URL-driven state; sandboxed from your page's JS Full (same-origin or cross-origin) <iframe src="/online/webapp/{app}"> -- this doc
In-page (no iframe) Running SGApps apps directly inside your page's DOM: multi-window desktop, drop-in alongside your UI, no postMessage round-trip None (shares your page's globals and styles) exporter.js + wm.open(...) -- In-page section + Window Manager docs

Basic Iframe Embed

Every SGApps web application is available at:

https://sgapps.io/online/webapp/{app-name}

Embed with a standard <iframe>:

<iframe
    id="sgapps-editor"
    src="https://sgapps.io/online/webapp/photo-editor"
    width="100%"
    height="600"
    frameborder="0"
    allow="camera; microphone; clipboard-write"
></iframe>

Recommended allow Attributes

Permission Needed By
camera Recorder
microphone Recorder, ChatBox
clipboard-write Code Editor, File Manager
fullscreen All apps (fullscreen button)

URL Patterns

Default (Demo Mode)

/online/webapp/{app-name}

Opens the app with its default demo arguments (if configured). For Photo Editor this loads a sample image.

Open a Remote URL

/online/webapp/{app-name}/url/{base64-encoded-url}

The third segment is the URL base64-encoded with btoa():

var url = "https://example.com/image.jpg";
var src = "https://sgapps.io/online/webapp/photo-editor/url/" + btoa(url);

Open a File by Path

/online/webapp/{app-name}/file/{base64-encoded-path}

For authenticated users, opens a file from their GridFS storage.

Embed Mode

By default, the app renders with window chrome (title bar, controls). For a cleaner embed, enable embed mode via the WindowSocket API:

socket.fire("webapp::instance::embed-mode", true, function (err, isEmbedded) {
    // App is now maximized with no title bar
});

This is a compound preset that:

Disable embed mode:

socket.fire("webapp::instance::embed-mode", false, function (err, isEmbedded) {
    // Window chrome is restored
});

Granular Window Chrome Controls

For finer control than the all-or-nothing embed-mode preset, each chromeaspect is exposed on its own WindowSocket channel. Pass a boolean to setthe state, omit it to read the current state. Every endpoint returns theresulting state via cb(null, state). These are composable with embed-mode.

Channel Effect
webapp::instance::window:controls Show/hide the entire title bar (win.controls(bool))
webapp::instance::window:header Show/hide the header: icon + title + action buttons (win.controlsHeader(bool))
webapp::instance::window:header-actions Show/hide the close/minimize/maximize/fullscreen buttons only, keep the title (win.controlsHeaderActions(bool))
webapp::instance::window:footer Show/hide the footer strip (win.controlsFooter(bool))
webapp::instance::window:maximize Enter fullscreen (true → win.maximize(true)) / exit fullscreen (false → win.restore())

Examples

Keep title bar, drop only the window buttons:

socket.fire("webapp::instance::window:header-actions", false, function (err, state) {
    // close/minimize/maximize/fullscreen icons gone, title still visible
});

Toggle fullscreen on demand (no header change):

// Enter fullscreen
socket.fire("webapp::instance::window:maximize", true);

// Exit fullscreen
socket.fire("webapp::instance::window:maximize", false);

Read current state without changing it:

socket.fire("webapp::instance::window:footer", null, function (err, isFooterVisible) {
    console.log("footer visible:", isFooterVisible);
});

Minimal chrome (full canvas, keep the fullscreen escape button):

socket.fire("webapp::instance::window:header", true);          // keep header
socket.fire("webapp::instance::window:header-actions", true);  // keep buttons
socket.fire("webapp::instance::window:footer", false);         // drop footer
socket.fire("webapp::instance::window:maximize", true);        // fullscreen

Binary Payloads (Blobs, Files)

The WindowSocket bridge serializes arguments through its own typed encoder(not postMessage structured clone directly). The encoder understands:

Blob and File instances are not recognized — they fall into the plain-objectbranch, for..in yields no enumerable keys, and the receiver sees &#x7B;&#x7D; withno bytes. Convert client-side before fire:

file.arrayBuffer().then(function (buffer) {
    socket.fire("webapp::instance::request", "fs:write",
        "input://uploads/" + file.name, buffer,
        function (err) { /* ... */ }
    );
});

The receiving handlers (e.g. file-manager's fs:write) wrap incomingArrayBuffers in new Blob([content]) internally, so a plain-string payloadalso works for text content:

socket.fire("webapp::instance::request", "fs:write",
    "input://notes/hello.txt", "plain text content",
    function (err) { /* ... */ }
);

Responsive Sizing

The webapp container auto-resizes to fill the iframe viewport. A polling loop (500ms) adjusts the container height to account for the top bar and footer. For best results, set the iframe to fill its parent:

iframe {
    width: 100%;
    height: 100vh;
    border: none;
}

In-page (no iframe)

For integrations that need SGApps apps inside your page -- same JS globals, shared stylesheet, zero postMessage latency, multi-window desktop UI -- load exporter.js from the SGApps CDN. It bootstraps the full client runtime (ApplicationPrototype core, module aliases, icons, translator, optional WindowSocket + GridFS) and exposes a global Application(callback) fired as soon as the stack is ready. The script detects its own src at runtime, so RootHost is always correct even when your page lives on a different domain.

Minimal boilerplate

<!doctype html>
<html>
<body>
    <div id="wm-root" style="position:relative;height:100vh;"></div>

    <script src="https://sgapps.io/scripts/modules-webapp/exporter.js"></script>
    <script>
    Application(function (App) {
        // Load the full stack (styles + modules + libs + WindowSocket + fs + i18n).
        // Lighter variants exist -- see Window Manager docs.
        App["exporter:configuration:full"]().then(function () {
            App.require(["wm :: sgapps-webapps.b2b.window-manager"]).then(function (libs) {
                // Mount the WM's DOM host wherever you want windows to live.
                document.getElementById("wm-root").appendChild(libs.wm.root());

                // Open an app directly -- no iframe, no URL, no base64 payload.
                libs.wm.open({
                    run: ["sgapps/applications/code-editor"],
                    SKIP_APP_LIBRARY: true,
                    width: 900,
                    height: 600
                });
            });
        });
    });
    </script>
</body>
</html>

Opening any app

Pass the module path as the first entry of run. The same arguments the URL-mode exposes work here as positional strings:

// Code Editor with a preloaded file
libs.wm.open({
    run: ["sgapps/applications/code-editor", "input://notes.md"],
    SKIP_APP_LIBRARY: true
});

// Writer with a .docx (binary, read client-side via the SGApps fs)
libs.wm.open({
    run: ["sgapps/applications/writer", "input://report.docx"],
    SKIP_APP_LIBRARY: true
});

// Photo Editor with a sample image, custom size
libs.wm.open({
    run: ["sgapps/applications/photo-editor", "web://images/sample.jpg"],
    SKIP_APP_LIBRARY: true,
    width: 1200, height: 800
});

Paths accepted by apps that take a file:

Direct API calls -- no WindowSocket

In-page mode skips the postMessage relay entirely. The handle returned by wm.open(...) is the live WindowApplication -- you call its methods directly:

var win = libs.wm.open({ run: ["sgapps/applications/code-editor"], SKIP_APP_LIBRARY: true });

// Wait for the app inside the window to be fully constructed.
win.on("api-request::attached", function () {
    // Every `api-request::instance:*` event documented in the per-app pages
    // works here too -- emit it on the window directly.
    win.emit("api-request::instance:active-file:codeEditor:value",
        "console.log('hello from in-page');",
        function (err) { if (err) console.error(err); }
    );
});

The event names (active-file:codeEditor:value, fs:write, files:open, …) are identical to the ones listed in each app's docs page under Events Reference -- the only difference is you emit them on the win handle instead of firing them through a WindowSocket relay. Callbacks are the same (err, result) node-style shape.

Ready-made wrappers (iframe + WindowSocket, API-only -- no window chrome)

Sometimes you want an app rendered as a plain DOM subtree -- no WindowManager, no title bar, no window buttons -- but still want the postMessage/WindowSocket API to drive it. That's what the sgapps-webapps.b2b.* aliases below give you: each one is a thin factory producing an ApplicationPrototype whose .node() is a custom-element host containing a single &#x3C;iframe&#x3E; pointed at /online/webapp/&#x3C;slug&#x3E;. When the embedded app pings back, the factory flips it into embed-mode and resolves a Promise exposing the live WindowSocket client. Zero chrome, zero wm, full API.

<script src="https://sgapps.io/scripts/modules-webapp/exporter.js"></script>
<script>
Application(function (App) {
    App["exporter:configuration:full"]().then(function () {
        App.require([
            "writer :: sgapps-webapps.b2b.editor.writer"
        ]).then(function (libs) {
            var writerApp = libs.writer();            // ApplicationPrototype
            document.body.appendChild(writerApp.node());  // mount the host
            writerApp.editor().then(function (editor) {
                // editor = { node: <iframe>, window: iframeContentWindow,
                //            client: WindowSocket-Client, visible: fn(state) }
                editor.visible(true);                 // show + require-redraw

                // Same `api-request::instance:*` events as iframe mode.
                editor.client.fire(
                    "webapp::instance::request",
                    "setContent",
                    "<h1>Hello from an embedded writer</h1>",
                    function (err) { if (err) console.error(err); }
                );
            });
        });
    });
});
</script>

The handle shape is identical across every wrapper:

Available aliases:

Alias Opens Per-app events
sgapps-webapps.b2b.editor.writer /online/webapp/writer Writer events
sgapps-webapps.b2b.editor.spreadsheet /online/webapp/spreadsheet Spreadsheet events
sgapps-webapps.b2b.editor.code-editor /online/webapp/code-editor Code Editor events
sgapps-webapps.b2b.editor.photo-editor /online/webapp/photo-editor Photo Editor events
sgapps-webapps.b2b.editor.gcode-editor /online/webapp/gcode-editor GCode Editor events
sgapps-webapps.b2b.viewer.pdf-browser /online/webapp/pdf-browser PDF Browser events
sgapps-webapps.b2b.viewer.markdown-browser /online/webapp/markdown-browser Markdown Browser events
sgapps-webapps.b2b.file-manager /online/webapp/file-manager File Manager events

All eight share one shared factory internally -- the per-wrapper files (modules--sgapps-web-apps/editors/writer.js, etc.) are each ~8 lines pulling it in via module.require([&#x22;x :: ../_create-webapp-wrapper&#x22;]). For a route not on the list, the plain iframe embed + WindowSocket still works -- or ask us to publish a new b2b.* alias for it.
When to pick a ready-made wrapper vs. the raw wm.open(...) call -- the wrapper is lighter (no window chrome, no window-manager singleton) and keeps the iframe round-trip, so you still get sandboxing. wm.open has no sandboxing (apps share your JS globals) but has zero postMessage overhead and lets you compose a desktop with several SGApps' own apps + your own custom apps in one WindowManager. Pick the wrapper for a single drop-in app embedded as a plain DOM subtree; pick wm.open for a multi-window product.

Lighter footprint

exporter:configuration:full bundles seven independent loaders. If you don't need the WindowSocket bridge (iframe embedding), the GridFS client, or the translator, load only the pieces you use. See Window Manager -- Piecewise loading for the full list and dependency chain.

When to pick which mode

Cross-Origin Considerations

The apps are hosted on sgapps.io. When embedding in iframe mode from a different domain, the WindowSocket API uses postMessage which works cross-origin -- no special CORS headers are needed for the iframe itself. If you load files from your domain into the app, they must be accessible via a public URL.
In-page mode is also cross-origin friendly: exporter.js loads all its own assets from sgapps.io (CORS enabled on the SGApps CDN), so you can drop the &#x3C;script src=&#x22;https://sgapps.io/scripts/modules-webapp/exporter.js&#x22;&#x3E; tag on any domain and it works.

App Name Reference

The URL slug is used for iframe mode (/online/webapp/&#x7B;slug&#x7D;). The module path is used for in-page mode (wm.open(&#x7B;run:[&#x22;&#x7B;path&#x7D;&#x22;]&#x7D;)).

App Name URL Slug In-page Module Path
Photo Editor photo-editor sgapps/applications/photo-editor
SVG Editor svg-editor sgapps/applications/svg-editor
Code Editor code-editor sgapps/applications/code-editor
Writer writer sgapps/applications/writer
Spreadsheet spreadsheet sgapps/applications/spreadsheet
Archive Viewer archive-viewer sgapps/applications/archive-viewer
Media Player media-player sgapps/applications/media-player
Recorder recorder sgapps/applications/recorder
File Manager file-manager sgapps/applications/file-manager
Presentation presentation sgapps/applications/presentation
GCode Editor gcode-editor sgapps/applications/gcode-editor
CNC GCode Simulator cnc-gcode-simulator sgapps/applications/cnc-gcode-simulator
ChatBox chatbox sgapps/applications/chatbox
SVFS Manager svfs-manager sgapps/applications/fs/svfs-manager
SSH Web Client ssh-web-client sgapps/applications/ssh-web-client

sgapps.io