| 1 |
import Raven from '../lib/Raven'; |
| 2 |
|
| 3 |
const POLL_INTERVAL_MS = 250; |
| 4 |
const DEFAULT_TIMEOUT_MS = 15000; |
| 5 |
|
| 6 |
const EMBEDDER_SCRIPT_SELECTOR = 'script[src*="/integrated-app-embedder/"]'; |
| 7 |
|
| 8 |
export function isEmbedderReady() { |
| 9 |
const { IntegratedAppEmbedder, IntegratedAppOptions }: any = window; |
| 10 |
|
| 11 |
return !!IntegratedAppEmbedder && typeof IntegratedAppOptions === 'function'; |
| 12 |
} |
| 13 |
|
| 14 |
function getEmbedderDiagnostics() { |
| 15 |
const scriptTag = document.querySelector( |
| 16 |
EMBEDDER_SCRIPT_SELECTOR |
| 17 |
) as HTMLScriptElement | null; |
| 18 |
|
| 19 |
return { |
| 20 |
hasScriptTag: !!scriptTag, |
| 21 |
scriptSrc: scriptTag ? scriptTag.src : null, |
| 22 |
readyState: document.readyState, |
| 23 |
hasEmbedder: !!(window as any).IntegratedAppEmbedder, |
| 24 |
typeofOptions: typeof (window as any).IntegratedAppOptions, |
| 25 |
}; |
| 26 |
} |
| 27 |
|
| 28 |
/** |
| 29 |
* The embedder script is a cross-origin script from js.hubspot.com, and nothing |
| 30 |
* makes the widgets wait for it: they read its globals off `window` the moment a |
| 31 |
* control renders. On sites where an asset pipeline defers it, or where the |
| 32 |
* editor is simply slow, the widget can lose that race and fail permanently even |
| 33 |
* though the script arrives seconds later. |
| 34 |
* |
| 35 |
* Resolves true as soon as the globals appear, or false once the wait is up. |
| 36 |
*/ |
| 37 |
export function whenEmbedderReady( |
| 38 |
timeoutMs: number = DEFAULT_TIMEOUT_MS |
| 39 |
): { promise: Promise<boolean>; cancel: () => void } { |
| 40 |
let intervalId: number | undefined; |
| 41 |
|
| 42 |
const cancel = () => { |
| 43 |
if (intervalId !== undefined) { |
| 44 |
window.clearInterval(intervalId); |
| 45 |
intervalId = undefined; |
| 46 |
} |
| 47 |
}; |
| 48 |
|
| 49 |
const promise = new Promise<boolean>(resolve => { |
| 50 |
if (isEmbedderReady()) { |
| 51 |
console.info('HubSpot plugin - embedder ready immediately'); |
| 52 |
resolve(true); |
| 53 |
return; |
| 54 |
} |
| 55 |
|
| 56 |
const startedAt = Date.now(); |
| 57 |
|
| 58 |
console.info( |
| 59 |
`HubSpot plugin - waiting up to ${timeoutMs}ms for embedder script`, |
| 60 |
getEmbedderDiagnostics() |
| 61 |
); |
| 62 |
|
| 63 |
intervalId = window.setInterval(() => { |
| 64 |
const waitedMs = Date.now() - startedAt; |
| 65 |
|
| 66 |
if (isEmbedderReady()) { |
| 67 |
cancel(); |
| 68 |
console.info(`HubSpot plugin - embedder ready after ${waitedMs}ms`); |
| 69 |
resolve(true); |
| 70 |
} else if (waitedMs >= timeoutMs) { |
| 71 |
cancel(); |
| 72 |
resolve(false); |
| 73 |
} |
| 74 |
}, POLL_INTERVAL_MS); |
| 75 |
}); |
| 76 |
|
| 77 |
return { promise, cancel }; |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Reports a give-up so these failures stop being invisible. Until now the widget |
| 82 |
* rendered its error box without telling anyone, so the only signal we had for |
| 83 |
* this whole class of problem came from unrelated admin pages. |
| 84 |
* |
| 85 |
* Uses captureException rather than captureMessage on purpose: Raven is |
| 86 |
* configured with a shouldSendCallback that drops any event whose culprit does |
| 87 |
* not match plugins/leadin/, and captureMessage does not attach a stack trace, |
| 88 |
* so it has no culprit to match on. |
| 89 |
*/ |
| 90 |
export function reportEmbedderUnavailable(waitedMs: number) { |
| 91 |
const diagnostics = getEmbedderDiagnostics(); |
| 92 |
|
| 93 |
console.error( |
| 94 |
`HubSpot plugin - embedder unavailable after ${waitedMs}ms`, |
| 95 |
diagnostics |
| 96 |
); |
| 97 |
|
| 98 |
Raven.captureException(new Error('Leadin embedder unavailable'), { |
| 99 |
fingerprint: ['EMBEDDER_UNAVAILABLE'], |
| 100 |
extra: { waitedMs, ...diagnostics }, |
| 101 |
}); |
| 102 |
} |
| 103 |
|