@readium/navigator
Version:
Next generation SDK for publications in Web Apps
74 lines (62 loc) • 2.44 kB
text/typescript
export interface IframeEmbeddingDetectorOptions {
/** Callback when iframe embedding is detected */
onDetected?: (isCrossOrigin: boolean) => void;
}
export class IframeEmbeddingDetector {
private options: IframeEmbeddingDetectorOptions;
private observer?: MutationObserver;
private detected = false;
constructor(options: IframeEmbeddingDetectorOptions) {
if (!options.onDetected) {
throw new Error('onDetected callback is required');
}
this.options = options;
this.setupDetection();
}
private isIframed(): { isEmbedded: boolean; isCrossOrigin: boolean } {
try {
// If we can access top, check if we're in an iframe
const isEmbedded = window.self !== window.top;
if (!isEmbedded) {
return { isEmbedded: false, isCrossOrigin: false };
}
// Try to access top's location - will throw if cross-origin
// @ts-ignore - We know this might throw
const isCrossOrigin = !window.top.location.href;
return { isEmbedded: true, isCrossOrigin };
} catch (e) {
// If we can't access top due to same-origin policy, it's cross-origin
return { isEmbedded: true, isCrossOrigin: true };
}
}
private setupDetection() {
const { isEmbedded, isCrossOrigin } = this.isIframed();
if (isEmbedded) {
this.handleDetected(isCrossOrigin);
return;
}
// Set up a listener for future checks in case the page is embedded later
this.observer = new MutationObserver(() => {
const { isEmbedded, isCrossOrigin } = this.isIframed();
if (isEmbedded && !this.detected) {
this.handleDetected(isCrossOrigin);
this.observer?.disconnect(); // No need to observe further
}
});
this.observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true
});
window.addEventListener("unload", () => this.destroy());
}
private handleDetected(isCrossOrigin: boolean) {
this.detected = true;
this.options.onDetected?.(isCrossOrigin);
}
public destroy() {
this.observer?.disconnect();
this.observer = undefined;
this.detected = false;
}
}