JavaScript License Client
Complete JavaScript/Node.js implementation for integrating WiLicensor.
Installation
Node.js
npm install node-fetch # If using Node < 18
Browser
No additional dependencies required.
Complete Client Class
/**
* WiLicensor JavaScript Client
* Works in both Node.js and browser environments
*/
class LicenseResponse {
constructor(data = {}) {
this.key = data.key || '';
this.condition = data.condition || '';
this.type = data.type || '';
this.machineId = data.machineId || '';
this.machineName = data.machineName || '';
this.productId = data.productId || 0;
this.email = data.email || '';
this.settings = data.settings || '';
this.endAt = data.endAt ? new Date(data.endAt) : null;
this.activatedAt = data.activatedAt ? new Date(data.activatedAt) : null;
this.error = data.error || null;
}
get isValid() {
return this.condition === 'activated' || this.condition === 'purchased';
}
get isExpired() {
if (this.condition === 'expired') return true;
if (this.endAt && this.endAt < new Date()) return true;
return false;
}
}
class LicenseClient {
constructor(baseUrl, productName, options = {}) {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.productName = productName;
this.timeout = options.timeout || 30000;
}
/**
* Check if a license key is valid
*/
async checkKey(licenseKey) {
try {
const url = `${this.baseUrl}/api/check?term=${encodeURIComponent(licenseKey)}`;
const response = await this._fetch(url);
const data = await response.json();
return new LicenseResponse(data);
} catch (error) {
return new LicenseResponse({ error: error.message });
}
}
/**
* Find and activate a license with machine binding
*/
async activateLicense(email, machineId = null) {
try {
const mId = machineId || this.getMachineId();
const machineName = this.getMachineName();
const encryptedKey = EncryptKey.makePassword(mId, '358');
const params = new URLSearchParams({
key: encryptedKey,
email: email,
product: this.productName,
machineId: mId,
machineName: machineName
});
const url = `${this.baseUrl}/api/find?${params}`;
const response = await this._fetch(url);
const data = await response.json();
return new LicenseResponse(data);
} catch (error) {
return new LicenseResponse({ error: error.message });
}
}
/**
* Generate or retrieve a trial license
*/
async getTrial(email, machineId = null) {
try {
const mId = machineId || this.getMachineId();
const machineName = this.getMachineName();
const params = new URLSearchParams({
product: this.productName,
machineId: mId,
machineName: machineName,
email: email
});
const url = `${this.baseUrl}/api/gen?${params}`;
const response = await this._fetch(url);
const data = await response.json();
return new LicenseResponse(data);
} catch (error) {
return new LicenseResponse({ error: error.message });
}
}
/**
* Transfer license to a new machine
*/
async transferLicense(email, oldMachineId, newMachineId = null) {
try {
const newMId = newMachineId || this.getMachineId();
const machineName = this.getMachineName();
const newEncryptedKey = EncryptKey.makePassword(newMId, '358');
const params = new URLSearchParams({
key: newEncryptedKey,
email: email,
product: this.productName,
machineIdOld: oldMachineId,
machineIdNew: newMId,
machineName: machineName
});
const url = `${this.baseUrl}/api/replace?${params}`;
const response = await this._fetch(url);
const data = await response.json();
return new LicenseResponse(data);
} catch (error) {
return new LicenseResponse({ error: error.message });
}
}
/**
* Get machine identifier
* Override this method for custom machine ID logic
*/
getMachineId() {
// In browser, use a combination of available identifiers
if (typeof window !== 'undefined') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('WiLicensor', 2, 2);
const canvasHash = canvas.toDataURL().slice(-50);
const nav = [
navigator.userAgent,
navigator.language,
screen.width,
screen.height
].join('|');
return this._simpleHash(nav + canvasHash);
}
// In Node.js, use OS info
if (typeof require !== 'undefined') {
try {
const os = require('os');
const crypto = require('crypto');
const data = [
os.hostname(),
os.platform(),
os.arch(),
os.cpus()[0]?.model || ''
].join('|');
return crypto.createHash('md5').update(data).digest('hex').substring(0, 16);
} catch (e) {
return 'node-' + Date.now();
}
}
return 'unknown-' + Date.now();
}
/**
* Get machine name
*/
getMachineName() {
if (typeof window !== 'undefined') {
return navigator.userAgent.split(' ')[0] || 'Browser';
}
if (typeof require !== 'undefined') {
try {
return require('os').hostname();
} catch (e) {
return 'Node';
}
}
return 'Unknown';
}
async _fetch(url) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return response;
} finally {
clearTimeout(timeoutId);
}
}
_simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16).substring(0, 16);
}
}
/**
* Key encryption algorithm - must match server implementation
*/
class EncryptKey {
static makePassword(st, identifier) {
if (identifier.length !== 3) {
throw new Error('Identifier must be 3 characters long');
}
const num = [
parseInt(identifier[0], 10),
parseInt(identifier[1], 10),
parseInt(identifier[2], 10)
];
st = this.boring(st);
st = this.inverseByBase(st, num[0]);
st = this.inverseByBase(st, num[1]);
st = this.inverseByBase(st, num[2]);
let result = '';
for (const ch of st) {
result += this.changeChar(ch, num);
}
return result;
}
static inverseByBase(st, moveBase) {
if (moveBase === 0) moveBase = 1;
let result = '';
for (let i = 0; i < st.length; i += moveBase) {
const c = Math.min(moveBase, st.length - i);
result += this.inverseString(st.substring(i, i + c));
}
return result;
}
static inverseString(st) {
return st.split('').reverse().join('');
}
static boring(st) {
let chars = st.split('');
for (let i = 0; i < chars.length; i++) {
let newPlace = (i * chars[i].charCodeAt(0)) % chars.length;
const ch = chars[i];
chars.splice(i, 1);
chars.splice(newPlace, 0, ch);
}
return chars.join('');
}
static changeChar(ch, enCode) {
ch = ch.toUpperCase();
const chValue = ch.charCodeAt(0);
if (ch >= 'A' && ch <= 'H') {
return String.fromCharCode(chValue + 2 * enCode[0]);
} else if (ch >= 'I' && ch <= 'P') {
return String.fromCharCode(chValue - enCode[2]);
} else if (ch >= 'Q' && ch <= 'Z') {
return String.fromCharCode(chValue - enCode[1]);
} else if (ch >= '0' && ch <= '4') {
return String.fromCharCode(chValue + 5);
} else if (ch >= '5' && ch <= '9') {
return String.fromCharCode(chValue - 5);
} else {
return '0';
}
}
}
/**
* Parse settings string into object
*/
function parseSettings(settings) {
const result = {};
if (!settings) return result;
const regex = /"([^"]+)"\s*:\s*"([^"]*)"/g;
let match;
while ((match = regex.exec(settings)) !== null) {
result[match[1]] = match[2];
}
return result;
}
// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = { LicenseClient, LicenseResponse, EncryptKey, parseSettings };
}
Usage Examples
Node.js
const { LicenseClient, parseSettings } = require('./license-client');
const client = new LicenseClient('https://license.yourserver.com', 'YourProduct');
// Check existing license
async function checkLicense() {
const result = await client.checkKey('saved-license-key');
if (result.isValid) {
console.log(`License valid until: ${result.endAt}`);
return true;
} else if (result.isExpired) {
console.log('License has expired. Please renew.');
return false;
} else {
console.log(`License error: ${result.error || result.condition}`);
return false;
}
}
// Activate license
async function activateLicense(email) {
const result = await client.activateLicense(email);
if (result.isValid) {
console.log(`License activated! Type: ${result.type}`);
// Parse and use settings
const settings = parseSettings(result.settings);
console.log('Settings:', settings);
return result.key; // Save this for future checks
} else {
console.log(`Activation failed: ${result.error}`);
return null;
}
}
// Run
(async () => {
const key = await activateLicense('user@example.com');
if (key) {
console.log('Application licensed and ready!');
}
})();
Browser
<!DOCTYPE html>
<html>
<head>
<title>License Check</title>
<script src="license-client.js"></script>
</head>
<body>
<div id="status">Checking license...</div>
<script>
const client = new LicenseClient('https://license.yourserver.com', 'YourProduct');
const statusEl = document.getElementById('status');
async function checkOrActivate() {
// Check if we have a saved license
const savedKey = localStorage.getItem('license_key');
if (savedKey) {
const result = await client.checkKey(savedKey);
if (result.isValid) {
statusEl.textContent = `Licensed! Expires: ${result.endAt}`;
return true;
}
}
// Need to activate - prompt for email
const email = prompt('Enter your email to activate:');
if (!email) {
statusEl.textContent = 'License required';
return false;
}
const result = await client.activateLicense(email);
if (result.isValid) {
localStorage.setItem('license_key', result.key);
statusEl.textContent = `Activated! Type: ${result.type}`;
return true;
} else {
statusEl.textContent = `Failed: ${result.error || result.condition}`;
return false;
}
}
checkOrActivate();
</script>
</body>
</html>
Electron App
const { app, BrowserWindow } = require('electron');
const { LicenseClient } = require('./license-client');
const client = new LicenseClient('https://license.yourserver.com', 'YourElectronApp');
// Override machine ID for Electron
client.getMachineId = () => {
const { machineIdSync } = require('node-machine-id');
return machineIdSync();
};
async function checkLicense() {
const Store = require('electron-store');
const store = new Store();
const savedKey = store.get('licenseKey');
const email = store.get('email');
if (savedKey) {
const result = await client.checkKey(savedKey);
if (result.isValid) {
return true;
}
}
if (email) {
const result = await client.activateLicense(email);
if (result.isValid) {
store.set('licenseKey', result.key);
return true;
}
}
return false;
}
app.whenReady().then(async () => {
const isLicensed = await checkLicense();
if (!isLicensed) {
// Show license activation window
createLicenseWindow();
} else {
// Start main application
createMainWindow();
}
});
Express Middleware
const { LicenseClient } = require('./license-client');
function licenseMiddleware(baseUrl, productName) {
const client = new LicenseClient(baseUrl, productName);
return async (req, res, next) => {
const licenseKey = req.headers['x-license-key'];
if (!licenseKey) {
return res.status(401).json({ error: 'License key required' });
}
const result = await client.checkKey(licenseKey);
if (!result.isValid) {
return res.status(403).json({
error: 'Invalid license',
condition: result.condition
});
}
req.license = result;
next();
};
}
// Usage
const express = require('express');
const app = express();
app.use('/api', licenseMiddleware('https://license.yourserver.com', 'YourAPI'));
app.get('/api/data', (req, res) => {
res.json({
message: 'Licensed request',
licenseType: req.license.type
});
});