exacturi 1.0.0
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/LICENSE.md +7 -0
- package/README.md +2 -0
- package/dist/cjs/errors.d.ts +11 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +39 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/exacturi.d.ts +91 -0
- package/dist/cjs/exacturi.d.ts.map +1 -0
- package/dist/cjs/exacturi.js +332 -0
- package/dist/cjs/exacturi.js.map +1 -0
- package/dist/cjs/index.d.ts +29 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/parts.d.ts +15 -0
- package/dist/cjs/parts.d.ts.map +1 -0
- package/dist/cjs/parts.js +3 -0
- package/dist/cjs/parts.js.map +1 -0
- package/dist/cjs/reuri.d.ts +92 -0
- package/dist/cjs/reuri.d.ts.map +1 -0
- package/dist/cjs/reuri.js +345 -0
- package/dist/cjs/reuri.js.map +1 -0
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +45 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/errors.d.ts +11 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +28 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/exacturi.d.ts +91 -0
- package/dist/esm/exacturi.d.ts.map +1 -0
- package/dist/esm/exacturi.js +328 -0
- package/dist/esm/exacturi.js.map +1 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/parts.d.ts +15 -0
- package/dist/esm/parts.d.ts.map +1 -0
- package/dist/esm/parts.js +2 -0
- package/dist/esm/parts.js.map +1 -0
- package/dist/esm/reuri.d.ts +92 -0
- package/dist/esm/reuri.d.ts.map +1 -0
- package/dist/esm/reuri.js +341 -0
- package/dist/esm/reuri.js.map +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +41 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/lib/errors.d.ts +14 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +52 -0
- package/dist/lib/index.d.ts +31 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +20 -0
- package/dist/lib/parts.d.ts +18 -0
- package/dist/lib/parts.d.ts.map +1 -0
- package/dist/lib/parts.js +3 -0
- package/dist/lib/payload.d.ts +17 -0
- package/dist/lib/payload.d.ts.map +1 -0
- package/dist/lib/payload.js +126 -0
- package/dist/lib/reuri.d.ts +127 -0
- package/dist/lib/reuri.d.ts.map +1 -0
- package/dist/lib/reuri.js +457 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +26 -0
- package/dist/test/basic.test.js +319 -0
- package/dist/test/basic.test.js.map +1 -0
- package/dist/test/extras.test.js +77 -0
- package/dist/test/extras.test.js.map +1 -0
- package/dist/test/matching.test.js +80 -0
- package/dist/test/matching.test.js.map +1 -0
- package/package.json +101 -0
- package/src/errors.ts +31 -0
- package/src/exacturi.ts +444 -0
- package/src/index.ts +37 -0
- package/src/parts.ts +29 -0
- package/src/utils.ts +42 -0
package/src/exacturi.ts
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { ExactUriError, inputMustBeString, inputMustBeUrl, portMustBeNumeric } from "./errors.js";
|
|
2
|
+
import { hostnameContains } from "./utils.js";
|
|
3
|
+
import {
|
|
4
|
+
ExactUri_CompoundNames,
|
|
5
|
+
ExactUri_Parts,
|
|
6
|
+
ExactUri_PathKey,
|
|
7
|
+
ExactUri_Protocol
|
|
8
|
+
} from "./parts.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Represents a URI and allows performing operations and inspecting it.
|
|
12
|
+
*
|
|
13
|
+
* This class is immutable.
|
|
14
|
+
*/
|
|
15
|
+
export class ExactUri {
|
|
16
|
+
// Lots of params needed here
|
|
17
|
+
// eslint-disable-next-line max-params
|
|
18
|
+
constructor(
|
|
19
|
+
private _protocol: ExactUri_Protocol | undefined,
|
|
20
|
+
private _hostname: string | undefined,
|
|
21
|
+
private _path: string,
|
|
22
|
+
private _query: string | undefined,
|
|
23
|
+
private _hash: string | undefined,
|
|
24
|
+
private _parsed: string | undefined,
|
|
25
|
+
private _port: string | undefined
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
static is(other: unknown): other is ExactUri {
|
|
29
|
+
return !!(other && typeof other === "object" && other.constructor === ExactUri);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
segments(): string[];
|
|
33
|
+
segments(overwrite: string[]): ExactUri;
|
|
34
|
+
segments(overwrite?: string[]) {
|
|
35
|
+
if (arguments.length === 0) {
|
|
36
|
+
return this._path.split("/");
|
|
37
|
+
}
|
|
38
|
+
const clone = this._noCopyClone();
|
|
39
|
+
clone._path = overwrite!.join("/");
|
|
40
|
+
return clone;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
merge(other: string): ExactUri;
|
|
44
|
+
merge(other: ExactUri): ExactUri;
|
|
45
|
+
merge(other: ExactUri | string): ExactUri {
|
|
46
|
+
if (typeof other === "string") {
|
|
47
|
+
return this.merge(ExactUri.parse(other));
|
|
48
|
+
}
|
|
49
|
+
return this.parts({
|
|
50
|
+
hash: other._hash || this._hash,
|
|
51
|
+
hostname: other._hostname || this._hostname,
|
|
52
|
+
path: other._path || this._path,
|
|
53
|
+
port: other._port || this._port,
|
|
54
|
+
protocol: other._protocol || this._protocol,
|
|
55
|
+
query: other._query || this._query
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pick(...keys: ExactUri_PathKey[]): ExactUri {
|
|
60
|
+
let result = ExactUri.empty();
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
result = result.part(key, this.part(key as ExactUri_CompoundNames));
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
parts(): ExactUri_Parts;
|
|
68
|
+
parts(overwrite: Partial<ExactUri_Parts>): ExactUri;
|
|
69
|
+
parts(overwrite?: Partial<ExactUri_Parts>): any {
|
|
70
|
+
if (arguments.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
protocol: this._protocol,
|
|
73
|
+
hostname: this._hostname,
|
|
74
|
+
port: this._port,
|
|
75
|
+
path: this._path,
|
|
76
|
+
query: this._query,
|
|
77
|
+
hash: this._hash
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (!overwrite) {
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
const clone = this._noCopyClone();
|
|
84
|
+
clone._protocol = "protocol" in overwrite ? overwrite.protocol : clone._protocol;
|
|
85
|
+
clone._hostname = "hostname" in overwrite ? overwrite.hostname : clone._hostname;
|
|
86
|
+
clone._port = "port" in overwrite ? overwrite.port : clone._port;
|
|
87
|
+
clone._path = "path" in overwrite ? (overwrite.path as any) : clone._path;
|
|
88
|
+
clone._query = "query" in overwrite ? overwrite.query : clone._query;
|
|
89
|
+
clone._hash = "hash" in overwrite ? overwrite.hash : clone._hash;
|
|
90
|
+
return clone;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
part(key: ExactUri_PathKey): string | undefined;
|
|
94
|
+
part(key: ExactUri_PathKey, value: string | undefined): ExactUri;
|
|
95
|
+
part(key: ExactUri_PathKey, value?: string): any {
|
|
96
|
+
if (arguments.length === 1) {
|
|
97
|
+
const result = (this as any)[key]();
|
|
98
|
+
return result != null ? String(result) : undefined;
|
|
99
|
+
}
|
|
100
|
+
return (this as any)[key](value as any);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
address(address: string | undefined | null): ExactUri;
|
|
104
|
+
address(): string | undefined;
|
|
105
|
+
address(address?: string): string | ExactUri | undefined {
|
|
106
|
+
if (arguments.length === 1) {
|
|
107
|
+
const parsed = ExactUri.parse(address!);
|
|
108
|
+
return parsed.parts({
|
|
109
|
+
protocol: this._protocol
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return this.parts({
|
|
113
|
+
protocol: undefined
|
|
114
|
+
}).href;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
resource(resource: string | undefined | null): ExactUri;
|
|
118
|
+
resource(): string | undefined;
|
|
119
|
+
resource(resource?: string): string | ExactUri | undefined {
|
|
120
|
+
if (arguments.length === 1) {
|
|
121
|
+
const parsed = ExactUri.parse(resource!);
|
|
122
|
+
return parsed.parts({
|
|
123
|
+
protocol: this._protocol,
|
|
124
|
+
hostname: this._hostname,
|
|
125
|
+
port: this._port
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return this.parts({
|
|
129
|
+
protocol: undefined,
|
|
130
|
+
hostname: undefined,
|
|
131
|
+
port: undefined
|
|
132
|
+
}).href;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
origin(origin: string | undefined | null): ExactUri;
|
|
136
|
+
origin(): string | undefined;
|
|
137
|
+
origin(origin?: string): string | ExactUri | undefined {
|
|
138
|
+
if (arguments.length === 1) {
|
|
139
|
+
const parsed = ExactUri.parse(origin!);
|
|
140
|
+
return this.parts({
|
|
141
|
+
protocol: parsed._protocol,
|
|
142
|
+
hostname: parsed._hostname,
|
|
143
|
+
port: parsed._port
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const parts = this.parts();
|
|
147
|
+
return (
|
|
148
|
+
ExactUri.empty().parts({
|
|
149
|
+
protocol: parts.protocol,
|
|
150
|
+
hostname: parts.hostname,
|
|
151
|
+
port: parts.port
|
|
152
|
+
}).href || undefined
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
segment(index: number): string;
|
|
157
|
+
segment(index: number, value: string): ExactUri;
|
|
158
|
+
segment(index: number, value?: string) {
|
|
159
|
+
if (arguments.length === 0) {
|
|
160
|
+
throw new ExactUriError(`You must provide an index.`);
|
|
161
|
+
}
|
|
162
|
+
if (arguments.length === 1) {
|
|
163
|
+
return this._path.split("/")[index];
|
|
164
|
+
}
|
|
165
|
+
const segments = this.segments();
|
|
166
|
+
segments[index] = value!;
|
|
167
|
+
return this.segments(segments);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Gets the path. It can't be nullish.
|
|
171
|
+
path(): string;
|
|
172
|
+
// Sets the path to `path`. Null is converted to `""`.
|
|
173
|
+
path(path: string | undefined | null): ExactUri;
|
|
174
|
+
path(path?: string) {
|
|
175
|
+
if (arguments.length === 0) {
|
|
176
|
+
return this._path;
|
|
177
|
+
}
|
|
178
|
+
const clone = this._noCopyClone();
|
|
179
|
+
clone._path = path ?? "";
|
|
180
|
+
return clone;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
port(): number | undefined;
|
|
184
|
+
port(raw: true): string | undefined;
|
|
185
|
+
port(port: number | undefined | string | null): ExactUri;
|
|
186
|
+
port(port?: number | true | string | null) {
|
|
187
|
+
if (arguments.length === 0) {
|
|
188
|
+
return this._port != null ? Number(this._port) : undefined;
|
|
189
|
+
}
|
|
190
|
+
if (port === true) {
|
|
191
|
+
return this._port;
|
|
192
|
+
}
|
|
193
|
+
const clone = this._noCopyClone();
|
|
194
|
+
clone._port = port != null ? String(port) : undefined;
|
|
195
|
+
return clone;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Gets the protocol.
|
|
199
|
+
protocol(): ExactUri_Protocol | undefined;
|
|
200
|
+
// Sets the protocol to `proto`.
|
|
201
|
+
protocol(proto: ExactUri_Protocol | null | undefined): ExactUri;
|
|
202
|
+
protocol(proto?: ExactUri_Protocol | null | undefined) {
|
|
203
|
+
if (arguments.length === 0) {
|
|
204
|
+
return this._protocol;
|
|
205
|
+
}
|
|
206
|
+
const clone = this._noCopyClone();
|
|
207
|
+
clone._protocol = proto ?? undefined;
|
|
208
|
+
return clone;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Gets the hostname and port.
|
|
212
|
+
host(): string | undefined;
|
|
213
|
+
// Sets the hostname and port.
|
|
214
|
+
host(host: string | undefined | null): ExactUri;
|
|
215
|
+
host(host?: string): any {
|
|
216
|
+
if (arguments.length === 0) {
|
|
217
|
+
return (
|
|
218
|
+
ExactUri.empty().hostname(this._hostname).port(this._port).href.slice(2) ||
|
|
219
|
+
undefined
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (host == null) {
|
|
223
|
+
return this.hostname(null).port(null);
|
|
224
|
+
}
|
|
225
|
+
const parsed = ExactUri.parse(`//${host}`);
|
|
226
|
+
return this.hostname(parsed._hostname).port(parsed._port);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Gets the hostname, which is the domain without the port.
|
|
230
|
+
hostname(): string | undefined;
|
|
231
|
+
// Sets the hostname to `hostname`.
|
|
232
|
+
hostname(hostname: string | undefined | null): ExactUri;
|
|
233
|
+
hostname(hostname?: string) {
|
|
234
|
+
if (arguments.length === 0) {
|
|
235
|
+
return this._hostname;
|
|
236
|
+
}
|
|
237
|
+
const clone = this._noCopyClone();
|
|
238
|
+
clone._hostname = hostname ?? undefined;
|
|
239
|
+
return clone;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Gets the query string.
|
|
243
|
+
query(): string | undefined;
|
|
244
|
+
// Sets the query string to `query`.
|
|
245
|
+
query(query: string | null | undefined): ExactUri;
|
|
246
|
+
query(query?: string) {
|
|
247
|
+
if (arguments.length === 0) {
|
|
248
|
+
return this._query;
|
|
249
|
+
}
|
|
250
|
+
const clone = this._noCopyClone();
|
|
251
|
+
clone._query = query ?? undefined;
|
|
252
|
+
return clone;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Gets the hash, aka the fragment, without the #.
|
|
256
|
+
hash(): string;
|
|
257
|
+
// Sets the hash of this to `hash`, and returns `this`.
|
|
258
|
+
hash(hash: string | null | undefined): ExactUri;
|
|
259
|
+
hash(hash?: string) {
|
|
260
|
+
if (arguments.length === 0) {
|
|
261
|
+
return this._hash;
|
|
262
|
+
}
|
|
263
|
+
const clone = this._noCopyClone();
|
|
264
|
+
clone._hash = hash ?? undefined;
|
|
265
|
+
return clone;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private _noCopyClone() {
|
|
269
|
+
return new ExactUri(
|
|
270
|
+
this._protocol,
|
|
271
|
+
this._hostname,
|
|
272
|
+
this._path,
|
|
273
|
+
this._query,
|
|
274
|
+
this._hash,
|
|
275
|
+
this._parsed,
|
|
276
|
+
this._port
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Does this URL have a web protocol?
|
|
281
|
+
get isWeb() {
|
|
282
|
+
return !!this._protocol && webProtocols.has(this._protocol);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Gets a clone of this instance.
|
|
287
|
+
*/
|
|
288
|
+
clone(): ExactUri {
|
|
289
|
+
return new ExactUri(
|
|
290
|
+
this._protocol,
|
|
291
|
+
this._hostname,
|
|
292
|
+
this._path,
|
|
293
|
+
this._query,
|
|
294
|
+
this._hash,
|
|
295
|
+
this._parsed,
|
|
296
|
+
this._port
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
static empty() {
|
|
301
|
+
return new ExactUri(undefined, undefined, "", undefined, undefined, undefined, undefined);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Parses `input` as a URI. Errors on bad input.
|
|
305
|
+
static parse(input: null | undefined): ExactUri;
|
|
306
|
+
static parse(input: string | ExactUri | Partial<ExactUri_Parts>): ExactUri;
|
|
307
|
+
static parse(input: any) {
|
|
308
|
+
if (input == null) {
|
|
309
|
+
return ExactUri.empty();
|
|
310
|
+
}
|
|
311
|
+
if (input instanceof ExactUri) {
|
|
312
|
+
return input;
|
|
313
|
+
}
|
|
314
|
+
if (typeof input === "object") {
|
|
315
|
+
return ExactUri.empty().parts(input);
|
|
316
|
+
}
|
|
317
|
+
if (typeof input !== "string") {
|
|
318
|
+
throw inputMustBeString(input);
|
|
319
|
+
}
|
|
320
|
+
const r = urlRegexp.exec(input);
|
|
321
|
+
if (!r) {
|
|
322
|
+
throw inputMustBeUrl(input);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const [, proto, hostname, port, path, query, fragment] = r;
|
|
326
|
+
if (port != null && (Number.isNaN(+port) || port === "")) {
|
|
327
|
+
throw portMustBeNumeric(input);
|
|
328
|
+
}
|
|
329
|
+
return new ExactUri(
|
|
330
|
+
proto?.trim(),
|
|
331
|
+
hostname?.trim(),
|
|
332
|
+
path?.trim() ?? "",
|
|
333
|
+
query?.trim(),
|
|
334
|
+
fragment?.trim(),
|
|
335
|
+
input,
|
|
336
|
+
port
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Checks if this URI (CHILD) matches another URI (PARENT). The parent can be a string,
|
|
342
|
+
* an {@link ExactUri}, or an object describing the parts of a URI -- an {@link ExactUri_Parts}.
|
|
343
|
+
*
|
|
344
|
+
* CHILD is considered to be a child of PARENT if:
|
|
345
|
+
* 1. If PARENT has a protocol, it must equal CHILD protocol.
|
|
346
|
+
* 2. If PARENT has a hostname, it must be a parent of CHILD's hostname.
|
|
347
|
+
* 3. If PARENT has a port, it must equal CHILD's port.
|
|
348
|
+
* 4. If PARENT has a path, it must be a parent of CHILD's path.
|
|
349
|
+
* 5. If PARENT has a query, it must equal CHILD's query.
|
|
350
|
+
* 6. If PARENT has a hash, it must equal CHILD's hash.
|
|
351
|
+
* @param parent The parent URI to check against.
|
|
352
|
+
*/
|
|
353
|
+
matches(parent: Partial<ExactUri_Parts> | ExactUri | string): boolean {
|
|
354
|
+
parent = ExactUri.parse(parent);
|
|
355
|
+
if (parent._protocol && parent._protocol !== this._protocol) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
if (parent._hostname && !hostnameContains(this._hostname, parent._hostname)) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (parent._port && parent._port !== this._port) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
if (parent._query && parent._query !== this._query) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (parent._hash && parent._hash !== this._hash) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const parentSegments = parent.segments();
|
|
373
|
+
const childSegments = this.segments();
|
|
374
|
+
for (let i = 0; i < parentSegments.length; i++) {
|
|
375
|
+
if (parentSegments[i] !== childSegments[i]) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Checks if this URI equals another URI, which can be given as a string, an
|
|
384
|
+
* {@link ExactUri}, or an object describing the parts of a URI -- an {@link ExactUri_Parts}.
|
|
385
|
+
* @param other The other URI to check against.
|
|
386
|
+
*/
|
|
387
|
+
equal(other: string | ExactUri | Partial<ExactUri_Parts>): boolean {
|
|
388
|
+
if (this === other) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
other = ExactUri.parse(other);
|
|
392
|
+
const myParts = this.parts();
|
|
393
|
+
const otherParts = other.parts();
|
|
394
|
+
for (const key of ["protocol", "hostname", "port", "path", "query", "hash"] as const) {
|
|
395
|
+
if (myParts[key] !== otherParts[key]) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// An artificial URI is one that can't have been parsed from a string due to an invalid combination of components.
|
|
403
|
+
get isArtificial(): boolean {
|
|
404
|
+
return !ExactUri.parse(this._print()).equal(this);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Does this URL have a non-web protocol?
|
|
409
|
+
*/
|
|
410
|
+
get isNonWeb() {
|
|
411
|
+
return !!this._protocol && !webProtocols.has(this._protocol);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Gets the full URI.
|
|
416
|
+
*/
|
|
417
|
+
get href() {
|
|
418
|
+
return this._print();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private _print() {
|
|
422
|
+
let { _protocol, _hostname, _query, _hash } = this;
|
|
423
|
+
const { _port } = this;
|
|
424
|
+
_protocol = _protocol != null ? `${_protocol}:` : _protocol;
|
|
425
|
+
_hostname = _hostname != null ? `//${_hostname}` : "";
|
|
426
|
+
_query = _query != null ? `?${_query}` : "";
|
|
427
|
+
_hash = _hash != null ? `#${_hash}` : "";
|
|
428
|
+
const strPort = _port != null ? `:${_port}` : "";
|
|
429
|
+
return [_protocol, _hostname, strPort, this._path, _query, _hash].join("");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
get [Symbol.toStringTag]() {
|
|
433
|
+
return this.href;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
toString() {
|
|
437
|
+
return this.href;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const urlRegexp =
|
|
442
|
+
/^(?:([a-zA-Z][A-Za-z\d+-.]*):)?(?:\/\/([^/?#:]*)(?::([^/?#]*))?)?([^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/;
|
|
443
|
+
|
|
444
|
+
const webProtocols = new Set(["http", "ws", "https", "wss"]);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ExactUri } from "./exacturi.js";
|
|
2
|
+
|
|
3
|
+
import { ExactUri_Parts } from "./parts.js";
|
|
4
|
+
|
|
5
|
+
export { ExactUri } from "./exacturi.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates an empty ExactUri object.
|
|
9
|
+
*/
|
|
10
|
+
export function exacturi(): ExactUri;
|
|
11
|
+
/**
|
|
12
|
+
* Creates an ExactUri object from an {@link ExactUri_Parts} describing the parts of the URI.
|
|
13
|
+
* @param parts The parts of the URI.
|
|
14
|
+
*/
|
|
15
|
+
export function exacturi(parts: Partial<ExactUri_Parts>): ExactUri;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the same ExactUri object that was passed in.
|
|
18
|
+
* @param uri
|
|
19
|
+
*/
|
|
20
|
+
export function exacturi(uri: ExactUri): ExactUri;
|
|
21
|
+
/**
|
|
22
|
+
* Parses a string into an ExactUri object.
|
|
23
|
+
* @param uri The string to parse.
|
|
24
|
+
*/
|
|
25
|
+
export function exacturi(uri: string): ExactUri;
|
|
26
|
+
export function exacturi(uri?: ExactUri | string | Partial<ExactUri_Parts>): ExactUri {
|
|
27
|
+
return ExactUri.parse(uri as any);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns true if the given value is an ExactUri object.
|
|
32
|
+
* @param uri The value to check.
|
|
33
|
+
*/
|
|
34
|
+
export function isExactUri(uri: unknown): uri is ExactUri {
|
|
35
|
+
return ExactUri.is(uri);
|
|
36
|
+
}
|
|
37
|
+
export { ExactUri_Protocol } from "./parts.js";
|
package/src/parts.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Possible URL protocols.
|
|
2
|
+
export type ExactUri_Protocol =
|
|
3
|
+
| "http"
|
|
4
|
+
| "https"
|
|
5
|
+
| "ws"
|
|
6
|
+
| "wss"
|
|
7
|
+
| "about"
|
|
8
|
+
| "chrome"
|
|
9
|
+
| "chrome-extension"
|
|
10
|
+
| "data"
|
|
11
|
+
| "blob"
|
|
12
|
+
| "javascript"
|
|
13
|
+
| "file"
|
|
14
|
+
| "ftp"
|
|
15
|
+
| string;
|
|
16
|
+
export type ExactUri_CompoundNames = "host" | "origin" | "resource" | "address";
|
|
17
|
+
export type ExactUri_PathKey = keyof ExactUri_Parts | ExactUri_CompoundNames;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Describes the parts of a URI.
|
|
21
|
+
*/
|
|
22
|
+
export interface ExactUri_Parts {
|
|
23
|
+
protocol?: ExactUri_Protocol;
|
|
24
|
+
hostname?: string;
|
|
25
|
+
port?: string;
|
|
26
|
+
path: string;
|
|
27
|
+
query?: string;
|
|
28
|
+
hash?: string;
|
|
29
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function hostnameContains(child: string | undefined, parent: string) {
|
|
2
|
+
if (!child) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
const parentParts = parent.split(".").reverse();
|
|
6
|
+
const childParts = child.split(".").reverse();
|
|
7
|
+
if (parentParts.length > childParts.length) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < parentParts.length; i++) {
|
|
12
|
+
if (!parentParts[i]) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (childParts[i] !== parentParts[i]) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function equal(a: any, b: any): boolean {
|
|
23
|
+
if (!a || typeof a !== "object" || !b || typeof b !== "object") {
|
|
24
|
+
return a === b;
|
|
25
|
+
}
|
|
26
|
+
const aProto = Object.getPrototypeOf(a);
|
|
27
|
+
const bProto = Object.getPrototypeOf(b);
|
|
28
|
+
if (aProto !== bProto) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const aKeys = Object.keys(a);
|
|
32
|
+
const bKeys = Object.keys(b);
|
|
33
|
+
if (aKeys.length !== bKeys.length) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
for (const key of aKeys) {
|
|
37
|
+
if (a[key] !== b[key]) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|