Completed
Push — master ( e7fc88...cf2345 )
by Kyungmi
01:38
created

common-util.js ➔ ???   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 0 Features 0
Metric Value
cc 6
c 13
b 0
f 0
nc 12
dl 0
loc 49
rs 8.5906
nop 1
1
/**
2
 * Util functions
3
 *
4
 * @since 1.0.0
5
 */
6
7
/**
8
 * Get client IP address
9
 *
10
 * Will return 127.0.0.1 when testing locally
11
 * Useful when you need the user ip for geolocation or serving localized content
12
 *
13
 * @param request
14
 * @returns {string} ip
15
 */
16
exports.getClientIp = (request) => {
17
  // workaround to get real client IP
18
  // most likely because our app will be behind a [reverse] proxy or load balancer
19
  const clientIp = request.headers['x-client-ip'];
20
  // x-forwarded-for
21
  // (typically when your node app is behind a load-balancer (eg. AWS ELB) or proxy)
22
  //
23
  // x-forwarded-for may return multiple IP addresses in the format:
24
  // "client IP, proxy 1 IP, proxy 2 IP"
25
  // Therefore, the right-most IP address is the IP address of the most recent proxy
26
  // and the left-most IP address is the IP address of the originating client.
27
  // source: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
28
  const forwardedForAlt = request.headers['x-forwarded-for'];
29
  // x-real-ip
30
  // (default nginx proxy/fcgi)
31
  // alternative to x-forwarded-for, used by some proxies
32
  const realIp = request.headers['x-real-ip'];
33
34
  // more obsure ones below
35
36
  // x-cluster-client-ip
37
  // (Rackspace LB and Riverbed's Stingray)
38
  // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
39
  // https://splash.riverbed.com/docs/DOC-1926
40
  const clusterClientIp = request.headers['x-cluster-client-ip'];
41
  const forwardedAlt = request.headers['x-forwarded'];
42
  const forwardedFor = request.headers['forwarded-for'];
43
  const forwarded = request.headers['forwarded'];
44
45
  // remote address check
46
  const reqConnectionRemoteAddress = request.connection ? request.connection.remoteAddress : null;
47
  const reqSocketRemoteAddress = request.socket ? request.socket.remoteAddress : null;
48
  // remote address checks
49
  const reqConnectionSocketRemoteAddress = (request.connection && request.connection.socket)
50
    ? request.connection.socket.remoteAddress : null;
51
  const reqInfoRemoteAddress = request.info ? request.info.remoteAddress : null;
52
53
  return clientIp
54
    || (forwardedForAlt && forwardedForAlt.split(',')[0])
55
    || realIp
56
    || clusterClientIp
57
    || forwardedAlt
58
    || forwardedFor
59
    || forwarded
60
    || reqConnectionRemoteAddress
61
    || reqSocketRemoteAddress
62
    || reqConnectionSocketRemoteAddress
63
    || reqInfoRemoteAddress;
64
};
65
66
const regex = /([>=<]{1,2})([0-9a-zA-Z\-.]+)*/g;
67
68
const getComparator = (conditionStr) => {
69
  let result = conditionStr.charAt(0);
70
  if ('<>'.includes(result)) {
71
    result = '~';
72
  }
73
  return result;
74
};
75
76
const parsers = {
77
  '*': () => ({ comparator: '*' }),
78
  '=': (conditionStr) => {
79
    let execResult;
80
    if ((execResult = regex.exec(conditionStr)) !== null) {
81
      return { comparator: '=', version: execResult[2] };
82
    }
83
    return false;
84
  },
85
  '~': (conditionStr) => {
86
    const resultItem = { comparator: '~' };
87
    let execResult;
88
    while ((execResult = regex.exec(conditionStr)) !== null) {
89
      if (execResult[1] === '>=') {
90
        resultItem.versionStart = execResult[2];
91
      } else if (execResult[1] === '<') {
92
        resultItem.versionEnd = execResult[2];
93
      }
94
    }
95
    if (resultItem.versionStart || resultItem.versionEnd) {
96
      return resultItem;
97
    }
98
    return false;
99
  },
100
};
101
102
/**
103
 * Parse below "limited" formatted Semantic Version to an array for UI expression
104
 *  - equals: "=2.3", "=2", "=4.3.3"
105
 *  - range: ">=1.2.3 <3.0", ">=1.2.3", "<3.0.0"  (only permitted gte(>=) and lt(<) for ranges)
106
 *  - all: "*"
107
 *  - mixed (by OR): ">=1.2.3 <3.0.0 || <0.1.2 || >=5.1"
108
 * @param {string} conditionString
109
 * @return {Array}
110
 */
