Skip to content
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

Merged
merged 85 commits into from Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
9a2c05e
Adds 3p iframe to amp analytics. Uses Subscription API. Event message…
jonkeller Jul 13, 2017
c539c66
Fix SubscriptionAPI
jonkeller Jul 14, 2017
2f892c3
Brings in previous chanages to anchor-click-interceptor.js
jonkeller Jul 14, 2017
e754522
Adds unit tests
jonkeller Jul 14, 2017
be880d6
Updates queue unit tests
jonkeller Jul 14, 2017
8c76838
Updates custom types and unit tests
jonkeller Jul 14, 2017
dbf2960
Adds misc small fixes/nits from self-review
jonkeller Jul 14, 2017
7e58280
Changes 2 URLs in example from absolute to relative
jonkeller Jul 14, 2017
5d46a9d
Comments only
jonkeller Jul 14, 2017
b6351f2
Fixes TODO re: unlayoutCallback destroying 3p iframe
jonkeller Jul 14, 2017
d15b9c5
Fixes an 80-char line length violation introduced in previous PR
jonkeller Jul 14, 2017
fb539c8
Implements review feedback
jonkeller Jul 14, 2017
f252fe1
Implements review feedback
jonkeller Jul 14, 2017
ab3b882
Addresses review feedback
jonkeller Jul 14, 2017
8b47fb0
Reverts change to URL calculation, pending additional discussion
jonkeller Jul 14, 2017
b291ea1
Fixes unit test
jonkeller Jul 14, 2017
8373e55
Fixes indentation of 2 lines
jonkeller Jul 14, 2017
e56dbed
Removes extraData, new creative message
jonkeller Jul 15, 2017
83fa626
Fixes transport unit tests
jonkeller Jul 15, 2017
8b5f533
Clarifies transport ID
jonkeller Jul 15, 2017
88d118d
Fixes whitespace
jonkeller Jul 15, 2017
5e4e3f7
Improves example triggers. Fixes issue of delivery of creative response
jonkeller Jul 15, 2017
558339f
Makes indentation consistent in this section
jonkeller Jul 15, 2017
305a84b
Addresses review feedback
jonkeller Jul 17, 2017
421b56e
Fixes a null check
jonkeller Jul 17, 2017
c1b36b5
Keys off of type rather than URL. Fixes issue of null origin, without…
jonkeller Jul 17, 2017
e111811
Minor type annotation corrections
jonkeller Jul 17, 2017
e590d9e
Makes use of new param to SubscriptionAPI c'tor
jonkeller Jul 17, 2017
4c17cbe
Removes allow-same-origin
jonkeller Jul 18, 2017
3ce7c3b
Addresses review feedback, including removing response
jonkeller Jul 19, 2017
fbdb4a8
Fixes unit test
jonkeller Jul 19, 2017
f664d31
Adds a forgotten semicolon :(
jonkeller Jul 19, 2017
fcad58a
Addresses review feedback
jonkeller Jul 20, 2017
4c52b48
Splits crossDomainIframe functionality out from transport.js into ifr…
jonkeller Jul 20, 2017
2920068
Removes a couple blank lines
jonkeller Jul 20, 2017
d5b2dae
Fixes merge conflict
jonkeller Jul 20, 2017
7b12404
Mostly changes comments, removes a small amount of redundant unit tes…
jonkeller Jul 20, 2017
28c7b7a
Changes enum to const since all but one enum value no longer used. Re…
jonkeller Jul 21, 2017
409ac7c
Renames things using 3P in name, changes wire format from map to array
jonkeller Jul 21, 2017
ee822cf
Renamed files/classes to get rid of '3p'
jonkeller Jul 24, 2017
f4f5048
Adds 3p iframe to amp analytics. Uses Subscription API. Event message…
jonkeller Jul 13, 2017
0203122
Implements review feedback
jonkeller Jul 14, 2017
bff17d9
Removes extraData, new creative message
jonkeller Jul 15, 2017
0d64916
Amp 3p analytics using SubscriptionAPI, 3p side
jonkeller Jul 14, 2017
0e54a38
Fixes ampanalytics-lib tests
jonkeller Jul 14, 2017
3d7dbd6
Fixes linter issues
jonkeller Jul 14, 2017
4889860
Removes extraData, new creative message
jonkeller Jul 15, 2017
21d272f
Stash
jonkeller Jul 15, 2017
f363bf1
Changes creative ID to transport ID
jonkeller Jul 15, 2017
284d363
Includes transport ID in example logging, response
jonkeller Jul 15, 2017
c7898d7
Makes example ad click URL match parent branch
jonkeller Jul 15, 2017
352f1f8
Addresses review feedback
jonkeller Jul 17, 2017
904148d
Addresses further review feedback
jonkeller Jul 17, 2017
56cc6a6
extensions/amp-analytics/0.1/amp-analytics.js
jonkeller Jul 17, 2017
2cccfc3
Corrects rebase issue
jonkeller Jul 17, 2017
cfa8c33
Add XSS check to example HTML
jonkeller Jul 18, 2017
d8dde0f
Changes XSS message from log to warning
jonkeller Jul 18, 2017
98249eb
Addresses review nits
jonkeller Jul 18, 2017
78098c5
Addresses review feedback including removing response temporarily
jonkeller Jul 19, 2017
fa9c3f3
Clean up unit tests, rename event datatype
jonkeller Jul 20, 2017
ac5bdca
Resolves rebase issue
jonkeller Jul 20, 2017
d96b644
Intentionally adding trivial change, will remove in a minute
jonkeller Jul 20, 2017
0f03774
Removing trival change added a minute ago. This is because Git UI is …
jonkeller Jul 20, 2017
3ff0ae4
Changes an enum to a const, since all but one enum value have been re…
jonkeller Jul 21, 2017
3092770
Renames things using 3P in name, changes wire format from map to array
jonkeller Jul 21, 2017
ac08ff3
Fixes unit test
jonkeller Jul 21, 2017
8e4a0a3
Renamed files/classes to get rid of '3p'
jonkeller Jul 24, 2017
29e94e5
Fixes some merge issues
jonkeller Jul 24, 2017
ab3db5f
Found typo in method name, which led to renaming that and a few other…
jonkeller Jul 24, 2017
a7f05ea
Adds missing space in error msg
jonkeller Jul 24, 2017
2f9bec5
Addresses review feedback re: renaming files/class, and object field …
jonkeller Jul 26, 2017
66d0cc4
Switch to IframeMessagingClient, add clarifying comment
jonkeller Jul 26, 2017
545ac7b
Now uses IframeMessagingClient
jonkeller Jul 26, 2017
c7b6c88
Greatly simplified iframe-transport-client
jonkeller Jul 26, 2017
88b6c4c
Updates unit tests
jonkeller Jul 26, 2017
015c48a
Changes whitespace only
jonkeller Jul 26, 2017
ef8fb8d
Fixes a broken unit test
jonkeller Jul 26, 2017
e7c0311
Changes comment only
jonkeller Jul 26, 2017
6ca88ee
Makes lib architecture more similar to ampcontext
jonkeller Jul 26, 2017
1e8595d
Adds 3p/iframe-transport-client-lib.js
jonkeller Jul 26, 2017
3349b6b
Differentiates message name, fixes possible null exception
jonkeller Jul 27, 2017
bd9a9eb
Trivial change to re-run Percy, which was flakey last time
jonkeller Jul 27, 2017
39164c5
Splits request/response message types better
jonkeller Jul 28, 2017
7b0a03b
Moves message type declarations into 3p-frame-messaging.js
jonkeller Jul 28, 2017
d265980
Addresses a few nits
jonkeller Jul 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions 3p/iframe-transport-client-lib.js
@@ -0,0 +1,37 @@
/**
* Copyright 2017 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './polyfills';
import {IframeTransportClient} from './iframe-transport-client.js';
import {initLogConstructor, setReportError} from '../src/log';

initLogConstructor();
// TODO(alanorozco): Refactor src/error.reportError so it does not contain big
// transitive dependencies and can be included here.
setReportError(() => {});

/**
* If window.iframeTransportClient does not exist, we must instantiate and
* assign it to window.iframeTransportClient, to provide the creative with
* all the required functionality.
*/
try {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const iframeTransportClientCreated =
new Event('amp-iframeTransportClientCreated');
window.iframeTransportClient = new IframeTransportClient(window);
window.dispatchEvent(iframeTransportClientCreated);
} catch (err) {
// do nothing with error
}
93 changes: 93 additions & 0 deletions 3p/iframe-transport-client.js
@@ -0,0 +1,93 @@
/**
* Copyright 2017 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {tryParseJson} from '../src/json';
import {dev, user} from '../src/log';
import {MessageType} from '../src/3p-frame-messaging';
import {IframeMessagingClient} from './iframe-messaging-client';

/** @private @const {string} */
const TAG_ = 'iframe-transport-client';

/**
* Receives event messages bound for this cross-domain iframe, from all
* creatives
*/
export class IframeTransportClient {

/** @param {!Window} win */
constructor(win) {
/** @private {!Window} */
this.win_ = win;

/** @private {?function(string,string)} */
this.listener_ = null;

/** @protected {!IframeMessagingClient} */
this.client_ = new IframeMessagingClient(win);
this.client_.setHostWindow(this.win_.parent);
this.client_.setSentinel(user().assertString(
tryParseJson(this.win_.name)['sentinel'],
'Invalid/missing sentinel on iframe name attribute' + this.win_.name));
this.client_.makeRequest(
MessageType.SEND_IFRAME_TRANSPORT_EVENTS,
MessageType.IFRAME_TRANSPORT_EVENTS,
eventData => {
const events =
/**
* @type
* {!Array<../src/3p-frame-messaging.IframeTransportEvent>}
*/
(eventData['events']);
user().assert(events,
'Received malformed events list in ' + this.win_.location.href);
dev().assert(events.length,
'Received empty events list in ' + this.win_.location.href);
user().assert(this.listener_,
'Must call onAnalyticsEvent in ' + this.win_.location.href);
events.forEach(event => {
try {
this.listener_ &&
this.listener_(event.message, event.transportId);
} catch (e) {
user().error(TAG_,
'Exception in callback passed to onAnalyticsEvent: ' +
e.message);
}
});
});
}

/**
* Registers a callback function to be called when an AMP analytics event
* is received.
* Note that calling this a second time will result in the first listener
* being removed - the events will not be sent to both callbacks.
* @param {function(string,string)} callback
*/
onAnalyticsEvent(callback) {
this.listener_ = callback;
}

/**
* Gets the IframeMessagingClient
* @returns {!IframeMessagingClient}
* @VisibleForTesting
*/
getClient() {
return this.client_;
}
}
2 changes: 1 addition & 1 deletion build-system/app.js
Expand Up @@ -936,7 +936,7 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'],
next();
});

