Skip to content

Commit

Permalink
refactor(client): create createSocketUrl module into utils (#1954)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroppy authored and evilebottnawi committed Jun 3, 2019
1 parent 9a1ad89 commit 1581adc
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 72 deletions.
74 changes: 2 additions & 72 deletions client-src/default/index.js
Expand Up @@ -2,17 +2,14 @@

/* global __resourceQuery WorkerGlobalScope self */
/* eslint prefer-destructuring: off */
const querystring = require('querystring');
const url = require('url');
const stripAnsi = require('strip-ansi');
const log = require('loglevel').getLogger('webpack-dev-server');
const socket = require('./socket');
const overlay = require('./overlay');
const sendMessage = require('./utils/sendMessage');
const reloadApp = require('./utils/reloadApp');
const getCurrentScriptSource = require('./utils/getCurrentScriptSource');
const createSocketUrl = require('./utils/createSocketUrl');

let urlParts;
const status = {
isUnloading: false,
currentHash: '',
Expand All @@ -26,6 +23,7 @@ const options = {
useErrorOverlay: false,
useProgress: false,
};
const socketUrl = createSocketUrl(__resourceQuery);

self.addEventListener('beforeunload', () => {
status.isUnloading = true;
Expand All @@ -35,20 +33,6 @@ if (typeof window !== 'undefined') {
const qs = window.location.search.toLowerCase();
options.hotReload = qs.indexOf('hotreload=false') === -1;
}
if (typeof __resourceQuery === 'string' && __resourceQuery) {
// If this bundle is inlined, use the resource query to get the correct url.
urlParts = url.parse(__resourceQuery.substr(1));
} else {
// Else, get the url from the <script> this file was called with.
let scriptHost = getCurrentScriptSource();
// eslint-disable-next-line no-useless-escape
scriptHost = scriptHost.replace(/\/[^\/]+$/, '');
urlParts = url.parse(scriptHost || '/', false, true);
}

if (!urlParts.port || urlParts.port === '0') {
urlParts.port = self.location.port;
}

const INFO = 'info';
const WARN = 'warn';
Expand Down Expand Up @@ -192,58 +176,4 @@ const onSocketMsg = {
},
};

let { hostname, protocol } = urlParts;

// check ipv4 and ipv6 `all hostname`
if (hostname === '0.0.0.0' || hostname === '::') {
// why do we need this check?
// hostname n/a for file protocol (example, when using electron, ionic)
// see: https://github.com/webpack/webpack-dev-server/pull/384
// eslint-disable-next-line no-bitwise
if (self.location.hostname && !!~self.location.protocol.indexOf('http')) {
hostname = self.location.hostname;
}
}

// `hostname` can be empty when the script path is relative. In that case, specifying
// a protocol would result in an invalid URL.
// When https is used in the app, secure websockets are always necessary
// because the browser doesn't accept non-secure websockets.
if (
hostname &&
(self.location.protocol === 'https:' || urlParts.hostname === '0.0.0.0')
) {
protocol = self.location.protocol;
}

// default values of the sock url if they are not provided
let sockHost = hostname;
let sockPath = '/sockjs-node';
let sockPort = urlParts.port;
if (
urlParts.path !== null &&
// eslint-disable-next-line no-undefined
urlParts.path !== undefined &&
urlParts.path !== '/'
) {
const parsedQuery = querystring.parse(urlParts.path);
// all of these sock url params are optionally passed in through
// __resourceQuery, so we need to fall back to the default if
// they are not provided
sockHost = parsedQuery.sockHost || sockHost;
sockPath = parsedQuery.sockPath || sockPath;
sockPort = parsedQuery.sockPort || sockPort;
}

const socketUrl = url.format({
protocol,
auth: urlParts.auth,
hostname: sockHost,
port: sockPort,
// If sockPath is provided it'll be passed in via the __resourceQuery as a
// query param so it has to be parsed out of the querystring in order for the
// client to open the socket to the correct location.
pathname: sockPath,
});

socket(socketUrl, onSocketMsg);
83 changes: 83 additions & 0 deletions client-src/default/utils/createSocketUrl.js
@@ -0,0 +1,83 @@
'use strict';

/* global self */

const url = require('url');
const querystring = require('querystring');
const getCurrentScriptSource = require('./getCurrentScriptSource');

function createSocketUrl(resourceQuery) {
let urlParts;

if (typeof resourceQuery === 'string' && resourceQuery !== '') {
// If this bundle is inlined, use the resource query to get the correct url.
urlParts = url.parse(resourceQuery.substr(1));
} else {
// Else, get the url from the <script> this file was called with.
let scriptHost = getCurrentScriptSource();

// eslint-disable-next-line no-useless-escape
scriptHost = scriptHost.replace(/\/[^\/]+$/, '');
urlParts = url.parse(scriptHost || '/', false, true);
}

if (!urlParts.port || urlParts.port === '0') {
urlParts.port = self.location.port;
}

const { auth, path } = urlParts;
let { hostname, protocol } = urlParts;

// check ipv4 and ipv6 `all hostname`
// why do we need this check?
// hostname n/a for file protocol (example, when using electron, ionic)
// see: https://github.com/webpack/webpack-dev-server/pull/384
if (
(hostname === '0.0.0.0' || hostname === '::') &&
self.location.hostname &&
// eslint-disable-next-line no-bitwise
!!~self.location.protocol.indexOf('http')
) {
hostname = self.location.hostname;
}

// `hostname` can be empty when the script path is relative. In that case, specifying
// a protocol would result in an invalid URL.
// When https is used in the app, secure websockets are always necessary
// because the browser doesn't accept non-secure websockets.
if (
hostname &&
(self.location.protocol === 'https:' || urlParts.hostname === '0.0.0.0')
) {
protocol = self.location.protocol;
}

// default values of the sock url if they are not provided
let sockHost = hostname;
let sockPath = '/sockjs-node';
let sockPort = urlParts.port;

// eslint-disable-next-line no-undefined
if (path !== null && path !== undefined && path !== '/') {
const parsedQuery = querystring.parse(path);
// all of these sock url params are optionally passed in through
// resourceQuery, so we need to fall back to the default if
// they are not provided
sockHost = parsedQuery.sockHost || sockHost;
sockPath = parsedQuery.sockPath || sockPath;
sockPort = parsedQuery.sockPort || sockPort;
}

return url.format({
protocol,
auth,
hostname: sockHost,
port: sockPort,
// If sockPath is provided it'll be passed in via the resourceQuery as a
// query param so it has to be parsed out of the querystring in order for the
// client to open the socket to the correct location.
pathname: sockPath,
});
}

module.exports = createSocketUrl;
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`createSocketUrl should return the url when __resourceQuery is ?test 1`] = `"/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is http://0.0.0.0 1`] = `"http://localhost/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is http://user:pass@[::]:8080 1`] = `"ttp:user:pass@localhost:8080/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is http://user:password@localhost/ 1`] = `"ttp:user:password@localhost/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is https://example.com 1`] = `"ttps:example.com/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is https://example.com/path 1`] = `"ttps:example.com/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is https://example.com/path/foo.js 1`] = `"ttps:example.com/sockjs-node"`;

