Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pagexray

Package Overview
Dependencies
Maintainers
3
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pagexray - npm Package Compare versions

Comparing version 0.14.3 to 1.0.0

bin/.eslintrc.json

10

bin/index.js

@@ -10,3 +10,3 @@ #!/usr/bin/env node

const argv = minimist(process.argv.slice(2), {
boolean: ['pretty', 'includeAssets'],
boolean: ['pretty', 'includeAssets']
});

@@ -19,4 +19,8 @@

console.log(' --pretty Pretty format the JSON [false]');
console.log(' --includeAssets Include info about every asset in the result [false]');
console.log(' --firstParty A regex defining if a URL is 1st or 3rd party URL');
console.log(
' --includeAssets Include info about every asset in the result [false]'
);
console.log(
' --firstParty A regex defining if a URL is 1st or 3rd party URL'
);
} else {

@@ -23,0 +27,0 @@ const har = JSON.parse(fs.readFileSync(argv._[0]));

# CHANGELOG - PageXray
version 1.0.0 2017-08-26
------------------------
### Added
* You can now run PageXray in the browser! Check Github releases to download that version.
* If the HAR is generated with sitespeed.io > 5.4.3, we pickup some extra meta data: connection type, URL to the result page, URL to the video, URL to the screenshot. And we also add the browser and version if that is availible.
* Pickup firstParty/thirdParty config from the HAR, override with config.
* Collect timings per domain.
* Collect timings per timing type instead of total. This change is not backward compatible and you need to calculate the total yourself.
version 0.14.3 2017-06-23

@@ -4,0 +13,0 @@ ------------------------

@@ -7,3 +7,5 @@ 'use strict';

function getTiming(entry, field) {
return entry.timings[field] === -1 ? 0 : entry.timings[field];
return entry.timings[field] === -1
? 0
: Number(entry.timings[field].toFixed(0));
}

@@ -16,6 +18,19 @@

headerSize: 0,
requests: 0,
requests: 0
};
}
function newContentDataWithTimings() {
const content = newContentData();
content.timings = {
blocked: 0,
dns: 0,
connect: 0,
send: 0,
wait: 0,
receive: 0
};
return content;
}
/*

@@ -28,4 +43,3 @@ * Collect information about a response (asset).

module.exports = {
asset: (entry) => {
asset: entry => {
const response = entry.response;

@@ -38,6 +52,10 @@ const request = entry.request;

const timing = getTiming(entry, 'blocked') + getTiming(entry, 'dns') +
getTiming(entry, 'connect') +
getTiming(entry, 'send') + getTiming(entry, 'wait') +
getTiming(entry, 'receive');
const timings = {
blocked: getTiming(entry, 'blocked'),
dns: getTiming(entry, 'dns'),
connect: getTiming(entry, 'connect'),
send: getTiming(entry, 'send'),
wait: getTiming(entry, 'wait'),
receive: getTiming(entry, 'receive')
};

@@ -48,3 +66,4 @@ return {

transferSize: response.bodySize,
contentSize: response.content.size < 0 ? response.bodySize : response.content.size,
contentSize:
response.content.size < 0 ? response.bodySize : response.content.size,
headerSize: response.headersSize,

@@ -57,6 +76,6 @@ expires: headers.getExpires(responseHeaders),

request: requestHeaders,
response: responseHeaders,
response: responseHeaders
},
timing,
cookies: request.cookies.length,
timings,
cookies: request.cookies.length
};

@@ -75,3 +94,3 @@ },

const domain = util.getHostname(asset.url);
const contentData = domains[domain] || newContentData();
const contentData = domains[domain] || newContentDataWithTimings();

@@ -83,2 +102,9 @@ contentData.transferSize += asset.transferSize;

contentData.timings.blocked += asset.timings.blocked;
contentData.timings.dns += asset.timings.dns;
contentData.timings.connect += asset.timings.connect;
contentData.timings.send += asset.timings.send;
contentData.timings.wait += asset.timings.wait;
contentData.timings.receive += asset.timings.receive;
domains[domain] = contentData;

@@ -105,3 +131,10 @@ },

const couldBeCompressed = ['html', 'plain', 'json', 'javascript', 'css', 'svg'].includes(asset.type);
const couldBeCompressed = [
'html',
'plain',
'json',
'javascript',
'css',
'svg'
].includes(asset.type);
const isCompressed = ['gzip', 'br', 'deflate'].includes(encoding);

@@ -123,3 +156,3 @@ const isLargeFile = asset.contentSize > 2000;

image: newContentData(),
font: newContentData(),
font: newContentData()
};

@@ -135,3 +168,3 @@ },

contentType: (asset, contentTypes) => {
if (!(/^2\d{2}/.test(asset.status))) {
if (!/^2\d{2}/.test(asset.status)) {
return;

@@ -152,3 +185,3 @@ }

contentTypes[asset.type] = contentData;
},
}
};
'use strict';
module.exports = {
/**

@@ -12,3 +11,3 @@ * Flatten HAR file headers. Har files has the header of the

*/
flatten: (headers) => {
flatten: headers => {
const theHeaders = headers.reduce((result, header) => {

@@ -28,3 +27,3 @@ result[header.name.toLowerCase()] = header.value;

*/
getExpires: (responseHeaders) => {
getExpires: responseHeaders => {
const maxAgeRegExp = /max-age=(\d+)/;

@@ -34,4 +33,6 @@ let expireTime = 0;

if (responseHeaders['cache-control']) {
if (responseHeaders['cache-control'].indexOf('no-cache') !== -1 ||
responseHeaders['cache-control'].indexOf('no-store') !== -1) {
if (
responseHeaders['cache-control'].indexOf('no-cache') !== -1 ||
responseHeaders['cache-control'].indexOf('no-store') !== -1
) {
return 0;

@@ -56,19 +57,17 @@ }

*/
getTimeSinceLastModified: (headers) => {
let now = new Date();
let lastModifiedDate;
if (headers['last-modified']) {
lastModifiedDate = new Date(headers['last-modified']);
} else if (headers.date) {
now = new Date(headers.date);
getTimeSinceLastModified: headers => {
const lastModifiedHeader = headers['last-modified'];
if (!lastModifiedHeader) {
return -1;
}
// TODO how do we define if we don't have a timing
// is it better to just return 0?
if (!lastModifiedDate) {
return -1;
const lastModifiedMillis = Date.parse(lastModifiedHeader);
let createdMillis = Date.now();
if (headers.date) {
createdMillis = Date.parse(headers.date);
}
return (now.getTime() - lastModifiedDate.getTime()) / 1000;
},
return (createdMillis - lastModifiedMillis) / 1000;
}
};

@@ -5,4 +5,24 @@ 'use strict';

const collect = require('./collect');
const sitespeed = require('./sitespeed');
const webpagetest = require('./webpagetest');
const Statistics = require('./statistics').Statistics;
function cleanupStatistics(pages, config, firstParty) {
pages.forEach(page => {
page.expireStats = page.expireStats.summarize();
page.lastModifiedStats = page.lastModifiedStats.summarize();
page.cookieStats = page.cookieStats.summarize();
page.totalDomains = Object.keys(page.domains).length;
if (!config.includeAssets) {
page.assets = [];
}
if (!firstParty) {
page.firstParty = {};
page.thirdParty = {};
} else {
page.firstParty.cookieStats = page.firstParty.cookieStats.summarize();
page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize();
}
});
}
/**

@@ -14,2 +34,15 @@ * Convert a HAR object to a better page summary.

module.exports = {
/**
* Convert one HAR to a page. Use this when you are interested
* of one specific run in a HAR.
* @param {Object} har The HAR to process.
* @param {Number} index The index of the HAR file of the run that will be converted.
* @param {Object} config The config object.
* @returns {Array} The converted page object.
*/
convertIndex: (har, index, config) => {
// TODO in the future only convert that specific run to save time
const pages = module.exports.convert(har, config);
return pages[index];
},

@@ -29,11 +62,35 @@ /**

const testedPages = {};
let firstParty;
har.log.entries.forEach((entry) => {
if (
config.firstParty ||
(har.log.pages[0]._meta && har.log.pages[0]._meta.firstParty)
) {
firstParty = config.firstParty || har.log.pages[0]._meta.firstParty;
}
function sortByTime(a, b) {
return (
new Date(a.startedDateTime).getTime() -
new Date(b.startedDateTime).getTime()
);
}
har.log.entries.sort(sortByTime);
har.log.entries.forEach(entry => {
if (!testedPages[entry.pageref]) {
const redirects = util.getFinalURL(entry, har);
const httpVersion =
redirects.chain.length === 0
? entry.response.httpVersion
: util.getEntryByURL(har.log.entries, redirects.url).response
.httpVersion;
currentPage = {
url: har.log.entries[0].request.url,
url: entry.request.url,
meta: { browser: {}, startedDateTime: entry.startedDateTime },
finalUrl: redirects.url,
baseDomain: util.getHostname(redirects.url),
documentRedirects: (redirects.chain.length === 0) ? 0 : (redirects.chain.length - 1),
documentRedirects:
redirects.chain.length === 0 ? 0 : redirects.chain.length - 1,
redirectChain: redirects.chain,

@@ -45,5 +102,4 @@ transferSize: 0,

missingCompression: 0,
// TODO this will not be right if redirected!!!
httpType: util.getConnectionType(har.log.entries[0].response.httpVersion),
httpVersion: util.getHTTPVersion(har.log.entries[0].response.httpVersion),
httpType: util.getConnectionType(httpVersion),
httpVersion: util.getHTTPVersion(httpVersion),
contentTypes: collect.defaultContentTypes(),

@@ -54,7 +110,7 @@ assets: [],

cookieStats: new Statistics(),
contentTypes: collect.defaultContentTypes(),
contentTypes: collect.defaultContentTypes()
},
thirdParty: {
cookieStats: new Statistics(),
contentTypes: collect.defaultContentTypes(),
contentTypes: collect.defaultContentTypes()
},

@@ -64,4 +120,12 @@ domains: {},

lastModifiedStats: new Statistics(),
cookieStats: new Statistics(),
cookieStats: new Statistics()
};
if (har.log.browser && har.log.browser.name) {
currentPage.meta.browser.name = har.log.browser.name;
}
if (har.log.browser && har.log.browser.version) {
currentPage.meta.browser.version = har.log.browser.version;
}
testedPages[entry.pageref] = currentPage;

@@ -84,8 +148,10 @@ pages.push(currentPage);

currentPage.transferSize += entry.response.bodySize;
currentPage.contentSize += entry.response.content.size < 0 ? entry.response.bodySize :
entry.response.content.size;
currentPage.headerSize += entry.response.headersSize;
currentPage.contentSize +=
entry.response.content.size < 0
? entry.response.bodySize
: entry.response.content.size;
currentPage.headerSize += Math.max(entry.response.headersSize, 0);
// add first/third party info
if (config.firstParty) {
if (firstParty) {
// is it a third party asset?

@@ -95,3 +161,3 @@

if (asset.url.match(config.firstParty)) {
if (asset.url.match(firstParty)) {
stats = currentPage.firstParty;

@@ -101,8 +167,8 @@ }

stats.requests = stats.requests + 1 || 1;
stats.transferSize = stats.transferSize + asset.transferSize ||
asset.transferSize;
stats.contentSize = stats.contentSize + asset.contentSize ||
asset.contentSize;
stats.headerSize = stats.headerSize + asset.headerSize ||
asset.headerSize;
stats.transferSize =
stats.transferSize + asset.transferSize || asset.transferSize;
stats.contentSize =
stats.contentSize + asset.contentSize || asset.contentSize;
stats.headerSize =
stats.headerSize + asset.headerSize || asset.headerSize;
stats.cookieStats.add(asset.cookies);

@@ -116,20 +182,14 @@ collect.contentType(asset, stats.contentTypes);

// cleanup the stats
pages.forEach((page) => {
page.expireStats = page.expireStats.summarize();
page.lastModifiedStats = page.lastModifiedStats.summarize();
page.cookieStats = page.cookieStats.summarize();
page.totalDomains = Object.keys(page.domains).length;
if (!config.includeAssets) {
page.assets = [];
}
if (!config.firstParty) {
page.firstParty = {};
page.thirdParty = {};
} else {
page.firstParty.cookieStats = page.firstParty.cookieStats.summarize();
page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize();
}
});
cleanupStatistics(pages, config, firstParty);
// If we have that extra meta field in the HAR, we are pretty sure
// it is generated using sitespeed.io/browsertime, so add those
// extra metrics
if (har.log.pages[0]._meta) {
sitespeed.addMetrics(har, pages);
} else if (har.log.creator.name === 'WebPagetest') {
webpagetest.addMetrics(har, pages);
}
return pages;
},
}
};
'use strict';
const Stats = require('fast-stats').Stats;
function percentileName(percentile) {
if (percentile === 0) {
return 'min';
} else if (percentile === 100) {
return 'max';
} else if (percentile === 50) {
return 'median';
}
return `p${String(percentile).replace('.', '_')}`;
}
class Statistics {
constructor() {
this.stats = new Stats();
this.values = [];
this.total = 0;
}
add(value) {
this.stats.push(value);
this.values.push(value);
this.total += value;
return this;
}
summarize(options) {
if (isNaN(this.stats.amean())) {
summarize() {
const values = this.values;
// keeping backward compatibility
if (values.length === 0) {
return undefined;
}
options = options || {};
const percentiles = options.percentiles || [0, 10, 50, 90, 99, 100];
const decimals = options.decimals || 0;
const data = {};
const stats = this.stats;
values.sort((a, b) => a - b);
percentiles.forEach((p) => {
const name = percentileName(p);
data[name] = Number(stats.percentile(p).toFixed(decimals));
});
const middle = Math.floor(values.length / 2);
const isEven = values.length % 2 === 0;
let median;
if (isEven) {
median = Number(((values[middle] + values[middle - 1]) / 2).toFixed(0));
} else {
median = Number(values[middle].toFixed(0));
}
return data;
return {
min: Number(values[0].toFixed(0)),
median,
max: Number(values[values.length - 1].toFixed(0)),
total: Number(this.total.toFixed(0)),
values: values.length
};
}
}
module.exports = {
Statistics,
Statistics
};

@@ -20,3 +20,3 @@ 'use strict';

[/^application\/ocsp-response/, 'oscp'],
[/.*/, 'other'], // Always match 'other' if all else fails
[/.*/, 'other'] // Always match 'other' if all else fails
];

@@ -29,3 +29,2 @@

module.exports = {
/**

@@ -39,3 +38,3 @@ * Get the content type from mime type.

getHTTPVersion: (version) => {
getHTTPVersion: version => {
if (version === 'h2' || version === 'HTTP/2.0') {

@@ -48,3 +47,3 @@ return 'HTTP/2.0';

},
getConnectionType: (version) => {
getConnectionType: version => {
if (version === 'h2' || version === 'HTTP/2.0') {

@@ -62,3 +61,3 @@ return 'h2';

*/
getHostname: (url) => {
getHostname: url => {
if (!url) {

@@ -98,5 +97,12 @@ return '';

url,
chain,
chain
};
},
getEntryByURL(entries, url) {
for (let entry of entries) {
if (entry.request.url === url) {
return entry;
}
}
}
};
{
"name": "pagexray",
"version": "0.14.3",
"version": "1.0.0",
"description": "Xray your HAR file and know all about the page",

@@ -35,7 +35,11 @@ "keywords": [

"scripts": {
"lint": "npm run eslint",
"eslint": "eslint .",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check",
"test": "mocha",
"jsdoc": "jsdoc lib/* -d dist/doc",
"travis": "npm run lint && npm run test"
"travis": "npm run eslint-check && npm run lint && npm run test && npm run dist",
"browserify": "mkdirp dist && browserify lib/index.js -o dist/pagexray.js --standalone PageXray -t [ babelify --presets [ es2015 ] ] -p [ browserify-banner --template 'PageXray v<%= pkg.version %> - Xray your HAR file and know all about the page - https://github.com/sitespeedio/pagexray' ]",
"uglify": "uglifyjs dist/pagexray.js --compress --mangle --comments '/pagexray/' > dist/pagexray.min.js",
"dist": "npm run browserify && npm run uglify"
},

@@ -46,11 +50,18 @@ "engines": {

"devDependencies": {
"babel-preset-es2015": "^6.24.1",
"babelify": "^7.3.0",
"bluebird": "^3.3.5",
"chai": "^4.0.1",
"eslint": "^3.5.0",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.3.0",
"jsdoc": "^3.4.0",
"browserify": "^14.4.0",
"browserify-banner": "^1.0.4",
"chai": "^4.1.0",
"eslint": "^4.2.0",
"eslint-config-prettier": "^2.3.0",
"eslint-plugin-prettier": "^2.1.2",
"jsdoc": "^3.5.3",
"lodash.foreach": "^4.2.0",
"lodash.pluck": "^3.1.2",
"mocha": "^3.0.2"
"mkdirp": "^0.5.1",
"mocha": "^3.0.2",
"prettier": "^1.5.3",
"uglify-es": "^3.0.24"
},

@@ -60,5 +71,4 @@ "main": "./lib/index.js",

"dependencies": {
"fast-stats": "0.0.3",
"minimist": "1.2.0"
}
}

@@ -7,3 +7,3 @@ # PageXray

We love the HAR file but it's hard to actually see what the page includes only looking at the file. The PageXray converts a HAR file to a JSON format that is easier to read. We use the format internally in the coach and sitespeed.io.
We love the HAR file but it's hard to actually see what the page includes only looking at the file. The PageXray converts a HAR file to a JSON format that is easier to read. We use the format internally in the coach and sitespeed.io. And with PageXray you can use it standalone in your browser.

@@ -34,3 +34,3 @@ ## What do we collect?

```bash
pagexray -includeAssets /path/to/my.har
pagexray --includeAssets /path/to/my.har
```

@@ -44,2 +44,9 @@

```
## Using in your browser
Include the latest pagexray.min.js (that you find in the relases) on your page. PageXray is exposed as *window.PageXray*
```javascript
const pageXray = window.PageXray.convert(har);
```
## Output

@@ -52,18 +59,27 @@ All sizes are in bytes. Expires and timeSinceLastModified are in seconds.

"url": "https://www.sitespeed.io/",
"meta": {
"browser": {
"name": "Chrome",
"version": "60.0.3112.78"
},
"startedDateTime": "2017-08-24T18:26:29.077Z",
"connectivity": "native",
"title": "Sitespeed.io - Welcome to the wonderful world of Web Performance run 1"
},
"finalUrl": "https://www.sitespeed.io/",
"baseDomain": "www.sitespeed.io",
"documentRedirects": -1,
"documentRedirects": 0,
"redirectChain": [],
"transferSize": 160894,
"contentSize": 221699,
"headerSize": 8027,
"requests": 12,
"transferSize": 98791,
"contentSize": 120776,
"headerSize": 0,
"requests": 10,
"missingCompression": 0,
"httpType": "h1",
"httpVersion": "HTTP/1.1",
"httpType": "h2",
"httpVersion": "HTTP/2.0",
"contentTypes": {
"html": {
"transferSize": 7276,
"contentSize": 24962,
"headerSize": 776,
"transferSize": 8479,
"contentSize": 28279,
"headerSize": 0,
"requests": 1

@@ -79,11 +95,11 @@ },

"transferSize": 0,
"contentSize": 43082,
"contentSize": 0,
"headerSize": 0,
"requests": 1
"requests": 0
},
"image": {
"transferSize": 153620,
"contentSize": 153655,
"headerSize": 7253,
"requests": 10
"transferSize": 87309,
"contentSize": 85979,
"headerSize": 0,
"requests": 8
},

@@ -95,2 +111,8 @@ "font": {

"requests": 0
},
"favicon": {
"transferSize": 3003,
"contentSize": 6518,
"headerSize": 0,
"requests": 1
}

@@ -100,3 +122,3 @@ },

"responseCodes": {
"200": 12
"200": 10
},

@@ -107,39 +129,52 @@ "firstParty": {},

"www.sitespeed.io": {
"transferSize": 98791,
"contentSize": 120776,
"headerSize": -10,
"requests": 10,
"transferSize": 160896,
"contentSize": 178582,
"headerSize": 8029
},
"ssl.google-analytics.com": {
"requests": 2,
"transferSize": -2,
"contentSize": 43117,
"headerSize": -2
"timings": {
"blocked": 169,
"dns": 0,
"connect": 0,
"send": 6,
"wait": 3624,
"receive": 104
}
}
},
"expireStats": {
"min": 0,
"p10": 600,
"min": 600,
"median": 31536000,
"p90": 31536000,
"p99": 31536000,
"max": 31536000
"max": 31536000,
"total": 283824600,
"values": 10
},
"lastModifiedStats": {
"min": 3121029,
"p10": 8475293,
"median": 8475313,
"p90": 8475339,
"p99": 578982140,
"max": 578982140
"min": 733347,
"median": 733444,
"max": 733480,
"total": 7334359,
"values": 10
},
"cookieStats": {
"min": 0,
"p10": 0,
"median": 0,
"p90": 0,
"p99": 0,
"max": 0
"max": 0,
"total": 0,
"values": 10
},
"totalDomains": 2
"totalDomains": 1,
"visualMetrics": {
"FirstVisualChange": 617,
"SpeedIndex": 625,
"VisualComplete85": 617,
"LastVisualChange": 1033,
"VisualProgress": {
"0": 0,
"617": 98,
"633": 98,
"667": 98,
"850": 98,
"1033": 100
}
}
}

@@ -146,0 +181,0 @@ ]

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc