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

Subscriptions: Intercept login action #15362

Merged
merged 14 commits into from May 18, 2018
30 changes: 27 additions & 3 deletions extensions/amp-subscriptions/0.1/amp-subscriptions.js
Expand Up @@ -153,8 +153,7 @@ export class SubscriptionService {
* service.
*
* @param {string} serviceId
* @param {function(!JsonObject, !ServiceAdapter):!SubscriptionPlatform}
* subscriptionPlatformFactory
* @param {function(!JsonObject, !ServiceAdapter):!SubscriptionPlatform} subscriptionPlatformFactory
*/
registerPlatform(serviceId, subscriptionPlatformFactory) {
return this.initialize_().then(() => {
Expand Down Expand Up @@ -271,7 +270,7 @@ export class SubscriptionService {
this.initializeLocalPlatforms_(service);
});

this.platformStore_.getAllRegisteredPlatforms().forEach(
this.platformStore_.getAvailablePlatforms().forEach(
subscriptionPlatform => {
this.fetchEntitlements_(subscriptionPlatform);
}
Expand Down Expand Up @@ -457,6 +456,31 @@ export class SubscriptionService {
platform.decorateUI(element, action, options);
});
}

/**
* Evaluates platforms and select the one to be selected for login.
* @return {!Promise<!./subscription-platform.SubscriptionPlatform>}
*/
scoreBasedLogin() {
return this.platformStore_.getAllPlatforms().then(platforms => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We are taking some risk here getting into popup blocker situation. What if we went synchronous here with strictly already loaded platforms? Local is always there, so we always have one option. In other words, maybe getAvailablePlatforms() method instead? Otherwise "login" is only enhanced - there's no significant change in behavior. If we fail to enhance something - it's still functional.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

const platformScores = [];
platforms.forEach(platform => {
let score = 0;
if (platform.supportsCurrentViewer()) {
score += 1000;
}
platformScores.push({
platform,
score,
});
});

platformScores.sort(function(platform1, platform2) {
return platform2.weight - platform1.weight;
});
return platformScores[0].platform;
Copy link
Contributor

Choose a reason for hiding this comment

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

If none of the platforms matches viewer - would this guarantee that local wins then?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we have a reason to do this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. With all equal "local" should win.

});
}
}


Expand Down
13 changes: 10 additions & 3 deletions extensions/amp-subscriptions/0.1/local-subscription-platform.js
Expand Up @@ -151,11 +151,18 @@ export class LocalSubscriptionPlatform {
handleClick_(element) {
if (element) {
const action = element.getAttribute('subscriptions-action');
if (element.hasAttribute('subscriptions-service')) {
if (element.getAttribute('subscriptions-service') === 'local') {
this.executeAction(action);
} else if (!element.hasAttribute('subscriptions-service')
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be just easier to do if ((element.getAttribute('subscriptions-service') || 'auto') == 'auto')?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

|| element.getAttribute('subscriptions-service') === 'auto'
|| element.getAttribute('subscriptions-service') === '') {
this.serviceAdapter_.login().then(platform => {
const serviceId = platform.getServiceId();
this.serviceAdapter_.delegateActionToService(action, serviceId);
});
} else if (element.getAttribute('subscriptions-service')) {
const serviceId = element.getAttribute('subscriptions-service');
this.serviceAdapter_.delegateActionToService(action, serviceId);
} else {
this.executeAction(action);
}
}
}
Expand Down
148 changes: 84 additions & 64 deletions extensions/amp-subscriptions/0.1/platform-store.js
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import {Deferred} from '../../../src/utils/promise';
import {Entitlement} from './entitlement';
import {Observable} from '../../../src/observable';
import {dev, user} from '../../../src/log';
Expand Down Expand Up @@ -49,7 +50,7 @@ export class PlatformStore {
/** @private @const {!Observable<{serviceId: string}>} */
this.onPlatformResolvedCallbacks_ = new Observable();

/** @private {?Promise<boolean>} */
/** @private {?Deferred} */
this.grantStatusPromise_ = null;

/** @private @const {!Observable} */
Expand All @@ -58,10 +59,10 @@ export class PlatformStore {
/** @private {?Entitlement} */
this.grantStatusEntitlement_ = null;

/** @private {?Promise<?Entitlement>} */
/** @private {?Deferred<?Entitlement>} */
this.grantStatusEntitlementPromise_ = null;

/** @private {?Promise<!Array<!./entitlement.Entitlement>>} */
/** @private {?Deferred<!Array<!./entitlement.Entitlement>>} */
this.allResolvedPromise_ = null;

/** @private {!Array<string>} */
Expand Down Expand Up @@ -91,6 +92,7 @@ export class PlatformStore {
/**
*Calls a callback for when a platform is resolved.
* @param {string} serviceId
* @param {!Function} callback
*/
onPlatformResolves(serviceId, callback) {
const platform = this.subscriptionPlatforms_[serviceId];
Expand Down Expand Up @@ -128,10 +130,11 @@ export class PlatformStore {
}

/**
* Returns all the platforms;
* Returns all available platforms.
*
* @return {!Array<!./subscription-platform.SubscriptionPlatform>}
*/
getAllRegisteredPlatforms() {
getAvailablePlatforms() {
const platforms = [];
for (const platformKey in this.subscriptionPlatforms_) {
const subscriptionPlatform =
Expand All @@ -141,6 +144,27 @@ export class PlatformStore {
return platforms;
}

/**
* Returns a promise which resolves when all platforms are resolved.
*
* @return {!Promise}
*/
getAllPlatforms() {
const allPlatformPromise = new Deferred();
if (Object.keys(this.subscriptionPlatforms_).length
=== this.serviceIds_.length) {
allPlatformPromise.resolve(this.getAvailablePlatforms());
} else {
this.onPlatformResolvedCallbacks_.add(() => {
const platformLength = Object.keys(this.subscriptionPlatforms_).length;
if (platformLength === this.serviceIds_.length) {
allPlatformPromise.resolve(this.getAvailablePlatforms());
}
});
}
return allPlatformPromise.promise;
}

/**
* This registers a callback which is called whenever a service id is resolved
* with an entitlement.
Expand Down Expand Up @@ -184,37 +208,36 @@ export class PlatformStore {
*/
getGrantStatus() {
if (this.grantStatusPromise_ !== null) {
return this.grantStatusPromise_;
return this.grantStatusPromise_.promise;
}

this.grantStatusPromise_ = new Promise(resolve => {
this.grantStatusPromise_ = new Deferred();

// Check if current entitlements unblocks the reader
for (const key in this.entitlements_) {
const entitlement = (this.entitlements_[key]);
// Check if current entitlements unblocks the reader
for (const key in this.entitlements_) {
const entitlement = (this.entitlements_[key]);
if (entitlement.granted) {
this.saveGrantEntitlement_(entitlement);
this.grantStatusPromise_.resolve(true);
}
}

if (this.areAllPlatformsResolved_()) {
// Resolve with null if non of the entitlements unblocks the reader
this.grantStatusPromise_.resolve(false);
} else {
// Listen if any upcoming entitlements unblock the reader
this.onChange(({entitlement}) => {
if (entitlement.granted) {
this.saveGrantEntitlement_(entitlement);
return resolve(true);
this.grantStatusPromise_.resolve(true);
} else if (this.areAllPlatformsResolved_()) {
this.grantStatusPromise_.resolve(false);
}
}

if (this.areAllPlatformsResolved_()) {
// Resolve with null if non of the entitlements unblocks the reader
return resolve(false);
} else {
// Listen if any upcoming entitlements unblock the reader
this.onChange(({entitlement}) => {
if (entitlement.granted) {
this.saveGrantEntitlement_(entitlement);
resolve(true);
} else if (this.areAllPlatformsResolved_()) {
resolve(false);
}
});
}
});
});
}

return this.grantStatusPromise_;
return this.grantStatusPromise_.promise;
}

/**
Expand All @@ -240,25 +263,23 @@ export class PlatformStore {
*/
getGrantEntitlement() {
if (this.grantStatusEntitlementPromise_) {
return (this.grantStatusEntitlementPromise_);
return (this.grantStatusEntitlementPromise_.promise);
}

this.grantStatusEntitlementPromise_ = new Promise(resolve => {
if ((this.grantStatusEntitlement_
&& this.grantStatusEntitlement_.isSubscriber())
this.grantStatusEntitlementPromise_ = new Deferred();
if ((this.grantStatusEntitlement_
&& this.grantStatusEntitlement_.isSubscriber())
|| this.areAllPlatformsResolved_()) {
this.grantStatusEntitlementPromise_.resolve(this.grantStatusEntitlement_);
} else {
this.onGrantStateResolvedCallbacks_.add(() => {
if (this.grantStatusEntitlement_.granted
|| this.areAllPlatformsResolved_()) {
resolve(this.grantStatusEntitlement_);
} else {
this.onGrantStateResolvedCallbacks_.add(() => {
if (this.grantStatusEntitlement_.granted
|| this.areAllPlatformsResolved_()) {
resolve(this.grantStatusEntitlement_);
}
});
}
});

return this.grantStatusEntitlementPromise_;
this.grantStatusEntitlementPromise_.resolve(
this.grantStatusEntitlement_);
}
});
}
return this.grantStatusEntitlementPromise_.promise;
}

/**
Expand All @@ -275,24 +296,23 @@ export class PlatformStore {
*/
getAllPlatformsEntitlements_() {
if (this.allResolvedPromise_) {
return this.allResolvedPromise_;
return this.allResolvedPromise_.promise;
}

this.allResolvedPromise_ = new Promise(resolve => {
if (this.areAllPlatformsResolved_()) {
// Resolve with null if non of the entitlements unblocks the reader
return resolve(this.getAvailablePlatformsEntitlements_());
} else {
// Listen if any upcoming entitlements unblock the reader
this.onChange(() => {
if (this.areAllPlatformsResolved_()) {
resolve(this.getAvailablePlatformsEntitlements_());
}
});
}
});

return this.allResolvedPromise_;
this.allResolvedPromise_ = new Deferred();
if (this.areAllPlatformsResolved_()) {
// Resolve with null if non of the entitlements unblocks the reader
this.allResolvedPromise_.resolve(
this.getAvailablePlatformsEntitlements_());
} else {
// Listen if any upcoming entitlements unblock the reader
this.onChange(() => {
if (this.areAllPlatformsResolved_()) {
this.allResolvedPromise_.resolve(
this.getAvailablePlatformsEntitlements_());
}
});
}
return this.allResolvedPromise_.promise;
}

/**
Expand Down Expand Up @@ -354,7 +374,7 @@ export class PlatformStore {
dev().assert(this.areAllPlatformsResolved_(),
'All platforms are not resolved yet');

this.getAllRegisteredPlatforms().forEach(platform => {
this.getAvailablePlatforms().forEach(platform => {
let weight = 0;
const entitlement =
this.getResolvedEntitlementFor(platform.getServiceId());
Expand Down
9 changes: 9 additions & 0 deletions extensions/amp-subscriptions/0.1/service-adapter.js
Expand Up @@ -79,6 +79,15 @@ export class ServiceAdapter {
getDialog() {
return this.subscriptionService_.getDialog();
}

/**
* Returns login platform based on platform selection
*
* @return {!Promise}
Copy link
Contributor

Choose a reason for hiding this comment

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

!Promise<???>

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed, was an intermittent thing.

*/
login() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's rename to represent a bit more precisely what it does. E.g. "selectPlatformForLogin" or some such.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

return this.subscriptionService_.scoreBasedLogin();
}
}

/** @package @VisibleForTesting */
Expand Down
18 changes: 18 additions & 0 deletions extensions/amp-subscriptions/0.1/test/test-amp-subscriptions.js
Expand Up @@ -553,4 +553,22 @@ describes.fakeWin('AmpSubscriptions', {amp: true}, env => {
expect(decorateUIStub).to.be.calledWith(element);
});
});

describe('scoreBasedLogin', () => {

it('should return the platform which ever supports viewer', () => {
const platform = new SubscriptionPlatform();
platform.getServiceId = () => 'swg-google';
platform.supportsCurrentViewer = () => true;
const localPlatform = new SubscriptionPlatform();
localPlatform.getServiceId = () => 'local';
subscriptionService.platformStore_ = new PlatformStore(
['local', 'swg-google']);
sandbox.stub(subscriptionService.platformStore_, 'getAllPlatforms')
.callsFake(() => Promise.resolve([platform, localPlatform]));
return subscriptionService.scoreBasedLogin().then(platform => {
expect(platform.getServiceId()).to.be.equal(platform.getServiceId());
});
});
});
});
25 changes: 25 additions & 0 deletions extensions/amp-subscriptions/0.1/test/test-local-subscriptions.js
Expand Up @@ -136,6 +136,7 @@ describes.fakeWin('LocalSubscriptionsPlatform', {amp: true}, env => {
beforeEach(() => {
element = document.createElement('div');
element.setAttribute('subscriptions-action', 'subscribe');
element.setAttribute('subscriptions-service', 'local');
});

it('should call executeAction with subscriptions-action value', () => {
Expand All @@ -159,6 +160,30 @@ describes.fakeWin('LocalSubscriptionsPlatform', {amp: true}, env => {
expect(executeStub).to.not.be.called;
expect(delegateStub).to.be.called;
});

it('should delegate service selection to scoreBasedLogin if no service '
+ 'name is specified', () => {
element.removeAttribute('subscriptions-service');
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's also test "auto" value.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

const platform = {};
const serviceId = 'serviceId';
platform.getServiceId = sandbox.stub().callsFake(() => serviceId);
const loginStub = sandbox.stub(
localSubscriptionPlatform.serviceAdapter_,
'login'
).callsFake(() => Promise.resolve(platform));
const delegateStub = sandbox.stub(
localSubscriptionPlatform.serviceAdapter_,
'delegateActionToService'
);
localSubscriptionPlatform.handleClick_(element);
expect(loginStub).to.be.called;
return loginStub().then(() => {
expect(delegateStub).to.be.calledWith(
element.getAttribute('subscriptions-action'),
serviceId,
);
});
});
});

describe('executeAction', () => {
Expand Down