@privacyscrubber/mcp-server 1.7.1 ā 1.7.6
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 +5 -5
- package/config-schema.json +10 -0
- package/index.js +293 -176
- package/install.js +63 -0
- package/package.json +9 -3
- package/pii-masking-run.js +2 -2
- package/ps-license-manager.js +263 -0
- package/ps-pii-engine.cjs +823 -0
- package/ps-pii-engine.js +823 -0
- package/scrubber-core.cjs +1419 -11
package/install.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
|
|
7
|
+
console.log('š”ļø PrivacyScrubber MCP Server 1-Click Installer');
|
|
8
|
+
console.log('================================================\n');
|
|
9
|
+
|
|
10
|
+
const mcpConfig = {
|
|
11
|
+
command: "npx",
|
|
12
|
+
args: ["-y", "@privacyscrubber/mcp-server"]
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let claudeConfigPath = '';
|
|
16
|
+
if (process.platform === 'darwin') {
|
|
17
|
+
claudeConfigPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
18
|
+
} else if (process.platform === 'win32') {
|
|
19
|
+
claudeConfigPath = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
20
|
+
} else {
|
|
21
|
+
// Linux fallback, though Claude Desktop is not officially supported on Linux yet, we check common paths
|
|
22
|
+
claudeConfigPath = path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 1. Configure Claude Desktop
|
|
26
|
+
let installedClaude = false;
|
|
27
|
+
try {
|
|
28
|
+
let configDir = path.dirname(claudeConfigPath);
|
|
29
|
+
if (!fs.existsSync(configDir)) {
|
|
30
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let config = {};
|
|
34
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
35
|
+
const rawData = fs.readFileSync(claudeConfigPath, 'utf8');
|
|
36
|
+
if (rawData.trim() !== '') {
|
|
37
|
+
config = JSON.parse(rawData);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!config.mcpServers) {
|
|
42
|
+
config.mcpServers = {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
config.mcpServers['privacyscrubber'] = mcpConfig;
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
48
|
+
console.log(`ā
[Claude Desktop] Successfully injected PrivacyScrubber MCP into:`);
|
|
49
|
+
console.log(` ${claudeConfigPath}`);
|
|
50
|
+
installedClaude = true;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.log(`ā ļø [Claude Desktop] Could not configure automatically: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('\n---');
|
|
56
|
+
console.log('\nšµ [Cursor / Windsurf Users]');
|
|
57
|
+
console.log('To install PrivacyScrubber in Cursor or Windsurf:');
|
|
58
|
+
console.log('1. Open Settings -> Features -> MCP Servers (or type "MCP" in settings).');
|
|
59
|
+
console.log('2. Click "+ Add New MCP Server".');
|
|
60
|
+
console.log('3. Set Name: privacyscrubber');
|
|
61
|
+
console.log('4. Set Type: command');
|
|
62
|
+
console.log('5. Set Command: npx -y @privacyscrubber/mcp-server');
|
|
63
|
+
console.log('\nš Installation complete! Restart your IDE or Claude Desktop to start redacting PII locally.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"mcpName": "io.github.moxno/privacyscrubber-mcp",
|
|
5
5
|
"description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -10,8 +10,13 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"index.js",
|
|
13
|
+
"install.js",
|
|
13
14
|
"pii-masking-run.js",
|
|
14
15
|
"scrubber-core.cjs",
|
|
16
|
+
"ps-license-manager.js",
|
|
17
|
+
"ps-pii-engine.cjs",
|
|
18
|
+
"ps-pii-engine.js",
|
|
19
|
+
"config-schema.json",
|
|
15
20
|
"smithery.yaml",
|
|
16
21
|
"server.json",
|
|
17
22
|
"manifest.json",
|
|
@@ -21,7 +26,8 @@
|
|
|
21
26
|
"bin": {
|
|
22
27
|
"mcp-server": "index.js",
|
|
23
28
|
"privacyscrubber-mcp": "index.js",
|
|
24
|
-
"pii-masking-run": "pii-masking-run.js"
|
|
29
|
+
"pii-masking-run": "pii-masking-run.js",
|
|
30
|
+
"privacyscrubber-mcp-install": "install.js"
|
|
25
31
|
},
|
|
26
32
|
"scripts": {
|
|
27
33
|
"start": "node index.js"
|
|
@@ -57,7 +63,7 @@
|
|
|
57
63
|
],
|
|
58
64
|
"author": "Ilya Sibiryakov (BrandMeWeb)",
|
|
59
65
|
"license": "MIT",
|
|
60
|
-
"homepage": "https://privacyscrubber.com/pii-mcp
|
|
66
|
+
"homepage": "https://privacyscrubber.com/pii-mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server",
|
|
61
67
|
"repository": {
|
|
62
68
|
"type": "git",
|
|
63
69
|
"url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
|
package/pii-masking-run.js
CHANGED
|
@@ -190,12 +190,12 @@ function performSanitization(text) {
|
|
|
190
190
|
}
|
|
191
191
|
if (isSecret) {
|
|
192
192
|
hasSecrets = true;
|
|
193
|
-
result.scrubbedText = result.scrubbedText.replace(token,
|
|
193
|
+
result.scrubbedText = result.scrubbedText.replace(token, '[SECRET_BLOCKED_UPGRADE_TO_PRO]');
|
|
194
194
|
}
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
if (hasSecrets) {
|
|
198
|
-
process.stderr.write(
|
|
198
|
+
process.stderr.write(`\x1b[1;31mš« [PrivacyScrubber] Secrets/Keys detected in output. Replaced with [SECRET_BLOCKED_UPGRADE_TO_PRO] to protect your data (Requires PRO tier).\x1b[0m\n`);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
(function(global) {
|
|
2
|
+
// āā START LICENSE CRYPTO āā
|
|
3
|
+
const PUBLIC_KEY_N = BigInt("21317979104050344678233452423803986868842680501941538057759536089609908024281729760556922188810565541201321990374637212776148857190553192389608364990754937991254608109727112734447704854882786946805547031142530865504430968628760311494129154950865158735816268612258108296781892583171421605668998979064782378729322077179022581884976242706169323540456286526420742434868559866211716347133681429395477256705746276962607312574959642065929462114149296066812527505837608212730874704915352119747022918247963886630350365119206627670591400728523813746184290857872958847087691236909900501703203494160206941648263514117706558791691");
|
|
4
|
+
const PUBLIC_KEY_E = 65537n;
|
|
5
|
+
|
|
6
|
+
function modPow(base, exp, mod) {
|
|
7
|
+
let res = 1n;
|
|
8
|
+
base = base % mod;
|
|
9
|
+
while (exp > 0n) {
|
|
10
|
+
if (exp % 2n === 1n) res = (res * base) % mod;
|
|
11
|
+
exp = exp / 2n;
|
|
12
|
+
base = (base * base) % mod;
|
|
13
|
+
}
|
|
14
|
+
return res;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function bigIntToString(bi) {
|
|
18
|
+
let hex = bi.toString(16);
|
|
19
|
+
if (hex.length % 2 !== 0) hex = '0' + hex;
|
|
20
|
+
let str = '';
|
|
21
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
22
|
+
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
|
23
|
+
}
|
|
24
|
+
return str;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stringToBigInt(str) {
|
|
28
|
+
let hex = '';
|
|
29
|
+
for (let i = 0; i < str.length; i++) {
|
|
30
|
+
hex += str.charCodeAt(i).toString(16).padStart(2, '0');
|
|
31
|
+
}
|
|
32
|
+
return BigInt('0x' + hex);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sha256(ascii) {
|
|
36
|
+
function rightRotate(value, amount) { return (value>>>amount) | (value<<(32 - amount)); }
|
|
37
|
+
var mathPow = Math.pow; var maxWord = mathPow(2, 32);
|
|
38
|
+
var lengthProperty = 'length'; var i, j;
|
|
39
|
+
var result = ''; var words = []; var asciiBitLength = ascii[lengthProperty]*8;
|
|
40
|
+
var hash = sha256.h = sha256.h || [];
|
|
41
|
+
var k = sha256.k = sha256.k || [];
|
|
42
|
+
var primeCounter = k[lengthProperty];
|
|
43
|
+
var isComposite = {};
|
|
44
|
+
for (var candidate = 2; primeCounter < 64; candidate++) {
|
|
45
|
+
if (!isComposite[candidate]) {
|
|
46
|
+
for (i = 0; i < 313; i += candidate) { isComposite[i] = candidate; }
|
|
47
|
+
hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0;
|
|
48
|
+
k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
ascii += '\x80';
|
|
52
|
+
while (ascii[lengthProperty]%64 - 56) ascii += '\x00';
|
|
53
|
+
for (i = 0; i < ascii[lengthProperty]; i++) {
|
|
54
|
+
j = ascii.charCodeAt(i);
|
|
55
|
+
if (j>>8) return;
|
|
56
|
+
words[i>>2] |= j << ((3 - i)%4)*8;
|
|
57
|
+
}
|
|
58
|
+
words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0);
|
|
59
|
+
words[words[lengthProperty]] = (asciiBitLength)
|
|
60
|
+
for (j = 0; j < words[lengthProperty];) {
|
|
61
|
+
var w = words.slice(j, j += 16);
|
|
62
|
+
var oldHash = hash;
|
|
63
|
+
hash = hash.slice(0, 8);
|
|
64
|
+
for (i = 0; i < 64; i++) {
|
|
65
|
+
var w15 = w[i - 15], w2 = w[i - 2];
|
|
66
|
+
var a = hash[0], e = hash[4];
|
|
67
|
+
var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e&hash[5])^((~e)&hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)))|0);
|
|
68
|
+
var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2]));
|
|
69
|
+
hash = [(temp1 + temp2)|0].concat(hash);
|
|
70
|
+
hash[4] = (hash[4] + temp1)|0;
|
|
71
|
+
}
|
|
72
|
+
for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i])|0; }
|
|
73
|
+
}
|
|
74
|
+
for (i = 0; i < 8; i++) {
|
|
75
|
+
for (j = 3; j + 1; j--) {
|
|
76
|
+
var b = (hash[i]>>(j*8))&255;
|
|
77
|
+
result += ((b < 16) ? 0 : '') + b.toString(16);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
// āā END LICENSE CRYPTO āā
|
|
83
|
+
|
|
84
|
+
class LicenseManager {
|
|
85
|
+
/**
|
|
86
|
+
* Validates a license key universally across all environments.
|
|
87
|
+
* @param {string} key - The license key.
|
|
88
|
+
* @param {object} options - Options mapping to environment specifics.
|
|
89
|
+
* @returns {object} { valid: boolean, tier: string|null, reason?: string, expires?: number }
|
|
90
|
+
*/
|
|
91
|
+
static validate(key, options = {}) {
|
|
92
|
+
if (!key) return { valid: false, tier: null, reason: "No key provided" };
|
|
93
|
+
const rawKey = key.trim();
|
|
94
|
+
const k = rawKey.toUpperCase();
|
|
95
|
+
const parts = k.split('-');
|
|
96
|
+
|
|
97
|
+
const now = options.mockTime !== undefined && options.mockTime !== null ? options.mockTime : Date.now();
|
|
98
|
+
let lastSeen = 0;
|
|
99
|
+
|
|
100
|
+
if (options.storage) {
|
|
101
|
+
lastSeen = parseInt(options.storage.getItem('ps_last_seen_time') || '0', 10);
|
|
102
|
+
if (options.mockTime === undefined || options.mockTime === null) {
|
|
103
|
+
options.storage.setItem('ps_last_seen_time', now.toString());
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Clock rollback guard
|
|
108
|
+
if ((options.mockTime === undefined || options.mockTime === null) && now < lastSeen) {
|
|
109
|
+
if (options.onAnomaly) options.onAnomaly();
|
|
110
|
+
return { valid: false, tier: null, reason: "Clock rollback anomaly detected" };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _atob = typeof atob !== 'undefined' ? atob : (str) => Buffer.from(str, 'base64').toString('binary');
|
|
114
|
+
|
|
115
|
+
// 1. Process Pilot Keys (RSA-1024 Encrypted Timestamps)
|
|
116
|
+
if (k.startsWith('PS-PILOT-')) {
|
|
117
|
+
try {
|
|
118
|
+
const payload = rawKey.substring(9);
|
|
119
|
+
const binary = _atob(payload);
|
|
120
|
+
const bytes = new Uint8Array(binary.length);
|
|
121
|
+
for (let i = 0; i < binary.length; i++) {
|
|
122
|
+
bytes[i] = binary.charCodeAt(i);
|
|
123
|
+
}
|
|
124
|
+
let hex = '';
|
|
125
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
126
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
127
|
+
}
|
|
128
|
+
const S = BigInt('0x' + hex);
|
|
129
|
+
|
|
130
|
+
const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
|
|
131
|
+
const decryptedStr = bigIntToString(M);
|
|
132
|
+
|
|
133
|
+
if (decryptedStr.startsWith('ZTDS-PILOT-')) {
|
|
134
|
+
const expDate = parseInt(decryptedStr.substring(11), 10);
|
|
135
|
+
if (!isNaN(expDate)) {
|
|
136
|
+
if (now < expDate) return { valid: true, tier: 'TEAMS', expires: expDate };
|
|
137
|
+
|
|
138
|
+
if (options.onExpired) options.onExpired('Pilot');
|
|
139
|
+
return { valid: false, tier: null, reason: "Pilot expired" };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch(e) {
|
|
143
|
+
return { valid: false, tier: null, reason: "Pilot decryption failed" };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 1.5. Process RSA Signed Keys (PS-RSA-...)
|
|
148
|
+
if (k.startsWith('PS-RSA-')) {
|
|
149
|
+
try {
|
|
150
|
+
const payload = rawKey.substring(7);
|
|
151
|
+
const binary = _atob(payload);
|
|
152
|
+
const bytes = new Uint8Array(binary.length);
|
|
153
|
+
for (let i = 0; i < binary.length; i++) {
|
|
154
|
+
bytes[i] = binary.charCodeAt(i);
|
|
155
|
+
}
|
|
156
|
+
let hex = '';
|
|
157
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
158
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
159
|
+
}
|
|
160
|
+
const S = BigInt('0x' + hex);
|
|
161
|
+
|
|
162
|
+
const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
|
|
163
|
+
const decryptedStr = bigIntToString(M);
|
|
164
|
+
|
|
165
|
+
if (decryptedStr.startsWith('ZTDS-LIC-')) {
|
|
166
|
+
const pieces = decryptedStr.split('-');
|
|
167
|
+
if (pieces.length >= 5) {
|
|
168
|
+
const tier = pieces[2];
|
|
169
|
+
const expires = parseInt(pieces[4], 10);
|
|
170
|
+
if (!isNaN(expires)) {
|
|
171
|
+
if (now < expires) return { valid: true, tier: tier, expires: expires };
|
|
172
|
+
|
|
173
|
+
if (options.onExpired) options.onExpired('RSA');
|
|
174
|
+
return { valid: false, tier: null, reason: "RSA key expired" };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch(e) {
|
|
179
|
+
return { valid: false, tier: null, reason: "RSA decryption failed" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Process Modern Prefixed Keys (v1.4.2+ Salted Checksum)
|
|
184
|
+
if (parts.length === 4 && parts[0] === 'PS' && (parts[1] === 'PRO' || parts[1] === 'TEAMS')) {
|
|
185
|
+
const tier = parts[1];
|
|
186
|
+
const core = parts[2];
|
|
187
|
+
const checksum = parts[3];
|
|
188
|
+
const salt = "ZTDS_SALT_2026_!@#";
|
|
189
|
+
const input = core + salt + tier;
|
|
190
|
+
let sum = 0;
|
|
191
|
+
for (let i = 0; i < input.length; i++) {
|
|
192
|
+
sum += input.charCodeAt(i) * (i + 1);
|
|
193
|
+
}
|
|
194
|
+
const expectedChecksum = (sum % 9999).toString().padStart(4, '0');
|
|
195
|
+
if (checksum === expectedChecksum) {
|
|
196
|
+
return { valid: true, tier: tier };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Algorithm B: Legacy unsalted checksum (pre-v1.4.2, migration only)
|
|
200
|
+
const MIGRATION_CUTOFF = 1759276800000; // 2026-10-01T00:00:00Z
|
|
201
|
+
if (now < MIGRATION_CUTOFF) {
|
|
202
|
+
let sumOld = 0;
|
|
203
|
+
for (let i = 0; i < core.length; i++) sumOld += core.charCodeAt(i);
|
|
204
|
+
if (checksum === (sumOld * 42 % 9999).toString().padStart(4, '0')) {
|
|
205
|
+
if (options.onLegacyUpgrade) {
|
|
206
|
+
options.onLegacyUpgrade(tier, k);
|
|
207
|
+
}
|
|
208
|
+
return { valid: true, tier: tier };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 3. Process Standard RS256 JWTs (eyJ...)
|
|
214
|
+
if (rawKey.startsWith('eyJ') && rawKey.split('.').length === 3) {
|
|
215
|
+
try {
|
|
216
|
+
const [b64Header, b64Payload, b64Signature] = rawKey.split('.');
|
|
217
|
+
|
|
218
|
+
let sigStr = b64Signature.replace(/-/g, '+').replace(/_/g, '/');
|
|
219
|
+
while (sigStr.length % 4) sigStr += '=';
|
|
220
|
+
const binarySig = _atob(sigStr);
|
|
221
|
+
let hexSig = '';
|
|
222
|
+
for (let i = 0; i < binarySig.length; i++) {
|
|
223
|
+
hexSig += binarySig.charCodeAt(i).toString(16).padStart(2, '0');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const S = BigInt('0x' + hexSig);
|
|
227
|
+
const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
|
|
228
|
+
|
|
229
|
+
let mHex = M.toString(16);
|
|
230
|
+
if (mHex.length % 2 !== 0) mHex = '0' + mHex;
|
|
231
|
+
|
|
232
|
+
const expectedHash = sha256(b64Header + '.' + b64Payload);
|
|
233
|
+
|
|
234
|
+
if (mHex.endsWith(expectedHash)) {
|
|
235
|
+
const payloadJson = _atob(b64Payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
236
|
+
const payload = JSON.parse(payloadJson);
|
|
237
|
+
|
|
238
|
+
if (payload.exp && (now / 1000) < payload.exp) {
|
|
239
|
+
return { valid: true, tier: payload.tier || 'PRO', expires: payload.exp * 1000 };
|
|
240
|
+
} else {
|
|
241
|
+
if (options.onExpired) options.onExpired('JWT');
|
|
242
|
+
return { valid: false, tier: null, reason: "JWT expired" };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch(e) {
|
|
246
|
+
return { valid: false, tier: null, reason: "JWT validation failed" };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { valid: false, tier: null, reason: "Invalid format" };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Export module for ES modules, CommonJS, and Browser Global
|
|
255
|
+
if (typeof exports !== 'undefined') {
|
|
256
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
257
|
+
module.exports = LicenseManager;
|
|
258
|
+
}
|
|
259
|
+
exports.LicenseManager = LicenseManager;
|
|
260
|
+
} else {
|
|
261
|
+
global.LicenseManager = LicenseManager;
|
|
262
|
+
}
|
|
263
|
+
})(typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : this));
|