[D2D] Introduce GlobalDeviceWebServer for d2dservice.
[platform/framework/web/wrtjs.git] / d2d_app / node_modules / get-port / index.js
1 'use strict';
2 const net = require('net');
3
4 class Locked extends Error {
5         constructor(port) {
6                 super(`${port} is locked`);
7         }
8 }
9
10 const lockedPorts = {
11         old: new Set(),
12         young: new Set()
13 };
14
15 // On this interval, the old locked ports are discarded,
16 // the young locked ports are moved to old locked ports,
17 // and a new young set for locked ports are created.
18 const releaseOldLockedPortsIntervalMs = 1000 * 15;
19
20 // Lazily create interval on first use
21 let interval;
22
23 const getAvailablePort = options => new Promise((resolve, reject) => {
24         const server = net.createServer();
25         server.unref();
26         server.on('error', reject);
27         server.listen(options, () => {
28                 const {port} = server.address();
29                 server.close(() => {
30                         resolve(port);
31                 });
32         });
33 });
34
35 const portCheckSequence = function * (ports) {
36         if (ports) {
37                 yield * ports;
38         }
39
40         yield 0; // Fall back to 0 if anything else failed
41 };
42
43 module.exports = async options => {
44         let ports;
45
46         if (options) {
47                 ports = typeof options.port === 'number' ? [options.port] : options.port;
48         }
49
50         if (interval === undefined) {
51                 interval = setInterval(() => {
52                         lockedPorts.old = lockedPorts.young;
53                         lockedPorts.young = new Set();
54                 }, releaseOldLockedPortsIntervalMs);
55
56                 // Does not exist in some environments (Electron, Jest jsdom env, browser, etc).
57                 if (interval.unref) {
58                         interval.unref();
59                 }
60         }
61
62         for (const port of portCheckSequence(ports)) {
63                 try {
64                         let availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop
65                         while (lockedPorts.old.has(availablePort) || lockedPorts.young.has(availablePort)) {
66                                 if (port !== 0) {
67                                         throw new Locked(port);
68                                 }
69
70                                 availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop
71                         }
72
73                         lockedPorts.young.add(availablePort);
74                         return availablePort;
75                 } catch (error) {
76                         if (!['EADDRINUSE', 'EACCES'].includes(error.code) && !(error instanceof Locked)) {
77                                 throw error;
78                         }
79                 }
80         }
81
82         throw new Error('No available ports found');
83 };
84
85 module.exports.makeRange = (from, to) => {
86         if (!Number.isInteger(from) || !Number.isInteger(to)) {
87                 throw new TypeError('`from` and `to` must be integer numbers');
88         }
89
90         if (from < 1024 || from > 65535) {
91                 throw new RangeError('`from` must be between 1024 and 65535');
92         }
93
94         if (to < 1024 || to > 65536) {
95                 throw new RangeError('`to` must be between 1024 and 65536');
96         }
97
98         if (to < from) {
99                 throw new RangeError('`to` must be greater than or equal to `from`');
100         }
101
102         const generator = function * (from, to) {
103                 for (let port = from; port <= to; port++) {
104                         yield port;
105                 }
106         };
107
108         return generator(from, to);
109 };