mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Updated <GHBillingIframe>
to not leak event listeners
no issue
refs dac5cca899
- added cleanup of the `message` event handler added to `window` when the component is destroyed
- added a guard to the event handler method to abort early if a message is received whilst the component is being destroyed
- updated Billing Service and `<GHBillingIframe>` to not use `@classic` decorator
This commit is contained in:
parent
1575304627
commit
f2c0bff9df
5 changed files with 57 additions and 46 deletions
|
@ -1009,3 +1009,4 @@ remove|ember-template-lint|no-action|34|39|34|39|e65f48edccba27e52c1f8358a9795dc
|
|||
remove|ember-template-lint|no-action|50|35|50|35|7432725bd18c48f69bf22dc9487d14d25dc6c1b7|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|33|28|33|28|5b371baf419f247953b91b626611cb831c524af3|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|50|28|50|28|40caf07c7cebf6f4321c5b7e7f2f426b5c30217b|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs
|
||||
remove|ember-template-lint|require-iframe-title|1|0|1|0|d1c9631d150af53ca33b16c8c280c9d815bf43da|1662681600000|1673053200000|1678237200000|app/components/gh-billing-iframe.hbs
|
||||
|
|
|
@ -1 +1 @@
|
|||
<iframe id="billing-frame" class="billing-frame" frameborder="0"></iframe>
|
||||
<iframe id="billing-frame" class="billing-frame" frameborder="0" title="Billing" ...attributes {{did-insert this.setup}}></iframe>
|
|
@ -1,10 +1,10 @@
|
|||
import Component from '@ember/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
@classic
|
||||
export default class GhBillingIframe extends Component {
|
||||
@service billing;
|
||||
@service ghostPaths;
|
||||
|
@ -13,36 +13,46 @@ export default class GhBillingIframe extends Component {
|
|||
|
||||
@inject config;
|
||||
|
||||
isOwner = null;
|
||||
fetchingSubscription = false;
|
||||
@tracked isOwner = null;
|
||||
@tracked fetchingSubscription = false;
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
window.removeEventListener('message', this.handleIframeMessage);
|
||||
}
|
||||
|
||||
@action
|
||||
setup() {
|
||||
this.billing.getBillingIframe().src = this.billing.getIframeURL();
|
||||
window.addEventListener('message', this.handleIframeMessage);
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
// only process messages coming from the billing iframe
|
||||
if (event?.data && this.billing.getIframeURL().includes(event?.origin)) {
|
||||
if (event.data?.request === 'token') {
|
||||
this._handleTokenRequest();
|
||||
}
|
||||
@action
|
||||
async handleIframeMessage(event) {
|
||||
if (this.isDestroyed || this.isDestroying) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data?.request === 'forceUpgradeInfo') {
|
||||
this._handleForceUpgradeRequest();
|
||||
}
|
||||
|
||||
if (event.data?.subscription) {
|
||||
this._handleSubscriptionUpdate(event.data);
|
||||
}
|
||||
// only process messages coming from the billing iframe
|
||||
if (event?.data && this.billing.getIframeURL().includes(event?.origin)) {
|
||||
if (event.data?.request === 'token') {
|
||||
this._handleTokenRequest();
|
||||
}
|
||||
});
|
||||
|
||||
if (event.data?.request === 'forceUpgradeInfo') {
|
||||
this._handleForceUpgradeRequest();
|
||||
}
|
||||
|
||||
if (event.data?.subscription) {
|
||||
this._handleSubscriptionUpdate(event.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleTokenRequest() {
|
||||
this.set('fetchingSubscription', false);
|
||||
this.fetchingSubscription = false;
|
||||
let token;
|
||||
const ghostIdentityUrl = this.get('ghostPaths.url').api('identities');
|
||||
const ghostIdentityUrl = this.ghostPaths.url.api('identities');
|
||||
|
||||
this.ajax.request(ghostIdentityUrl).then((response) => {
|
||||
token = response && response.identities && response.identities[0] && response.identities[0].token;
|
||||
|
@ -51,11 +61,11 @@ export default class GhBillingIframe extends Component {
|
|||
response: token
|
||||
}, '*');
|
||||
|
||||
this.set('isOwner', true);
|
||||
this.isOwner = true;
|
||||
}).catch((error) => {
|
||||
if (error.payload?.errors && error.payload.errors[0]?.type === 'NoPermissionError') {
|
||||
// no permission means the current user requesting the token is not the owner of the site.
|
||||
this.set('isOwner', false);
|
||||
this.isOwner = false;
|
||||
|
||||
// Avoid letting the BMA waiting for a message and send an empty token response instead
|
||||
this.billing.getBillingIframe().contentWindow.postMessage({
|
||||
|
@ -69,8 +79,8 @@ export default class GhBillingIframe extends Component {
|
|||
|
||||
// NOTE: the handler is placed here to avoid additional logic to check if iframe has loaded
|
||||
// receiving a 'token' request is an indication that page is ready
|
||||
if (!this.fetchingSubscription && !this.billing.get('subscription') && token) {
|
||||
this.set('fetchingSubscription', true);
|
||||
if (!this.fetchingSubscription && !this.billing.subscription && token) {
|
||||
this.fetchingSubscription = true;
|
||||
this.billing.getBillingIframe().contentWindow.postMessage({
|
||||
query: 'getSubscription',
|
||||
response: 'subscription'
|
||||
|
@ -100,8 +110,8 @@ export default class GhBillingIframe extends Component {
|
|||
}
|
||||
|
||||
_handleSubscriptionUpdate(data) {
|
||||
this.billing.set('subscription', data.subscription);
|
||||
this.billing.set('checkoutRoute', data?.checkoutRoute || '/plans');
|
||||
this.billing.subscription = data.subscription;
|
||||
this.billing.checkoutRoute = data?.checkoutRoute ?? '/plans';
|
||||
|
||||
if (data.subscription.status === 'active' && this.config.hostSettings?.forceUpgrade) {
|
||||
// config might not be updated after a subscription has been set to active.
|
||||
|
@ -126,7 +136,7 @@ export default class GhBillingIframe extends Component {
|
|||
) {
|
||||
// The action param will be picked up on a transition from the router and can
|
||||
// then send the destination route as a message to the BMA, which then handles the redirect.
|
||||
const checkoutAction = this.billing.get('billingRouteRoot') + '?action=checkout';
|
||||
const checkoutAction = this.billing.billingRouteRoot + '?action=checkout';
|
||||
|
||||
this.notifications.showAlert(htmlSafe(`Your audience has grown! To continue publishing, the site owner must confirm pricing for this number of members <a href="${checkoutAction}">here</a>`), {type: 'warn', key: 'billing.exceeded'});
|
||||
} else {
|
||||
|
|
|
@ -21,12 +21,12 @@ export default class ProRoute extends AuthenticatedRoute {
|
|||
return this.transitionTo('home');
|
||||
}
|
||||
|
||||
this.billing.set('previousTransition', transition);
|
||||
this.billing.previousTransition = transition;
|
||||
}
|
||||
|
||||
model(params) {
|
||||
if (params.action) {
|
||||
this.billing.set('action', params.action);
|
||||
this.billing.action = params.action;
|
||||
}
|
||||
|
||||
this.billing.toggleProWindow(true);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import Service, {inject as service} from '@ember/service';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
@classic
|
||||
export default class BillingService extends Service {
|
||||
@service ghostPaths;
|
||||
@service router;
|
||||
|
@ -11,14 +10,15 @@ export default class BillingService extends Service {
|
|||
@inject config;
|
||||
|
||||
billingRouteRoot = '#/pro';
|
||||
billingWindowOpen = false;
|
||||
subscription = null;
|
||||
previousRoute = null;
|
||||
action = null;
|
||||
ownerUser = null;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
@tracked billingWindowOpen = false;
|
||||
@tracked subscription = null;
|
||||
@tracked previousRoute = null;
|
||||
@tracked action = null;
|
||||
@tracked ownerUser = null;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (this.config.hostSettings?.billing?.url) {
|
||||
window.addEventListener('message', (event) => {
|
||||
|
@ -70,7 +70,7 @@ export default class BillingService extends Service {
|
|||
await this.store.findAll('user', {reload: true});
|
||||
user = this.store.peekAll('user').findBy('isOwnerOnly', true);
|
||||
}
|
||||
this.set('ownerUser', user);
|
||||
this.ownerUser = user;
|
||||
}
|
||||
return this.ownerUser;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ export default class BillingService extends Service {
|
|||
}, '*');
|
||||
}
|
||||
|
||||
this.set('action', null);
|
||||
this.action = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ export default class BillingService extends Service {
|
|||
|
||||
this.sendRouteUpdate();
|
||||
|
||||
this.set('billingWindowOpen', value);
|
||||
this.billingWindowOpen = value;
|
||||
}
|
||||
|
||||
// Controls navigation to billing window modal which is triggered from the application UI.
|
||||
|
@ -119,7 +119,7 @@ export default class BillingService extends Service {
|
|||
return;
|
||||
}
|
||||
|
||||
this.set('previousRoute', currentRoute);
|
||||
this.previousRoute = currentRoute;
|
||||
|
||||
// Ensures correct "getIframeURL" calculation when syncing iframe location
|
||||
// in toggleProWindow
|
||||
|
|
Loading…
Add table
Reference in a new issue