/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
3p/integration-lib.js
359 строк
10 KB
velichkin
Remove lentainform support (#40038)
01 авг 2024, 13:16
Не верифицирован
01 авг 2024, 13:16
5121727
Код
Авторство
О чём код?
import * as mode from '#core/mode'; import {parseJson} from '#core/types/object/json'; import {endsWith} from '#core/types/string'; import { initLogConstructor, isUserErrorMessage, setReportError, userAssert, } from '#utils/log'; import {run, setExperimentToggles} from './3p'; import {IntegrationAmpContext} from './ampcontext-integration'; import {installEmbedStateListener, manageWin} from './environment'; import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; import * as urls from '../src/config/urls'; import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; /** * Whether the embed type may be used with amp-embed tag. * @const {!{[key: string]: boolean}} */ const AMP_EMBED_ALLOWED = { _ping_: true, '1wo': true, '24smi': true, adskeeper: true, adsloom: true, adstyle: true, bringhub: true, colombiafeed: true, dable: true, engageya: true, epeex: true, firstimpression: true, forkmedia: true, gecko: true, glomex: true, idealmedia: true, insticator: true, jubna: true, kuadio: true, 'mantis-recommend': true, mediaad: true, mgid: true, miximedia: true, myua: true, mywidget: true, nativery: true, opinary: true, outbrain: true, plista: true, postquare: true, ppstudio: true, pubexchange: true, pulse: true, rbinfox: true, rcmwidget: true, readmo: true, recreativ: true, runative: true, smartclip: true, smi2: true, speakol: true, strossle: true, svknative: true, taboola: true, temedya: true, trafficstars: true, vlyby: true, whopainfeed: true, yahoofedads: true, yahoonativeads: true, yektanet: true, zen: true, zergnet: true, }; // For backward compat, we always allow these types without the iframe // opting in. const defaultAllowedTypesInCustomFrame = [ // Entries must be reasonably safe and not allow overriding the injected // JS URL. // Each custom iframe can override this through the second argument to // draw3p. See amp-ad docs. 'facebook', 'twitter', 'doubleclick', 'yieldbot', '_ping_', ]; /** * Initialize 3p frame. * @param {!Window} win */ export function init(win) { initLogConstructor(); const config = getAmpConfig(); // Overriding to short-circuit src/mode#getMode() win.__AMP_MODE = config.mode; setReportError(console.error.bind(console)); setExperimentToggles(config.experimentToggles); } /** * Visible for testing. * Draws a 3p embed to the window. Expects the data to include the 3p type. * @param {!Window} win * @param {!Object} data * @param {function(!Object, function(!Object))|undefined} configCallback * Optional callback that allows user code to manipulate the incoming * configuration. See * https://github.com/ampproject/amphtml/issues/1210 for some context * on this. */ export function draw3pInternal(win, data, configCallback) { const type = data['type']; userAssert( isTagNameAllowed(type, win.context.tagName), 'Embed type %s not allowed with tag %s', type, win.context.tagName ); if (configCallback) { configCallback(data, (data) => { userAssert(data, 'Expected configuration to be passed as first argument'); run(type, win, data); }); } else { run(type, win, data); } } /** * Draws an embed, optionally synchronously, to the DOM. * @param {function(!Object, function(!Object))} opt_configCallback If provided * will be invoked with two arguments: * 1. The configuration parameters supplied to this embed. * 2. A callback that MUST be called for rendering to proceed. It takes * no arguments. Configuration is expected to be modified in-place. * @param {!Array<string>=} opt_allowed3pTypes List of advertising network * types you expect. * @param {!Array<string>=} opt_allowedEmbeddingOrigins List of domain suffixes * that are allowed to embed this frame. */ export function draw3p( opt_configCallback, opt_allowed3pTypes, opt_allowedEmbeddingOrigins ) { try { const location = getLocation(); ensureFramed(window); validateParentOrigin(window, location); validateAllowedTypes(window, getEmbedType(), opt_allowed3pTypes); if (opt_allowedEmbeddingOrigins) { validateAllowedEmbeddingOrigins(window, opt_allowedEmbeddingOrigins); } window.context = new IntegrationAmpContext(window); manageWin(window); installEmbedStateListener(); // Ugly type annotation is due to Event.prototype.data being denylisted // and the compiler not being able to discern otherwise // TODO(alanorozco): Do this more elegantly once old impl is cleaned up. draw3pInternal( window, /** @type {!IntegrationAmpContext} */ (window.context).data || {}, opt_configCallback ); window.context.bootstrapLoaded(); } catch (e) { if (window.context && window.context.report3pError) { // window.context has initiated yet if (e.message && isUserErrorMessage(e.message)) { // report user error to parent window window.context.report3pError(e); } } const c = window.context || {mode: {test: false}}; if (!c.mode.test) { lightweightErrorReport(e, c.canary); throw e; } } } /** * Throws if the current frame's parent origin is not equal to * the claimed origin. * Only check for browsers that support ancestorOrigins * @param {!Window} window * @param {!Location} parentLocation * @visibleForTesting */ export function validateParentOrigin(window, parentLocation) { const ancestors = window.location.ancestorOrigins; // Currently only webkit and blink based browsers support // ancestorOrigins. In that case we proceed but mark the origin // as non-validated. if (!ancestors || !ancestors.length) { return; } userAssert( ancestors[0] == parentLocation.origin, 'Parent origin mismatch: %s, %s', ancestors[0], parentLocation.origin ); } /** * Check that this iframe intended this particular ad type to run. * @param {!Window} window * @param {string} type 3p type * @param {!Array<string>|undefined} allowedTypes May be undefined. * @visibleForTesting */ export function validateAllowedTypes(window, type, allowedTypes) { const thirdPartyHost = parseUrlDeprecated(urls.thirdParty).hostname; // Everything allowed in default iframe. if (window.location.hostname == thirdPartyHost) { return; } if (urls.thirdPartyFrameRegex.test(window.location.hostname)) { return; } if (window.location.hostname == 'ads.localhost') { return; } if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) { return; } userAssert( allowedTypes && allowedTypes.indexOf(type) != -1, '3p type for custom iframe not allowed: %s', type ); } /** * Check that parent host name was allowed. * @param {!Window} window * @param {!Array<string>} allowedHostnames Suffixes of allowed host names. * @visibleForTesting */ export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { if (!window.document.referrer) { throw new Error('Referrer expected: ' + window.location.href); } const ancestors = window.location.ancestorOrigins; // We prefer the unforgable ancestorOrigins, but referrer is better than // nothing. const ancestor = ancestors ? ancestors[0] : window.document.referrer; let {hostname} = parseUrlDeprecated(ancestor); if (isProxyOrigin(ancestor)) { // If we are on the cache domain, parse the source hostname from // the referrer. The referrer is used because it should be // trustable. hostname = parseUrlDeprecated( getSourceUrl(window.document.referrer) ).hostname; } for (let i = 0; i < allowedHostnames.length; i++) { // Either the hostname is allowed if (allowedHostnames[i] == hostname) { return; } // Or it ends in .$hostname (aka is a sub domain of an allowed domain. if (endsWith(hostname, '.' + allowedHostnames[i])) { return; } } throw new Error( 'Invalid embedding hostname: ' + hostname + ' not in ' + allowedHostnames ); } /** * Throws if this window is a top level window. * @param {!Window} window * @visibleForTesting */ export function ensureFramed(window) { if (window == window.parent) { throw new Error('Must be framed: ' + window.location.href); } } /** * Expects the fragment to contain JSON. * @param {string} fragment Value of location.fragment * @return {?JsonObject} * @visibleForTesting */ export function parseFragment(fragment) { try { let json = fragment.substr(1); // Some browser, notably Firefox produce an encoded version of the fragment // while most don't. Since we know how the string should start, this is easy // to detect. if (json.startsWith('{%22')) { json = decodeURIComponent(json); } return /** @type {!JsonObject} */ (json ? parseJson(json) : {}); } catch (err) { return null; } } /** * Not all types of embeds are allowed to be used with all tag names on the * AMP side. This function checks whether the current usage is permissible. * @param {string} type * @param {string|undefined} tagName The tagName that was used to embed this * 3p-frame. * @return {boolean} */ export function isTagNameAllowed(type, tagName) { if (tagName == 'AMP-EMBED') { return !!AMP_EMBED_ALLOWED[type]; } return true; } /** * Reports an error to the server. Must only be called once per page. * Not for use in event handlers. * * We don't use the default error in error.js handler because it has * too many deps for this small JS binary. * * @param {!Error} e * @param {boolean} isCanary */ function lightweightErrorReport(e, isCanary) { new Image().src = urls.errorReporting + '?3p=1&v=' + encodeURIComponent(mode.version()) + '&m=' + encodeURIComponent(e.message) + '&ca=' + (isCanary ? 1 : 0) + '&r=' + encodeURIComponent(document.referrer) + '&s=' + encodeURIComponent(e.stack || ''); }