app.get('/dist/ampanalytics-lib.js', (req, res, next) => {
app.get('/dist/iframe-transport-client-lib.js', (req, res, next) => {
req.url = req.url.replace(/dist/, 'dist.3p/current');
next();
});
Expand Down
1 change: 1 addition & 0 deletions build-system/config.js
Expand Up @@ -96,6 +96,7 @@ module.exports = {
'!{node_modules,build,dist,dist.tools,' +
'dist.3p/[0-9]*,dist.3p/current-min}/**/*.*',
'!dist.3p/current/**/ampcontext-lib.js',
'!dist.3p/current/**/iframe-transport-client-lib.js',
'!validator/dist/**/*.*',
'!validator/node_modules/**/*.*',
'!validator/nodejs/node_modules/**/*.*',
Expand Down
1 change: 1 addition & 0 deletions build-system/tasks/presubmit-checks.js
Expand Up @@ -282,6 +282,7 @@ var forbiddenTerms = {
whitelist: [
'3p/integration.js',
'3p/ampcontext-lib.js',
'3p/iframe-transport-client-lib.js',
'ads/alp/install-alp.js',
'ads/inabox/inabox-host.js',
'dist.3p/current/integration.js',
Expand Down
39 changes: 39 additions & 0 deletions examples/analytics-iframe-transport-remote-frame.html
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Requests Frame</title>
<script>
window.addEventListener('amp-iframeTransportClientCreated', () => {
/**
* To receive AMP analytics events in a third-party frame, you must
* pass a callback function to this method. The callback will be called
* when an event is received, and will be passed two parameters:
* @param {string} event A string of the format specified in the
* requests block of the amp-analytics JSON config
* @param {string} transportId An ID uniquely identifying which creative
* generated the event
*/
window.iframeTransportClient.onAnalyticsEvent(
(event, transportId) => {
// Now, do something meaningful with the AMP Analytics event
console.log('The page at: ' + window.location.href +
' has received an event: ' + event +
' from the creative with transport ID: ' + transportId);
});
});

// Load the script specified in the iframe’s name attribute:
const url = JSON.parse(window.name).scriptSrc;
if (url && url.startsWith('https://3p.ampproject.net/')) {
script = document.createElement('script');
script.src = url;
document.head.appendChild(script);
// The script will be loaded, and will call onNewAmpAnalyticsInstance()
} else {
console.warn('Received invalid URL - risk of XSS! ' + url);
}
</script>
</head>
<!-- The frame will not be visible, so there is no need for a body tag. -->
</html>
2 changes: 1 addition & 1 deletion extensions/amp-analytics/0.1/amp-analytics.js
Expand Up @@ -419,7 +419,7 @@ export class AmpAnalytics extends AMP.BaseElement {
// TODO(zhouyx, #7096) Track overwrite percentage. Prevent transport overwriting
if (inlineConfig['transport'] || this.remoteConfig_['transport']) {
const TAG = this.getName_();
user().error(TAG, 'Inline or remote config should not' +
user().error(TAG, 'Inline or remote config should not ' +
'overwrite vendor transport settings');
}
}
Expand Down
15 changes: 5 additions & 10 deletions extensions/amp-analytics/0.1/iframe-transport-message-queue.js
Expand Up @@ -15,9 +15,7 @@
*/

import {dev} from '../../../src/log';
import {
IFRAME_TRANSPORT_EVENTS_TYPE,
} from '../../../src/iframe-transport-common';
import {MessageType} from '../../../src/3p-frame-messaging';
import {SubscriptionApi} from '../../../src/iframe-helper';

/** @private @const {string} */
Expand Down Expand Up @@ -48,16 +46,13 @@ export class IframeTransportMessageQueue {

/**
* @private
* {!Array<!../../../src/iframe-transport-common.IframeTransportEvent>}
* {!Array<!../../../src/3p-frame-messaging.IframeTransportEvent>}
*/
this.pendingEvents_ = [];

/** @private {string} */
this.messageType_ = IFRAME_TRANSPORT_EVENTS_TYPE;

/** @private {!../../../src/iframe-helper.SubscriptionApi} */
this.postMessageApi_ = new SubscriptionApi(this.frame_,
this.messageType_,
MessageType.SEND_IFRAME_TRANSPORT_EVENTS,
true,
() => {
this.setIsReady();
Expand Down Expand Up @@ -94,7 +89,7 @@ export class IframeTransportMessageQueue {

/**
* Enqueues an event to be sent to a cross-domain iframe.
* @param {!../../../src/iframe-transport-common.IframeTransportEvent} event
* @param {!../../../src/3p-frame-messaging.IframeTransportEvent} event
* Identifies the event and which Transport instance (essentially which
* creative) is sending it.
*/
Expand All @@ -117,7 +112,7 @@ export class IframeTransportMessageQueue {
*/
flushQueue_() {
if (this.isReady() && this.queueSize()) {
this.postMessageApi_.send(IFRAME_TRANSPORT_EVENTS_TYPE,
this.postMessageApi_.send(MessageType.IFRAME_TRANSPORT_EVENTS,
/** @type {!JsonObject} */
({events: this.pendingEvents_}));
this.pendingEvents_ = [];
Expand Down
5 changes: 3 additions & 2 deletions extensions/amp-analytics/0.1/iframe-transport.js
Expand Up @@ -102,7 +102,8 @@ export class IframeTransport {
const useLocal = getMode().localDev || getMode().test;
const useRtvVersion = !useLocal;
const scriptSrc = calculateEntryPointScriptUrl(
this.win_.parent.location, 'ampanalytics-lib', useLocal, useRtvVersion);
this.win_.parent.location, 'iframe-transport-client-lib',
useLocal, useRtvVersion);
const frameName = JSON.stringify(/** @type {JsonObject} */ ({
scriptSrc,
sentinel,
Expand Down Expand Up @@ -182,7 +183,7 @@ export class IframeTransport {
dev().assert(frameData.queue, 'Event queue is missing for ' + this.id_);
frameData.queue.enqueue(
/**
* @type {!../../../src/iframe-transport-common.IframeTransportEvent}
* @type {!../../../src/3p-frame-messaging.IframeTransportEvent}
*/
({transportId: this.id_, message: event}));
}
Expand Down
101 changes: 101 additions & 0 deletions extensions/amp-analytics/0.1/test/test-iframe-transport-client.js
@@ -0,0 +1,101 @@
/**
* Copyright 2017 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {MessageType} from '../../../../src/3p-frame-messaging';
import {
IframeTransportClient,
} from '../../../../3p/iframe-transport-client';
import {dev, user} from '../../../../src/log';
import {adopt} from '../../../../src/runtime';
import * as sinon from 'sinon';

adopt(window);

let nextId = 5000;
function createUniqueId() {
return String(++(nextId));
}

describe('iframe-transport-client', () => {
let sandbox;
let badAssertsCounterStub;
let iframeTransportClient;
let sentinel;

beforeEach(() => {
sandbox = sinon.sandbox.create();
badAssertsCounterStub = sandbox.stub();
sentinel = createUniqueId();
window.name = '{"sentinel": "' + sentinel + '"}';
iframeTransportClient = new IframeTransportClient(window);
sandbox.stub(dev(), 'assert', (condition, msg) => {
if (!condition) {
badAssertsCounterStub(msg);
}
});
sandbox.stub(user(), 'assert', (condition, msg) => {
if (!condition) {
badAssertsCounterStub(msg);
}
});
});

afterEach(() => {
sandbox.restore();
});

/**
* Sends a message from the current window to itself
* @param {string} type Type of the message.
* @param {!JsonObject} object Message payload.
*/
function send(type, data) {
const object = {};
object['type'] = type;
object['sentinel'] = sentinel;
if (data['events']) {
object['events'] = data['events'];
} else {
object['data'] = data;
}
const payload = 'amp-' + JSON.stringify(object);
window./*OK*/postMessage(payload, '*');
}

it('fails to create iframeTransportClient if no window.name ', () => {
const oldWindowName = window.name;
expect(() => {
window.name = '';
new IframeTransportClient(window);
}).to.throw(/Cannot read property 'sentinel' of undefined/);
window.name = oldWindowName;
});

it('sets sentinel from window.name.sentinel ', () => {
expect(iframeTransportClient.getClient().sentinel_).to.equal(sentinel);
});

it('receives an event message ', () => {
window.processAmpAnalyticsEvent = (event, transportId) => {
expect(transportId).to.equal('101');
expect(event).to.equal('hello, world!');
};
send(MessageType.IFRAME_TRANSPORT_EVENTS, /** @type {!JsonObject} */ ({
events: [
{transportId: '101', message: 'hello, world!'},
]}));
});
});