@nullbridge/sdk 1.0.4 → 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/errors.js +36 -0
- package/src/index.js +37 -40
- package/src/license.js +51 -60
package/package.json
CHANGED
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 };
|