Skip to content

Commit 6223c5f

Browse files
committed
Initial poc
1 parent d434a03 commit 6223c5f

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { IFRAME_ERROR_EVENT } from '../providers/YouTubeErrorProvider';
2+
3+
/**
4+
* @typedef {import("./iframe").IframeFeature} IframeFeature
5+
* @typedef {import("../components/Player").PlayerError} PlayerError
6+
*/
7+
8+
/**
9+
* Detects YouTube errors based on DOM queries
10+
*
11+
* @implements IframeFeature
12+
*/
13+
export class ErrorDetection {
14+
/**
15+
* @param {HTMLIFrameElement} iframe
16+
*/
17+
iframeDidLoad(iframe) {
18+
const documentBody = iframe.contentWindow?.document?.body;
19+
if (documentBody) {
20+
// Create a MutationObserver instance
21+
const observer = new MutationObserver(handleMutation);
22+
23+
// Start observing the iframe's document for changes
24+
observer.observe(documentBody, {
25+
childList: true,
26+
subtree: true, // Observe all descendants of the body
27+
});
28+
}
29+
return null;
30+
}
31+
}
32+
33+
/**
34+
* Mutation handler that checks new nodes for error states
35+
*
36+
* @type {MutationCallback}
37+
*/
38+
const handleMutation = (mutationsList) => {
39+
for (const mutation of mutationsList) {
40+
if (mutation.type === 'childList') {
41+
mutation.addedNodes.forEach((node) => {
42+
// Check if the added node is a div with the class ytp-error
43+
const error = errorForNode(node);
44+
if (error) {
45+
console.log('A node with an error has been added to the document:', node);
46+
47+
window.dispatchEvent(new CustomEvent(IFRAME_ERROR_EVENT, { detail: error }));
48+
}
49+
});
50+
}
51+
}
52+
};
53+
54+
/**
55+
* Analyses attributes of a node to determine if it contains an error state
56+
*
57+
* @param {Node} [node]
58+
* @returns {PlayerError|null}
59+
*/
60+
const errorForNode = (node) => {
61+
// if (node.nodeType === Node.ELEMENT_NODE && /** @type {HTMLElement} */(node).classList.contains('ytp-error')) {
62+
if (node?.nodeType === Node.ELEMENT_NODE) {
63+
const element = /** @type {HTMLElement} */ (node);
64+
if (element.classList.contains('ytp-error')) {
65+
return 'bot-detected';
66+
}
67+
// Add other error detection logic here
68+
}
69+
return null;
70+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useContext, useState } from 'preact/hooks';
2+
import { h, createContext } from 'preact';
3+
import { useEffect } from 'preact/hooks';
4+
5+
export const IFRAME_ERROR_EVENT = 'iframe-error';
6+
7+
/**
8+
* @typedef {import("../components/Player").PlayerError} PlayerError
9+
*/
10+
11+
const YouTubeErrorContext = createContext({
12+
/** @type {PlayerError|null} */
13+
error: null,
14+
});
15+
16+
/**
17+
* @param {object} props
18+
* @param {PlayerError|null} [props.initial=null]
19+
* @param {import("preact").ComponentChild} props.children
20+
*/
21+
export function YouTubeErrorProvider({ initial = null, children }) {
22+
// initial state
23+
const [error, setError] = useState(initial);
24+
25+
// listen for updates
26+
useEffect(() => {
27+
/** @type {(event: CustomEvent) => void} */
28+
const errorEventHandler = (event) => {
29+
if (event.detail) {
30+
setError(event.detail.error || null);
31+
}
32+
};
33+
34+
window.addEventListener(IFRAME_ERROR_EVENT, errorEventHandler);
35+
36+
return () => window.removeEventListener(IFRAME_ERROR_EVENT, errorEventHandler);
37+
}, []);
38+
39+
return <YouTubeErrorContext.Provider value={{ error }}>{children}</YouTubeErrorContext.Provider>;
40+
}
41+
42+
export function useYouTubeError() {
43+
return useContext(YouTubeErrorContext).error;
44+
}

0 commit comments

Comments
 (0)