├── .babelrc
├── .gitignore
├── .storybook
├── addons.js
└── config.js
├── LICENSE
├── README.md
├── dist
└── IframeComm.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── Demo.js
├── Demo.test.js
├── IframeComm.js
├── IframeComm.test.js
└── stories
│ └── index.js
├── storybook-static
├── favicon.ico
├── iframe.html
├── index.html
└── static
│ ├── manager.3c744e6541ad696e6e59.bundle.js
│ ├── manager.3c744e6541ad696e6e59.bundle.js.map
│ ├── preview.9f856c001e9f0142c59b.bundle.js
│ └── preview.9f856c001e9f0142c59b.bundle.js.map
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@kadira/storybook/addons";
2 | import "@kadira/storybook-addon-notes/register";
3 | import "@kadira/storybook-addon-knobs/register";
4 | import "@kadira/storybook-addon-options/register";
5 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from "@kadira/storybook";
2 | import { setOptions } from "@kadira/storybook-addon-options";
3 | setOptions({
4 | name: "Cool",
5 | downPanelInRight: true
6 | });
7 |
8 | function loadStories() {
9 | require("../src/stories");
10 | }
11 |
12 | configure(loadStories, module);
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Petar Bojinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React iFrame communication
2 | ============
3 |
4 | > A React component for communicating between a parent window and an iframe.
5 |
6 | ## Demo
7 |
8 | Live Demo: [https://pbojinov.github.io/react-iframe-comm/](https://pbojinov.github.io/react-iframe-comm/)
9 |
10 | Or locally run:
11 |
12 | ```
13 | npm install
14 | npm run storybook
15 | ```
16 |
17 | Then open [http://localhost:9009/](http://localhost:9009/) in your browser.
18 |
19 |
20 | ## Installation
21 |
22 | The easiest way to use React Iframe Communication is to install it from NPM.
23 |
24 | ```
25 | npm install react-iframe-comm --save
26 | ```
27 |
28 | At this point you can import `react-iframe-comm` in your application as follows:
29 |
30 | ```javascript
31 | import IframeComm from 'react-iframe-comm';
32 | ```
33 |
34 | ## Usage
35 |
36 |
37 | ```javascript
38 | import React from "react";
39 | import IframeComm from "react-iframe-comm";
40 |
41 | const Demo = ({}) => {
42 |
43 | // the html attributes to create the iframe with
44 | // make sure you use camelCase attribute names
45 | const attributes = {
46 | src: "https://pbojinov.github.io/iframe-communication/iframe.html",
47 | width: "100%",
48 | height: "175",
49 | frameBorder: 1, // show frame border just for fun...
50 | };
51 |
52 | // the postMessage data you want to send to your iframe
53 | // it will be send after the iframe has loaded
54 | const postMessageData = "hello iframe";
55 |
56 | // parent received a message from iframe
57 | const onReceiveMessage = () => {
58 | console.log("onReceiveMessage");
59 | };
60 |
61 | // iframe has loaded
62 | const onReady = () => {
63 | console.log("onReady");
64 | };
65 |
66 | return (
67 |
73 | );
74 | };
75 |
76 | export default Demo;
77 |
78 | ```
79 |
80 | ## Configuration Options
81 |
82 |
83 | ```javascript
84 | IframeComm.propTypes = {
85 | /*
86 | Iframe Attributes
87 | https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#Attributes
88 | React Supported Attributes
89 | https://facebook.github.io/react/docs/dom-elements.html#all-supported-html-attributes
90 | Note: attributes are camelCase, not all lowercase as usually defined.
91 | */
92 | attributes: PropTypes.shape({
93 | allowFullScreen: PropTypes.oneOfType([
94 | PropTypes.string,
95 | PropTypes.bool
96 | ]),
97 | frameBorder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
98 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
99 | name: PropTypes.string,
100 | scrolling: PropTypes.string,
101 | // https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
102 | sandbox: PropTypes.string,
103 | srcDoc: PropTypes.string,
104 | src: PropTypes.string.isRequired,
105 | width: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
106 | }),
107 |
108 | // Callback function called when iFrame sends the parent window a message.
109 | handleReceiveMessage: PropTypes.func,
110 |
111 | /*
112 | Callback function called when iframe loads.
113 | We're simply listening to the iframe's `window.onload`.
114 | To ensure communication code in your iframe is totally loaded,
115 | you can implement a syn-ack TCP-like handshake using `postMessageData` and `handleReceiveMessage`.
116 | */
117 | handleReady: PropTypes.func,
118 |
119 | /*
120 | You can pass it anything you want, we'll serialize to a string
121 | preferablly use a simple string message or an object.
122 | If you use an object, you need to follow the same naming convention
123 | in the iframe so you can parse it accordingly.
124 | */
125 | postMessageData: PropTypes.any.isRequired,
126 |
127 | /*
128 | Enable use of the browser's built-in structured clone algorithm for serialization
129 | by settings this to `false`.
130 | Default is `true`, using our built in logic for serializing everything to a string.
131 | */
132 | serializeMessage: PropTypes.bool,
133 |
134 | /*
135 | Always provide a specific targetOrigin, not *, if you know where the other window's document should be located. Failing to provide a specific target discloses the data you send to any interested malicious site.
136 | */
137 | targetOrigin: PropTypes.string
138 | };
139 | ```
140 |
141 | ## License
142 |
143 | The MIT License (MIT) - 2017
144 |
--------------------------------------------------------------------------------
/dist/IframeComm.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
10 |
11 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
12 |
13 | var _react = require("react");
14 |
15 | var _react2 = _interopRequireDefault(_react);
16 |
17 | var _propTypes = require("prop-types");
18 |
19 | var _propTypes2 = _interopRequireDefault(_propTypes);
20 |
21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22 |
23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
24 |
25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26 |
27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
28 |
29 | var IframeComm = function (_Component) {
30 | _inherits(IframeComm, _Component);
31 |
32 | function IframeComm() {
33 | _classCallCheck(this, IframeComm);
34 |
35 | var _this = _possibleConstructorReturn(this, (IframeComm.__proto__ || Object.getPrototypeOf(IframeComm)).call(this));
36 |
37 | _this.onReceiveMessage = _this.onReceiveMessage.bind(_this);
38 | _this.onLoad = _this.onLoad.bind(_this);
39 | _this.sendMessage = _this.sendMessage.bind(_this);
40 | return _this;
41 | }
42 |
43 | _createClass(IframeComm, [{
44 | key: "componentDidMount",
45 | value: function componentDidMount() {
46 | window.addEventListener("message", this.onReceiveMessage);
47 | this._frame.addEventListener("load", this.onLoad);
48 | }
49 | }, {
50 | key: "componentWillUnmount",
51 | value: function componentWillUnmount() {
52 | window.removeEventListener("message", this.onReceiveMessage, false);
53 | }
54 | }, {
55 | key: "componentWillReceiveProps",
56 | value: function componentWillReceiveProps(nextProps) {
57 | if (this.props.postMessageData !== nextProps.postMessageData) {
58 | // send a message if postMessageData changed
59 | this.sendMessage(nextProps.postMessageData);
60 | }
61 | }
62 | }, {
63 | key: "onReceiveMessage",
64 | value: function onReceiveMessage(event) {
65 | var handleReceiveMessage = this.props.handleReceiveMessage;
66 |
67 | if (handleReceiveMessage) {
68 | handleReceiveMessage(event);
69 | }
70 | }
71 | }, {
72 | key: "onLoad",
73 | value: function onLoad() {
74 | var handleReady = this.props.handleReady;
75 |
76 | if (handleReady) {
77 | handleReady();
78 | }
79 | // TODO: Look into doing a syn-ack TCP-like handshake
80 | // to make sure iFrame is ready to REALLY accept messages, not just loaded.
81 | // send intial props when iframe loads
82 | this.sendMessage(this.props.postMessageData);
83 | }
84 | }, {
85 | key: "serializePostMessageData",
86 | value: function serializePostMessageData(data) {
87 | // Rely on the browser's built-in structured clone algorithm for serialization of the
88 | // message as described in
89 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
90 | if (!this.props.serializeMessage) {
91 | return data;
92 | }
93 |
94 | // To be on the safe side we can also ignore the browser's built-in serialization feature
95 | // and serialize the data manually.
96 | if ((typeof data === "undefined" ? "undefined" : _typeof(data)) === "object") {
97 | return JSON.stringify(data);
98 | } else if (typeof data === "string") {
99 | return data;
100 | } else {
101 | return "" + data;
102 | }
103 | }
104 | }, {
105 | key: "sendMessage",
106 | value: function sendMessage(postMessageData) {
107 | // Using postMessage data from props will result in a subtle but deadly bug,
108 | // where old data from props is being sent instead of new postMessageData.
109 | // This is because data sent from componentWillReceiveProps is not yet in props but only in nextProps.
110 | var targetOrigin = this.props.targetOrigin;
111 |
112 | var serializedData = this.serializePostMessageData(postMessageData);
113 | this._frame.contentWindow.postMessage(serializedData, targetOrigin);
114 | }
115 | }, {
116 | key: "render",
117 | value: function render() {
118 | var _this2 = this;
119 |
120 | var attributes = this.props.attributes;
121 | // define some sensible defaults for our iframe attributes
122 |
123 | var defaultAttributes = {
124 | allowFullScreen: false,
125 | frameBorder: 0
126 | };
127 | // then merge in the user's attributes with our defaults
128 | var mergedAttributes = Object.assign({}, defaultAttributes, attributes);
129 | return _react2.default.createElement("iframe", _extends({
130 | ref: function ref(el) {
131 | _this2._frame = el;
132 | }
133 | }, mergedAttributes));
134 | }
135 | }]);
136 |
137 | return IframeComm;
138 | }(_react.Component);
139 |
140 | IframeComm.defaultProps = {
141 | serializeMessage: true,
142 | targetOrigin: "*",
143 | postMessageData: ""
144 | };
145 |
146 | IframeComm.propTypes = {
147 | /*
148 | Iframe Attributes
149 | https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#Attributes
150 | React Supported Attributes
151 | https://facebook.github.io/react/docs/dom-elements.html#all-supported-html-attributes
152 | Note: attributes are camelCase, not all lowercase as usually defined.
153 | */
154 | attributes: _propTypes2.default.shape({
155 | allowFullScreen: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.bool]),
156 | frameBorder: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
157 | height: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
158 | name: _propTypes2.default.string,
159 | scrolling: _propTypes2.default.string,
160 | // https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
161 | sandbox: _propTypes2.default.string,
162 | srcDoc: _propTypes2.default.string,
163 | src: _propTypes2.default.string.isRequired,
164 | width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number])
165 | }),
166 |
167 | // Callback function called when iFrame sends the parent window a message.
168 | handleReceiveMessage: _propTypes2.default.func,
169 |
170 | /*
171 | Callback function called when iframe loads.
172 | We're simply listening to the iframe's `window.onload`.
173 | To ensure communication code in your iframe is totally loaded,
174 | you can implement a syn-ack TCP-like handshake using `postMessageData` and `handleReceiveMessage`.
175 | */
176 | handleReady: _propTypes2.default.func,
177 |
178 | /*
179 | You can pass it anything you want, we'll serialize to a string
180 | preferablly use a simple string message or an object.
181 | If you use an object, you need to follow the same naming convention
182 | in the iframe so you can parse it accordingly.
183 | */
184 | postMessageData: _propTypes2.default.any.isRequired,
185 |
186 | /*
187 | Enable use of the browser's built-in structured clone algorithm for serialization
188 | by settings this to `false`.
189 | Default is `true`, using our built in logic for serializing everything to a string.
190 | */
191 | serializeMessage: _propTypes2.default.bool,
192 |
193 | /*
194 | Always provide a specific targetOrigin, not *, if you know where the other window's document should be located. Failing to provide a specific target discloses the data you send to any interested malicious site.
195 | */
196 | targetOrigin: _propTypes2.default.string
197 | };
198 |
199 | exports.default = IframeComm;
200 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-iframe-comm",
3 | "version": "1.2.2",
4 | "description": "A React component for communicating between a parent window and an iframe.",
5 | "main": "dist/IframeComm.js",
6 | "author": "Petar Bojinov ",
7 | "bugs": {
8 | "url": "https://github.com/pbojinov/react-iframe-comm/issues"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/pbojinov/react-iframe-comm.git"
13 | },
14 | "keywords": [
15 | "react",
16 | "iframe",
17 | "communication",
18 | "comm",
19 | "postMessage",
20 | "react-component"
21 | ],
22 | "license": "MIT",
23 | "dependencies": {
24 | "@kadira/storybook-deployer": "^1.2.0",
25 | "react": "^15.4.2",
26 | "react-dom": "^15.4.2",
27 | "prop-types": "^15.5.7"
28 | },
29 | "devDependencies": {
30 | "@kadira/storybook": "^2.21.0",
31 | "@kadira/storybook-addon-knobs": "^1.7.1",
32 | "@kadira/storybook-addon-notes": "^1.0.1",
33 | "@kadira/storybook-addon-options": "^1.0.2",
34 | "babel-core": "^6.24.0",
35 | "babel-loader": "^6.4.1",
36 | "babel-preset-es2015": "^6.24.0",
37 | "babel-preset-react": "^6.23.0",
38 | "babel-preset-stage-0": "^6.22.0",
39 | "react-scripts": "0.9.5"
40 | },
41 | "scripts": {
42 | "start": "react-scripts start",
43 | "/*--build--*/": "react-scripts build",
44 | "build": " babel src/IframeComm.js --out-file dist/IframeComm.js",
45 | "test": "react-scripts test --env=jsdom",
46 | "eject": "react-scripts eject",
47 | "storybook": "start-storybook -p 9009 -s public",
48 | "build-storybook": "build-storybook -s public",
49 | "deploy-storybook": "storybook-to-ghpages"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pbojinov/react-iframe-comm/ab4b076ea17672483b21b1720b1983fd993fe980/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Demo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import IframeComm from "react-iframe-comm"; // loads build file
3 |
4 | const Demo = ({}) => {
5 | // the html attributes to create the iframe with
6 | // make sure you use camelCase attribute names
7 | const attributes = {
8 | src: "https://pbojinov.github.io/iframe-communication/iframe.html",
9 | width: "100%",
10 | height: "175"
11 | };
12 |
13 | // the postMessage data you want to send to your iframe
14 | // it will be send after the iframe has loaded
15 | const postMessageData = "hello iframe";
16 |
17 | // parent received a message from iframe
18 | const onReceiveMessage = () => {
19 | console.log("handleReceiveMessage");
20 | };
21 |
22 | // iframe has loaded
23 | const onReady = () => {
24 | console.log("onReady");
25 | };
26 |
27 | return (
28 |
34 | );
35 | };
36 |
37 | export default Demo;
38 |
--------------------------------------------------------------------------------
/src/Demo.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Demo from "./Demo";
4 |
5 | it("renders without crashing", () => {
6 | const div = document.createElement("div");
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/IframeComm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from 'prop-types';
3 |
4 | class IframeComm extends Component {
5 | constructor() {
6 | super();
7 | this.onReceiveMessage = this.onReceiveMessage.bind(this);
8 | this.onLoad = this.onLoad.bind(this);
9 | this.sendMessage = this.sendMessage.bind(this);
10 | }
11 | componentDidMount() {
12 | window.addEventListener("message", this.onReceiveMessage);
13 | this._frame.addEventListener("load", this.onLoad);
14 | }
15 | componentWillUnmount() {
16 | window.removeEventListener("message", this.onReceiveMessage, false);
17 | }
18 | componentWillReceiveProps(nextProps) {
19 | if (this.props.postMessageData !== nextProps.postMessageData) {
20 | // send a message if postMessageData changed
21 | this.sendMessage(nextProps.postMessageData);
22 | }
23 | }
24 | onReceiveMessage(event) {
25 | const { handleReceiveMessage } = this.props;
26 | if (handleReceiveMessage) {
27 | handleReceiveMessage(event);
28 | }
29 | }
30 | onLoad() {
31 | const { handleReady } = this.props;
32 | if (handleReady) {
33 | handleReady();
34 | }
35 | // TODO: Look into doing a syn-ack TCP-like handshake
36 | // to make sure iFrame is ready to REALLY accept messages, not just loaded.
37 | // send intial props when iframe loads
38 | this.sendMessage(this.props.postMessageData);
39 | }
40 | serializePostMessageData(data) {
41 | // Rely on the browser's built-in structured clone algorithm for serialization of the
42 | // message as described in
43 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
44 | if (!this.props.serializeMessage) {
45 | return data;
46 | }
47 |
48 | // To be on the safe side we can also ignore the browser's built-in serialization feature
49 | // and serialize the data manually.
50 | if (typeof data === "object") {
51 | return JSON.stringify(data);
52 | } else if (typeof data === "string") {
53 | return data;
54 | } else {
55 | return `${data}`;
56 | }
57 | }
58 | sendMessage(postMessageData) {
59 | // Using postMessage data from props will result in a subtle but deadly bug,
60 | // where old data from props is being sent instead of new postMessageData.
61 | // This is because data sent from componentWillReceiveProps is not yet in props but only in nextProps.
62 | const { targetOrigin } = this.props;
63 | const serializedData = this.serializePostMessageData(postMessageData);
64 | this._frame.contentWindow.postMessage(serializedData, targetOrigin);
65 | }
66 | render() {
67 | const { attributes } = this.props;
68 | // define some sensible defaults for our iframe attributes
69 | const defaultAttributes = {
70 | allowFullScreen: false,
71 | frameBorder: 0
72 | };
73 | // then merge in the user's attributes with our defaults
74 | const mergedAttributes = Object.assign(
75 | {},
76 | defaultAttributes,
77 | attributes
78 | );
79 | return (
80 |