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

Login dialog done HTML page #1330

Merged
merged 1 commit into from
Jan 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build-system/tasks/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ gulp.task('compile', function() {
'src/**/*.js',
// We do not want to load the entry point that loads the babel helpers.
'!src/amp-babel.js',
'!third_party/babel/custom-babel-helpers.js',
// Exclude since it's not part of the runtime/extension binaries.
'!extensions/amp-access/0.1/amp-login-done.js',
'builtins/**.js',
'third_party/caja/html-sanitizer.js',
'third_party/closure-library/sha384-generated.js',
Expand Down
225 changes: 225 additions & 0 deletions extensions/amp-access/0.1/amp-login-done-dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/**
* Copyright 2015 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 {listen} from '../../../src/event-helper';
import {parseQueryString} from '../../../src/url';


/**
* @private Visible for testing.
*/
export class LoginDoneDialog {
/**
* @param {!Window} win
*/
constructor(win) {
/** @const {!Window} */
this.win = win;
}

/**
* Runs all the steps in processing. First, the dialog tries to postback
* results to the opener window via messaging. If opener is not available
* or if it timeouts, the dialog switches to error mode where the "close"
* button will be available.
*/
start() {
this.setStyles_();
this.postback_().then(
this.postbackSuccess_.bind(this),
this.postbackError_.bind(this));
}

/**
* Sets necessary CSS styles to select the language. See `buildStyles_` for
* more details.
* @private
*/
setStyles_() {
const doc = this.win.document;
const style = doc.createElement('style');
style.textContent = this.buildStyles_();
doc.head.appendChild(style);
}

/**
* The language is selected based on the `hl` query parameter or
* `navigate.language`.
*
* See `buildStyles` module function for details.
*
* @return {string}
* @private
*/
buildStyles_() {
const query = parseQueryString(this.win.location.search);
const doc = this.win.document;
const nav = this.win.navigator;
const langSet = [query['hl'], nav.language, nav.userLanguage, 'en-US'];
for (let i = 0; i < langSet.length; i++) {
const lang = langSet[i];
if (!lang) {
continue;
}
const selector = buildLangSelector(lang);
if (!selector) {
continue;
}
if (!doc.querySelector(selector)) {
continue;
}
return selector + ' {display: block}';
}
return '';
}

/**
* Posts the response to the opening window via messaging. The message has the
* follow form:
* ```
* {
* sentinel: 'amp',
* type: 'result',
* result: <location hash>
* }
* ```
* Then the postback waits for ack signal from the opening window in the
* following form:
* ```
* {
* sentinel: 'amp',
* type: 'result-ack'
* }
* ```
*
* The promise is resolved when the ack signal is received. If the signal is
* not received within 5 seconds the postback times out and the promise is
* rejected.
*
* If the opening window is not available the promise is likewise rejected.
*
* @return {!Promise}
* @private
*/
postback_() {
const response = this.win.location.hash;
let unlisten = () => {};
return new Promise((resolve, reject) => {
const opener = this.win.opener;
if (!opener) {
reject(new Error('Opener not available'));
return;
}

// There's no senstive information coming in the response and thus the
// target can be '*'.
const target = '*';

unlisten = listen(this.win, 'message', e => {
if (!e.data || e.data.sentinel != 'amp') {
return;
}
if (e.data.type == 'result-ack') {
resolve();
}
});

opener./*OK*/postMessage({
sentinel: 'amp',
type: 'result',
result: response
}, target);

this.win.setTimeout(() => {
reject(new Error('Timed out'));
}, 5000);
}).then(() => {
unlisten();
}, error => {
unlisten();
throw error;
});
}

/**
* Tries to close window and if window is not closed within 3 seconds the
* document is switched to error mode where the close button is shown.
* @private
*/
postbackSuccess_() {
try {
this.win.close();
} catch (e) {
// Ignore.
}

// Give the opener a chance to close the dialog, if not, show the
// close button.
this.win.setTimeout(() => {
this.postbackError_(new Error('Failed to close the dialog'));
}, 3000);
}

/**
* Switches to the error mode. Close button will be shown.
* @param {*} error
* @private
*/
postbackError_(error) {
if (this.win.console && this.win.console.log) {
(this.win.console./*OK*/error || this.win.console.log).call(
this.win.console, 'Postback failed: ', error);
}

const doc = this.win.document;
doc.documentElement.classList.toggle('amp-postback-error', true);
doc.getElementById('closeButton').onclick = () => {
this.win.close();
};
}
}


/**
* The language is selected based on the `hl` query parameter or
* `navigate.language` by setting CSS of the following form:
* ```
* [lang="fr"], [lang="fr-FR"] {display: block}
* ```
* @param {string} lang
* @return {?string}
* @private Visible for testing.
*/
export function buildLangSelector(lang) {
if (!lang) {
return null;
}
const parts = lang.split('-');
let langExpr = '';
let langPrefix = '';
for (let i = 0; i < parts.length; i++) {
if (i > 0) {
langExpr += ', ';
langPrefix += '-';
}
langPrefix += i == 0 ? parts[i].toLowerCase() : parts[i].toUpperCase();
if (langPrefix.indexOf('"') != -1) {
langPrefix = langPrefix.replace(/\"/g, '');
}
langExpr += `[lang="${langPrefix}"]`;
}
return langExpr;
}
72 changes: 72 additions & 0 deletions extensions/amp-access/0.1/amp-login-done.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,minimal-ui">
<meta name="robots" content="noindex">
<title>AMP Login Complete</title>
<script async src="../../../dist/v0/amp-login-done-0.1.max.js"></script>
<style>
body {
margin: 0;
}

section {
margin: 16px;
}

[lang] {
display: none;
}

.button-section {
display: none;
}
.amp-postback-error .button-section {
display: block;
}
.amp-postback-error .progress-section {
display: none;
}

.button-section {
text-align: center;
}

#closeButton {
background: #4285f4;
color: #fff;
font-size: 14px;
font-weight: 500;
padding: 10px 20px;
border-radius: 2px;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.4);
outline: none;
border: none;
}
</style>
</head>
<body>

<section class="progress-section">
<div lang="en">
Returning back...
</div>
<div lang="fr">
Retour...
</div>
</section>

<section class="button-section">
<button id="closeButton">
<div lang="en">
Close
</div>
<div lang="fr">
Fermer
</div>
</button>
</section>

</body>
</html>
29 changes: 29 additions & 0 deletions extensions/amp-access/0.1/amp-login-done.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright 2015 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.
*/

/**
* @fileoverview The endpoint for returning Login dialog. It passes the return
* code back to AMP runtime using window messaging.
*/

import '../../../third_party/babel/custom-babel-helpers';
import '../../../src/polyfills';
import {LoginDoneDialog} from './amp-login-done-dialog';
import {onDocumentReady} from '../../../src/document-state';

onDocumentReady(document, () => {
new LoginDoneDialog(window).start();
});
11 changes: 9 additions & 2 deletions extensions/amp-access/0.1/login-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ class LoginDialog {
}
log.fine(TAG, 'Received message from dialog: ', e.data);
if (e.data.type == 'result') {
if (this.dialog_) {
this.dialog_./*OK*/postMessage({
sentinel: 'amp',
type: 'result-ack'
}, returnOrigin);
}
this.loginDone_(e.data.result);
}
});
Expand Down Expand Up @@ -227,8 +233,9 @@ class LoginDialog {
getReturnUrl_() {
if (getMode().localDev) {
const loc = this.win.location;
return loc.protocol + '//' + loc.host + '/dist/v0/amp-login-done.html';
return loc.protocol + '//' + loc.host +
'/extensions/amp-access/0.1/amp-login-done.html';
}
return 'https://cdn.ampproject.org/v0/amp-login-done.html';
return 'https://cdn.ampproject.org/v0/amp-login-done-0.1.html';
}
}
Loading