Ghost/ghost/admin/app/services/billing.js
Nazar Gargol 5d59670ac3 Fixed browser URL syncronization with embeded iframe state
no issue

- Opted in to use explicit `hisotry.replaceState` and setting iframe's `src` using assignment instead of tracking it through computed property. This allows for tighter control over when iframe's history is updated which was causing problems when `src` was bound to computed property
- Added billing page metadata. This way browser history records appear with nicer signature
- Removed "update button" iframe and rewrote "global iframe" to not use modals. This allows to have single iframe on a page, which simplifies `postMessage` communication and preserve history inside iframe to be able to navigate it after closure
- Added route change handler responding to BMA app route changes. Allows to sync browser URL visible to the user with active route in BMA iframe. The sync is based on `hisory.replaceState` method that makes sure singular history records are kept in the browser history
- Added nested wildcard billing route. This is meant to catch all the nested routes inside of BMA iframe
2020-05-22 14:44:37 +12:00

92 lines
2.9 KiB
JavaScript

import Service from '@ember/service';
import {inject as service} from '@ember/service';
export default Service.extend({
router: service(),
config: service(),
ghostPaths: service(),
billingRouteRoot: '#/billing',
billingWindowOpen: false,
subscription: null,
previousRoute: null,
init() {
this._super(...arguments);
if (this.config.get('billingUrl')) {
window.addEventListener('message', (event) => {
if (event && event.data && event.data.route) {
this.handleRouteChangeInIframe(event.data.route);
}
});
}
},
handleRouteChangeInIframe(destinationRoute) {
if (this.get('billingWindowOpen')) {
let billingRoute = this.get('billingRouteRoot');
if (destinationRoute !== '/') {
billingRoute += destinationRoute;
}
if (window.location.hash !== billingRoute) {
window.history.replaceState(window.history.state, '', billingRoute);
}
}
},
getIframeURL() {
let url = this.config.get('billingUrl');
if (window.location.hash && window.location.hash.includes(this.get('billingRouteRoot'))) {
let destinationRoute = window.location.hash.replace(this.get('billingRouteRoot'), '');
if (destinationRoute) {
url += destinationRoute;
}
}
return url;
},
// Controls billing window modal visibility and sync of the URL visible in browser
// and the URL opened on the iframe. It is responsible to non user triggered iframe opening,
// for example: by entering "/billing" route in the URL or using history navigation (back and forward)
setBillingWindowOpen(value) {
let billingIframe = this.getBillingIframe();
if (billingIframe && value) {
billingIframe.contentWindow.location.replace(this.getIframeURL());
}
this.set('billingWindowOpen', value);
},
// Controls navigation to billing window modal which is triggered from the application UI.
// For example: pressing "View Billing" link in navigation menu. It's main side effect is
// remembering the route from which the action has been triggered - "previousRoute" so it
// could be reused when closing billing window
openBillingWindow(currentRoute, childRoute) {
this.set('previousRoute', currentRoute);
// Ensures correct "getIframeURL" calculation when syncing iframe location
// in setBillingWindowOpen
window.location.hash = childRoute || '/billing';
this.router.transitionTo(childRoute || '/billing');
},
closeBillingWindow() {
this.set('billingWindowOpen', false);
let transitionRoute = this.get('previousRoute') || '/';
this.router.transitionTo(transitionRoute);
},
getBillingIframe() {
return document.getElementById('billing-frame');
}
});