-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathIframeComm.js
158 lines (147 loc) · 5.71 KB
/
IframeComm.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import React, { Component } from "react";
import PropTypes from 'prop-types';
import { atLeastOneRequired } from './custom.proptypes'
class IframeComm extends Component {
constructor() {
super();
this.onReceiveMessage = this.onReceiveMessage.bind(this);
this.onLoad = this.onLoad.bind(this);
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount() {
window.addEventListener("message", this.onReceiveMessage);
this._frame.addEventListener("load", this.onLoad);
}
componentWillUnmount() {
window.removeEventListener("message", this.onReceiveMessage, false);
}
componentWillReceiveProps(nextProps) {
if (this.props.postMessageData !== nextProps.postMessageData) {
// send a message if postMessageData changed
this.sendMessage(nextProps.postMessageData);
}
}
onReceiveMessage(event) {
const { handleReceiveMessage } = this.props;
if (handleReceiveMessage) {
handleReceiveMessage(event);
}
}
onLoad() {
const { handleReady } = this.props;
if (handleReady) {
handleReady();
}
// TODO: Look into doing a syn-ack TCP-like handshake
// to make sure iFrame is ready to REALLY accept messages, not just loaded.
// send intial props when iframe loads
this.sendMessage(this.props.postMessageData);
}
serializePostMessageData(data) {
// Rely on the browser's built-in structured clone algorithm for serialization of the
// message as described in
// https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
if (!this.props.serializeMessage) {
return data;
}
// To be on the safe side we can also ignore the browser's built-in serialization feature
// and serialize the data manually.
if (typeof data === "object") {
return JSON.stringify(data);
} else if (typeof data === "string") {
return data;
} else {
return `${data}`;
}
}
sendMessage(postMessageData) {
// Using postMessage data from props will result in a subtle but deadly bug,
// where old data from props is being sent instead of new postMessageData.
// This is because data sent from componentWillReceiveProps is not yet in props but only in nextProps.
const { targetOrigin } = this.props;
const serializedData = this.serializePostMessageData(postMessageData);
this._frame.contentWindow.postMessage(serializedData, targetOrigin);
}
render() {
const { attributes } = this.props;
// define some sensible defaults for our iframe attributes
const defaultAttributes = {
allowFullScreen: false,
frameBorder: 0
};
// then merge in the user's attributes with our defaults
const mergedAttributes = Object.assign(
{},
defaultAttributes,
attributes
);
return (
<iframe
ref={el => {
this._frame = el;
}}
{...mergedAttributes}
/>
);
}
}
IframeComm.defaultProps = {
serializeMessage: true,
targetOrigin: "*",
postMessageData: ""
};
const atLeastOne = atLeastOneRequired({
name: PropTypes.string,
src: PropTypes.string
})
IframeComm.propTypes = {
/*
Iframe Attributes
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#Attributes
React Supported Attributes
https://facebook.github.io/react/docs/dom-elements.html#all-supported-html-attributes
Note: attributes are camelCase, not all lowercase as usually defined.
*/
attributes: PropTypes.shape({
allowFullScreen: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool
]),
frameBorder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: atLeastOne,
scrolling: PropTypes.string,
// https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
sandbox: PropTypes.string,
srcDoc: PropTypes.string,
src: atLeastOne,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
}),
// Callback function called when iFrame sends the parent window a message.
handleReceiveMessage: PropTypes.func,
/*
Callback function called when iframe loads.
We're simply listening to the iframe's `window.onload`.
To ensure communication code in your iframe is totally loaded,
you can implement a syn-ack TCP-like handshake using `postMessageData` and `handleReceiveMessage`.
*/
handleReady: PropTypes.func,
/*
You can pass it anything you want, we'll serialize to a string
preferablly use a simple string message or an object.
If you use an object, you need to follow the same naming convention
in the iframe so you can parse it accordingly.
*/
postMessageData: PropTypes.any.isRequired,
/*
Enable use of the browser's built-in structured clone algorithm for serialization
by settings this to `false`.
Default is `true`, using our built in logic for serializing everything to a string.
*/
serializeMessage: PropTypes.bool,
/*
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.
*/
targetOrigin: PropTypes.string
};
export default IframeComm;