ee-core 2.0.3 → 2.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/bin/tools.js +4 -0
  2. package/config/config.default.js +9 -0
  3. package/const/channel.js +10 -1
  4. package/core/lib/ee.js +1 -1
  5. package/core/lib/loader/file_loader.js +2 -2
  6. package/core/lib/loader/mixin/config.js +1 -1
  7. package/core/lib/utils/index.js +1 -1
  8. package/ee/eeApp.js +8 -7
  9. package/exception/index.js +40 -12
  10. package/httpclient/index.js +4 -12
  11. package/index.js +1 -1
  12. package/jobs/child/app.js +11 -2
  13. package/jobs/child/forkProcess.js +81 -6
  14. package/jobs/child/index.js +41 -45
  15. package/jobs/child-pool/index.js +205 -0
  16. package/jobs/index.js +3 -1
  17. package/jobs/load-balancer/algorithm/index.js +12 -0
  18. package/jobs/load-balancer/algorithm/minimumConnection.js +19 -0
  19. package/jobs/load-balancer/algorithm/polling.js +12 -0
  20. package/jobs/load-balancer/algorithm/random.js +10 -0
  21. package/jobs/load-balancer/algorithm/specify.js +15 -0
  22. package/jobs/load-balancer/algorithm/weights.js +22 -0
  23. package/jobs/load-balancer/algorithm/weightsMinimumConnection.js +30 -0
  24. package/jobs/load-balancer/algorithm/weightsPolling.js +23 -0
  25. package/jobs/load-balancer/algorithm/weightsRandom.js +17 -0
  26. package/jobs/load-balancer/consts.js +10 -0
  27. package/jobs/load-balancer/index.js +202 -0
  28. package/jobs/load-balancer/scheduler.js +32 -0
  29. package/loader/index.js +22 -2
  30. package/message/childMessage.js +23 -0
  31. package/package.json +1 -6
  32. package/ps/index.js +44 -0
  33. package/tools/encrypt.js +105 -45
  34. package/utils/co.js +237 -0
  35. package/utils/depd/index.js +538 -0
  36. package/utils/depd/lib/browser/index.js +77 -0
  37. package/utils/extend.js +73 -0
  38. package/utils/get-port/index.d.ts +64 -0
  39. package/utils/get-port/index.js +109 -0
  40. package/utils/helper.js +25 -1
  41. package/utils/index.js +46 -0
  42. package/utils/ip.js +261 -0
  43. package/utils/is.js +2 -1
  44. package/utils/time/index.js +20 -0
  45. package/utils/time/ms.js +162 -0
  46. package/jobs/childPool/app.js +0 -62
  47. package/jobs/childPool/forkProcess.js +0 -81
  48. package/jobs/childPool/index.js +0 -71
  49. package/jobs/childPool/pool.js +0 -67
  50. /package/{oldUtils → old-utils}/index.js +0 -0
