Giter Site home page Giter Site logo

xfc's Introduction

XFC (Cross-Frame-Container)

Build Status npm version

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

// webpack.config.js
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.

// Create an app broker to manage embedded apps
XFC.Consumer.init()

// Mount the app at this URL and append its frame to the body element.
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'})

To remove and clean up a mounted app, simply call unmount method.

frame.unmount()

To load a new page within the existing frame, simply call load method with the new URL.

frame.load(newURL)

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.

// Pass scrolling as true to resizeConfig to make scrollbar show up.
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
targetSelectors string null When the embedded page contains elements styled with position: absolute, the iframe resizing logic won't calculate the height of the embedded page correctly because those elements are removed from normal document flow.

In this case, targetSelectors can be used to specify those absolute positioned elements so that they will be taken into consideration when calculating the height of the embedded page. Multiple selectors may be specified by separating them using commas.

If not specified, normal resizing logic will be used.

NOTE: this attribute can be also specified from Provider's side, e.g. XFC.Provider.init({targetSelectors: '#target'})

Setting Custom Attributes on Iframe

Sometimes, it's useful for developers to add more attributes onto mounted iframes. A common use case, for instance, is adding allow attribute to <iframe> tag for cross-origin iframes in Chrome 64+ (See reference here). In those cases, we can pass an extra option called iframeAttrs into mount method as follows.

XFC.Consumer.mount(document.body, 'http://localprovider.com:8080/example/provider.html', { iframeAttrs: { allow: 'geolocation; camera' }});

Here iframeAttrs is an object that contains entries, each of them being an entry of attribute's name and value.

Applying Visual Focus Indicator Style on Iframes

Certain configuration within this library such as fixedWidth or fixedHeight may prevent the content to be fully displayed within the viewport, and thereby needing scrolling to be enabled. For keyboard only users, it is important to display a visual focus indicator to indicate where the focus currently is at to help guide them with page navigation.

Example CSS and JS Code:

  .iframe-focus-style {
    outline: 2px dashed #000;
    outline-offset: 1px;
  }
XFC.Consumer.mount(document.body,
  'http://localprovider.com:8080/example/provider.html',
  {
    iframeAttrs: {
      id: "frame-id",
      style: "margin-top: 4px; margin-bottom: 4px;",
    },
    focusIndicator: {
      classNameFocusStyle: "iframe-focus-style",
    }
  }
);

Please note that the style associated with classNameFocusStyle will be applied to the iframe by setting the classNameFocusStyle to class attribute on the iframe when there is a focus event. During a blur event, the classNameFocusStyle name will be removed.

Monitoring Embedded App Lifecycles

Application lifecycles go through 3 stages as they load:

  1. mounted The application frame has been appended to the DOM and is loading the remote application site.
  2. 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.
  3. authorized The application has approved authorization and is now visible.
  4. unload The application frame is about to unload due to redirect or other causes.

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

/* Hide mounted apps that haven't loaded or authorized */
.xfc[data-status='mounted'],
.xfc[data-status='launched'] {
  display: none;
}

/* Reveal the app once its been authorized */
.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.

// Listen for a container to trigger a mounted event
frame.on('xfc.mounted', function() {
  console.log('mounted', frame.wrapper);
})

// Listen for a container to trigger a launched event
frame.on('xfc.launched', function() {
  console.log('launched', frame.wrapper);
})

// Listen for a container to trigger an authorized event
frame.on('xfc.authorized', function(detail) {
  console.log('authorized', detail);
})

// Listen for a container to trigger an unload event
frame.on('xfc.unload', function(detail) {
  console.log('unloading', detail);
})

Fullscreen Events

A provider application may request to launch another provider app fullscreen.

// Simulate opening a fullscreen link.
// In real implementation this would invoke a modal.
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.

// The following code will emit 'fetchDetail' event with an object containing additional info (e.g. 'id')
// onto the provider embedded in the frame
frame.trigger('fetchDetail', {id: 10});

Setting Up A Provider

The provider is the application which is embedded by the consumer.

// Only load within http://localconsumer.com:8080 origins
XFC.Provider.init({
  acls: ['http://localconsumer.com:8080']
})

If the provider is being used across the same domain with different subdomains, a wildcard character can be set as the first character of the acl.

