New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Track impression on amp landing page #5606
Changes from all commits
487d029
fb805e4
9cc6b0b
dcb8cb2
281e0a2
5b9c451
6972a4f
e94029d
b521f9a
a88a015
63bfd3f
38df3d5
2d24321
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,8 +44,6 @@ export let DocumentInfoDef; | |
export function documentInfoForDoc(nodeOrDoc) { | ||
return /** @type {!DocumentInfoDef} */ (getServiceForDoc(nodeOrDoc, | ||
'documentInfo', ampdoc => { | ||
const url = ampdoc.getUrl(); | ||
const sourceUrl = getSourceUrl(url); | ||
const rootNode = ampdoc.getRootNode(); | ||
let canonicalUrl = rootNode && rootNode.AMP | ||
&& rootNode.AMP.canonicalUrl; | ||
|
@@ -56,11 +54,19 @@ export function documentInfoForDoc(nodeOrDoc) { | |
canonicalUrl = parseUrl(canonicalTag.href).href; | ||
} | ||
const pageViewId = getPageViewId(ampdoc.win); | ||
return {url, sourceUrl, canonicalUrl, pageViewId}; | ||
const res = { | ||
get sourceUrl() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still a getter property. Remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you want user to call data.getSourceUrl(), instead of data.sourceUrl? I think this |
||
return getSourceUrl(ampdoc.getUrl()); | ||
}, | ||
canonicalUrl, | ||
pageViewId, | ||
}; | ||
return res; | ||
})); | ||
} | ||
|
||
|
||
|
||
/** | ||
* Returns a relatively low entropy random string. | ||
* This should be called once per window and then cached for subsequent | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,31 +14,69 @@ | |
* limitations under the License. | ||
*/ | ||
|
||
import {user} from './log'; | ||
import {dev, user} from './log'; | ||
import {isExperimentOn} from './experiments'; | ||
import {viewerForDoc} from './viewer'; | ||
import {xhrFor} from './xhr'; | ||
import { | ||
isProxyOrigin, | ||
parseUrl, | ||
parseQueryString, | ||
addParamsToUrl, | ||
} from './url'; | ||
import {timerFor} from './timer'; | ||
import {getMode} from './mode'; | ||
|
||
const TIMEOUT_VALUE = 8000; | ||
|
||
let trackImpressionPromise = null; | ||
|
||
/** | ||
* A function to get the trackImpressionPromise; | ||
* @return {!Promise} | ||
*/ | ||
export function getTrackImpressionPromise() { | ||
return dev().assert(trackImpressionPromise); | ||
} | ||
|
||
/** | ||
* Function that reset the trackImpressionPromise only for testing | ||
* @visibleForTesting | ||
*/ | ||
export function resetTrackImpressionPromiseForTesting() { | ||
trackImpressionPromise = null; | ||
} | ||
|
||
/** | ||
* Emit a HTTP request to a destination defined on the incoming URL. | ||
* Protected by experiment. | ||
* @param {!Window} win | ||
*/ | ||
export function maybeTrackImpression(win) { | ||
let resolveImpression; | ||
|
||
trackImpressionPromise = new Promise(resolve => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
resolveImpression = resolve; | ||
}); | ||
|
||
if (!isExperimentOn(win, 'alp')) { | ||
resolveImpression(); | ||
return; | ||
} | ||
|
||
const viewer = viewerForDoc(win.document); | ||
/** @const {string|undefined} */ | ||
const clickUrl = viewer.getParam('click'); | ||
|
||
if (!clickUrl) { | ||
resolveImpression(); | ||
return; | ||
} | ||
if (clickUrl.indexOf('https://') != 0) { | ||
user().warn('Impression', | ||
'click fragment param should start with https://. Found ', | ||
clickUrl); | ||
resolveImpression(); | ||
return; | ||
} | ||
if (win.location.hash) { | ||
|
@@ -47,15 +85,63 @@ export function maybeTrackImpression(win) { | |
// avoid duplicate tracking. | ||
win.location.hash = ''; | ||
} | ||
|
||
viewer.whenFirstVisible().then(() => { | ||
invoke(win, clickUrl); | ||
// TODO(@zhouyx) need test with a real response. | ||
const promise = invoke(win, dev().assertString(clickUrl)).then(response => { | ||
applyResponse(win, viewer, response); | ||
}); | ||
|
||
// Timeout invoke promise after 8s and resolve trackImpressionPromise. | ||
resolveImpression(timerFor(win).timeoutPromise(TIMEOUT_VALUE, promise, | ||
'timeout waiting for ad server response').catch(() => {})); | ||
}); | ||
} | ||
|
||
/** | ||
* Send the url to ad server and wait for its response | ||
* @param {!Window} win | ||
* @param {string} clickUrl | ||
* @return {!Promise<!JSONType>} | ||
*/ | ||
function invoke(win, clickUrl) { | ||
xhrFor(win).fetchJson(clickUrl, { | ||
if (getMode().localDev && !getMode().test) { | ||
clickUrl = 'http://localhost:8000/impression-proxy?url=' + clickUrl; | ||
} | ||
return xhrFor(win).fetchJson(clickUrl, { | ||
credentials: 'include', | ||
requireAmpResponseSourceOrigin: true, | ||
}); | ||
// TODO(@cramforce): Do something with the result. | ||
} | ||
|
||
/** | ||
* parse the response back from ad server | ||
* Set for analytics purposes | ||
* @param {!Window} win | ||
* @param {!Object} response | ||
*/ | ||
function applyResponse(win, viewer, response) { | ||
const adLocation = response['location']; | ||
const adTracking = response['tracking_url']; | ||
|
||
// If there is a tracking_url, need to track it | ||
// Otherwise track the location | ||
const trackUrl = adTracking || adLocation; | ||
|
||
if (trackUrl && !isProxyOrigin(trackUrl)) { | ||
// To request the provided trackUrl for tracking purposes. | ||
new Image().src = trackUrl; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comment as to why and what this does. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
// Replace the location href params with new location params we get. | ||
if (adLocation) { | ||
if (!win.history.replaceState) { | ||
return; | ||
} | ||
const currentHref = win.location.href; | ||
const url = parseUrl(adLocation); | ||
const params = parseQueryString(url.search); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const newHref = addParamsToUrl(currentHref, params); | ||
win.history.replaceState(null, '', newHref); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imagine the URLs are: The above logic gives: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is probably OK I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we just make it the servers responsibility to come up the right URL, and we just blindly replace it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not 100% sure what you are suggesting, but we want to minimize server changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant to make the protocol simple, just completely replace the URL to the Overall, I think client is not the right place to concatenate the URL, because client does not have the full picture as the server does. It will potentially limit future changes at server side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lannka would it be the case that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lannka persuaded me that blindly replacing URL is the right way to do here on my bus home yesterday 😄 . It will help to keep behaviors same with what we have now w/o ALP. Server most likely will keep all query params and append There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is fine. I'd avoid the call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm, add the check for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not supported by some browsers that otherwise work with AMP. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. we just do nothing at all for this case? I mean we can probably use other way to attach the params to location.hash |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import {viewportForDoc} from '../viewport'; | |
import {userNotificationManagerFor} from '../user-notification'; | ||
import {activityFor} from '../activity'; | ||
import {isExperimentOn} from '../experiments'; | ||
import {getTrackImpressionPromise} from '../impression.js'; | ||
|
||
|
||
/** @private @const {string} */ | ||
|
@@ -163,6 +164,14 @@ export class UrlReplacements { | |
return removeFragment(info.sourceUrl); | ||
})); | ||
|
||
this.setAsync_('SOURCE_URL', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please provide sync alternatives to these that do not wait for the promise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is added. I didn't change the previous |
||
return getTrackImpressionPromise().then(() => { | ||
return this.getDocInfoValue_(info => { | ||
return removeFragment(info.sourceUrl); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto duplication. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I don't quite understand. ❓ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see! I don't want to change this though. it's a one line code, no need to add an extra method for it. |
||
}); | ||
}); | ||
}); | ||
|
||
// Returns the host of the Source URL for this AMP document. | ||
this.set_('SOURCE_HOST', this.getDocInfoValue_.bind(this, info => { | ||
return parseUrl(info.sourceUrl).host; | ||
|
@@ -186,15 +195,13 @@ export class UrlReplacements { | |
})); | ||
|
||
this.set_('QUERY_PARAM', (param, defaultValue = '') => { | ||
user().assert(param, | ||
'The first argument to QUERY_PARAM, the query string ' + | ||
'param is required'); | ||
const url = parseUrl(this.ampdoc.win.location.href); | ||
const params = parseQueryString(url.search); | ||
return this.getQueryParamData_(param, defaultValue); | ||
}); | ||
|
||
return (typeof params[param] !== 'undefined') ? | ||
params[param] : | ||
defaultValue; | ||
this.setAsync_('QUERY_PARAM', (param, defaultValue = '') => { | ||
return getTrackImpressionPromise().then(() => { | ||
return this.getQueryParamData_(param, defaultValue); | ||
}); | ||
}); | ||
|
||
/** | ||
|
@@ -540,6 +547,23 @@ export class UrlReplacements { | |
return navigationInfo[attribute]; | ||
} | ||
|
||
/** | ||
* Return the QUERY_PARAM from the current location href | ||
* @param {*} param | ||
* @param {string} defaultValue | ||
* @return {string} | ||
*/ | ||
getQueryParamData_(param, defaultValue) { | ||
user().assert(param, | ||
'The first argument to QUERY_PARAM, the query string ' + | ||
'param is required'); | ||
user().assert(typeof param == 'string', 'param should be a string'); | ||
const url = parseUrl(this.ampdoc.win.location.href); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't have the same problem as above, because it reads the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dvoytenko Should this look at the ampdoc? |
||
const params = parseQueryString(url.search); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
return (typeof params[param] !== 'undefined') | ||
? params[param] : defaultValue; | ||
} | ||
|
||
/** | ||
* | ||
* Sets a synchronous value resolver for the variable with the specified name. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this. I don't want to put a fake cookie here to get the ad server response. Is there a way to get the correct cookie here in proxy server ?(I don't think there should be due to security reason). If not I think I will stick with the fake ad response for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think you can. You'd need to hardcode one :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😢