@@ -9,8 +9,18 @@ class ChildMessage {
9
9
  * 向主进程发消息
10
10
  */
11
11
  sendToMain(eventName, params = {}) {
12
+ let receiver = Channel.receiver.childJob;
13
+ return this.send(eventName, params, receiver);
14
+ }
15
+
16
+ /**
17
+ * 向主进程发消息
18
+ */
19
+ send(eventName, params = {}, receiver) {
20
+ let eventReceiver = receiver || Channel.receiver.forkProcess;
12
21
  let message = {
13
22
  channel: Channel.process.sendToMain,
23
+ eventReceiver,
14
24
  event: eventName,
15
25
  data: params,
16
26
  }
@@ -24,6 +34,19 @@ class ChildMessage {
24
34
  exit(code = 0) {
25
35
  return process.exit(code);
26
36
  }
37
+
38
+ /**
39
+ * 发送错误到控制台
40
+ */
41
+ sendErrorToTerminal(err) {
42
+ let errTips = (err && typeof err == 'object') ? err.toString() : '';
43
+ errTips += ' Error !!! Please See file ee-core.log or ee-error-xxx.log for details !'
44
+ let message = {
45
+ channel: Channel.process.showException,
46
+ data: errTips
47
+ }
48
+ process.send(message);
49
+ }
27
50
  }
28
51
 
29
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ee-core",
3
- "version": "2.0.3",
3
+ "version": "2.1.0-beta.3",
4
4
  "description": "ee core",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,16 +14,11 @@
14
14
  "dependencies": {
15
15
  "agentkeepalive": "^4.2.0",
16
16
  "bytenode": "^1.3.6",
17
- "co": "^4.6.0",
18
17
  "debug": "^4.3.3",
19
- "depd": "^2.0.0",
20
18
  "egg-errors": "^2.3.0",
21
19
  "egg-logger": "^2.7.1",
22
- "extend2": "^1.0.1",
23
20
  "fs-extra": "^10.0.0",
24
- "get-port": "^5.1.1",
25
21
  "globby": "^10.0.0",
26
- "humanize-ms": "^1.2.1",
27
22
  "is-type-of": "^1.2.1",
28
23
  "javascript-obfuscator": "^4.0.0",
29
24
  "koa": "^2.13.4",
package/ps/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const path = require('path');
2
2
  const eis = require('../utils/is');
3
+ const Log = require('../log');
3
4
 
4
5
  /**
5
6
  * 当前进程的所有env
@@ -238,4 +239,47 @@ exports.makeMessage = function(msg = {}) {
238
239
  }, msg);
239
240
 
240
241
  return message;
242
+ }
243
+
244
+ /**
245
+ * 退出ChildJob进程
246
+ */
247
+ exports.exitChildJob = function(code = 0) {
248
+ try {
249
+ let args = JSON.parse(process.argv[2]);
250
+ if (args.type == 'childJob') {
251
+ process.exit(code);
252
+ }
253
+ } catch (e) {
254
+ process.exit(code);
255
+ }
256
+ }
257
+
258
+
259
+ /**
260
+ * 任务类型 ChildJob
261
+ */
262
+ exports.isChildJob = function() {
263
+ try {
264
+ let args = JSON.parse(process.argv[2]);
265
+ if (args.type == 'childJob') {
266
+ return true;
267
+ }
268
+ } catch (e) {
269
+ return false;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * 任务类型 ChildPoolJob
275
+ */
276
+ exports.isChildPoolJob = function() {
277
+ try {
278
+ let args = JSON.parse(process.argv[2]);
279
+ if (args.type == 'childPoolJob') {
280
+ return true;
281
+ }
282
+ } catch (e) {
283
+ return false;
284
+ }
241
285
  }
package/tools/encrypt.js CHANGED
@@ -7,6 +7,7 @@ const is = require('is-type-of');
7
7
  const bytenode = require('bytenode');
8
8
  const crypto = require('crypto');
9
9
  const JavaScriptObfuscator = require('javascript-obfuscator');
10
+ const globby = require('globby');
10
11
  const UtilsJson = require('../utils/json');
11
12
 
12
13
  class Encrypt {
@@ -16,11 +17,14 @@ class Encrypt {
16
17
  this.encryptCodeDir = path.join(this.basePath, 'public');
17
18
  this.config = this.loadConfig('encrypt.js');
18
19
  this.filesExt = this.config.fileExt || ['.js'];
19
- this.type = this.config.type || 'bytecode';
20
+ this.type = this.config.type || 'confusion';
20
21
  this.bOpt = this.config.bytecodeOptions || {};
21
22
  this.cOpt = this.config.confusionOptions || {};
23
+ this.cleanFiles = this.config.cleanFiles || ['electron'];
22
24
 
23
25
  const directory = this.config.directory || ['electron'];
26
+ this.patterns = this.config.files || null;
27
+ this.specificFiles = [ 'electron/preload/bridge.js' ];
24
28
  this.tmpFile = ''; // todo
25
29
  this.mapFile = ''; // todo
26
30
 
@@ -34,49 +38,80 @@ class Encrypt {
34
38
  }
35
39
  }
36
40
 
37
- // 检查存在的目录
38
41
  for (let i = 0; i < directory.length; i++) {
39
42
  let codeDirPath = path.join(this.basePath, directory[i]);
40
43
  if (fs.existsSync(codeDirPath)) {
41
44
  this.dirs.push(directory[i]);
42
45
  }
43
46
  }
44
- console.log('[ee-core] [tools/encrypt] dirs:', this.dirs);
47
+
48
+ this.codefiles = this._initCodeFiles();
49
+ //console.log('[ee-core] [tools/encrypt] codefiles:', this.codefiles);
45
50
  }
46
51
 
47
52
  /**
48
- * 备份 electron 目录代码
53
+ * 初始化需要加密的文件列表
49
54
  */
50
- backup () {
51
- console.log('[ee-core] [tools/encrypt] backup start');
52
-
53
- for (let i = 0; i < this.dirs.length; i++) {
54
- // check code dir
55
- let codeDirPath = path.join(this.basePath, this.dirs[i]);
56
- if (!fs.existsSync(codeDirPath)) {
57
- console.log('[ee-core] [tools/encrypt] ERROR: backup %s is not exist', codeDirPath);
58
- return
59
- }
55
+ _initCodeFiles() {
56
+ if (!this.patterns) return;
60
57
 
61
- let targetDir = path.join(this.encryptCodeDir, this.dirs[i]);
58
+ const files = globby.sync(this.patterns, { cwd: this.basePath });
59
+ return files;
60
+ }
62
61
 
63
- // remove old
64
- this.rmBackup(targetDir);
62
+ /**
63
+ * 备份代码
64
+ */
65
+ backup() {
66
+ // clean
67
+ this.cleanCode();
65
68
 
66
- // copy
67
- console.log('[ee-core] [tools/encrypt] backup target Dir:', targetDir);
68
- if (!fs.existsSync(targetDir)) {
69
- this.mkdir(targetDir);
70
- this.chmodPath(targetDir, '777');
69
+ console.log('[ee-core] [tools/encrypt] backup start');
70
+ if (this.patterns) {
71
+ this.codefiles.forEach((filepath) => {
72
+ let source = path.join(this.basePath, filepath);
73
+ if (fs.existsSync(source)) {
74
+ let target = path.join(this.encryptCodeDir, filepath);
75
+ fsPro.copySync(source, target);
76
+ }
77
+ })
78
+ } else {
79
+ for (let i = 0; i < this.dirs.length; i++) {
80
+ // check code dir
81
+ let codeDirPath = path.join(this.basePath, this.dirs[i]);
82
+ if (!fs.existsSync(codeDirPath)) {
83
+ console.log('[ee-core] [tools/encrypt] ERROR: backup %s is not exist', codeDirPath);
84
+ return
85
+ }
86
+
87
+ // copy
88
+ let targetDir = path.join(this.encryptCodeDir, this.dirs[i]);
89
+ console.log('[ee-core] [tools/encrypt] backup target Dir:', targetDir);
90
+ if (!fs.existsSync(targetDir)) {
91
+ this.mkdir(targetDir);
92
+ this.chmodPath(targetDir, '777');
93
+ }
94
+
95
+ fsPro.copySync(codeDirPath, targetDir);
71
96
  }
72
-
73
- fsPro.copySync(codeDirPath, targetDir);
74
97
  }
98
+
75
99
  console.log('[ee-core] [tools/encrypt] backup end');
76
100
  return true;
77
101
  }
78
102
 
79
- prepare () {
103
+ /**
104
+ * 清除加密代码
105
+ */
106
+ cleanCode() {
107
+ this.cleanFiles.forEach((file) => {
108
+ let tmpFile = path.join(this.encryptCodeDir, file);
109
+ this.rmBackup(tmpFile);
110
+ console.log('[ee-core] [tools/encrypt] clean up tmp files:', tmpFile);
111
+ })
112
+ }
113
+
114
+ prepare() {
80
115
  if (this.type == 'bytecode') {
81
116
  let filename = this.config.mangle || this.config.mangle.file || null;
82
117
  if (filename) {
@@ -96,11 +131,28 @@ class Encrypt {
96
131
  /**
97
132
  * 加密代码
98
133
  */
99
- encrypt () {
134
+ encrypt() {
100
135
  console.log('[ee-core] [tools/encrypt] start ciphering');
101
- for (let i = 0; i < this.dirs.length; i++) {
102
- let codeDirPath = path.join(this.encryptCodeDir, this.dirs[i]);
103
- this.loop(codeDirPath);
136
+ if (this.patterns) {
137
+ for (const file of this.codefiles) {
138
+ const fullpath = path.join(this.encryptCodeDir, file);
139
+ if (!fs.statSync(fullpath).isFile()) continue;
140
+
141
+ // 特殊文件处理
142
+ if (this.specificFiles.includes(file)) {
143
+ this.generate(fullpath, 'confusion');
144
+ continue;
145
+ }
146
+
147
+ this.generate(fullpath);
148
+ }
149
+ } else {
150
+ console.log('[ee-core] [tools/encrypt] !!!!!! please use the new encryption method !!!!!!');
151
+ for (let i = 0; i < this.dirs.length; i++) {
152
+ let codeDirPath = path.join(this.encryptCodeDir, this.dirs[i]);
153
+ this.loop(codeDirPath);
154
+ }
155
+ console.log('[ee-core] [tools/encrypt] !!!!!! please use the new encryption method !!!!!!');
104
156
  }
105
157
 
106
158
  console.log('[ee-core] [tools/encrypt] end ciphering');
@@ -109,7 +161,7 @@ class Encrypt {
109
161
  /**
110
162
  * 递归
111
163
  */
112
- loop (dirPath) {
164
+ loop(dirPath) {
113
165
  let files = [];
114
166
  if (fs.existsSync(dirPath)) {
115
167
  files = fs.readdirSync(dirPath);
@@ -130,10 +182,13 @@ class Encrypt {
130
182
  /**
131
183
  * 生成文件
132
184
  */
133
- generate (curPath) {
134
- if (this.type == 'bytecode') {
185
+ generate(curPath, type) {
186
+ let encryptType = type ? type : this.type;
187
+ console.log(`[ee-core] [tools/encrypt] file: ${curPath} (${encryptType})`);
188
+
189
+ if (encryptType == 'bytecode') {
135
190
  this.generateBytecodeFile(curPath);
136
- } else if (this.type == 'confusion') {
191
+ } else if (encryptType == 'confusion') {
137
192
  this.generateJSConfuseFile(curPath);
138
193
  } else {
139
194
  this.generateJSConfuseFile(curPath);
@@ -144,7 +199,7 @@ class Encrypt {
144
199
  /**
145
200
  * 使用 javascript-obfuscator 生成压缩/混淆文件
146
201
  */
147
- generateJSConfuseFile (file) {
202
+ generateJSConfuseFile(file) {
148
203
  let opt = Object.assign({
149
204
  compact: true,
150
205
  stringArray: true,
@@ -159,7 +214,7 @@ class Encrypt {
159
214
  /**
160
215
  * 生成字节码文件
161
216
  */
162
- generateBytecodeFile (curPath) {
217
+ generateBytecodeFile(curPath) {
163
218
  if (path.extname(curPath) !== '.js') {
164
219
  return
165
220
  }
@@ -181,10 +236,9 @@ class Encrypt {
181
236
  /**
182
237
  * 移除备份
183
238
  */
184
- rmBackup (dir) {
185
- if (fs.existsSync(dir)) {
186
- console.log('[ee-core] [tools/encrypt] clean old directory:', dir);
187
- fsPro.removeSync(dir);
239
+ rmBackup(file) {
240
+ if (fs.existsSync(file)) {
241
+ fsPro.removeSync(file);
188
242
  }
189
243
  return;
190
244
  }
@@ -192,7 +246,7 @@ class Encrypt {
192
246
  /**
193
247
  * 检查文件是否存在
194
248
  */
195
- fileExist (filePath) {
249
+ fileExist(filePath) {
196
250
  try {
197
251
  return fs.statSync(filePath).isFile();
198
252
  } catch (err) {
@@ -200,7 +254,7 @@ class Encrypt {
200
254
  }
201
255
  };
202
256
 
203
- mkdir (dirpath, dirname) {
257
+ mkdir(dirpath, dirname) {
204
258
  // 判断是否是第一次调用
205
259
  if (typeof dirname === 'undefined') {
206
260
  if (fs.existsSync(dirpath)) {
@@ -222,7 +276,7 @@ class Encrypt {
222
276
  }
223
277
  };
224
278
 
225
- chmodPath (path, mode) {
279
+ chmodPath(path, mode) {
226
280
  let files = [];
227
281
  if (fs.existsSync(path)) {
228
282
  files = fs.readdirSync(path);
@@ -238,7 +292,7 @@ class Encrypt {
238
292
  }
239
293
  };
240
294
 
241
- loadConfig (prop) {
295
+ loadConfig(prop) {
242
296
  const filepath = path.join(this.basePath, 'electron', 'config', prop);
243
297
  if (!fs.existsSync(filepath)) {
244
298
  return {};
@@ -254,7 +308,7 @@ class Encrypt {
254
308
  return ret || {};
255
309
  };
256
310
 
257
- md5 (file) {
311
+ md5(file) {
258
312
  const buffer = fs.readFileSync(file);
259
313
  const hash = crypto.createHash('md5');
260
314
  hash.update(buffer, 'utf8');
@@ -270,6 +324,12 @@ const run = () => {
270
324
  e.encrypt();
271
325
  }
272
326
 
327
+ const clean = () => {
328
+ const e = new Encrypt();
329
+ e.cleanCode();
330
+ }
331
+
273
332
  module.exports = {
274
- run
333
+ run,
334
+ clean,
275
335
  };
package/utils/co.js ADDED
@@ -0,0 +1,237 @@
1
+
2
+ /**
3
+ * slice() reference.
4
+ */
5
+
6
+ var slice = Array.prototype.slice;
7
+
8
+ /**
9
+ * Expose `co`.
10
+ */
11
+
12
+ module.exports = co['default'] = co.co = co;
13
+
14
+ /**
15
+ * Wrap the given generator `fn` into a
16
+ * function that returns a promise.
17
+ * This is a separate function so that
18
+ * every `co()` call doesn't create a new,
19
+ * unnecessary closure.
20
+ *
21
+ * @param {GeneratorFunction} fn
22
+ * @return {Function}
23
+ * @api public
24
+ */
25
+
26
+ co.wrap = function (fn) {
27
+ createPromise.__generatorFunction__ = fn;
28
+ return createPromise;
29
+ function createPromise() {
30
+ return co.call(this, fn.apply(this, arguments));
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Execute the generator function or a generator
36
+ * and return a promise.
37
+ *
38
+ * @param {Function} fn
39
+ * @return {Promise}
40
+ * @api public
41
+ */
42
+
43
+ function co(gen) {
44
+ var ctx = this;
45
+ var args = slice.call(arguments, 1)
46
+
47
+ // we wrap everything in a promise to avoid promise chaining,
48
+ // which leads to memory leak errors.
49
+ // see https://github.com/tj/co/issues/180
50
+ return new Promise(function(resolve, reject) {
51
+ if (typeof gen === 'function') gen = gen.apply(ctx, args);
52
+ if (!gen || typeof gen.next !== 'function') return resolve(gen);
53
+
54
+ onFulfilled();
55
+
56
+ /**
57
+ * @param {Mixed} res
58
+ * @return {Promise}
59
+ * @api private
60
+ */
61
+
62
+ function onFulfilled(res) {
63
+ var ret;
64
+ try {
65
+ ret = gen.next(res);
66
+ } catch (e) {
67
+ return reject(e);
68
+ }
69
+ next(ret);
70
+ }
71
+
72
+ /**
73
+ * @param {Error} err
74
+ * @return {Promise}
75
+ * @api private
76
+ */
77
+
78
+ function onRejected(err) {
79
+ var ret;
80
+ try {
81
+ ret = gen.throw(err);
82
+ } catch (e) {
83
+ return reject(e);
84
+ }
85
+ next(ret);
86
+ }
87
+
88
+ /**
89
+ * Get the next value in the generator,
90
+ * return a promise.
91
+ *
92
+ * @param {Object} ret
93
+ * @return {Promise}
94
+ * @api private
95
+ */
96
+
97
+ function next(ret) {
98
+ if (ret.done) return resolve(ret.value);
99
+ var value = toPromise.call(ctx, ret.value);
100
+ if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
101
+ return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
102
+ + 'but the following object was passed: "' + String(ret.value) + '"'));
103
+ }
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Convert a `yield`ed value into a promise.
109
+ *
110
+ * @param {Mixed} obj
111
+ * @return {Promise}
112
+ * @api private
113
+ */
114
+
115
+ function toPromise(obj) {
116
+ if (!obj) return obj;
117
+ if (isPromise(obj)) return obj;
118
+ if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
119
+ if ('function' == typeof obj) return thunkToPromise.call(this, obj);
120
+ if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
121
+ if (isObject(obj)) return objectToPromise.call(this, obj);
122
+ return obj;
123
+ }
124
+
125
+ /**
126
+ * Convert a thunk to a promise.
127
+ *
128
+ * @param {Function}
129
+ * @return {Promise}
130
+ * @api private
131
+ */
132
+
133
+ function thunkToPromise(fn) {
134
+ var ctx = this;
135
+ return new Promise(function (resolve, reject) {
136
+ fn.call(ctx, function (err, res) {
137
+ if (err) return reject(err);
138
+ if (arguments.length > 2) res = slice.call(arguments, 1);
139
+ resolve(res);
140
+ });
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Convert an array of "yieldables" to a promise.
146
+ * Uses `Promise.all()` internally.
147
+ *
148
+ * @param {Array} obj
149
+ * @return {Promise}
150
+ * @api private
151
+ */
152
+
153
+ function arrayToPromise(obj) {
154
+ return Promise.all(obj.map(toPromise, this));
155
+ }
156
+
157
+ /**
158
+ * Convert an object of "yieldables" to a promise.
159
+ * Uses `Promise.all()` internally.
160
+ *
161
+ * @param {Object} obj
162
+ * @return {Promise}
163
+ * @api private
164
+ */
165
+
166
+ function objectToPromise(obj){
167
+ var results = new obj.constructor();
168
+ var keys = Object.keys(obj);
169
+ var promises = [];
170
+ for (var i = 0; i < keys.length; i++) {
171
+ var key = keys[i];
172
+ var promise = toPromise.call(this, obj[key]);
173
+ if (promise && isPromise(promise)) defer(promise, key);
174
+ else results[key] = obj[key];
175
+ }
176
+ return Promise.all(promises).then(function () {
177
+ return results;
178
+ });
179
+
180
+ function defer(promise, key) {
181
+ // predefine the key in the result
182
+ results[key] = undefined;
183
+ promises.push(promise.then(function (res) {
184
+ results[key] = res;
185
+ }));
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Check if `obj` is a promise.
191
+ *
192
+ * @param {Object} obj
193
+ * @return {Boolean}
194
+ * @api private
195
+ */
196
+
197
+ function isPromise(obj) {
198
+ return 'function' == typeof obj.then;
199
+ }
200
+
201
+ /**
202
+ * Check if `obj` is a generator.
203
+ *
204
+ * @param {Mixed} obj
205
+ * @return {Boolean}
206
+ * @api private
207
+ */
208
+
209
+ function isGenerator(obj) {
210
+ return 'function' == typeof obj.next && 'function' == typeof obj.throw;
211
+ }
212
+
213
+ /**
214
+ * Check if `obj` is a generator function.
215
+ *
216
+ * @param {Mixed} obj
217
+ * @return {Boolean}
218
+ * @api private
219
+ */
220
+ function isGeneratorFunction(obj) {
221
+ var constructor = obj.constructor;
222
+ if (!constructor) return false;
223
+ if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
224
+ return isGenerator(constructor.prototype);
225
+ }
226
+
227
+ /**
228
+ * Check for plain object.
229
+ *
230
+ * @param {Mixed} val
231
+ * @return {Boolean}
232
+ * @api private
233
+ */
234
+
235
+ function isObject(val) {
236
+ return Object == val.constructor;
237
+ }