PageSourceSearch

https://superedit.ai/sdk/v1/superedit.js

js superedit.ai collected 2026-10-05 15:08:58 UTC 12,501 bytes, 301 lines download raw bytes

1/**
2 * SuperEdit SDK v2.0.0 (protocol 2)
3 * Embed SuperEdit canvas editor in any web application.
4 *
5 * Usage:
6 *   <script src="https://superedit.ai/sdk/v1/superedit.js"></script>
7 *   <script>
8 *     const editor = SuperEdit.open({
9 *       mode: "tab",  // "iframe" | "popup" | "tab"
10 *       designUrl: "https://example.com/my-image.png",
11 *       appName: "My App",
12 *       apiKey: "se_live_...",
13 *       onSave: (result) => {
14 *         console.log(result.imageDataUrl); // base64 PNG
15 *         console.log(result.r2Url);        // server-side backup URL (may be null)
16 *         console.log(result.canvasJson);   // fabric.js JSON
17 *         console.log(result.transparent);  // boolean — was BG transparent?
18 *       },
19 *       onError: (err) => console.warn(err.code, err.message),
20 *       onClose: () => console.log("Editor closed"),
21 *     });
22 *   </script>
23 *
24 * Methods:
25 *   editor.close()         - Close the editor
26 *   editor.loadImage(url)  - Load a new image into the editor
27 *   editor.loadJson(json)  - Load a fabric.js JSON state
28 *
29 * Events (via postMessage, all carry protocol: 2):
30 *   superedit:ready  - Editor is loaded and ready
31 *   superedit:save   - User clicked "Save & Return" (imageDataUrl, r2Url, canvasJson, width, height, transparent)
32 *   superedit:close  - Editor requests close (after save) — SDK closes the overlay automatically
33 *   superedit:error  - Something failed in the editor ({ code, message }) → onError
34 *
35 * Error codes: ready_timeout, design_load_failed, embed_blocked
36 *
37 * Modes:
38 *   iframe (default)  - Opens inside a fullscreen overlay iframe on the current page
39 *   popup             - Opens in a popup window (centered, configurable size)
40 *   tab               - Opens in a new browser tab (recommended for 3rd-party integrations)
41 */
42
43(function (global) {
44  "use strict";
45
46  var SUPEREDIT_HOST = "https://superedit.ai";
47
48  // Allow override for development
49  if (global.__SUPEREDIT_HOST__) {
50    SUPEREDIT_HOST = global.__SUPEREDIT_HOST__;
51  }
52
53  /**
54   * @param {Object} options
55   * @param {string} [options.designUrl]   - URL of the image to edit
56   * @param {string} [options.appName]     - Your app name (shown in "Save & Return to {appName}")
57   * @param {string} [options.returnUrl]   - URL to redirect after save (optional, for popup mode)
58   * @param {string} [options.apiKey]      - Your SuperEdit API key (for analytics & access control)
59   * @param {string} [options.container]   - CSS selector for iframe container (default: fullscreen overlay)
60   * @param {string} [options.mode]        - "iframe" | "popup" | "tab" (default: "iframe")
61   * @param {number} [options.width]       - Popup width (default: 1200)
62   * @param {number} [options.height]      - Popup height (default: 800)
63   * @param {Function} [options.onReady]   - Called when editor is ready
64   * @param {Function} [options.onSave]    - Called with { imageDataUrl, canvasJson, width, height }
65   * @param {Function} [options.onClose]   - Called when editor is closed
66   */
67  function open(options) {
68    options = options || {};
69    var mode = options.mode || "iframe";
70
71    // Build embed URL — large data: URIs are uploaded via POST first
72    var params = new URLSearchParams();
73    var deferredDesignUrl = null;
74
75    if (options.designUrl) {
76      if (options.designUrl.indexOf("data:") === 0) {
77        // Large base64 data — upload via POST, open with token
78        deferredDesignUrl = options.designUrl;
79      } else {
80        params.set("designUrl", options.designUrl);
81      }
82    }
83    if (options.appName) params.set("appName", options.appName);
84    if (options.returnUrl) params.set("returnUrl", options.returnUrl);
85    if (options.apiKey) params.set("apiKey", options.apiKey);
86    params.set("origin", global.location.origin);
87
88    // For large data URIs: open editor immediately (short URL), then send design
89    // via postMessage after editor signals ready. This avoids URL length limits
90    // and cross-origin sync XHR issues.
91    var url = SUPEREDIT_HOST + "/embed?" + params.toString();
92
93    var iframe = null;
94    var popup = null;
95    var overlay = null;
96    var closed = false;
97    var readyReceived = false;
98    var readyTimer = null;
99
100    function emitError(code, message) {
101      if (options.onError) {
102        try { options.onError({ code: code, message: message }); } catch (e) {}
103      }
104    }
105
106    // ─── postMessage handler ─────────────────────────────
107    function onMessage(ev) {
108      // In dev, allow localhost origins
109      if (ev.origin !== SUPEREDIT_HOST && ev.origin !== global.location.origin) return;
110      var msg = ev.data;
111      if (!msg || typeof msg !== "object" || msg.source !== "superedit") return;
112      // msg.protocol: 2 on current editors; absent on older ones — both accepted
113
114      if (msg.type === "superedit:ready") {
115        readyReceived = true;
116        if (readyTimer) { clearTimeout(readyTimer); readyTimer = null; }
117        // If we have a deferred large designUrl, send it now via postMessage
118        if (deferredDesignUrl) {
119          var target = iframe ? iframe.contentWindow : popup;
120          if (target) {
121            target.postMessage({
122              source: "superedit",
123              protocol: 2,
124              type: "superedit:load-design",
125              designUrl: deferredDesignUrl,
126            }, SUPEREDIT_HOST);
127          }
128          deferredDesignUrl = null; // only send once
129        }
130        if (options.onReady) options.onReady();
131      }
132
133      if (msg.type === "superedit:save" && options.onSave) {
134        options.onSave({
135          imageDataUrl: msg.imageDataUrl,
136          canvasJson: msg.canvasJson,
137          width: msg.width,
138          height: msg.height,
139          transparent: !!msg.transparent,
140          r2Url: msg.r2Url || null,
141        });
142      }
143
144      // Editor asks us to close (e.g. after save in iframe mode where
145      // window.close() is a no-op)
146      if (msg.type === "superedit:close") {
147        instance.close();
148      }
149
150      if (msg.type === "superedit:error") {
151        emitError(msg.code || "unknown", msg.message || "SuperEdit error");
152      }
153    }
154
155    global.addEventListener("message", onMessage);
156
157    // Ready-handshake watchdog: if the editor never signals ready, surface
158    // it instead of failing silently.
159    var readyTimeoutMs = options.readyTimeoutMs || 20000;
160    readyTimer = setTimeout(function () {
161      if (!readyReceived && !closed) {
162        emitError("ready_timeout", "SuperEdit editor did not become ready within " + readyTimeoutMs + "ms");
163      }
164    }, readyTimeoutMs);
165
166    // ─── iframe mode ─────────────────────────────────────
167    if (mode === "iframe") {
168      // Create fullscreen overlay or attach to container
169      if (options.container) {
170        var container = document.querySelector(options.container);
171        if (!container) throw new Error("SuperEdit: container not found: " + options.container);
172        iframe = document.createElement("iframe");
173        iframe.src = url;
174        iframe.style.cssText = "width:100%;height:100%;border:none;";
175        iframe.setAttribute("allow", "clipboard-write");
176        container.innerHTML = "";
177        container.appendChild(iframe);
178      } else {
179        // Fullscreen overlay
180        overlay = document.createElement("div");
181        overlay.style.cssText =
182          "position:fixed;top:0;left:0;right:0;bottom:0;z-index:999999;" +
183          "background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;" +
184          "backdrop-filter:blur(4px);animation:se-fade-in 0.2s ease-out;";
185        overlay.innerHTML =
186          '<style>@keyframes se-fade-in{from{opacity:0}to{opacity:1}}@keyframes se-slide-up{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}</style>';
187
188        var frame = document.createElement("div");
189        frame.style.cssText =
190          "width:95vw;height:92vh;max-width:1400px;border-radius:16px;overflow:hidden;" +
191          "box-shadow:0 25px 60px rgba(0,0,0,0.3);animation:se-slide-up 0.25s ease-out;position:relative;";
192
193        // Close button on overlay
194        var closeBtn = document.createElement("button");
195        closeBtn.innerHTML = "&#x2715;";
196        closeBtn.style.cssText =
197          "position:absolute;top:12px;right:12px;z-index:10;width:32px;height:32px;" +
198          "border-radius:50%;border:none;background:rgba(0,0,0,0.5);color:#fff;" +
199          "font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;" +
200          "transition:background 0.2s;";
201        closeBtn.onmouseover = function () { closeBtn.style.background = "rgba(0,0,0,0.7)"; };
202        closeBtn.onmouseout = function () { closeBtn.style.background = "rgba(0,0,0,0.5)"; };
203        closeBtn.onclick = function () { instance.close(); };
204
205        iframe = document.createElement("iframe");
206        iframe.src = url;
207        iframe.style.cssText = "width:100%;height:100%;border:none;";
208        iframe.setAttribute("allow", "clipboard-write");
209
210        frame.appendChild(closeBtn);
211        frame.appendChild(iframe);
212        overlay.appendChild(frame);
213        document.body.appendChild(overlay);
214
215        // Close on backdrop click
216        overlay.addEventListener("click", function (e) {
217          if (e.target === overlay) instance.close();
218        });
219
220        // Close on Escape
221        var onEsc = function (e) {
222          if (e.key === "Escape") instance.close();
223        };
224        global.addEventListener("keydown", onEsc);
225      }
226    }
227
228    // ─── popup mode ──────────────────────────────────────
229    if (mode === "popup") {
230      var w = options.width || 1200;
231      var h = options.height || 800;
232      var left = (screen.width - w) / 2;
233      var top = (screen.height - h) / 2;
234      popup = global.open(url, "superedit", "width=" + w + ",height=" + h + ",left=" + left + ",top=" + top + ",resizable=yes,scrollbars=no");
235
236      // Poll for popup close
237      var pollTimer = setInterval(function () {
238        if (popup && popup.closed) {
239          clearInterval(pollTimer);
240          if (!closed && options.onClose) options.onClose();
241          closed = true;
242          global.removeEventListener("message", onMessage);
243        }
244      }, 500);
245    }
246
247    // ─── tab mode (new browser tab) ─────────────────────
248    if (mode === "tab") {
249      popup = global.open(url, "_blank");
250
251      // Poll for tab close
252      var tabPollTimer = setInterval(function () {
253        if (popup && popup.closed) {
254          clearInterval(tabPollTimer);
255          if (!closed && options.onClose) options.onClose();
256          closed = true;
257          global.removeEventListener("message", onMessage);
258        }
259      }, 500);
260    }
261
262    // ─── instance API ────────────────────────────────────
263    var instance = {
264      close: function () {
265        if (closed) return;
266        closed = true;
267        if (readyTimer) { clearTimeout(readyTimer); readyTimer = null; }
268        global.removeEventListener("message", onMessage);
269        if (onEsc) global.removeEventListener("keydown", onEsc);
270        if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
271        if (popup && !popup.closed) popup.close();
272        if (options.onClose) options.onClose();
273      },
274
275      loadImage: function (imageUrl) {
276        var target = iframe ? iframe.contentWindow : popup;
277        if (!target) return;
278        target.postMessage({ source: "superedit", protocol: 2, type: "superedit:load", imageUrl: imageUrl }, SUPEREDIT_HOST);
279      },
280
281      loadJson: function (json) {
282        var target = iframe ? iframe.contentWindow : popup;
283        if (!target) return;
284        target.postMessage({ source: "superedit", protocol: 2, type: "superedit:loadJson", json: json }, SUPEREDIT_HOST);
285      },
286
287      getIframe: function () { return iframe; },
288      getPopup: function () { return popup; },
289    };
290
291    return instance;
292  }
293
294  // ─── Expose global ────────────────────────────────────
295  global.SuperEdit = {
296    open: open,
297    version: "2.0.0",
298    protocol: 2,
299  };
300
301})(typeof window !== "undefined" ? window : this);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.