exports[`createSocketUrl should return the url when __resourceQuery is https://localhost:123 1`] = `"ttps:localhost:123/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is ?test 1`] = `"/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is http://0.0.0.0 1`] = `"http:/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is http://user:pass@[::]:8080 1`] = `"http:/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is http://user:password@localhost/ 1`] = `"http://user:password@localhost/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is https://example.com 1`] = `"https:/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is https://example.com/path 1`] = `"https://example.com/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is https://example.com/path/foo.js 1`] = `"https://example.com/sockjs-node"`;

exports[`createSocketUrl should return the url when the current script source is https://localhost:123 1`] = `"https:/sockjs-node"`;
33 changes: 33 additions & 0 deletions test/client/default/utils/createSocketUrl.test.js
@@ -0,0 +1,33 @@
'use strict';

describe('createSocketUrl', () => {
const samples = [
'?test',
'https://example.com',
'https://example.com/path',
'https://example.com/path/foo.js',
'http://user:password@localhost/',
'http://0.0.0.0',
'https://localhost:123',
'http://user:pass@[::]:8080',
// TODO: comment out after the major release
// https://github.com/webpack/webpack-dev-server/pull/1954#issuecomment-498043376
// 'file://filename',
];

samples.forEach((url) => {
jest.doMock('../../../../client-src/default/utils/getCurrentScriptSource.js', () => () => url);
const createSocketUrl = require('../../../../client-src/default/utils/createSocketUrl');

test(`should return the url when __resourceQuery is ${url}`, () => {
expect(createSocketUrl(url)).toMatchSnapshot();
});

test(`should return the url when the current script source is ${url}`, () => {
expect(createSocketUrl()).toMatchSnapshot();
});

// put here because resetModules mustn't be reset when L20 is finished
jest.resetModules();
});
});

0 comments on commit 1581adc

Please sign in to comment.