-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathcreateSocketURL.js
165 lines (135 loc) · 4.46 KB
/
createSocketURL.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
/**
* @param {{ protocol?: string, auth?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL
* @returns {string}
*/
function format(objURL) {
let protocol = objURL.protocol || "";
if (protocol && protocol.substr(-1) !== ":") {
protocol += ":";
}
let auth = objURL.auth || "";
if (auth) {
auth = encodeURIComponent(auth);
auth = auth.replace(/%3A/i, ":");
auth += "@";
}
let host = "";
if (objURL.hostname) {
host =
auth +
(objURL.hostname.indexOf(":") === -1
? objURL.hostname
: `[${objURL.hostname}]`);
if (objURL.port) {
host += `:${objURL.port}`;
}
}
let pathname = objURL.pathname || "";
if (objURL.slashes) {
host = `//${host || ""}`;
if (pathname && pathname.charAt(0) !== "/") {
pathname = `/${pathname}`;
}
} else if (!host) {
host = "";
}
let search = objURL.search || "";
if (search && search.charAt(0) !== "?") {
search = `?${search}`;
}
let hash = objURL.hash || "";
if (hash && hash.charAt(0) !== "#") {
hash = `#${hash}`;
}
pathname = pathname.replace(
/[?#]/g,
/**
* @param {string} match
* @returns {string}
*/
(match) => encodeURIComponent(match)
);
search = search.replace("#", "%23");
return `${protocol}${host}${pathname}${search}${hash}`;
}
/**
* @param {URL & { fromCurrentScript?: boolean }} parsedURL
* @returns {string}
*/
function createSocketURL(parsedURL) {
let { hostname } = parsedURL;
let socketURLPort = parsedURL.port;
// Node.js module parses it as `::`
// `new URL(urlString, [baseURLString])` parses it as '[::]'
const isInAddrAny =
hostname === "0.0.0.0" || hostname === "::" || 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 (
isInAddrAny &&
self.location.hostname &&
self.location.protocol.indexOf("http") === 0
) {
hostname = self.location.hostname;
// If we are using the location.hostname for the hostname, we should use the port from
// the location as well
socketURLPort = self.location.port;
}
let socketURLProtocol = parsedURL.protocol || self.location.protocol;
// When https is used in the app, secure web sockets are always necessary because the browser doesn't accept non-secure web sockets.
if (
socketURLProtocol === "auto:" ||
(hostname && isInAddrAny && self.location.protocol === "https:")
) {
socketURLProtocol = self.location.protocol;
}
socketURLProtocol = socketURLProtocol.replace(
/^(?:http|.+-extension|file)/i,
"ws"
);
let socketURLAuth = "";
// `new URL(urlString, [baseURLstring])` doesn't have `auth` property
// Parse authentication credentials in case we need them
if (parsedURL.username) {
socketURLAuth = parsedURL.username;
// Since HTTP basic authentication does not allow empty username,
// we only include password if the username is not empty.
if (parsedURL.password) {
// Result: <username>:<password>
socketURLAuth = socketURLAuth.concat(":", parsedURL.password);
}
}
// In case the host is a raw IPv6 address, it can be enclosed in
// the brackets as the brackets are needed in the final URL string.
// Need to remove those as url.format blindly adds its own set of brackets
// if the host string contains colons. That would lead to non-working
// double brackets (e.g. [[::]]) host
//
// All of these web socket url params are optionally passed in through resourceQuery,
// so we need to fall back to the default if they are not provided
const socketURLHostname = (
hostname ||
self.location.hostname ||
"localhost"
).replace(/^\[(.*)\]$/, "$1");
if (!socketURLPort || socketURLPort === "0") {
socketURLPort = self.location.port;
}
// If path 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.
let socketURLPathname = "/ws";
if (parsedURL.pathname && !parsedURL.fromCurrentScript) {
socketURLPathname = parsedURL.pathname;
}
return format({
protocol: socketURLProtocol,
auth: socketURLAuth,
hostname: socketURLHostname,
port: socketURLPort,
pathname: socketURLPathname,
slashes: true,
});
}
export default createSocketURL;