111
112
exports.parseSemVersion = (semVerString) => {
113
  if (!semVerString) {
114
    return [{ comparator: '*' }];   // default
115
  }
116
  const conditions = semVerString.split('||').map(cond => cond.trim());  // OR 연산을 기준으로 분리
117
  const result = [];
118
  conditions.forEach((cond) => {
119
    regex.lastIndex = 0;  // reset find index
120
    const comparator = getComparator(cond);
121
    const parsedObj = parsers[comparator](cond);
122
    if (parsedObj) {
123
      result.push(parsedObj);
124
    }
125
  });
126
  return result;
127
};
128
129
/**
130
 * Stringify parsed version conditions to SemVer representation
131
 * @param {Array} parsedConditions
132
 * @return {string}
133
 */
134
exports.stringifySemVersion = (parsedConditions) => {
135
  const result = parsedConditions.map((cond) => {
136
    switch (cond.comparator) {
137
      case '*':
138
        return '*';
139
      case '=':
140
        return `=${cond.version}`;
141
      case '~':
142
        return `${cond.versionStart ? `>=${cond.versionStart}` : ''} ${cond.versionEnd ? `<${cond.versionEnd}` : ''}`;
143
      default:
144
        return '';
145
    }
146
  });
147
  if (result.includes('*')) {
148
    return '*';
149
  }
150
  return result.filter(cond => !!cond).join(' || ').trim();
151
};
152
153
/**
154
 * Replace camelCase string to snake_case
155
 * @param {string} str
156
 * @returns {string}
157
 */
158
exports.camel2snake = (str) => {
159
  if (typeof str === 'string') {
160
    // ignore first occurrence of underscore(_) and capital
161
    return str.replace(/^_/, '').replace(/^([A-Z])/, $1 => `${$1.toLowerCase()}`).replace(/([A-Z])/g, $1 => `_${$1.toLowerCase()}`);
162
  }
163
  return str;
164
};
165
166
/**
167
 * Replace camelCase string to snake_case on the property names in objects
168
 * @param {Array|Object} object
169
 * @returns {*}
170
 */
171
exports.camel2snakeObject = (object) => {
172
  if (object instanceof Array) {
173
    const result = [];
174
    for (let i = 0, n = object.length; i < n; i++) {
175
      result[i] = exports.camel2snakeObject(object[i]);
176
    }
177
    return result;
178
  } else if (object && typeof object === 'object') {
179
    const result = {};
180
    for (let prop in object) {
181
      if (Object.prototype.hasOwnProperty.call(object, prop)) {
182
        result[exports.camel2snake(prop)] = exports.camel2snakeObject(object[prop]);
183
      }
184
    }
185
    return result;
186
  }
187
  return object;
188
};
189
190
/**
191
 * Replace snake_case string to camelCase
192
 * @param {string} str
193
 * @returns {string}
194
 */
195
exports.snake2camel = (str) => {
196
  if (typeof str === 'string') {
197
    // ignore first occurrence of underscore(_)
198
    return str.replace(/^_/, '').replace(/_([a-z0-9]?)/g, ($1, $2) => `${$2.toUpperCase()}`);
199
  }
200
  return str;
201
};
202
203
/**
204
 * Replace snake_case string to camelCase on the property names in objects
205
 * @param {Array|Object} object
206
 * @returns {*}
207
 */
208
exports.snake2camelObject = (object) => {
209
  if (object instanceof Array) {
210
    const result = [];
211
    for (let i = 0, n = object.length; i < n; i++) {
212
      result[i] = exports.snake2camelObject(object[i]);
213
    }
214
    return result;
215
  } else if (object && typeof object === 'object') {
216
    const result = {};
217
    for (let prop in object) {
218
      if (Object.prototype.hasOwnProperty.call(object, prop)) {
219
        result[exports.snake2camel(prop)] = exports.snake2camelObject(object[prop]);
220
      }
221
    }
222
    return result;
223
  }
224
  return object;
225
};
226