/**
* IF MEDIA INC. — VBOUT CRM Bridge v2
* JS · Divi > Theme Options > Integration > Body Code
* OR enqueued via child theme (preferred)
*
* Single public namespace: window.IFSForms
*
* Modules
* ───────
* 1. CONFIG — all constants and thresholds
* 2. EVENTS — internal pub/sub lifecycle bus
* 3. SDK LOADER — idempotent async script injection
* 4. FIELD BRIDGE — polls for live VBOUT fields, builds registry
* 5. OBSERVER — MutationObserver-based success detection
* 6. SUBMIT ENGINE — public API, data injection, state machine
*/(function () {
'use strict';/* ─────────────────────────────────────────────────────────────
1. CONFIG
───────────────────────────────────────────────────────────── */
var CONFIG = {
SCRIPT_SRC: 'https://www.vbt.io/ext/vbtforms.js?lang=en',
SCRIPT_ID: 'ifs-vbout-sdk',
SCRIPT_CHARSET: 'utf-8',
BRIDGE_SELECTOR: '.ifs-crm-bridge',
FORM_SELECTOR: '.ifs-crm-form',
FORM_KEY_ATTR: 'data-form-key',
BRIDGE_STATE_ATTR: 'data-bridge-state',/* How long to poll for VBOUT fields after SDK loads */
POLL_INTERVAL_MS: 200,
POLL_TIMEOUT_MS: 12000,/* Max wait for VBOUT SDK script to load */
SDK_TIMEOUT_MS: 10000,/* requestIdleCallback budget */
IDLE_TIMEOUT_MS: 3000,/*
* How long to observe the VBOUT wrapper for a success signal
* after submit.click() fires. If nothing is detected in this
* window, we emit 'submitted_unconfirmed' instead of 'success'.
*/
SUCCESS_TIMEOUT_MS: 8000,/*
* Text/class patterns VBOUT uses to signal a successful
* submission. These are observed in the wrapper's DOM.
* Extend this list if VBOUT's output changes.
*/
SUCCESS_TEXT_PATTERNS: [
'thank you',
'thanks',
'submitted',
'received',
'success',
'confirmation',
],
SUCCESS_CLASS_PATTERNS: [
'success',
'confirmation',
'thank',
'submitted',
],
};/* ─────────────────────────────────────────────────────────────
2. EVENTS
Internal pub/sub. Errors in subscribers never crash the bridge.
Lifecycle events:
sdk:loaded | sdk:error
form:ready | form:error
bridge:ready
ready | error | submit | submitting | success | submitted_unconfirmed
───────────────────────────────────────────────────────────── */
var Events = (function () {
var listeners = {};function on(event, fn) {
if (typeof fn !== 'function') return;
(listeners[event] = listeners[event] || []).push(fn);
}function off(event, fn) {
if (!listeners[event]) return;
listeners[event] = listeners[event].filter(function (f) { return f !== fn; });
}function emit(event, payload) {
(listeners[event] || []).forEach(function (fn) {
try { fn(payload); } catch (_) {}
});
}return { on: on, off: off, emit: emit };
})();/* ─────────────────────────────────────────────────────────────
3. SDK LOADER
Injects the VBOUT script once. Guards against Divi partial
re-renders, WP ajax navigation, and double-execution.
───────────────────────────────────────────────────────────── */
var SDKLoader = (function () {
var loaded = false;
var pending = false;
var timer = null;function load() {
if (document.getElementById(CONFIG.SCRIPT_ID)) {
loaded = true;
Events.emit('sdk:loaded');
return;
}
if (pending || loaded) return;
pending = true;var script = document.createElement('script');
script.id = CONFIG.SCRIPT_ID;
script.src = CONFIG.SCRIPT_SRC;
script.async = true;
script.charset = CONFIG.SCRIPT_CHARSET;timer = setTimeout(function () {
if (!loaded) {
pending = false;
Events.emit('sdk:error', { reason: 'timeout' });
}
}, CONFIG.SDK_TIMEOUT_MS);script.onload = function () {
clearTimeout(timer);
loaded = pending = false;
loaded = true;
Events.emit('sdk:loaded');
};script.onerror = function () {
clearTimeout(timer);
pending = false;
Events.emit('sdk:error', { reason: 'load_error' });
};document.body.appendChild(script);
}return { load: load };
})();/* ─────────────────────────────────────────────────────────────
4. FIELD BRIDGE
Polls each .ifs-crm-form wrapper until VBOUT has injected
its real /