arn-browser 0.0.3 → 0.0.5

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.
Files changed (67) hide show
  1. package/README.md +3 -2
  2. package/package.json +31 -47
  3. package/src/all_routes/routeWithSuperagent.d.ts +67 -0
  4. package/src/all_routes/routeWithSuperagent.js +322 -0
  5. package/src/human-cursor/HumanCursor.js +448 -0
  6. package/src/human-cursor/bezier.js +248 -0
  7. package/src/human-cursor/index.d.ts +154 -0
  8. package/src/human-cursor/index.js +9 -0
  9. package/src/human-cursor/randomizer.js +149 -0
  10. package/src/human-cursor/tweening.js +260 -0
  11. package/src/index.d.ts +19 -0
  12. package/src/index.js +15 -0
  13. package/src/others/totp-generator.d.ts +15 -0
  14. package/src/others/totp-generator.js +86 -0
  15. package/src/utility/deleteDirectory.js +105 -0
  16. package/src/utility/launchBrowser.d.ts +248 -0
  17. package/src/utility/launchBrowser.js +899 -0
  18. package/src/utility/multilogin_token_manager.js +164 -0
  19. package/src/utility/playwright-helper.d.ts +61 -0
  20. package/src/utility/playwright-helper.js +129 -0
  21. package/src/utility/proxy-utility/custom-proxy.d.ts +93 -0
  22. package/src/utility/proxy-utility/custom-proxy.js +625 -0
  23. package/src/utility/proxy-utility/proxy-chain.d.ts +123 -0
  24. package/src/utility/proxy-utility/proxy-chain.js +337 -0
  25. package/src/utility/proxy-utility/proxy-helper.d.ts +91 -0
  26. package/src/utility/proxy-utility/proxy-helper.js +222 -0
  27. package/dist/__main__.d.ts +0 -2
  28. package/dist/__main__.js +0 -127
  29. package/dist/__version__.d.ts +0 -11
  30. package/dist/__version__.js +0 -16
  31. package/dist/addons.d.ts +0 -17
  32. package/dist/addons.js +0 -70
  33. package/dist/data-files/territoryInfo.xml +0 -2024
  34. package/dist/data-files/webgl_data.db +0 -0
  35. package/dist/exceptions.d.ts +0 -76
  36. package/dist/exceptions.js +0 -153
  37. package/dist/fingerprints.d.ts +0 -4
  38. package/dist/fingerprints.js +0 -82
  39. package/dist/index.d.ts +0 -3
  40. package/dist/index.js +0 -3
  41. package/dist/ip.d.ts +0 -25
  42. package/dist/ip.js +0 -90
  43. package/dist/locale.d.ts +0 -26
  44. package/dist/locale.js +0 -280
  45. package/dist/mappings/browserforge.config.d.ts +0 -47
  46. package/dist/mappings/browserforge.config.js +0 -72
  47. package/dist/mappings/fonts.config.d.ts +0 -6
  48. package/dist/mappings/fonts.config.js +0 -822
  49. package/dist/mappings/warnings.config.d.ts +0 -16
  50. package/dist/mappings/warnings.config.js +0 -28
  51. package/dist/pkgman.d.ts +0 -62
  52. package/dist/pkgman.js +0 -347
  53. package/dist/server.d.ts +0 -6
  54. package/dist/server.js +0 -9
  55. package/dist/sync_api.d.ts +0 -7
  56. package/dist/sync_api.js +0 -27
  57. package/dist/utils.d.ts +0 -88
  58. package/dist/utils.js +0 -500
  59. package/dist/virtdisplay.d.ts +0 -20
  60. package/dist/virtdisplay.js +0 -123
  61. package/dist/warnings.d.ts +0 -4
  62. package/dist/warnings.js +0 -30
  63. package/dist/webgl/db-compat.d.ts +0 -9
  64. package/dist/webgl/db-compat.js +0 -44
  65. package/dist/webgl/sample.d.ts +0 -19
  66. package/dist/webgl/sample.js +0 -85
  67. /package/{LICENSE.md → LICENSE} +0 -0
