From 012e70d66871affe507a661fa51de4ce48ef464a Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 25 May 2018 11:08:22 +0100 Subject: [PATCH] Clipper: Dynamically allocate and detect clipper service port --- Clipper/joplin-webclipper/.gitignore | 1 + Clipper/joplin-webclipper/manifest.json | 2 - .../joplin-webclipper/popup/package-lock.json | 34 +++- Clipper/joplin-webclipper/popup/package.json | 6 +- .../popup/scripts/postinstall.js | 3 + Clipper/joplin-webclipper/popup/src/bridge.js | 57 +++++- .../popup/src/randomClipperPort.js | 167 ++++++++++++++++++ ReactNativeClient/lib/ClipperServer.js | 37 +++- ReactNativeClient/lib/randomClipperPort.js | 167 ++++++++++++++++++ 9 files changed, 457 insertions(+), 17 deletions(-) create mode 100644 Clipper/joplin-webclipper/.gitignore create mode 100644 Clipper/joplin-webclipper/popup/scripts/postinstall.js create mode 100644 Clipper/joplin-webclipper/popup/src/randomClipperPort.js create mode 100644 ReactNativeClient/lib/randomClipperPort.js diff --git a/Clipper/joplin-webclipper/.gitignore b/Clipper/joplin-webclipper/.gitignore new file mode 100644 index 0000000000..77738287f0 --- /dev/null +++ b/Clipper/joplin-webclipper/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/Clipper/joplin-webclipper/manifest.json b/Clipper/joplin-webclipper/manifest.json index 37b0f797d4..8d69f88618 100644 --- a/Clipper/joplin-webclipper/manifest.json +++ b/Clipper/joplin-webclipper/manifest.json @@ -1,5 +1,4 @@ { - "manifest_version": 2, "name": "Joplin Web Clipper", "version": "1.0", @@ -44,5 +43,4 @@ "id": "{8419486a-54e9-11e8-9401-ac9e17909436}" } } - } \ No newline at end of file diff --git a/Clipper/joplin-webclipper/popup/package-lock.json b/Clipper/joplin-webclipper/popup/package-lock.json index 3c912c1331..4c66bbd55d 100644 --- a/Clipper/joplin-webclipper/popup/package-lock.json +++ b/Clipper/joplin-webclipper/popup/package-lock.json @@ -4081,12 +4081,13 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", - "jsonfile": "^3.0.0", + "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, @@ -6103,9 +6104,10 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -8772,6 +8774,24 @@ "slash": "^1.0.0", "source-map": "^0.5.6" } + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "requires": { + "graceful-fs": "^4.1.6" + } } } }, diff --git a/Clipper/joplin-webclipper/popup/package.json b/Clipper/joplin-webclipper/popup/package.json index 67dbef5066..36a52dcc67 100644 --- a/Clipper/joplin-webclipper/popup/package.json +++ b/Clipper/joplin-webclipper/popup/package.json @@ -15,9 +15,11 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", - "watch": "cra-build-watch" + "watch": "cra-build-watch", + "postinstall": "node scripts/postinstall.js" }, "devDependencies": { - "cra-build-watch": "^1.0.1" + "cra-build-watch": "^1.0.1", + "fs-extra": "^6.0.1" } } diff --git a/Clipper/joplin-webclipper/popup/scripts/postinstall.js b/Clipper/joplin-webclipper/popup/scripts/postinstall.js new file mode 100644 index 0000000000..a3ef3cd404 --- /dev/null +++ b/Clipper/joplin-webclipper/popup/scripts/postinstall.js @@ -0,0 +1,3 @@ +const fs = require('fs-extra'); + +fs.copySync(__dirname + '/../../../../ReactNativeClient/lib/randomClipperPort.js', __dirname + '/../src/randomClipperPort.js'); \ No newline at end of file diff --git a/Clipper/joplin-webclipper/popup/src/bridge.js b/Clipper/joplin-webclipper/popup/src/bridge.js index 65dce4229e..dc6e21bb9f 100644 --- a/Clipper/joplin-webclipper/popup/src/bridge.js +++ b/Clipper/joplin-webclipper/popup/src/bridge.js @@ -1,3 +1,5 @@ +const randomClipperPort = require('./randomClipperPort'); + class Bridge { init(browser, browserSupportsPromises, dispatch) { @@ -6,6 +8,8 @@ class Bridge { this.browser_ = browser; this.dispatch_ = dispatch; this.browserSupportsPromises_ = browserSupportsPromises; + this.clipperServerPort_ = null; + this.clipperServerPortStatus_ = 'searching'; this.browser_notify = async (command) => { console.info('Popup: Got command: ' + command.name); @@ -30,6 +34,8 @@ class Bridge { } this.browser_.runtime.onMessage.addListener(this.browser_notify); + + this.findClipperServerPort(); } browser() { @@ -40,6 +46,53 @@ class Bridge { return this.dispatch_(action); } + async findClipperServerPort() { + let state = null; + for (let i = 0; i < 10; i++) { + state = randomClipperPort(state); + + try { + console.info('findClipperServerPort: Trying ' + state.port); + const response = await fetch('http://127.0.0.1:' + state.port + '/ping'); + const text = await response.text(); + console.info('findClipperServerPort: Got response: ' + text); + if (text.trim() === 'JoplinClipperServer') { + this.clipperServerPortStatus_ = 'found'; + this.clipperServerPort_ = state.port; + return; + } + } catch (error) { + // continue + } + } + + this.clipperServerPortStatus_ = 'not_found'; + + return null; + } + + async clipperServerPort() { + return new Promise((resolve, reject) => { + const checkStatus = () => { + if (this.clipperServerPortStatus_ === 'not_found') { + reject(new Error('Could not find clipper service. Please make sure that Joplin is running and that the clipper server is enabled.')); + return true; + } else if (this.clipperServerPortStatus_ === 'found') { + resolve(this.clipperServerPort_); + return true; + } + return false; + } + + if (checkStatus()) return; + + const waitIID = setInterval(() => { + if (!checkStatus()) return; + clearInterval(waitIID); + }, 1000); + }); + } + async tabsExecuteScript(options) { if (this.browserSupportsPromises_) return this.browser().tabs.executeScript(options); @@ -92,7 +145,9 @@ class Bridge { if (!content) throw new Error('Cannot send empty content'); - const response = await fetch("http://127.0.0.1:9967/notes", { + const port = await this.clipperServerPort(); + + const response = await fetch("http://127.0.0.1:" + port + "/notes", { method: "POST", headers: { 'Accept': 'application/json', diff --git a/Clipper/joplin-webclipper/popup/src/randomClipperPort.js b/Clipper/joplin-webclipper/popup/src/randomClipperPort.js new file mode 100644 index 0000000000..9a1599d2e5 --- /dev/null +++ b/Clipper/joplin-webclipper/popup/src/randomClipperPort.js @@ -0,0 +1,167 @@ +const reservedPorts = [1024, 1027, 1028, 1029, 1058, 1059, 1080, 1085, 1098, 1099, 1109, 1119, 1167, 1194, 1198, 1214, 1220, 1234, 1241, 1270, 1293, 1311, 1314, 1337, 1341, 1344, +1352, 1360, 1414, 1417, 1418, 1419, 1420, 1431, 1433, 1434, 1492, 1494, 1500, 1501, 1503, 1512, 1513, 1521, 1524, 1527, 1533, 1547, 1550, 1581, 1582, 1583, 1589, 1604, 1626, 1627, +1628, 1629, 1645, 1646, 1666, 1677, 1688, 1701, 1707, 1716, 1719, 1720, 1723, 1755, 1761, 1783, 1801, 1812, 1813, 1863, 1880, 1883, 1900, 1935, 1967, 1970, 1972, 1984, 1985, 1998, +2000, 2010, 2033, 2049, 2056, 2080, 2082, 2083, 2086, 2087, 2095, 2096, 2100, 2101, 2102, 2103, 2104, 2123, 2142, 2152, 2159, 2181, 2195, 2196, 2210, 2211, 2221, 2222, 2261, 2262, +2266, 2305, 2351, 2368, 2369, 2370, 2372, 2375, 2376, 2377, 2379, 2380, 2399, 2401, 2404, 2424, 2427, 2447, 2480, 2483, 2484, 2535, 2541, 2546, 2593, 2598, 2599, 2638, 2710, 2727, +2809, 2811, 2827, 2944, 2945, 2947, 2948, 2949, 2967, 3000, 3004, 3020, 3050, 3052, 3074, 3101, 3128, 3225, 3233, 3260, 3268, 3269, 3283, 3290, 3305, 3306, 3313, 3316, 3323, 3332, +3333, 3351, 3386, 3389, 3396, 3412, 3423, 3424, 3455, 3478, 3479, 3480, 3483, 3493, 3516, 3527, 3535, 3544, 3632, 3645, 3659, 3667, 3689, 3690, 3702, 3724, 3725, 3768, 3784, 3785, +3799, 3804, 3825, 3826, 3830, 3835, 3856, 3868, 3872, 3880, 3900, 3960, 3962, 3978, 3979, 3999, 4000, 4001, 4018, 4035, 4045, 4050, 4069, 4089, 4090, 4093, 4096, 4105, 4111, 4116, +4125, 4172, 4190, 4198, 4201, 4222, 4226, 4242, 4243, 4244, 4303, 4321, 4352, 4444, 4486, 4488, 4500, 4502, 4505, 4534, 4560, 4567, 4569, 4604, 4605, 4610, 4662, 4664, 4672, 4711, +4713, 4728, 4730, 4739, 4747, 4750, 4753, 4840, 4843, 4847, 4848, 4894, 4949, 4950, 5000, 5001, 5002, 5003, 5004, 5005, 5010, 5011, 5031, 5037, 5048, 5050, 5051, 5060, 5061, 5062, +5064, 5065, 5070, 5084, 5085, 5093, 5099, 5104, 5121, 5124, 5125, 5150, 5151, 5154, 5190, 5201, 5222, 5223, 5228, 5242, 5243, 5246, 5247, 5269, 5280, 5281, 5298, 5310, 5349, 5351, +5353, 5355, 5357, 5358, 5394, 5402, 5405, 5412, 5413, 5417, 5421, 5432, 5433, 5445, 5480, 5481, 5495, 5498, 5499, 5500, 5501, 5517, 5550, 5554, 5555, 5556, 5568, 5601, 5631, 5632, +5656, 5666, 5667, 5670, 5672, 5678, 5683, 5701, 5718, 5719, 5722, 5723, 5724, 5741, 5742, 5800, 5900, 5931, 5938, 5984, 5985, 5986, 5988, 6000, 6001, 6002, 6003, 6004, 6005, 6006, +6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, +6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6060, 6061, 6062, 6063, 6086, 6100, 6101, +6110, 6111, 6112, 6113, 6136, 6159, 6200, 6201, 6225, 6227, 6240, 6244, 6255, 6257, 6260, 6262, 6343, 6346, 6347, 6350, 6379, 6389, 6432, 6436, 6437, 6444, 6445, 6463, 6502, 6513, +6514, 6515, 6543, 6556, 6560, 6566, 6571, 6600, 6601, 6602, 6619, 6622, 6653, 6660, 6665, 6679, 6690, 6697, 6699, 6715, 6771, 6783, 6789, 6869, 6881, 6888, 6889, 6891, 6901, 6902, +6969, 6970, 7000, 7001, 7002, 7005, 7006, 7010, 7022, 7023, 7025, 7047, 7070, 7133, 7144, 7145, 7171, 7262, 7272, 7306, 7307, 7312, 7396, 7400, 7401, 7402, 7471, 7473, 7474, 7478, +7542, 7547, 7575, 7624, 7631, 7634, 7652, 7655, 7656, 7670, 7687, 7707, 7717, 7777, 7831, 7880, 7890, 7915, 7935, 7946, 7990, 8000, 8005, 8006, 8007, 8008, 8009, 8042, 8069, 8070, +8074, 8075, 8080, 8088, 8089, 8090, 8091, 8092, 8111, 8112, 8116, 8118, 8123, 8139, 8140, 8172, 8184, 8194, 8200, 8222, 8243, 8245, 8280, 8281, 8291, 8303, 8332, 8333, 8337, 8384, +8388, 8443, 8444, 8484, 8500, 8530, 8531, 8580, 8629, 8642, 8691, 8767, 8834, 8840, 8880, 8883, 8887, 8888, 8889, 8983, 8997, 8998, 8999, 9000, 9001, 9002, 9006, 9030, 9042, 9043, +9050, 9060, 9080, 9090, 9091, 9092, 9100, 9101, 9102, 9103, 9119, 9150, 9191, 9199, 9200, 9217, 9293, 9300, 9303, 9306, 9309, 9312, 9332, 9333, 9339, 9389, 9418, 9419, 9420, 9421, +9422, 9425, 9443, 9535, 9536, 9600, 9675, 9676, 9695, 9785, 9800, 9875, 9898, 9899, 9981, 9982, 9987, 9993, 9997, 9999, 10000, 10001, 10009, 10010, 10024, 10025, 10042, 10050, 10051, +10080, 10110, 10172, 10200, 10201, 10212, 10308, 10480, 10505, 10514, 10823, 10891, 10933, 11001, 11111, 11112, 11211, 11214, 11215, 11235, 11311, 11371, 11753, 12012, 12013, 12035, +12043, 12046, 12201, 12222, 12223, 12345, 12443, 12489, 12975, 13000, 13008, 13075, 13720, 13721, 13724, 13782, 13783, 13785, 13786, 14550, 14567, 15000, 15345, 15441, 15567, 15672, +16000, 16080, 16200, 16225, 16250, 16261, 16300, 16384, 16385, 16386, 16387, 16393, 16394, 16395, 16396, 16397, 16398, 16399, 16400, 16401, 16402, 16403, 16404, 16405, 16406, 16407, +16408, 16409, 16410, 16411, 16412, 16413, 16414, 16415, 16416, 16417, 16418, 16419, 16420, 16421, 16422, 16423, 16424, 16425, 16426, 16427, 16428, 16429, 16430, 16431, 16432, 16433, +16434, 16435, 16436, 16437, 16438, 16439, 16440, 16441, 16442, 16443, 16444, 16445, 16446, 16447, 16448, 16449, 16450, 16451, 16452, 16453, 16454, 16455, 16456, 16457, 16458, 16459, +16460, 16461, 16462, 16463, 16464, 16465, 16466, 16467, 16468, 16469, 16470, 16471, 16472, 16482, 16567, 17011, 17500, 18091, 18092, 18104, 18200, 18201, 18206, 18300, 18301, 18306, +18333, 18400, 18401, 18505, 18506, 18605, 18606, 19000, 19001, 19132, 19150, 19226, 19294, 19295, 19302, 19812, 19813, 19814, 19999, 20000, 20560, 20595, 20808, 21025, 22000, 22136, +22222, 23073, 23399, 23513, 24441, 24444, 24465, 24554, 24800, 24842, 25565, 25575, 25826, 26000, 26900, 27000, 27016, 27017, 27031, 27036, 27037, 27374, 27500, 27888, 27901, 27950, +27960, 28001, 28015, 28770, 28785, 28852, 28910, 28960, 29000, 29070, 29900, 29920, 30564, 31337, 31416, 31438, 31457, 32137, 32400, 32764, 32887, 32976, 33434, 33848, 34000, 34197, +35357, 37008, 40000, 43110, 43594, 44405, 44818, 47001, 47808, 49151]; + +// From https://github.com/coverslide/node-alea +const AleaModule = function () { + + 'use strict'; + + // importState to sync generator states + Alea.importState = function(i){ + var random = new Alea(); + random.importState(i); + return random; + }; + + return Alea; + + function Alea() { + return (function(args) { + // Johannes Baagøe , 2010 + var s0 = 0; + var s1 = 0; + var s2 = 0; + var c = 1; + + if (args.length == 0) { + args = [+new Date]; + } + var mash = Mash(); + s0 = mash(' '); + s1 = mash(' '); + s2 = mash(' '); + + for (var i = 0; i < args.length; i++) { + s0 -= mash(args[i]); + if (s0 < 0) { + s0 += 1; + } + s1 -= mash(args[i]); + if (s1 < 0) { + s1 += 1; + } + s2 -= mash(args[i]); + if (s2 < 0) { + s2 += 1; + } + } + mash = null; + + var random = function() { + var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32 + s0 = s1; + s1 = s2; + return s2 = t - (c = t | 0); + }; + random.uint32 = function() { + return random() * 0x100000000; // 2^32 + }; + random.fract53 = function() { + return random() + + (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53 + }; + random.version = 'Alea 0.9'; + random.args = args; + + // my own additions to sync state between two generators + random.exportState = function(){ + return [s0, s1, s2, c]; + }; + random.importState = function(i){ + s0 = +i[0] || 0; + s1 = +i[1] || 0; + s2 = +i[2] || 0; + c = +i[3] || 0; + }; + + return random; + + } (Array.prototype.slice.call(arguments))); + } + + function Mash() { + var n = 0xefc8249d; + + var mash = function(data) { + data = data.toString(); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + }; + + mash.version = 'Mash 0.9'; + return mash; + } +} + +const Alea = AleaModule() + +function findClipperPort(state) { + const minPort = 1024 + const maxPort = 49151 + + let prng = null; + if (!state) { + prng = new Alea(1867) + state = { prng: prng } + } else { + prng = state.prng; + } + + const randomPort = () => { + return minPort + Math.floor(prng() * ((maxPort + 1) - minPort)); + } + + let port = null; + for (let i = 0; i < maxPort; i++) { + port = randomPort(); + if (reservedPorts.indexOf(port) < 0) break; + } + + if (!port) throw new Error('Cannot find a non-reserved port'); + + state.port = port; + + return state; +} + +module.exports = findClipperPort; \ No newline at end of file diff --git a/ReactNativeClient/lib/ClipperServer.js b/ReactNativeClient/lib/ClipperServer.js index e42050c876..13e9afb72f 100644 --- a/ReactNativeClient/lib/ClipperServer.js +++ b/ReactNativeClient/lib/ClipperServer.js @@ -11,6 +11,7 @@ const HtmlToMd = require('lib/HtmlToMd'); const { Logger } = require('lib/logger.js'); const markdownUtils = require('lib/markdownUtils'); const mimeUtils = require('lib/mime-utils.js').mime; +const randomClipperPort = require('lib/randomClipperPort'); class ClipperServer { @@ -152,10 +153,26 @@ class ClipperServer { return output; } + async findAvailablePort() { + const tcpPortUsed = require('tcp-port-used'); + + let state = null; + for (let i = 0; i < 10000; i++) { + state = randomClipperPort(state); + const inUse = await tcpPortUsed.check(state.port); + if (!inUse) return state.port; + } + + throw new Error('All potential ports are in use or not available.') + } + async start() { - const port = await netUtils.findAvailablePort([9967, 8967, 8867], 0); // TODO: Make it shared with OneDrive server - if (!port) { - this.logger().error('All potential ports are in use or not available.'); + let port = null; + + try { + port = await this.findAvailablePort(); + } catch (error) { + this.logger().error(error); return; } @@ -178,12 +195,22 @@ class ClipperServer { response.end(); } + const writeResponseText = (code, text) => { + writeCorsHeaders(code); + response.write(text); + response.end(); + } + const requestId = Date.now(); this.logger().info('Request (' + requestId + '): ' + request.method + ' ' + request.url); - if (request.method === 'POST') { - const url = urlParser.parse(request.url, true); + const url = urlParser.parse(request.url, true); + if (request.method === 'GET') { + if (url.pathname === '/ping') { + return writeResponseText(200, 'JoplinClipperServer'); + } + } else if (request.method === 'POST') { if (url.pathname === '/notes') { let body = ''; diff --git a/ReactNativeClient/lib/randomClipperPort.js b/ReactNativeClient/lib/randomClipperPort.js new file mode 100644 index 0000000000..9a1599d2e5 --- /dev/null +++ b/ReactNativeClient/lib/randomClipperPort.js @@ -0,0 +1,167 @@ +const reservedPorts = [1024, 1027, 1028, 1029, 1058, 1059, 1080, 1085, 1098, 1099, 1109, 1119, 1167, 1194, 1198, 1214, 1220, 1234, 1241, 1270, 1293, 1311, 1314, 1337, 1341, 1344, +1352, 1360, 1414, 1417, 1418, 1419, 1420, 1431, 1433, 1434, 1492, 1494, 1500, 1501, 1503, 1512, 1513, 1521, 1524, 1527, 1533, 1547, 1550, 1581, 1582, 1583, 1589, 1604, 1626, 1627, +1628, 1629, 1645, 1646, 1666, 1677, 1688, 1701, 1707, 1716, 1719, 1720, 1723, 1755, 1761, 1783, 1801, 1812, 1813, 1863, 1880, 1883, 1900, 1935, 1967, 1970, 1972, 1984, 1985, 1998, +2000, 2010, 2033, 2049, 2056, 2080, 2082, 2083, 2086, 2087, 2095, 2096, 2100, 2101, 2102, 2103, 2104, 2123, 2142, 2152, 2159, 2181, 2195, 2196, 2210, 2211, 2221, 2222, 2261, 2262, +2266, 2305, 2351, 2368, 2369, 2370, 2372, 2375, 2376, 2377, 2379, 2380, 2399, 2401, 2404, 2424, 2427, 2447, 2480, 2483, 2484, 2535, 2541, 2546, 2593, 2598, 2599, 2638, 2710, 2727, +2809, 2811, 2827, 2944, 2945, 2947, 2948, 2949, 2967, 3000, 3004, 3020, 3050, 3052, 3074, 3101, 3128, 3225, 3233, 3260, 3268, 3269, 3283, 3290, 3305, 3306, 3313, 3316, 3323, 3332, +3333, 3351, 3386, 3389, 3396, 3412, 3423, 3424, 3455, 3478, 3479, 3480, 3483, 3493, 3516, 3527, 3535, 3544, 3632, 3645, 3659, 3667, 3689, 3690, 3702, 3724, 3725, 3768, 3784, 3785, +3799, 3804, 3825, 3826, 3830, 3835, 3856, 3868, 3872, 3880, 3900, 3960, 3962, 3978, 3979, 3999, 4000, 4001, 4018, 4035, 4045, 4050, 4069, 4089, 4090, 4093, 4096, 4105, 4111, 4116, +4125, 4172, 4190, 4198, 4201, 4222, 4226, 4242, 4243, 4244, 4303, 4321, 4352, 4444, 4486, 4488, 4500, 4502, 4505, 4534, 4560, 4567, 4569, 4604, 4605, 4610, 4662, 4664, 4672, 4711, +4713, 4728, 4730, 4739, 4747, 4750, 4753, 4840, 4843, 4847, 4848, 4894, 4949, 4950, 5000, 5001, 5002, 5003, 5004, 5005, 5010, 5011, 5031, 5037, 5048, 5050, 5051, 5060, 5061, 5062, +5064, 5065, 5070, 5084, 5085, 5093, 5099, 5104, 5121, 5124, 5125, 5150, 5151, 5154, 5190, 5201, 5222, 5223, 5228, 5242, 5243, 5246, 5247, 5269, 5280, 5281, 5298, 5310, 5349, 5351, +5353, 5355, 5357, 5358, 5394, 5402, 5405, 5412, 5413, 5417, 5421, 5432, 5433, 5445, 5480, 5481, 5495, 5498, 5499, 5500, 5501, 5517, 5550, 5554, 5555, 5556, 5568, 5601, 5631, 5632, +5656, 5666, 5667, 5670, 5672, 5678, 5683, 5701, 5718, 5719, 5722, 5723, 5724, 5741, 5742, 5800, 5900, 5931, 5938, 5984, 5985, 5986, 5988, 6000, 6001, 6002, 6003, 6004, 6005, 6006, +6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, +6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6060, 6061, 6062, 6063, 6086, 6100, 6101, +6110, 6111, 6112, 6113, 6136, 6159, 6200, 6201, 6225, 6227, 6240, 6244, 6255, 6257, 6260, 6262, 6343, 6346, 6347, 6350, 6379, 6389, 6432, 6436, 6437, 6444, 6445, 6463, 6502, 6513, +6514, 6515, 6543, 6556, 6560, 6566, 6571, 6600, 6601, 6602, 6619, 6622, 6653, 6660, 6665, 6679, 6690, 6697, 6699, 6715, 6771, 6783, 6789, 6869, 6881, 6888, 6889, 6891, 6901, 6902, +6969, 6970, 7000, 7001, 7002, 7005, 7006, 7010, 7022, 7023, 7025, 7047, 7070, 7133, 7144, 7145, 7171, 7262, 7272, 7306, 7307, 7312, 7396, 7400, 7401, 7402, 7471, 7473, 7474, 7478, +7542, 7547, 7575, 7624, 7631, 7634, 7652, 7655, 7656, 7670, 7687, 7707, 7717, 7777, 7831, 7880, 7890, 7915, 7935, 7946, 7990, 8000, 8005, 8006, 8007, 8008, 8009, 8042, 8069, 8070, +8074, 8075, 8080, 8088, 8089, 8090, 8091, 8092, 8111, 8112, 8116, 8118, 8123, 8139, 8140, 8172, 8184, 8194, 8200, 8222, 8243, 8245, 8280, 8281, 8291, 8303, 8332, 8333, 8337, 8384, +8388, 8443, 8444, 8484, 8500, 8530, 8531, 8580, 8629, 8642, 8691, 8767, 8834, 8840, 8880, 8883, 8887, 8888, 8889, 8983, 8997, 8998, 8999, 9000, 9001, 9002, 9006, 9030, 9042, 9043, +9050, 9060, 9080, 9090, 9091, 9092, 9100, 9101, 9102, 9103, 9119, 9150, 9191, 9199, 9200, 9217, 9293, 9300, 9303, 9306, 9309, 9312, 9332, 9333, 9339, 9389, 9418, 9419, 9420, 9421, +9422, 9425, 9443, 9535, 9536, 9600, 9675, 9676, 9695, 9785, 9800, 9875, 9898, 9899, 9981, 9982, 9987, 9993, 9997, 9999, 10000, 10001, 10009, 10010, 10024, 10025, 10042, 10050, 10051, +10080, 10110, 10172, 10200, 10201, 10212, 10308, 10480, 10505, 10514, 10823, 10891, 10933, 11001, 11111, 11112, 11211, 11214, 11215, 11235, 11311, 11371, 11753, 12012, 12013, 12035, +12043, 12046, 12201, 12222, 12223, 12345, 12443, 12489, 12975, 13000, 13008, 13075, 13720, 13721, 13724, 13782, 13783, 13785, 13786, 14550, 14567, 15000, 15345, 15441, 15567, 15672, +16000, 16080, 16200, 16225, 16250, 16261, 16300, 16384, 16385, 16386, 16387, 16393, 16394, 16395, 16396, 16397, 16398, 16399, 16400, 16401, 16402, 16403, 16404, 16405, 16406, 16407, +16408, 16409, 16410, 16411, 16412, 16413, 16414, 16415, 16416, 16417, 16418, 16419, 16420, 16421, 16422, 16423, 16424, 16425, 16426, 16427, 16428, 16429, 16430, 16431, 16432, 16433, +16434, 16435, 16436, 16437, 16438, 16439, 16440, 16441, 16442, 16443, 16444, 16445, 16446, 16447, 16448, 16449, 16450, 16451, 16452, 16453, 16454, 16455, 16456, 16457, 16458, 16459, +16460, 16461, 16462, 16463, 16464, 16465, 16466, 16467, 16468, 16469, 16470, 16471, 16472, 16482, 16567, 17011, 17500, 18091, 18092, 18104, 18200, 18201, 18206, 18300, 18301, 18306, +18333, 18400, 18401, 18505, 18506, 18605, 18606, 19000, 19001, 19132, 19150, 19226, 19294, 19295, 19302, 19812, 19813, 19814, 19999, 20000, 20560, 20595, 20808, 21025, 22000, 22136, +22222, 23073, 23399, 23513, 24441, 24444, 24465, 24554, 24800, 24842, 25565, 25575, 25826, 26000, 26900, 27000, 27016, 27017, 27031, 27036, 27037, 27374, 27500, 27888, 27901, 27950, +27960, 28001, 28015, 28770, 28785, 28852, 28910, 28960, 29000, 29070, 29900, 29920, 30564, 31337, 31416, 31438, 31457, 32137, 32400, 32764, 32887, 32976, 33434, 33848, 34000, 34197, +35357, 37008, 40000, 43110, 43594, 44405, 44818, 47001, 47808, 49151]; + +// From https://github.com/coverslide/node-alea +const AleaModule = function () { + + 'use strict'; + + // importState to sync generator states + Alea.importState = function(i){ + var random = new Alea(); + random.importState(i); + return random; + }; + + return Alea; + + function Alea() { + return (function(args) { + // Johannes Baagøe , 2010 + var s0 = 0; + var s1 = 0; + var s2 = 0; + var c = 1; + + if (args.length == 0) { + args = [+new Date]; + } + var mash = Mash(); + s0 = mash(' '); + s1 = mash(' '); + s2 = mash(' '); + + for (var i = 0; i < args.length; i++) { + s0 -= mash(args[i]); + if (s0 < 0) { + s0 += 1; + } + s1 -= mash(args[i]); + if (s1 < 0) { + s1 += 1; + } + s2 -= mash(args[i]); + if (s2 < 0) { + s2 += 1; + } + } + mash = null; + + var random = function() { + var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32 + s0 = s1; + s1 = s2; + return s2 = t - (c = t | 0); + }; + random.uint32 = function() { + return random() * 0x100000000; // 2^32 + }; + random.fract53 = function() { + return random() + + (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53 + }; + random.version = 'Alea 0.9'; + random.args = args; + + // my own additions to sync state between two generators + random.exportState = function(){ + return [s0, s1, s2, c]; + }; + random.importState = function(i){ + s0 = +i[0] || 0; + s1 = +i[1] || 0; + s2 = +i[2] || 0; + c = +i[3] || 0; + }; + + return random; + + } (Array.prototype.slice.call(arguments))); + } + + function Mash() { + var n = 0xefc8249d; + + var mash = function(data) { + data = data.toString(); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + }; + + mash.version = 'Mash 0.9'; + return mash; + } +} + +const Alea = AleaModule() + +function findClipperPort(state) { + const minPort = 1024 + const maxPort = 49151 + + let prng = null; + if (!state) { + prng = new Alea(1867) + state = { prng: prng } + } else { + prng = state.prng; + } + + const randomPort = () => { + return minPort + Math.floor(prng() * ((maxPort + 1) - minPort)); + } + + let port = null; + for (let i = 0; i < maxPort; i++) { + port = randomPort(); + if (reservedPorts.indexOf(port) < 0) break; + } + + if (!port) throw new Error('Cannot find a non-reserved port'); + + state.port = port; + + return state; +} + +module.exports = findClipperPort; \ No newline at end of file