// Only load within domain.com (http://test.domain.com, https://test2.domain.com, https://test.test2.domain.com, etc.)
XFC.Provider.init({
  acls: ['*.domain.com']
})

If the app is using secret authorization, it may pass in a secret and wildcard for authorization.

// Don't know which origin to trust, trust all origins that know the secret.
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.

// Don't know which origin to trust, trust all origins that know the secret.
XFC.Provider.init({
  acls: ['*'],
  secret: function(secret) {
    return Promise.resolve('Success');
  }
})

If the case of secret validation failures, acceptance is a new Error

XFC.Provider.init({
  acls: ['*'],
  secret: function(secret) {
    return Promise.reject(new Error('Failure'));
  }
})

If the app is using an alternate form a security and does require XFC to provide clickjacking support, a wildcard with no secret may be passed. Under these conditions, XFC will not hide the content and the consumer will automatically be authorized.

XFC.Provider.init({
  acls: ['*']
})

If the app wants to transmit details to frame after authorization, it may pass in an options object.

XFC.Provider.init({
  acls: ['*'],
  options: { moreDetail: 'detail' }
})

If the app and framework wants to register new custom methods with JSONRPC, it may pass in an customMethods object and Provider can call custom events on the frame using invoke method.

XFC.Consumer.mount(
  document.body,
  'http://localprovider.com:8080/example/provider.html',
  { customMethods: { add(x, y) { return Promise.resolve(x + y); } } }
);
XFC.Provider.invoke('add', [1, 2]);

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.

// The following code will emit 'ProviderURL' event with an additional object containing URL info
// onto the frame where the provider is embedded
// NOTE: trigger method can only be called after initializing provider.
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.

// httpError accepts a parameter that is an object containing message's detail information.
XFC.Provider.httpError({code: 500, message: 'Internal Server Error'});

Browser Support

All supported browsers are defined in here with browserslist queries.

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

xfc's People

Contributors

chris-boyle avatar divyaashritha avatar dkschoonover avatar gagerdude avatar gauthamsriman avatar grneggandsam avatar jmcountryman avatar jvarness avatar kafkahw avatar kolkheang avatar mhemesath avatar mschile avatar poloka avatar roxjcalderon avatar yusufali2205 avatar zhongyn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xfc's Issues

Allow the consumer to skip the mounting lifecycle so that he can reuse the same frame

Please fill out the below template as best you can.

Description of Issue

As a consumer of xfc i should be able to skip the mounting lifecycle so that i can reuse the same iframe and go through the remaining life cycle events of xfc launched and authorized so that i can skip the costly iframe creation operation

System Configuration

Project Version

Additional Details (optional)

In a use case where we launch the provider using xfc initial life cycle events and in a refresh scenario where we launch the provider with same url with different authentication params i should be able to use the existing frame and go through the launched and authorized state

Steps to Reproduce the Issue

  1. Launch any provider using xfc lifecycle events which will create a frame. Relaunch the same content provider which will create a new frame

Expected Outcomes

Ability to reuse the frame by skipping the mounting lifecycle event and go through the remaining lifecycle events

Eliminate the need to challenge the consumer when route protected

Please fill out the below template as best you can.

Description of Issue

If the route to mount a content provider is protected by some other means, such as OAuth header validation, there is no need to challenge the consumer for a secret. In this casethe Provider.init will not contain a secret but only ['*'] to embed into its calling consumer. The existing Application.launch has a check against the presence of the secret but if one doesn't exist, as the example provides there is not an else noted at this point

} else { 
  this.authorizeConsumer();
}

Expected Outcomes

  • Expect the challenge to the consumer is not executed
  • A log message can be displayed indicating the decision to authorize the consumer

Add trigger for iframe unloading (beforeunload event)

Please fill out the below template as best you can.

Description of Issue

As a consumer or content provider I would like the ability to use callback functions triggered by 'beforeunload' events within the frame. This is useful to allow consumers to detect when content within the frame is about to be redirected and perform actions based on the event. I would also like to be able to extract the url of a link clicked in the event a link caused the redirect.

A simple example would be for a consumer that is about to link within the frame and needs to scroll to the top of the page based on the redirect. With this feature, a callback can be registered which is triggered by an 'xfc.unload' event to scroll the page to the top.

