ethstats-server/web-app/.meteor/local/build/programs/server/packages/webapp.js
2015-08-14 19:22:53 +02:00

832 lines
72 KiB
JavaScript

(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var Log = Package.logging.Log;
var _ = Package.underscore._;
var RoutePolicy = Package.routepolicy.RoutePolicy;
var Boilerplate = Package['boilerplate-generator'].Boilerplate;
var WebAppHashing = Package['webapp-hashing'].WebAppHashing;
/* Package-scope variables */
var WebApp, main, WebAppInternals;
(function () {
//////////////////////////////////////////////////////////////////////////////////////
// //
// packages/webapp/webapp_server.js //
// //
//////////////////////////////////////////////////////////////////////////////////////
//
////////// Requires ////////// // 1
// 2
var fs = Npm.require("fs"); // 3
var http = Npm.require("http"); // 4
var os = Npm.require("os"); // 5
var path = Npm.require("path"); // 6
var url = Npm.require("url"); // 7
var crypto = Npm.require("crypto"); // 8
// 9
var connect = Npm.require('connect'); // 10
var useragent = Npm.require('useragent'); // 11
var send = Npm.require('send'); // 12
// 13
var Future = Npm.require('fibers/future'); // 14
var Fiber = Npm.require('fibers'); // 15
// 16
var SHORT_SOCKET_TIMEOUT = 5*1000; // 17
var LONG_SOCKET_TIMEOUT = 120*1000; // 18
// 19
WebApp = {}; // 20
WebAppInternals = {}; // 21
// 22
WebAppInternals.NpmModules = { // 23
connect: { // 24
version: Npm.require('connect/package.json').version, // 25
module: connect // 26
} // 27
}; // 28
// 29
WebApp.defaultArch = 'web.browser'; // 30
// 31
// XXX maps archs to manifests // 32
WebApp.clientPrograms = {}; // 33
// 34
// XXX maps archs to program path on filesystem // 35
var archPath = {}; // 36
// 37
var bundledJsCssPrefix; // 38
// 39
var sha1 = function (contents) { // 40
var hash = crypto.createHash('sha1'); // 41
hash.update(contents); // 42
return hash.digest('hex'); // 43
}; // 44
// 45
var readUtf8FileSync = function (filename) { // 46
return Meteor.wrapAsync(fs.readFile)(filename, 'utf8'); // 47
}; // 48
// 49
// #BrowserIdentification // 50
// // 51
// We have multiple places that want to identify the browser: the // 52
// unsupported browser page, the appcache package, and, eventually // 53
// delivering browser polyfills only as needed. // 54
// // 55
// To avoid detecting the browser in multiple places ad-hoc, we create a // 56
// Meteor "browser" object. It uses but does not expose the npm // 57
// useragent module (we could choose a different mechanism to identify // 58
// the browser in the future if we wanted to). The browser object // 59
// contains // 60
// // 61
// * `name`: the name of the browser in camel case // 62
// * `major`, `minor`, `patch`: integers describing the browser version // 63
// // 64
// Also here is an early version of a Meteor `request` object, intended // 65
// to be a high-level description of the request without exposing // 66
// details of connect's low-level `req`. Currently it contains: // 67
// // 68
// * `browser`: browser identification object described above // 69
// * `url`: parsed url, including parsed query params // 70
// // 71
// As a temporary hack there is a `categorizeRequest` function on WebApp which // 72
// converts a connect `req` to a Meteor `request`. This can go away once smart // 73
// packages such as appcache are being passed a `request` object directly when // 74
// they serve content. // 75
// // 76
// This allows `request` to be used uniformly: it is passed to the html // 77
// attributes hook, and the appcache package can use it when deciding // 78
// whether to generate a 404 for the manifest. // 79
// // 80
// Real routing / server side rendering will probably refactor this // 81
// heavily. // 82
// 83
// 84
// e.g. "Mobile Safari" => "mobileSafari" // 85
var camelCase = function (name) { // 86
var parts = name.split(' '); // 87
parts[0] = parts[0].toLowerCase(); // 88
for (var i = 1; i < parts.length; ++i) { // 89
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1); // 90
} // 91
return parts.join(''); // 92
}; // 93
// 94
var identifyBrowser = function (userAgentString) { // 95
var userAgent = useragent.lookup(userAgentString); // 96
return { // 97
name: camelCase(userAgent.family), // 98
major: +userAgent.major, // 99
minor: +userAgent.minor, // 100
patch: +userAgent.patch // 101
}; // 102
}; // 103
// 104
// XXX Refactor as part of implementing real routing. // 105
WebAppInternals.identifyBrowser = identifyBrowser; // 106
// 107
WebApp.categorizeRequest = function (req) { // 108
return { // 109
browser: identifyBrowser(req.headers['user-agent']), // 110
url: url.parse(req.url, true) // 111
}; // 112
}; // 113
// 114
// HTML attribute hooks: functions to be called to determine any attributes to // 115
// be added to the '<html>' tag. Each function is passed a 'request' object (see // 116
// #BrowserIdentification) and should return null or object. // 117
var htmlAttributeHooks = []; // 118
var getHtmlAttributes = function (request) { // 119
var combinedAttributes = {}; // 120
_.each(htmlAttributeHooks || [], function (hook) { // 121
var attributes = hook(request); // 122
if (attributes === null) // 123
return; // 124
if (typeof attributes !== 'object') // 125
throw Error("HTML attribute hook must return null or object"); // 126
_.extend(combinedAttributes, attributes); // 127
}); // 128
return combinedAttributes; // 129
}; // 130
WebApp.addHtmlAttributeHook = function (hook) { // 131
htmlAttributeHooks.push(hook); // 132
}; // 133
// 134
// Serve app HTML for this URL? // 135
var appUrl = function (url) { // 136
if (url === '/favicon.ico' || url === '/robots.txt') // 137
return false; // 138
// 139
// NOTE: app.manifest is not a web standard like favicon.ico and // 140
// robots.txt. It is a file name we have chosen to use for HTML5 // 141
// appcache URLs. It is included here to prevent using an appcache // 142
// then removing it from poisoning an app permanently. Eventually, // 143
// once we have server side routing, this won't be needed as // 144
// unknown URLs with return a 404 automatically. // 145
if (url === '/app.manifest') // 146
return false; // 147
// 148
// Avoid serving app HTML for declared routes such as /sockjs/. // 149
if (RoutePolicy.classify(url)) // 150
return false; // 151
// 152
// we currently return app HTML on all URLs by default // 153
return true; // 154
}; // 155
// 156
// 157
// We need to calculate the client hash after all packages have loaded // 158
// to give them a chance to populate __meteor_runtime_config__. // 159
// // 160
// Calculating the hash during startup means that packages can only // 161
// populate __meteor_runtime_config__ during load, not during startup. // 162
// // 163
// Calculating instead it at the beginning of main after all startup // 164
// hooks had run would allow packages to also populate // 165
// __meteor_runtime_config__ during startup, but that's too late for // 166
// autoupdate because it needs to have the client hash at startup to // 167
// insert the auto update version itself into // 168
// __meteor_runtime_config__ to get it to the client. // 169
// // 170
// An alternative would be to give autoupdate a "post-start, // 171
// pre-listen" hook to allow it to insert the auto update version at // 172
// the right moment. // 173
// 174
Meteor.startup(function () { // 175
var calculateClientHash = WebAppHashing.calculateClientHash; // 176
WebApp.clientHash = function (archName) { // 177
archName = archName || WebApp.defaultArch; // 178
return calculateClientHash(WebApp.clientPrograms[archName].manifest); // 179
}; // 180
// 181
WebApp.calculateClientHashRefreshable = function (archName) { // 182
archName = archName || WebApp.defaultArch; // 183
return calculateClientHash(WebApp.clientPrograms[archName].manifest, // 184
function (name) { // 185
return name === "css"; // 186
}); // 187
}; // 188
WebApp.calculateClientHashNonRefreshable = function (archName) { // 189
archName = archName || WebApp.defaultArch; // 190
return calculateClientHash(WebApp.clientPrograms[archName].manifest, // 191
function (name) { // 192
return name !== "css"; // 193
}); // 194
}; // 195
WebApp.calculateClientHashCordova = function () { // 196
var archName = 'web.cordova'; // 197
if (! WebApp.clientPrograms[archName]) // 198
return 'none'; // 199
// 200
return calculateClientHash( // 201
WebApp.clientPrograms[archName].manifest, null, _.pick( // 202
__meteor_runtime_config__, 'PUBLIC_SETTINGS')); // 203
}; // 204
}); // 205
// 206
// 207
// 208
// When we have a request pending, we want the socket timeout to be long, to // 209
// give ourselves a while to serve it, and to allow sockjs long polls to // 210
// complete. On the other hand, we want to close idle sockets relatively // 211
// quickly, so that we can shut down relatively promptly but cleanly, without // 212
// cutting off anyone's response. // 213
WebApp._timeoutAdjustmentRequestCallback = function (req, res) { // 214
// this is really just req.socket.setTimeout(LONG_SOCKET_TIMEOUT); // 215
req.setTimeout(LONG_SOCKET_TIMEOUT); // 216
// Insert our new finish listener to run BEFORE the existing one which removes // 217
// the response from the socket. // 218
var finishListeners = res.listeners('finish'); // 219
// XXX Apparently in Node 0.12 this event is now called 'prefinish'. // 220
// https://github.com/joyent/node/commit/7c9b6070 // 221
res.removeAllListeners('finish'); // 222
res.on('finish', function () { // 223
res.setTimeout(SHORT_SOCKET_TIMEOUT); // 224
}); // 225
_.each(finishListeners, function (l) { res.on('finish', l); }); // 226
}; // 227
// 228
// 229
// Will be updated by main before we listen. // 230
// Map from client arch to boilerplate object. // 231
// Boilerplate object has: // 232
// - func: XXX // 233
// - baseData: XXX // 234
var boilerplateByArch = {}; // 235
// 236
// Given a request (as returned from `categorizeRequest`), return the // 237
// boilerplate HTML to serve for that request. Memoizes on HTML // 238
// attributes (used by, eg, appcache) and whether inline scripts are // 239
// currently allowed. // 240
// XXX so far this function is always called with arch === 'web.browser' // 241
var memoizedBoilerplate = {}; // 242
var getBoilerplate = function (request, arch) { // 243
// 244
var htmlAttributes = getHtmlAttributes(request); // 245
// 246
// The only thing that changes from request to request (for now) are // 247
// the HTML attributes (used by, eg, appcache) and whether inline // 248
// scripts are allowed, so we can memoize based on that. // 249
var memHash = JSON.stringify({ // 250
inlineScriptsAllowed: inlineScriptsAllowed, // 251
htmlAttributes: htmlAttributes, // 252
arch: arch // 253
}); // 254
// 255
if (! memoizedBoilerplate[memHash]) { // 256
memoizedBoilerplate[memHash] = boilerplateByArch[arch].toHTML({ // 257
htmlAttributes: htmlAttributes // 258
}); // 259
} // 260
return memoizedBoilerplate[memHash]; // 261
}; // 262
// 263
WebAppInternals.generateBoilerplateInstance = function (arch, // 264
manifest, // 265
additionalOptions) { // 266
additionalOptions = additionalOptions || {}; // 267
// 268
var runtimeConfig = _.extend( // 269
_.clone(__meteor_runtime_config__), // 270
additionalOptions.runtimeConfigOverrides || {} // 271
); // 272
// 273
var jsCssPrefix; // 274
if (arch === 'web.cordova') { // 275
// in cordova we serve assets up directly from disk so it doesn't make // 276
// sense to use the prefix (ordinarily something like a CDN) and go out // 277
// to the internet for those files. // 278
jsCssPrefix = ''; // 279
} else { // 280
jsCssPrefix = bundledJsCssPrefix || // 281
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; // 282
} // 283
// 284
return new Boilerplate(arch, manifest, // 285
_.extend({ // 286
pathMapper: function (itemPath) { // 287
return path.join(archPath[arch], itemPath); }, // 288
baseDataExtension: { // 289
additionalStaticJs: _.map( // 290
additionalStaticJs || [], // 291
function (contents, pathname) { // 292
return { // 293
pathname: pathname, // 294
contents: contents // 295
}; // 296
} // 297
), // 298
// Convert to a JSON string, then get rid of most weird characters, then // 299
// wrap in double quotes. (The outermost JSON.stringify really ought to // 300
// just be "wrap in double quotes" but we use it to be safe.) This might // 301
// end up inside a <script> tag so we need to be careful to not include // 302
// "</script>", but normal {{spacebars}} escaping escapes too much! See // 303
// https://github.com/meteor/meteor/issues/3730 // 304
meteorRuntimeConfig: JSON.stringify( // 305
encodeURIComponent(JSON.stringify(runtimeConfig))), // 306
rootUrlPathPrefix: __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '', // 307
bundledJsCssPrefix: jsCssPrefix, // 308
inlineScriptsAllowed: WebAppInternals.inlineScriptsAllowed(), // 309
inline: additionalOptions.inline // 310
} // 311
}, additionalOptions) // 312
); // 313
}; // 314
// 315
// A mapping from url path to "info". Where "info" has the following fields: // 316
// - type: the type of file to be served // 317
// - cacheable: optionally, whether the file should be cached or not // 318
// - sourceMapUrl: optionally, the url of the source map // 319
// // 320
// Info also contains one of the following: // 321
// - content: the stringified content that should be served at this path // 322
// - absolutePath: the absolute path on disk to the file // 323
// 324
var staticFiles; // 325
// 326
// Serve static files from the manifest or added with // 327
// `addStaticJs`. Exported for tests. // 328
WebAppInternals.staticFilesMiddleware = function (staticFiles, req, res, next) { // 329
if ('GET' != req.method && 'HEAD' != req.method) { // 330
next(); // 331
return; // 332
} // 333
var pathname = connect.utils.parseUrl(req).pathname; // 334
try { // 335
pathname = decodeURIComponent(pathname); // 336
} catch (e) { // 337
next(); // 338
return; // 339
} // 340
// 341
var serveStaticJs = function (s) { // 342
res.writeHead(200, { // 343
'Content-type': 'application/javascript; charset=UTF-8' // 344
}); // 345
res.write(s); // 346
res.end(); // 347
}; // 348
// 349
if (pathname === "/meteor_runtime_config.js" && // 350
! WebAppInternals.inlineScriptsAllowed()) { // 351
serveStaticJs("__meteor_runtime_config__ = " + // 352
JSON.stringify(__meteor_runtime_config__) + ";"); // 353
return; // 354
} else if (_.has(additionalStaticJs, pathname) && // 355
! WebAppInternals.inlineScriptsAllowed()) { // 356
serveStaticJs(additionalStaticJs[pathname]); // 357
return; // 358
} // 359
// 360
if (!_.has(staticFiles, pathname)) { // 361
next(); // 362
return; // 363
} // 364
// 365
// We don't need to call pause because, unlike 'static', once we call into // 366
// 'send' and yield to the event loop, we never call another handler with // 367
// 'next'. // 368
// 369
var info = staticFiles[pathname]; // 370
// 371
// Cacheable files are files that should never change. Typically // 372
// named by their hash (eg meteor bundled js and css files). // 373
// We cache them ~forever (1yr). // 374
// // 375
// We cache non-cacheable files anyway. This isn't really correct, as users // 376
// can change the files and changes won't propagate immediately. However, if // 377
// we don't cache them, browsers will 'flicker' when rerendering // 378
// images. Eventually we will probably want to rewrite URLs of static assets // 379
// to include a query parameter to bust caches. That way we can both get // 380
// good caching behavior and allow users to change assets without delay. // 381
// https://github.com/meteor/meteor/issues/773 // 382
var maxAge = info.cacheable // 383
? 1000 * 60 * 60 * 24 * 365 // 384
: 1000 * 60 * 60 * 24; // 385
// 386
// Set the X-SourceMap header, which current Chrome, FireFox, and Safari // 387
// understand. (The SourceMap header is slightly more spec-correct but FF // 388
// doesn't understand it.) // 389
// // 390
// You may also need to enable source maps in Chrome: open dev tools, click // 391
// the gear in the bottom right corner, and select "enable source maps". // 392
if (info.sourceMapUrl) { // 393
res.setHeader('X-SourceMap', // 394
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX + // 395
info.sourceMapUrl); // 396
} // 397
// 398
if (info.type === "js") { // 399
res.setHeader("Content-Type", "application/javascript; charset=UTF-8"); // 400
} else if (info.type === "css") { // 401
res.setHeader("Content-Type", "text/css; charset=UTF-8"); // 402
} else if (info.type === "json") { // 403
res.setHeader("Content-Type", "application/json; charset=UTF-8"); // 404
// XXX if it is a manifest we are serving, set additional headers // 405
if (/\/manifest.json$/.test(pathname)) { // 406
res.setHeader("Access-Control-Allow-Origin", "*"); // 407
} // 408
} // 409
// 410
if (info.content) { // 411
res.write(info.content); // 412
res.end(); // 413
} else { // 414
send(req, info.absolutePath) // 415
.maxage(maxAge) // 416
.hidden(true) // if we specified a dotfile in the manifest, serve it // 417
.on('error', function (err) { // 418
Log.error("Error serving static file " + err); // 419
res.writeHead(500); // 420
res.end(); // 421
}) // 422
.on('directory', function () { // 423
Log.error("Unexpected directory " + info.absolutePath); // 424
res.writeHead(500); // 425
res.end(); // 426
}) // 427
.pipe(res); // 428
} // 429
}; // 430
// 431
var getUrlPrefixForArch = function (arch) { // 432
// XXX we rely on the fact that arch names don't contain slashes // 433
// in that case we would need to uri escape it // 434
// 435
// We add '__' to the beginning of non-standard archs to "scope" the url // 436
// to Meteor internals. // 437
return arch === WebApp.defaultArch ? // 438
'' : '/' + '__' + arch.replace(/^web\./, ''); // 439
}; // 440
// 441
var runWebAppServer = function () { // 442
var shuttingDown = false; // 443
var syncQueue = new Meteor._SynchronousQueue(); // 444
// 445
var getItemPathname = function (itemUrl) { // 446
return decodeURIComponent(url.parse(itemUrl).pathname); // 447
}; // 448
// 449
WebAppInternals.reloadClientPrograms = function () { // 450
syncQueue.runTask(function() { // 451
staticFiles = {}; // 452
var generateClientProgram = function (clientPath, arch) { // 453
// read the control for the client we'll be serving up // 454
var clientJsonPath = path.join(__meteor_bootstrap__.serverDir, // 455
clientPath); // 456
var clientDir = path.dirname(clientJsonPath); // 457
var clientJson = JSON.parse(readUtf8FileSync(clientJsonPath)); // 458
if (clientJson.format !== "web-program-pre1") // 459
throw new Error("Unsupported format for client assets: " + // 460
JSON.stringify(clientJson.format)); // 461
// 462
if (! clientJsonPath || ! clientDir || ! clientJson) // 463
throw new Error("Client config file not parsed."); // 464
// 465
var urlPrefix = getUrlPrefixForArch(arch); // 466
// 467
var manifest = clientJson.manifest; // 468
_.each(manifest, function (item) { // 469
if (item.url && item.where === "client") { // 470
staticFiles[urlPrefix + getItemPathname(item.url)] = { // 471
absolutePath: path.join(clientDir, item.path), // 472
cacheable: item.cacheable, // 473
// Link from source to its map // 474
sourceMapUrl: item.sourceMapUrl, // 475
type: item.type // 476
}; // 477
// 478
if (item.sourceMap) { // 479
// Serve the source map too, under the specified URL. We assume all // 480
// source maps are cacheable. // 481
staticFiles[urlPrefix + getItemPathname(item.sourceMapUrl)] = { // 482
absolutePath: path.join(clientDir, item.sourceMap), // 483
cacheable: true // 484
}; // 485
} // 486
} // 487
}); // 488
// 489
var program = { // 490
manifest: manifest, // 491
version: WebAppHashing.calculateClientHash(manifest, null, _.pick( // 492
__meteor_runtime_config__, 'PUBLIC_SETTINGS')), // 493
PUBLIC_SETTINGS: __meteor_runtime_config__.PUBLIC_SETTINGS // 494
}; // 495
// 496
WebApp.clientPrograms[arch] = program; // 497
// 498
// Serve the program as a string at /foo/<arch>/manifest.json // 499
// XXX change manifest.json -> program.json // 500
staticFiles[path.join(urlPrefix, 'manifest.json')] = { // 501
content: JSON.stringify(program), // 502
cacheable: true, // 503
type: "json" // 504
}; // 505
}; // 506
// 507
try { // 508
var clientPaths = __meteor_bootstrap__.configJson.clientPaths; // 509
_.each(clientPaths, function (clientPath, arch) { // 510
archPath[arch] = path.dirname(clientPath); // 511
generateClientProgram(clientPath, arch); // 512
}); // 513
// 514
// Exported for tests. // 515
WebAppInternals.staticFiles = staticFiles; // 516
} catch (e) { // 517
Log.error("Error reloading the client program: " + e.stack); // 518
process.exit(1); // 519
} // 520
}); // 521
}; // 522
// 523
WebAppInternals.generateBoilerplate = function () { // 524
// This boilerplate will be served to the mobile devices when used with // 525
// Meteor/Cordova for the Hot-Code Push and since the file will be served by // 526
// the device's server, it is important to set the DDP url to the actual // 527
// Meteor server accepting DDP connections and not the device's file server. // 528
var defaultOptionsForArch = { // 529
'web.cordova': { // 530
runtimeConfigOverrides: { // 531
// XXX We use absoluteUrl() here so that we serve https:// // 532
// URLs to cordova clients if force-ssl is in use. If we were // 533
// to use __meteor_runtime_config__.ROOT_URL instead of // 534
// absoluteUrl(), then Cordova clients would immediately get a // 535
// HCP setting their DDP_DEFAULT_CONNECTION_URL to // 536
// http://example.meteor.com. This breaks the app, because // 537
// force-ssl doesn't serve CORS headers on 302 // 538
// redirects. (Plus it's undesirable to have clients // 539
// connecting to http://example.meteor.com when force-ssl is // 540
// in use.) // 541
DDP_DEFAULT_CONNECTION_URL: process.env.MOBILE_DDP_URL || // 542
Meteor.absoluteUrl(), // 543
ROOT_URL: process.env.MOBILE_ROOT_URL || // 544
Meteor.absoluteUrl() // 545
} // 546
} // 547
}; // 548
// 549
syncQueue.runTask(function() { // 550
_.each(WebApp.clientPrograms, function (program, archName) { // 551
boilerplateByArch[archName] = // 552
WebAppInternals.generateBoilerplateInstance( // 553
archName, program.manifest, // 554
defaultOptionsForArch[archName]); // 555
}); // 556
// 557
// Clear the memoized boilerplate cache. // 558
memoizedBoilerplate = {}; // 559
// 560
// Configure CSS injection for the default arch // 561
// XXX implement the CSS injection for all archs? // 562
WebAppInternals.refreshableAssets = { // 563
allCss: boilerplateByArch[WebApp.defaultArch].baseData.css // 564
}; // 565
}); // 566
}; // 567
// 568
WebAppInternals.reloadClientPrograms(); // 569
// 570
// webserver // 571
var app = connect(); // 572
// 573
// Auto-compress any json, javascript, or text. // 574
app.use(connect.compress()); // 575
// 576
// Packages and apps can add handlers that run before any other Meteor // 577
// handlers via WebApp.rawConnectHandlers. // 578
var rawConnectHandlers = connect(); // 579
app.use(rawConnectHandlers); // 580
// 581
// We're not a proxy; reject (without crashing) attempts to treat us like // 582
// one. (See #1212.) // 583
app.use(function(req, res, next) { // 584
if (RoutePolicy.isValidUrl(req.url)) { // 585
next(); // 586
return; // 587
} // 588
res.writeHead(400); // 589
res.write("Not a proxy"); // 590
res.end(); // 591
}); // 592
// 593
// Strip off the path prefix, if it exists. // 594
app.use(function (request, response, next) { // 595
var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX; // 596
var url = Npm.require('url').parse(request.url); // 597
var pathname = url.pathname; // 598
// check if the path in the url starts with the path prefix (and the part // 599
// after the path prefix must start with a / if it exists.) // 600
if (pathPrefix && pathname.substring(0, pathPrefix.length) === pathPrefix && // 601
(pathname.length == pathPrefix.length // 602
|| pathname.substring(pathPrefix.length, pathPrefix.length + 1) === "/")) { // 603
request.url = request.url.substring(pathPrefix.length); // 604
next(); // 605
} else if (pathname === "/favicon.ico" || pathname === "/robots.txt") { // 606
next(); // 607
} else if (pathPrefix) { // 608
response.writeHead(404); // 609
response.write("Unknown path"); // 610
response.end(); // 611
} else { // 612
next(); // 613
} // 614
}); // 615
// 616
// Parse the query string into res.query. Used by oauth_server, but it's // 617
// generally pretty handy.. // 618
app.use(connect.query()); // 619
// 620
// Serve static files from the manifest. // 621
// This is inspired by the 'static' middleware. // 622
app.use(function (req, res, next) { // 623
Fiber(function () { // 624
WebAppInternals.staticFilesMiddleware(staticFiles, req, res, next); // 625
}).run(); // 626
}); // 627
// 628
// Packages and apps can add handlers to this via WebApp.connectHandlers. // 629
// They are inserted before our default handler. // 630
var packageAndAppHandlers = connect(); // 631
app.use(packageAndAppHandlers); // 632
// 633
var suppressConnectErrors = false; // 634
// connect knows it is an error handler because it has 4 arguments instead of // 635
// 3. go figure. (It is not smart enough to find such a thing if it's hidden // 636
// inside packageAndAppHandlers.) // 637
app.use(function (err, req, res, next) { // 638
if (!err || !suppressConnectErrors || !req.headers['x-suppress-error']) { // 639
next(err); // 640
return; // 641
} // 642
res.writeHead(err.status, { 'Content-Type': 'text/plain' }); // 643
res.end("An error message"); // 644
}); // 645
// 646
app.use(function (req, res, next) { // 647
if (! appUrl(req.url)) // 648
return next(); // 649
// 650
var headers = { // 651
'Content-Type': 'text/html; charset=utf-8' // 652
}; // 653
if (shuttingDown) // 654
headers['Connection'] = 'Close'; // 655
// 656
var request = WebApp.categorizeRequest(req); // 657
// 658
if (request.url.query && request.url.query['meteor_css_resource']) { // 659
// In this case, we're requesting a CSS resource in the meteor-specific // 660
// way, but we don't have it. Serve a static css file that indicates that // 661
// we didn't have it, so we can detect that and refresh. // 662
headers['Content-Type'] = 'text/css; charset=utf-8'; // 663
res.writeHead(200, headers); // 664
res.write(".meteor-css-not-found-error { width: 0px;}"); // 665
res.end(); // 666
return undefined; // 667
} // 668
// 669
// /packages/asdfsad ... /__cordova/dafsdf.js // 670
var pathname = connect.utils.parseUrl(req).pathname; // 671
var archKey = pathname.split('/')[1]; // 672
var archKeyCleaned = 'web.' + archKey.replace(/^__/, ''); // 673
// 674
if (! /^__/.test(archKey) || ! _.has(archPath, archKeyCleaned)) { // 675
archKey = WebApp.defaultArch; // 676
} else { // 677
archKey = archKeyCleaned; // 678
} // 679
// 680
var boilerplate; // 681
try { // 682
boilerplate = getBoilerplate(request, archKey); // 683
} catch (e) { // 684
Log.error("Error running template: " + e); // 685
res.writeHead(500, headers); // 686
res.end(); // 687
return undefined; // 688
} // 689
// 690
res.writeHead(200, headers); // 691
res.write(boilerplate); // 692
res.end(); // 693
return undefined; // 694
}); // 695
// 696
// Return 404 by default, if no other handlers serve this URL. // 697
app.use(function (req, res) { // 698
res.writeHead(404); // 699
res.end(); // 700
}); // 701
// 702
// 703
var httpServer = http.createServer(app); // 704
var onListeningCallbacks = []; // 705
// 706
// After 5 seconds w/o data on a socket, kill it. On the other hand, if // 707
// there's an outstanding request, give it a higher timeout instead (to avoid // 708
// killing long-polling requests) // 709
httpServer.setTimeout(SHORT_SOCKET_TIMEOUT); // 710
// 711
// Do this here, and then also in livedata/stream_server.js, because // 712
// stream_server.js kills all the current request handlers when installing its // 713
// own. // 714
httpServer.on('request', WebApp._timeoutAdjustmentRequestCallback); // 715
// 716
// 717
// start up app // 718
_.extend(WebApp, { // 719
connectHandlers: packageAndAppHandlers, // 720
rawConnectHandlers: rawConnectHandlers, // 721
httpServer: httpServer, // 722
// For testing. // 723
suppressConnectErrors: function () { // 724
suppressConnectErrors = true; // 725
}, // 726
onListening: function (f) { // 727
if (onListeningCallbacks) // 728
onListeningCallbacks.push(f); // 729
else // 730
f(); // 731
} // 732
}); // 733
// 734
// Let the rest of the packages (and Meteor.startup hooks) insert connect // 735
// middlewares and update __meteor_runtime_config__, then keep going to set up // 736
// actually serving HTML. // 737
main = function (argv) { // 738
WebAppInternals.generateBoilerplate(); // 739
// 740
// only start listening after all the startup code has run. // 741
var localPort = parseInt(process.env.PORT) || 0; // 742
var host = process.env.BIND_IP; // 743
var localIp = host || '0.0.0.0'; // 744
httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() { // 745
if (process.env.METEOR_PRINT_ON_LISTEN) // 746
console.log("LISTENING"); // must match run-app.js // 747
// 748
var callbacks = onListeningCallbacks; // 749
onListeningCallbacks = null; // 750
_.each(callbacks, function (x) { x(); }); // 751
// 752
}, function (e) { // 753
console.error("Error listening:", e); // 754
console.error(e && e.stack); // 755
})); // 756
// 757
return 'DAEMON'; // 758
}; // 759
}; // 760
// 761
// 762
runWebAppServer(); // 763
// 764
// 765
var inlineScriptsAllowed = true; // 766
// 767
WebAppInternals.inlineScriptsAllowed = function () { // 768
return inlineScriptsAllowed; // 769
}; // 770
// 771
WebAppInternals.setInlineScriptsAllowed = function (value) { // 772
inlineScriptsAllowed = value; // 773
WebAppInternals.generateBoilerplate(); // 774
}; // 775
// 776
WebAppInternals.setBundledJsCssPrefix = function (prefix) { // 777
bundledJsCssPrefix = prefix; // 778
WebAppInternals.generateBoilerplate(); // 779
}; // 780
// 781
// Packages can call `WebAppInternals.addStaticJs` to specify static // 782
// JavaScript to be included in the app. This static JS will be inlined, // 783
// unless inline scripts have been disabled, in which case it will be // 784
// served under `/<sha1 of contents>`. // 785
var additionalStaticJs = {}; // 786
WebAppInternals.addStaticJs = function (contents) { // 787
additionalStaticJs["/" + sha1(contents) + ".js"] = contents; // 788
}; // 789
// 790
// Exported for tests // 791
WebAppInternals.getBoilerplate = getBoilerplate; // 792
WebAppInternals.additionalStaticJs = additionalStaticJs; // 793
// 794
//////////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package.webapp = {
WebApp: WebApp,
main: main,
WebAppInternals: WebAppInternals
};
})();
//# sourceMappingURL=webapp.js.map