-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Introduce iframe transport to amp-analytics - 3p side #10596
Conversation
… queue much simpler than previous PR. Response functionality is included, but extraData is currently commented out - will add again before PR
… relying on omitting sandbox allow-same-origin
3p/ampanalytics-lib.js
Outdated
@@ -0,0 +1,264 @@ | |||
/** |
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.
how about iframe-transport-client.js?
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.
Can do, if you think this will be more meaningful to customers. (Note that I am taking a vacation day Tuesday 7/25.)
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.
this name is internal in our repo only, so it's a good idea to keep it consistent with other naming.
we can definitely use a different name for the compiled library (maybe including ampanalytics in the 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 like this idea. Done.
3p/ampanalytics-lib.js
Outdated
* Receives event messages bound for this cross-domain iframe, from all | ||
* creatives | ||
*/ | ||
export class EventRouter { |
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.
IframeTransportClient
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.
Will do
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.
Done
3p/ampanalytics-lib.js
Outdated
* @param messageType The type of message to subscribe to | ||
* @VisibleForTesting | ||
*/ | ||
subscribeTo(messageType) { |
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.
should you use IframeMessagingClient?
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'm a bit confused here. You said to change from IframeMessagingClient to SubscriptionAPI. Do you mean to have one side of the conversation using IframeMessagingClient, and the other side using SubscriptionAPI? I can research whether that will work, but want to make sure I understand you correctly.
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.
sorry if i didn't make it clear.
SubscriptionApi and IframeMessagingClient is a pair. the former is installed in host page, the latter is for the iframe client.
that's how it works for our 3P ads iframe
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.
Done
3p/ampanalytics-lib.js
Outdated
'Must implement onNewAmpAnalyticsInstance in ' + | ||
this.win_.location.href); | ||
events.forEach(event => { | ||
const transportId = event['transportId']; |
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.
to leverage the typecheck : event.transportid
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.
Good idea - will do.
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.
Done
console.log("The page at: " + window.location.href + | ||
" has received an event: " + event + | ||
" from the creative with transport ID: " + | ||
ampAnalytics.getTransportId()); |
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.
so this onNewAmpAnalyticsInstance
will be called for every transport?
I propose a simpler API:
function processAnalyticsEvent(event) {
// vendor code goes here ...
}
const url = JSON.parse(window.name).scriptSrc;
if (url && url.startsWith('https://3p.ampproject.net/')) {
loadScript(url).then(() => { // see 3p.js
window.context.onAnalyticsEvent(processAnalyticsEvent.bind(null));
});
}
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.
Yes, it contains the transportId as state. This way, if 2 creatives use the same vendor, we will not confuse events from one creative with events from the other. I fear that your proposal would force the vendor to do the work of keeping events from different creatives separated.
Can't call loadScript() in 3p from here: to do so, we would have to compile it into the JS lib, and this is the code to load that lib, so it would be a chicken-and-egg problem. But, my code to createElement(), set src, and then appendChild() is identical to what is in loadScript(), minus the callbacks which I don't have a use for currently.
Also, window context API is not being used.
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.
would force the vendor to do the work of keeping events from different creatives separated
I don't feel we should do that much for the vendor. The reason we have this client lib with sacrificing of speed is that we don't want to expose the raw postMessage API to them (which is hard to migrate once vendors start to depend on it). So the only job of the client lib is to convert raw message to a js friendly object.
I want to keep the API simple and flexible. We simply flush the events to the client, each event has the transport info. And vendors can have their own design about how to passing the creative IDs along with the event. And it's their job how to group them / process them.
Can't call loadScript() in 3p from here
I was not talking about import the code here. you can copy-paste the impl here.
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.
Ok, I will work to simplify this. If you don't mind, I'll still leave my assert()s though.
Re: loadScript(), my current implementation is essentially the same, minus the callbacks. loadScript():
export function loadScript(win, url, opt_cb, opt_errorCb) {
/** @const {!Element} */
const s = win.document.createElement('script');
s.src = url;
if (opt_cb) {
s.onload = opt_cb;
}
if (opt_errorCb) {
s.onerror = opt_errorCb;
}
win.document.body.appendChild(s);
}
Mine:
script = document.createElement('script');
script.src = url;
document.head.appendChild(script);
I'll switch that last line from document.head to document.body, though.
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.
Simplification is done. Did not end up changing from "document.head" to "document.body" since the remote frame has no content and thus no body.
3p/ampanalytics-lib.js
Outdated
* Receives messages bound for this cross-domain iframe, from a particular | ||
* creative. | ||
*/ | ||
export class CreativeEventRouter { |
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.
why is this class necessary?
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.
Currently CreativeEventRouter provides an association between a creative (transport) and the customer-provided listener function, and EventRouter handles communication with the parent frame (for any/all transports). Therefore the function passed to registerCreativeEventListener() is specific to one creative.
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.
did we normalized all events into one type at host side already?
…access. Have not yet addressed the comments about IframeMessagingClient nor simplifying to eliminate CreativeEventRouter.
<title>Requests Frame</title> | ||
<script> | ||
/** | ||
* To receive analytics events in a third-party frame, you must |
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.
Comment is outdated
* @param ampAnalytics Call registerAmpAnalyticsEventListener() on this, | ||
* passing your function which will receive the analytics events. | ||
*/ | ||
window.processAmpAnalyticsEvent = (event, transportId) => { |
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.
thanks for simplifying. but this is a bit different from what I proposed.
i was thinking of exposing the API as window.context.onAnalyticsEvent(callback)
- more consistent with our ampcontext api
- this gives 3p code to decide when to call the api, as they might be loading something necessary asynchronously.
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.
Done, back to you.
3p/iframe-transport-client.js
Outdated
} | ||
} | ||
|
||
if (!window.AMP_TEST) { |
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.
we can follow ampcontext-lib.js to split this piece out of this file.
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.
Done
<meta charset="UTF-8"> | ||
<title>Requests Frame</title> | ||
<script> | ||
window.addEventListener('amp-iframeTransportClientCreated', function() { |
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.
Changed to resemble:
amphtml/examples/ampcontext-creative.html
Line 12 in d98d8e1
window.addEventListener('amp-windowContextCreated', function(){ |
* assign it to window.iframeTransportClient, to provide the creative with | ||
* all the required functionality. | ||
*/ | ||
try { |
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.
3p/iframe-transport-client.js
Outdated
'Invalid/missing sentinel on iframe name attribute' + this.win_.name)); | ||
this.client_.makeRequest( | ||
IFRAME_TRANSPORT_EVENTS_TYPE, | ||
IFRAME_TRANSPORT_EVENTS_TYPE, |
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.
back to my previous comment, i think it make sense to have 2 different message type
SEND_IFRAME_TRANSPORT_EVENTS,
IFRAME_TRANSPORT_EVENTS
naming is after 3p-frame-messaging.js
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.
Fixed by creating constants with different names, but same value. See:
https://github.com/ampproject/amphtml/blob/master/src/iframe-helper.js#L217
https://github.com/ampproject/amphtml/blob/master/src/iframe-helper.js#L265
...for why values must be the same.
Just to be safe: Tested with same values, works. Tested with different values, does not work.
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.
Tested with different values, does not work.
That does not sound right. Can you investigate?
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 believe this makes sense. In the class at https://github.com/ampproject/amphtml/blob/master/src/iframe-helper.js#L412, subscribers send a message of a certain type. That is stored in this.clientWindows_
Later, when send() is called (line 446), it looks at this.clientWindows_ to see who wants messages of that type, and sends it to them.
I don't see how it could work with the request/response message types being different. If you send a subscription request for message type X, it wouldn't/shouldn't know to send you message type Y.
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.
All the current usage of IframeMessagingClient#makeRequest
takes different type: https://github.com/ampproject/amphtml/blob/master/3p/ampcontext.js#L158
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.
Fixed, to resemble code in provided link
3p/iframe-transport-client.js
Outdated
'Must call onAnalyticsEvent in ' + this.win_.location.href); | ||
events.forEach(event => { | ||
try { | ||
this.listener_(event.message, event.transportId); |
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.
listener_ can be null
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.
Fixed.
src/iframe-transport-common.js
Outdated
@@ -19,9 +19,16 @@ | |||
* This is the type of message that will be sent to the 3p frame. | |||
* The message will contain an array of the typedef declared below. | |||
*/ | |||
export const IFRAME_TRANSPORT_EVENTS_TYPE = 'IframeTransportEvents'; | |||
export const IFRAME_TRANSPORT_EVENTS = 'iframe-transport-events'; |
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.
actually, it probably makes sense to merge this whole file into 3p-frame-messaging.js
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.
Ok, I can do that. Thanks!
3p/iframe-transport-client.js
Outdated
/** @protected {!IframeMessagingClient} */ | ||
this.client_ = new IframeMessagingClient(win); | ||
this.client_.setHostWindow(this.win_.parent); | ||
this.client_.setSentinel(dev().assertString( |
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.
Should this be user() instead of dev()? If it fails, I assume we cannot recover?
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 agree. We should probably change it at https://github.com/ampproject/amphtml/blob/master/3p/ampcontext.js#L103 also
<meta charset="UTF-8"> | ||
<title>Requests Frame</title> | ||
<script> | ||
window.addEventListener('amp-iframeTransportClientCreated', function() { |
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.
Any reason not to use big arrow notation? (e.g. () => {...})
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.
Agree with this, too. Will leave myself a note to also change
amphtml/examples/ampcontext-creative.html
Line 12 in d98d8e1
window.addEventListener('amp-windowContextCreated', function(){ |
window.iframeTransportClient.onAnalyticsEvent( | ||
(event, transportId) => { | ||
// Now, do something meaningful with the AMP Analytics event | ||
console.log("The page at: " + window.location.href + |
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.
Single quotes?
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.
LGTM
thanks for the change.
This is the 3p-side companion to the amp analytics impl PR at #10442 and thus a simplified version of #10184
It is the result of rebasing the code that was at https://github.com/google/amphtml/pull/20 over to ampproject/amphtml