@usertour/helpers 0.0.18 → 0.0.21

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.
@@ -1,68 +1,85 @@
1
1
  "use strict";
2
2
 
3
- // src/conditions/url.ts
4
- var parseUrl = (url) => {
5
- const urlPatterns = url.match(/^(([a-z\d]+):\/\/)?([^/?#]+)?(\/[^?#]*)?(\?([^#]*))?(#.*)?$/i);
6
- if (!urlPatterns) {
3
+ // src/conditions/url-v2.ts
4
+ function parseUrl(url) {
5
+ const match = url.match(/^(([a-z\d]+):\/\/)?([^/?#]+)?(\/[^?#]*)?(\?([^#]*))?(#.*)?$/i);
6
+ if (!match)
7
7
  return null;
8
- }
9
- const [, , scheme = "", domain = "", path = "", , query = "", fragment = ""] = urlPatterns;
10
- return { scheme, domain, path, query, fragment };
11
- };
12
- var replaceWildcard = (input, s1, s2) => {
13
- const withSpecialWords = replaceSpecialWords(input);
14
- const withWildcard = withSpecialWords.replace(/\\\*/g, `${s1}*`);
15
- if (!s2) {
16
- return withWildcard;
17
- }
18
- return withWildcard.replace(/:[a-z0-9_]+/g, `[^${s2}]+`);
19
- };
20
- var replaceSpecialWords = (str) => {
8
+ const [, , scheme, domain, path, , query, fragment] = match;
9
+ return {
10
+ scheme: scheme || "",
11
+ domain: domain || "",
12
+ path: path || "",
13
+ query: query || "",
14
+ fragment: fragment || ""
15
+ };
16
+ }
17
+ function escapeRegex(str) {
21
18
  return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
22
- };
23
- var parsePattern = (pattern) => {
24
- if (!pattern || !pattern.trim()) {
25
- return null;
19
+ }
20
+ function processUrlPattern(pattern, wildcardReplacement, paramReplacement) {
21
+ let processed = escapeRegex(pattern);
22
+ processed = processed.replace(/\\\*/g, `${wildcardReplacement}*`);
23
+ if (paramReplacement) {
24
+ processed = processed.replace(/:[a-zA-Z0-9_]+/g, `[^${paramReplacement}]+`);
26
25
  }
27
- const _pattern = parseUrl(pattern);
28
- if (!_pattern) {
29
- console.error("Invalid URL pattern:", pattern);
26
+ return processed;
27
+ }
28
+ function urlPatternToRegex(pattern) {
29
+ const parsed = parseUrl(pattern);
30
+ if (!parsed) {
30
31
  return null;
31
32
  }
32
- const { scheme, domain, path, query, fragment } = _pattern;
33
- const _scheme = scheme ? replaceSpecialWords(scheme) : "[a-z\\d]+";
34
- const _domain = domain ? replaceWildcard(domain, "[^/]", ".") : "[^/]*";
35
- const _fragment = fragment ? replaceWildcard(fragment, ".", "/") : "(#.*)?";
36
- const _path = path ? replaceWildcard(path, "[^?#]", "/") : "/[^?#]*";
37
- let _query = "(\\?[^#]*)?";
33
+ const { scheme, domain, path, query, fragment } = parsed;
34
+ const schemePattern = scheme ? escapeRegex(scheme) : "[a-z\\d]+";
35
+ const domainPattern = domain ? processUrlPattern(domain, "[^/]", ".") : "[^/]*";
36
+ const portPattern = "(:\\d+)?";
37
+ const pathPattern = path ? processUrlPattern(path, "[^?#]", "/") : "/[^?#]*";
38
+ let queryPattern;
38
39
  if (query) {
40
+ queryPattern = "";
39
41
  new URLSearchParams(query).forEach((value, key) => {
40
- const _str = value === "" ? "=?" : value === "*" ? "(=[^&#]*)?" : `=${replaceWildcard(value, "[^#]")}`;
41
- _query += `(?=.*[?&]${replaceSpecialWords(key)}${_str}([&#]|$))`;
42
+ let valuePattern;
43
+ if (value === "") {
44
+ valuePattern = "=?";
45
+ } else if (value === "*") {
46
+ valuePattern = "(=[^&#]*)?";
47
+ } else {
48
+ const encodedValue = value.split(/\*/g).map((part) => encodeURI(part)).join("*");
49
+ valuePattern = `=${processUrlPattern(encodedValue, "[^#]")}`;
50
+ }
51
+ queryPattern += `(?=.*[?&]${escapeRegex(key)}${valuePattern}([&#]|$))`;
42
52
  });
43
- _query += "\\?[^#]*";
53
+ queryPattern += "\\?[^#]*";
54
+ } else {
55
+ queryPattern = "(\\?[^#]*)?";
44
56
  }
45
- return new RegExp(`^${_scheme}://${_domain}(:\\d+)?${_path}${_query}${_fragment}$`);
46
- };
47
- var isMatchUrlPattern = (_url, includes, excludes) => {
48
- const isMatchIncludesConditions = includes.length > 0 ? includes.some((_include) => {
49
- const reg = parsePattern(_include);
50
- if (reg) {
51
- return reg.test(_url);
52
- }
57
+ const fragmentPattern = fragment ? processUrlPattern(fragment, ".", "/") : "(#.*)?";
58
+ return new RegExp(
59
+ `^${schemePattern}://${domainPattern}${portPattern}${pathPattern}${queryPattern}${fragmentPattern}$`
60
+ );
61
+ }
62
+ function matchUrlPattern(urlPattern, url) {
63
+ if (urlPattern.includes.length === 0 && urlPattern.excludes.length === 0) {
53
64
  return false;
54
- }) : true;
55
- const isMatchExcludesConditions = excludes.length > 0 ? excludes.some((_exclude) => {
56
- const reg = parsePattern(_exclude);
57
- if (reg) {
58
- return reg.test(_url);
59
- }
60
- return false;
61
- }) : false;
62
- return isMatchIncludesConditions && !isMatchExcludesConditions;
65
+ }
66
+ const matchesInclude = urlPattern.includes.length === 0 || urlPattern.includes.some((includePattern) => {
67
+ const regex = urlPatternToRegex(includePattern);
68
+ return regex && url.match(regex);
69
+ });
70
+ const matchesExclude = urlPattern.excludes.some((excludePattern) => {
71
+ const regex = urlPatternToRegex(excludePattern);
72
+ return regex && url.match(regex);
73
+ });
74
+ return matchesInclude && !matchesExclude;
75
+ }
76
+
77
+ // src/conditions/url.ts
78
+ var isMatchUrlPattern = (_url, includes, excludes) => {
79
+ return matchUrlPattern({ includes, excludes }, _url);
63
80
  };
64
81
  var evaluateUrlCondition = (rules, url) => {
65
- const { excludes, includes } = rules.data;
82
+ const { excludes = [], includes = [] } = rules.data || {};
66
83
  return isMatchUrlPattern(url, includes, excludes);
67
84
  };
68
85
 
@@ -121,7 +138,7 @@ describe("URL Condition Evaluation", () => {
121
138
  const url = "https://example.com/dashboard";
122
139
  expect(evaluateUrlCondition(rules, url)).toBe(true);
123
140
  });
124
- test("should return true when no includes specified (matches all)", () => {
141
+ test("should return false when no includes and no excludes specified", () => {
125
142
  const rules = {
126
143
  id: "condition-5",
127
144
  type: "condition",
@@ -132,7 +149,7 @@ describe("URL Condition Evaluation", () => {
132
149
  }
133
150
  };
134
151
  const url = "https://example.com/dashboard";
135
- expect(evaluateUrlCondition(rules, url)).toBe(true);
152
+ expect(evaluateUrlCondition(rules, url)).toBe(false);
136
153
  });
137
154
  test("should return false when URL matches exclude even with no includes", () => {
138
155
  const rules = {
@@ -147,6 +164,97 @@ describe("URL Condition Evaluation", () => {
147
164
  const url = "https://example.com/admin";
148
165
  expect(evaluateUrlCondition(rules, url)).toBe(false);
149
166
  });
167
+ test("should handle URLs with ports", () => {
168
+ const rules = {
169
+ id: "condition-7",
170
+ type: "condition",
171
+ operators: "and",
172
+ data: {
173
+ includes: ["https://example.com:8080/dashboard"],
174
+ excludes: []
175
+ }
176
+ };
177
+ const url = "https://example.com:8080/dashboard";
178
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
179
+ });
180
+ test("should handle URLs with complex query parameters", () => {
181
+ const rules = {
182
+ id: "condition-8",
183
+ type: "condition",
184
+ operators: "and",
185
+ data: {
186
+ includes: ["https://example.com/dashboard?tab=overview&user=123"],
187
+ excludes: []
188
+ }
189
+ };
190
+ const url = "https://example.com/dashboard?tab=overview&user=123";
191
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
192
+ });
193
+ test("should handle URLs with special characters in query parameters", () => {
194
+ const rules = {
195
+ id: "condition-9",
196
+ type: "condition",
197
+ operators: "and",
198
+ data: {
199
+ includes: ["https://example.com/search?q=hello+world"],
200
+ excludes: []
201
+ }
202
+ };
203
+ const url = "https://example.com/search?q=hello+world";
204
+ expect(evaluateUrlCondition(rules, url)).toBe(false);
205
+ });
206
+ test("should handle URLs with fragments", () => {
207
+ const rules = {
208
+ id: "condition-10",
209
+ type: "condition",
210
+ operators: "and",
211
+ data: {
212
+ includes: ["https://example.com/dashboard#overview"],
213
+ excludes: []
214
+ }
215
+ };
216
+ const url = "https://example.com/dashboard#overview";
217
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
218
+ });
219
+ test("should handle wildcard patterns in query parameters", () => {
220
+ const rules = {
221
+ id: "condition-11",
222
+ type: "condition",
223
+ operators: "and",
224
+ data: {
225
+ includes: ["https://example.com/dashboard?tab=*"],
226
+ excludes: []
227
+ }
228
+ };
229
+ const url = "https://example.com/dashboard?tab=overview";
230
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
231
+ });
232
+ test("should handle empty query parameter values", () => {
233
+ const rules = {
234
+ id: "condition-12",
235
+ type: "condition",
236
+ operators: "and",
237
+ data: {
238
+ includes: ["https://example.com/dashboard?tab="],
239
+ excludes: []
240
+ }
241
+ };
242
+ const url = "https://example.com/dashboard?tab=";
243
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
244
+ });
245
+ test("should handle multiple query parameters with wildcards", () => {
246
+ const rules = {
247
+ id: "condition-13",
248
+ type: "condition",
249
+ operators: "and",
250
+ data: {
251
+ includes: ["https://example.com/dashboard?tab=*&user=*"],
252
+ excludes: []
253
+ }
254
+ };
255
+ const url = "https://example.com/dashboard?tab=overview&user=123";
256
+ expect(evaluateUrlCondition(rules, url)).toBe(true);
257
+ });
150
258
  });
151
259
  describe("isMatchUrlPattern", () => {
152
260
  test("should match exact URL", () => {
@@ -193,7 +301,7 @@ describe("URL Condition Evaluation", () => {
193
301
  const url = "https://example.com/dashboard";
194
302
  const includes = [];
195
303
  const excludes = [];
196
- expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
304
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
197
305
  });
198
306
  test("should handle empty excludes", () => {
199
307
  const url = "https://example.com/dashboard";
@@ -255,5 +363,157 @@ describe("URL Condition Evaluation", () => {
255
363
  const excludes = ["https://example.com/admin", "https://example.com/settings"];
256
364
  expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
257
365
  });
366
+ test("should handle URLs with special regex characters", () => {
367
+ const url = "https://example.com/dashboard?param=value+with+spaces";
368
+ const includes = ["https://example.com/dashboard?param=value+with+spaces"];
369
+ const excludes = [];
370
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
371
+ });
372
+ test("should handle URLs with encoded characters", () => {
373
+ const url = "https://example.com/search?q=hello%20world";
374
+ const includes = ["https://example.com/search?q=hello%20world"];
375
+ const excludes = [];
376
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
377
+ });
378
+ test("should handle URLs with multiple query parameters in different order", () => {
379
+ const url = "https://example.com/dashboard?tab=overview&user=123&theme=dark";
380
+ const includes = ["https://example.com/dashboard?user=123&tab=overview"];
381
+ const excludes = [];
382
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
383
+ });
384
+ test("should handle URLs with empty path", () => {
385
+ const url = "https://example.com/";
386
+ const includes = ["https://example.com/"];
387
+ const excludes = [];
388
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
389
+ });
390
+ test("should handle URLs with root path wildcard", () => {
391
+ const url = "https://example.com/";
392
+ const includes = ["https://example.com/*"];
393
+ const excludes = [];
394
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
395
+ });
396
+ test("should handle URLs with subdomain wildcards", () => {
397
+ const url = "https://app.example.com/dashboard";
398
+ const includes = ["https://*.example.com/dashboard"];
399
+ const excludes = [];
400
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
401
+ });
402
+ test("should handle URLs with port numbers", () => {
403
+ const url = "https://example.com:3000/dashboard";
404
+ const includes = ["https://example.com:3000/dashboard"];
405
+ const excludes = [];
406
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
407
+ });
408
+ test("should handle URLs with port wildcards", () => {
409
+ const url = "https://example.com:8080/dashboard";
410
+ const includes = ["https://example.com:*/dashboard"];
411
+ const excludes = [];
412
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
413
+ });
414
+ test("should handle complex fragment patterns", () => {
415
+ const url = "https://example.com/dashboard#section-1";
416
+ const includes = ["https://example.com/dashboard#section-*"];
417
+ const excludes = [];
418
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
419
+ });
420
+ test("should handle URLs with special characters in path", () => {
421
+ const url = "https://example.com/user/123/profile";
422
+ const includes = ["https://example.com/user/*/profile"];
423
+ const excludes = [];
424
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
425
+ });
426
+ test("should handle URLs with query parameters containing special characters", () => {
427
+ const url = "https://example.com/search?q=hello&filter=active";
428
+ const includes = ["https://example.com/search?q=*&filter=active"];
429
+ const excludes = [];
430
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
431
+ });
432
+ test("should handle URLs with empty query parameter values", () => {
433
+ const url = "https://example.com/dashboard?tab=";
434
+ const includes = ["https://example.com/dashboard?tab="];
435
+ const excludes = [];
436
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
437
+ });
438
+ test("should handle URLs with query parameters that have no value", () => {
439
+ const url = "https://example.com/dashboard?tab";
440
+ const includes = ["https://example.com/dashboard?tab"];
441
+ const excludes = [];
442
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
443
+ });
444
+ test("should handle URLs with multiple fragments", () => {
445
+ const url = "https://example.com/dashboard#section-1#subsection-a";
446
+ const includes = ["https://example.com/dashboard#section-1#*"];
447
+ const excludes = [];
448
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
449
+ });
450
+ test("should handle URLs with IPv4 addresses", () => {
451
+ const url = "https://192.168.1.1/dashboard";
452
+ const includes = ["https://192.168.1.1/dashboard"];
453
+ const excludes = [];
454
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
455
+ });
456
+ test("should handle URLs with IPv6 addresses", () => {
457
+ const url = "https://[2001:db8::1]/dashboard";
458
+ const includes = ["https://[2001:db8::1]/dashboard"];
459
+ const excludes = [];
460
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
461
+ });
462
+ test("should handle URLs with mixed case in domain", () => {
463
+ const url = "https://Example.COM/dashboard";
464
+ const includes = ["https://example.com/dashboard"];
465
+ const excludes = [];
466
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
467
+ });
468
+ test("should handle URLs with mixed case in path", () => {
469
+ const url = "https://example.com/Dashboard";
470
+ const includes = ["https://example.com/dashboard"];
471
+ const excludes = [];
472
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
473
+ });
474
+ });
475
+ describe("Implementation Compatibility Tests", () => {
476
+ test("should handle query parameters with special characters consistently", () => {
477
+ const url = "https://example.com/search?q=hello+world&filter=active";
478
+ const includes = ["https://example.com/search?q=hello+world"];
479
+ const excludes = [];
480
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
481
+ });
482
+ test("should handle query parameters with encoded characters consistently", () => {
483
+ const url = "https://example.com/search?q=hello%20world";
484
+ const includes = ["https://example.com/search?q=hello%20world"];
485
+ const excludes = [];
486
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
487
+ });
488
+ test("should handle query parameters with wildcards consistently", () => {
489
+ const url = "https://example.com/search?q=hello&filter=*";
490
+ const includes = ["https://example.com/search?q=*&filter=active"];
491
+ const excludes = [];
492
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
493
+ });
494
+ test("should handle empty includes and excludes consistently", () => {
495
+ const url = "https://example.com/dashboard";
496
+ const includes = [];
497
+ const excludes = [];
498
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(false);
499
+ });
500
+ test("should handle only excludes consistently", () => {
501
+ const url = "https://example.com/dashboard";
502
+ const includes = [];
503
+ const excludes = ["https://example.com/admin"];
504
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
505
+ });
506
+ test("should handle complex nested paths consistently", () => {
507
+ const url = "https://example.com/app/dashboard/user/123/settings";
508
+ const includes = ["https://example.com/app/*/user/*/settings"];
509
+ const excludes = [];
510
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
511
+ });
512
+ test("should handle URLs with multiple query parameters consistently", () => {
513
+ const url = "https://example.com/dashboard?tab=overview&user=123&theme=dark&lang=en";
514
+ const includes = ["https://example.com/dashboard?tab=overview&user=*&theme=dark"];
515
+ const excludes = [];
516
+ expect(isMatchUrlPattern(url, includes, excludes)).toBe(true);
517
+ });
258
518
  });
259
519
  });