@@ -0,0 +1,448 @@
1
+ /**
2
+ * HumanCursor - Human-like cursor movements for Playwright
3
+ * Drop-in replacement for Page with human cursor for click/fill/type/hover
4
+ */
5
+
6
+ import { HumanizeMouseTrajectory } from './bezier.js';
7
+ import { generateRandomCurveParameters, calculateAbsoluteOffset } from './randomizer.js';
8
+
9
+ /**
10
+ * Random integer between min and max (inclusive)
11
+ */
12
+ function randInt(min, max) {
13
+ return Math.floor(Math.random() * (max - min + 1)) + min;
14
+ }
15
+
16
+ /**
17
+ * Sleep for specified milliseconds
18
+ */
19
+ function sleep(ms) {
20
+ return new Promise(resolve => setTimeout(resolve, ms));
21
+ }
22
+
23
+ /**
24
+ * Creates a HumanLocator that wraps a Playwright Locator
25
+ * with human-like cursor movement for actions
26
+ */
27
+ function createHumanLocator(cursor, locator) {
28
+ // Methods that need human cursor movement
29
+ const humanMethods = {
30
+ async click(options = {}) {
31
+ // If humanize is disabled, use native Playwright click
32
+ if (!cursor.config.humanize) {
33
+ return await locator.click(options);
34
+ }
35
+
36
+ await cursor._moveToLocator(locator, options);
37
+ const clicks = options.clickCount || 1;
38
+ const button = options.button || 'left';
39
+
40
+ for (let i = 0; i < clicks; i++) {
41
+ if (options.delay) {
42
+ await cursor._page.mouse.down({ button });
43
+ await sleep(options.delay);
44
+ await cursor._page.mouse.up({ button });
45
+ } else {
46
+ await cursor._page.mouse.click(
47
+ cursor.originCoordinates[0],
48
+ cursor.originCoordinates[1],
49
+ { button }
50
+ );
51
+ }
52
+ if (i < clicks - 1) {
53
+ await sleep(randInt(170, 280));
54
+ }
55
+ }
56
+ },
57
+
58
+ async dblclick(options = {}) {
59
+ if (!cursor.config.humanize) {
60
+ return await locator.dblclick(options);
61
+ }
62
+ await cursor._moveToLocator(locator, options);
63
+ await cursor._page.mouse.dblclick(
64
+ cursor.originCoordinates[0],
65
+ cursor.originCoordinates[1]
66
+ );
67
+ },
68
+
69
+ async fill(value, options = {}) {
70
+ if (!cursor.config.humanize) {
71
+ return await locator.fill(value, options);
72
+ }
73
+ await this.click(options);
74
+ await cursor._page.keyboard.press('Control+A');
75
+ await sleep(randInt(50, 100));
76
+ await cursor._type(value, options);
77
+ },
78
+
79
+ async type(text, options = {}) {
80
+ if (!cursor.config.humanize) {
81
+ return await locator.type(text, options);
82
+ }
83
+ await this.click(options);
84
+ await cursor._type(text, options);
85
+ },
86
+
87
+ async hover(options = {}) {
88
+ if (!cursor.config.humanize) {
89
+ return await locator.hover(options);
90
+ }
91
+ await cursor._moveToLocator(locator, options);
92
+ },
93
+
94
+ async press(key, options = {}) {
95
+ if (!cursor.config.humanize) {
96
+ return await locator.press(key, options);
97
+ }
98
+ await this.click(options);
99
+ await cursor._page.keyboard.press(key);
100
+ },
101
+
102
+ // Chainable methods that return new HumanLocators
103
+ filter(options) {
104
+ return createHumanLocator(cursor, locator.filter(options));
105
+ },
106
+ first() {
107
+ return createHumanLocator(cursor, locator.first());
108
+ },
109
+ last() {
110
+ return createHumanLocator(cursor, locator.last());
111
+ },
112
+ nth(index) {
113
+ return createHumanLocator(cursor, locator.nth(index));
114
+ },
115
+ locator(selector) {
116
+ return createHumanLocator(cursor, locator.locator(selector));
117
+ },
118
+ getByText(text, options) {
119
+ return createHumanLocator(cursor, locator.getByText(text, options));
120
+ },
121
+ getByRole(role, options) {
122
+ return createHumanLocator(cursor, locator.getByRole(role, options));
123
+ },
124
+ getByLabel(text, options) {
125
+ return createHumanLocator(cursor, locator.getByLabel(text, options));
126
+ },
127
+ getByPlaceholder(text, options) {
128
+ return createHumanLocator(cursor, locator.getByPlaceholder(text, options));
129
+ },
130
+ getByTestId(testId) {
131
+ return createHumanLocator(cursor, locator.getByTestId(testId));
132
+ }
133
+ };
134
+
135
+ // Use Proxy to pass through all other methods to original locator
136
+ return new Proxy(locator, {
137
+ get(target, prop) {
138
+ // Human methods override
139
+ if (prop in humanMethods) {
140
+ return humanMethods[prop];
141
+ }
142
+ // Pass through to original locator
143
+ const value = target[prop];
144
+ if (typeof value === 'function') {
145
+ return value.bind(target);
146
+ }
147
+ return value;
148
+ }
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Create a HumanCursor that acts as a drop-in replacement for Page
154
+ * All page methods work, but click/fill/type/hover use human cursor
155
+ *
156
+ * @param {import('playwright').Page} page - Playwright Page object
157
+ * @param {Object} options - Configuration options
158
+ * @returns {import('playwright').Page & HumanCursorMethods}
159
+ */
160
+ export function createCursor(page, options = {}) {
161
+ const config = {
162
+ humanize: options.humanize ?? true, // Enable human cursor by default
163
+ maxTime: options.maxTime ?? 1.5,
164
+ minTime: options.minTime ?? 0.5,
165
+ showCursor: options.showCursor ?? true // Show cursor by default
166
+ };
167
+
168
+ // Internal state
169
+ const cursor = {
170
+ _page: page,
171
+ originCoordinates: [0, 0],
172
+ config,
173
+
174
+ async _moveToLocator(locator, options = {}) {
175
+ await locator.scrollIntoViewIfNeeded();
176
+ const bounds = await locator.boundingBox();
177
+
178
+ if (!bounds) {
179
+ throw new Error('Element not found or not visible');
180
+ }
181
+
182
+ let xOffset, yOffset;
183
+ if (options.position) {
184
+ xOffset = options.position.x;
185
+ yOffset = options.position.y;
186
+ } else {
187
+ xOffset = bounds.width * (randInt(25, 75) / 100);
188
+ yOffset = bounds.height * (randInt(25, 75) / 100);
189
+ }
190
+
191
+ const destination = [bounds.x + xOffset, bounds.y + yOffset];
192
+ return await this._moveToPoint(destination, options);
193
+ },
194
+
195
+ async _moveToPoint(destination, options = {}) {
196
+ const viewport = page.viewportSize() || { width: 1280, height: 720 };
197
+
198
+ const params = generateRandomCurveParameters(
199
+ viewport,
200
+ this.originCoordinates,
201
+ destination
202
+ );
203
+
204
+ if (options.steady) {
205
+ params.offsetBoundaryX = 10;
206
+ params.offsetBoundaryY = 10;
207
+ params.distortionMean = 1.2;
208
+ params.distortionStdDev = 1.2;
209
+ params.distortionFrequency = 1;
210
+ }
211
+
212
+ const curve = new HumanizeMouseTrajectory(
213
+ this.originCoordinates,
214
+ destination,
215
+ {
216
+ offsetBoundaryX: params.offsetBoundaryX,
217
+ offsetBoundaryY: params.offsetBoundaryY,
218
+ knotsCount: params.knotsCount,
219
+ distortionMean: params.distortionMean,
220
+ distortionStdDev: params.distortionStdDev,
221
+ distortionFrequency: params.distortionFrequency,
222
+ tweening: params.tween,
223
+ targetPoints: params.targetPoints
224
+ }
225
+ );
226
+
227
+ const distance = Math.sqrt(
228
+ Math.pow(destination[0] - this.originCoordinates[0], 2) +
229
+ Math.pow(destination[1] - this.originCoordinates[1], 2)
230
+ );
231
+ const totalTime = Math.min(
232
+ config.maxTime,
233
+ Math.max(config.minTime, distance / 1000)
234
+ ) * 1000;
235
+ const baseDelay = totalTime / curve.points.length;
236
+ const speedMultiplier = options.moveSpeed || 1.0;
237
+
238
+ for (const point of curve.points) {
239
+ await page.mouse.move(point[0], point[1]);
240
+ if (baseDelay > 0) {
241
+ await sleep((baseDelay / speedMultiplier) + Math.random() * 2);
242
+ }
243
+ }
244
+
245
+ this.originCoordinates = destination;
246
+ return destination;
247
+ },
248
+
249
+ async _type(text, options = {}) {
250
+ const minDelay = options.minDelay ?? 50;
251
+ const maxDelay = options.maxDelay ?? 150;
252
+
253
+ for (const char of text) {
254
+ await page.keyboard.type(char);
255
+ await sleep(randInt(minDelay, maxDelay));
256
+ }
257
+ },
258
+
259
+ async showCursor() {
260
+ await page.evaluate(() => {
261
+ if (window.__humanCursorDot) return;
262
+
263
+ let cursor;
264
+ function updateCursor(event) {
265
+ const x = event.clientX;
266
+ const y = event.clientY;
267
+
268
+ if (!cursor) {
269
+ // Create cursor container
270
+ cursor = document.createElement('div');
271
+ cursor.id = '__human_cursor';
272
+ cursor.style.cssText = `
273
+ position: fixed;
274
+ pointer-events: none;
275
+ z-index: 2147483647;
276
+ transition: left 0.05s ease-out, top 0.05s ease-out;
277
+ `;
278
+
279
+ // Outer ring (animated)
280
+ const ring = document.createElement('div');
281
+ ring.style.cssText = `
282
+ width: 28px;
283
+ height: 28px;
284
+ border: 2px solid rgba(59, 130, 246, 0.6);
285
+ border-radius: 50%;
286
+ box-sizing: border-box;
287
+ position: absolute;
288
+ top: -14px;
289
+ left: -14px;
290
+ animation: pulse 1.5s ease-in-out infinite;
291
+ `;
292
+
293
+ // Inner dot
294
+ const dot = document.createElement('div');
295
+ dot.style.cssText = `
296
+ width: 10px;
297
+ height: 10px;
298
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
299
+ border-radius: 50%;
300
+ position: absolute;
301
+ top: -5px;
302
+ left: -5px;
303
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.8), 0 0 4px rgba(255, 255, 255, 0.9);
304
+ `;
305
+
306
+ // Click indicator
307
+ const clickRing = document.createElement('div');
308
+ clickRing.id = '__human_cursor_click';
309
+ clickRing.style.cssText = `
310
+ width: 36px;
311
+ height: 36px;
312
+ border: 2px solid rgba(239, 68, 68, 0.8);
313
+ border-radius: 50%;
314
+ box-sizing: border-box;
315
+ position: absolute;
316
+ top: -18px;
317
+ left: -18px;
318
+ opacity: 0;
319
+ transform: scale(0.5);
320
+ transition: opacity 0.15s, transform 0.15s;
321
+ `;
322
+
323
+ // Add pulse animation
324
+ const style = document.createElement('style');
325
+ style.textContent = `
326
+ @keyframes pulse {
327
+ 0%, 100% { transform: scale(1); opacity: 0.6; }
328
+ 50% { transform: scale(1.2); opacity: 0.3; }
329
+ }
330
+ `;
331
+ document.head.appendChild(style);
332
+
333
+ cursor.appendChild(ring);
334
+ cursor.appendChild(dot);
335
+ cursor.appendChild(clickRing);
336
+ document.body.appendChild(cursor);
337
+
338
+ // Click effect
339
+ document.addEventListener('mousedown', () => {
340
+ const click = document.getElementById('__human_cursor_click');
341
+ if (click) {
342
+ click.style.opacity = '1';
343
+ click.style.transform = 'scale(1)';
344
+ }
345
+ });
346
+ document.addEventListener('mouseup', () => {
347
+ const click = document.getElementById('__human_cursor_click');
348
+ if (click) {
349
+ click.style.opacity = '0';
350
+ click.style.transform = 'scale(0.5)';
351
+ }
352
+ });
353
+ }
354
+
355
+ cursor.style.left = x + 'px';
356
+ cursor.style.top = y + 'px';
357
+ }
358
+
359
+ document.addEventListener('mousemove', updateCursor);
360
+ window.__humanCursorDot = true;
361
+ });
362
+ }
363
+ };
364
+
365
+ // Methods that return HumanLocators
366
+ const locatorMethods = {
367
+ locator(selector) {
368
+ return createHumanLocator(cursor, page.locator(selector));
369
+ },
370
+ getByText(text, opts) {
371
+ return createHumanLocator(cursor, page.getByText(text, opts));
372
+ },
373
+ getByRole(role, opts) {
374
+ return createHumanLocator(cursor, page.getByRole(role, opts));
375
+ },
376
+ getByLabel(text, opts) {
377
+ return createHumanLocator(cursor, page.getByLabel(text, opts));
378
+ },
379
+ getByPlaceholder(text, opts) {
380
+ return createHumanLocator(cursor, page.getByPlaceholder(text, opts));
381
+ },
382
+ getByAltText(text, opts) {
383
+ return createHumanLocator(cursor, page.getByAltText(text, opts));
384
+ },
385
+ getByTitle(text, opts) {
386
+ return createHumanLocator(cursor, page.getByTitle(text, opts));
387
+ },
388
+ getByTestId(testId) {
389
+ return createHumanLocator(cursor, page.getByTestId(testId));
390
+ }
391
+ };
392
+
393
+ // Wrap goto to auto-show cursor after navigation
394
+ const originalGoto = page.goto.bind(page);
395
+ const wrappedGoto = async (...args) => {
396
+ const result = await originalGoto(...args);
397
+ if (config.showCursor) {
398
+ await cursor.showCursor();
399
+ }
400
+ return result;
401
+ };
402
+
403
+ // Use Proxy to create drop-in page replacement
404
+ return new Proxy(page, {
405
+ get(target, prop) {
406
+ // Override goto to auto-show cursor
407
+ if (prop === 'goto') {
408
+ return wrappedGoto;
409
+ }
410
+ // Locator methods return HumanLocators
411
+ if (prop in locatorMethods) {
412
+ return locatorMethods[prop];
413
+ }
414
+ // Expose cursor utilities
415
+ if (prop === 'showCursor') {
416
+ return cursor.showCursor.bind(cursor);
417
+ }
418
+ if (prop === 'originCoordinates') {
419
+ return cursor.originCoordinates;
420
+ }
421
+ if (prop === 'setPosition') {
422
+ return (x, y) => { cursor.originCoordinates = [x, y]; };
423
+ }
424
+ // Pass through everything else to original page
425
+ const value = target[prop];
426
+ if (typeof value === 'function') {
427
+ return value.bind(target);
428
+ }
429
+ return value;
430
+ }
431
+ });
432
+ }
433
+
434
+ // Legacy class export for backwards compatibility
435
+ export class HumanCursor {
436
+ constructor(page, options = {}) {
437
+ this.page = page;
438
+ this.originCoordinates = [0, 0];
439
+ this.options = {
440
+ maxTime: options.maxTime ?? 1.5,
441
+ minTime: options.minTime ?? 0.5,
442
+ showCursor: options.showCursor ?? true // Show cursor by default
443
+ };
444
+
445
+ // Return proxy instead of class instance
446
+ return createCursor(page, options);
447
+ }
448
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Bezier curve calculator and human-like trajectory generator
3
+ * Ported from Python HumanCursor library
4
+ */
5
+
6
+ import { easeOutQuad } from './tweening.js';
7
+
8
+ /**
9
+ * Calculates the binomial coefficient "n choose k"
10
+ * @param {number} n
11
+ * @param {number} k
12
+ * @returns {number}
13
+ */
14
+ function binomial(n, k) {
15
+ let result = 1;
16
+ for (let i = 1; i <= k; i++) {
17
+ result = result * (n - i + 1) / i;
18
+ }
19
+ return result;
20
+ }
21
+
22
+ /**
23
+ * Calculate the i-th component of a Bernstein polynomial of degree n
24
+ * @param {number} x - Point on curve (0 to 1)
25
+ * @param {number} i - Index
26
+ * @param {number} n - Degree
27
+ * @returns {number}
28
+ */
29
+ function bernsteinPolynomialPoint(x, i, n) {
30
+ return binomial(n, i) * Math.pow(x, i) * Math.pow(1 - x, n - i);
31
+ }
32
+
33
+ /**
34
+ * Returns a function that calculates a point on the Bezier curve
35
+ * @param {Array<[number, number]>} points - Control points
36
+ * @returns {function(number): [number, number]}
37
+ */
38
+ function bernsteinPolynomial(points) {
39
+ return function (t) {
40
+ const n = points.length - 1;
41
+ let x = 0;
42
+ let y = 0;
43
+ for (let i = 0; i <= n; i++) {
44
+ const bern = bernsteinPolynomialPoint(t, i, n);
45
+ x += points[i][0] * bern;
46
+ y += points[i][1] * bern;
47
+ }
48
+ return [x, y];
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Calculate n points on a Bezier curve
54
+ * @param {number} n - Number of points to generate
55
+ * @param {Array<[number, number]>} points - Control points
56
+ * @returns {Array<[number, number]>}
57
+ */
58
+ function calculatePointsInCurve(n, points) {
59
+ const curvePoints = [];
60
+ const getBezierPoint = bernsteinPolynomial(points);
61
+ for (let i = 0; i < n; i++) {
62
+ const t = i / (n - 1);
63
+ curvePoints.push(getBezierPoint(t));
64
+ }
65
+ return curvePoints;
66
+ }
67
+
68
+ /**
69
+ * Check if a value is numeric
70
+ * @param {*} val
71
+ * @returns {boolean}
72
+ */
73
+ function isNumeric(val) {
74
+ return typeof val === 'number' && !isNaN(val);
75
+ }
76
+
77
+ /**
78
+ * Check if list contains valid points
79
+ * @param {Array} list
80
+ * @returns {boolean}
81
+ */
82
+ function isListOfPoints(list) {
83
+ if (!Array.isArray(list)) return false;
84
+ return list.every(p =>
85
+ Array.isArray(p) &&
86
+ p.length === 2 &&
87
+ isNumeric(p[0]) &&
88
+ isNumeric(p[1])
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Random integer between min and max (inclusive)
94
+ * @param {number} min
95
+ * @param {number} max
96
+ * @returns {number}
97
+ */
98
+ function randInt(min, max) {
99
+ return Math.floor(Math.random() * (max - min + 1)) + min;
100
+ }
101
+
102
+ /**
103
+ * Random float with normal distribution
104
+ * @param {number} mean
105
+ * @param {number} stdDev
106
+ * @returns {number}
107
+ */
108
+ function randomNormal(mean, stdDev) {
109
+ // Box-Muller transform
110
+ const u1 = Math.random();
111
+ const u2 = Math.random();
112
+ const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
113
+ return z0 * stdDev + mean;
114
+ }
115
+
116
+ /**
117
+ * Generates human-like mouse trajectory using Bezier curves
118
+ */
119
+ export class HumanizeMouseTrajectory {
120
+ /**
121
+ * @param {[number, number]} fromPoint - Starting point [x, y]
122
+ * @param {[number, number]} toPoint - Ending point [x, y]
123
+ * @param {Object} options - Curve generation options
124
+ */
125
+ constructor(fromPoint, toPoint, options = {}) {
126
+ this.fromPoint = fromPoint;
127
+ this.toPoint = toPoint;
128
+ this.points = this.generateCurve(options);
129
+ }
130
+
131
+ /**
132
+ * Generates the curve based on options
133
+ * @param {Object} options
134
+ * @returns {Array<[number, number]>}
135
+ */
136
+ generateCurve(options) {
137
+ const offsetBoundaryX = options.offsetBoundaryX ?? 80;
138
+ const offsetBoundaryY = options.offsetBoundaryY ?? 80;
139
+ const leftBoundary = (options.leftBoundary ?? Math.min(this.fromPoint[0], this.toPoint[0])) - offsetBoundaryX;
140
+ const rightBoundary = (options.rightBoundary ?? Math.max(this.fromPoint[0], this.toPoint[0])) + offsetBoundaryX;
141
+ const downBoundary = (options.downBoundary ?? Math.min(this.fromPoint[1], this.toPoint[1])) - offsetBoundaryY;
142
+ const upBoundary = (options.upBoundary ?? Math.max(this.fromPoint[1], this.toPoint[1])) + offsetBoundaryY;
143
+ const knotsCount = options.knotsCount ?? 2;
144
+ const distortionMean = options.distortionMean ?? 1;
145
+ const distortionStdDev = options.distortionStdDev ?? 1;
146
+ const distortionFrequency = options.distortionFrequency ?? 0.5;
147
+ const tween = options.tweening ?? easeOutQuad;
148
+ const targetPoints = options.targetPoints ?? 100;
149
+
150
+ const internalKnots = this.generateInternalKnots(
151
+ leftBoundary, rightBoundary, downBoundary, upBoundary, knotsCount
152
+ );
153
+ let points = this.generatePoints(internalKnots);
154
+ points = this.distortPoints(points, distortionMean, distortionStdDev, distortionFrequency);
155
+ points = this.tweenPoints(points, tween, targetPoints);
156
+ return points;
157
+ }
158
+
159
+ /**
160
+ * Generates random internal knots for the curve
161
+ * @param {number} lBoundary
162
+ * @param {number} rBoundary
163
+ * @param {number} dBoundary
164
+ * @param {number} uBoundary
165
+ * @param {number} knotsCount
166
+ * @returns {Array<[number, number]>}
167
+ */
168
+ generateInternalKnots(lBoundary, rBoundary, dBoundary, uBoundary, knotsCount) {
169
+ if (knotsCount < 0) knotsCount = 0;
170
+ if (lBoundary > rBoundary) {
171
+ throw new Error('leftBoundary must be less than or equal to rightBoundary');
172
+ }
173
+ if (dBoundary > uBoundary) {
174
+ throw new Error('downBoundary must be less than or equal to upperBoundary');
175
+ }
176
+
177
+ const knots = [];
178
+ for (let i = 0; i < knotsCount; i++) {
179
+ const x = randInt(Math.floor(lBoundary), Math.floor(rBoundary));
180
+ const y = randInt(Math.floor(dBoundary), Math.floor(uBoundary));
181
+ knots.push([x, y]);
182
+ }
183
+ return knots;
184
+ }
185
+
186
+ /**
187
+ * Generates points from Bezier curve
188
+ * @param {Array<[number, number]>} knots
189
+ * @returns {Array<[number, number]>}
190
+ */
191
+ generatePoints(knots) {
192
+ const midPtsCnt = Math.max(
193
+ Math.abs(this.fromPoint[0] - this.toPoint[0]),
194
+ Math.abs(this.fromPoint[1] - this.toPoint[1]),
195
+ 2
196
+ );
197
+ const allKnots = [this.fromPoint, ...knots, this.toPoint];
198
+ return calculatePointsInCurve(Math.floor(midPtsCnt), allKnots);
199
+ }
200
+
201
+ /**
202
+ * Distorts points to add human-like imperfections
203
+ * @param {Array<[number, number]>} points
204
+ * @param {number} distortionMean
205
+ * @param {number} distortionStdDev
206
+ * @param {number} distortionFrequency
207
+ * @returns {Array<[number, number]>}
208
+ */
209
+ distortPoints(points, distortionMean, distortionStdDev, distortionFrequency) {
210
+ if (distortionFrequency < 0 || distortionFrequency > 1) {
211
+ throw new Error('distortionFrequency must be in range [0, 1]');
212
+ }
213
+
214
+ const distorted = [points[0]];
215
+ for (let i = 1; i < points.length - 1; i++) {
216
+ const [x, y] = points[i];
217
+ const delta = Math.random() < distortionFrequency
218
+ ? randomNormal(distortionMean, distortionStdDev)
219
+ : 0;
220
+ distorted.push([x, y + delta]);
221
+ }
222
+ distorted.push(points[points.length - 1]);
223
+ return distorted;
224
+ }
225
+
226
+ /**
227
+ * Applies tweening to points
228
+ * @param {Array<[number, number]>} points
229
+ * @param {function(number): number} tween
230
+ * @param {number} targetPoints
231
+ * @returns {Array<[number, number]>}
232
+ */
233
+ tweenPoints(points, tween, targetPoints) {
234
+ if (targetPoints < 2) {
235
+ throw new Error('targetPoints must be >= 2');
236
+ }
237
+
238
+ const result = [];
239
+ for (let i = 0; i < targetPoints; i++) {
240
+ const t = i / (targetPoints - 1);
241
+ const index = Math.floor(tween(t) * (points.length - 1));
242
+ result.push(points[index]);
243
+ }
244
+ return result;
245
+ }
246
+ }
247
+
248
+ export { calculatePointsInCurve, bernsteinPolynomial };