/** * Netflix WebView Injection Scripts * * JavaScript code injected into Netflix WebView to: * 1. Show consent popup for user approval * 2. Fetch user viewing data (history, my list, ratings) * 3. Extract profile preferences data * 4. Track progress during export * 5. Send data back to React Native * * @reference Sephora implementation: src/utils/webviewScripts/sephora.ts * * API Authentication: * - TODO: Document Netflix API authentication method * - Token location and format TBD */ /** * Consent Popup Script * Shows user consent UI before exporting data */ export declare const NETFLIX_CONSENT_POPUP_SCRIPT = "\n(function() {\n try {\n const existing = document.getElementById('onairos-consent-overlay');\n if (existing) existing.remove();\n \n const overlay = document.createElement('div');\n overlay.id = 'onairos-consent-overlay';\n overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.85);\n z-index: 999999;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n \n const popup = document.createElement('div');\n popup.style.cssText = `\n background: #141414;\n border-radius: 16px;\n padding: 32px;\n max-width: 400px;\n width: 90%;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333;\n `;\n \n popup.innerHTML = `\n
\n
\uD83C\uDFAC
\n

\n Export Netflix Data?\n

\n

\n Allow Onairos to export your Netflix viewing activity (watch history, My List, ratings) for analysis?\n

\n
\n \n \n
\n
\n `;\n \n overlay.appendChild(popup);\n document.body.appendChild(overlay);\n \n document.getElementById('onairos-deny').onclick = () => {\n overlay.remove();\n window.ReactNativeWebView.postMessage(JSON.stringify({\n type: 'NETFLIX_CONSENT',\n status: 'denied'\n }));\n };\n \n document.getElementById('onairos-allow').onclick = () => {\n document.getElementById('onairos-allow').innerHTML = '\u23F3 Starting...';\n document.getElementById('onairos-allow').disabled = true;\n \n window.ReactNativeWebView.postMessage(JSON.stringify({\n type: 'NETFLIX_CONSENT',\n status: 'allowed'\n }));\n \n setTimeout(() => overlay.remove(), 500);\n };\n } catch (error) {\n window.ReactNativeWebView.postMessage(JSON.stringify({\n type: 'NETFLIX_CONSENT',\n status: 'error',\n message: error.message\n }));\n }\n})();\ntrue;\n"; /** * Export Script - Netflix data extraction * * Extraction approach: * Netflix renders viewing history as server-side HTML at /viewingactivity. * Each page contains ~20 .retableRow elements with date, title, and title ID. * Pagination via ?pg=0, ?pg=1, etc. Auth is cookie-based (credentials: 'include'). * No bearer token or build identifier needed for this endpoint. */ export declare const NETFLIX_EXPORT_SCRIPT = "\n(async function() {\n try {\n var postMsg = function(obj) {\n window.ReactNativeWebView.postMessage(JSON.stringify(obj));\n };\n\n var progress = function(stage, pct, msg) {\n postMsg({ type: 'NETFLIX_EXPORT_PROGRESS', stage: stage, progress: pct, message: msg });\n };\n\n function debugMsg(msg) {\n postMsg({ type: 'NETFLIX_EXPORT_PROGRESS', stage: 'debug', progress: 0, message: '[DEBUG] ' + msg });\n }\n\n function delay(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }\n\n if (!document.body) {\n postMsg({ type: 'NETFLIX_EXPORT_ERROR', message: 'Page not ready' });\n return;\n }\n\n // ============================================================\n // STEP 1: Extract profile name from reactContext if available\n // ============================================================\n progress('auth', 5, 'Getting profile info...');\n\n var PROFILE_NAME = 'unknown';\n try {\n if (window.netflix && window.netflix.reactContext) {\n var ctx = window.netflix.reactContext;\n debugMsg('reactContext keys: ' + Object.keys(ctx.models || {}).join(','));\n var userInfo = ctx.models.memberContext && ctx.models.memberContext.data && ctx.models.memberContext.data.userInfo;\n if (userInfo) {\n PROFILE_NAME = userInfo.accountOwnerName || userInfo.name || userInfo.profileName || 'unknown';\n debugMsg('Profile: ' + PROFILE_NAME + ', country: ' + (userInfo.countryOfSignup || 'unknown'));\n }\n }\n } catch(e) { debugMsg('reactContext failed: ' + e.message); }\n\n // ============================================================\n // STEP 2: Fetch viewing history by scraping /viewingactivity pages\n // ============================================================\n progress('history', 10, 'Fetching viewing history...');\n\n var allItems = [];\n var page = 0;\n var maxPages = 2;\n var consecutiveEmpty = 0;\n\n while (page < maxPages && consecutiveEmpty < 2) {\n try {\n var resp = await fetch('/viewingactivity?pg=' + page, { credentials: 'include' });\n if (!resp.ok) {\n debugMsg('viewingactivity page ' + page + ' status: ' + resp.status);\n break;\n }\n\n var html = await resp.text();\n var doc = new DOMParser().parseFromString(html, 'text/html');\n var rows = doc.querySelectorAll('.retableRow');\n\n if (rows.length === 0) {\n consecutiveEmpty++;\n debugMsg('Page ' + page + ': 0 rows (empty ' + consecutiveEmpty + ')');\n page++;\n continue;\n }\n\n consecutiveEmpty = 0;\n\n for (var ri = 0; ri < rows.length; ri++) {\n var dateEl = rows[ri].querySelector('.col.date');\n var titleEl = rows[ri].querySelector('.col.title a');\n\n if (titleEl) {\n var href = titleEl.getAttribute('href') || '';\n var idMatch = href.match(/\\/title\\/(\\d+)/);\n allItems.push({\n videoId: idMatch ? idMatch[1] : '',\n title: titleEl.textContent.trim(),\n date: dateEl ? dateEl.textContent.trim() : '',\n type: titleEl.textContent.includes(':') ? 'series' : 'movie'\n });\n }\n }\n\n debugMsg('Page ' + page + ': ' + rows.length + ' rows, total: ' + allItems.length);\n\n var pct = 10 + Math.round((page / Math.max(page + 2, 10)) * 75);\n progress('history', Math.min(pct, 85), 'Fetching viewing history (' + allItems.length + ' items)...');\n\n page++;\n await delay(300);\n } catch(e) {\n debugMsg('Page ' + page + ' failed: ' + e.message);\n break;\n }\n }\n\n debugMsg('Total viewing history: ' + allItems.length + ' items across ' + page + ' pages');\n if (allItems.length > 0) {\n debugMsg('Sample: ' + allItems[0].title + ' (' + allItems[0].date + ')');\n }\n\n // ============================================================\n // STEP 3: Send results\n // ============================================================\n progress('complete', 95, 'Done! Got ' + allItems.length + ' items');\n\n postMsg({\n type: 'NETFLIX_EXPORT_COMPLETE',\n status: 'success',\n data: {\n viewingHistory: allItems,\n profile: { profileName: PROFILE_NAME },\n summary: {\n totalItems: allItems.length,\n totalPages: page,\n profileName: PROFILE_NAME\n }\n },\n success: true\n });\n\n } catch (error) {\n console.error('[NETFLIX] Init error:', error.message);\n window.ReactNativeWebView.postMessage(JSON.stringify({\n type: 'NETFLIX_EXPORT_ERROR',\n status: 'error',\n message: 'Script init error: ' + error.message,\n success: false\n }));\n }\n})();\ntrue;\n"; export declare const NETFLIX_SUCCESS_SCRIPT = "\n(function() {\n console.log('\u2705 Netflix export successful!');\n})();\ntrue;\n"; export declare const NETFLIX_ERROR_SCRIPT = "\n(function() {\n console.log('\u274C Netflix export failed!');\n})();\ntrue;\n"; //# sourceMappingURL=netflix.d.ts.map