Using version 1.3.0 of xfc

Expected Outcomes

  • 'xfc.unload' triggers will be called when redirects occur within the page
  • 'xfc.unload' triggers will be called with the href of links that caused the redirect

Unable to access frame in orion-mpage-component

Please fill out the below template as best you can.

I have to send bedrock configuration to content provider, so I am  adding this.trigger(‘message’, xxx) at authorized in orion-mpage-component-body.js. I can see in console that ‘this’ is the frame, but when I used it, its scope change to component and throws error.

Description of Issue

Unable to access frame inside xfcInit.

System Configuration

Project Version

Additional Details (optional)

Steps to Reproduce the Issue

  1. Step 1

Add this lines in "xfc.authorized"

var component = this.getProp("component");
this.trigger('message', component.getFilterMappingsObj());

  1. Step 2

Expected Outcomes

  • Links can be styled as buttons
  • Disabled links behave the same as disabled buttons

Conflict with F-Twelve tool

Description of Issue

F-Twelve adds a JS console to the DOM i.e. whenever console.log is called, it writes that message to the DOM. However, when the DOM mutates, XFC writes to console.log so F-Twelve writes the message to the DOM and the cycle repeats. This causes an endless loop, freezing the browser.

System Configuration

Project Version

1.8.1

Additional Details (optional)

I would fix this on the F-Twelve side if I could but I don't think I can. The whole tool is designed around outputing console.log messages to the DOM.

Steps to Reproduce the Issue

  1. Install XFC
  2. Save the f-twelve js and css files to \example\embedded_app_lifecycle
  3. Add the following to the head tag in 2_c_provider_embedded_app.html
    <link rel="stylesheet" href="f-twelve.css"/>
    <script src="f-twelve.umd.js"></script>
  1. Add FTwelve.show(); in the body's script tag after XFC.Provider.init in 2_c_provider_embedded_app.html
  2. Navigate to http://localconsumer.com:8080/example/embedded_app_lifecycle/2_c_index.html
  3. Open your browser's console
  4. Click "Console" in the bottom left of the DOM to open the f-twelve console
    image
  5. Everything freezes and the below message gets written to the console forever. In Chrome the only way to close the window is kill the process.
    image

Adding a breakpoint to the console.log in logger.js shows the self.requestResize() in application.js at the bottom of the stack.
image

It seems like this is meant to be called only on a resize, so maybe it needs to be smarter about detecting if the event is actually a resize or not. Another idea is add a special exception class (xfc-exclude-mutation-observer for example) which I would add to the root element of f-twelve:

if(!Array.from(mutations).some((mutation) => parentHasClass(mutation.target, 'xfc-exclude-mutation-observer'))){
    return self.requestResize()
}

The parentHasClass helper function used above would be like:

const parentHasClass = function(element, className) {
    if (element.classList && Array.from(element.classList).some((pClassName) => pClassName === className)){
        return true;
    } else {
        return element.parentNode && parentHasClass(element.parentNode, className);
    }
}

Expected Outcomes

It is common for an MPage developer to use F-Twelve as well as cerner-smart-embeddable-lib, they should able to use them both at the same time.

Slow loading images not accounted for in resize calculation

Description of Issue

Slow loading images mess up the resize logic. If an image takes longer to load than than it takes for the Provider to trigger resize, the image won't be accounted for in the resize calculation. When the image does finally load, the iframe will be too small.

I think the Provider needs to add an event handler to trigger resize logic whenever an image loads

document.body.addEventListener(
    'load',
    function(event){
        var tgt = event.target;
        if( tgt.tagName == 'IMG'){
            this.requestResize()
        }
    },
    true
)

usecapture is required because image load events don't bubble.

Update unload listener to handle only when a page is unloaded

Description of Issue

As a consumer I would like the the data-status to be changed to unloaded only when the user navigates away from the page.

Additional Details (optional)

Steps to Reproduce the Issue

  1. When href with tel(href="tel:XXX") is clicked the data-status changes to unloaded(it should change the status only when the user navigates away from the page i.e., for real links)

Consumer trigger to content provider not functioning

Please fill out the below template as best you can.

Description of Issue

Attempted to utilize provided example within project to demonstrate triggering events into the content provider from the consumer. Receive the following error:

