@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 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
- // initialize a request
328
- // set important cookies and all
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.httpOnly && !isHttp) {
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
- if (browserName === 'Chrome') return 'Chrome Mobile';
489
- if (browserName === 'Firefox') return 'Firefox Mobile';
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",
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.1.1",
36
- "koa-send": "^5.0.0",
37
- "koa2-ratelimit": "^0.9.0",
38
- "maxmind": "^4.1.1",
39
- "tar": "^6.0.2",
40
- "ua-parser-js": "^0.7.21"
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",
@@ -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)
@@ -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
- }
@@ -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 '&lt;';\n\t\t\t\tcase '>': return '&gt;';\n\t\t\t\tcase '\"': return '&quot;';\n\t\t\t\tcase '\\'': return '&#39;';\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
- }
@@ -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
- }