XFC (Cross-Frame-Container)
This project handles securely embedding web content into a 3rd party domain. Out of the box, it provides several features:
- Clickjacking protection using either a trusted origin or secret
- Automatic iFrame resizing
- Event dispatching from embedded content into a framework
Usage
Include xfc.js
in your project.
Ensure process.env.NODE_ENV
is set correctly in the build enviornment. Logging is only enabled in non-production environments. The environment can be set in webpack using the DefinePlugin
const webpack = require('webpack');
module.exports = {
plugins:[
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
Setting Up A Consumer
The consumer is the application which is embedding the 3rd party applications within it.
XFC.Consumer.init()
var frame = XFC.Consumer.mount(document.body, 'http://localprovider.com:8080/example/provider.html')
If the embedded app does not know which domain to trust, it may require secret authorization.
var frame = XFC.Consumer.mount(document.body, 'http://localprovider.com:8080/example/provider_b.html', {secret: 'abc123'})
Iframe Resizing Config
By default, the height of iframe will automatically resize based on the height of the embedded content. This behavior can be changed by passing an extra option (resizeConfig
) into mount
method.
XFC.Consumer.mount(document.body, 'http://localprovider.com:8080/example/provider.html', { resizeConfig: { scrolling: true } });
resizeConfig
is an object that accepts the following attributes.
name | type | default value | usage |
---|
scrolling | boolean | false | When set to be true , scrollbar may show up on iframe. |
autoResizeWidth | boolean | false | When set to be true , iframe will autoresize on width instead of on height |
fixedHeight | string | empty string | If specified (e.g. '200px'), the height will stay at the specified value.
NOTE: setting this attribute will turn off autoresizing. |
fixedWidth | string | empty string | If specified (e.g. '400px'), the width of iframe will stay at the specified value.
NOTE: setting this attribute will turn off autoresizing. |
heightCalculationMethod | string | 'bodyOffset' | Accepted values: 'bodyOffset' - use document.body.offsetHeight
'bodyScroll' - use document.body.scrollHeight
'documentElementOffset' - use document.documentElement.offsetHeight
'documentElementScroll' - use document.documentElement.scrollHeight
'max' - max of all of above options.
'min' - min of all of above options. |
widthCalculationMethod | string | 'scroll' | Accepted values: 'bodyOffset' - use document.body.offsetWidth
'bodyScroll' - use document.body.scrollWidth
'documentElementOffset' - use document.documentElement.offsetWidth
'documentElementScroll' - use document.documentElement.scrollWidth
'scroll' - max of bodyScroll and documentElementScroll
'max' - max of all of above options.
'min' - min of all of above options. |
customCalculationMethod | function | null | When specified, XFC will use the given method to update iframe's size when necessary (e.g. dom changes, window resized, etc.)
NOTE: context this is provided as iframe to this method, so in the method you can access the iframe by accessing this |
Monitoring Embedded App Lifecycles
Application lifecycles go through 3 stages as they load:
mounted
The application frame has been appended to the DOM and is loading the remote application site.launched
The application frame has loaded and the embedded application has begun authorization sequence. At this time the app is loaded, but is hidden to prevent clickjacking.authorized
The application has approved authorization and is now visible.
These statuses are communicated to the consumer application environment in 2 ways.
data-status
attribute on the embedded iFrame wrapper- A custom application event originating from the embedded iFrame.
Styling Cross Frame Containers Based On Status
The cross frame container data-status
attribute can be used as a styling hook to hide containers until they have authorized
.xfc[data-status='mounted'],
.xfc[data-status='launched'] {
display: none;
}
.xfc[data-status='authorized'] {
display: block;
}
Listening for Lifecycle Events
Event listeners can be set up to listen for lifecycle changes to a cross frame container.
The target of the event will be the embedded application frame which is an instance of EventEmitter.
frame.on('xfc.mounted', function() {
console.log('mounted', frame.wrapper);
})
frame.on('xfc.launched', function() {
console.log('launched', frame.wrapper);
})
frame.on('xfc.authorized', function(detail) {
console.log('authorized', detail);
})
Fullscreen Events
A provider application may request to launch another provider app fullscreen.
frame.on('xfc.fullscreen', function(url) {
window.open(url)
})
Sending custom events to a provider
Each frame of the consumer can send custom events to its embedded provider through its trigger method.
frame.trigger('fetchDetail', {id: 10});
Setting Up A Provider
The provider is the application which is embedded by the consumer.
XFC.Provider.init({
acls: ['http://localconsumer.com:8080']
})
If the app is using secret authorization, it may pass in a secret and wildcard for authorization.
XFC.Provider.init({
acls: ['*'],
secret: 'abc123'
})
If the app is using a custom secret callback function for authorization, it may pass in a callback function for validation of the secret.
XFC.Provider.init({
acls: ['*'],
secret: function(secret) {
return Promise.resolve('Success');
}
})
Launching Fullscreen
An application may request to launch a pagelet fullscreen within the consumer application.
XFC.Provider.init({ acls: ['http://localconsumer.com:8080'] })
XFC.Provider.fullscreen('http://localconsumer.com:8080/workflow')
Sending custom events to its frame
Provider can send custom events to the frame where it's embedded through trigger method.
XFC.Provider.trigger('ProviderURL', {url: window.location.href});
Sending http errors to its frame
Provider can send http errors to its frame by calling httpError method. This method will emit an 'xfc.provider.httpError' event onto the frame where the provider is embedded.
XFC.Provider.httpError({code: 500, message: 'Internal Server Error'});
Development
Add localconsumer.com to/etc/hosts
.
Add localprovider.com to/etc/hosts
.
Install dependencies and start the server.
npm install
npm run dev
Navigate to http://localconsumer.com:8080/example