Licensor

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
  });
});

Back to Overview | C# Client | Python Client