-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPhantasm.js
301 lines (249 loc) · 11.2 KB
/
Phantasm.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/**
* Module dependencies.
*/
var phantom = require('phantom');
var express = require('express')
, settings = require('./settings')
, common = require('./common')
, http = require('http')
, path = require('path')
, flow = require('flow')
, ga = require('nodealytics')
, shortId = require('shortid');
var app = express();
var routes = [];
// all environments
app.set('ipaddr', settings.application.ip);
app.set('port', process.env.PORT || settings.application.port);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('your secret here'));
app.use(app.router);
app.use(require('less-middleware')({ src: __dirname + '/public' }));
app.use(express.static(path.join(__dirname, 'public')));
app.use("/output", express.static(path.join(__dirname, 'output')));
//Google Analytics - if settings are blank, then won't initialize
if(settings.ga && settings.ga.key && settings.ga.domain) ga.initialize(settings.ga.key, settings.ga.domain, function () {});
var exportImage = flow.define(
function (req, res) {
this.req = req;
this.res = res;
this.args = {};
//Grab POST or QueryString args depending on type
if (this.req.method.toLowerCase() == "post") {
//If a post, then arguments will be members of the this.req.body property
this.args = this.req.body;
}
else if (this.req.method.toLowerCase() == "get") {
//If request is a get, then args will be members of the this.req.query property
this.args = this.req.query;
}
if (JSON.stringify(this.args) != '{}') {
//check for required parameters
if(!this.args.url){
this.args.errorMessage = "Need to supply a url.";
//Abort
common.respond(this.res, this.args);
return;
}
//Google Analytics
//sGATrackEvent("Print", "URL", this.args.url); //Analytics
//Which view to render in jade
this.args.view = "print";
//Set defaults for parameters.
this.args.format = this.args.format || "html";
this.args.imageformat = this.args.imageformat || "png";
this.args.viewportheight = this.args.viewportheight || 800;
this.args.viewportwidth = this.args.viewportwidth || 1200;
this.args.delay = this.args.delay || 1000; //ms - the delay after waitFor request watcher has finished, but before the screen is captured
this.args.predelay = this.args.predelay || 1000; //ms - the delay before the waitFor request watcher is started.
//DO IT
common.log("Creating Phantom Instance...");
phantom.create(this, {parameters:{'ignore-ssl-errors':'yes'}}); //flow to next function
}
else {
this.args.view = "print";
//Render Query Form without any results.
common.respond(this.res, this.args);
}
},
function (ph) {
//coming from phantom.create
common.log("Creating Page Object...");
this.ph = ph;
return ph.createPage(this); //flow to next function
},
function (page) {
//set size
this.page = page;
common.log("Setting Page size...");
this.page.set('viewportSize', { width: this.args.viewportwidth, height: this.args.viewportheight }, this); //flow to next function
},
function (err) {
this.resources = [];//Set up an array to hold all outgoing requests
var resources = this.resources;
//Setup Resource Listeners
this.page.onResourceRequested = function (request) {
resources[request.id] = request.stage;
common.log("outgoing request - " + request.url);
};
//When a resource has been recieved, remove it from the list
this.page.onResourceReceived = function (request) {
resources[request.id] = request.stage;
common.log("outgoing response recieved for " + request.url);
};
common.log("Opening Page...");
return this.page.open(this.args.url, this);
},
function (status) {
common.log("Opened Page...");
//Let page render before continuing
this.page.onConsoleMessage = function (msg) {
console.log(msg);
};
return this.page.evaluate(function (args) {
//console.log("Codeblock: " + args.codeblock);
console.log("Evaluating js...");
if (args.selector) console.log("selector: " + args.selector);
// console.log("args: " + args);
var result = {};
//Execute any pre-rendering javascript here
if (args.codeblock) {
console.log("About to execute pre-render logic.");
var preFunk;
try {
preFunk = (new Function("return function() {" + args.codeblock + "};"))();
preFunk();
} catch (e) {
console.log("error executing code block: " + e.message);
}
console.log("Executed pre-render logic.");
}
if (args.selector) {
console.log("Getting clip extent");
var clipRect = document.querySelector(args.selector).getBoundingClientRect();
console.log(clipRect);
result.clipRect = clipRect;
return result;
}
else {
return result;
}
}, this, { codeblock: this.args.codeblock, selector: this.args.selector }); //arguments to be passed to page.evaluate
},
function (err, result) {
//Callback for page.evaluate
//this is being called too quickly. add delay
this.clipResult = result; //if user is clipping, then store the info here.
var resources = this.resources; //copy for closure
var flo = this;
setTimeout(function () {
common.log("about to synch outgoing and incoming requests.");
//Keep an eye on all of the outgoing resource requests.
//When they all are complete, then move on.
waitFor(function () {
// See if all outgoing requests have completed
for (var i = 1; i < resources.length; ++i) {
common.log(resources[i]);
if (!resources[i] || resources[i] != 'end') {
common.log("outgoing requests still pending.");
return false;
}
}
common.log("outgoing requests all complete, moving on.");
return true;
},
flo, //when done, go to next flow
50000); //The Timeout milliseconds. After this, give up and move on
}, this.args.predelay); //Built in delay to let the execution block have a chance to send out requests.
},
function (status) {
//Callback for when all initial pageload resource requests have ended.
var flo = this;
this.outputURL = this.req.protocol + "://" + this.req.get('host') + "/output/";
this.filename = 'phantomoutput' + shortId.generate() + '.' + this.args.imageformat;
setTimeout(function () {
if (this.clipResult && this.clipResult.clipRect) {
//Clip
this.page.set('clipRect', { width: this.clipResult.clipRect.width, height: this.clipResult.clipRect.height, top: this.clipResult.clipRect.top, left: this.clipResult.clipRect.left }, this);
}
else {
//Don't clip
common.log("about to render page:");
flo(); //just flow
}
}, this.args.delay); //wait before rendering.
},
function (err) {
return this.page.render('output/' + this.filename, this);
},
function () {
common.log('Page Rendered.');
this.ph.exit();
//Render
this.args.imageLink = this.filename;
this.args.featureCollection = { image: this.outputURL + this.filename };
common.respond(this.res, this.args);
}
)
//Define Paths
//Root Request
app.all('/', exportImage);
//redundant endpoint for backwards compatability
app.all('/print', exportImage);
//create server
http.createServer(app).listen(app.get('port'), app.get('ipaddr'), function () {
console.log('Express server listening on IP:' + app.get('ipaddr') + ', port ' + app.get('port'));
});
/**
* See https://github.com/ariya/phantomjs/blob/master/examples/waitfor.js
*
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()), //< defensive code
interval = setInterval(function () {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if (!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
typeof (onReady) === "string" ? eval(onReady) : onReady('timeout'); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
//ph.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof (onReady) === "string" ? eval(onReady) : onReady('success'); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 250); //< repeat check every 250ms
};
//Google Analytics function
function GATrackEvent(category, action, label) {
//Make sure settings are specified. Otherwise, don't send
if (settings.ga && settings.ga.key && settings.ga.domain) {
ga.trackEvent(category, action, label, function (err, resp) {
if (!err && resp.statusCode === 200) {
common.log('Event has been tracked with Google Analytics');
}
});
}
}