UNPKG

@birdie-so/snippet

Version:

Helper for integrating the Birdie screen recording snippet into modern JavaScript apps. Requires a Birdie account.

301 lines (240 loc) 9.66 kB
(function () { try { if (typeof window === 'undefined' || typeof document === 'undefined') return; var params = new URLSearchParams(window.location.search); var recordingUrl = params.get('birdie-recording'); if (!recordingUrl) return; try { var parsed = new URL(decodeURIComponent(recordingUrl)); if (parsed.protocol !== 'https:') return; if (parsed.searchParams.get('birdie-embed') !== '1') return; } catch (e) { return; } if (!document.getElementById('birdie-recording-preload-style')) { var style = document.createElement('style'); style.id = 'birdie-recording-preload-style'; style.textContent = ` html, body { overflow: hidden !important; } #birdie-recording-preload-overlay { position: fixed; inset: 0; background: #fff; z-index: 2147483647; pointer-events: auto; } `; (document.head || document.documentElement).appendChild(style); } if (!document.getElementById('birdie-recording-preload-overlay')) { var overlay = document.createElement('div'); overlay.id = 'birdie-recording-preload-overlay'; if (document.documentElement) { document.documentElement.appendChild(overlay); } } } catch (e) { } })(); let _readyCallbacks = []; function ensureBirdieSettings() { if (typeof window === 'undefined') return {}; if (!window.birdieSettings) { window.birdieSettings = {}; } return window.birdieSettings; } function normalizeContact(contact, otherSettings = {}) { if (contact && typeof contact === 'object') { return contact; } const legacyContact = {}; if (typeof otherSettings.contact_id !== 'undefined') legacyContact.id = otherSettings.contact_id; if (typeof otherSettings.contact_name !== 'undefined') legacyContact.name = otherSettings.contact_name; if (typeof otherSettings.contact_email !== 'undefined') legacyContact.email = otherSettings.contact_email; if (typeof otherSettings.contact_phone !== 'undefined') legacyContact.phone = otherSettings.contact_phone; return Object.keys(legacyContact).length ? legacyContact : undefined; } function applyBirdieRuntimeUpdate({ metadata, contact } = {}) { if (typeof window === 'undefined') return; const settings = ensureBirdieSettings(); if (typeof metadata !== 'undefined') { settings.metadata = metadata; } if (typeof contact !== 'undefined') { settings.contact = contact; } if (!window.birdie) return; if (typeof metadata !== 'undefined') { if (typeof window.birdie.setMetadata === 'function') { window.birdie.setMetadata(metadata); } else { window.birdie.metadata = metadata; } } if (typeof contact !== 'undefined') { if (typeof window.birdie.setContact === 'function') { window.birdie.setContact(contact); } else { window.birdie.contact = contact; } } } function injectBirdieScript(clientId) { const existing = document.getElementById('birdie-snippet'); if (existing) return existing; const embed_path = clientId == 'e4bf56a2' ? 'embed_birdie' : 'embed'; // Load the loader straight from CloudFront. app.birdie.so/widget/embed/* is // 302-redirected to cf.birdie.so by the ALB, so hitting cf directly skips // that hop — and makes the preconnect/preload below target the host the // script is actually fetched from (previously they warmed/preloaded // app.birdie.so, i.e. only the redirect, not the script). const SNIPPET_HOST = 'https://cf.birdie.so'; const scriptUrl = `${SNIPPET_HOST}/widget/${embed_path}/${clientId}?_bh=${encodeURIComponent(window.location.hostname)}`; if (document.head) { if (!document.querySelector('link[data-birdie-preconnect]')) { const preconnect = document.createElement('link'); preconnect.rel = 'preconnect'; preconnect.href = SNIPPET_HOST; preconnect.crossOrigin = 'anonymous'; preconnect.setAttribute('data-birdie-preconnect', 'true'); document.head.appendChild(preconnect); } if (!document.querySelector('link[data-birdie-preload]')) { const preload = document.createElement('link'); preload.rel = 'preload'; preload.as = 'script'; preload.href = scriptUrl; preload.setAttribute('data-birdie-preload', 'true'); document.head.appendChild(preload); } } const script = document.createElement('script'); script.src = scriptUrl; script.async = true; script.id = 'birdie-snippet'; script.referrerPolicy = 'origin'; const firstScript = document.scripts?.[0]; if (firstScript?.parentNode) { firstScript.parentNode.insertBefore(script, firstScript); } else if (document.head) { document.head.appendChild(script); } else { document.documentElement.appendChild(script); } return script; } export function initBirdie({ clientId, metadata, contact, onReady, singleHandlerPerEvent = true, ...otherSettings }) { if (!clientId) { console.error('[Birdie] Missing required clientId in initBirdie()'); return; } if (typeof window === 'undefined') return; // Always predefine birdieSettings first (or reuse if already present) // if (!window.birdieSettings) { // window.birdieSettings = {}; // } // Assign metadata and other settings first // window.birdieSettings.metadata = metadata; const settings = ensureBirdieSettings(); const normalizedContact = normalizeContact(contact, otherSettings); settings.metadata = metadata; if (typeof normalizedContact !== 'undefined') { settings.contact = normalizedContact; } Object.assign(window.birdieSettings, otherSettings); window.birdieSettings.onBirdieReady = () => { try { installBirdieGuards({ singleHandlerPerEvent }); } catch (e) { console.error('[Birdie] guard install error', e); } _readyCallbacks.forEach(cb => { try { cb(window.birdie); } catch (e) { console.error('[Birdie] onReady error', e); } }); _readyCallbacks = []; }; // Queue the onReady handler if (onReady) { registerBirdieReadyCallback(onReady); } // Inject script if not already // if (!document.getElementById('birdie-snippet')) { // const script = document.createElement('script'); // const embed_path = clientId == 'e4bf56a2' ? 'embed_birdie' : 'embed' // script.src = `https://app.birdie.so/widget/${embed_path}/${clientId}`; // script.async = true; // script.id = 'birdie-snippet'; // script.referrerPolicy = "origin"; // document.body.appendChild(script); // } injectBirdieScript(clientId); // if (!document.getElementById('birdie-snippet')) { // const script = document.createElement('script'); // script.src = `https://birdie.eu.ngrok.io/snippet/index.local.min.js`; // script.async = true; // script.id = 'birdie-snippet'; // document.body.appendChild(script); // } } function registerBirdieReadyCallback(callback) { if (typeof window === 'undefined') return; if (window.birdie) { callback(window.birdie); } else { _readyCallbacks.push(callback); } } export function getBirdieInstance(onReady) { if (typeof window === 'undefined') return null; if (window.birdie) { onReady?.(window.birdie); return window.birdie; } else { registerBirdieReadyCallback(onReady); return null; } } export function setBirdieMetadata(metadata) { applyBirdieRuntimeUpdate({ metadata }); } export function setBirdieContact(contact) { applyBirdieRuntimeUpdate({ contact }); } export function updateBirdie({ metadata, contact } = {}) { applyBirdieRuntimeUpdate({ metadata, contact }); } // ---- guards & helpers ---- function installBirdieGuards({ singleHandlerPerEvent }) { const b = window.birdie; if (!b || b.__pkgGuardsInstalled) return; // Keep originals const originalOn = b.on?.bind(b); const originalOff = b.off?.bind(b); // Registry of the single active handler per event (for dedupe) const singletons = new Map(); // Add onOnce b.onOnce = (event, cb) => { const wrapper = (data) => { try { cb(data); } finally { b.off?.(event, wrapper); } }; b.on(event, wrapper); }; if (singleHandlerPerEvent && originalOn && originalOff) { b.on = (event, cb) => { // If there was a previous handler for this event installed via this layer, remove it const prev = singletons.get(event); if (prev) { try { originalOff(event, prev); } catch { } } singletons.set(event, cb); return originalOn(event, cb); }; b.off = (event, cb) => { // Clean our registry if the same cb is removed const prev = singletons.get(event); if (prev === cb) singletons.delete(event); return originalOff(event, cb); }; } b.__pkgGuardsInstalled = true; }