@nullbridge/sdk 1.0.3 → 1.0.5
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/package.json +1 -1
- package/src/agents.js +72 -17
- package/src/errors.js +36 -0
- package/src/index.js +37 -40
- package/src/license.js +51 -60
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -9,34 +9,36 @@ class AgentRegistry {
|
|
|
9
9
|
constructor(config, http) {
|
|
10
10
|
this._config = config;
|
|
11
11
|
this._http = http;
|
|
12
|
-
this._agents = new Map();
|
|
12
|
+
this._agents = new Map();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Register a new AI agent with NullBridge.
|
|
17
17
|
*
|
|
18
18
|
* @param {object} options
|
|
19
|
+
* @param {string} [options.id] - Stable unique identifier. Auto-generated if omitted.
|
|
19
20
|
* @param {string} options.name - Human-readable agent name (e.g. 'claims-processor')
|
|
20
21
|
* @param {string} options.type - Agent type: 'llm' | 'rpa' | 'workflow' | 'custom'
|
|
21
22
|
* @param {string} [options.model] - Model name (e.g. 'gpt-4o', 'claude-3-5-sonnet')
|
|
22
|
-
* @param {string[]} [options.scopes] -
|
|
23
|
-
* @param {object} [options.metadata] -
|
|
23
|
+
* @param {string[]} [options.scopes] - Permission scopes (e.g. ['read:claims', 'write:reports'])
|
|
24
|
+
* @param {object} [options.metadata] - Additional metadata
|
|
25
|
+
* @param {number} [options.heartbeatInterval] - Heartbeat interval in ms (default: 60000)
|
|
24
26
|
* @returns {Promise<Agent>}
|
|
25
27
|
*/
|
|
26
28
|
async register(options = {}) {
|
|
27
29
|
if (!options.name) throw new Error('[NullBridge] agent.name is required');
|
|
28
30
|
if (!options.type) throw new Error('[NullBridge] agent.type is required');
|
|
29
31
|
|
|
30
|
-
const agentId = this._generateAgentId(options.name);
|
|
32
|
+
const agentId = options.id || this._generateAgentId(options.name);
|
|
31
33
|
|
|
32
34
|
const payload = {
|
|
33
|
-
id:
|
|
34
|
-
name:
|
|
35
|
-
type:
|
|
36
|
-
model:
|
|
37
|
-
scopes:
|
|
38
|
-
metadata:
|
|
39
|
-
sdk_version:
|
|
35
|
+
id: agentId,
|
|
36
|
+
name: options.name,
|
|
37
|
+
type: options.type,
|
|
38
|
+
model: options.model || null,
|
|
39
|
+
scopes: options.scopes || [],
|
|
40
|
+
metadata: options.metadata || {},
|
|
41
|
+
sdk_version: '1.0.3',
|
|
40
42
|
registered_at: Math.floor(Date.now() / 1000),
|
|
41
43
|
};
|
|
42
44
|
|
|
@@ -47,13 +49,20 @@ class AgentRegistry {
|
|
|
47
49
|
{ license_key: this._config.licenseKey, agent: payload }
|
|
48
50
|
);
|
|
49
51
|
|
|
50
|
-
const agent = status === 200 || status === 201
|
|
52
|
+
const agent = (status === 200 || status === 201)
|
|
51
53
|
? { ...payload, ...body.agent, _registered: true }
|
|
52
54
|
: { ...payload, _registered: false, _local: true };
|
|
53
55
|
|
|
54
56
|
this._agents.set(agentId, agent);
|
|
55
57
|
console.info(`[NullBridge] Agent registered: ${options.name} (${agentId})`);
|
|
56
|
-
|
|
58
|
+
|
|
59
|
+
const instance = new Agent(agent, this._config, this._http);
|
|
60
|
+
|
|
61
|
+
// Start heartbeat automatically
|
|
62
|
+
const heartbeatMs = options.heartbeatInterval || 60000;
|
|
63
|
+
instance.startHeartbeat(heartbeatMs);
|
|
64
|
+
|
|
65
|
+
return instance;
|
|
57
66
|
|
|
58
67
|
} catch (err) {
|
|
59
68
|
// API unreachable — register locally and continue
|
|
@@ -103,9 +112,10 @@ class AgentRegistry {
|
|
|
103
112
|
*/
|
|
104
113
|
class Agent {
|
|
105
114
|
constructor(data, config, http) {
|
|
106
|
-
this._data
|
|
107
|
-
this._config
|
|
108
|
-
this._http
|
|
115
|
+
this._data = data;
|
|
116
|
+
this._config = config;
|
|
117
|
+
this._http = http;
|
|
118
|
+
this._heartbeatTimer = null;
|
|
109
119
|
|
|
110
120
|
// Expose agent properties
|
|
111
121
|
this.id = data.id;
|
|
@@ -166,9 +176,10 @@ class Agent {
|
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
/**
|
|
169
|
-
* Deregister this agent — revokes its identity and
|
|
179
|
+
* Deregister this agent — revokes its identity and stops heartbeat
|
|
170
180
|
*/
|
|
171
181
|
async deregister() {
|
|
182
|
+
this.stopHeartbeat();
|
|
172
183
|
try {
|
|
173
184
|
await this._http.post(
|
|
174
185
|
this._config.apiUrl,
|
|
@@ -181,6 +192,50 @@ class Agent {
|
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Send a heartbeat to update last_seen in the dashboard.
|
|
197
|
+
* Called automatically by startHeartbeat() — you don't need to call this manually.
|
|
198
|
+
*/
|
|
199
|
+
async heartbeat() {
|
|
200
|
+
try {
|
|
201
|
+
await this._http.post(
|
|
202
|
+
this._config.apiUrl,
|
|
203
|
+
`/sdk/agents/${this.id}/heartbeat`,
|
|
204
|
+
{ license_key: this._config.licenseKey }
|
|
205
|
+
);
|
|
206
|
+
if (this._config.debug) {
|
|
207
|
+
console.debug(`[NullBridge] Heartbeat sent: ${this.name}`);
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
// Silently fail — heartbeat is best-effort
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Start sending periodic heartbeats to keep last_seen current in the dashboard.
|
|
216
|
+
* Called automatically after register(). You only need this if you stopped it manually.
|
|
217
|
+
*
|
|
218
|
+
* @param {number} [intervalMs=60000] - How often to ping in milliseconds
|
|
219
|
+
*/
|
|
220
|
+
startHeartbeat(intervalMs = 60000) {
|
|
221
|
+
if (this._heartbeatTimer) return; // already running
|
|
222
|
+
// Send immediately on start
|
|
223
|
+
this.heartbeat();
|
|
224
|
+
this._heartbeatTimer = setInterval(() => this.heartbeat(), intervalMs);
|
|
225
|
+
// Allow Node.js to exit even if heartbeat is still running
|
|
226
|
+
if (this._heartbeatTimer.unref) this._heartbeatTimer.unref();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Stop sending heartbeats. Called automatically by deregister() and shutdown().
|
|
231
|
+
*/
|
|
232
|
+
stopHeartbeat() {
|
|
233
|
+
if (this._heartbeatTimer) {
|
|
234
|
+
clearInterval(this._heartbeatTimer);
|
|
235
|
+
this._heartbeatTimer = null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
184
239
|
toJSON() {
|
|
185
240
|
return {
|
|
186
241
|
id: this.id, name: this.name, type: this.type,
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NullBridgeLicenseError — thrown when license validation fails
|
|
5
|
+
*/
|
|
6
|
+
class NullBridgeLicenseError extends Error {
|
|
7
|
+
constructor(reason, message) {
|
|
8
|
+
super(message || `NullBridge license error: ${reason}. Contact brian@nullbridge.ai.`);
|
|
9
|
+
this.name = 'NullBridgeLicenseError';
|
|
10
|
+
this.reason = reason;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* NullBridgeApiError — thrown when the NullBridge API returns an error
|
|
16
|
+
*/
|
|
17
|
+
class NullBridgeApiError extends Error {
|
|
18
|
+
constructor(status, message, code) {
|
|
19
|
+
super(message || `NullBridge API error (${status})${code ? ': ' + code : ''}`);
|
|
20
|
+
this.name = 'NullBridgeApiError';
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* NullBridgeCredentialError — thrown for credential operation failures
|
|
28
|
+
*/
|
|
29
|
+
class NullBridgeCredentialError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(`[NullBridge] Credential error: ${message}`);
|
|
32
|
+
this.name = 'NullBridgeCredentialError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { NullBridgeLicenseError, NullBridgeApiError, NullBridgeCredentialError };
|
package/src/index.js
CHANGED
|
@@ -7,41 +7,25 @@ const { AuditLogger } = require('./audit');
|
|
|
7
7
|
const { AnomalyDetector } = require('./anomaly');
|
|
8
8
|
const { SiemStreamer } = require('./siem');
|
|
9
9
|
const { HttpClient } = require('./http');
|
|
10
|
+
const { NullBridgeLicenseError, NullBridgeApiError, NullBridgeCredentialError } = require('./errors');
|
|
11
|
+
|
|
12
|
+
const SDK_VERSION = '1.0.5';
|
|
10
13
|
|
|
11
|
-
const SDK_VERSION = '1.0.0';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* NullBridge SDK
|
|
15
|
-
* AI Agent Identity Governance Platform
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* const { NullBridge } = require('@nullbridge/sdk');
|
|
19
|
-
*
|
|
20
|
-
* const nb = new NullBridge({
|
|
21
|
-
* licenseKey: process.env.NULLBRIDGE_LICENSE_KEY,
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* await nb.init();
|
|
25
|
-
* const agent = await nb.agents.register({ name: 'my-agent', type: 'llm' });
|
|
26
|
-
*/
|
|
27
14
|
class NullBridge {
|
|
28
15
|
/**
|
|
29
16
|
* @param {object} config
|
|
30
17
|
* @param {string} config.licenseKey - Your NullBridge license key (NB-XXXX-XXXX-XXXX-XXXX)
|
|
31
|
-
* @param {string} [config.serverUrl] - License server URL (default: https://nullbridge-license-server-production.up.railway.app)
|
|
32
18
|
* @param {string} [config.apiUrl] - NullBridge API URL (default: https://api.nullbridge.ai)
|
|
33
|
-
* @param {
|
|
19
|
+
* @param {string} [config.serverUrl] - License server URL (default: https://nullbridge-license-server-production.up.railway.app)
|
|
20
|
+
* @param {boolean} [config.skipLicense] - Skip license check (for local development only)
|
|
34
21
|
* @param {boolean} [config.autoShutdown] - Shut down process if license is revoked (default: true)
|
|
35
|
-
* @param {number} [config.checkInterval] - License
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {string} [config.siem.endpoint] - SIEM webhook URL
|
|
38
|
-
* @param {string} [config.siem.format] - Log format: 'json' | 'cef' | 'leef' (default: 'json')
|
|
39
|
-
* @param {boolean} [config.debug] - Enable debug logging
|
|
22
|
+
* @param {number} [config.checkInterval] - License recheck interval in ms (default: 86400000 — 24h)
|
|
23
|
+
* @param {boolean} [config.debug] - Enable verbose logging
|
|
40
24
|
*/
|
|
41
25
|
constructor(config = {}) {
|
|
42
26
|
if (!config.licenseKey && !config.skipLicense) {
|
|
43
|
-
throw new
|
|
44
|
-
'[NullBridge] licenseKey is required. Set NULLBRIDGE_LICENSE_KEY in your environment
|
|
27
|
+
throw new NullBridgeLicenseError('NO_KEY',
|
|
28
|
+
'[NullBridge] licenseKey is required. Set NULLBRIDGE_LICENSE_KEY in your environment.\n' +
|
|
45
29
|
'Contact brian@nullbridge.ai to obtain a license key.'
|
|
46
30
|
);
|
|
47
31
|
}
|
|
@@ -61,7 +45,6 @@ class NullBridge {
|
|
|
61
45
|
this._licenseData = null;
|
|
62
46
|
this._http = new HttpClient(this._config);
|
|
63
47
|
|
|
64
|
-
// Sub-modules — available after init()
|
|
65
48
|
this.license = new LicenseClient(this._config, this._http);
|
|
66
49
|
this.agents = new AgentRegistry(this._config, this._http);
|
|
67
50
|
this.credentials = new CredentialVault(this._config, this._http);
|
|
@@ -69,15 +52,15 @@ class NullBridge {
|
|
|
69
52
|
this.anomaly = new AnomalyDetector(this._config, this._http);
|
|
70
53
|
this.siem = this._config.siem ? new SiemStreamer(this._config, this._http) : null;
|
|
71
54
|
|
|
72
|
-
this._log(
|
|
55
|
+
this._log(`NullBridge SDK v${SDK_VERSION} initialized`);
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
/**
|
|
76
59
|
* Initialize NullBridge — validates license and starts background services.
|
|
77
|
-
* Call
|
|
60
|
+
* Call once on application startup before serving traffic.
|
|
78
61
|
*
|
|
79
|
-
* @returns {Promise<NullBridge>}
|
|
80
|
-
* @throws {
|
|
62
|
+
* @returns {Promise<NullBridge>}
|
|
63
|
+
* @throws {NullBridgeLicenseError} if license is invalid or expired
|
|
81
64
|
*/
|
|
82
65
|
async init() {
|
|
83
66
|
if (this._initialized) {
|
|
@@ -87,17 +70,26 @@ class NullBridge {
|
|
|
87
70
|
|
|
88
71
|
this._log('Initializing...');
|
|
89
72
|
|
|
90
|
-
// Validate license
|
|
91
73
|
if (!this._config.skipLicense) {
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
try {
|
|
75
|
+
this._licenseData = await this.license.validate();
|
|
76
|
+
this._log(`License valid — customer: ${this._licenseData.customer}, plan: ${this._licenseData.plan}`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof NullBridgeLicenseError) {
|
|
79
|
+
// Re-throw with clear message
|
|
80
|
+
throw new NullBridgeLicenseError(err.reason,
|
|
81
|
+
`[NullBridge] Cannot initialize — ${err.message}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
94
86
|
|
|
95
87
|
// Start periodic license checks
|
|
96
88
|
this.license.startWatcher((valid, reason) => {
|
|
97
89
|
if (!valid) {
|
|
98
|
-
console.error(`[NullBridge] License revoked: ${reason}
|
|
90
|
+
console.error(`[NullBridge] License revoked: ${reason}. Contact brian@nullbridge.ai.`);
|
|
99
91
|
if (this._config.autoShutdown) {
|
|
100
|
-
console.error('[NullBridge] Shutting down in 60 seconds.
|
|
92
|
+
console.error('[NullBridge] Shutting down in 60 seconds.');
|
|
101
93
|
setTimeout(() => process.exit(1), 60 * 1000);
|
|
102
94
|
}
|
|
103
95
|
}
|
|
@@ -106,19 +98,18 @@ class NullBridge {
|
|
|
106
98
|
this._log('License check skipped (development mode)');
|
|
107
99
|
}
|
|
108
100
|
|
|
109
|
-
// Start SIEM streaming if configured
|
|
110
101
|
if (this.siem) {
|
|
111
102
|
await this.siem.start();
|
|
112
103
|
this._log('SIEM streaming started');
|
|
113
104
|
}
|
|
114
105
|
|
|
115
106
|
this._initialized = true;
|
|
116
|
-
console.info(
|
|
107
|
+
console.info(`[NullBridge] Ready — SDK v${SDK_VERSION}`);
|
|
117
108
|
return this;
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
/**
|
|
121
|
-
* Gracefully shut down
|
|
112
|
+
* Gracefully shut down — flushes audit logs, stops heartbeats, stops watchers.
|
|
122
113
|
*/
|
|
123
114
|
async shutdown() {
|
|
124
115
|
this._log('Shutting down...');
|
|
@@ -129,7 +120,7 @@ class NullBridge {
|
|
|
129
120
|
}
|
|
130
121
|
|
|
131
122
|
/**
|
|
132
|
-
* Returns current license data
|
|
123
|
+
* Returns current license data
|
|
133
124
|
*/
|
|
134
125
|
getLicenseInfo() {
|
|
135
126
|
return this._licenseData;
|
|
@@ -149,4 +140,10 @@ class NullBridge {
|
|
|
149
140
|
}
|
|
150
141
|
}
|
|
151
142
|
|
|
152
|
-
module.exports = {
|
|
143
|
+
module.exports = {
|
|
144
|
+
NullBridge,
|
|
145
|
+
SDK_VERSION,
|
|
146
|
+
NullBridgeLicenseError,
|
|
147
|
+
NullBridgeApiError,
|
|
148
|
+
NullBridgeCredentialError,
|
|
149
|
+
};
|
package/src/license.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { NullBridgeLicenseError } = require('./errors');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LicenseClient — validates and monitors NullBridge license keys
|
|
7
|
+
*/
|
|
3
8
|
class LicenseClient {
|
|
4
9
|
constructor(config, http) {
|
|
5
|
-
this._config
|
|
6
|
-
this._http
|
|
7
|
-
this.
|
|
8
|
-
this._data = null;
|
|
10
|
+
this._config = config;
|
|
11
|
+
this._http = http;
|
|
12
|
+
this._watcherTimer = null;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
|
-
* Validate license against
|
|
13
|
-
*
|
|
16
|
+
* Validate the license key against the license server
|
|
17
|
+
* @returns {Promise<object>} license data
|
|
18
|
+
* @throws {NullBridgeLicenseError} if license is invalid or expired
|
|
14
19
|
*/
|
|
15
20
|
async validate() {
|
|
16
21
|
try {
|
|
@@ -21,80 +26,66 @@ class LicenseClient {
|
|
|
21
26
|
);
|
|
22
27
|
|
|
23
28
|
if (status === 200 && body.valid) {
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
return {
|
|
30
|
+
valid: true,
|
|
31
|
+
customer: body.customer,
|
|
32
|
+
company: body.company,
|
|
33
|
+
plan: body.plan,
|
|
34
|
+
maxAgents: body.max_agents,
|
|
35
|
+
expiresAt: body.expires_at,
|
|
36
|
+
reason: body.reason,
|
|
37
|
+
};
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
// License
|
|
29
|
-
const reason
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const errors = {
|
|
33
|
-
SUSPENDED: `License suspended. Contact brian@nullbridge.ai to restore service.`,
|
|
34
|
-
CANCELLED: `License cancelled. Contact brian@nullbridge.ai.`,
|
|
35
|
-
EXPIRED: `License expired. Contact brian@nullbridge.ai to renew.`,
|
|
36
|
-
NOT_FOUND: `License key not found. Verify your NULLBRIDGE_LICENSE_KEY is correct.`,
|
|
37
|
-
GRACE_PERIOD: null, // still valid — handled below
|
|
38
|
-
};
|
|
40
|
+
// License server reachable but license invalid
|
|
41
|
+
const reason = body.reason || 'INVALID';
|
|
42
|
+
const message = body.message || 'Invalid or expired license key.';
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return body;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
throw new Error(`[NullBridge] ${errors[reason] || msg}`);
|
|
44
|
+
throw new NullBridgeLicenseError(reason,
|
|
45
|
+
`${message} Contact brian@nullbridge.ai to renew or obtain a license key.`
|
|
46
|
+
);
|
|
47
47
|
|
|
48
48
|
} catch (err) {
|
|
49
|
-
if (err
|
|
50
|
-
|
|
51
|
-
// Network error —
|
|
52
|
-
console.warn(
|
|
53
|
-
return
|
|
49
|
+
if (err instanceof NullBridgeLicenseError) throw err;
|
|
50
|
+
|
|
51
|
+
// Network error — fail open with a warning
|
|
52
|
+
console.warn('[NullBridge] License server unreachable — continuing in offline mode.');
|
|
53
|
+
return {
|
|
54
|
+
valid: true,
|
|
55
|
+
reason: 'SERVER_UNREACHABLE',
|
|
56
|
+
offline: true,
|
|
57
|
+
customer: 'Unknown',
|
|
58
|
+
plan: 'unknown',
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
|
-
* Start periodic license
|
|
59
|
-
* @param {function}
|
|
64
|
+
* Start periodic license re-validation
|
|
65
|
+
* @param {function} onStatusChange - called with (valid, reason) when status changes
|
|
60
66
|
*/
|
|
61
|
-
startWatcher(
|
|
62
|
-
if (this.
|
|
63
|
-
|
|
64
|
-
this._watcher = setInterval(async () => {
|
|
67
|
+
startWatcher(onStatusChange) {
|
|
68
|
+
if (this._watcherTimer) return;
|
|
69
|
+
this._watcherTimer = setInterval(async () => {
|
|
65
70
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
'/api/validate',
|
|
69
|
-
{ key: this._config.licenseKey }
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
if (status !== 200 || !body.valid) {
|
|
73
|
-
if (body.reason !== 'GRACE_PERIOD') {
|
|
74
|
-
onInvalid(false, body.reason || 'UNKNOWN');
|
|
75
|
-
} else {
|
|
76
|
-
console.warn(`[NullBridge] License check: grace period — payment overdue.`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
71
|
+
const result = await this.validate();
|
|
72
|
+
if (!result.valid) onStatusChange(false, result.reason);
|
|
79
73
|
} catch (err) {
|
|
80
|
-
|
|
74
|
+
if (err instanceof NullBridgeLicenseError) {
|
|
75
|
+
onStatusChange(false, err.reason);
|
|
76
|
+
}
|
|
81
77
|
}
|
|
82
78
|
}, this._config.checkInterval);
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
if (this._watcher.unref) this._watcher.unref();
|
|
80
|
+
if (this._watcherTimer.unref) this._watcherTimer.unref();
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
stopWatcher() {
|
|
89
|
-
if (this.
|
|
90
|
-
clearInterval(this.
|
|
91
|
-
this.
|
|
84
|
+
if (this._watcherTimer) {
|
|
85
|
+
clearInterval(this._watcherTimer);
|
|
86
|
+
this._watcherTimer = null;
|
|
92
87
|
}
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
getData() {
|
|
96
|
-
return this._data;
|
|
97
|
-
}
|
|
98
89
|
}
|
|
99
90
|
|
|
100
91
|
module.exports = { LicenseClient };
|