fast-uri 3.0.6 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,28 +1,49 @@
1
1
  'use strict'
2
2
 
3
- const { normalizeIPv6, normalizeIPv4, removeDotSegments, recomposeAuthority, normalizeComponentEncoding } = require('./lib/utils')
4
- const SCHEMES = require('./lib/schemes')
5
-
3
+ const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, isIPv4, nonSimpleDomain } = require('./lib/utils')
4
+ const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
5
+
6
+ /**
7
+ * @template {import('./types/index').URIComponent|string} T
8
+ * @param {T} uri
9
+ * @param {import('./types/index').Options} [options]
10
+ * @returns {T}
11
+ */
6
12
  function normalize (uri, options) {
7
13
  if (typeof uri === 'string') {
8
- uri = serialize(parse(uri, options), options)
14
+ uri = /** @type {T} */ (serialize(parse(uri, options), options))
9
15
  } else if (typeof uri === 'object') {
10
- uri = parse(serialize(uri, options), options)
16
+ uri = /** @type {T} */ (parse(serialize(uri, options), options))
11
17
  }
12
18
  return uri
13
19
  }
14
20
 
21
+ /**
22
+ * @param {string} baseURI
23
+ * @param {string} relativeURI
24
+ * @param {import('./types/index').Options} [options]
25
+ * @returns {string}
26
+ */
15
27
  function resolve (baseURI, relativeURI, options) {
16
- const schemelessOptions = Object.assign({ scheme: 'null' }, options)
17
- const resolved = resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
18
- return serialize(resolved, { ...schemelessOptions, skipEscape: true })
28
+ const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
29
+ const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
30
+ schemelessOptions.skipEscape = true
31
+ return serialize(resolved, schemelessOptions)
19
32
  }
20
33
 
21
- function resolveComponents (base, relative, options, skipNormalization) {
34
+ /**
35
+ * @param {import ('./types/index').URIComponent} base
36
+ * @param {import ('./types/index').URIComponent} relative
37
+ * @param {import('./types/index').Options} [options]
38
+ * @param {boolean} [skipNormalization=false]
39
+ * @returns {import ('./types/index').URIComponent}
40
+ */
41
+ function resolveComponent (base, relative, options, skipNormalization) {
42
+ /** @type {import('./types/index').URIComponent} */
22
43
  const target = {}
23
44
  if (!skipNormalization) {
24
- base = parse(serialize(base, options), options) // normalize base components
25
- relative = parse(serialize(relative, options), options) // normalize relative components
45
+ base = parse(serialize(base, options), options) // normalize base component
46
+ relative = parse(serialize(relative, options), options) // normalize relative component
26
47
  }
27
48
  options = options || {}
28
49
 
@@ -51,7 +72,7 @@ function resolveComponents (base, relative, options, skipNormalization) {
51
72
  target.query = base.query
52
73
  }
53
74
  } else {
54
- if (relative.path.charAt(0) === '/') {
75
+ if (relative.path[0] === '/') {
55
76
  target.path = removeDotSegments(relative.path)
56
77
  } else {
57
78
  if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
@@ -78,26 +99,35 @@ function resolveComponents (base, relative, options, skipNormalization) {
78
99
  return target
79
100
  }
80
101
 
102
+ /**
103
+ * @param {import ('./types/index').URIComponent|string} uriA
104
+ * @param {import ('./types/index').URIComponent|string} uriB
105
+ * @param {import ('./types/index').Options} options
106
+ * @returns {boolean}
107
+ */
81
108
  function equal (uriA, uriB, options) {
82
109
  if (typeof uriA === 'string') {
83
- uriA = unescape(uriA)
84
- uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true })
110
+ uriA = serialize(parse(uriA, options), options)
85
111
  } else if (typeof uriA === 'object') {
86
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true })
112
+ uriA = serialize(uriA, options)
87
113
  }
88
114
 
89
115
  if (typeof uriB === 'string') {
90
- uriB = unescape(uriB)
91
- uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true })
116
+ uriB = serialize(parse(uriB, options), options)
92
117
  } else if (typeof uriB === 'object') {
93
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true })
118
+ uriB = serialize(uriB, options)
94
119
  }
95
120
 
96
121
  return uriA.toLowerCase() === uriB.toLowerCase()
97
122
  }
98
123
 
