@nice2dev/licensing 1.0.10
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 +123 -0
- package/dist/FeatureGate-Cs1zphXs.cjs +1405 -0
- package/dist/FeatureGate-Cs1zphXs.cjs.map +1 -0
- package/dist/FeatureGate-Da7fCJA5.js +1406 -0
- package/dist/FeatureGate-Da7fCJA5.js.map +1 -0
- package/dist/__vite-browser-external-2Ng8QIWW.js +5 -0
- package/dist/__vite-browser-external-2Ng8QIWW.js.map +1 -0
- package/dist/__vite-browser-external-DES75WN9.cjs +5 -0
- package/dist/__vite-browser-external-DES75WN9.cjs.map +1 -0
- package/dist/index.cjs +4352 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2285 -0
- package/dist/index.mjs +4353 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.cjs +1039 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.ts +327 -0
- package/dist/react.mjs +1039 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const SEGMENT_LENGTH = 4;
|
|
3
|
+
const NUM_SEGMENTS = 5;
|
|
4
|
+
const KEY_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
5
|
+
const TIER_CODES = {
|
|
6
|
+
trial: 1,
|
|
7
|
+
personal: 2,
|
|
8
|
+
team: 3,
|
|
9
|
+
enterprise: 4,
|
|
10
|
+
site: 5,
|
|
11
|
+
oem: 6
|
|
12
|
+
};
|
|
13
|
+
const CODE_TO_TIER = Object.entries(TIER_CODES).reduce(
|
|
14
|
+
(acc, [tier, code]) => ({ ...acc, [code]: tier }),
|
|
15
|
+
{}
|
|
16
|
+
);
|
|
17
|
+
function secureRandom(length) {
|
|
18
|
+
const array = new Uint8Array(length);
|
|
19
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
20
|
+
crypto.getRandomValues(array);
|
|
21
|
+
} else {
|
|
22
|
+
for (let i = 0; i < length; i++) {
|
|
23
|
+
array[i] = Math.floor(Math.random() * 256);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return Array.from(array).map((b) => KEY_CHARS[b % KEY_CHARS.length]).join("");
|
|
27
|
+
}
|
|
28
|
+
function calculateChecksum(data) {
|
|
29
|
+
let hash = 0;
|
|
30
|
+
for (let i = 0; i < data.length; i++) {
|
|
31
|
+
const char = data.charCodeAt(i);
|
|
32
|
+
hash = (hash << 5) - hash + char | 0;
|
|
33
|
+
}
|
|
34
|
+
const positive = Math.abs(hash);
|
|
35
|
+
let checksum = "";
|
|
36
|
+
let remaining = positive;
|
|
37
|
+
for (let i = 0; i < SEGMENT_LENGTH; i++) {
|
|
38
|
+
checksum += KEY_CHARS[remaining % KEY_CHARS.length];
|
|
39
|
+
remaining = Math.floor(remaining / KEY_CHARS.length);
|
|
40
|
+
}
|
|
41
|
+
return checksum;
|
|
42
|
+
}
|
|
43
|
+
function encodeMetadata(tier, year, month) {
|
|
44
|
+
const tierCode = TIER_CODES[tier];
|
|
45
|
+
const yearOffset = year - 2024;
|
|
46
|
+
const encoded = tierCode * 1e3 + yearOffset * 12 + month;
|
|
47
|
+
let segment = "";
|
|
48
|
+
let remaining = encoded;
|
|
49
|
+
for (let i = 0; i < SEGMENT_LENGTH; i++) {
|
|
50
|
+
segment += KEY_CHARS[remaining % KEY_CHARS.length];
|
|
51
|
+
remaining = Math.floor(remaining / KEY_CHARS.length);
|
|
52
|
+
}
|
|
53
|
+
return segment;
|
|
54
|
+
}
|
|
55
|
+
function decodeMetadata(segment) {
|
|
56
|
+
let decoded = 0;
|
|
57
|
+
for (let i = segment.length - 1; i >= 0; i--) {
|
|
58
|
+
const idx = KEY_CHARS.indexOf(segment[i]);
|
|
59
|
+
if (idx === -1) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
decoded = decoded * KEY_CHARS.length + idx;
|
|
63
|
+
}
|
|
64
|
+
const tierCode = Math.floor(decoded / 1e3);
|
|
65
|
+
const remainder = decoded % 1e3;
|
|
66
|
+
const yearOffset = Math.floor(remainder / 12);
|
|
67
|
+
const month = remainder % 12;
|
|
68
|
+
const tier = CODE_TO_TIER[tierCode];
|
|
69
|
+
if (!tier) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
tier,
|
|
74
|
+
year: 2024 + yearOffset,
|
|
75
|
+
month
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function generateLicenseKey(tier = "personal") {
|
|
79
|
+
const now = /* @__PURE__ */ new Date();
|
|
80
|
+
const metadataSegment = encodeMetadata(tier, now.getFullYear(), now.getMonth());
|
|
81
|
+
const randomSegments = [
|
|
82
|
+
secureRandom(SEGMENT_LENGTH),
|
|
83
|
+
secureRandom(SEGMENT_LENGTH),
|
|
84
|
+
secureRandom(SEGMENT_LENGTH)
|
|
85
|
+
];
|
|
86
|
+
const dataWithoutChecksum = [metadataSegment, ...randomSegments].join("-");
|
|
87
|
+
const checksumSegment = calculateChecksum(dataWithoutChecksum);
|
|
88
|
+
return `${dataWithoutChecksum}-${checksumSegment}`;
|
|
89
|
+
}
|
|
90
|
+
function validateKeyFormat(key) {
|
|
91
|
+
const pattern = new RegExp(
|
|
92
|
+
`^[${KEY_CHARS}]{${SEGMENT_LENGTH}}(-[${KEY_CHARS}]{${SEGMENT_LENGTH}}){${NUM_SEGMENTS - 1}}$`
|
|
93
|
+
);
|
|
94
|
+
if (!pattern.test(key)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const segments = key.split("-");
|
|
98
|
+
const dataSegments = segments.slice(0, -1).join("-");
|
|
99
|
+
const providedChecksum = segments[segments.length - 1];
|
|
100
|
+
const calculatedChecksum = calculateChecksum(dataSegments);
|
|
101
|
+
return providedChecksum === calculatedChecksum;
|
|
102
|
+
}
|
|
103
|
+
function extractKeyMetadata(key) {
|
|
104
|
+
if (!validateKeyFormat(key)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const segments = key.split("-");
|
|
108
|
+
return decodeMetadata(segments[0]);
|
|
109
|
+
}
|
|
110
|
+
function generateLicenseInfo(options) {
|
|
111
|
+
const key = generateLicenseKey(options.tier);
|
|
112
|
+
const now = /* @__PURE__ */ new Date();
|
|
113
|
+
const issuedAt = now.toISOString();
|
|
114
|
+
let expiresAt = null;
|
|
115
|
+
let graceUntil;
|
|
116
|
+
if (options.durationDays !== null) {
|
|
117
|
+
const expiry = new Date(now);
|
|
118
|
+
expiry.setDate(expiry.getDate() + options.durationDays);
|
|
119
|
+
expiresAt = expiry.toISOString();
|
|
120
|
+
if (options.graceDays) {
|
|
121
|
+
const grace = new Date(expiry);
|
|
122
|
+
grace.setDate(grace.getDate() + options.graceDays);
|
|
123
|
+
graceUntil = grace.toISOString();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
key,
|
|
128
|
+
tier: options.tier,
|
|
129
|
+
status: "valid",
|
|
130
|
+
licensee: options.licensee,
|
|
131
|
+
email: options.email,
|
|
132
|
+
issuedAt,
|
|
133
|
+
expiresAt,
|
|
134
|
+
graceUntil,
|
|
135
|
+
features: options.features ?? getDefaultFeatures(options.tier),
|
|
136
|
+
maxSeats: options.maxSeats ?? getDefaultSeats(options.tier),
|
|
137
|
+
activeSeats: 0,
|
|
138
|
+
boundMachines: [],
|
|
139
|
+
maxMachines: options.maxMachines ?? getDefaultMachines(options.tier),
|
|
140
|
+
floatingSeats: options.floatingSeats,
|
|
141
|
+
metadata: options.metadata
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function getDefaultFeatures(tier) {
|
|
145
|
+
const features = ["core", "basic-components"];
|
|
146
|
+
if (tier === "trial") {
|
|
147
|
+
features.push("trial-watermark");
|
|
148
|
+
}
|
|
149
|
+
if (["personal", "team", "enterprise", "site", "oem"].includes(tier)) {
|
|
150
|
+
features.push("advanced-components", "theming", "i18n");
|
|
151
|
+
}
|
|
152
|
+
if (["team", "enterprise", "site", "oem"].includes(tier)) {
|
|
153
|
+
features.push("collaboration", "analytics", "priority-support");
|
|
154
|
+
}
|
|
155
|
+
if (["enterprise", "site", "oem"].includes(tier)) {
|
|
156
|
+
features.push("white-label", "sso", "audit-log", "custom-branding", "api-access");
|
|
157
|
+
}
|
|
158
|
+
if (["site", "oem"].includes(tier)) {
|
|
159
|
+
features.push("unlimited-seats", "source-code-access", "dedicated-support");
|
|
160
|
+
}
|
|
161
|
+
if (tier === "oem") {
|
|
162
|
+
features.push("redistribution", "custom-licensing");
|
|
163
|
+
}
|
|
164
|
+
return features;
|
|
165
|
+
}
|
|
166
|
+
function getDefaultSeats(tier) {
|
|
167
|
+
switch (tier) {
|
|
168
|
+
case "trial":
|
|
169
|
+
return 1;
|
|
170
|
+
case "personal":
|
|
171
|
+
return 1;
|
|
172
|
+
case "team":
|
|
173
|
+
return 10;
|
|
174
|
+
case "enterprise":
|
|
175
|
+
return 50;
|
|
176
|
+
case "site":
|
|
177
|
+
return null;
|
|
178
|
+
// unlimited
|
|
179
|
+
case "oem":
|
|
180
|
+
return null;
|
|
181
|
+
default:
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function getDefaultMachines(tier) {
|
|
186
|
+
switch (tier) {
|
|
187
|
+
case "trial":
|
|
188
|
+
return 1;
|
|
189
|
+
case "personal":
|
|
190
|
+
return 2;
|
|
191
|
+
case "team":
|
|
192
|
+
return 20;
|
|
193
|
+
case "enterprise":
|
|
194
|
+
return 100;
|
|
195
|
+
case "site":
|
|
196
|
+
return 1e3;
|
|
197
|
+
case "oem":
|
|
198
|
+
return 1e4;
|
|
199
|
+
default:
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function compareTiers(a, b) {
|
|
204
|
+
return TIER_CODES[a] - TIER_CODES[b];
|
|
205
|
+
}
|
|
206
|
+
function tierMeetsRequirement(userTier, requiredTier) {
|
|
207
|
+
return compareTiers(userTier, requiredTier) >= 0;
|
|
208
|
+
}
|
|
209
|
+
function maskLicenseKey(key) {
|
|
210
|
+
const segments = key.split("-");
|
|
211
|
+
if (segments.length !== NUM_SEGMENTS) {
|
|
212
|
+
return "****-****-****-****-****";
|
|
213
|
+
}
|
|
214
|
+
return `${segments[0]}-****-****-****-${segments[segments.length - 1]}`;
|
|
215
|
+
}
|
|
216
|
+
function normalizeLicenseKey(input) {
|
|
217
|
+
const cleaned = input.replace(/\s/g, "").toUpperCase();
|
|
218
|
+
if (cleaned.includes("-")) {
|
|
219
|
+
return validateKeyFormat(cleaned) ? cleaned : null;
|
|
220
|
+
}
|
|
221
|
+
if (cleaned.length === SEGMENT_LENGTH * NUM_SEGMENTS) {
|
|
222
|
+
const segments = [];
|
|
223
|
+
for (let i = 0; i < NUM_SEGMENTS; i++) {
|
|
224
|
+
segments.push(cleaned.substring(i * SEGMENT_LENGTH, (i + 1) * SEGMENT_LENGTH));
|
|
225
|
+
}
|
|
226
|
+
const formatted = segments.join("-");
|
|
227
|
+
return validateKeyFormat(formatted) ? formatted : null;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
function fnv1aHash(str) {
|
|
232
|
+
let hash = 2166136261;
|
|
233
|
+
for (let i = 0; i < str.length; i++) {
|
|
234
|
+
hash ^= str.charCodeAt(i);
|
|
235
|
+
hash = Math.imul(hash, 16777619);
|
|
236
|
+
}
|
|
237
|
+
return hash >>> 0;
|
|
238
|
+
}
|
|
239
|
+
function hashToHex(hash) {
|
|
240
|
+
return hash.toString(16).padStart(8, "0");
|
|
241
|
+
}
|
|
242
|
+
function strongHash(input) {
|
|
243
|
+
const h1 = fnv1aHash(input);
|
|
244
|
+
const h2 = fnv1aHash(input + h1.toString());
|
|
245
|
+
const h3 = fnv1aHash(h2.toString() + input);
|
|
246
|
+
return hashToHex(h1) + hashToHex(h2) + hashToHex(h3);
|
|
247
|
+
}
|
|
248
|
+
function getWebGLFingerprint() {
|
|
249
|
+
if (typeof document === "undefined") {
|
|
250
|
+
return void 0;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const canvas = document.createElement("canvas");
|
|
254
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
255
|
+
if (!gl) {
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
const glAny = gl;
|
|
259
|
+
const debugInfo = glAny.getExtension("WEBGL_debug_renderer_info");
|
|
260
|
+
if (!debugInfo) {
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
const vendor = glAny.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
|
|
264
|
+
const renderer = glAny.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
265
|
+
return strongHash(`${vendor}|${renderer}`);
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getCanvasFingerprint() {
|
|
271
|
+
if (typeof document === "undefined") {
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const canvas = document.createElement("canvas");
|
|
276
|
+
canvas.width = 200;
|
|
277
|
+
canvas.height = 50;
|
|
278
|
+
const ctx = canvas.getContext("2d");
|
|
279
|
+
if (!ctx) {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
ctx.textBaseline = "top";
|
|
283
|
+
ctx.font = "14px Arial";
|
|
284
|
+
ctx.fillStyle = "#f60";
|
|
285
|
+
ctx.fillRect(125, 1, 62, 20);
|
|
286
|
+
ctx.fillStyle = "#069";
|
|
287
|
+
ctx.fillText("Nice2Dev Licensing", 2, 15);
|
|
288
|
+
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
|
|
289
|
+
ctx.fillText("Nice2Dev Licensing", 4, 17);
|
|
290
|
+
return strongHash(canvas.toDataURL());
|
|
291
|
+
} catch {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function getAudioFingerprint() {
|
|
296
|
+
if (typeof window === "undefined" || !window.AudioContext) {
|
|
297
|
+
return Promise.resolve(void 0);
|
|
298
|
+
}
|
|
299
|
+
return new Promise((resolve) => {
|
|
300
|
+
try {
|
|
301
|
+
const audioContext = new AudioContext();
|
|
302
|
+
const oscillator = audioContext.createOscillator();
|
|
303
|
+
const analyser = audioContext.createAnalyser();
|
|
304
|
+
const gainNode = audioContext.createGain();
|
|
305
|
+
const scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
306
|
+
gainNode.gain.value = 0;
|
|
307
|
+
oscillator.type = "triangle";
|
|
308
|
+
oscillator.frequency.value = 1e4;
|
|
309
|
+
oscillator.connect(analyser);
|
|
310
|
+
analyser.connect(scriptProcessor);
|
|
311
|
+
scriptProcessor.connect(gainNode);
|
|
312
|
+
gainNode.connect(audioContext.destination);
|
|
313
|
+
let fingerprint = "";
|
|
314
|
+
scriptProcessor.onaudioprocess = (e) => {
|
|
315
|
+
const buffer = e.inputBuffer.getChannelData(0);
|
|
316
|
+
fingerprint = strongHash(buffer.slice(0, 100).join(","));
|
|
317
|
+
scriptProcessor.disconnect();
|
|
318
|
+
oscillator.disconnect();
|
|
319
|
+
analyser.disconnect();
|
|
320
|
+
gainNode.disconnect();
|
|
321
|
+
audioContext.close();
|
|
322
|
+
resolve(fingerprint);
|
|
323
|
+
};
|
|
324
|
+
oscillator.start(0);
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
if (!fingerprint) {
|
|
327
|
+
resolve(void 0);
|
|
328
|
+
}
|
|
329
|
+
}, 1e3);
|
|
330
|
+
} catch {
|
|
331
|
+
resolve(void 0);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function getUserAgentHash() {
|
|
336
|
+
if (typeof navigator === "undefined") {
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
return strongHash(navigator.userAgent);
|
|
340
|
+
}
|
|
341
|
+
function getTimezoneInfo() {
|
|
342
|
+
try {
|
|
343
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
344
|
+
} catch {
|
|
345
|
+
return "unknown";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function getScreenInfo() {
|
|
349
|
+
if (typeof screen === "undefined") {
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
return `${screen.width}x${screen.height}x${screen.colorDepth}`;
|
|
353
|
+
}
|
|
354
|
+
function getLanguageInfo() {
|
|
355
|
+
if (typeof navigator === "undefined") {
|
|
356
|
+
return void 0;
|
|
357
|
+
}
|
|
358
|
+
return navigator.language || navigator.userLanguage || "unknown";
|
|
359
|
+
}
|
|
360
|
+
function getPlatformInfo() {
|
|
361
|
+
if (typeof navigator === "undefined") {
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
return navigator.platform;
|
|
365
|
+
}
|
|
366
|
+
function getHardwareConcurrency() {
|
|
367
|
+
if (typeof navigator === "undefined") {
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
return navigator.hardwareConcurrency;
|
|
371
|
+
}
|
|
372
|
+
function getDeviceMemory() {
|
|
373
|
+
if (typeof navigator === "undefined") {
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
376
|
+
return navigator.deviceMemory;
|
|
377
|
+
}
|
|
378
|
+
async function collectFingerprintData() {
|
|
379
|
+
var _a, _b;
|
|
380
|
+
const audioFingerprint = await getAudioFingerprint();
|
|
381
|
+
return {
|
|
382
|
+
webglHash: getWebGLFingerprint(),
|
|
383
|
+
canvasFingerprint: getCanvasFingerprint(),
|
|
384
|
+
audioFingerprint,
|
|
385
|
+
userAgentHash: getUserAgentHash(),
|
|
386
|
+
browserFingerprint: strongHash(
|
|
387
|
+
[
|
|
388
|
+
getTimezoneInfo(),
|
|
389
|
+
getScreenInfo(),
|
|
390
|
+
getLanguageInfo(),
|
|
391
|
+
getPlatformInfo(),
|
|
392
|
+
(_a = getHardwareConcurrency()) == null ? void 0 : _a.toString(),
|
|
393
|
+
(_b = getDeviceMemory()) == null ? void 0 : _b.toString()
|
|
394
|
+
].filter(Boolean).join("|")
|
|
395
|
+
)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async function generateFingerprint() {
|
|
399
|
+
const data = await collectFingerprintData();
|
|
400
|
+
const components = [
|
|
401
|
+
data.webglHash,
|
|
402
|
+
data.canvasFingerprint,
|
|
403
|
+
data.audioFingerprint,
|
|
404
|
+
data.userAgentHash,
|
|
405
|
+
data.browserFingerprint
|
|
406
|
+
].filter(Boolean);
|
|
407
|
+
if (components.length === 0) {
|
|
408
|
+
const fallback = Date.now().toString() + Math.random().toString(36);
|
|
409
|
+
return strongHash(fallback);
|
|
410
|
+
}
|
|
411
|
+
return strongHash(components.join(":"));
|
|
412
|
+
}
|
|
413
|
+
async function generateMachineId() {
|
|
414
|
+
const fingerprint = await generateFingerprint();
|
|
415
|
+
return fingerprint.substring(0, 16).toUpperCase();
|
|
416
|
+
}
|
|
417
|
+
function compareFingerprintsData(fp1, fp2) {
|
|
418
|
+
const fields = [
|
|
419
|
+
"webglHash",
|
|
420
|
+
"canvasFingerprint",
|
|
421
|
+
"audioFingerprint",
|
|
422
|
+
"userAgentHash",
|
|
423
|
+
"browserFingerprint"
|
|
424
|
+
];
|
|
425
|
+
let matches = 0;
|
|
426
|
+
let total = 0;
|
|
427
|
+
for (const field of fields) {
|
|
428
|
+
const v1 = fp1[field];
|
|
429
|
+
const v2 = fp2[field];
|
|
430
|
+
if (v1 !== void 0 || v2 !== void 0) {
|
|
431
|
+
total++;
|
|
432
|
+
if (v1 === v2) {
|
|
433
|
+
matches++;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return total > 0 ? matches / total : 0;
|
|
438
|
+
}
|
|
439
|
+
function storeFingerprint(fingerprint) {
|
|
440
|
+
if (typeof localStorage === "undefined") {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
localStorage.setItem("nice2dev_fp", fingerprint);
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function getStoredFingerprint() {
|
|
449
|
+
if (typeof localStorage === "undefined") {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
return localStorage.getItem("nice2dev_fp");
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async function getOrGenerateFingerprint() {
|
|
459
|
+
const stored = getStoredFingerprint();
|
|
460
|
+
if (stored) {
|
|
461
|
+
return stored;
|
|
462
|
+
}
|
|
463
|
+
const fingerprint = await generateFingerprint();
|
|
464
|
+
storeFingerprint(fingerprint);
|
|
465
|
+
return fingerprint;
|
|
466
|
+
}
|
|
467
|
+
const DEFAULT_CONFIG = {
|
|
468
|
+
serverUrl: "https://license.nice2dev.com/api/v1",
|
|
469
|
+
timeout: 1e4,
|
|
470
|
+
retries: 3,
|
|
471
|
+
cacheTtl: 3600,
|
|
472
|
+
// 1 hour
|
|
473
|
+
offlineMode: false,
|
|
474
|
+
telemetryEnabled: false
|
|
475
|
+
};
|
|
476
|
+
const CACHE_PREFIX = "nice2dev_license_";
|
|
477
|
+
class LicenseValidator {
|
|
478
|
+
constructor(config = {}) {
|
|
479
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
480
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
481
|
+
this.loadCacheFromStorage();
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Validate a license key
|
|
485
|
+
*/
|
|
486
|
+
async validate(key, fingerprint) {
|
|
487
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
488
|
+
if (!validateKeyFormat(key)) {
|
|
489
|
+
return {
|
|
490
|
+
valid: false,
|
|
491
|
+
errorCode: "INVALID_KEY",
|
|
492
|
+
errorMessage: "Invalid license key format",
|
|
493
|
+
validatedAt: now
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const cached = this.getFromCache(key);
|
|
497
|
+
if (cached) {
|
|
498
|
+
return cached;
|
|
499
|
+
}
|
|
500
|
+
const machineFingerprint = fingerprint || await generateMachineId();
|
|
501
|
+
if (!this.config.offlineMode) {
|
|
502
|
+
try {
|
|
503
|
+
const result = await this.validateOnline(key, machineFingerprint);
|
|
504
|
+
this.setCache(key, result);
|
|
505
|
+
return result;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.warn("Online validation failed, trying offline:", error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return this.validateOffline(key, machineFingerprint);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Online license validation
|
|
514
|
+
*/
|
|
515
|
+
async validateOnline(key, fingerprint) {
|
|
516
|
+
const url = `${this.config.serverUrl}/validate`;
|
|
517
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
518
|
+
let lastError = null;
|
|
519
|
+
for (let attempt = 0; attempt < (this.config.retries || 1); attempt++) {
|
|
520
|
+
try {
|
|
521
|
+
const controller = new AbortController();
|
|
522
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
523
|
+
const response = await fetch(url, {
|
|
524
|
+
method: "POST",
|
|
525
|
+
headers: {
|
|
526
|
+
"Content-Type": "application/json",
|
|
527
|
+
...this.config.apiKey ? { "X-API-Key": this.config.apiKey } : {}
|
|
528
|
+
},
|
|
529
|
+
body: JSON.stringify({
|
|
530
|
+
licenseKey: key,
|
|
531
|
+
machineFingerprint: fingerprint,
|
|
532
|
+
timestamp: now
|
|
533
|
+
}),
|
|
534
|
+
signal: controller.signal
|
|
535
|
+
});
|
|
536
|
+
clearTimeout(timeoutId);
|
|
537
|
+
if (!response.ok) {
|
|
538
|
+
if (response.status === 404) {
|
|
539
|
+
return {
|
|
540
|
+
valid: false,
|
|
541
|
+
errorCode: "INVALID_KEY",
|
|
542
|
+
errorMessage: "License key not found",
|
|
543
|
+
validatedAt: now
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
throw new Error(`Server error: ${response.status}`);
|
|
547
|
+
}
|
|
548
|
+
const data = await response.json();
|
|
549
|
+
return this.parseServerResponse(data, now);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
lastError = error;
|
|
552
|
+
if (attempt < (this.config.retries || 1) - 1) {
|
|
553
|
+
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1e3));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
throw lastError || new Error("Validation failed");
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Parse server validation response
|
|
561
|
+
*/
|
|
562
|
+
parseServerResponse(data, validatedAt) {
|
|
563
|
+
if (data.valid === false) {
|
|
564
|
+
return {
|
|
565
|
+
valid: false,
|
|
566
|
+
errorCode: data.errorCode,
|
|
567
|
+
errorMessage: data.errorMessage,
|
|
568
|
+
validatedAt
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const license = data.license;
|
|
572
|
+
const now = /* @__PURE__ */ new Date();
|
|
573
|
+
let daysRemaining;
|
|
574
|
+
let inGracePeriod = false;
|
|
575
|
+
if (license.expiresAt) {
|
|
576
|
+
const expiry = new Date(license.expiresAt);
|
|
577
|
+
const diffMs = expiry.getTime() - now.getTime();
|
|
578
|
+
daysRemaining = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
579
|
+
if (daysRemaining < 0 && license.graceUntil) {
|
|
580
|
+
const graceEnd = new Date(license.graceUntil);
|
|
581
|
+
if (now < graceEnd) {
|
|
582
|
+
inGracePeriod = true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
valid: true,
|
|
588
|
+
license,
|
|
589
|
+
daysRemaining,
|
|
590
|
+
inGracePeriod,
|
|
591
|
+
availableFeatures: license.features,
|
|
592
|
+
validatedAt
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Offline license validation
|
|
597
|
+
*/
|
|
598
|
+
validateOffline(key, fingerprint) {
|
|
599
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
600
|
+
const metadata = extractKeyMetadata(key);
|
|
601
|
+
if (!metadata) {
|
|
602
|
+
return {
|
|
603
|
+
valid: false,
|
|
604
|
+
errorCode: "INVALID_KEY",
|
|
605
|
+
errorMessage: "Cannot decode license key",
|
|
606
|
+
validatedAt: now
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const storedLicense = this.getStoredLicense(key);
|
|
610
|
+
if (storedLicense) {
|
|
611
|
+
if (!storedLicense.boundMachines.some((m) => m.startsWith(fingerprint.substring(0, 8)))) {
|
|
612
|
+
return {
|
|
613
|
+
valid: false,
|
|
614
|
+
errorCode: "MACHINE_LIMIT",
|
|
615
|
+
errorMessage: "This machine is not authorized for this license",
|
|
616
|
+
validatedAt: now
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (storedLicense.expiresAt) {
|
|
620
|
+
const expiry = new Date(storedLicense.expiresAt);
|
|
621
|
+
const nowDate = /* @__PURE__ */ new Date();
|
|
622
|
+
if (nowDate > expiry) {
|
|
623
|
+
if (storedLicense.graceUntil) {
|
|
624
|
+
const graceEnd = new Date(storedLicense.graceUntil);
|
|
625
|
+
if (nowDate > graceEnd) {
|
|
626
|
+
return {
|
|
627
|
+
valid: false,
|
|
628
|
+
errorCode: "EXPIRED",
|
|
629
|
+
errorMessage: "License has expired",
|
|
630
|
+
validatedAt: now
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
valid: true,
|
|
635
|
+
license: storedLicense,
|
|
636
|
+
inGracePeriod: true,
|
|
637
|
+
daysRemaining: 0,
|
|
638
|
+
availableFeatures: storedLicense.features,
|
|
639
|
+
validatedAt: now
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
valid: false,
|
|
644
|
+
errorCode: "EXPIRED",
|
|
645
|
+
errorMessage: "License has expired",
|
|
646
|
+
validatedAt: now
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const diffMs = expiry.getTime() - nowDate.getTime();
|
|
650
|
+
const daysRemaining = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
651
|
+
return {
|
|
652
|
+
valid: true,
|
|
653
|
+
license: storedLicense,
|
|
654
|
+
daysRemaining,
|
|
655
|
+
inGracePeriod: false,
|
|
656
|
+
availableFeatures: storedLicense.features,
|
|
657
|
+
validatedAt: now
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
valid: true,
|
|
662
|
+
license: storedLicense,
|
|
663
|
+
availableFeatures: storedLicense.features,
|
|
664
|
+
validatedAt: now
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
valid: true,
|
|
669
|
+
// Trust the key format
|
|
670
|
+
license: {
|
|
671
|
+
key,
|
|
672
|
+
tier: metadata.tier,
|
|
673
|
+
status: "pending",
|
|
674
|
+
// Needs online verification
|
|
675
|
+
licensee: "Unknown (offline)",
|
|
676
|
+
email: "",
|
|
677
|
+
issuedAt: `${metadata.year}-${String(metadata.month + 1).padStart(2, "0")}-01T00:00:00Z`,
|
|
678
|
+
expiresAt: null,
|
|
679
|
+
features: [],
|
|
680
|
+
// Limited features offline
|
|
681
|
+
maxSeats: null,
|
|
682
|
+
activeSeats: 0,
|
|
683
|
+
boundMachines: [fingerprint],
|
|
684
|
+
maxMachines: 1
|
|
685
|
+
},
|
|
686
|
+
availableFeatures: ["core"],
|
|
687
|
+
// Only core features offline
|
|
688
|
+
validatedAt: now
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Generate offline activation challenge
|
|
693
|
+
*/
|
|
694
|
+
async generateChallenge(key) {
|
|
695
|
+
const fingerprint = await generateMachineId();
|
|
696
|
+
const challengeId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
697
|
+
const challengeData = JSON.stringify({
|
|
698
|
+
key,
|
|
699
|
+
fingerprint,
|
|
700
|
+
timestamp: Date.now()
|
|
701
|
+
});
|
|
702
|
+
const challengeCode = typeof btoa !== "undefined" ? btoa(challengeData) : Buffer.from(challengeData).toString("base64");
|
|
703
|
+
const expiresAt = /* @__PURE__ */ new Date();
|
|
704
|
+
expiresAt.setHours(expiresAt.getHours() + 24);
|
|
705
|
+
return {
|
|
706
|
+
challengeId,
|
|
707
|
+
challengeCode,
|
|
708
|
+
fingerprint,
|
|
709
|
+
expiresAt: expiresAt.toISOString()
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Apply offline activation response
|
|
714
|
+
*/
|
|
715
|
+
applyOfflineResponse(response) {
|
|
716
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
717
|
+
try {
|
|
718
|
+
const licenseDataJson = typeof atob !== "undefined" ? atob(response.licenseData) : Buffer.from(response.licenseData, "base64").toString("utf-8");
|
|
719
|
+
const license = JSON.parse(licenseDataJson);
|
|
720
|
+
this.storeLicense(license);
|
|
721
|
+
return {
|
|
722
|
+
valid: true,
|
|
723
|
+
license,
|
|
724
|
+
availableFeatures: license.features,
|
|
725
|
+
validatedAt: now
|
|
726
|
+
};
|
|
727
|
+
} catch {
|
|
728
|
+
return {
|
|
729
|
+
valid: false,
|
|
730
|
+
errorCode: "TAMPERED",
|
|
731
|
+
errorMessage: "Invalid offline activation response",
|
|
732
|
+
validatedAt: now
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Check if feature is available
|
|
738
|
+
*/
|
|
739
|
+
async hasFeature(key, feature) {
|
|
740
|
+
var _a;
|
|
741
|
+
const result = await this.validate(key);
|
|
742
|
+
if (!result.valid) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
return ((_a = result.availableFeatures) == null ? void 0 : _a.includes(feature)) ?? false;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Activate machine for license
|
|
749
|
+
*/
|
|
750
|
+
async activateMachine(key) {
|
|
751
|
+
const fingerprint = await generateMachineId();
|
|
752
|
+
const url = `${this.config.serverUrl}/activate`;
|
|
753
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
754
|
+
try {
|
|
755
|
+
const response = await fetch(url, {
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: {
|
|
758
|
+
"Content-Type": "application/json",
|
|
759
|
+
...this.config.apiKey ? { "X-API-Key": this.config.apiKey } : {}
|
|
760
|
+
},
|
|
761
|
+
body: JSON.stringify({
|
|
762
|
+
licenseKey: key,
|
|
763
|
+
machineFingerprint: fingerprint,
|
|
764
|
+
machineName: this.getMachineName(),
|
|
765
|
+
timestamp: now
|
|
766
|
+
})
|
|
767
|
+
});
|
|
768
|
+
if (!response.ok) {
|
|
769
|
+
const data2 = await response.json();
|
|
770
|
+
return {
|
|
771
|
+
valid: false,
|
|
772
|
+
errorCode: data2.errorCode || "SERVER_ERROR",
|
|
773
|
+
errorMessage: data2.errorMessage || "Activation failed",
|
|
774
|
+
validatedAt: now
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const data = await response.json();
|
|
778
|
+
const license = data.license;
|
|
779
|
+
this.storeLicense(license);
|
|
780
|
+
this.clearCache(key);
|
|
781
|
+
return {
|
|
782
|
+
valid: true,
|
|
783
|
+
license,
|
|
784
|
+
availableFeatures: license.features,
|
|
785
|
+
validatedAt: now
|
|
786
|
+
};
|
|
787
|
+
} catch (error) {
|
|
788
|
+
return {
|
|
789
|
+
valid: false,
|
|
790
|
+
errorCode: "NETWORK_ERROR",
|
|
791
|
+
errorMessage: "Cannot connect to license server",
|
|
792
|
+
validatedAt: now
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Deactivate machine from license
|
|
798
|
+
*/
|
|
799
|
+
async deactivateMachine(key) {
|
|
800
|
+
const fingerprint = await generateMachineId();
|
|
801
|
+
const url = `${this.config.serverUrl}/deactivate`;
|
|
802
|
+
try {
|
|
803
|
+
const response = await fetch(url, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers: {
|
|
806
|
+
"Content-Type": "application/json",
|
|
807
|
+
...this.config.apiKey ? { "X-API-Key": this.config.apiKey } : {}
|
|
808
|
+
},
|
|
809
|
+
body: JSON.stringify({
|
|
810
|
+
licenseKey: key,
|
|
811
|
+
machineFingerprint: fingerprint
|
|
812
|
+
})
|
|
813
|
+
});
|
|
814
|
+
if (response.ok) {
|
|
815
|
+
this.clearStoredLicense(key);
|
|
816
|
+
this.clearCache(key);
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
return false;
|
|
820
|
+
} catch {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// ─── Cache Management ─────────────────────────────────────────
|
|
825
|
+
getFromCache(key) {
|
|
826
|
+
const cached = this.cache.get(key);
|
|
827
|
+
if (!cached) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
if (Date.now() > cached.expiresAt) {
|
|
831
|
+
this.cache.delete(key);
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
return cached.result;
|
|
835
|
+
}
|
|
836
|
+
setCache(key, result) {
|
|
837
|
+
const now = Date.now();
|
|
838
|
+
const ttl = (this.config.cacheTtl || 3600) * 1e3;
|
|
839
|
+
this.cache.set(key, {
|
|
840
|
+
result,
|
|
841
|
+
cachedAt: now,
|
|
842
|
+
expiresAt: now + ttl
|
|
843
|
+
});
|
|
844
|
+
this.saveCacheToStorage();
|
|
845
|
+
}
|
|
846
|
+
clearCache(key) {
|
|
847
|
+
this.cache.delete(key);
|
|
848
|
+
this.saveCacheToStorage();
|
|
849
|
+
}
|
|
850
|
+
// ─── Storage Management ───────────────────────────────────────
|
|
851
|
+
getStoredLicense(key) {
|
|
852
|
+
if (typeof localStorage === "undefined") {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
try {
|
|
856
|
+
const data = localStorage.getItem(`${CACHE_PREFIX}info_${key}`);
|
|
857
|
+
return data ? JSON.parse(data) : null;
|
|
858
|
+
} catch {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
storeLicense(license) {
|
|
863
|
+
if (typeof localStorage === "undefined") {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
localStorage.setItem(`${CACHE_PREFIX}info_${license.key}`, JSON.stringify(license));
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
clearStoredLicense(key) {
|
|
872
|
+
if (typeof localStorage === "undefined") {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
localStorage.removeItem(`${CACHE_PREFIX}info_${key}`);
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
loadCacheFromStorage() {
|
|
881
|
+
if (typeof localStorage === "undefined") {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const data = localStorage.getItem(`${CACHE_PREFIX}cache`);
|
|
886
|
+
if (data) {
|
|
887
|
+
const parsed = JSON.parse(data);
|
|
888
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
889
|
+
this.cache.set(key, value);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
saveCacheToStorage() {
|
|
896
|
+
if (typeof localStorage === "undefined") {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
const obj = {};
|
|
901
|
+
for (const [key, value] of this.cache.entries()) {
|
|
902
|
+
obj[key] = value;
|
|
903
|
+
}
|
|
904
|
+
localStorage.setItem(`${CACHE_PREFIX}cache`, JSON.stringify(obj));
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
getMachineName() {
|
|
909
|
+
if (typeof navigator !== "undefined") {
|
|
910
|
+
return navigator.userAgent.split(/[()]/)[1] || "Unknown Device";
|
|
911
|
+
}
|
|
912
|
+
return "Server";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
let globalValidator = null;
|
|
916
|
+
function getValidator(config) {
|
|
917
|
+
if (!globalValidator || config) {
|
|
918
|
+
globalValidator = new LicenseValidator(config);
|
|
919
|
+
}
|
|
920
|
+
return globalValidator;
|
|
921
|
+
}
|
|
922
|
+
async function validateLicense(key) {
|
|
923
|
+
return getValidator().validate(key);
|
|
924
|
+
}
|
|
925
|
+
async function isFeatureLicensed(key, feature) {
|
|
926
|
+
return getValidator().hasFeature(key, feature);
|
|
927
|
+
}
|
|
928
|
+
const featureRegistry = /* @__PURE__ */ new Map();
|
|
929
|
+
const planRegistry = /* @__PURE__ */ new Map();
|
|
930
|
+
function registerFeature(feature) {
|
|
931
|
+
featureRegistry.set(feature.id, feature);
|
|
932
|
+
}
|
|
933
|
+
function registerFeatures(features) {
|
|
934
|
+
features.forEach(registerFeature);
|
|
935
|
+
}
|
|
936
|
+
function getFeature(id) {
|
|
937
|
+
return featureRegistry.get(id);
|
|
938
|
+
}
|
|
939
|
+
function getAllFeatures() {
|
|
940
|
+
return Array.from(featureRegistry.values());
|
|
941
|
+
}
|
|
942
|
+
function getFeaturesByCategory(category) {
|
|
943
|
+
return getAllFeatures().filter((f) => f.category === category);
|
|
944
|
+
}
|
|
945
|
+
function registerPlan(plan) {
|
|
946
|
+
planRegistry.set(plan.id, plan);
|
|
947
|
+
}
|
|
948
|
+
function registerPlans(plans) {
|
|
949
|
+
plans.forEach(registerPlan);
|
|
950
|
+
}
|
|
951
|
+
function getPlan(tier) {
|
|
952
|
+
return planRegistry.get(tier);
|
|
953
|
+
}
|
|
954
|
+
function getAllPlans() {
|
|
955
|
+
return Array.from(planRegistry.values());
|
|
956
|
+
}
|
|
957
|
+
function hasFeature(license, featureId) {
|
|
958
|
+
if (!license) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
if (license.features.includes(featureId)) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
const feature = getFeature(featureId);
|
|
965
|
+
if (feature) {
|
|
966
|
+
return tierMeetsRequirement(license.tier, feature.requiredTier);
|
|
967
|
+
}
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
function hasTier(license, requiredTier) {
|
|
971
|
+
if (!license) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
return tierMeetsRequirement(license.tier, requiredTier);
|
|
975
|
+
}
|
|
976
|
+
function getAvailableFeatures(license) {
|
|
977
|
+
if (!license) {
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
const available = new Set(license.features);
|
|
981
|
+
for (const feature of getAllFeatures()) {
|
|
982
|
+
if (tierMeetsRequirement(license.tier, feature.requiredTier)) {
|
|
983
|
+
available.add(feature.id);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return Array.from(available);
|
|
987
|
+
}
|
|
988
|
+
function getMissingFeatures(license, targetTier) {
|
|
989
|
+
const currentFeatures = new Set(license ? getAvailableFeatures(license) : []);
|
|
990
|
+
const targetFeatures = getDefaultFeatures(targetTier);
|
|
991
|
+
return getAllFeatures().filter(
|
|
992
|
+
(f) => targetFeatures.includes(f.id) && !currentFeatures.has(f.id)
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
function checkFeatureAccess(license, featureId) {
|
|
996
|
+
if (!license) {
|
|
997
|
+
return {
|
|
998
|
+
allowed: false,
|
|
999
|
+
reason: "no-license",
|
|
1000
|
+
requiredTier: "personal"
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
if (license.status === "expired") {
|
|
1004
|
+
return {
|
|
1005
|
+
allowed: false,
|
|
1006
|
+
reason: "expired"
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
const feature = getFeature(featureId);
|
|
1010
|
+
if (!feature) {
|
|
1011
|
+
return {
|
|
1012
|
+
allowed: license.features.includes(featureId),
|
|
1013
|
+
reason: license.features.includes(featureId) ? void 0 : "feature-not-included"
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (!tierMeetsRequirement(license.tier, feature.requiredTier)) {
|
|
1017
|
+
return {
|
|
1018
|
+
allowed: false,
|
|
1019
|
+
reason: "tier-insufficient",
|
|
1020
|
+
requiredTier: feature.requiredTier,
|
|
1021
|
+
upgradeTarget: feature.requiredTier
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
if (feature.dependencies) {
|
|
1025
|
+
for (const dep of feature.dependencies) {
|
|
1026
|
+
const depResult = checkFeatureAccess(license, dep);
|
|
1027
|
+
if (!depResult.allowed) {
|
|
1028
|
+
return depResult;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return { allowed: true };
|
|
1033
|
+
}
|
|
1034
|
+
const DEFAULT_FEATURES = [
|
|
1035
|
+
// Core features
|
|
1036
|
+
{
|
|
1037
|
+
id: "core",
|
|
1038
|
+
name: "Core Components",
|
|
1039
|
+
description: "Basic UI components",
|
|
1040
|
+
requiredTier: "trial",
|
|
1041
|
+
category: "core"
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
id: "basic-components",
|
|
1045
|
+
name: "Basic Components",
|
|
1046
|
+
description: "Buttons, inputs, cards",
|
|
1047
|
+
requiredTier: "trial",
|
|
1048
|
+
category: "core"
|
|
1049
|
+
},
|
|
1050
|
+
// Personal tier
|
|
1051
|
+
{
|
|
1052
|
+
id: "advanced-components",
|
|
1053
|
+
name: "Advanced Components",
|
|
1054
|
+
description: "Data grids, charts, editors",
|
|
1055
|
+
requiredTier: "personal",
|
|
1056
|
+
category: "components"
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
id: "theming",
|
|
1060
|
+
name: "Custom Theming",
|
|
1061
|
+
description: "Theme customization",
|
|
1062
|
+
requiredTier: "personal",
|
|
1063
|
+
category: "customization"
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
id: "i18n",
|
|
1067
|
+
name: "Internationalization",
|
|
1068
|
+
description: "Multi-language support",
|
|
1069
|
+
requiredTier: "personal",
|
|
1070
|
+
category: "customization"
|
|
1071
|
+
},
|
|
1072
|
+
// Team tier
|
|
1073
|
+
{
|
|
1074
|
+
id: "collaboration",
|
|
1075
|
+
name: "Collaboration",
|
|
1076
|
+
description: "Real-time collaboration features",
|
|
1077
|
+
requiredTier: "team",
|
|
1078
|
+
category: "team"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
id: "analytics",
|
|
1082
|
+
name: "Analytics",
|
|
1083
|
+
description: "Usage analytics dashboard",
|
|
1084
|
+
requiredTier: "team",
|
|
1085
|
+
category: "team"
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
id: "priority-support",
|
|
1089
|
+
name: "Priority Support",
|
|
1090
|
+
description: "24-hour response time",
|
|
1091
|
+
requiredTier: "team",
|
|
1092
|
+
category: "support"
|
|
1093
|
+
},
|
|
1094
|
+
// Enterprise tier
|
|
1095
|
+
{
|
|
1096
|
+
id: "white-label",
|
|
1097
|
+
name: "White Labeling",
|
|
1098
|
+
description: "Remove Nice2Dev branding",
|
|
1099
|
+
requiredTier: "enterprise",
|
|
1100
|
+
category: "enterprise"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
id: "sso",
|
|
1104
|
+
name: "Single Sign-On",
|
|
1105
|
+
description: "SAML/OIDC integration",
|
|
1106
|
+
requiredTier: "enterprise",
|
|
1107
|
+
category: "enterprise"
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
id: "audit-log",
|
|
1111
|
+
name: "Audit Log",
|
|
1112
|
+
description: "Detailed activity logging",
|
|
1113
|
+
requiredTier: "enterprise",
|
|
1114
|
+
category: "enterprise"
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
id: "custom-branding",
|
|
1118
|
+
name: "Custom Branding",
|
|
1119
|
+
description: "Full brand customization",
|
|
1120
|
+
requiredTier: "enterprise",
|
|
1121
|
+
category: "enterprise"
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
id: "api-access",
|
|
1125
|
+
name: "API Access",
|
|
1126
|
+
description: "Full REST API access",
|
|
1127
|
+
requiredTier: "enterprise",
|
|
1128
|
+
category: "enterprise"
|
|
1129
|
+
},
|
|
1130
|
+
// Site tier
|
|
1131
|
+
{
|
|
1132
|
+
id: "unlimited-seats",
|
|
1133
|
+
name: "Unlimited Seats",
|
|
1134
|
+
description: "No user limits",
|
|
1135
|
+
requiredTier: "site",
|
|
1136
|
+
category: "site"
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
id: "source-code-access",
|
|
1140
|
+
name: "Source Code Access",
|
|
1141
|
+
description: "Access to source code",
|
|
1142
|
+
requiredTier: "site",
|
|
1143
|
+
category: "site"
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
id: "dedicated-support",
|
|
1147
|
+
name: "Dedicated Support",
|
|
1148
|
+
description: "Dedicated support engineer",
|
|
1149
|
+
requiredTier: "site",
|
|
1150
|
+
category: "support"
|
|
1151
|
+
},
|
|
1152
|
+
// OEM tier
|
|
1153
|
+
{
|
|
1154
|
+
id: "redistribution",
|
|
1155
|
+
name: "Redistribution Rights",
|
|
1156
|
+
description: "Include in your products",
|
|
1157
|
+
requiredTier: "oem",
|
|
1158
|
+
category: "oem"
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
id: "custom-licensing",
|
|
1162
|
+
name: "Custom Licensing",
|
|
1163
|
+
description: "Your own license terms",
|
|
1164
|
+
requiredTier: "oem",
|
|
1165
|
+
category: "oem"
|
|
1166
|
+
},
|
|
1167
|
+
// Trial marker
|
|
1168
|
+
{
|
|
1169
|
+
id: "trial-watermark",
|
|
1170
|
+
name: "Trial Watermark",
|
|
1171
|
+
description: "Watermark for trial versions",
|
|
1172
|
+
requiredTier: "trial",
|
|
1173
|
+
category: "trial"
|
|
1174
|
+
},
|
|
1175
|
+
// Game Engine features
|
|
1176
|
+
{
|
|
1177
|
+
id: "game-editor",
|
|
1178
|
+
name: "Game Editor",
|
|
1179
|
+
description: "Visual game editor",
|
|
1180
|
+
requiredTier: "personal",
|
|
1181
|
+
category: "game-engine"
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
id: "game-export",
|
|
1185
|
+
name: "Game Export",
|
|
1186
|
+
description: "Export to various platforms",
|
|
1187
|
+
requiredTier: "team",
|
|
1188
|
+
category: "game-engine"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
id: "game-multiplayer",
|
|
1192
|
+
name: "Multiplayer",
|
|
1193
|
+
description: "Online multiplayer support",
|
|
1194
|
+
requiredTier: "team",
|
|
1195
|
+
category: "game-engine"
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
id: "game-build-cloud",
|
|
1199
|
+
name: "Build Cloud",
|
|
1200
|
+
description: "Cloud compilation for games",
|
|
1201
|
+
requiredTier: "enterprise",
|
|
1202
|
+
category: "game-engine"
|
|
1203
|
+
},
|
|
1204
|
+
// Spatial features
|
|
1205
|
+
{
|
|
1206
|
+
id: "spatial-editor",
|
|
1207
|
+
name: "Spatial Editor",
|
|
1208
|
+
description: "Floor plan editor",
|
|
1209
|
+
requiredTier: "personal",
|
|
1210
|
+
category: "spatial"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
id: "spatial-3d",
|
|
1214
|
+
name: "3D Visualization",
|
|
1215
|
+
description: "3D building visualization",
|
|
1216
|
+
requiredTier: "team",
|
|
1217
|
+
category: "spatial"
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
id: "spatial-bim",
|
|
1221
|
+
name: "BIM Integration",
|
|
1222
|
+
description: "IFC/BIM file support",
|
|
1223
|
+
requiredTier: "enterprise",
|
|
1224
|
+
category: "spatial"
|
|
1225
|
+
}
|
|
1226
|
+
];
|
|
1227
|
+
const DEFAULT_PLANS = [
|
|
1228
|
+
{
|
|
1229
|
+
id: "trial",
|
|
1230
|
+
name: "Trial",
|
|
1231
|
+
description: "14-day free trial with all features",
|
|
1232
|
+
priceMonthly: 0,
|
|
1233
|
+
priceYearly: 0,
|
|
1234
|
+
features: ["core", "basic-components", "trial-watermark"],
|
|
1235
|
+
maxSeats: 1,
|
|
1236
|
+
maxMachines: 1,
|
|
1237
|
+
highlights: ["All features for 14 days", "No credit card required"]
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
id: "personal",
|
|
1241
|
+
name: "Personal",
|
|
1242
|
+
description: "For individual developers",
|
|
1243
|
+
priceMonthly: 1900,
|
|
1244
|
+
// $19
|
|
1245
|
+
priceYearly: 15900,
|
|
1246
|
+
// $159 (2 months free)
|
|
1247
|
+
features: ["core", "basic-components", "advanced-components", "theming", "i18n"],
|
|
1248
|
+
maxSeats: 1,
|
|
1249
|
+
maxMachines: 2,
|
|
1250
|
+
highlights: ["All UI components", "Custom theming", "Email support"]
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
id: "team",
|
|
1254
|
+
name: "Team",
|
|
1255
|
+
description: "For small to medium teams",
|
|
1256
|
+
priceMonthly: 4900,
|
|
1257
|
+
// $49/seat
|
|
1258
|
+
priceYearly: 41900,
|
|
1259
|
+
// $419/seat
|
|
1260
|
+
features: [
|
|
1261
|
+
"core",
|
|
1262
|
+
"basic-components",
|
|
1263
|
+
"advanced-components",
|
|
1264
|
+
"theming",
|
|
1265
|
+
"i18n",
|
|
1266
|
+
"collaboration",
|
|
1267
|
+
"analytics",
|
|
1268
|
+
"priority-support"
|
|
1269
|
+
],
|
|
1270
|
+
maxSeats: 10,
|
|
1271
|
+
maxMachines: 20,
|
|
1272
|
+
highlights: ["Up to 10 seats", "Collaboration tools", "Priority support"],
|
|
1273
|
+
recommended: true
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
id: "enterprise",
|
|
1277
|
+
name: "Enterprise",
|
|
1278
|
+
description: "For large organizations",
|
|
1279
|
+
priceMonthly: 19900,
|
|
1280
|
+
// $199/month base
|
|
1281
|
+
priceYearly: 199900,
|
|
1282
|
+
// $1999/year
|
|
1283
|
+
features: [
|
|
1284
|
+
"core",
|
|
1285
|
+
"basic-components",
|
|
1286
|
+
"advanced-components",
|
|
1287
|
+
"theming",
|
|
1288
|
+
"i18n",
|
|
1289
|
+
"collaboration",
|
|
1290
|
+
"analytics",
|
|
1291
|
+
"priority-support",
|
|
1292
|
+
"white-label",
|
|
1293
|
+
"sso",
|
|
1294
|
+
"audit-log",
|
|
1295
|
+
"custom-branding",
|
|
1296
|
+
"api-access"
|
|
1297
|
+
],
|
|
1298
|
+
maxSeats: 50,
|
|
1299
|
+
maxMachines: 100,
|
|
1300
|
+
highlights: ["Up to 50 seats", "SSO & SAML", "White labeling", "API access"]
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
id: "site",
|
|
1304
|
+
name: "Site License",
|
|
1305
|
+
description: "Unlimited usage for your organization",
|
|
1306
|
+
priceMonthly: 0,
|
|
1307
|
+
// Contact sales
|
|
1308
|
+
priceYearly: 0,
|
|
1309
|
+
features: [
|
|
1310
|
+
"core",
|
|
1311
|
+
"basic-components",
|
|
1312
|
+
"advanced-components",
|
|
1313
|
+
"theming",
|
|
1314
|
+
"i18n",
|
|
1315
|
+
"collaboration",
|
|
1316
|
+
"analytics",
|
|
1317
|
+
"priority-support",
|
|
1318
|
+
"white-label",
|
|
1319
|
+
"sso",
|
|
1320
|
+
"audit-log",
|
|
1321
|
+
"custom-branding",
|
|
1322
|
+
"api-access",
|
|
1323
|
+
"unlimited-seats",
|
|
1324
|
+
"source-code-access",
|
|
1325
|
+
"dedicated-support"
|
|
1326
|
+
],
|
|
1327
|
+
maxSeats: null,
|
|
1328
|
+
maxMachines: 1e3,
|
|
1329
|
+
highlights: ["Unlimited seats", "Source code access", "Dedicated support engineer"]
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
id: "oem",
|
|
1333
|
+
name: "OEM",
|
|
1334
|
+
description: "Embed in your products",
|
|
1335
|
+
priceMonthly: 0,
|
|
1336
|
+
// Contact sales
|
|
1337
|
+
priceYearly: 0,
|
|
1338
|
+
features: [
|
|
1339
|
+
"core",
|
|
1340
|
+
"basic-components",
|
|
1341
|
+
"advanced-components",
|
|
1342
|
+
"theming",
|
|
1343
|
+
"i18n",
|
|
1344
|
+
"collaboration",
|
|
1345
|
+
"analytics",
|
|
1346
|
+
"priority-support",
|
|
1347
|
+
"white-label",
|
|
1348
|
+
"sso",
|
|
1349
|
+
"audit-log",
|
|
1350
|
+
"custom-branding",
|
|
1351
|
+
"api-access",
|
|
1352
|
+
"unlimited-seats",
|
|
1353
|
+
"source-code-access",
|
|
1354
|
+
"dedicated-support",
|
|
1355
|
+
"redistribution",
|
|
1356
|
+
"custom-licensing"
|
|
1357
|
+
],
|
|
1358
|
+
maxSeats: null,
|
|
1359
|
+
maxMachines: 1e4,
|
|
1360
|
+
highlights: ["Redistribution rights", "Custom licensing", "Full support"]
|
|
1361
|
+
}
|
|
1362
|
+
];
|
|
1363
|
+
function initializeDefaults() {
|
|
1364
|
+
registerFeatures(DEFAULT_FEATURES);
|
|
1365
|
+
registerPlans(DEFAULT_PLANS);
|
|
1366
|
+
}
|
|
1367
|
+
initializeDefaults();
|
|
1368
|
+
exports.LicenseValidator = LicenseValidator;
|
|
1369
|
+
exports.checkFeatureAccess = checkFeatureAccess;
|
|
1370
|
+
exports.collectFingerprintData = collectFingerprintData;
|
|
1371
|
+
exports.compareFingerprintsData = compareFingerprintsData;
|
|
1372
|
+
exports.compareTiers = compareTiers;
|
|
1373
|
+
exports.extractKeyMetadata = extractKeyMetadata;
|
|
1374
|
+
exports.generateFingerprint = generateFingerprint;
|
|
1375
|
+
exports.generateLicenseInfo = generateLicenseInfo;
|
|
1376
|
+
exports.generateLicenseKey = generateLicenseKey;
|
|
1377
|
+
exports.generateMachineId = generateMachineId;
|
|
1378
|
+
exports.getAllFeatures = getAllFeatures;
|
|
1379
|
+
exports.getAllPlans = getAllPlans;
|
|
1380
|
+
exports.getAvailableFeatures = getAvailableFeatures;
|
|
1381
|
+
exports.getDefaultFeatures = getDefaultFeatures;
|
|
1382
|
+
exports.getDefaultMachines = getDefaultMachines;
|
|
1383
|
+
exports.getDefaultSeats = getDefaultSeats;
|
|
1384
|
+
exports.getFeature = getFeature;
|
|
1385
|
+
exports.getFeaturesByCategory = getFeaturesByCategory;
|
|
1386
|
+
exports.getMissingFeatures = getMissingFeatures;
|
|
1387
|
+
exports.getOrGenerateFingerprint = getOrGenerateFingerprint;
|
|
1388
|
+
exports.getPlan = getPlan;
|
|
1389
|
+
exports.getStoredFingerprint = getStoredFingerprint;
|
|
1390
|
+
exports.getValidator = getValidator;
|
|
1391
|
+
exports.hasFeature = hasFeature;
|
|
1392
|
+
exports.hasTier = hasTier;
|
|
1393
|
+
exports.initializeDefaults = initializeDefaults;
|
|
1394
|
+
exports.isFeatureLicensed = isFeatureLicensed;
|
|
1395
|
+
exports.maskLicenseKey = maskLicenseKey;
|
|
1396
|
+
exports.normalizeLicenseKey = normalizeLicenseKey;
|
|
1397
|
+
exports.registerFeature = registerFeature;
|
|
1398
|
+
exports.registerFeatures = registerFeatures;
|
|
1399
|
+
exports.registerPlan = registerPlan;
|
|
1400
|
+
exports.registerPlans = registerPlans;
|
|
1401
|
+
exports.storeFingerprint = storeFingerprint;
|
|
1402
|
+
exports.tierMeetsRequirement = tierMeetsRequirement;
|
|
1403
|
+
exports.validateKeyFormat = validateKeyFormat;
|
|
1404
|
+
exports.validateLicense = validateLicense;
|
|
1405
|
+
//# sourceMappingURL=FeatureGate-Cs1zphXs.cjs.map
|