Skip to content

Commit aee2bb3

Browse files
author
Adrien Thiery
committed
Using more regexps, use library for decoding HTMLChars instead of homemade stuff, added debounce on OpenGraphAwareTextInput and added GoogleBot's User-agent to requests
1 parent 8808ac4 commit aee2bb3

File tree

9 files changed

+133
-220
lines changed

9 files changed

+133
-220
lines changed

OpenGraphAwareInput.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from 'react-native';
77
import OpenGraphDisplay from './OpenGraphDisplay';
88
import OpenGraphParser from './OpenGraphParser';
9+
import debounce from 'lodash.debounce';
910

1011
const styles = StyleSheet.create({
1112
container: {
@@ -19,10 +20,15 @@ const styles = StyleSheet.create({
1920
export default class OpenGraphAwareInput extends Component {
2021
static propTypes = {
2122
containerStyle: View.propTypes.style,
23+
debounceDelay: React.PropTypes.number,
2224
onChange: React.PropTypes.func,
2325
textInputStyle: TextInput.propTypes.style,
2426
};
2527

28+
static defaultProps = {
29+
debounceDelay: 300,
30+
};
31+
2632
constructor(props) {
2733
super(props);
2834

@@ -31,29 +37,32 @@ export default class OpenGraphAwareInput extends Component {
3137
};
3238
}
3339

34-
handleTextInputChange = (event) => {
35-
const text = event.nativeEvent.text;
40+
extractMetaAndSetState = debounce(
41+
(text) => {
42+
OpenGraphParser.extractMeta(text)
43+
.then(
44+
(data) => {
45+
const customEvent = {};
3646

37-
OpenGraphParser.extractMeta(text).then(
38-
(data) => {
39-
const customEvent = {};
47+
this.setState({ openGraphData: data || {} });
4048

41-
if (data) {
42-
this.setState({
43-
openGraphData: data,
44-
});
45-
}
46-
if (this.props.onChange) {
47-
customEvent.event = event;
48-
if (data) {
49-
customEvent.opengraphData = data;
50-
}
51-
customEvent.text = text;
49+
if (this.props.onChange) {
50+
customEvent.event = event;
51+
customEvent.opengraphData = data || {};
52+
customEvent.text = text;
5253

53-
this.props.onChange(customEvent);
54+
this.props.onChange(customEvent);
55+
}
5456
}
55-
}
56-
);
57+
);
58+
},
59+
this.props.debounceDelay
60+
);
61+
62+
handleTextInputChange = (event) => {
63+
const text = event.nativeEvent.text;
64+
65+
this.extractMetaAndSetState(text);
5766
};
5867

5968
render() {

OpenGraphDisplay.js

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import {
2323
TouchableWithoutFeedback,
2424
} from 'react-native';
2525

26-
import { unescape } from 'lodash';
27-
2826
const colors = {
2927
defaultBackgroundColor: '#EEEEEE',
3028
};
@@ -98,20 +96,8 @@ export default class OpenGraphDisplay extends Component {
9896
);
9997
};
10098

101-
unescape = (content) => {
102-
if (!content) {
103-
return '';
104-
}
105-
106-
return unescape(
107-
content.replace(/&#([0-9]{1,3});/gi, (match, numStr) => (
108-
String.fromCharCode(Number(numStr))
109-
))
110-
);
111-
}
112-
11399
render() {
114-
if (!this.props.data.url) {
100+
if (!this.props.data || !this.props.data.url) {
115101
return null;
116102
}
117103

@@ -150,23 +136,23 @@ export default class OpenGraphDisplay extends Component {
150136
this.props.titleStyle,
151137
]}
152138
>
153-
{this.unescape(this.props.data.title) || ''}
139+
{this.props.data.title || ''}
154140
</Text>
155141
<Text
156142
style={[
157143
styles.description,
158144
this.props.descriptionStyle,
159145
]}
160146
>
161-
{this.unescape(this.props.data.description) || ''}
147+
{this.props.data.description || ''}
162148
</Text>
163149
<Text
164150
style={[
165151
styles.url,
166152
this.props.urlStyle,
167153
]}
168154
>
169-
{this.unescape(this.props.data.url) ? this.unescape(this.props.data.url).toLowerCase() : ''}
155+
{this.props.data.url ? this.props.data.url.toLowerCase() : ''}
170156
</Text>
171157
</View>
172158
</View>
@@ -189,7 +175,7 @@ export default class OpenGraphDisplay extends Component {
189175
this.props.urlStyle,
190176
]}
191177
>
192-
{this.unescape(this.props.data.url) ? this.unescape(this.props.data.url).toLowerCase() : ''}
178+
{this.props.data.url ? this.props.data.url.toLowerCase() : ''}
193179
</Text>
194180
</View>
195181
</TouchableWithoutFeedback>

OpenGraphParser.js

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
1-
import decodeHTMLChars from './decodeHTMLChars';
1+
import { AllHtmlEntities } from 'html-entities';
22

3-
function parseMeta(html, url) {
4-
const metaTagRegex = /<meta[^>]*property=[ '"]*og:([^'"]*)[^>]*content=['"]([^'"]*)['"][^>]*>/gi;
5-
const meta = {
6-
url: url,
7-
};
8-
const matches = html.match(metaTagRegex);
3+
const entities = new AllHtmlEntities();
4+
5+
function parseMeta(html, url, options) {
6+
const metaTagOGRegex = /<meta[^>]*property=[ '"]*og:([^'"]*)[^>]*content=['"]([^'"]*)['"][^>]*>/gi;
7+
const metaPropertyRegex = /<meta[^>]*property=[ '"]*og:([^'"]*)[^>]*>/i;
8+
const metaContentRegex = /<meta[^>]*content=[ '"]([^'"]*)[^>]*>/i;
9+
const meta = { url };
10+
11+
const matches = html.match(metaTagOGRegex);
912

1013
if (matches) {
1114
for (let i = matches.length; i--;) {
12-
let metaName = matches[i].split('og:');
15+
let metaName;
16+
let metaValue;
1317

14-
if (metaName.length > 1) {
15-
metaName = metaName[1].split('"');
16-
} else {
17-
break;
18-
}
18+
try {
19+
const propertyMatch = metaPropertyRegex.exec(matches[i]);
20+
const contentMatch = metaContentRegex.exec(matches[i]);
21+
metaName = propertyMatch[1].trim();
22+
metaValue = contentMatch[1].trim();
1923

20-
if (metaName.length > 1) {
21-
metaName = metaName[0];
22-
} else {
23-
metaName = metaName[0].split("'");
24-
25-
if (metaName.length > 1) {
26-
metaName = metaName[0];
27-
} else {
28-
break;
24+
if (!metaName || !metaValue) {
25+
continue;
26+
}
27+
} catch (error) {
28+
if (__DEV__) {
29+
console.log('Error on ', matches[i]);
30+
console.log('propertyMatch', propertyMatch);
31+
console.log('contentMatch', contentMatch);
32+
console.log(error);
2933
}
30-
}
3134

32-
let metaValue = matches[i].split('content=');
35+
continue;
36+
}
3337

34-
if (metaValue.length > 1) {
35-
metaValue = metaValue[1].split(metaValue[1].trim()[0])[1];
38+
if (metaValue.length > 0) {
3639
if (metaValue[0] === '/') {
3740
if (url[url.length - 1] === '/') {
3841
metaValue = url + metaValue.substring(1);
@@ -41,10 +44,20 @@ function parseMeta(html, url) {
4144
}
4245
}
4346
} else {
44-
break;
47+
continue;
4548
}
4649

47-
meta[metaName] = decodeHTMLChars(metaValue);
50+
meta[metaName] = entities.decode(metaValue);
51+
}
52+
53+
if (options.fallbackOnHTMLTags) {
54+
try {
55+
fallbackOnHTMLTags(html, meta);
56+
} catch (error) {
57+
if (__DEV__) {
58+
console.log('Error in fallback', error);
59+
}
60+
}
4861
}
4962

5063
return meta;
@@ -53,11 +66,46 @@ function parseMeta(html, url) {
5366
}
5467
}
5568

69+
function fallbackOnHTMLTags(htmlContent, metaDataObject) {
70+
if (!metaDataObject.description) {
71+
const descriptionMetaTagRegex = /<meta[^>]*name=[ '"]*description[^'"]* [^>]*content=['"]([^'"]*)['"][^>]*>/gi;
72+
const descriptionMatches = htmlContent.match(descriptionMetaTagRegex);
73+
74+
if (descriptionMatches && descriptionMatches.length > 0) {
75+
const descriptionContentRegex = /<meta[^>]*name=[ '"]*description[^'"]* [^>]*content=['"]([^'"]*)['"][^>]*>/i;
76+
const descriptionMatch = descriptionContentRegex.exec(descriptionMatches[0]);
77+
78+
if (descriptionMatch) {
79+
metaDataObject.description = descriptionMatch[1].trim();
80+
}
81+
}
82+
}
83+
84+
if (!metaDataObject.title) {
85+
const titleMetaTagRegex = /<title>([^<]*)<\/title>/gi;
86+
const titleMatches = htmlContent.match(titleMetaTagRegex);
87+
88+
if (titleMatches && titleMatches.length > 0) {
89+
const titleContentRegex = /<title>([^<]*)<\/title>/i;
90+
const titleMatch = titleContentRegex.exec(titleMatches[0]);
91+
92+
if (titleMatch) {
93+
metaDataObject.title = titleMatch[1].trim();
94+
}
95+
}
96+
}
97+
}
98+
5699
async function fetchHtml(urlToFetch) {
57100
let result;
58101

59102
try {
60-
result = await fetch(urlToFetch);
103+
result = await fetch(urlToFetch, {
104+
method: 'GET',
105+
headers: {
106+
"user-agent": 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
107+
},
108+
});
61109

62110
if (result.status >= 400) {
63111
throw result;
@@ -66,14 +114,20 @@ async function fetchHtml(urlToFetch) {
66114
return result.text()
67115
.then((resultParsed) => (resultParsed));
68116
} catch (responseOrError) {
69-
if (responseOrError.message) {
70-
console.log(responseOrError);
117+
if (responseOrError.message && __DEV__) {
118+
if (responseOrError.message === 'Network request failed') {
119+
console.log(urlToFetch, 'could not be fetched');
120+
} else {
121+
console.log(responseOrError);
122+
}
71123
return null;
72124
}
73125

74126
return responseOrError.text()
75127
.then((error) => {
76-
console.log('An error has occured while fetching url content', error);
128+
if (__DEV__) {
129+
console.log('An error has occured while fetching url content', error);
130+
}
77131
return null;
78132
});
79133
}
@@ -92,14 +146,16 @@ function getUrls(contentToMatch) {
92146
urlsToReturn.push(`http://${url}`);
93147
}
94148
});
95-
96-
return urlsToReturn;
97149
} else {
98-
throw new Error('Could not find an html link');
150+
if (__DEV__) {
151+
console.log('Could not find an html link');
152+
}
99153
}
154+
155+
return urlsToReturn;
100156
}
101157

102-
async function extractMeta(textContent = '') {
158+
async function extractMeta(textContent = '', options = { fallbackOnHTMLTags: true }) {
103159
try {
104160
const urls = getUrls(textContent);
105161

@@ -110,7 +166,7 @@ async function extractMeta(textContent = '') {
110166
metaData = await fetchHtml(urls[i])
111167
.then(
112168
(html) => ({
113-
...parseMeta(html, urls[i]),
169+
...html ? parseMeta(html, urls[i], options) : {},
114170
url: urls[i],
115171
})
116172
);

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# react-native-opengraph-kit
2-
A set of components and utils useful to extract opengraph data directly from your react-native app
2+
A set of components and utils useful to extract opengraph data directly from your react-native app, with almost no dependency.
33

44
For react-native v0.26+
55

0 commit comments

Comments
 (0)