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