@smpx/koa-request 0.2.3 → 0.2.7
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/Request.js +123 -7
- package/package.json +7 -7
- package/.lh/.gitignore.json +0 -18
- package/.lh/.lhignore +0 -6
- package/.lh/IPRange.js.json +0 -18
- package/.lh/Request.js.json +0 -98
- package/.lh/postinstall.js.json +0 -22
package/Request.js
CHANGED
|
@@ -240,6 +240,24 @@ class Request {
|
|
|
240
240
|
return paramValue;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Get parameter as a string (from query or body)
|
|
245
|
+
* @param {string} key
|
|
246
|
+
* @param {number} defaultValue
|
|
247
|
+
* @returns {number}
|
|
248
|
+
*/
|
|
249
|
+
paramStr(key, defaultValue = '') {
|
|
250
|
+
const value = this.param(key, null);
|
|
251
|
+
if (value === null) return defaultValue;
|
|
252
|
+
return String(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get parameter as an integer (from query or body)
|
|
257
|
+
* @param {string} key
|
|
258
|
+
* @param {number} defaultValue
|
|
259
|
+
* @returns {number}
|
|
260
|
+
*/
|
|
243
261
|
paramInt(key, defaultValue = 0) {
|
|
244
262
|
const value = this.param(key, null);
|
|
245
263
|
if (value === null) return defaultValue;
|
|
@@ -252,11 +270,21 @@ class Request {
|
|
|
252
270
|
return intVal;
|
|
253
271
|
}
|
|
254
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Get parameter as a boolean
|
|
275
|
+
* Only '0', 'false', 'no', 'off' are considered falsy
|
|
276
|
+
* empty string is truthy, so url?param would be truthy
|
|
277
|
+
* but url?param=0 or url?param=false would be falsy
|
|
278
|
+
* @param {string} key
|
|
279
|
+
* @param {boolean} defaultValue
|
|
280
|
+
* @returns {boolean}
|
|
281
|
+
*/
|
|
255
282
|
paramBool(key, defaultValue = false) {
|
|
256
283
|
const value = this.param(key, null);
|
|
257
284
|
if (value === null) return defaultValue;
|
|
258
285
|
|
|
259
286
|
if (
|
|
287
|
+
value === false ||
|
|
260
288
|
value === 0 ||
|
|
261
289
|
value === '0' ||
|
|
262
290
|
value === 'false' ||
|
|
@@ -267,6 +295,13 @@ class Request {
|
|
|
267
295
|
return true;
|
|
268
296
|
}
|
|
269
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Get the parameter as an id.
|
|
300
|
+
* id any is a max 20 character long alphanumeric string
|
|
301
|
+
* @param {string} key
|
|
302
|
+
* @param {string} defaultValue
|
|
303
|
+
* @returns {string}
|
|
304
|
+
*/
|
|
270
305
|
paramId(key, defaultValue = '') {
|
|
271
306
|
const value = this.param(key, null);
|
|
272
307
|
if (value === null) return defaultValue;
|
|
@@ -295,10 +330,20 @@ class Request {
|
|
|
295
330
|
});
|
|
296
331
|
}
|
|
297
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Get the file with this param name
|
|
335
|
+
* @param {string} key
|
|
336
|
+
* @returns
|
|
337
|
+
*/
|
|
298
338
|
file(key) {
|
|
299
339
|
return this.files(key)[0] || null;
|
|
300
340
|
}
|
|
301
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Get all the files with this param name
|
|
344
|
+
* @param {string} key
|
|
345
|
+
* @returns {array}
|
|
346
|
+
*/
|
|
302
347
|
files(key) {
|
|
303
348
|
if (this.ctx.request.files && this.ctx.request.files[key]) {
|
|
304
349
|
const result = this.ctx.request.files[key];
|
|
@@ -309,6 +354,11 @@ class Request {
|
|
|
309
354
|
return [];
|
|
310
355
|
}
|
|
311
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Get the bearer token of the request.
|
|
359
|
+
* Authorization: Bearer abc would give abc as bearer token
|
|
360
|
+
* @returns {string}
|
|
361
|
+
*/
|
|
312
362
|
bearerToken() {
|
|
313
363
|
const authorization = this.ctx.headers.authorization;
|
|
314
364
|
if (!authorization) return '';
|
|
@@ -320,12 +370,19 @@ class Request {
|
|
|
320
370
|
return parts[1];
|
|
321
371
|
}
|
|
322
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Get the api token of the request.
|
|
375
|
+
* API token can be sent as a header x-api-token
|
|
376
|
+
* @returns {string}
|
|
377
|
+
*/
|
|
323
378
|
apiToken() {
|
|
324
379
|
return this.header('x-api-token');
|
|
325
380
|
}
|
|
326
381
|
|
|
327
|
-
|
|
328
|
-
|
|
382
|
+
/**
|
|
383
|
+
* initialize a request
|
|
384
|
+
* set important cookies and all
|
|
385
|
+
*/
|
|
329
386
|
async init() {
|
|
330
387
|
// don't allow other domains to embed our iframe
|
|
331
388
|
if (isProduction) {
|
|
@@ -346,10 +403,18 @@ class Request {
|
|
|
346
403
|
}
|
|
347
404
|
}
|
|
348
405
|
|
|
406
|
+
/**
|
|
407
|
+
* check whether the request is http or https
|
|
408
|
+
* isHttp returns false if the request is https, true otherwise
|
|
409
|
+
* @returns {boolean}
|
|
410
|
+
*/
|
|
349
411
|
isHttp() {
|
|
350
412
|
if (this._isHttp === undefined) {
|
|
351
413
|
const ctx = this.ctx;
|
|
352
414
|
this._isHttp = (ctx.headers.origin || '').startsWith('http:') || (ctx.headers.host || '').includes(':');
|
|
415
|
+
if (!this._isHttp && this.ctx.protocol !== 'https') {
|
|
416
|
+
this.ctx.cookies.secure = true;
|
|
417
|
+
}
|
|
353
418
|
}
|
|
354
419
|
return this._isHttp;
|
|
355
420
|
}
|
|
@@ -363,6 +428,7 @@ class Request {
|
|
|
363
428
|
*/
|
|
364
429
|
|
|
365
430
|
/**
|
|
431
|
+
* Get or set a cookie
|
|
366
432
|
* @template {string | number | boolean | undefined} V
|
|
367
433
|
* @param {string} name
|
|
368
434
|
* @param {V} value
|
|
@@ -418,7 +484,7 @@ class Request {
|
|
|
418
484
|
}
|
|
419
485
|
|
|
420
486
|
const isHttp = this.isHttp();
|
|
421
|
-
if (options
|
|
487
|
+
if (!('secure' in options) && !isHttp) {
|
|
422
488
|
options.secure = true;
|
|
423
489
|
}
|
|
424
490
|
|
|
@@ -460,18 +526,35 @@ class Request {
|
|
|
460
526
|
return key ? this._trackingHeader[key] : this._trackingHeader;
|
|
461
527
|
}
|
|
462
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Get the user agent of the request.
|
|
531
|
+
* @returns {string}
|
|
532
|
+
*/
|
|
463
533
|
userAgent() {
|
|
464
534
|
return this.trackingHeader('user-agent') || this.header('user-agent');
|
|
465
535
|
}
|
|
466
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Get the referer of the request.
|
|
539
|
+
* @returns {string}
|
|
540
|
+
*/
|
|
467
541
|
referer() {
|
|
468
542
|
return this.trackingHeader('referer') || this.header('referer');
|
|
469
543
|
}
|
|
470
544
|
|
|
545
|
+
/**
|
|
546
|
+
* Get the referer name of the request.
|
|
547
|
+
* @returns {string}
|
|
548
|
+
*/
|
|
471
549
|
refererName() {
|
|
472
550
|
return this._refererName;
|
|
473
551
|
}
|
|
474
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Parse the user agent with ua-parser-js and return the result
|
|
555
|
+
* Returns {ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }
|
|
556
|
+
* @returns {object}
|
|
557
|
+
*/
|
|
475
558
|
parseUserAgent() {
|
|
476
559
|
if (!this._ua) {
|
|
477
560
|
this._ua = uaParser.setUA(this.userAgent()).getResult() || {};
|
|
@@ -479,19 +562,32 @@ class Request {
|
|
|
479
562
|
return this._ua;
|
|
480
563
|
}
|
|
481
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Get the browser name
|
|
567
|
+
* @returns {string}
|
|
568
|
+
*/
|
|
482
569
|
browser() {
|
|
483
570
|
const ua = this.parseUserAgent();
|
|
484
571
|
const deviceType = (ua && ua.device && ua.device.type) || '';
|
|
485
572
|
const browserName = (ua && ua.browser && ua.browser.name) || '';
|
|
486
573
|
|
|
487
574
|
if (deviceType === 'mobile') {
|
|
488
|
-
|
|
489
|
-
|
|
575
|
+
switch (browserName) {
|
|
576
|
+
case 'Chrome': return 'Chrome Mobile';
|
|
577
|
+
case 'Firefox': return 'Firefox Mobile';
|
|
578
|
+
case 'Safari': return 'Safari Mobile';
|
|
579
|
+
case 'Mobile Safari': return 'Safari Mobile';
|
|
580
|
+
default: return browserName;
|
|
581
|
+
}
|
|
490
582
|
}
|
|
491
583
|
|
|
492
584
|
return browserName;
|
|
493
585
|
}
|
|
494
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Get the browser name + version (eg. Chrome 96.1.0.110)
|
|
589
|
+
* @returns {string}
|
|
590
|
+
*/
|
|
495
591
|
browserVersion() {
|
|
496
592
|
const ua = this.parseUserAgent();
|
|
497
593
|
let browerVersion = (ua.browser && ua.browser.version) || '';
|
|
@@ -499,17 +595,29 @@ class Request {
|
|
|
499
595
|
return this.browser() + browerVersion;
|
|
500
596
|
}
|
|
501
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Get the browser version only (eg. 96.1.0.110)
|
|
600
|
+
* @returns {string}
|
|
601
|
+
*/
|
|
502
602
|
browserVersionRaw() {
|
|
503
603
|
const ua = this.parseUserAgent();
|
|
504
604
|
const version = (ua.browser && ua.browser.version) || '';
|
|
505
605
|
return String(version);
|
|
506
606
|
}
|
|
507
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Get the os name (eg. Windows)
|
|
610
|
+
* @returns {string}
|
|
611
|
+
*/
|
|
508
612
|
os() {
|
|
509
613
|
const ua = this.parseUserAgent();
|
|
510
614
|
return (ua.os && ua.os.name) || '';
|
|
511
615
|
}
|
|
512
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Get the os name + version (eg. Windows 11)
|
|
619
|
+
* @returns {string}
|
|
620
|
+
*/
|
|
513
621
|
osVersion() {
|
|
514
622
|
const ua = this.parseUserAgent();
|
|
515
623
|
let osVersion = (ua.os && ua.os.version) || '';
|
|
@@ -517,6 +625,10 @@ class Request {
|
|
|
517
625
|
return this.os() + osVersion;
|
|
518
626
|
}
|
|
519
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Get the header sm-user-agent
|
|
630
|
+
* @returns {string}
|
|
631
|
+
*/
|
|
520
632
|
smUserAgent() {
|
|
521
633
|
return this.header('sm-user-agent');
|
|
522
634
|
}
|
|
@@ -847,6 +959,7 @@ class Request {
|
|
|
847
959
|
}
|
|
848
960
|
|
|
849
961
|
/**
|
|
962
|
+
* Get the ip of the request
|
|
850
963
|
* @returns {string}
|
|
851
964
|
*/
|
|
852
965
|
ip() {
|
|
@@ -868,6 +981,7 @@ class Request {
|
|
|
868
981
|
}
|
|
869
982
|
|
|
870
983
|
/**
|
|
984
|
+
* Get the real ip of the request (after considering x-forwarded-for)
|
|
871
985
|
* @returns {string}
|
|
872
986
|
*/
|
|
873
987
|
realIP() {
|
|
@@ -900,14 +1014,16 @@ class Request {
|
|
|
900
1014
|
}
|
|
901
1015
|
|
|
902
1016
|
/**
|
|
903
|
-
* Is being proxied through local proxy
|
|
1017
|
+
* Is the request being proxied through local proxy
|
|
1018
|
+
* @returns {boolean}
|
|
904
1019
|
*/
|
|
905
1020
|
isProxied() {
|
|
906
1021
|
return this.header('x-real-ip') && this.ctx.ip.endsWith('127.0.0.1');
|
|
907
1022
|
}
|
|
908
1023
|
|
|
909
1024
|
/**
|
|
910
|
-
* Is request being proxied through an external proxy (like chrome data-saver)
|
|
1025
|
+
* Is the request being proxied through an external proxy (like chrome data-saver)
|
|
1026
|
+
* @returns {boolean}
|
|
911
1027
|
*/
|
|
912
1028
|
isVia() {
|
|
913
1029
|
return this.header('via') && this.realIP() === this.ip();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smpx/koa-request",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Handle basic tasks for koajs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"ip": "^1.1.5",
|
|
34
34
|
"koa-basic-auth": "^4.0.0",
|
|
35
|
-
"koa-body": "^4.
|
|
36
|
-
"koa-send": "^5.0.
|
|
37
|
-
"koa2-ratelimit": "^0.9.
|
|
38
|
-
"maxmind": "^4.
|
|
39
|
-
"tar": "^6.
|
|
40
|
-
"ua-parser-js": "^0.
|
|
35
|
+
"koa-body": "^4.2.0",
|
|
36
|
+
"koa-send": "^5.0.1",
|
|
37
|
+
"koa2-ratelimit": "^0.9.1",
|
|
38
|
+
"maxmind": "^4.3.3",
|
|
39
|
+
"tar": "^6.1.11",
|
|
40
|
+
"ua-parser-js": "^1.0.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"eslint": "^5.7.0",
|
package/.lh/.gitignore.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": ".gitignore",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 0,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622045206905,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"date": 1622045206905,
|
|
14
|
-
"name": "Commit-0",
|
|
15
|
-
"content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# lock files\npackage-lock.json\nyarn.lock\n\nprivate\ndist\n\n# geoip files\nGeoLite2-City.mmdb\n\n# local history (vs code extension - xyz.local-history)\n.history/*\n.lh\n"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/.lh/.lhignore
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# list file to not track by the local-history extension. comment line starts with a '#' character
|
|
2
|
-
# each line describe a regular expression pattern (search for "Javascript regex")
|
|
3
|
-
# it will relate to the workspace directory root. for example:
|
|
4
|
-
# ".*\.txt" ignores any file with "txt" extension
|
|
5
|
-
# "/test/.*" ignores all the files under the "test" directory
|
|
6
|
-
# ".*/test/.*" ignores all the files under any "test" directory (even under sub-folders)
|
package/.lh/IPRange.js.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "IPRange.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 0,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622576413980,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"date": 1622576413980,
|
|
14
|
-
"name": "Commit-0",
|
|
15
|
-
"content": "const ipLib = require('ip');\n\nconst PRIVATE_IPS = [\n\t'0.0.0.0/8',\n\t'240.0.0.0/4',\n\t'10.0.0.0/8',\n\t'127.0.0.0/8',\n\t'169.254.0.0/16',\n\t'172.16.0.0/12',\n\t'192.168.0.0/16',\n];\n\n// Source: https://www.lifewire.com/what-is-the-ip-address-of-google-818153\nconst GOOGLE_IPS = [\n\t'64.233.160.0 - 64.233.191.255',\n\t'66.102.0.0 - 66.102.15.255',\n\t'66.249.64.0 - 66.249.95.255',\n\t'72.14.192.0 - 72.14.255.255',\n\t'74.125.0.0 - 74.125.255.255',\n\t'209.85.128.0 - 209.85.255.255',\n\t'216.239.32.0 - 216.239.63.255',\n\t//\n\t'64.68.90.1 - 64.68.90.255',\n\t'64.233.173.193 - 64.233.173.255',\n\t'66.249.64.1 - 66.249.79.255',\n\t'216.239.33.96 - 216.239.59.128',\n];\n\nconst SEARCH_IPS = [\n\t'178.154.128.0/17', // YANDAX\n\t'87.250.224.0/19',\t// YANDAX\n\t'157.55.0.0/16',\t// Microsoft\n\t'207.46.0.0/19',\t// Microsoft\n\t'17.0.0.0/8',\t\t// Apple\n];\n\nlet compiledPrivateIps;\nlet compiledGoogleIps;\nlet compiledSearchIps;\nlet publicIp;\nlet privateIp;\n\nfunction ipRangeCheckRegex(cidrs) {\n\tconst cidrJoin = cidrs.map((cidr) => {\n\t\tif (!cidr.endsWith('.')) cidr += '.';\n\t\treturn cidr.replace(/\\./g, '\\\\.');\n\t}).join('|');\n\n\treturn new RegExp(`^(?:${cidrJoin})`);\n}\n\nfunction compile(cidrs) {\n\tif (!Array.isArray(cidrs)) {\n\t\tcidrs = [cidrs];\n\t}\n\n\tconst ranges = [];\n\tconst startsWith = [];\n\tlet rangeRegex = null;\n\n\tfor (const cidr of cidrs) {\n\t\t// match cidr\n\t\tif (cidr.includes('/')) {\n\t\t\t// Split the range by the slash\n\t\t\tconst parts = cidr.split('/');\n\n\t\t\t// Work out how many IPs the /slash-part matches.\n\t\t\t// We run 2^(32-slash)\n\t\t\tconst numIps = 2 ** (32 - Number(parts[1]));\n\n\t\t\tconst ipStart = ipLib.toLong(parts[0]);\n\t\t\tconst ipEnd = (ipStart + numIps) - 1;\n\t\t\tranges.push([ipStart, ipEnd]);\n\t\t}\n\n\t\t// match range\n\t\tif (cidr.includes('-')) {\n\t\t\tconst parts = cidr.split('-');\n\t\t\tconst ipStart = ipLib.toLong(parts[0].trim());\n\t\t\tconst ipEnd = ipLib.toLong(parts[1].trim());\n\t\t\tranges.push([ipStart, ipEnd]);\n\t\t}\n\n\t\t// match a single ip or ips like (123.156)\n\t\tstartsWith.push(cidr);\n\t}\n\n\t// if we only need to check for simple ranges, do it with regex\n\tif (startsWith.length) {\n\t\trangeRegex = ipRangeCheckRegex(startsWith);\n\t}\n\n\treturn [ranges, rangeRegex];\n}\n\nfunction contains(ip, ranges, rangeRegex) {\n\tif (ranges.length) {\n\t\tconst ipLong = ipLib.toLong(ip);\n\t\tfor (const range of ranges) {\n\t\t\tif (ipLong >= range[0] && ipLong <= range[1]) return true;\n\t\t}\n\t}\n\n\tif (rangeRegex) {\n\t\treturn rangeRegex.test(ip + '.');\n\t}\n\n\treturn false;\n}\n\nclass IPRange {\n\tconstructor(cidrs) {\n\t\tconst [range, rangeRegex] = compile(cidrs || []);\n\t\tthis.range = range;\n\t\tthis.rangeRegex = rangeRegex;\n\t}\n\n\thas(ip) {\n\t\treturn contains(ip, this.range, this.rangeRegex);\n\t}\n\n\tstatic has(ip, cidrs) {\n\t\tif (!cidrs || !cidrs.length) return false;\n\t\tconst [ranges, rangeRegex] = compile(cidrs);\n\t\treturn contains(ip, ranges, rangeRegex);\n\t}\n\n\tstatic isPrivateIp(ip) {\n\t\tif (!compiledPrivateIps) {\n\t\t\tcompiledPrivateIps = new IPRange(PRIVATE_IPS);\n\t\t}\n\t\treturn compiledPrivateIps.has(ip);\n\t}\n\n\tstatic isGoogleIp(ip) {\n\t\tif (!compiledGoogleIps) {\n\t\t\tcompiledGoogleIps = new IPRange(GOOGLE_IPS);\n\t\t}\n\t\treturn compiledGoogleIps.has(ip);\n\t}\n\n\tstatic isSearchIp(ip) {\n\t\tif (!compiledSearchIps) {\n\t\t\tcompiledSearchIps = new IPRange(SEARCH_IPS);\n\t\t}\n\t\treturn this.isGoogleIp(ip) || compiledSearchIps.has(ip);\n\t}\n\n\tstatic isLocalhost(ip) {\n\t\tif (!publicIp) {\n\t\t\tpublicIp = ipLib.address('public') || '0.0.0.0';\n\t\t}\n\t\tif (!privateIp) {\n\t\t\tprivateIp = ipLib.address('private') || '127.0.0.1';\n\t\t}\n\n\t\treturn ['127.0.0.1', publicIp, privateIp].includes(ip);\n\t}\n}\n\nmodule.exports = IPRange;\n"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/.lh/Request.js.json
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "Request.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 20,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622037853546,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"date": 1622037859422,
|
|
14
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -820,9 +820,8 @@\n \t\treturn sessionId;\n \t}\n \n \t// session id that's existing (not set in this request)\n-\t// TODO: testing & take params into account\n \texistingSessionId() {\n \t\treturn this.ctx.cookies.get(SESSIONID_COOKIE);\n \t}\n \n"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"date": 1622037867232,
|
|
18
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1165,16 +1165,9 @@\n \t\t}\n \n \t\t// if the medium is direct then only set cookie if it doesn't already exist\n \t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) {\n-\t\t\t// fix existing utm cookies (not urlencoded)\n-\t\t\t// TODO: remove later\n-\t\t\tconst existing = this.ctx.cookies.get(UTM_COOKIE) || '';\n-\t\t\tif (!existing.includes('|')) {\n-\t\t\t\treturn;\n-\t\t\t}\n-\t\t}\n+\t\tif (!shouldSetCookie) return;\n \n \t\tthis.cookie(\n \t\t\tUTM_COOKIE,\n \t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"date": 1622044243935,
|
|
22
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1176,8 +1176,50 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\t/**\n+\t * sets UTM cookies from a predefined url\n+\t */\n+\tsetUTMCookieFromUrl(url) {\n+\t\tconst uri = new URL(url, 'http://localhost');\n+\n+\t\tconst params = uri.searchParams;\n+\t\tlet source = params.get('utm_source') || '';\n+\t\tlet medium = params.get('utm_medium') || '';\n+\t\tlet campaign = params.get('utm_campaign') || '';\n+\t\tlet term = params.get('utm_term') || '';\n+\t\tconst content = params.get('utm_content') || '';\n+\n+\t\tif (params.get('gclid')) {\n+\t\t\tsource = source || 'google';\n+\t\t\tmedium = medium || 'cpc';\n+\t\t\tcampaign = campaign || 'google_cpc';\n+\t\t}\n+\n+\t\tconst utmExists = Boolean(source || medium || campaign);\n+\t\tconst referer = this.parseReferer();\n+\t\tthis._refererName = referer.name;\n+\n+\t\tif (!utmExists) {\n+\t\t\tsource = referer.source;\n+\t\t\tmedium = referer.medium;\n+\t\t\tterm = referer.term;\n+\t\t}\n+\n+\t\t// if the medium is direct then only set cookie if it doesn't already exist\n+\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n+\t\tif (!shouldSetCookie) return;\n+\n+\t\tthis.cookie(\n+\t\t\tUTM_COOKIE,\n+\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n+\t\t\t\tmaxAge: ONE_MONTH,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n+\t}\n+\n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n \t\tif (!affid) return;\n"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"date": 1622044357897,
|
|
26
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1218,8 +1218,44 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\tsetUTMCookieFromQuery(query) {\n+\t\tlet source = query.utm_source || '';\n+\t\tlet medium = query.utm_medium || '';\n+\t\tlet campaign = query.utm_campaign || '';\n+\t\tlet term = query.utm_term || '';\n+\t\tconst content = query.utm_content || '';\n+\n+\t\tif (query.gclid) {\n+\t\t\tsource = source || 'google';\n+\t\t\tmedium = medium || 'cpc';\n+\t\t\tcampaign = campaign || 'google_cpc';\n+\t\t}\n+\n+\t\tconst utmExists = Boolean(source || medium || campaign);\n+\t\tconst referer = this.parseReferer();\n+\t\tthis._refererName = referer.name;\n+\n+\t\tif (!utmExists) {\n+\t\t\tsource = referer.source;\n+\t\t\tmedium = referer.medium;\n+\t\t\tterm = referer.term;\n+\t\t}\n+\n+\t\t// if the medium is direct then only set cookie if it doesn't already exist\n+\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n+\t\tif (!shouldSetCookie) return;\n+\n+\t\tthis.cookie(\n+\t\t\tUTM_COOKIE,\n+\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n+\t\t\t\tmaxAge: ONE_MONTH,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n+\t}\n+\n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n \t\tif (!affid) return;\n"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"date": 1622044385879,
|
|
30
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1139,43 +1139,9 @@\n \t\t};\n \t}\n \n \tsetUTMCookie() {\n-\t\tconst ctx = this.ctx;\n-\n-\t\tlet source = ctx.query.utm_source || '';\n-\t\tlet medium = ctx.query.utm_medium || '';\n-\t\tlet campaign = ctx.query.utm_campaign || '';\n-\t\tlet term = ctx.query.utm_term || '';\n-\t\tconst content = ctx.query.utm_content || '';\n-\n-\t\tif (ctx.query.gclid) {\n-\t\t\tsource = source || 'google';\n-\t\t\tmedium = medium || 'cpc';\n-\t\t\tcampaign = campaign || 'google_cpc';\n-\t\t}\n-\n-\t\tconst utmExists = Boolean(source || medium || campaign);\n-\t\tconst referer = this.parseReferer();\n-\t\tthis._refererName = referer.name;\n-\n-\t\tif (!utmExists) {\n-\t\t\tsource = referer.source;\n-\t\t\tmedium = referer.medium;\n-\t\t\tterm = referer.term;\n-\t\t}\n-\n-\t\t// if the medium is direct then only set cookie if it doesn't already exist\n-\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) return;\n-\n-\t\tthis.cookie(\n-\t\t\tUTM_COOKIE,\n-\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n-\t\t\t\tmaxAge: ONE_MONTH,\n-\t\t\t\tdomain: '*',\n-\t\t\t},\n-\t\t);\n+\t\tthis.setUTMCookieFromQuery(this.ctx.query);\n \t}\n \n \t/**\n \t * sets UTM cookies from a predefined url\n"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"date": 1622044489994,
|
|
34
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1147,43 +1147,17 @@\n \t * sets UTM cookies from a predefined url\n \t */\n \tsetUTMCookieFromUrl(url) {\n \t\tconst uri = new URL(url, 'http://localhost');\n-\n \t\tconst params = uri.searchParams;\n-\t\tlet source = params.get('utm_source') || '';\n-\t\tlet medium = params.get('utm_medium') || '';\n-\t\tlet campaign = params.get('utm_campaign') || '';\n-\t\tlet term = params.get('utm_term') || '';\n-\t\tconst content = params.get('utm_content') || '';\n-\n-\t\tif (params.get('gclid')) {\n-\t\t\tsource = source || 'google';\n-\t\t\tmedium = medium || 'cpc';\n-\t\t\tcampaign = campaign || 'google_cpc';\n-\t\t}\n-\n-\t\tconst utmExists = Boolean(source || medium || campaign);\n-\t\tconst referer = this.parseReferer();\n-\t\tthis._refererName = referer.name;\n-\n-\t\tif (!utmExists) {\n-\t\t\tsource = referer.source;\n-\t\t\tmedium = referer.medium;\n-\t\t\tterm = referer.term;\n-\t\t}\n-\n-\t\t// if the medium is direct then only set cookie if it doesn't already exist\n-\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) return;\n-\n-\t\tthis.cookie(\n-\t\t\tUTM_COOKIE,\n-\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n-\t\t\t\tmaxAge: ONE_MONTH,\n-\t\t\t\tdomain: '*',\n-\t\t\t},\n-\t\t);\n+\t\tthis.setUTMCookieFromQuery({\n+\t\t\tutm_source: params.get('utm_source'),\n+\t\t\tutm_medium: params.get('utm_medium'),\n+\t\t\tutm_campaign: params.get('utm_campaign'),\n+\t\t\tutm_term: params.get('utm_term'),\n+\t\t\tutm_content: params.get('utm_content'),\n+\t\t\tgclid: params.get('gclid'),\n+\t\t});\n \t}\n \n \tsetUTMCookieFromQuery(query) {\n \t\tlet source = query.utm_source || '';\n"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"date": 1622044507475,
|
|
38
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1158,8 +1158,11 @@\n \t\t\tgclid: params.get('gclid'),\n \t\t});\n \t}\n \n+\t/**\n+\t * sets UTM cookies from a predefined query object\n+\t */\n \tsetUTMCookieFromQuery(query) {\n \t\tlet source = query.utm_source || '';\n \t\tlet medium = query.utm_medium || '';\n \t\tlet campaign = query.utm_campaign || '';\n"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"date": 1622044687850,
|
|
42
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -965,12 +965,19 @@\n \t\treturn ['get', 'head', 'options'].includes(this.ctx.method.toLowerCase());\n \t}\n \n \tisAjax() {\n-\t\tif (this.ctx.headers['x-requested-with']) {\n-\t\t\treturn this.ctx.headers['x-requested-with'].toLowerCase() === 'xmlhttprequest';\n+\t\tif (this._isAjax === undefined) {\n+\t\t\tconst h = this.ctx.headers['x-requested-with'];\n+\t\t\tif (h) {\n+\t\t\t\tthis._isAjax = h.toLowerCase() === 'xmlhttprequest';\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tthis._isAjax = false;\n+\t\t\t}\n \t\t}\n-\t\treturn false;\n+\n+\t\treturn this._isAjax;\n \t}\n \n \tisJSEnabled() {\n \t\treturn !!this.cookie('js');\n"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"date": 1622044695588,
|
|
46
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -974,9 +974,8 @@\n \t\t\telse {\n \t\t\t\tthis._isAjax = false;\n \t\t\t}\n \t\t}\n-\n \t\treturn this._isAjax;\n \t}\n \n \tisJSEnabled() {\n"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"date": 1622044750464,
|
|
50
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1218,10 +1218,10 @@\n \t\t);\n \t}\n \n \thandlePlatformModification() {\n-\t\t// don't change platform in mobile apps\n-\t\tif (this.isMobileApp()) return;\n+\t\t// don't change platform in mobile apps and ajax\n+\t\tif (this.isMobileApp() || this.isAjax()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n \t\tif (setPlatformCookie) {\n"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"date": 1622044809259,
|
|
54
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -968,9 +968,9 @@\n \tisAjax() {\n \t\tif (this._isAjax === undefined) {\n \t\t\tconst h = this.ctx.headers['x-requested-with'];\n \t\t\tif (h) {\n-\t\t\t\tthis._isAjax = h.toLowerCase() === 'xmlhttprequest';\n+\t\t\t\tthis._isAjax = ['xmlhttprequest', 'fetch'].includes(h.toLowerCase());\n \t\t\t}\n \t\t\telse {\n \t\t\t\tthis._isAjax = false;\n \t\t\t}\n"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"date": 1622044831254,
|
|
58
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1219,9 +1219,9 @@\n \t}\n \n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n-\t\tif (this.isMobileApp() || this.isAjax()) return;\n+\t\tif (this.isMobileApp()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n \t\tif (setPlatformCookie) {\n"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"date": 1622044874192,
|
|
62
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -332,12 +332,14 @@\n \t\t\tconst domain = this.baseDomain();\n \t\t\tthis.ctx.set('Content-Security-Policy', `frame-ancestors https://*.${domain}`);\n \t\t}\n \n-\t\tthis.handlePlatformModification();\n-\t\tthis.setUTMCookie();\n-\t\tthis.setAffidCookie();\n-\t\tthis.handleFlashMessage();\n+\t\tif (!this.isAjax()) {\n+\t\t\tthis.handlePlatformModification();\n+\t\t\tthis.setUTMCookie();\n+\t\t\tthis.setAffidCookie();\n+\t\t\tthis.handleFlashMessage();\n+\t\t}\n \n \t\t// in case of visit out from app we need to set cookies from params\n \t\tif (this.ctx.query.installId) {\n \t\t\tawait this.setCookiesFromParams();\n"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"date": 1622044935260,
|
|
66
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -337,13 +337,13 @@\n \t\t\tthis.handlePlatformModification();\n \t\t\tthis.setUTMCookie();\n \t\t\tthis.setAffidCookie();\n \t\t\tthis.handleFlashMessage();\n-\t\t}\n \n-\t\t// in case of visit out from app we need to set cookies from params\n-\t\tif (this.ctx.query.installId) {\n-\t\t\tawait this.setCookiesFromParams();\n+\t\t\t// in case of visit out from app we need to set cookies from params\n+\t\t\tif (this.ctx.query.installId) {\n+\t\t\t\tawait this.setCookiesFromParams();\n+\t\t\t}\n \t\t}\n \t}\n \n \tisHttp() {\n"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"date": 1622045056034,
|
|
70
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1207,10 +1207,10 @@\n \t}\n \n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n+\t\tif (!affid) return;\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n-\t\tif (!affid) return;\n \n \t\tthis.cookie(\n \t\t\tAFFID_COOKIE,\n \t\t\tjoinCookieParts([affid, subaffid]), {\n@@ -1219,8 +1219,16 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\tsetAffidCookieFromUrl(url) {\n+\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst params = uri.searchParams;\n+\t\tconst affid = params.get(AFFID_PARAM);\n+\t\tif (!affid) return;\n+\t\tconst subaffid = params.get(SUBAFFID_PARAM);\n+\t}\n+\n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n \t\tif (this.isMobileApp()) return;\n \n"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"date": 1622045061592,
|
|
74
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1225,8 +1225,16 @@\n \t\tconst params = uri.searchParams;\n \t\tconst affid = params.get(AFFID_PARAM);\n \t\tif (!affid) return;\n \t\tconst subaffid = params.get(SUBAFFID_PARAM);\n+\n+\t\tthis.cookie(\n+\t\t\tAFFID_COOKIE,\n+\t\t\tjoinCookieParts([affid, subaffid]), {\n+\t\t\t\tmaxAge: AFFID_COOKIE_DURATION,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n \t}\n \n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n"
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"date": 1622045128428,
|
|
78
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1220,9 +1220,9 @@\n \t\t);\n \t}\n \n \tsetAffidCookieFromUrl(url) {\n-\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst uri = (url instanceof URL) ? url : new URL(url, 'http://localhost');\n \t\tconst params = uri.searchParams;\n \t\tconst affid = params.get(AFFID_PARAM);\n \t\tif (!affid) return;\n \t\tconst subaffid = params.get(SUBAFFID_PARAM);\n"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"date": 1622045144910,
|
|
82
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1154,9 +1154,9 @@\n \t/**\n \t * sets UTM cookies from a predefined url\n \t */\n \tsetUTMCookieFromUrl(url) {\n-\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst uri = (url instanceof URL) ? url : new URL(url, 'http://localhost');\n \t\tconst params = uri.searchParams;\n \t\tthis.setUTMCookieFromQuery({\n \t\t\tutm_source: params.get('utm_source'),\n \t\t\tutm_medium: params.get('utm_medium'),\n"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"date": 1622045238469,
|
|
86
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1236,9 +1236,9 @@\n \t\t);\n \t}\n \n \thandlePlatformModification() {\n-\t\t// don't change platform in mobile apps and ajax\n+\t\t// don't change platform in mobile apps\n \t\tif (this.isMobileApp()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"date": 1622703215844,
|
|
90
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1127,10 +1127,10 @@\n \t\t\tif (['search', 'searches'].includes(refererSource)) {\n \t\t\t\trefererSource = host;\n \t\t\t}\n \n-\t\t\tconst query = refererUri.query(true);\n-\t\t\tconst term = query.q || query.searchfor || query.pq || 'not_available';\n+\t\t\tconst query = refererUri.searchParams;\n+\t\t\tconst term = query.get('q') || query.get('searchfor') || query.get('pq') || 'not_available';\n \n \t\t\treturn {\n \t\t\t\tname: host,\n \t\t\t\tsource: refererSource,\n"
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
"date": 1622037853546,
|
|
94
|
-
"name": "Commit-0",
|
|
95
|
-
"content": "/* eslint-disable class-methods-use-this, max-lines */\nconst {UAParser} = require('ua-parser-js');\nconst bodyParser = require('koa-body');\n\nconst IPRange = require('./IPRange');\nconst GeoIP = require('./GeoIP');\n\nconst enableRateLimit = require('./rateLimit');\nconst enableBasicAuth = require('./basicAuth');\nconst enableStaticPaths = require('./staticPaths');\nconst enableBotBanning = require('./botBanning');\n\nconst uaParser = new UAParser();\n\nconst ONE_HOUR = 3600 * 1000;\nconst ONE_DAY = 24 * ONE_HOUR;\nconst ONE_MONTH = 30 * ONE_DAY;\nconst ONE_YEAR = 365 * ONE_DAY;\nconst TEN_YEARS = 10 * ONE_YEAR;\n\nconst PLATFORM_PARAM = 'platform';\nconst PLATFORM_COOKIE = 'platform';\nconst PLATFORM_COOKIE_DURATION = 4 * ONE_HOUR;\nconst APPINFO_PARAM = 'sm_app';\nconst APPINFO_COOKIE = 'sm_app';\nconst APPINFO_HEADER = 'sm-app';\nconst TRACKING_HEADER = 'sm-tracking';\nconst UTM_COOKIE = 'sm_utm';\nconst AFFID_PARAM = 'affid';\nconst SUBAFFID_PARAM = 'subaffid';\nconst COUNTRY_COOKIE = 'country';\nconst AFFID_COOKIE = 'sm_aff';\nconst AFFID_COOKIE_DURATION = ONE_DAY;\nconst REF_PARAM = 'ref';\nconst SESSIONID_COOKIE = 'sid';\nconst COOKIEID_COOKIE = 'id';\nconst COOKIE_PARAM_PREFIX = '_ck_';\nconst USER_TOKEN_COOKIE = 'utok';\nconst FLASH_COOKIE = 'flash';\n// these cookies are httpOnly, should not be readable from js\nconst SENSITIVE_COOKIES = [USER_TOKEN_COOKIE, COOKIEID_COOKIE, SESSIONID_COOKIE];\n\nconst APP_PLATFORMS = new Map([\n\t['android', {}],\n\t['ios', {}],\n\t['wp', {}],\n\t['tizen', {}],\n\t['jio', {}],\n]);\n\n// sometimes request parameters can be an array (like &q=s&q=b)\n// since we expect a string, this will convert array to a string\nfunction handleArray(value, defaultValue = '') {\n\tif (Array.isArray(value)) {\n\t\treturn value[value.length - 1] || value[0] || defaultValue;\n\t}\n\treturn value || defaultValue;\n}\n\nfunction sanitizeCookiePart(value, defaultValue = '') {\n\tif (!value) return defaultValue;\n\treturn handleArray(value).replace(/\\|/g, '!~!').substring(0, 255);\n}\n\nfunction joinCookieParts(parts, defaultValue = '') {\n\treturn parts.map(part => sanitizeCookiePart(part, defaultValue)).join('|');\n}\n\nfunction splitCookieParts(cookie) {\n\treturn cookie.split('|').map(part => part.replace(/!~!/g, '|'));\n}\n\n/**\n * return [subDomain, baseDomain, canHaveSubdomain]\n */\nfunction getDomainParts(domain) {\n\tconst parts = domain.split('.');\n\tif (parts.length <= 1) {\n\t\t// domain is localhost\n\t\treturn ['', domain, false];\n\t}\n\tif (/^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$/.test(domain)) {\n\t\t// domain is an ip\n\t\treturn ['', domain, false];\n\t}\n\n\tif (parts.length <= 2) {\n\t\t// a simple domain like example.com\n\t\treturn ['', domain, true];\n\t}\n\n\tconst secondTld = parts[parts.length - 2];\n\tif (/co|nic|gov/i.test(secondTld)) {\n\t\t// a domain with second level tld, like example.co.uk\n\t\tif (parts.length <= 3) {\n\t\t\treturn ['', domain, true];\n\t\t}\n\t}\n\n\treturn [parts[0], parts.slice(1).join('.'), true];\n}\n\nfunction randomId(length) {\n\tlet id = '';\n\twhile (id.length < length) {\n\t\tid += Math.random().toString(36).substring(2);\n\t}\n\treturn id.substring(0, length);\n}\n\nfunction getIntegerKey(key) {\n\tif (typeof key === 'number') return key;\n\tif (typeof key === 'string') {\n\t\tlet value = 0;\n\t\tfor (let i = 0; i < key.length; ++i) {\n\t\t\tvalue = ((value << 5) - value) + key.charCodeAt(i); // value * 31 + c\n\t\t\tvalue |= 0; // to 32bit integer\n\t\t}\n\t\treturn value;\n\t}\n\treturn key;\n}\n\nfunction addQuery(url, query = {}) {\n\tconst uri = new URL(url, 'http://localhost');\n\tif (typeof query === 'string') {\n\t\tquery = new URLSearchParams(query);\n\t\tfor (const [key, val] of query) {\n\t\t\turi.searchParams.set(key, val);\n\t\t}\n\t}\n\telse {\n\t\tfor (const [key, val] of Object.entries(query)) {\n\t\t\turi.searchParams.set(key, val);\n\t\t}\n\t}\n\treturn `${uri.pathname}${uri.search}`;\n}\n\nconst isProduction = (process.env.NODE_ENV === 'production');\n\nclass Request {\n\t/**\n\t * @param {import('koa').Context} ctx\n\t */\n\tconstructor(ctx, options = {}) {\n\t\tthis.ctx = ctx;\n\t\tthis.options = options;\n\t}\n\n\n\t/** @type {Routes.installer} */\n\tstatic install(app, options = {}) {\n\t\tenableStaticPaths(app, options.staticPaths);\n\t\tapp.use(bodyParser({\n\t\t\tmultipart: true,\n\t\t}));\n\t\tif (options.middleware) {\n\t\t\tapp.use(this.middleware(options));\n\t\t}\n\t\tenableBasicAuth(app, options.basicAuth);\n\t\tenableBotBanning(app, options.banBots);\n\t\tenableRateLimit(app, options.rateLimit);\n\t}\n\n\t/** @returns {Routes.middleware} */\n\tstatic middleware(options) {\n\t\treturn async (ctx, next) => {\n\t\t\tctx.$req = await this.from(ctx, options);\n\t\t\tawait next();\n\t\t};\n\t}\n\n\t/**\n\t * @param {import('koa').Context} ctx\n\t */\n\tstatic async from(ctx, options) {\n\t\tconst req = new this(ctx, options);\n\t\tawait req.init();\n\t\treturn req;\n\t}\n\n\tappPlatforms() {\n\t\treturn APP_PLATFORMS;\n\t}\n\n\t/**\n\t * get or set request header value\n\t * @param {string} name name of the header\n\t * @param {string} value value to set, if not given header value is return\n\t * @returns {string}\n\t * get: header value, or empty string if header not found\n\t * set: empty string\n\t * @example\n\t * // get header value\n\t * ctx.$req.header('user-agent')\n\t * // set header value\n\t * ctx.$req.header('ETag', '1234')\n\t */\n\theader(name, value) {\n\t\t// get header value\n\t\tif (value === undefined) {\n\t\t\treturn this.ctx.headers[name.toLowerCase()] || '';\n\t\t}\n\n\t\t// set header value\n\t\tthis.ctx.set(name, value);\n\t\treturn '';\n\t}\n\n\t/**\n\t * Get value from either from request body or query params\n\t * @param {string} key\n\t * @param {string} defaultValue\n\t * @return {any}\n\t */\n\tparam(key, defaultValue = '') {\n\t\tconst ctx = this.ctx;\n\t\tlet paramValue;\n\n\t\tif (ctx.request.body &&\n\t\t\tctx.request.body.fields &&\n\t\t\t(key in ctx.request.body.fields)\n\t\t) {\n\t\t\tparamValue = ctx.request.body.fields[key];\n\t\t}\n\t\tif (ctx.request.body &&\n\t\t\t(key in ctx.request.body)\n\t\t) {\n\t\t\tparamValue = ctx.request.body[key];\n\t\t}\n\t\telse {\n\t\t\tparamValue = ctx.query[key];\n\t\t}\n\n\t\tparamValue = paramValue || defaultValue;\n\n\t\tif (typeof paramValue === 'string') return paramValue.trim();\n\t\tif (Array.isArray(paramValue)) return paramValue.map(v => v.trim());\n\t\treturn paramValue;\n\t}\n\n\tparamInt(key, defaultValue = 0) {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\n\t\tconst intVal = Number(value);\n\t\tif (Number.isNaN(intVal)) {\n\t\t\treturn defaultValue;\n\t\t}\n\n\t\treturn intVal;\n\t}\n\n\tparamBool(key, defaultValue = false) {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\n\t\tif (\n\t\t\tvalue === 0 ||\n\t\t\tvalue === '0' ||\n\t\t\tvalue === 'false' ||\n\t\t\tvalue === 'no' ||\n\t\t\tvalue === 'off'\n\t\t) return false;\n\n\t\treturn true;\n\t}\n\n\tparamId(key, defaultValue = '') {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\t\tif (/^[A-Za-z0-9]{1,20}$/.test(value)) return value;\n\t\treturn defaultValue;\n\t}\n\n\t/**\n\t * get a string parameter and escape it for xss attacks\n\t * @param {string} key\n\t * @param {string} defaultValue\n\t * @returns {string} value of the param\n\t */\n\tparamXSS(key, defaultValue = '') {\n\t\tconst param = this.param(key, null);\n\t\tif (param === null) return defaultValue;\n\n\t\treturn param.replace(/([<>\"'])/g, (match, g1) => {\n\t\t\tswitch (g1) {\n\t\t\t\tcase '<': return '<';\n\t\t\t\tcase '>': return '>';\n\t\t\t\tcase '\"': return '"';\n\t\t\t\tcase '\\'': return ''';\n\t\t\t\tdefault: return g1;\n\t\t\t}\n\t\t});\n\t}\n\n\tfile(key) {\n\t\treturn this.files(key)[0] || null;\n\t}\n\n\tfiles(key) {\n\t\tif (this.ctx.request.files && this.ctx.request.files[key]) {\n\t\t\tconst result = this.ctx.request.files[key];\n\t\t\tif (!Array.isArray(result)) return [result];\n\t\t\treturn result.filter(Boolean);\n\t\t}\n\n\t\treturn [];\n\t}\n\n\tbearerToken() {\n\t\tconst authorization = this.ctx.headers.authorization;\n\t\tif (!authorization) return '';\n\n\t\tconst parts = authorization.split(' ');\n\t\tif (parts.length !== 2) return '';\n\t\tif (parts[0] !== 'Bearer') return '';\n\n\t\treturn parts[1];\n\t}\n\n\tapiToken() {\n\t\treturn this.header('x-api-token');\n\t}\n\n\t// initialize a request\n\t// set important cookies and all\n\tasync init() {\n\t\t// don't allow other domains to embed our iframe\n\t\tif (isProduction) {\n\t\t\tconst domain = this.baseDomain();\n\t\t\tthis.ctx.set('Content-Security-Policy', `frame-ancestors https://*.${domain}`);\n\t\t}\n\n\t\tthis.handlePlatformModification();\n\t\tthis.setUTMCookie();\n\t\tthis.setAffidCookie();\n\t\tthis.handleFlashMessage();\n\n\t\t// in case of visit out from app we need to set cookies from params\n\t\tif (this.ctx.query.installId) {\n\t\t\tawait this.setCookiesFromParams();\n\t\t}\n\t}\n\n\tisHttp() {\n\t\tif (this._isHttp === undefined) {\n\t\t\tconst ctx = this.ctx;\n\t\t\tthis._isHttp = (ctx.headers.origin || '').startsWith('http:') || (ctx.headers.host || '').includes(':');\n\t\t}\n\t\treturn this._isHttp;\n\t}\n\n\t/**\n\t * @typedef {Object} CustomCookieOpts\n\t * @property {boolean} [onlyCache]\n\t * @property {boolean} [onlyCacheIfExists]\n\t * @property {number} [days]\n\t * @property {number} [years]\n\t */\n\n\t/**\n\t * @template {string | number | boolean | undefined} V\n\t * @param {string} name\n\t * @param {V} value\n\t * @param {CustomCookieOpts & import('cookies').SetOption} [options]\n\t * @returns {V extends undefined ? string : null}\n\t */\n\tcookie(name, value, options = {}) {\n\t\tconst cookies = this._cookies || (this._cookies = {});\n\n\t\tif (value === undefined) {\n\t\t\tif (name in cookies) {\n\t\t\t\treturn cookies[name];\n\t\t\t}\n\n\t\t\tconst existing = this.ctx.cookies.get(name) || '';\n\t\t\treturn decodeURIComponent(existing);\n\t\t}\n\n\t\tcookies[name] = value;\n\n\t\t// only set the cookie in cache\n\t\t// don't set it in real\n\t\tif (options.onlyCache) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// set the cookie only if does not exist\n\t\t// but always set it in cache\n\t\tif (options.onlyCacheIfExists) {\n\t\t\tif (this.ctx.cookies.get(name)) return null;\n\t\t}\n\n\t\t// clone options\n\t\toptions = Object.assign({}, options);\n\n\t\tif (options.domain === '*') {\n\t\t\toptions.domain = this.baseDomain();\n\t\t}\n\t\tif (!('path' in options)) {\n\t\t\toptions.path = '/';\n\t\t}\n\t\tif ('days' in options) {\n\t\t\toptions.maxAge = options.days * ONE_DAY;\n\t\t}\n\t\tif ('years' in options) {\n\t\t\toptions.maxAge = options.years * ONE_YEAR;\n\t\t}\n\n\t\tif (!('httpOnly' in options)) {\n\t\t\tif (!SENSITIVE_COOKIES.includes(name)) {\n\t\t\t\toptions.httpOnly = false;\n\t\t\t}\n\t\t}\n\n\t\tconst isHttp = this.isHttp();\n\t\tif (options.httpOnly && !isHttp) {\n\t\t\toptions.secure = true;\n\t\t}\n\n\t\tif (!('sameSite' in options)) {\n\t\t\t// sameSite = none means intentionally send the cookie in 3rd party contexts\n\t\t\toptions.sameSite = isHttp ? false : 'none';\n\t\t}\n\n\t\tif (value) {\n\t\t\tvalue = encodeURIComponent(value);\n\t\t}\n\n\t\tthis.ctx.cookies.set(name, value, options);\n\t\treturn null;\n\t}\n\n\t/**\n\t * Used for sending tracking info from other servers/sites\n\t * @template {string | undefined} T\n\t * @param {T} key\n\t * @returns {T extends string ? string : Object.<string, string>}\n\t */\n\ttrackingHeader(key) {\n\t\tif (!this._trackingHeader) {\n\t\t\tconst header = this.header(TRACKING_HEADER);\n\t\t\tif (!header) {\n\t\t\t\tthis._trackingHeader = {};\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttry {\n\t\t\t\t\tthis._trackingHeader = JSON.parse(header) || {};\n\t\t\t\t}\n\t\t\t\tcatch (e) {\n\t\t\t\t\tthis._trackingHeader = {};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn key ? this._trackingHeader[key] : this._trackingHeader;\n\t}\n\n\tuserAgent() {\n\t\treturn this.trackingHeader('user-agent') || this.header('user-agent');\n\t}\n\n\treferer() {\n\t\treturn this.trackingHeader('referer') || this.header('referer');\n\t}\n\n\trefererName() {\n\t\treturn this._refererName;\n\t}\n\n\tparseUserAgent() {\n\t\tif (!this._ua) {\n\t\t\tthis._ua = uaParser.setUA(this.userAgent()).getResult() || {};\n\t\t}\n\t\treturn this._ua;\n\t}\n\n\tbrowser() {\n\t\tconst ua = this.parseUserAgent();\n\t\tconst deviceType = (ua && ua.device && ua.device.type) || '';\n\t\tconst browserName = (ua && ua.browser && ua.browser.name) || '';\n\n\t\tif (deviceType === 'mobile') {\n\t\t\tif (browserName === 'Chrome') return 'Chrome Mobile';\n\t\t\tif (browserName === 'Firefox') return 'Firefox Mobile';\n\t\t}\n\n\t\treturn browserName;\n\t}\n\n\tbrowserVersion() {\n\t\tconst ua = this.parseUserAgent();\n\t\tlet browerVersion = (ua.browser && ua.browser.version) || '';\n\t\tif (browerVersion) browerVersion = ' ' + browerVersion;\n\t\treturn this.browser() + browerVersion;\n\t}\n\n\tbrowserVersionRaw() {\n\t\tconst ua = this.parseUserAgent();\n\t\tconst version = (ua.browser && ua.browser.version) || '';\n\t\treturn String(version);\n\t}\n\n\tos() {\n\t\tconst ua = this.parseUserAgent();\n\t\treturn (ua.os && ua.os.name) || '';\n\t}\n\n\tosVersion() {\n\t\tconst ua = this.parseUserAgent();\n\t\tlet osVersion = (ua.os && ua.os.version) || '';\n\t\tif (osVersion) osVersion = ' ' + osVersion;\n\t\treturn this.os() + osVersion;\n\t}\n\n\tsmUserAgent() {\n\t\treturn this.header('sm-user-agent');\n\t}\n\n\tappUserAgent() {\n\t\treturn this.smUserAgent() || this.userAgent();\n\t}\n\n\tgetAppInfoFromUserAgent() {\n\t\treturn null;\n\t}\n\n\t_getAppInfoFromString(infoStr, separator = '#') {\n\t\tif (!infoStr) return null;\n\t\t// eslint-disable-next-line prefer-const\n\t\tlet [platform, appVersion, installId] = infoStr.split(separator);\n\t\tplatform = platform.toLowerCase();\n\n\t\tconst appInfo = {\n\t\t\tplatform,\n\t\t\tappVersion,\n\t\t\tinstallId,\n\t\t};\n\n\t\tif (this.appPlatforms().has(platform)) {\n\t\t\tappInfo.isMobileApp = true;\n\t\t}\n\n\t\treturn appInfo;\n\t}\n\n\tgetAppInfoFromParam() {\n\t\tconst appInfoParam = this.ctx.query[APPINFO_PARAM];\n\t\tif (appInfoParam) {\n\t\t\tthis.cookie(APPINFO_COOKIE, appInfoParam, {\n\t\t\t\tpath: '/',\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\n\t\t\treturn this._getAppInfoFromString(appInfoParam, ':');\n\t\t}\n\n\t\tconst appInfoCookie = this.cookie(APPINFO_COOKIE);\n\t\tif (appInfoCookie) {\n\t\t\treturn this._getAppInfoFromString(appInfoParam, ':');\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tgetAppInfoFromHeader() {\n\t\treturn this._getAppInfoFromString(this.ctx.headers[APPINFO_HEADER]);\n\t}\n\n\tgetAppInfo() {\n\t\tconst appInfoHeader = this.getAppInfoFromHeader();\n\t\tif (appInfoHeader) return appInfoHeader;\n\n\t\tconst appInfoParam = this.getAppInfoFromParam();\n\t\tif (appInfoParam) return appInfoParam;\n\n\t\tconst appInfoUserAgent = this.getAppInfoFromUserAgent();\n\t\tif (appInfoUserAgent) return appInfoUserAgent;\n\n\t\treturn {};\n\t}\n\n\t/*\n\t * get the app related info using a header / query parameter / user agent\n\t * this means that you can have the url as sm_app=android\n\t * and it'll automatically identify it as an android app\n\t */\n\tappInfo(param = null) {\n\t\tif (!this._appInfo) {\n\t\t\tthis._appInfo = this.getAppInfo() || {};\n\t\t}\n\t\treturn param ? this._appInfo[param] : this._appInfo;\n\t}\n\n\tinstallId() {\n\t\treturn this.appInfo('installId') || this.ctx.query.installId || '';\n\t}\n\n\tappVersion() {\n\t\treturn this.appInfo('appVersion') || this.ctx.query.appVersion || '';\n\t}\n\n\tisAndroidApp() {\n\t\treturn this.appInfo('platform') === 'android';\n\t}\n\n\tisIOSApp() {\n\t\treturn this.appInfo('platform') === 'ios';\n\t}\n\n\tisWPApp() {\n\t\treturn this.appInfo('platform') === 'wp';\n\t}\n\n\tisTizenApp() {\n\t\treturn this.appInfo('platform') === 'tizen';\n\t}\n\n\tisJIOApp() {\n\t\treturn this.appInfo('platform') === 'jio';\n\t}\n\n\tisMobileApp() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'mobile_app') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !!this.appInfo('isMobileApp');\n\t}\n\n\tisMobileWeb() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'mobile_web') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this._isMobileWeb) {\n\t\t\tconst ua = this.parseUserAgent();\n\t\t\tthis._isMobileWeb = (ua && ua.device && ua.device.type === 'mobile') || false;\n\t\t}\n\t\treturn this._isMobileWeb;\n\t}\n\n\tisMobile() {\n\t\treturn this.isMobileApp() || this.isMobileWeb();\n\t}\n\n\tplatform() {\n\t\tif (!this._platform) {\n\t\t\tif (this.isMobileApp()) {\n\t\t\t\tthis._platform = 'mobile_app';\n\t\t\t}\n\t\t\telse if (this.isMobileWeb()) {\n\t\t\t\tthis._platform = 'mobile_web';\n\t\t\t}\n\t\t\telse if (this.isAPI()) {\n\t\t\t\tthis._platform = 'api';\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis._platform = 'desktop';\n\t\t\t}\n\t\t}\n\n\t\treturn this._platform;\n\t}\n\n\tsetPlatform(platform) {\n\t\tif (!platform) return false;\n\t\tplatform = handleArray(platform);\n\t\tconst appPlatform = platform.replace('_app', '');\n\n\t\tif (this.appPlatforms().has(appPlatform)) {\n\t\t\tthis._platform = 'mobile_app';\n\t\t\tthis._subPlatform = `${appPlatform}_app`;\n\t\t\treturn false;\n\t\t}\n\n\t\tswitch (platform) {\n\t\t\tcase 'mobile':\n\t\t\tcase 'mobile_web':\n\t\t\t\tthis._platform = 'mobile_web';\n\t\t\t\treturn true;\n\t\t\tcase 'www':\n\t\t\tcase 'desktop':\n\t\t\t\tthis._platform = 'desktop';\n\t\t\t\treturn true;\n\t\t\tcase 'mobile_app':\n\t\t\t\tthis._platform = 'mobile_app';\n\t\t\t\treturn true;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\tsubPlatform() {\n\t\tif (!this._subPlatform) {\n\t\t\tconst appPlatform = this.appInfo('platform');\n\t\t\tif (appPlatform && this.isMobileApp()) {\n\t\t\t\tthis._subPlatform = `${appPlatform}_app`;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis._subPlatform = this.platform();\n\t\t\t}\n\t\t}\n\t\treturn this._subPlatform;\n\t}\n\n\tisDesktop() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'desktop') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.platform() === 'desktop';\n\t}\n\n\t/**\n\t * @typedef {Object} UTMObject\n\t * @property {string} source\n\t * @property {string} medium\n\t * @property {string} campaign\n\t * @property {string} term\n\t * @property {string} content\n\t * @property {string} sourceMedium\n\t */\n\n\t/**\n\t * @template {string | null} P\n\t * @param {P} [param]\n\t * @returns {P extends null ? UTMObject : P extends string ? string : UTMObject}\n\t */\n\tutm(param = null) {\n\t\tif (!this._utm) {\n\t\t\tconst utmCookie = this.cookie(UTM_COOKIE);\n\t\t\tif (!utmCookie) {\n\t\t\t\tthis._utm = {};\n\t\t\t}\n\t\t\telse {\n\t\t\t\tconst [source, medium, campaign, term, content] = splitCookieParts(utmCookie);\n\t\t\t\tthis._utm = {\n\t\t\t\t\tsource,\n\t\t\t\t\tmedium,\n\t\t\t\t\tcampaign,\n\t\t\t\t\tterm,\n\t\t\t\t\tcontent,\n\t\t\t\t\tsourceMedium: `${source}/${medium}`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn param ? this._utm[param] : this._utm;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\tref() {\n\t\treturn this.param(REF_PARAM).replace(/[^a-zA-Z0-9_.:~-]+/g, '-');\n\t}\n\n\t_affidSubaffid() {\n\t\tconst affidCookie = this.cookie(AFFID_COOKIE);\n\t\tif (!affidCookie) return ['', ''];\n\n\t\tconst [affid, subaffid] = affidCookie.split('|');\n\t\treturn [affid || '', subaffid || ''];\n\t}\n\n\taffid() {\n\t\treturn this._affidSubaffid()[0];\n\t}\n\n\tsubaffid() {\n\t\treturn this._affidSubaffid()[1];\n\t}\n\n\tsubAffid() {\n\t\treturn this.subaffid();\n\t}\n\n\tcookieId() {\n\t\tlet cookieId = this.cookie(COOKIEID_COOKIE);\n\t\tif (!cookieId) {\n\t\t\tconst version = '1';\n\t\t\tcookieId = version + randomId(15);\n\t\t\tthis.cookie(COOKIEID_COOKIE, cookieId, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\treturn cookieId;\n\t}\n\n\t// cookie id that's existing (not set in this request)\n\texistingCookieId() {\n\t\treturn this.ctx.cookies.get(COOKIEID_COOKIE);\n\t}\n\n\tsessionId() {\n\t\tlet sessionId = this.cookie(SESSIONID_COOKIE);\n\t\tif (!sessionId) {\n\t\t\tconst version = '1';\n\t\t\tsessionId = version + randomId(15);\n\t\t\tthis.cookie(SESSIONID_COOKIE, sessionId, {\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\treturn sessionId;\n\t}\n\n\t// session id that's existing (not set in this request)\n\t// TODO: testing & take params into account\n\texistingSessionId() {\n\t\treturn this.ctx.cookies.get(SESSIONID_COOKIE);\n\t}\n\n\tisOriginRequest() {\n\t\tif (!isProduction) return true;\n\n\t\tconst baseDomain = this.options.baseDomain;\n\t\tif (!baseDomain) return true;\n\n\t\tconst origin = this.ctx.headers.origin;\n\n\t\t// this is needed because firefox currently does not\n\t\t// send origin with form submit requests (sends with xhr)\n\t\t// so this might cause csrf on firefox\n\t\tif (!origin) return true;\n\n\t\tconst matches = origin.match(/^((http|https):\\/\\/)?([^/:]+)[/]?/i);\n\t\tif (!matches) return false;\n\t\tif (('.' + matches[3]).endsWith('.' + baseDomain)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\tip() {\n\t\tif (!this._ip) {\n\t\t\tif (this.trackingHeader('ip')) {\n\t\t\t\tthis._ip = this.trackingHeader('ip');\n\t\t\t}\n\t\t\t// nginx proxy sets x-real-ip header as real ip address\n\t\t\telse if (this.ctx.headers['x-real-ip']) {\n\t\t\t\tthis._ip = this.ctx.headers['x-real-ip'];\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// ip is of format ::ffff:127.1.0.1, strip ::ffff: from it\n\t\t\t\tthis._ip = this.ctx.ip.replace(/^.*:/, '');\n\t\t\t}\n\t\t}\n\n\t\treturn this._ip;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\trealIP() {\n\t\tif (this._realIP) return this._realIP;\n\n\t\tconst forwardedFor = this.header('x-forwarded-for');\n\t\tif (!forwardedFor) {\n\t\t\tthis._realIP = this.ip();\n\t\t\treturn this._realIP;\n\t\t}\n\n\t\tconst forwardedList = forwardedFor.split(',');\n\t\tfor (let ipAddress of forwardedList) {\n\t\t\tipAddress = ipAddress.trim();\n\n\t\t\t// validate ipv4\n\t\t\tconst ipv4Regex = /(::ffff:)?(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})/;\n\t\t\tconst matches = ipAddress.match(ipv4Regex);\n\t\t\tif (!matches) continue;\n\t\t\tipAddress = matches[2];\n\n\t\t\t// validate not private\n\t\t\tif (IPRange.isPrivateIp(ipAddress)) continue;\n\t\t\tthis._realIP = ipAddress;\n\t\t\treturn this._realIP;\n\t\t}\n\n\t\tthis._realIP = this.ip();\n\t\treturn this._realIP;\n\t}\n\n\t/**\n\t * Is being proxied through local proxy\n\t */\n\tisProxied() {\n\t\treturn this.header('x-real-ip') && this.ctx.ip.endsWith('127.0.0.1');\n\t}\n\n\t/**\n\t * Is request being proxied through an external proxy (like chrome data-saver)\n\t */\n\tisVia() {\n\t\treturn this.header('via') && this.realIP() === this.ip();\n\t}\n\n\tparseUrl() {\n\t\tif (!this._uri) {\n\t\t\tthis._uri = new URL(this.ctx.href);\n\t\t}\n\t\treturn this._uri;\n\t}\n\n\tbaseDomain() {\n\t\tif (!this._domainParts) {\n\t\t\tthis._domainParts = getDomainParts(this.domain());\n\t\t}\n\t\treturn this._domainParts[1];\n\t}\n\n\tdomain() {\n\t\treturn this.ctx.hostname;\n\t}\n\n\tsubDomain() {\n\t\tif (!this._domainParts) {\n\t\t\tthis._domainParts = getDomainParts(this.domain());\n\t\t}\n\t\treturn this._domainParts[0];\n\t}\n\n\tport() {\n\t\tconst port = this.ctx.host.split(':')[1];\n\t\treturn port ? Number(port) : 80;\n\t}\n\n\tisLocalhost() {\n\t\treturn IPRange.isLocalhost(this.ip());\n\t}\n\n\tisGoogleIP() {\n\t\treturn IPRange.isGoogleIp(this.ip());\n\t}\n\n\tisSearchIP() {\n\t\treturn IPRange.isSearchIp(this.ip());\n\t}\n\n\tisGet() {\n\t\treturn this.ctx.method.toLowerCase() === 'get';\n\t}\n\n\tisPost() {\n\t\treturn this.ctx.method.toLowerCase() === 'post';\n\t}\n\n\tisGetLike() {\n\t\treturn ['get', 'head', 'options'].includes(this.ctx.method.toLowerCase());\n\t}\n\n\tisAjax() {\n\t\tif (this.ctx.headers['x-requested-with']) {\n\t\t\treturn this.ctx.headers['x-requested-with'].toLowerCase() === 'xmlhttprequest';\n\t\t}\n\t\treturn false;\n\t}\n\n\tisJSEnabled() {\n\t\treturn !!this.cookie('js');\n\t}\n\n\tnextUrl() {\n\t\tconst ctx = this.ctx;\n\t\tif (ctx.query.next) return ctx.query.next;\n\n\t\tconst paramNext = this.param('next');\n\t\tif (paramNext) return paramNext;\n\n\t\tif (!['/login', '/logout', '/signup', '/user/oauth'].includes(ctx.path)) {\n\t\t\treturn ctx.url;\n\t\t}\n\n\t\treturn '/';\n\t}\n\n\tlogoutUrl(redirectUrl = null) {\n\t\tconst nextUrl = redirectUrl || this.nextUrl();\n\t\treturn '/logout?next=' + encodeURIComponent(nextUrl);\n\t}\n\n\tloginUrl() {\n\t\treturn '/login?next=' + encodeURIComponent(this.nextUrl());\n\t}\n\n\tsignupUrl() {\n\t\treturn '/signup?next=' + encodeURIComponent(this.nextUrl());\n\t}\n\n\tmobileUrl() {\n\t\treturn addQuery(this.ctx.url, {[PLATFORM_PARAM]: 'mobile'});\n\t}\n\n\tdesktopUrl() {\n\t\treturn addQuery(this.ctx.url, {[PLATFORM_PARAM]: 'desktop'});\n\t}\n\n\tredirect(url, qs = true) {\n\t\tif (qs === true) {\n\t\t\turl = addQuery(url, this.ctx.querystring);\n\t\t}\n\t\telse if (typeof qs === 'object' || typeof qs === 'string') {\n\t\t\turl = addQuery(url, qs);\n\t\t}\n\n\t\treturn this.ctx.redirect(url);\n\t}\n\n\tredirectPermanent(url, qs = true) {\n\t\tif (qs === true) {\n\t\t\turl = addQuery(url, this.ctx.querystring);\n\t\t}\n\t\telse if (typeof qs === 'object' || typeof qs === 'string') {\n\t\t\turl = addQuery(url, qs);\n\t\t}\n\n\t\tthis.ctx.status = 301;\n\t\treturn this.ctx.redirect(url);\n\t}\n\n\t// this is when user visits an out link from our app\n\t// we send all the cookies as params\n\t// so we need to set the cookies on browser if they don't exist\n\tasync setCookiesFromParams() {\n\t\tconst query = this.ctx.query;\n\n\t\tconst utmCookie = query[COOKIE_PARAM_PREFIX + UTM_COOKIE];\n\t\tif (utmCookie) {\n\t\t\tthis.cookie(UTM_COOKIE, utmCookie, {\n\t\t\t\tmaxAge: ONE_MONTH,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst idCookie = query[COOKIE_PARAM_PREFIX + COOKIEID_COOKIE];\n\t\tif (idCookie) {\n\t\t\tthis.cookie(COOKIEID_COOKIE, idCookie, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst sessionIdCookie = query[COOKIE_PARAM_PREFIX + SESSIONID_COOKIE];\n\t\tif (sessionIdCookie) {\n\t\t\tthis.cookie(SESSIONID_COOKIE, sessionIdCookie, {\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst affidCookie = query[COOKIE_PARAM_PREFIX + AFFID_COOKIE];\n\t\tif (affidCookie) {\n\t\t\tthis.cookie(AFFID_COOKIE, affidCookie, {\n\t\t\t\tmaxAge: ONE_DAY,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst countryCookie = query[COOKIE_PARAM_PREFIX + COUNTRY_COOKIE];\n\t\tif (countryCookie) {\n\t\t\tthis.cookie(COUNTRY_COOKIE, countryCookie, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t});\n\t\t}\n\t}\n\n\tparseReferer() {\n\t\tconst referer = this.referer();\n\t\tif (!referer) {\n\t\t\treturn {\n\t\t\t\tname: '',\n\t\t\t\tsource: 'direct',\n\t\t\t\tmedium: 'direct',\n\t\t\t\tterm: '',\n\t\t\t};\n\t\t}\n\n\t\tconst refererUri = new URL(referer);\n\t\tlet host = refererUri.hostname;\n\t\tconst baseDomain = this.baseDomain();\n\n\t\tif (host.endsWith(baseDomain)) {\n\t\t\treturn {\n\t\t\t\tname: host,\n\t\t\t\tsource: host,\n\t\t\t\tmedium: 'direct',\n\t\t\t\tterm: '',\n\t\t\t};\n\t\t}\n\n\t\thost = host.replace(/^(?:www|m|shop|mobile|lm|l)\\./, '')\n\t\t\t.replace('search.yahoo', 'yahoo');\n\n\t\t// extract search engine names\n\t\tconst searchRegex = /\\.(images\\.google|google|yahoo|bing|ask|duckduckgo|yandex|baidu|babylon|avg|wow|reliancenetconnect|webcrawler|inspsearch|speedbit|searches|search)\\./;\n\t\tconst matches = `.${host}`.match(searchRegex);\n\n\t\tif (matches) {\n\t\t\tlet refererSource = matches[1];\n\t\t\tif (['search', 'searches'].includes(refererSource)) {\n\t\t\t\trefererSource = host;\n\t\t\t}\n\n\t\t\tconst query = refererUri.query(true);\n\t\t\tconst term = query.q || query.searchfor || query.pq || 'not_available';\n\n\t\t\treturn {\n\t\t\t\tname: host,\n\t\t\t\tsource: refererSource,\n\t\t\t\tmedium: 'organic',\n\t\t\t\tterm: handleArray(term).trim(),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tname: host,\n\t\t\tsource: host,\n\t\t\tmedium: 'referral',\n\t\t\tterm: '',\n\t\t};\n\t}\n\n\tsetUTMCookie() {\n\t\tconst ctx = this.ctx;\n\n\t\tlet source = ctx.query.utm_source || '';\n\t\tlet medium = ctx.query.utm_medium || '';\n\t\tlet campaign = ctx.query.utm_campaign || '';\n\t\tlet term = ctx.query.utm_term || '';\n\t\tconst content = ctx.query.utm_content || '';\n\n\t\tif (ctx.query.gclid) {\n\t\t\tsource = source || 'google';\n\t\t\tmedium = medium || 'cpc';\n\t\t\tcampaign = campaign || 'google_cpc';\n\t\t}\n\n\t\tconst utmExists = Boolean(source || medium || campaign);\n\t\tconst referer = this.parseReferer();\n\t\tthis._refererName = referer.name;\n\n\t\tif (!utmExists) {\n\t\t\tsource = referer.source;\n\t\t\tmedium = referer.medium;\n\t\t\tterm = referer.term;\n\t\t}\n\n\t\t// if the medium is direct then only set cookie if it doesn't already exist\n\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n\t\tif (!shouldSetCookie) {\n\t\t\t// fix existing utm cookies (not urlencoded)\n\t\t\t// TODO: remove later\n\t\t\tconst existing = this.ctx.cookies.get(UTM_COOKIE) || '';\n\t\t\tif (!existing.includes('|')) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.cookie(\n\t\t\tUTM_COOKIE,\n\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n\t\t\t\tmaxAge: ONE_MONTH,\n\t\t\t\tdomain: '*',\n\t\t\t},\n\t\t);\n\t}\n\n\tsetAffidCookie() {\n\t\tconst affid = this.ctx.query[AFFID_PARAM];\n\t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n\t\tif (!affid) return;\n\n\t\tthis.cookie(\n\t\t\tAFFID_COOKIE,\n\t\t\tjoinCookieParts([affid, subaffid]), {\n\t\t\t\tmaxAge: AFFID_COOKIE_DURATION,\n\t\t\t\tdomain: '*',\n\t\t\t},\n\t\t);\n\t}\n\n\thandlePlatformModification() {\n\t\t// don't change platform in mobile apps\n\t\tif (this.isMobileApp()) return;\n\n\t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n\t\tconst setPlatformCookie = this.setPlatform(platform);\n\t\tif (setPlatformCookie) {\n\t\t\tthis.cookie(PLATFORM_COOKIE, platform, {\n\t\t\t\tmaxAge: PLATFORM_COOKIE_DURATION,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\t}\n\n\tipLocation() {\n\t\tif (this._ipLoc === undefined) {\n\t\t\tconst loc = GeoIP.getSync(this.ip()) || {};\n\t\t\tconst country = loc.country;\n\t\t\tconst city = loc.city;\n\t\t\tconst state = loc.subdivisions && loc.subdivisions[0];\n\t\t\tthis._ipLoc = {\n\t\t\t\tcountry: {\n\t\t\t\t\tname: (country && country.names.en) || '',\n\t\t\t\t\tisoCode: (country && country.iso_code) || '',\n\t\t\t\t},\n\t\t\t\tcity: {\n\t\t\t\t\tname: (city && city.names.en) || '',\n\t\t\t\t\tisoCode: (city && city.iso_code) || '',\n\t\t\t\t},\n\t\t\t\tstate: {\n\t\t\t\t\tname: (state && state.names.en) || '',\n\t\t\t\t\tisoCode: (state && state.iso_code) || '',\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn this._ipLoc;\n\t}\n\n\tipCountry() {\n\t\treturn this.ipLocation().country.name;\n\t}\n\n\tipCity() {\n\t\treturn this.ipLocation().city.name;\n\t}\n\n\tipState() {\n\t\treturn this.ipLocation().state.name;\n\t}\n\n\tgetABNumber(min = 0, max = false) {\n\t\tconst cookieId = this.cookieId() || 'nocookie';\n\t\tconst num = getIntegerKey(cookieId.substr(-4));\n\t\tif (max === false) return num;\n\t\treturn (num % ((max + 1) - min)) + min;\n\t}\n\n\tgetBot() {\n\t\tconst bots = [\n\t\t\t['googlebot', 'googlebot'],\n\t\t\t['googlepreview', 'google web preview'],\n\t\t\t['googlemobile', 'google wireless'],\n\t\t\t['googleadsbot', 'Mediapartners-Google'],\n\n\t\t\t['bingbot', 'bingbot', 'msnbot'],\n\n\t\t\t['yahoobot', 'yahoo'],\n\t\t\t['facebookbot', 'facebook'],\n\t\t\t['alexabot', 'ia_archiver'],\n\n\t\t\t['baidubot', 'baidu'],\n\t\t\t['twitterbot', 'twitter'],\n\n\t\t\t['bot', 'bot', 'spider', 'crawl', 'dig', 'search', 'http', 'url'],\n\t\t];\n\n\t\tconst userAgent = this.userAgent().toLowerCase();\n\t\tif (!userAgent) {\n\t\t\treturn 'emptybot';\n\t\t}\n\n\t\tfor (const bot of bots) {\n\t\t\tconst botName = bot[0];\n\t\t\tfor (let i = 1; i < bot.length; i++) {\n\t\t\t\tif (userAgent.includes(bot[i].toLowerCase())) {\n\t\t\t\t\treturn botName;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tisBot() {\n\t\treturn Boolean(this.getBot());\n\t}\n\n\tsupportsWebp() {\n\t\tconst accept = this.ctx.headers.accept || '';\n\t\treturn accept.includes('image/webp');\n\t}\n\n\tbestImageFormat(defaultFormat = 'jpg') {\n\t\tif (this.supportsWebp()) return 'webp';\n\t\treturn defaultFormat;\n\t}\n\n\thandleFlashMessage() {\n\t\tif (this.isAjax()) return;\n\n\t\tthis._flash = '';\n\t\tconst cookieVal = this.cookie(FLASH_COOKIE);\n\t\tif (!cookieVal) return;\n\n\t\ttry {\n\t\t\t// cookie format is `format:actualMessage`\n\t\t\tconst formatIndex = cookieVal.indexOf(':');\n\t\t\tconst format = cookieVal.substring(0, formatIndex);\n\t\t\tif (format !== 'json') {\n\t\t\t\tthrow new Error(`Unknown flash message format ${format}`);\n\t\t\t}\n\n\t\t\tthis._flash = JSON.parse(cookieVal.substring(formatIndex + 1));\n\t\t}\n\t\tcatch (e) {\n\t\t\tthis._flash = '';\n\t\t\tconsole.error('Error parsing flash message', e);\n\t\t}\n\n\t\tthis.cookie(FLASH_COOKIE, null);\n\t}\n\n\t/**\n\t * get or set a flash message (can be an object too)\n\t * @example\n\t * $req.flash('info', 'hello'); // set a flash message\n\t * $req.flash('info'); // get a flash message\n\t *\n\t * @param {string} key key of the flash message\n\t * @param {*} message message to set (skip to get the message)\n\t * @returns {*} flash message\n\t */\n\tflash(key, message) {\n\t\tif (message === undefined) {\n\t\t\treturn (this._flash && this._flash[key]) || '';\n\t\t}\n\n\t\t// ignore empty flash message\n\t\tif (!message) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet flashes = this._responseFlash;\n\t\tif (!flashes) {\n\t\t\tflashes = {};\n\t\t\tthis._responseFlash = flashes;\n\t\t}\n\n\t\tflashes[key] = message;\n\n\t\tconst maxAge = 3 * 60 * 1000; // valid till 3 minutes\n\t\tconst cookieValue = `json:${JSON.stringify(flashes)}`;\n\t\tthis.cookie(\n\t\t\tFLASH_COOKIE,\n\t\t\tcookieValue,\n\t\t\t{maxAge, httpOnly: false},\n\t\t);\n\t\treturn null;\n\t}\n\n\tstatic async getDummyCtx({\n\t\tparams = {},\n\t\tquery = {},\n\t\tstate = {},\n\t\tmethod = 'GET',\n\t\tpath = '/',\n\t\turl,\n\t\treq = false,\n\t} = {}) {\n\t\tconst context = {\n\t\t\tquery,\n\t\t\tparams,\n\t\t\tstate,\n\t\t\turl: url || path,\n\t\t\tpath,\n\t\t\tthrow: (...args) => {\n\t\t\t\tthrow new Error(args.join(' '));\n\t\t\t},\n\t\t\tmethod,\n\t\t\tsession: {},\n\t\t\theaders: {},\n\t\t\tcookies: new Map(),\n\t\t\thost: this.options.baseDomain || 'localhost',\n\t\t\thostname: this.options.baseDomain || 'localhost',\n\t\t\tredirect: (...args) => { console.warn('Redirect to', args) },\n\t\t\tset(key, val) {\n\t\t\t\tif (typeof key === 'string') {\n\t\t\t\t\tthis.headers[key] = val;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tObject.assign(this.headers, key);\n\t\t\t},\n\t\t};\n\t\tif (req) context.$req = await this.from(context);\n\t\treturn context;\n\t}\n}\n\nmodule.exports = Request;\n"
|
|
96
|
-
}
|
|
97
|
-
]
|
|
98
|
-
}
|
package/.lh/postinstall.js.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "postinstall.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 1,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622571930512,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"date": 1622571942798,
|
|
14
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -7,5 +7,6 @@\n main().then(() => {\n \tprocess.exit();\n }).catch((err) => {\n \tconsole.error('KoaRequest postinstall error', err);\n+\tprocess.exit(1);\n });\n"
|
|
15
|
-
}
|
|
16
|
-
],
|
|
17
|
-
"date": 1622571930512,
|
|
18
|
-
"name": "Commit-0",
|
|
19
|
-
"content": "const GeoIP = require('./GeoIP');\n\nasync function main() {\n\tawait GeoIP.init();\n}\n\nmain().then(() => {\n\tprocess.exit();\n}).catch((err) => {\n\tconsole.error('KoaRequest postinstall error', err);\n});\n"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|