You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/public/js/builder.js

123 lines
4.7 KiB

// Self-hosted embed shim for the <docuseal-builder> web component.
// Upstream `EmbedScriptsController#show` returns an "Upgrade to Pro" stub;
// the Rails static middleware serves this file ahead of that route, so the
// real shim wins without touching the controller.
//
// What this does:
// 1. Reads the JWT minted by the embedding app from `data-token`.
// 2. Decodes its payload to discover `document_urls`, `name`,
// `external_id`, and `template_id`.
// 3. Iframes either `/templates/:id/edit` (existing template) or
// `/new?url=...&filename=...&external_id=...` (fresh upload flow).
// 4. Relays the inner builder's `save` postMessage up as a DOM
// `CustomEvent('save')` on the outer element so `@docuseal/react`'s
// `onSave` callback fires.
//
// JWT signature is NOT verified here — host DocuSeal session cookies carry
// auth, and the calling app signs with the shared secret it controls.
(function () {
function decodeJwtPayload(token) {
try {
var part = token.split('.')[1]
if (!part) return null
var b64 = part.replace(/-/g, '+').replace(/_/g, '/')
var pad = b64.length % 4
if (pad) b64 += '='.repeat(4 - pad)
return JSON.parse(decodeURIComponent(escape(atob(b64))))
} catch (e) {
return null
}
}
// Discover the DocuSeal host from this script's own src so embedders
// don't have to set data-host on every element.
var SCRIPT_HOST = (function () {
try {
var current = document.currentScript
var src = current ? current.src : ''
if (!src) {
var all = document.getElementsByTagName('script')
for (var i = 0; i < all.length; i++) {
if (/\/js\/(builder|form)\.js/.test(all[i].src)) {
src = all[i].src
break
}
}
}
return src ? new URL(src).host : ''
} catch (e) {
return ''
}
})()
// Relay `save` postMessage from the iframe (templates_builder fires it
// after a successful manual SAVE) up as a DOM `save` CustomEvent on the
// outer <docuseal-builder> element so React listeners can close the
// embedding sheet.
if (!window.__docusealEmbedListenerInstalled) {
window.__docusealEmbedListenerInstalled = true
window.addEventListener('message', function (ev) {
var d = ev && ev.data
if (!d || d.source !== 'docuseal-embed' || d.type !== 'save') return
document.querySelectorAll('docuseal-builder, docuseal-form').forEach(function (el) {
el.dispatchEvent(new CustomEvent('save', { detail: { template_id: d.template_id } }))
})
})
}
var EmbedBuilder = class extends HTMLElement {
static get observedAttributes() {
return ['data-token', 'data-host']
}
connectedCallback() {
this._maybeMount()
}
attributeChangedCallback() {
this._maybeMount()
}
_maybeMount() {
if (this._mounted) return
var token = this.getAttribute('data-token') || ''
if (!token) return
this._mounted = true
var host = this.getAttribute('data-host') || SCRIPT_HOST || window.location.host
var payload = decodeJwtPayload(token) || {}
var docUrl = (payload.document_urls && payload.document_urls[0]) || ''
var name = payload.name || 'Untitled'
var filename = name.replace(/[^A-Za-z0-9_\-]+/g, '_') + '.pdf'
// Match parent page scheme. Both ends run TLS locally (mkcert), so
// https parent -> https iframe with no mixed-content blocking.
var origin = window.location.protocol + '//' + host
var src
if (payload.template_id) {
// Existing template: open the editor directly so prior field
// edits are preserved. Posting to /new would create another
// template and orphan the original.
src = origin + '/templates/' + encodeURIComponent(payload.template_id) + '/edit'
} else {
var qs = new URLSearchParams()
if (docUrl) qs.set('url', docUrl)
qs.set('filename', filename)
if (payload.external_id) qs.set('external_id', payload.external_id)
src = origin + '/new?' + qs.toString()
}
var iframe = document.createElement('iframe')
iframe.src = src
iframe.style.cssText = 'width:100%;height:100%;min-height:600px;border:0;display:block'
iframe.setAttribute('allow', 'clipboard-write')
this.style.display = 'block'
this.style.height = this.style.height || '100%'
this.appendChild(iframe)
}
}
if (!window.customElements.get('docuseal-builder')) {
window.customElements.define('docuseal-builder', EmbedBuilder)
}
if (!window.customElements.get('docuseal-form')) {
window.customElements.define('docuseal-form', class extends EmbedBuilder {})
}
})()