diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-02-06 16:22:39 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-02-06 16:22:39 +0100 |
commit | c5a8be2b6282d2042e469e1b7c1e7ce8795c026f (patch) | |
tree | c6f48bbe551bce98a4fdb0c4853a499cdb175e82 | |
parent | c4660e08dac95ef42e16c478436f3889bddc44fb (diff) | |
download | PeerTube-c5a8be2b6282d2042e469e1b7c1e7ce8795c026f.tar.gz PeerTube-c5a8be2b6282d2042e469e1b7c1e7ce8795c026f.tar.zst PeerTube-c5a8be2b6282d2042e469e1b7c1e7ce8795c026f.zip |
Rename webtorrent files (process/node)
-rw-r--r-- | controllers/api/v1/videos.js | 2 | ||||
-rw-r--r-- | lib/videos.js | 2 | ||||
-rw-r--r-- | lib/webTorrentNode.js | 161 | ||||
-rw-r--r-- | lib/webtorrent.js | 186 | ||||
-rw-r--r-- | lib/webtorrentProcess.js | 95 | ||||
-rw-r--r-- | server.js | 2 | ||||
-rw-r--r-- | tests/api/multiplePods.js | 2 | ||||
-rw-r--r-- | tests/api/singlePod.js | 2 |
8 files changed, 226 insertions, 226 deletions
diff --git a/controllers/api/v1/videos.js b/controllers/api/v1/videos.js index eec95c801..7dccfa7c3 100644 --- a/controllers/api/v1/videos.js +++ b/controllers/api/v1/videos.js | |||
@@ -13,7 +13,7 @@ | |||
13 | var reqValidator = middleware.reqValidators.videos | 13 | var reqValidator = middleware.reqValidators.videos |
14 | var Videos = require('../../../models/videos') // model | 14 | var Videos = require('../../../models/videos') // model |
15 | var videos = require('../../../lib/videos') | 15 | var videos = require('../../../lib/videos') |
16 | var webtorrent = require('../../../lib/webTorrentNode') | 16 | var webtorrent = require('../../../lib/webtorrent') |
17 | 17 | ||
18 | var router = express.Router() | 18 | var router = express.Router() |
19 | var uploads = config.get('storage.uploads') | 19 | var uploads = config.get('storage.uploads') |
diff --git a/lib/videos.js b/lib/videos.js index 0da7715c4..1bb6f2493 100644 --- a/lib/videos.js +++ b/lib/videos.js | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | var async = require('async') | 4 | var async = require('async') |
5 | var config = require('config') | 5 | var config = require('config') |
6 | var webtorrent = require('../lib/webTorrentNode') | 6 | var webtorrent = require('../lib/webtorrent') |
7 | 7 | ||
8 | var logger = require('../helpers/logger') | 8 | var logger = require('../helpers/logger') |
9 | var Videos = require('../models/videos') | 9 | var Videos = require('../models/videos') |
diff --git a/lib/webTorrentNode.js b/lib/webTorrentNode.js deleted file mode 100644 index 69fa6b012..000000000 --- a/lib/webTorrentNode.js +++ /dev/null | |||
@@ -1,161 +0,0 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var config = require('config') | ||
5 | var ipc = require('node-ipc') | ||
6 | var pathUtils = require('path') | ||
7 | var spawn = require('electron-spawn') | ||
8 | |||
9 | var logger = require('../helpers/logger') | ||
10 | |||
11 | var host = config.get('webserver.host') | ||
12 | var port = config.get('webserver.port') | ||
13 | var nodeKey = 'webtorrentnode' + port | ||
14 | var processKey = 'webtorrent' + port | ||
15 | ipc.config.silent = true | ||
16 | ipc.config.id = nodeKey | ||
17 | |||
18 | var webtorrentnode = { | ||
19 | add: add, | ||
20 | app: null, // Pid of the app | ||
21 | create: create, | ||
22 | remove: remove, | ||
23 | seed: seed, | ||
24 | silent: false // Useful for beautiful tests | ||
25 | } | ||
26 | |||
27 | function create (options, callback) { | ||
28 | if (typeof options === 'function') { | ||
29 | callback = options | ||
30 | options = {} | ||
31 | } | ||
32 | |||
33 | // Override options | ||
34 | if (options.host) host = options.host | ||
35 | if (options.port) { | ||
36 | port = options.port | ||
37 | nodeKey = 'webtorrentnode' + port | ||
38 | processKey = 'webtorrent' + port | ||
39 | ipc.config.id = nodeKey | ||
40 | } | ||
41 | |||
42 | ipc.serve(function () { | ||
43 | if (!webtorrentnode.silent) logger.info('IPC server ready.') | ||
44 | |||
45 | // Run a timeout of 30s after which we exit the process | ||
46 | var timeout_webtorrent_process = setTimeout(function () { | ||
47 | logger.error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.') | ||
48 | process.exit() | ||
49 | }, 30000) | ||
50 | |||
51 | ipc.server.on(processKey + '.ready', function () { | ||
52 | if (!webtorrentnode.silent) logger.info('Webtorrent process ready.') | ||
53 | clearTimeout(timeout_webtorrent_process) | ||
54 | callback() | ||
55 | }) | ||
56 | |||
57 | ipc.server.on(processKey + '.exception', function (data) { | ||
58 | logger.error('Received exception error from webtorrent process.', { exception: data.exception }) | ||
59 | process.exit() | ||
60 | }) | ||
61 | |||
62 | var webtorrent_process = spawn(__dirname + '/webtorrent.js', host, port, { detached: true }) | ||
63 | webtorrent_process.stderr.on('data', function (data) { | ||
64 | // logger.debug('Webtorrent process stderr: ', data.toString()) | ||
65 | }) | ||
66 | |||
67 | webtorrent_process.stdout.on('data', function (data) { | ||
68 | // logger.debug('Webtorrent process:', data.toString()) | ||
69 | }) | ||
70 | |||
71 | webtorrentnode.app = webtorrent_process | ||
72 | }) | ||
73 | |||
74 | ipc.server.start() | ||
75 | } | ||
76 | |||
77 | function seed (path, callback) { | ||
78 | var extension = pathUtils.extname(path) | ||
79 | var basename = pathUtils.basename(path, extension) | ||
80 | var data = { | ||
81 | _id: basename, | ||
82 | args: { | ||
83 | path: path | ||
84 | } | ||
85 | } | ||
86 | |||
87 | if (!webtorrentnode.silent) logger.debug('Node wants to seed %s.', data._id) | ||
88 | |||
89 | // Finish signal | ||
90 | var event_key = nodeKey + '.seedDone.' + data._id | ||
91 | ipc.server.on(event_key, function listener (received) { | ||
92 | if (!webtorrentnode.silent) logger.debug('Process seeded torrent %s.', received.magnetUri) | ||
93 | |||
94 | // This is a fake object, we just use the magnetUri in this project | ||
95 | var torrent = { | ||
96 | magnetURI: received.magnetUri | ||
97 | } | ||
98 | |||
99 | ipc.server.off(event_key) | ||
100 | callback(torrent) | ||
101 | }) | ||
102 | |||
103 | ipc.server.broadcast(processKey + '.seed', data) | ||
104 | } | ||
105 | |||
106 | function add (magnetUri, callback) { | ||
107 | var data = { | ||
108 | _id: magnetUri, | ||
109 | args: { | ||
110 | magnetUri: magnetUri | ||
111 | } | ||
112 | } | ||
113 | |||
114 | if (!webtorrentnode.silent) logger.debug('Node wants to add ' + data._id) | ||
115 | |||
116 | // Finish signal | ||
117 | var event_key = nodeKey + '.addDone.' + data._id | ||
118 | ipc.server.on(event_key, function (received) { | ||
119 | if (!webtorrentnode.silent) logger.debug('Process added torrent.') | ||
120 | |||
121 | // This is a fake object, we just use the magnetUri in this project | ||
122 | var torrent = { | ||
123 | files: received.files | ||
124 | } | ||
125 | |||
126 | ipc.server.off(event_key) | ||
127 | callback(torrent) | ||
128 | }) | ||
129 | |||
130 | ipc.server.broadcast(processKey + '.add', data) | ||
131 | } | ||
132 | |||
133 | function remove (magnetUri, callback) { | ||
134 | var data = { | ||
135 | _id: magnetUri, | ||
136 | args: { | ||
137 | magnetUri: magnetUri | ||
138 | } | ||
139 | } | ||
140 | |||
141 | if (!webtorrentnode.silent) logger.debug('Node wants to stop seeding %s.', data._id) | ||
142 | |||
143 | // Finish signal | ||
144 | var event_key = nodeKey + '.removeDone.' + data._id | ||
145 | ipc.server.on(event_key, function (received) { | ||
146 | if (!webtorrentnode.silent) logger.debug('Process removed torrent %s.', data._id) | ||
147 | |||
148 | var err = null | ||
149 | if (received.err) err = received.err | ||
150 | |||
151 | ipc.server.off(event_key) | ||
152 | callback(err) | ||
153 | }) | ||
154 | |||
155 | ipc.server.broadcast(processKey + '.remove', data) | ||
156 | } | ||
157 | |||
158 | // --------------------------------------------------------------------------- | ||
159 | |||
160 | module.exports = webtorrentnode | ||
161 | })() | ||
diff --git a/lib/webtorrent.js b/lib/webtorrent.js index d0db6e066..7cd38156c 100644 --- a/lib/webtorrent.js +++ b/lib/webtorrent.js | |||
@@ -1,92 +1,158 @@ | |||
1 | ;(function () { | 1 | ;(function () { |
2 | 'use strict' | 2 | 'use strict' |
3 | 3 | ||
4 | function webtorrent (args) { | 4 | var config = require('config') |
5 | var WebTorrent = require('webtorrent') | 5 | var ipc = require('node-ipc') |
6 | var ipc = require('node-ipc') | 6 | var pathUtils = require('path') |
7 | var spawn = require('electron-spawn') | ||
8 | |||
9 | var logger = require('../helpers/logger') | ||
10 | |||
11 | var host = config.get('webserver.host') | ||
12 | var port = config.get('webserver.port') | ||
13 | var nodeKey = 'webtorrentnode' + port | ||
14 | var processKey = 'webtorrentprocess' + port | ||
15 | ipc.config.silent = true | ||
16 | ipc.config.id = nodeKey | ||
17 | |||
18 | var webtorrent = { | ||
19 | add: add, | ||
20 | app: null, // Pid of the app | ||
21 | create: create, | ||
22 | remove: remove, | ||
23 | seed: seed, | ||
24 | silent: false // Useful for beautiful tests | ||
25 | } | ||
7 | 26 | ||
8 | if (args.length !== 3) { | 27 | function create (options, callback) { |
9 | console.log('Wrong arguments number: ' + args.length + '/3') | 28 | if (typeof options === 'function') { |
10 | process.exit(-1) | 29 | callback = options |
30 | options = {} | ||
11 | } | 31 | } |
12 | 32 | ||
13 | var host = args[1] | 33 | // Override options |
14 | var port = args[2] | 34 | if (options.host) host = options.host |
15 | var nodeKey = 'webtorrentnode' + port | 35 | if (options.port) { |
16 | var processKey = 'webtorrent' + port | 36 | port = options.port |
37 | nodeKey = 'webtorrentnode' + port | ||
38 | processKey = 'webtorrentprocess' + port | ||
39 | ipc.config.id = nodeKey | ||
40 | } | ||
17 | 41 | ||
18 | ipc.config.silent = true | 42 | ipc.serve(function () { |
19 | ipc.config.id = processKey | 43 | if (!webtorrent.silent) logger.info('IPC server ready.') |
20 | 44 | ||
21 | if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = [] | 45 | // Run a timeout of 30s after which we exit the process |
22 | else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket' | 46 | var timeout_webtorrent_process = setTimeout(function () { |
23 | var wt = new WebTorrent({ dht: false }) | 47 | logger.error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.') |
48 | process.exit() | ||
49 | }, 30000) | ||
24 | 50 | ||
25 | function seed (data) { | 51 | ipc.server.on(processKey + '.ready', function () { |
26 | var args = data.args | 52 | if (!webtorrent.silent) logger.info('Webtorrent process ready.') |
27 | var path = args.path | 53 | clearTimeout(timeout_webtorrent_process) |
28 | var _id = data._id | 54 | callback() |
55 | }) | ||
29 | 56 | ||
30 | wt.seed(path, { announceList: '' }, function (torrent) { | 57 | ipc.server.on(processKey + '.exception', function (data) { |
31 | var to_send = { | 58 | logger.error('Received exception error from webtorrent process.', { exception: data.exception }) |
32 | magnetUri: torrent.magnetURI | 59 | process.exit() |
33 | } | 60 | }) |
34 | 61 | ||
35 | ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send) | 62 | var webtorrent_process = spawn(__dirname + '/webtorrentProcess.js', host, port, { detached: true }) |
63 | webtorrent_process.stderr.on('data', function (data) { | ||
64 | // logger.debug('Webtorrent process stderr: ', data.toString()) | ||
36 | }) | 65 | }) |
37 | } | ||
38 | 66 | ||
39 | function add (data) { | 67 | webtorrent_process.stdout.on('data', function (data) { |
40 | var args = data.args | 68 | // logger.debug('Webtorrent process:', data.toString()) |
41 | var magnetUri = args.magnetUri | 69 | }) |
42 | var _id = data._id | ||
43 | 70 | ||
44 | wt.add(magnetUri, function (torrent) { | 71 | webtorrent.app = webtorrent_process |
45 | var to_send = { | 72 | }) |
46 | files: [] | ||
47 | } | ||
48 | 73 | ||
49 | torrent.files.forEach(function (file) { | 74 | ipc.server.start() |
50 | to_send.files.push({ path: file.path }) | 75 | } |
51 | }) | ||
52 | 76 | ||
53 | ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send) | 77 | function seed (path, callback) { |
54 | }) | 78 | var extension = pathUtils.extname(path) |
79 | var basename = pathUtils.basename(path, extension) | ||
80 | var data = { | ||
81 | _id: basename, | ||
82 | args: { | ||
83 | path: path | ||
84 | } | ||
55 | } | 85 | } |
56 | 86 | ||
57 | function remove (data) { | 87 | if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id) |
58 | var args = data.args | ||
59 | var magnetUri = args.magnetUri | ||
60 | var _id = data._id | ||
61 | 88 | ||
62 | try { | 89 | // Finish signal |
63 | wt.remove(magnetUri, callback) | 90 | var event_key = nodeKey + '.seedDone.' + data._id |
64 | } catch (err) { | 91 | ipc.server.on(event_key, function listener (received) { |
65 | console.log('Cannot remove the torrent from WebTorrent.') | 92 | if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri) |
66 | return callback(null) | 93 | |
94 | // This is a fake object, we just use the magnetUri in this project | ||
95 | var torrent = { | ||
96 | magnetURI: received.magnetUri | ||
67 | } | 97 | } |
68 | 98 | ||
69 | function callback () { | 99 | ipc.server.off(event_key) |
70 | var to_send = {} | 100 | callback(torrent) |
71 | ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send) | 101 | }) |
102 | |||
103 | ipc.server.broadcast(processKey + '.seed', data) | ||
104 | } | ||
105 | |||
106 | function add (magnetUri, callback) { | ||
107 | var data = { | ||
108 | _id: magnetUri, | ||
109 | args: { | ||
110 | magnetUri: magnetUri | ||
72 | } | 111 | } |
73 | } | 112 | } |
74 | 113 | ||
75 | console.log('Configuration: ' + host + ':' + port) | 114 | if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id) |
76 | console.log('Connecting to IPC...') | ||
77 | 115 | ||
78 | ipc.connectTo(nodeKey, function () { | 116 | // Finish signal |
79 | ipc.of[nodeKey].on(processKey + '.seed', seed) | 117 | var event_key = nodeKey + '.addDone.' + data._id |
80 | ipc.of[nodeKey].on(processKey + '.add', add) | 118 | ipc.server.on(event_key, function (received) { |
81 | ipc.of[nodeKey].on(processKey + '.remove', remove) | 119 | if (!webtorrent.silent) logger.debug('Process added torrent.') |
120 | |||
121 | // This is a fake object, we just use the magnetUri in this project | ||
122 | var torrent = { | ||
123 | files: received.files | ||
124 | } | ||
82 | 125 | ||
83 | ipc.of[nodeKey].emit(processKey + '.ready') | 126 | ipc.server.off(event_key) |
84 | console.log('Ready.') | 127 | callback(torrent) |
85 | }) | 128 | }) |
86 | 129 | ||
87 | process.on('uncaughtException', function (e) { | 130 | ipc.server.broadcast(processKey + '.add', data) |
88 | ipc.of[nodeKey].emit(processKey + '.exception', { exception: e }) | 131 | } |
132 | |||
133 | function remove (magnetUri, callback) { | ||
134 | var data = { | ||
135 | _id: magnetUri, | ||
136 | args: { | ||
137 | magnetUri: magnetUri | ||
138 | } | ||
139 | } | ||
140 | |||
141 | if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id) | ||
142 | |||
143 | // Finish signal | ||
144 | var event_key = nodeKey + '.removeDone.' + data._id | ||
145 | ipc.server.on(event_key, function (received) { | ||
146 | if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id) | ||
147 | |||
148 | var err = null | ||
149 | if (received.err) err = received.err | ||
150 | |||
151 | ipc.server.off(event_key) | ||
152 | callback(err) | ||
89 | }) | 153 | }) |
154 | |||
155 | ipc.server.broadcast(processKey + '.remove', data) | ||
90 | } | 156 | } |
91 | 157 | ||
92 | // --------------------------------------------------------------------------- | 158 | // --------------------------------------------------------------------------- |
diff --git a/lib/webtorrentProcess.js b/lib/webtorrentProcess.js new file mode 100644 index 000000000..7dc655f10 --- /dev/null +++ b/lib/webtorrentProcess.js | |||
@@ -0,0 +1,95 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | function webtorrent (args) { | ||
5 | var WebTorrent = require('webtorrent') | ||
6 | var ipc = require('node-ipc') | ||
7 | |||
8 | if (args.length !== 3) { | ||
9 | console.log('Wrong arguments number: ' + args.length + '/3') | ||
10 | process.exit(-1) | ||
11 | } | ||
12 | |||
13 | var host = args[1] | ||
14 | var port = args[2] | ||
15 | var nodeKey = 'webtorrentnode' + port | ||
16 | var processKey = 'webtorrentprocess' + port | ||
17 | |||
18 | ipc.config.silent = true | ||
19 | ipc.config.id = processKey | ||
20 | |||
21 | if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = [] | ||
22 | else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket' | ||
23 | var wt = new WebTorrent({ dht: false }) | ||
24 | |||
25 | function seed (data) { | ||
26 | var args = data.args | ||
27 | var path = args.path | ||
28 | var _id = data._id | ||
29 | |||
30 | wt.seed(path, { announceList: '' }, function (torrent) { | ||
31 | var to_send = { | ||
32 | magnetUri: torrent.magnetURI | ||
33 | } | ||
34 | |||
35 | ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send) | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | function add (data) { | ||
40 | var args = data.args | ||
41 | var magnetUri = args.magnetUri | ||
42 | var _id = data._id | ||
43 | |||
44 | wt.add(magnetUri, function (torrent) { | ||
45 | var to_send = { | ||
46 | files: [] | ||
47 | } | ||
48 | |||
49 | torrent.files.forEach(function (file) { | ||
50 | to_send.files.push({ path: file.path }) | ||
51 | }) | ||
52 | |||
53 | ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send) | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | function remove (data) { | ||
58 | var args = data.args | ||
59 | var magnetUri = args.magnetUri | ||
60 | var _id = data._id | ||
61 | |||
62 | try { | ||
63 | wt.remove(magnetUri, callback) | ||
64 | } catch (err) { | ||
65 | console.log('Cannot remove the torrent from WebTorrent.') | ||
66 | return callback(null) | ||
67 | } | ||
68 | |||
69 | function callback () { | ||
70 | var to_send = {} | ||
71 | ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | console.log('Configuration: ' + host + ':' + port) | ||
76 | console.log('Connecting to IPC...') | ||
77 | |||
78 | ipc.connectTo(nodeKey, function () { | ||
79 | ipc.of[nodeKey].on(processKey + '.seed', seed) | ||
80 | ipc.of[nodeKey].on(processKey + '.add', add) | ||
81 | ipc.of[nodeKey].on(processKey + '.remove', remove) | ||
82 | |||
83 | ipc.of[nodeKey].emit(processKey + '.ready') | ||
84 | console.log('Ready.') | ||
85 | }) | ||
86 | |||
87 | process.on('uncaughtException', function (e) { | ||
88 | ipc.of[nodeKey].emit(processKey + '.exception', { exception: e }) | ||
89 | }) | ||
90 | } | ||
91 | |||
92 | // --------------------------------------------------------------------------- | ||
93 | |||
94 | module.exports = webtorrent | ||
95 | })() | ||
@@ -37,7 +37,7 @@ | |||
37 | var routes = require('./controllers') | 37 | var routes = require('./controllers') |
38 | var utils = require('./helpers/utils') | 38 | var utils = require('./helpers/utils') |
39 | var videos = require('./lib/videos') | 39 | var videos = require('./lib/videos') |
40 | var webtorrent = require('./lib/webTorrentNode') | 40 | var webtorrent = require('./lib/webtorrent') |
41 | 41 | ||
42 | // Get configurations | 42 | // Get configurations |
43 | var port = config.get('listen.port') | 43 | var port = config.get('listen.port') |
diff --git a/tests/api/multiplePods.js b/tests/api/multiplePods.js index c27f7121e..5070b450a 100644 --- a/tests/api/multiplePods.js +++ b/tests/api/multiplePods.js | |||
@@ -6,7 +6,7 @@ | |||
6 | var expect = chai.expect | 6 | var expect = chai.expect |
7 | 7 | ||
8 | var utils = require('./utils') | 8 | var utils = require('./utils') |
9 | var webtorrent = require(__dirname + '/../../lib/webTorrentNode') | 9 | var webtorrent = require(__dirname + '/../../lib/webtorrent') |
10 | webtorrent.silent = true | 10 | webtorrent.silent = true |
11 | 11 | ||
12 | describe('Test multiple pods', function () { | 12 | describe('Test multiple pods', function () { |
diff --git a/tests/api/singlePod.js b/tests/api/singlePod.js index 7621d766c..e5337256b 100644 --- a/tests/api/singlePod.js +++ b/tests/api/singlePod.js | |||
@@ -6,7 +6,7 @@ | |||
6 | var expect = chai.expect | 6 | var expect = chai.expect |
7 | var fs = require('fs') | 7 | var fs = require('fs') |
8 | 8 | ||
9 | var webtorrent = require(__dirname + '/../../lib/webTorrentNode') | 9 | var webtorrent = require(__dirname + '/../../lib/webtorrent') |
10 | webtorrent.silent = true | 10 | webtorrent.silent = true |
11 | 11 | ||
12 | var utils = require('./utils') | 12 | var utils = require('./utils') |