error message : Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘http://localprovider.com:8080’) does not match the recipient window’s origin (‘http://localconsumer.com:8080’)

System Configuration

Project Version

https://github.com/cerner/xfc/tree/a74c23f11d033d8cd099a73f588ce30fe6e2d068

Steps to Reproduce the Issue

consumer: https://github.com/prateekgta/xfc/blob/master/example/cross_origin_communication/3_a_index.html
provider: https://github.com/prateekgta/xfc/blob/master/example/cross_origin_communication/3_a_provider.html

Expected Outcomes

A message relayed and output in the console that event triggered to the content provider.

Add ability to pass attributes to consumer mount method for setting on iframe

Please fill out the below template as best you can.

Description of Issue

Starting from version 64, Chrome is deprecating permissions in cross origin iframes. Hence, iframe needs to have allow attribute where permissions can be set. Reference.

So, mount method in consumer has to allow passing attributes and ultimately set it on the iframe created here.

Expected Outcomes

  • mount() allows the attributes to be passed and sets it on the iframe.

Throw Error When Provider is not Initialized

Please fill out the below template as best you can.

Description of Issue

Add meaningful error when Provider is not Initialized and a event is triggered or listener is added/called. For example, if Prover.trigger is called when the Provider has not been initialized, it attempts to access this.application which is created upon Provider.init(). If the Provider has not been initialized, it throws the error

Error: Cannot read property 'trigger' of undefined
at Provider.trigger

This is error does not make it immediately obvious that the issue is related to the Provider initialization.

Steps to Reproduce the Issue

  1. Setup an application which called Provider.trigger() without initializing the provider
  2. See error in the console

Expected Outcomes

  • Throw Error message saying something like "Cannot handle/perform because the Provider has not been initialized"

Getting script error 'hasAttribute' method or property undefined in IE7

Please fill out the below template as best you can.

Description of Issue

Getting script error 'hasAttribute' method or property undefined when our application is rendered in IE7. nil checks need to added around the hasAttribute property/method usage in provider.js file.

image

System Configuration

IE7

Project Version

Steps to Reproduce the Issue

  1. This library is included in cerner-smart-embeddable-library which is used by Cerner Smart Apps.
  2. When our smart app is loading, this java script error pops up.

Expected Outcomes

  • Javascript errors should not be present

Consumer iframe not working on Firefox/Safari

The following code works fine with chrome. But, Firefox/Safari does not load the iframe.

XFC.init();
XFC.Consumer.mount(document.getElementById('video_frame'), "url://xxx");

But, setting the iframe.src through the browser console after the page load, renders the iframe properly.

Possible fix:
Setting iframe.src = this.source; after this.wrapper.appendChild(iframe); might fix the issue.

Add ability to 'fire and wait' a message

As a consumer or content provider I would like the ability to send and wait for a response to a message
So that I may ask either the consumer or content provider for some information
I will know when complete when I can send a message to say my consumer asking for a value and my consumer can listen and respond to that message back to the calling event.

A simple example would be for consumer that is about to refresh their page asks its content providers if they are dirty before allowing the entire page to be destroyed. And upon check the page either refreshes or a message is displayed. The logic for both sides of the fence and event names would have to be handled by the individual's implementation but looking for a reusable way to provide this 'fire and wait' type of model rather than a 'fire and forget'. Grated this could be accomplished by providing a payload with a return event name but would be nice if was a first class method.

Unable to get property 'ADDITION' of undefined or null reference

Please fill out the below template as best you can.

Description of Issue

Receiving the following error when embedded within an iframe

Unable to get property 'ADDITION' of undefined or null reference

System Configuration

Project Version

xfc-1.0.1

Additional Details (optional)

IE 11.0.32 build 11.0.9600.18349CO (KB3160005)
Also logged issue against the mutation-observer for additional assistance.
The line of code that is throwing the error was here.

Steps to Reproduce the Issue

Unable to create in sample application with local, only reproducible on a citrix machine.

Expected Outcomes

Content loads without javascript errors.

Update Dependencies

Description of Issue

Several dependencies are out of date and have potential security vulnerabilities.

I took a brief look. It appeared most would be resolved by upgrading webpack-dev-server, webpack, and mocha versions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.