@abtnode/router-provider 1.15.17 → 1.16.0-beta-8ee536d7

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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ const { getProvider } = require('router-provider');
9
9
  const providerName = 'nginx';
10
10
  const Provider = getProvider(providerName);
11
11
 
12
- const provider = new Provider({ configDirectory });
12
+ const provider = new Provider({ configDir });
13
13
  provider.start();
14
14
  provider.stop();
15
15
  // ...
package/lib/base.js CHANGED
@@ -1,3 +1,5 @@
1
+ const os = require('os');
2
+
1
3
  class BaseProvider {
2
4
  constructor(name) {
3
5
  this.name = name;
@@ -34,6 +36,18 @@ class BaseProvider {
34
36
  rotateLogs() {
35
37
  throw new Error('rotateLogs method is not implemented');
36
38
  }
39
+
40
+ getWorkerProcess(max = +process.env.ABT_NODE_MAX_CLUSTER_SIZE) {
41
+ if (this.isTest) {
42
+ return 1;
43
+ }
44
+
45
+ if (process.env.NODE_ENV === 'production') {
46
+ return Math.min(os.cpus().length, max);
47
+ }
48
+
49
+ return 1;
50
+ }
37
51
  }
38
52
 
39
53
  module.exports = BaseProvider;
@@ -0,0 +1,338 @@
1
+ /* eslint-disable no-continue */
2
+ const url = require('url');
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const get = require('lodash/get');
6
+ const joinUrl = require('url-join');
7
+ const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
8
+ const { DEFAULT_IP_DOMAIN_SUFFIX, ROUTING_RULE_TYPES, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
9
+
10
+ const logger = require('@abtnode/logger')('router:default:daemon', { filename: 'engine' });
11
+
12
+ const { findCertificate, isSpecificDomain, toSlotDomain, matchRule } = require('../util');
13
+ const ProxyServer = require('./proxy');
14
+
15
+ const configPath = process.env.ABT_NODE_ROUTER_CONFIG;
16
+ const httpPort = +process.env.ABT_NODE_ROUTER_HTTP_PORT;
17
+ const httpsPort = +process.env.ABT_NODE_ROUTER_HTTPS_PORT;
18
+ const servicePort = +process.env.ABT_NODE_SERVICE_PORT;
19
+
20
+ if (fs.existsSync(configPath) === false) {
21
+ throw new Error('Router config file not found');
22
+ }
23
+
24
+ const wwwDir = path.join(path.dirname(configPath), 'www');
25
+ const fileIndex = fs.readFileSync(path.join(wwwDir, 'index.html')).toString();
26
+ const file404 = fs.readFileSync(path.join(wwwDir, '404.html')).toString();
27
+ const file5xx = fs.readFileSync(path.join(wwwDir, '5xx.html')).toString();
28
+ const file502 = fs.readFileSync(path.join(wwwDir, '502.html')).toString();
29
+
30
+ process.on('message', (msg) => {
31
+ if (msg.data === 'reload') {
32
+ logger.info('update config', msg);
33
+ updateConfig(true);
34
+ }
35
+ });
36
+
37
+ const config = { sites: [], rules: {}, certs: [], headers: [] };
38
+
39
+ // eslint-disable-next-line
40
+ const createRequestHandler = (id) => (req, res, target) => {
41
+ const rule = config.rules[id];
42
+
43
+ if (rule.type === ROUTING_RULE_TYPES.DIRECT_RESPONSE) {
44
+ if (rule.response.contentType) {
45
+ res.writeHead(rule.response.status, { 'Content-Type': rule.response.contentType });
46
+ }
47
+
48
+ res.end(rule.response.body);
49
+
50
+ return { abort: true };
51
+ }
52
+
53
+ // Note: redirection required for relative asset loading
54
+ if (req.method === 'GET' && rule.prefix !== '/') {
55
+ const parsed = url.parse(req.url);
56
+ if (parsed.pathname === rule.prefix) {
57
+ logger.info('redirect url', { url: req.url, rule });
58
+ parsed.pathname = joinUrl(rule.prefix, '/');
59
+ res.writeHead(307, { Location: url.format(parsed) });
60
+ res.end();
61
+ return { abort: true };
62
+ }
63
+ }
64
+
65
+ if (rule.type === ROUTING_RULE_TYPES.GENERAL_PROXY) {
66
+ // do not rewrite for internal servers
67
+ } else if (rule.prefix.includes(WELLKNOWN_SERVICE_PATH_PREFIX)) {
68
+ // do not rewrite for service requests
69
+ } else {
70
+ req.url = joinUrl(rule.target, req.url.substr(rule.prefix.length));
71
+ }
72
+ if (req.url.startsWith('/') === false) {
73
+ req.url = `/${req.url}`;
74
+ }
75
+ logger.debug('transform url', { url: req.url, rule });
76
+
77
+ // NOTE: x-* header keys must be lower case to avoid collisions from chained proxy
78
+ if (rule.did) {
79
+ req.headers['x-blocklet-did'] = rule.did;
80
+ }
81
+ if (rule.componentId) {
82
+ req.headers['x-blocklet-component-id'] = rule.componentId;
83
+ }
84
+
85
+ req.headers['x-path-prefix'] = rule.prefix.replace(WELLKNOWN_SERVICE_PATH_PREFIX, '') || '/';
86
+ req.headers['x-group-path-prefix'] = rule.groupPrefix || '/';
87
+
88
+ if (rule.type === ROUTING_RULE_TYPES.BLOCKLET) {
89
+ req.headers['x-blocklet-url'] = `http://127.0.0.1:${rule.port}`;
90
+ }
91
+
92
+ if (rule.ruleId) {
93
+ req.headers['x-routing-rule-id'] = rule.ruleId;
94
+ }
95
+
96
+ if (rule.target && rule.target !== '/') {
97
+ req.headers['x-routing-rule-path-prefix'] = rule.target;
98
+ }
99
+ };
100
+
101
+ // eslint-disable-next-line
102
+ const sharedResolver = (host, url, req) => {
103
+ // match slot domain
104
+ const domain = toSlotDomain(host);
105
+
106
+ // match specific domain
107
+ let site = config.sites.find((x) => x.domain === domain);
108
+
109
+ // match wildcard domain
110
+ if (!site) {
111
+ site = config.sites.find((x) => checkDomainMatch(x.domain, domain));
112
+ }
113
+
114
+ // fallback to default domain
115
+ if (!site) {
116
+ site = config.sites.find((x) => x.domain === '_');
117
+ }
118
+
119
+ const rule = matchRule(site.rules, url);
120
+ if (!rule) {
121
+ logger.warn('rule not found for request', { host, url, domain });
122
+ return null;
123
+ }
124
+
125
+ const match = findCertificate(config.certs, domain);
126
+ const upstream = `http://127.0.0.1:${rule.type === ROUTING_RULE_TYPES.BLOCKLET ? servicePort : rule.port}`;
127
+ logger.debug('resolved request', { host, url, domain, upstream, rule });
128
+
129
+ return {
130
+ id: rule.id,
131
+ url: upstream,
132
+ path: '/',
133
+ opts: {
134
+ ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
135
+ onRequest: createRequestHandler(rule.id),
136
+ },
137
+ };
138
+ };
139
+ sharedResolver.priority = 200;
140
+
141
+ const onError = (err, req, res) => {
142
+ logger.error('proxy error', { error: err });
143
+ if (typeof res.writeHead !== 'function') {
144
+ return;
145
+ }
146
+
147
+ if (err.code === 'ECONNREFUSED') {
148
+ res.writeHead(502);
149
+ res.end(file502);
150
+ } else if (err.code === 'NOTFOUND') {
151
+ const parsed = url.parse(req.url);
152
+ if (config.info.enableWelcomePage && parsed.pathname === '/') {
153
+ res.writeHead(404);
154
+ res.end(fileIndex);
155
+ } else {
156
+ res.writeHead(404);
157
+ res.end(file404);
158
+ }
159
+ } else {
160
+ res.writeHead(500);
161
+ res.end(file5xx);
162
+ }
163
+ };
164
+
165
+ const corsHandler = (host, req, res) => {
166
+ if (req.method === 'OPTIONS') {
167
+ const domain = toSlotDomain(host);
168
+ const site = config.sites.find((x) => x.domain === domain);
169
+ if (!site) {
170
+ return true;
171
+ }
172
+
173
+ const allowedOrigins = site.corsAllowedOrigins;
174
+ const currentOrigin = req.headers.origin;
175
+ if (allowedOrigins.includes('*')) {
176
+ res.writeHead(204, {
177
+ Vary: 'Origin',
178
+ 'Access-Control-Allow-Origin': '*',
179
+ 'Access-Control-Allow-Credentials': false,
180
+ 'Access-Control-Allow-Methods': 'POST, GET, HEAD, PUT, DELETE, OPTIONS',
181
+ 'Access-Control-Allow-Headers':
182
+ 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,$http_access_control_request_headers',
183
+ 'Access-Control-Max-Age': 1800,
184
+ });
185
+ res.end();
186
+ return false;
187
+ }
188
+
189
+ if (allowedOrigins.some((x) => checkDomainMatch(x, currentOrigin))) {
190
+ res.writeHead(204, {
191
+ Vary: 'Origin',
192
+ 'Access-Control-Allow-Origin': currentOrigin,
193
+ 'Access-Control-Allow-Credentials': false,
194
+ 'Access-Control-Allow-Methods': 'POST, GET, HEAD, PUT, DELETE, OPTIONS',
195
+ 'Access-Control-Allow-Headers':
196
+ 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,$http_access_control_request_headers',
197
+ 'Access-Control-Max-Age': 1800,
198
+ });
199
+ res.end();
200
+ return false;
201
+ }
202
+ }
203
+
204
+ return true;
205
+ };
206
+
207
+ // internal servers
208
+ const internalServers = {};
209
+ const ensureInternalServer = (port) => {
210
+ if (internalServers[port]) {
211
+ return;
212
+ }
213
+
214
+ const server = new ProxyServer({ xfwd: false, internal: true, port, headers: config.headers, onError, corsHandler });
215
+ server.addResolver(sharedResolver);
216
+ internalServers[port] = server;
217
+ logger.info('internal server ready on port', { port });
218
+ };
219
+
220
+ const updateConfig = (reload = false) => {
221
+ const { sites, certificates: rawCerts, info } = fs.readJsonSync(configPath);
222
+ config.info = info;
223
+
224
+ const obj = get(info, 'routing.headers', {});
225
+ config.headers = Object.keys(obj).map((key) => ({ key, value: JSON.parse(obj[key]) }));
226
+
227
+ // format certificates
228
+ config.certs = rawCerts.map((x) => {
229
+ x.privateKey = Buffer.from(x.privateKey);
230
+ x.certificate = Buffer.from(x.certificate);
231
+ return x;
232
+ });
233
+
234
+ // format sites
235
+ sites.forEach((site) => {
236
+ site.rules.forEach((rule) => {
237
+ config.rules[rule.id] = rule;
238
+ });
239
+ });
240
+
241
+ // re-register sites in reload mode
242
+ if (reload) {
243
+ const oldDomains = config.sites.map((x) => x.domain);
244
+ const newDomains = sites.map((x) => x.domain);
245
+
246
+ const addedDomains = newDomains.filter((x) => !oldDomains.includes(x));
247
+ for (const domain of addedDomains) {
248
+ if (isSpecificDomain(domain) === false) {
249
+ continue;
250
+ }
251
+ const site = sites.find((x) => x.domain === domain);
252
+ const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
253
+ if (!rule) {
254
+ continue;
255
+ }
256
+
257
+ logger.info('register domain on reload', { domain });
258
+ const match = findCertificate(config.certs, site.domain);
259
+ main.register({
260
+ src: site.domain,
261
+ target: `http://127.0.0.1:${rule.port}`,
262
+ ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
263
+ onRequest: createRequestHandler(rule.id),
264
+ });
265
+ }
266
+
267
+ const removedDomains = oldDomains.filter((x) => !newDomains.includes(x));
268
+ for (const domain of removedDomains) {
269
+ main.unregister(domain);
270
+ logger.info('unregister domain on reload', { domain });
271
+ delete main.routing[domain];
272
+ logger.info('remove domain cache on reload', { domain });
273
+ }
274
+
275
+ config.sites.filter((x) => x.port).forEach((x) => ensureInternalServer(x.port));
276
+ }
277
+
278
+ config.sites = sites;
279
+ };
280
+
281
+ // load initial config
282
+ updateConfig();
283
+
284
+ // create main server
285
+ const defaultCert = config.certs.find((x) => x.domain.endsWith(DEFAULT_IP_DOMAIN_SUFFIX));
286
+ const main = new ProxyServer({
287
+ xfwd: true,
288
+ onError,
289
+ corsHandler,
290
+ port: httpPort,
291
+ headers: config.headers,
292
+ ssl: {
293
+ port: httpsPort,
294
+ key: defaultCert ? defaultCert.privateKey : null,
295
+ cert: defaultCert ? defaultCert.certificate : null,
296
+ opts: {
297
+ honorCipherOrder: false,
298
+ maxVersion: 'TLSv1.3',
299
+ minVersion: 'TLSv1.2',
300
+ },
301
+ },
302
+ });
303
+ main.sites = config.sites;
304
+ main.addResolver(sharedResolver);
305
+
306
+ // register sites: to ensure https certificates are loaded
307
+ config.sites.forEach((site) => {
308
+ const { domain } = site;
309
+ if (isSpecificDomain(domain) === false) {
310
+ return;
311
+ }
312
+
313
+ const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
314
+ if (!rule) {
315
+ return;
316
+ }
317
+
318
+ const match = findCertificate(config.certs, domain);
319
+ logger.info('register domain on start', { domain });
320
+ main.register({
321
+ src: domain,
322
+ target: `http://127.0.0.1:${rule.port}`,
323
+ ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
324
+ onRequest: createRequestHandler(rule.id),
325
+ });
326
+ });
327
+
328
+ // initialize internal servers
329
+ config.sites.filter((x) => x.port).forEach((x) => ensureInternalServer(x.port));
330
+
331
+ logger.info(`Default routing engine ready on ${httpPort} and ${httpsPort}`);
332
+
333
+ ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'].forEach((e) => {
334
+ process.on(e, () => {
335
+ main.close();
336
+ Object.keys(internalServers).forEach((key) => internalServers[key].close());
337
+ });
338
+ });
@@ -0,0 +1,252 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const http = require('http');
4
+ const shelljs = require('shelljs');
5
+ const get = require('lodash/get');
6
+ const omit = require('lodash/omit');
7
+ const sortBy = require('lodash/sortBy');
8
+ const objectHash = require('object-hash');
9
+ const promiseRetry = require('promise-retry');
10
+ const pm2 = require('@abtnode/util/lib/async-pm2');
11
+ const { PROCESS_NAME_ROUTER, DAEMON_MAX_MEM_LIMIT_IN_MB } = require('@abtnode/constant');
12
+
13
+ const logger = require('@abtnode/logger')('router:default:controller', { filename: 'engine' });
14
+
15
+ const BaseProvider = require('../base');
16
+ const {
17
+ decideHttpPort,
18
+ decideHttpsPort,
19
+ getUsablePorts,
20
+ get404Template,
21
+ get502Template,
22
+ get5xxTemplate,
23
+ getWelcomeTemplate,
24
+ formatRoutingTable,
25
+ } = require('../util');
26
+
27
+ class DefaultProvider extends BaseProvider {
28
+ constructor({ configDir, httpPort, httpsPort, isTest }) {
29
+ super('default');
30
+
31
+ this.isTest = !!isTest;
32
+ this.configDir = configDir;
33
+
34
+ this.logDir = path.join(this.configDir, 'log');
35
+ this.wwwDir = path.join(this.configDir, 'www');
36
+
37
+ this.engineLog = path.join(this.logDir, 'engine.log'); // should be symbol link created by winston
38
+ this.errorLog = path.join(this.logDir, 'engine-error.log'); // should be symbol link created by winston
39
+ this.ctrlLog = path.join(this.logDir, 'controller.log'); // managed by pm2
40
+
41
+ this.configPath = path.join(this.configDir, 'config.json');
42
+
43
+ this.httpPort = decideHttpPort(httpPort);
44
+ this.httpsPort = decideHttpsPort(httpsPort);
45
+
46
+ // ensure directories
47
+ [this.configDir, this.logDir, this.wwwDir].forEach((dir) => {
48
+ if (!fs.existsSync(dir)) {
49
+ fs.mkdirSync(dir);
50
+ }
51
+ });
52
+
53
+ this.initialize();
54
+ }
55
+
56
+ // Services are not supported by default provider
57
+ update({ routingTable = [], certificates = [], nodeInfo = {} } = {}) {
58
+ this.info = nodeInfo;
59
+
60
+ logger.info('update routing table');
61
+ this._addWwwFiles(nodeInfo);
62
+ const { sites: rawSites } = formatRoutingTable(routingTable);
63
+ const sites = sortBy(rawSites, (x) => -x.domain.length);
64
+ sites.forEach((site) => {
65
+ site.rules.forEach((rule) => {
66
+ // NOTE: we generate uniq hash from rule, because there are duplicate ruleId across different rules
67
+ rule.id = objectHash(rule);
68
+ });
69
+ });
70
+
71
+ fs.outputJSONSync(this.configPath, { sites, certificates, info: this.info }, { spaces: 2 });
72
+ }
73
+
74
+ async reload() {
75
+ if (process.env.NODE_ENV === 'development') {
76
+ return this.start();
77
+ }
78
+
79
+ // ensure daemon is live
80
+ let [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
81
+ if (!info) {
82
+ await this.start();
83
+ [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
84
+ logger.info('start daemon because not running', omit(info, ['pm2_env']));
85
+ } else {
86
+ logger.info('daemon already running', omit(info, ['pm2_env']));
87
+ }
88
+
89
+ // send signal to daemon
90
+ return new Promise((resolve) => {
91
+ pm2.sendDataToProcessId(info.pm2_env.pm_id, { type: 'process:msg', data: 'reload', topic: 'reload' }, (err) => {
92
+ if (err) {
93
+ logger.error('failed to reload', { error: err });
94
+ }
95
+
96
+ resolve();
97
+ });
98
+ });
99
+ }
100
+
101
+ async start() {
102
+ await pm2.startAsync({
103
+ namespace: 'daemon',
104
+ name: PROCESS_NAME_ROUTER,
105
+ script: path.join(__dirname, 'daemon.js'),
106
+ max_memory_restart: `${get(this.info, 'runtimeConfig.daemonMaxMemoryLimit', DAEMON_MAX_MEM_LIMIT_IN_MB)}M`,
107
+ output: this.ctrlLog,
108
+ error: this.ctrlLog,
109
+ cwd: __dirname,
110
+ max_restarts: 1,
111
+ time: true,
112
+ mergeLogs: true,
113
+ execMode: 'fork', // Note: there are some issues with reloading when run in cluster mode
114
+ env: {
115
+ ABT_NODE_LOG_DIR: this.logDir, // Note: this tells logger module to write log to router log
116
+ ABT_NODE_ROUTER_CONFIG: this.configPath,
117
+ ABT_NODE_ROUTER_HTTP_PORT: this.httpPort,
118
+ ABT_NODE_ROUTER_HTTPS_PORT: this.httpsPort,
119
+ },
120
+ });
121
+ }
122
+
123
+ async restart() {
124
+ await pm2.restartAsync(PROCESS_NAME_ROUTER, { updateEnv: true });
125
+ }
126
+
127
+ async stop() {
128
+ const [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
129
+ if (!info) {
130
+ return null;
131
+ }
132
+
133
+ const proc = await pm2.deleteAsync(PROCESS_NAME_ROUTER);
134
+ return proc;
135
+ }
136
+
137
+ initialize() {}
138
+
139
+ validateConfig() {}
140
+
141
+ rotateLogs() {}
142
+
143
+ _addWwwFiles(nodeInfo) {
144
+ const welcomePage = nodeInfo.enableWelcomePage ? getWelcomeTemplate(nodeInfo) : get404Template(nodeInfo);
145
+ fs.writeFileSync(`${this.wwwDir}/index.html`, welcomePage); // disable index.html
146
+ fs.writeFileSync(`${this.wwwDir}/404.html`, get404Template(nodeInfo));
147
+ fs.writeFileSync(`${this.wwwDir}/502.html`, get502Template(nodeInfo));
148
+ fs.writeFileSync(`${this.wwwDir}/5xx.html`, get5xxTemplate(nodeInfo));
149
+ // 将 @abtnode/router-templates/lib/styles (font 相关样式) 复制到 www/router-template-styles 中
150
+ fs.copySync(
151
+ `${path.dirname(require.resolve('@abtnode/router-templates/package.json'))}/lib/styles`,
152
+ `${this.wwwDir}/router-template-styles`
153
+ );
154
+ }
155
+
156
+ getLogFilesForToday() {
157
+ return {
158
+ access: fs.realpathSync(this.engineLog),
159
+ error: fs.realpathSync(this.errorLog),
160
+ };
161
+ }
162
+
163
+ getLogDir() {
164
+ return this.logDir;
165
+ }
166
+ }
167
+
168
+ DefaultProvider.describe = async ({ configDir = '' } = {}) => {
169
+ const meta = {
170
+ name: 'default',
171
+ description: 'Use default to provide a lightweight routing layer for development purpose',
172
+ };
173
+
174
+ try {
175
+ // Use retry as a workaround for race-conditions between check and normal start
176
+ const result = await promiseRetry((retry) => DefaultProvider.check({ configDir }).catch(retry), { retries: 3 });
177
+ return { ...meta, ...result };
178
+ } catch (err) {
179
+ return { ...meta, error: err.message, available: false, running: false };
180
+ }
181
+ };
182
+
183
+ const getRouterStatus = async (configDir) => {
184
+ const result = {
185
+ managed: false,
186
+ pid: 0,
187
+ running: false,
188
+ };
189
+
190
+ const [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
191
+ if (!info) {
192
+ return result;
193
+ }
194
+
195
+ result.running = true;
196
+ if (info.pm2_env.ABT_NODE_ROUTER_CONFIG.indexOf(configDir) > -1) {
197
+ result.managed = true;
198
+ result.pid = info.pid;
199
+ }
200
+
201
+ return result;
202
+ };
203
+
204
+ DefaultProvider.exists = () => true;
205
+ DefaultProvider.getStatus = getRouterStatus;
206
+ DefaultProvider.check = async ({ configDir = '' } = {}) => {
207
+ logger.info('check default provider', { configDir });
208
+ const binPath = shelljs.which('node').stdout;
209
+ const result = {
210
+ binPath,
211
+ available: true,
212
+ running: false,
213
+ managed: false,
214
+ error: '',
215
+ };
216
+
217
+ const status = await getRouterStatus(configDir);
218
+ result.managed = status.managed;
219
+ result.running = status.running;
220
+
221
+ if (status.running && !status.managed) {
222
+ result.available = false;
223
+ result.error = 'Seems the default routing engine is running, please terminate the process before try again.';
224
+ }
225
+
226
+ return result;
227
+ };
228
+
229
+ DefaultProvider.getUsablePorts = async () =>
230
+ getUsablePorts(
231
+ 'default',
232
+ (port) =>
233
+ new Promise((resolve) => {
234
+ try {
235
+ const server = http.createServer();
236
+
237
+ server.once('error', () => {
238
+ server.close();
239
+ resolve(false);
240
+ });
241
+
242
+ server.listen(port, (err) => {
243
+ server.close();
244
+ resolve(!err);
245
+ });
246
+ } catch (err) {
247
+ resolve(false);
248
+ }
249
+ })
250
+ );
251
+
252
+ module.exports = DefaultProvider;