|
726 | 726 | } |
727 | 727 |
|
728 | 728 | console.log(`[SentienceAPI] Found ${iframes.length} iframe(s), requesting snapshots...`); |
729 | | - |
730 | 729 | // Request snapshot from each iframe |
731 | 730 | const iframePromises = iframes.map((iframe, idx) => { |
| 731 | + // OPTIMIZATION: Skip common ad domains to save time |
| 732 | + const src = iframe.src || ''; |
| 733 | + if (src.includes('doubleclick') || src.includes('googleadservices') || src.includes('ads system')) { |
| 734 | + console.log(`[SentienceAPI] Skipping ad iframe: ${src.substring(0, 30)}...`); |
| 735 | + return Promise.resolve(null); |
| 736 | + } |
| 737 | + |
732 | 738 | return new Promise((resolve) => { |
733 | 739 | const requestId = `iframe-${idx}-${Date.now()}`; |
734 | 740 |
|
735 | 741 | // 1. EXTENDED TIMEOUT (Handle slow children) |
736 | 742 | const timeout = setTimeout(() => { |
737 | 743 | console.warn(`[SentienceAPI] ⚠️ Iframe ${idx} snapshot TIMEOUT (id: ${requestId})`); |
738 | 744 | resolve(null); |
739 | | - }, 10000); // Increased to 10s to handle slow processing |
| 745 | + }, 5000); // Increased to 5s to handle slow processing |
740 | 746 |
|
741 | 747 | // 2. ROBUST LISTENER with debugging |
742 | 748 | const listener = (event) => { |
|
1036 | 1042 | return { |
1037 | 1043 | status: "success", |
1038 | 1044 | url: window.location.href, |
| 1045 | + viewport: { |
| 1046 | + width: window.innerWidth, |
| 1047 | + height: window.innerHeight |
| 1048 | + }, |
1039 | 1049 | elements: cleanedElements, |
1040 | 1050 | raw_elements: cleanedRawElements, |
1041 | 1051 | screenshot: screenshot |
|
1073 | 1083 | }; |
1074 | 1084 | }, |
1075 | 1085 |
|
| 1086 | + // 2b. Find Text Rectangle - Get exact pixel coordinates of specific text |
| 1087 | + findTextRect: (options = {}) => { |
| 1088 | + const { |
| 1089 | + text, |
| 1090 | + containerElement = document.body, |
| 1091 | + caseSensitive = false, |
| 1092 | + wholeWord = false, |
| 1093 | + maxResults = 10 |
| 1094 | + } = options; |
| 1095 | + |
| 1096 | + if (!text || text.trim().length === 0) { |
| 1097 | + return { |
| 1098 | + status: "error", |
| 1099 | + error: "Text parameter is required" |
| 1100 | + }; |
| 1101 | + } |
| 1102 | + |
| 1103 | + const results = []; |
| 1104 | + const searchText = caseSensitive ? text : text.toLowerCase(); |
| 1105 | + |
| 1106 | + // Helper function to find text in a single text node |
| 1107 | + function findInTextNode(textNode) { |
| 1108 | + const nodeText = textNode.nodeValue; |
| 1109 | + const searchableText = caseSensitive ? nodeText : nodeText.toLowerCase(); |
| 1110 | + |
| 1111 | + let startIndex = 0; |
| 1112 | + while (startIndex < nodeText.length && results.length < maxResults) { |
| 1113 | + const foundIndex = searchableText.indexOf(searchText, startIndex); |
| 1114 | + |
| 1115 | + if (foundIndex === -1) break; |
| 1116 | + |
| 1117 | + // Check whole word matching if required |
| 1118 | + if (wholeWord) { |
| 1119 | + const before = foundIndex > 0 ? nodeText[foundIndex - 1] : ' '; |
| 1120 | + const after = foundIndex + text.length < nodeText.length |
| 1121 | + ? nodeText[foundIndex + text.length] |
| 1122 | + : ' '; |
| 1123 | + |
| 1124 | + // Check if surrounded by word boundaries |
| 1125 | + if (!/\s/.test(before) || !/\s/.test(after)) { |
| 1126 | + startIndex = foundIndex + 1; |
| 1127 | + continue; |
| 1128 | + } |
| 1129 | + } |
| 1130 | + |
| 1131 | + try { |
| 1132 | + // Create range for this occurrence |
| 1133 | + const range = document.createRange(); |
| 1134 | + range.setStart(textNode, foundIndex); |
| 1135 | + range.setEnd(textNode, foundIndex + text.length); |
| 1136 | + |
| 1137 | + const rect = range.getBoundingClientRect(); |
| 1138 | + |
| 1139 | + // Only include visible rectangles |
| 1140 | + if (rect.width > 0 && rect.height > 0) { |
| 1141 | + results.push({ |
| 1142 | + text: nodeText.substring(foundIndex, foundIndex + text.length), |
| 1143 | + rect: { |
| 1144 | + x: rect.left + window.scrollX, |
| 1145 | + y: rect.top + window.scrollY, |
| 1146 | + width: rect.width, |
| 1147 | + height: rect.height, |
| 1148 | + left: rect.left + window.scrollX, |
| 1149 | + top: rect.top + window.scrollY, |
| 1150 | + right: rect.right + window.scrollX, |
| 1151 | + bottom: rect.bottom + window.scrollY |
| 1152 | + }, |
| 1153 | + viewport_rect: { |
| 1154 | + x: rect.left, |
| 1155 | + y: rect.top, |
| 1156 | + width: rect.width, |
| 1157 | + height: rect.height |
| 1158 | + }, |
| 1159 | + context: { |
| 1160 | + before: nodeText.substring(Math.max(0, foundIndex - 20), foundIndex), |
| 1161 | + after: nodeText.substring(foundIndex + text.length, Math.min(nodeText.length, foundIndex + text.length + 20)) |
| 1162 | + }, |
| 1163 | + in_viewport: ( |
| 1164 | + rect.top >= 0 && |
| 1165 | + rect.left >= 0 && |
| 1166 | + rect.bottom <= window.innerHeight && |
| 1167 | + rect.right <= window.innerWidth |
| 1168 | + ) |
| 1169 | + }); |
| 1170 | + } |
| 1171 | + } catch (e) { |
| 1172 | + console.warn('[SentienceAPI] Failed to get rect for text:', e); |
| 1173 | + } |
| 1174 | + |
| 1175 | + startIndex = foundIndex + 1; |
| 1176 | + } |
| 1177 | + } |
| 1178 | + |
| 1179 | + // Tree walker to find all text nodes |
| 1180 | + const walker = document.createTreeWalker( |
| 1181 | + containerElement, |
| 1182 | + NodeFilter.SHOW_TEXT, |
| 1183 | + { |
| 1184 | + acceptNode: function(node) { |
| 1185 | + // Skip script, style, and empty text nodes |
| 1186 | + const parent = node.parentElement; |
| 1187 | + if (!parent) return NodeFilter.FILTER_REJECT; |
| 1188 | + |
| 1189 | + const tagName = parent.tagName.toLowerCase(); |
| 1190 | + if (tagName === 'script' || tagName === 'style' || tagName === 'noscript') { |
| 1191 | + return NodeFilter.FILTER_REJECT; |
| 1192 | + } |
| 1193 | + |
| 1194 | + // Skip whitespace-only nodes |
| 1195 | + if (!node.nodeValue || node.nodeValue.trim().length === 0) { |
| 1196 | + return NodeFilter.FILTER_REJECT; |
| 1197 | + } |
| 1198 | + |
| 1199 | + // Check if element is visible |
| 1200 | + const computedStyle = window.getComputedStyle(parent); |
| 1201 | + if (computedStyle.display === 'none' || |
| 1202 | + computedStyle.visibility === 'hidden' || |
| 1203 | + computedStyle.opacity === '0') { |
| 1204 | + return NodeFilter.FILTER_REJECT; |
| 1205 | + } |
| 1206 | + |
| 1207 | + return NodeFilter.FILTER_ACCEPT; |
| 1208 | + } |
| 1209 | + } |
| 1210 | + ); |
| 1211 | + |
| 1212 | + // Walk through all text nodes |
| 1213 | + let currentNode; |
| 1214 | + while ((currentNode = walker.nextNode()) && results.length < maxResults) { |
| 1215 | + findInTextNode(currentNode); |
| 1216 | + } |
| 1217 | + |
| 1218 | + return { |
| 1219 | + status: "success", |
| 1220 | + query: text, |
| 1221 | + case_sensitive: caseSensitive, |
| 1222 | + whole_word: wholeWord, |
| 1223 | + matches: results.length, |
| 1224 | + results: results, |
| 1225 | + viewport: { |
| 1226 | + width: window.innerWidth, |
| 1227 | + height: window.innerHeight, |
| 1228 | + scroll_x: window.scrollX, |
| 1229 | + scroll_y: window.scrollY |
| 1230 | + } |
| 1231 | + }; |
| 1232 | + }, |
| 1233 | + |
1076 | 1234 | // 3. Click Action (unchanged) |
1077 | 1235 | click: (id) => { |
1078 | 1236 | const el = window.sentience_registry[id]; |
|
0 commit comments