124
+ /**
125
+ * @param {Readonly<import('./types/index').URIComponent>} cmpts
126
+ * @param {import('./types/index').Options} [opts]
127
+ * @returns {string}
128
+ */
99
129
  function serialize (cmpts, opts) {
100
- const components = {
130
+ const component = {
101
131
  host: cmpts.host,
102
132
  scheme: cmpts.scheme,
103
133
  userinfo: cmpts.userinfo,
@@ -117,28 +147,28 @@ function serialize (cmpts, opts) {
117
147
  const uriTokens = []
118
148
 
119
149
  // find scheme handler
120
- const schemeHandler = SCHEMES[(options.scheme || components.scheme || '').toLowerCase()]
150
+ const schemeHandler = getSchemeHandler(options.scheme || component.scheme)
121
151
 
122
152
  // perform scheme specific serialization
123
- if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options)
153
+ if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
124
154
 
125
- if (components.path !== undefined) {
155
+ if (component.path !== undefined) {
126
156
  if (!options.skipEscape) {
127
- components.path = escape(components.path)
157
+ component.path = escapePreservingEscapes(component.path)
128
158
 
129
- if (components.scheme !== undefined) {
130
- components.path = components.path.split('%3A').join(':')
159
+ if (component.scheme !== undefined) {
160
+ component.path = component.path.split('%3A').join(':')
131
161
  }
132
162
  } else {
133
- components.path = unescape(components.path)
163
+ component.path = normalizePercentEncoding(component.path)
134
164
  }
135
165
  }
136
166
 
137
- if (options.reference !== 'suffix' && components.scheme) {
138
- uriTokens.push(components.scheme, ':')
167
+ if (options.reference !== 'suffix' && component.scheme) {
168
+ uriTokens.push(component.scheme, ':')
139
169
  }
140
170
 
141
- const authority = recomposeAuthority(components)
171
+ const authority = recomposeAuthority(component)
142
172
  if (authority !== undefined) {
143
173
  if (options.reference !== 'suffix') {
144
174
  uriTokens.push('//')
@@ -146,51 +176,49 @@ function serialize (cmpts, opts) {
146
176
 
147
177
  uriTokens.push(authority)
148
178
 
149
- if (components.path && components.path.charAt(0) !== '/') {
179
+ if (component.path && component.path[0] !== '/') {
150
180
  uriTokens.push('/')
151
181
  }
152
182
  }
153
- if (components.path !== undefined) {
154
- let s = components.path
183
+ if (component.path !== undefined) {
184
+ let s = component.path
155
185
 
156
186
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
157
187
  s = removeDotSegments(s)
158
188
  }
159
189
 
160
- if (authority === undefined) {
161
- s = s.replace(/^\/\//u, '/%2F') // don't allow the path to start with "//"
190
+ if (
191
+ authority === undefined &&
192
+ s[0] === '/' &&
193
+ s[1] === '/'
194
+ ) {
195
+ // don't allow the path to start with "//"
196
+ s = '/%2F' + s.slice(2)
162
197
  }
163
198
 
164
199
  uriTokens.push(s)
165
200
  }
166
201
 
167
- if (components.query !== undefined) {
168
- uriTokens.push('?', components.query)
202
+ if (component.query !== undefined) {
203
+ uriTokens.push('?', component.query)
169
204
  }
170
205
 
171
- if (components.fragment !== undefined) {
172
- uriTokens.push('#', components.fragment)
206
+ if (component.fragment !== undefined) {
207
+ uriTokens.push('#', component.fragment)
173
208
  }
174
209
  return uriTokens.join('')
175
210
  }
176
211
 
177
- const hexLookUp = Array.from({ length: 127 }, (_v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k)))
178
-
179
- function nonSimpleDomain (value) {
180
- let code = 0
181
- for (let i = 0, len = value.length; i < len; ++i) {
182
- code = value.charCodeAt(i)
183
- if (code > 126 || hexLookUp[code]) {
184
- return true
185
- }
186
- }
187
- return false
188
- }
189
-
190
212
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
191
213
 
214
+ /**
215
+ * @param {string} uri
216
+ * @param {import('./types/index').Options} [opts]
217
+ * @returns
218
+ */
192
219
  function parse (uri, opts) {
193
220
  const options = Object.assign({}, opts)
221
+ /** @type {import('./types/index').URIComponent} */
194
222
  const parsed = {
195
223
  scheme: undefined,
196
224
  userinfo: undefined,
@@ -200,9 +228,15 @@ function parse (uri, opts) {
200
228
  query: undefined,
201
229
  fragment: undefined
202
230
  }
203
- const gotEncoding = uri.indexOf('%') !== -1
231
+
204
232
  let isIP = false
205
- if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri
233
+ if (options.reference === 'suffix') {
234
+ if (options.scheme) {
235
+ uri = options.scheme + ':' + uri
236
+ } else {
237
+ uri = '//' + uri
238
+ }
239
+ }
206
240
 
207
241
  const matches = uri.match(URI_PARSE)
208
242
 
@@ -221,13 +255,12 @@ function parse (uri, opts) {
221
255
  parsed.port = matches[5]
222
256
  }
223
257
  if (parsed.host) {
224
- const ipv4result = normalizeIPv4(parsed.host)
225
- if (ipv4result.isIPV4 === false) {
226
- const ipv6result = normalizeIPv6(ipv4result.host)
258
+ const ipv4result = isIPv4(parsed.host)
259
+ if (ipv4result === false) {
260
+ const ipv6result = normalizeIPv6(parsed.host)
227
261
  parsed.host = ipv6result.host.toLowerCase()
228
262
  isIP = ipv6result.isIPV6
229
263
  } else {
230
- parsed.host = ipv4result.host
231
264
  isIP = true
232
265
  }
233
266
  }
@@ -247,7 +280,7 @@ function parse (uri, opts) {
247
280
  }
248
281
 
249
282
  // find scheme handler
250
- const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()]
283
+ const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme)
251
284
 
252
285
  // check if scheme can't handle IRIs
253
286
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
@@ -264,14 +297,16 @@ function parse (uri, opts) {
264
297
  }
265
298
 
266
299
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
267
- if (gotEncoding && parsed.scheme !== undefined) {
268
- parsed.scheme = unescape(parsed.scheme)
269
- }
270
- if (gotEncoding && parsed.host !== undefined) {
271
- parsed.host = unescape(parsed.host)
300
+ if (uri.indexOf('%') !== -1) {
301
+ if (parsed.scheme !== undefined) {
302
+ parsed.scheme = unescape(parsed.scheme)
303
+ }
304
+ if (parsed.host !== undefined) {
305
+ parsed.host = unescape(parsed.host)
306
+ }
272
307
  }
273
308
  if (parsed.path) {
274
- parsed.path = escape(unescape(parsed.path))
309
+ parsed.path = normalizePathEncoding(parsed.path)
275
310
  }
276
311
  if (parsed.fragment) {
277
312
  parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment))
@@ -292,7 +327,7 @@ const fastUri = {
292
327
  SCHEMES,
293
328
  normalize,
294
329
  resolve,
295
- resolveComponents,
330
+ resolveComponent,
296
331
  equal,
297
332
  serialize,
298
333
  parse
package/lib/schemes.js CHANGED
@@ -1,188 +1,267 @@
1
1
  'use strict'
2
2
 
3
- const UUID_REG = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu
3
+ const { isUUID } = require('./utils')
4
4
  const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
5
5
 
6
- function isSecure (wsComponents) {
7
- return typeof wsComponents.secure === 'boolean' ? wsComponents.secure : String(wsComponents.scheme).toLowerCase() === 'wss'
6
+ const supportedSchemeNames = /** @type {const} */ (['http', 'https', 'ws',
7
+ 'wss', 'urn', 'urn:uuid'])
8
+
9
+ /** @typedef {supportedSchemeNames[number]} SchemeName */
10
+
11
+ /**
12
+ * @param {string} name
13
+ * @returns {name is SchemeName}
14
+ */
15
+ function isValidSchemeName (name) {
16
+ return supportedSchemeNames.indexOf(/** @type {*} */ (name)) !== -1
8
17
  }
9
18
 
10
- function httpParse (components) {
11
- if (!components.host) {
12
- components.error = components.error || 'HTTP URIs must have a host.'
19
+ /**
20
+ * @callback SchemeFn
21
+ * @param {import('../types/index').URIComponent} component
22
+ * @param {import('../types/index').Options} options
23
+ * @returns {import('../types/index').URIComponent}
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object} SchemeHandler
28
+ * @property {SchemeName} scheme - The scheme name.
29
+ * @property {boolean} [domainHost] - Indicates if the scheme supports domain hosts.
30
+ * @property {SchemeFn} parse - Function to parse the URI component for this scheme.
31
+ * @property {SchemeFn} serialize - Function to serialize the URI component for this scheme.
32
+ * @property {boolean} [skipNormalize] - Indicates if normalization should be skipped for this scheme.
33
+ * @property {boolean} [absolutePath] - Indicates if the scheme uses absolute paths.
34
+ * @property {boolean} [unicodeSupport] - Indicates if the scheme supports Unicode.
35
+ */
36
+
37
+ /**
38
+ * @param {import('../types/index').URIComponent} wsComponent
39
+ * @returns {boolean}
40
+ */
41
+ function wsIsSecure (wsComponent) {
42
+ if (wsComponent.secure === true) {
43
+ return true
44
+ } else if (wsComponent.secure === false) {
45
+ return false
46
+ } else if (wsComponent.scheme) {
47
+ return (
48
+ wsComponent.scheme.length === 3 &&
49
+ (wsComponent.scheme[0] === 'w' || wsComponent.scheme[0] === 'W') &&
50
+ (wsComponent.scheme[1] === 's' || wsComponent.scheme[1] === 'S') &&
51
+ (wsComponent.scheme[2] === 's' || wsComponent.scheme[2] === 'S')
52
+ )
53
+ } else {
54
+ return false
13
55
  }
56
+ }
14
57
 
15
- return components
58
+ /** @type {SchemeFn} */
59
+ function httpParse (component) {
60
+ if (!component.host) {
61
+ component.error = component.error || 'HTTP URIs must have a host.'
62
+ }
63
+
64
+ return component
16
65
  }
17
66
 
18
- function httpSerialize (components) {
19
- const secure = String(components.scheme).toLowerCase() === 'https'
67
+ /** @type {SchemeFn} */
68
+ function httpSerialize (component) {
69
+ const secure = String(component.scheme).toLowerCase() === 'https'
20
70
 
21
71
  // normalize the default port
22
- if (components.port === (secure ? 443 : 80) || components.port === '') {
23
- components.port = undefined
72
+ if (component.port === (secure ? 443 : 80) || component.port === '') {
73
+ component.port = undefined
24
74
  }
25
75
 
26
76
  // normalize the empty path
27
- if (!components.path) {
28
- components.path = '/'
77
+ if (!component.path) {
78
+ component.path = '/'
29
79
  }
30
80
 
31
81
  // NOTE: We do not parse query strings for HTTP URIs
32
82
  // as WWW Form Url Encoded query strings are part of the HTML4+ spec,
33
83
  // and not the HTTP spec.
34
84
 
35
- return components
85
+ return component
36
86
  }
37
87
 
38
- function wsParse (wsComponents) {
88
+ /** @type {SchemeFn} */
89
+ function wsParse (wsComponent) {
39
90
  // indicate if the secure flag is set
40
- wsComponents.secure = isSecure(wsComponents)
91
+ wsComponent.secure = wsIsSecure(wsComponent)
41
92
 
42
93
  // construct resouce name
43
- wsComponents.resourceName = (wsComponents.path || '/') + (wsComponents.query ? '?' + wsComponents.query : '')
44
- wsComponents.path = undefined
45
- wsComponents.query = undefined
94
+ wsComponent.resourceName = (wsComponent.path || '/') + (wsComponent.query ? '?' + wsComponent.query : '')
95
+ wsComponent.path = undefined
96
+ wsComponent.query = undefined
46
97
 
47
- return wsComponents
98
+ return wsComponent
48
99
  }
49
100
 
50
- function wsSerialize (wsComponents) {
101
+ /** @type {SchemeFn} */
102
+ function wsSerialize (wsComponent) {
51
103
  // normalize the default port
52
- if (wsComponents.port === (isSecure(wsComponents) ? 443 : 80) || wsComponents.port === '') {
53
- wsComponents.port = undefined
104
+ if (wsComponent.port === (wsIsSecure(wsComponent) ? 443 : 80) || wsComponent.port === '') {
105
+ wsComponent.port = undefined
54
106
  }
55
107
 
56
108
  // ensure scheme matches secure flag
57
- if (typeof wsComponents.secure === 'boolean') {
58
- wsComponents.scheme = (wsComponents.secure ? 'wss' : 'ws')
59
- wsComponents.secure = undefined
109
+ if (typeof wsComponent.secure === 'boolean') {
110
+ wsComponent.scheme = (wsComponent.secure ? 'wss' : 'ws')
111
+ wsComponent.secure = undefined
60
112
  }
61
113
 
62
114
  // reconstruct path from resource name
63
- if (wsComponents.resourceName) {
64
- const [path, query] = wsComponents.resourceName.split('?')
65
- wsComponents.path = (path && path !== '/' ? path : undefined)
66
- wsComponents.query = query
67
- wsComponents.resourceName = undefined
115
+ if (wsComponent.resourceName) {
116
+ const [path, query] = wsComponent.resourceName.split('?')
117
+ wsComponent.path = (path && path !== '/' ? path : undefined)
118
+ wsComponent.query = query
119
+ wsComponent.resourceName = undefined
68
120
  }
69
121
 
70
122
  // forbid fragment component
71
- wsComponents.fragment = undefined
123
+ wsComponent.fragment = undefined
72
124
 
73
- return wsComponents
125
+ return wsComponent
74
126
  }
75
127
 
76
- function urnParse (urnComponents, options) {
77
- if (!urnComponents.path) {
78
- urnComponents.error = 'URN can not be parsed'
79
- return urnComponents
128
+ /** @type {SchemeFn} */
129
+ function urnParse (urnComponent, options) {
130
+ if (!urnComponent.path) {
131
+ urnComponent.error = 'URN can not be parsed'
132
+ return urnComponent
80
133
  }
81
- const matches = urnComponents.path.match(URN_REG)
134
+ const matches = urnComponent.path.match(URN_REG)
82
135
  if (matches) {
83
- const scheme = options.scheme || urnComponents.scheme || 'urn'
84
- urnComponents.nid = matches[1].toLowerCase()
85
- urnComponents.nss = matches[2]
86
- const urnScheme = `${scheme}:${options.nid || urnComponents.nid}`
87
- const schemeHandler = SCHEMES[urnScheme]
88
- urnComponents.path = undefined
136
+ const scheme = options.scheme || urnComponent.scheme || 'urn'
137
+ urnComponent.nid = matches[1].toLowerCase()
138
+ urnComponent.nss = matches[2]
139
+ const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`
140
+ const schemeHandler = getSchemeHandler(urnScheme)
141
+ urnComponent.path = undefined
89
142
 
90
143
  if (schemeHandler) {
91
- urnComponents = schemeHandler.parse(urnComponents, options)
144
+ urnComponent = schemeHandler.parse(urnComponent, options)
92
145
  }
93
146
  } else {
94
- urnComponents.error = urnComponents.error || 'URN can not be parsed.'
147
+ urnComponent.error = urnComponent.error || 'URN can not be parsed.'
95
148
  }
96
149
 
97
- return urnComponents
150
+ return urnComponent
98
151
  }
99
152
 
100
- function urnSerialize (urnComponents, options) {
101
- const scheme = options.scheme || urnComponents.scheme || 'urn'
102
- const nid = urnComponents.nid.toLowerCase()
153
+ /** @type {SchemeFn} */
154
+ function urnSerialize (urnComponent, options) {
155
+ if (urnComponent.nid === undefined) {
156
+ throw new Error('URN without nid cannot be serialized')
157
+ }
158
+ const scheme = options.scheme || urnComponent.scheme || 'urn'
159
+ const nid = urnComponent.nid.toLowerCase()
103
160
  const urnScheme = `${scheme}:${options.nid || nid}`
104
- const schemeHandler = SCHEMES[urnScheme]
161
+ const schemeHandler = getSchemeHandler(urnScheme)
105
162
 
106
163
  if (schemeHandler) {
107
- urnComponents = schemeHandler.serialize(urnComponents, options)
164
+ urnComponent = schemeHandler.serialize(urnComponent, options)
108
165
  }
109
166
 
110
- const uriComponents = urnComponents
111
- const nss = urnComponents.nss
112
- uriComponents.path = `${nid || options.nid}:${nss}`
167
+ const uriComponent = urnComponent
168
+ const nss = urnComponent.nss
169
+ uriComponent.path = `${nid || options.nid}:${nss}`
113
170
 
114
171
  options.skipEscape = true
115
- return uriComponents
172
+ return uriComponent
116
173
  }
117
174
 
118
- function urnuuidParse (urnComponents, options) {
119
- const uuidComponents = urnComponents
120
- uuidComponents.uuid = uuidComponents.nss
121
- uuidComponents.nss = undefined
175
+ /** @type {SchemeFn} */
176
+ function urnuuidParse (urnComponent, options) {
177
+ const uuidComponent = urnComponent
178
+ uuidComponent.uuid = uuidComponent.nss
179
+ uuidComponent.nss = undefined
122
180
 
123
- if (!options.tolerant && (!uuidComponents.uuid || !UUID_REG.test(uuidComponents.uuid))) {
124
- uuidComponents.error = uuidComponents.error || 'UUID is not valid.'
181
+ if (!options.tolerant && (!uuidComponent.uuid || !isUUID(uuidComponent.uuid))) {
182
+ uuidComponent.error = uuidComponent.error || 'UUID is not valid.'
125
183
  }
126
184
 
127
- return uuidComponents
185
+ return uuidComponent
128
186
  }
129
187
 
130
- function urnuuidSerialize (uuidComponents) {
131
- const urnComponents = uuidComponents
188
+ /** @type {SchemeFn} */
189
+ function urnuuidSerialize (uuidComponent) {
190
+ const urnComponent = uuidComponent
132
191
  // normalize UUID
133
- urnComponents.nss = (uuidComponents.uuid || '').toLowerCase()
134
- return urnComponents
192
+ urnComponent.nss = (uuidComponent.uuid || '').toLowerCase()
193
+ return urnComponent
135
194
  }
136
195
 
137
- const http = {
196
+ const http = /** @type {SchemeHandler} */ ({
138
197
  scheme: 'http',
139
198
  domainHost: true,
140
199
  parse: httpParse,
141
200
  serialize: httpSerialize
142
- }
201
+ })
143
202
 
144
- const https = {
203
+ const https = /** @type {SchemeHandler} */ ({
145
204
  scheme: 'https',
146
205
  domainHost: http.domainHost,
147
206
  parse: httpParse,
148
207
  serialize: httpSerialize
149
- }
208
+ })
150
209
 
151
- const ws = {
210
+ const ws = /** @type {SchemeHandler} */ ({
152
211
  scheme: 'ws',
153
212
  domainHost: true,
154
213
  parse: wsParse,
155
214
  serialize: wsSerialize
156
- }
215
+ })
157
216
 
158
- const wss = {
217
+ const wss = /** @type {SchemeHandler} */ ({
159
218
  scheme: 'wss',
160
219
  domainHost: ws.domainHost,
161
220
  parse: ws.parse,
162
221
  serialize: ws.serialize
163
- }
222
+ })
164
223
 
165
- const urn = {
224
+ const urn = /** @type {SchemeHandler} */ ({
166
225
  scheme: 'urn',
167
226
  parse: urnParse,
168
227
  serialize: urnSerialize,
169
228
  skipNormalize: true
170
- }
229
+ })
171
230
 
172
- const urnuuid = {
231
+ const urnuuid = /** @type {SchemeHandler} */ ({
173
232
  scheme: 'urn:uuid',
174
233
  parse: urnuuidParse,
175
234
  serialize: urnuuidSerialize,
176
235
  skipNormalize: true
177
- }
236
+ })
178
237
 
179
- const SCHEMES = {
238
+ const SCHEMES = /** @type {Record<SchemeName, SchemeHandler>} */ ({
180
239
  http,
181
240
  https,
182
241
  ws,
183
242
  wss,
184
243
  urn,
185
244
  'urn:uuid': urnuuid
245
+ })
246
+
247
+ Object.setPrototypeOf(SCHEMES, null)
248
+
249
+ /**
250
+ * @param {string|undefined} scheme
251
+ * @returns {SchemeHandler|undefined}
252
+ */
253
+ function getSchemeHandler (scheme) {
254
+ return (
255
+ scheme && (
256
+ SCHEMES[/** @type {SchemeName} */ (scheme)] ||
257
+ SCHEMES[/** @type {SchemeName} */(scheme.toLowerCase())])
258
+ ) ||
259
+ undefined
186
260
  }
187
261
 
188
- module.exports = SCHEMES
262
+ module.exports = {
263
+ wsIsSecure,
264
+ SCHEMES,
265
+ isValidSchemeName,
266
+ getSchemeHandler,
267
+ }