@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullbridge/sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "NullBridge AI Agent Identity Governance SDK",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
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 {boolean} [config.skipLicense] - Skip license check (for development only)
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 check interval in ms (default: 86400000 — 24 hours)
36
- * @param {object} [config.siem] - SIEM streaming config
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 Error(
44
- '[NullBridge] licenseKey is required. Set NULLBRIDGE_LICENSE_KEY in your environment variables.\n' +
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('NullBridge SDK v' + SDK_VERSION + ' initialized');
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 this once on application startup, before serving traffic.
60
+ * Call once on application startup before serving traffic.
78
61
  *
79
- * @returns {Promise<NullBridge>} — returns this for chaining
80
- * @throws {Error} if license is invalid or expired
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
- this._licenseData = await this.license.validate();
93
- this._log(`License valid customer: ${this._licenseData.customer}, plan: ${this._licenseData.plan}`);
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. Contact brian@nullbridge.ai.');
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('[NullBridge] Ready');
107
+ console.info(`[NullBridge] Ready — SDK v${SDK_VERSION}`);
117
108
  return this;
118
109
  }
119
110
 
120
111
  /**
121
- * Gracefully shut down NullBridge — flushes audit logs and stops watchers.
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 (customer, plan, max_agents, expires_at)
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 = { NullBridge, SDK_VERSION };
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 = config;
6
- this._http = http;
7
- this._watcher = null;
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 NullBridge license server.
13
- * Throws if license is invalid, expired, suspended, or cancelled.
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
- this._data = body;
25
- return body;
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 exists but is not valid
29
- const reason = body.reason || 'UNKNOWN';
30
- const msg = body.message || 'License validation failed.';
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
- if (reason === 'GRACE_PERIOD') {
41
- console.warn(`[NullBridge] Warning: ${msg}`);
42
- this._data = body;
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.message.startsWith('[NullBridge]')) throw err;
50
-
51
- // Network error — allow startup but warn
52
- console.warn(`[NullBridge] License server unreachable (${err.message}). Allowing startup will retry in 24 hours.`);
53
- return null;
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 checks.
59
- * @param {function} onInvalid - callback(valid, reason) when license becomes invalid
64
+ * Start periodic license re-validation
65
+ * @param {function} onStatusChange - called with (valid, reason) when status changes
60
66
  */
61
- startWatcher(onInvalid) {
62
- if (this._watcher) return;
63
-
64
- this._watcher = setInterval(async () => {
67
+ startWatcher(onStatusChange) {
68
+ if (this._watcherTimer) return;
69
+ this._watcherTimer = setInterval(async () => {
65
70
  try {
66
- const { status, body } = await this._http.post(
67
- this._config.serverUrl,
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
- console.warn(`[NullBridge] License check: server unreachable. Will retry in 24 hours.`);
74
+ if (err instanceof NullBridgeLicenseError) {
75
+ onStatusChange(false, err.reason);
76
+ }
81
77
  }
82
78
  }, this._config.checkInterval);
83
79
 
84
- // Don't let the watcher keep the process alive
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._watcher) {
90
- clearInterval(this._watcher);
91
- this._watcher = null;
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 };