Skip to content

Commit 7d38943

Browse files
committed
Initial commit
0 parents  commit 7d38943

11 files changed

+358
-0
lines changed

.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
root = true
2+
3+
[*]
4+
indent_size = 4
5+
indent_style = tab
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.json]
12+
indent_size = 2
13+
indent_style = space
14+
15+
[*.md]
16+
trim_trailing_whitespace = false

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.jshintrc

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"bitwise": true,
3+
"browser": true,
4+
"camelcase": true,
5+
"curly": true,
6+
"eqeqeq": true,
7+
"esnext": true,
8+
"immed": true,
9+
"newcap": true,
10+
"noarg": true,
11+
"node": true,
12+
"quotmark": "single",
13+
"strict": true,
14+
"undef": true,
15+
"unused": "vars"
16+
}

.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
node_js:
3+
- '0.10'

LICENSE.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) Kevin Mårtensson
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# screenshot-stream [![Build Status](http://img.shields.io/travis/kevva/screenshot-stream.svg?style=flat)](https://travis-ci.org/kevva/screenshot-stream)
2+
3+
> Capture screenshot of a website and return it as a stream
4+
5+
## Install
6+
7+
```sh
8+
$ npm install --save screenshot-stream
9+
```
10+
11+
## Usage
12+
13+
```js
14+
var fs = require('fs');
15+
var screenshot = require('screenshot-stream');
16+
17+
var stream = screenshot('http://google.com', '1024x768', {
18+
crop: true
19+
});
20+
21+
stream.pipe(fs.createWriteStream('google.com-1024x768.png'));
22+
```
23+
24+
## License
25+
26+
MIT © [Kevin Mårtensson](https://github.com/kevva)

index.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
var base64 = require('base64-stream');
4+
var path = require('path');
5+
var parseCookie = require('parse-cookie-phantomjs');
6+
var phantomjs = require('phantomjs').path;
7+
var spawn = require('child_process').spawn;
8+
9+
/**
10+
* Screenshot stream
11+
*
12+
* @param {String} url
13+
* @param {String} size
14+
* @param {Object} opts
15+
* @api public
16+
*/
17+
18+
module.exports = function (url, size, opts) {
19+
opts = opts || {};
20+
opts.url = url;
21+
opts.width = size.split(/x/i)[0];
22+
opts.height = size.split(/x/i)[1];
23+
opts.cookies = (opts.cookies || []).map(function (cookie) {
24+
return typeof cookie === 'string' ? parseCookie(cookie) : cookie;
25+
});
26+
27+
var args = [
28+
path.join(__dirname, 'lib/index.js'),
29+
JSON.stringify(opts)
30+
];
31+
32+
var cp = spawn(phantomjs, args);
33+
var stream = cp.stdout.pipe(base64.decode());
34+
35+
process.stderr.setMaxListeners(0);
36+
37+
cp.stderr.setEncoding('utf8');
38+
cp.stderr.on('data', function (data) {
39+
if (/ phantomjs\[/.test(data)) {
40+
return;
41+
}
42+
43+
if (/^WARN: /.test(data)) {
44+
stream.emit('warn', data.replace(/^WARN: /, ''));
45+
return;
46+
}
47+
48+
stream.emit('error', new Error(data));
49+
});
50+
51+
return stream;
52+
};

lib/index.js

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* global phantom */
2+
'use strict';
3+
4+
var assign = require('object-assign');
5+
var log = console.log;
6+
var opts = JSON.parse(phantom.args[0]);
7+
var page = require('webpage').create();
8+
var system = require('system');
9+
10+
/**
11+
* Format trace
12+
*
13+
* @param {Object} trace
14+
* @api private
15+
*/
16+
17+
function format(trace) {
18+
var src = trace.file || trace.sourceURL;
19+
var fn = (trace.function ? ' in function ' + trace.function : '');
20+
return '→ ' + src + ' on line ' + trace.line + fn;
21+
}
22+
23+
/**
24+
* Make sure phantom never outputs to stdout
25+
*/
26+
27+
console.log = console.error = function () {
28+
system.stderr.writeLine([].slice.call(arguments).join(' '));
29+
};
30+
31+
/**
32+
* Add HTTP auth
33+
*/
34+
35+
if (opts.username && opts.password) {
36+
opts.customHeaders = assign({}, opts.customHeaders, {
37+
'Authorization': 'Basic ' + btoa(opts.username + ':' + opts.password)
38+
});
39+
}
40+
41+
/**
42+
* Add cookies
43+
*/
44+
45+
opts.cookies.forEach(function (cookie) {
46+
if (!phantom.addCookie(cookie)) {
47+
console.error('Couldn\'t add cookie: ' + cookie).trim();
48+
phantom.exit(1);
49+
}
50+
});
51+
52+
/**
53+
* Handle PhantomJS errors
54+
*/
55+
56+
phantom.onError = function (err, trace) {
57+
console.error([
58+
'PHANTOM ERROR: ' + err,
59+
format(trace[0])
60+
].join('\n').trim());
61+
62+
phantom.exit(1);
63+
};
64+
65+
/**
66+
* Handle page errors
67+
*/
68+
69+
page.onError = function (err, trace) {
70+
console.error([
71+
'WARN: ' + err,
72+
format(trace[0])
73+
].join('\n').trim());
74+
};
75+
76+
/**
77+
* Set viewport size
78+
*/
79+
80+
page.viewportSize = {
81+
width: opts.width,
82+
height: opts.height
83+
};
84+
85+
/**
86+
* Set custom headers
87+
*/
88+
89+
page.customHeaders = opts.customHeaders || {};
90+
91+
/**
92+
* Open page
93+
*/
94+
95+
page.open(opts.url, function (status) {
96+
if (status === 'fail') {
97+
console.error('Couldn\'t load url');
98+
phantom.exit(1);
99+
}
100+
101+
if (opts.crop) {
102+
page.clipRect = {
103+
top: 0,
104+
left: 0,
105+
width: opts.width,
106+
height: opts.height
107+
};
108+
}
109+
110+
if (opts.selector) {
111+
page.clipRect = page.evaluate(function (el) {
112+
return document
113+
.querySelector(el)
114+
.getBoundingClientRect();
115+
}, opts.selector);
116+
}
117+
118+
page.evaluate(function () {
119+
var bgColor = window
120+
.getComputedStyle(document.body)
121+
.getPropertyValue('background-color');
122+
123+
if (!bgColor || bgColor === 'rgba(0, 0, 0, 0)') {
124+
document.body.style.backgroundColor = 'white';
125+
}
126+
});
127+
128+
window.setTimeout(function () {
129+
log.call(console, page.renderBase64('png'));
130+
phantom.exit();
131+
}, opts.delay * 1000);
132+
});

package.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "screenshot-stream",
3+
"version": "1.0.0",
4+
"description": "Capture screenshot of a website and return it as a stream",
5+
"license": "MIT",
6+
"repository": "kevva/screenshot-stream",
7+
"author": {
8+
"name": "Kevin Mårtensson",
9+
"email": "kevinmartensson@gmail.com",
10+
"url": "https://github.com/kevva"
11+
},
12+
"engines": {
13+
"node": ">=0.10.0"
14+
},
15+
"scripts": {
16+
"test": "node test.js"
17+
},
18+
"files": [
19+
"index.js",
20+
"lib"
21+
],
22+
"keywords": [],
23+
"dependencies": {
24+
"base64-stream": "^0.1.2",
25+
"object-assign": "^1.0.0",
26+
"parse-cookie-phantomjs": "^1.0.0",
27+
"phantomjs": "^1.9.10"
28+
},
29+
"devDependencies": {
30+
"ava": "0.0.4",
31+
"concat-stream": "^1.4.6",
32+
"image-size": "^0.3.3"
33+
}
34+
}

test.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
var concat = require('concat-stream');
4+
var imageSize = require('image-size');
5+
var screenshot = require('./');
6+
var test = require('ava');
7+
8+
test('crop image using the `crop` option', function (t) {
9+
t.plan(2);
10+
11+
var stream = screenshot('http://yeoman.io', '1024x768', {
12+
crop: true
13+
});
14+
15+
stream.pipe(concat(function (data) {
16+
t.assert(imageSize(data).width === 1024);
17+
t.assert(imageSize(data).height === 768);
18+
}));
19+
});
20+
21+
test('capture a DOM element using the `selector` option', function (t) {
22+
t.plan(2);
23+
24+
var stream = screenshot('http://yeoman.io', '1024x768', {
25+
selector: '.page-header'
26+
});
27+
28+
stream.pipe(concat(function (data) {
29+
t.assert(imageSize(data).width === 1024);
30+
t.assert(imageSize(data).height === 80);
31+
}));
32+
});
33+
34+
test('auth using the `username` and `password` options', function (t) {
35+
t.plan(1);
36+
37+
var stream = screenshot('http://httpbin.org/basic-auth/user/passwd', '1024x768', {
38+
username: 'user',
39+
password: 'passwd'
40+
});
41+
42+
stream.on('data', function (data) {
43+
t.assert(data.length);
44+
});
45+
});
46+
47+
test('have a `delay` option', function (t) {
48+
t.plan(2);
49+
50+
var now = new Date();
51+
var stream = screenshot('http://yeoman.io', '1024x768', {
52+
delay: 2
53+
});
54+
55+
stream.on('data', function (data) {
56+
t.assert((new Date()) - now > 2000);
57+
});
58+
});

0 commit comments

Comments
 (0)