Blog: Tutorials

How to Add Time-Limited Functionality to Your JavaScript Code

Learn how to implement code expiration and time-limited functionality in your JavaScript applications for demos, trial versions, and promotional features.

Introduction

Time-limited functionality is a valuable technique for JavaScript developers who want to offer trial versions, demos, seasonal features, or promotional content with an expiration date. By implementing expiration mechanisms in your code, you can control when and how long your JavaScript functionality is available to users.

While time-based restrictions can be bypassed by determined users with technical knowledge, a well-implemented expiration system combined with proper obfuscation creates a sufficient barrier for most scenarios. This tutorial will guide you through implementing effective, tamper-resistant time-limited functionality in your JavaScript applications.

Use Cases for Time-Limited Code

Before diving into implementation details, let's explore some common use cases for time-limited JavaScript functionality:

Trial Versions

Offer fully functional software for a limited period to let users experience premium features before purchasing. After the trial period, functionality is either disabled or limited.

Seasonal Features

Implement holiday-themed features, limited-time promotions, or seasonal content that automatically activates and deactivates on specific dates.

Promotional Content

Create time-sensitive promotional offers, discounts, or special features that disappear after a limited period to create urgency.

License Management

Enforce license expirations in JavaScript libraries, components, or plugins by automatically disabling functionality after the license period ends.

Basic Implementation

Let's start with basic implementation strategies for time-limited functionality. We'll cover two common approaches: date-based and usage-based expiration.

Date-Based Expiration

The simplest approach is to define a specific expiration date after which your code ceases to function:

Date-Based Expiration Basic

/**
 * Date-based expiration check
 * @param {Date} expirationDate - The date when the code should expire
 * @returns {boolean} - True if code is still valid, false if expired
 */
function isCodeValid(expirationDate) {
  const currentDate = new Date();
  return currentDate < expirationDate;
}

// Usage example
const EXPIRATION_DATE = new Date('2025-12-31T23:59:59');

function initializeApp() {
  if (isCodeValid(EXPIRATION_DATE)) {
    // Code is still valid, initialize normally
    console.log('Application initialized successfully!');
    enableFeatures();
  } else {
    // Code has expired, disable functionality
    console.log('This version has expired. Please upgrade to continue using this feature.');
    showExpiredMessage();
  }
}

// Initialize the application
initializeApp();

This simple approach checks the current date against a predefined expiration date. If the current date is past the expiration date, the code can display a message, redirect to a purchase page, or gracefully degrade functionality.

Warning

This basic implementation is vulnerable to tampering. Users can manipulate their system clock or modify the JavaScript code to bypass the expiration. We'll address these vulnerabilities in later sections.

Usage-Based Expiration

Another approach is to limit functionality based on the number of times the code has been used, rather than a specific date:

Usage-Based Expiration Basic

/**
 * Usage-based expiration using localStorage
 * @param {number} maxUsage - Maximum number of allowed uses
 * @returns {boolean} - True if usage limit not exceeded, false otherwise
 */
function checkUsageLimit(maxUsage) {
  const storageKey = 'app_usage_count';
  
  // Get current usage count from storage
  let usageCount = parseInt(localStorage.getItem(storageKey) || '0');
  
  // Increment usage count
  usageCount++;
  
  // Store updated count
  localStorage.setItem(storageKey, usageCount.toString());
  
  // Check if usage is within limits
  return usageCount <= maxUsage;
}

// Usage example
const MAX_USAGE = 10; // Allow 10 uses

function initializeApp() {
  if (checkUsageLimit(MAX_USAGE)) {
    // Usage limit not exceeded
    const remainingUses = MAX_USAGE - parseInt(localStorage.getItem('app_usage_count'));
    console.log(`Application initialized successfully! Remaining uses: ${remainingUses}`);
    enableFeatures();
  } else {
    // Usage limit exceeded
    console.log('Usage limit exceeded. Please purchase the full version to continue.');
    showUpgradeMessage();
  }
}

// Initialize the application
initializeApp();

This approach counts the number of times the application has been used and stops functioning after reaching a predefined limit. This creates a usage-limited trial rather than a time-limited one.

Pro Tip

For a better user experience, consider showing the remaining usage count to users, allowing them to make informed decisions about when to upgrade.

Both basic approaches have their own advantages and use cases, but they are easily bypassed. Let's explore how to make our expiration checks more tamper-resistant.

Protecting Against Tampering

To create more robust time-limited functionality, we need to address common tampering methods:

Obfuscation Techniques

One of the first lines of defense is to obfuscate your expiration logic:

Obfuscation Protection Intermediate

// Instead of a simple date comparison, make the expiration check more complex
function verifyLicense() {
  // Split the expiration timestamp into multiple parts
  const p1 = 1735;  // These values combine to form the expiration timestamp
  const p2 = 686;   // (Unix timestamp for December 31, 2025: 1735686000)
  const p3 = 000;
  
  // Create a decoy date to confuse analysis
  const decoyDate = new Date('2026-06-30T23:59:59');
  
  // Complex calculation to get the actual expiration timestamp
  const expirationTimestamp = (p1 * 1000000) + (p2 * 1000) + p3;
  
  // Get current timestamp with an obscured method
  const now = () => { 
    return new Date().getTime(); 
  };
  
  // Multiple checks to make static analysis harder
  if (decoyDate.getFullYear() !== 2026) return false;
  if (now() > expirationTimestamp) return false;
  if (Math.floor(now() / 1000) > Math.floor(expirationTimestamp / 1000)) return false;
  
  return true;
}

// Additional layer: Hide the usage of the verification function
const features = {
  initialize: function() {
    // Multiple function calls that include the verification
    this.setupEnvironment();
    this.checkCompatibility();
    this.prepareResources();
  },
  
  setupEnvironment: function() {
    // Normal setup code
    console.log('Setting up environment...');
  },
  
  checkCompatibility: function() {
    // Hide license check in a seemingly unrelated function
    console.log('Checking compatibility...');
    if (!verifyLicense()) {
      // Hide the true reason for disabling
      throw new Error('Compatibility check failed: Environment not supported');
    }
  },
  
  prepareResources: function() {
    console.log('Preparing resources...');
    // Normal initialization continues
  }
};

// Start application
features.initialize();

This approach makes the expiration mechanism more difficult to identify and modify by:

  • Splitting the expiration timestamp into multiple parts
  • Adding decoy variables and checks
  • Hiding the verification logic in seemingly unrelated functions
  • Using obscure error messages that don't directly indicate license expiration

Multi-Source Time Verification

To mitigate system clock manipulation, implement multiple time sources:

Multi-Source Time Verification Intermediate

/**
 * Fetches time from multiple sources to prevent clock manipulation
 * @returns {Promise} - Promise resolving to the verified timestamp
 */
async function getVerifiedTime() {
  const sources = [
    'https://worldtimeapi.org/api/ip',
    'https://timeapi.io/api/Time/current/zone?timeZone=UTC',
    // Add more time API sources as backup
  ];
  
  const localTime = new Date().getTime();
  let timeResults = [localTime]; // Include local time as fallback
  
  // Fetch time from external sources
  const fetchPromises = sources.map(async (source) => {
    try {
      const response = await fetch(source);
      const data = await response.json();
      
      // Extract timestamp - adjust based on API response format
      let timestamp;
      if (source.includes('worldtimeapi')) {
        timestamp = new Date(data.utc_datetime).getTime();
      } else if (source.includes('timeapi')) {
        timestamp = new Date(data.dateTime).getTime();
      }
      
      return timestamp;
    } catch (error) {
      console.warn(`Failed to fetch time from ${source}`, error);
      return null;
    }
  });
  
  // Collect all successful time results
  const results = await Promise.allSettled(fetchPromises);
  results.forEach(result => {
    if (result.status === 'fulfilled' && result.value) {
      timeResults.push(result.value);
    }
  });
  
  // Use median time to avoid outliers
  timeResults.sort((a, b) => a - b);
  const medianTime = timeResults[Math.floor(timeResults.length / 2)];
  
  return medianTime;
}

/**
 * Checks if license is valid using verified time
 * @param {number} expirationTimestamp - Expiration timestamp
 * @returns {Promise} - Promise resolving to license validity
 */
async function verifyLicenseWithTime(expirationTimestamp) {
  try {
    const verifiedTime = await getVerifiedTime();
    return verifiedTime < expirationTimestamp;
  } catch (error) {
    console.error('Error during time verification:', error);
    // Fallback to local time if all time sources fail
    return new Date().getTime() < expirationTimestamp;
  }
}

// Usage example
const EXPIRATION_TIMESTAMP = 1735686000000; // Dec 31, 2025

async function initializeApp() {
  const isValid = await verifyLicenseWithTime(EXPIRATION_TIMESTAMP);
  
  if (isValid) {
    console.log('License valid, initializing application...');
    enableFeatures();
  } else {
    console.log('License expired, showing upgrade prompt...');
    showUpgradeMessage();
  }
}

// Initialize with time verification
initializeApp();

This implementation retrieves time from multiple external sources, making it much harder for users to bypass expiration by changing their system clock. By using the median time from multiple sources, you also handle potential outliers or API failures gracefully.

Note

This method requires an internet connection to function properly. Include a sensible fallback for offline usage, potentially with degraded functionality.

Server-Side Verification

For the strongest protection, combine client-side checks with server-side verification:

Server-Side Verification Advanced

/**
 * Server-side license verification
 * @param {string} licenseKey - User's license key
 * @returns {Promise} - License validity and details
 */
async function verifyLicenseWithServer(licenseKey) {
  try {
    const response = await fetch('https://your-license-server.com/api/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        licenseKey: licenseKey,
        productId: 'your-product-id',
        instanceId: getInstanceId(), // Unique identifier for this installation
        timestamp: new Date().getTime()
      })
    });
    
    if (!response.ok) {
      throw new Error('License verification failed');
    }
    
    const data = await response.json();
    
    // Store verification result securely
    sessionStorage.setItem('license_verified', 'true');
    sessionStorage.setItem('license_expiration', data.expirationTimestamp);
    
    return {
      valid: data.valid,
      expiration: new Date(data.expirationTimestamp),
      features: data.features
    };
  } catch (error) {
    console.error('License verification error:', error);
    
    // Fallback to local verification if server is unreachable
    return fallbackVerification(licenseKey);
  }
}

/**
 * Generate a unique instance ID for this installation
 * @returns {string} - Unique instance identifier
 */
function getInstanceId() {
  // Check if we already have an instance ID
  let instanceId = localStorage.getItem('instance_id');
  
  if (!instanceId) {
    // Generate a new instance ID
    instanceId = 'instance_' + Math.random().toString(36).substring(2, 15) + 
                 Math.random().toString(36).substring(2, 15);
    localStorage.setItem('instance_id', instanceId);
  }
  
  return instanceId;
}

/**
 * Fallback verification when server is unavailable
 * @param {string} licenseKey - User's license key
 * @returns {object} - Offline verification result
 */
function fallbackVerification(licenseKey) {
  // Get last successful verification from storage
  const lastVerified = sessionStorage.getItem('license_verified');
  const storedExpiration = sessionStorage.getItem('license_expiration');
  
  if (lastVerified === 'true' && storedExpiration) {
    // We have a verified license in session storage
    return {
      valid: new Date().getTime() < parseInt(storedExpiration),
      expiration: new Date(parseInt(storedExpiration)),
      features: [] // Limited features in offline mode
    };
  }
  
  // Basic offline check (limited grace period)
  const gracePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
  const lastCheckTime = localStorage.getItem('last_offline_check');
  
  if (lastCheckTime && (new Date().getTime() - parseInt(lastCheckTime)) < gracePeriod) {
    // Within grace period, allow limited functionality
    return {
      valid: true,
      expiration: new Date(parseInt(lastCheckTime) + gracePeriod),
      features: ['basic'], // Limited feature set
      offlineMode: true
    };
  }
  
  // No valid verification
  return {
    valid: false,
    expiration: new Date(),
    features: []
  };
}

// Usage example
const LICENSE_KEY = 'USER-LICENSE-KEY-HERE';

async function initializeApp() {
  const licenseStatus = await verifyLicenseWithServer(LICENSE_KEY);
  
  if (licenseStatus.valid) {
    console.log(`License valid until ${licenseStatus.expiration.toDateString()}`);
    
    // Initialize features based on license
    licenseStatus.features.forEach(feature => {
      enableFeature(feature);
    });
    
    // Setup periodic re-verification (every 24 hours)
    setTimeout(() => {
      verifyLicenseWithServer(LICENSE_KEY);
    }, 24 * 60 * 60 * 1000);
    
  } else {
    console.log('License invalid or expired');
    showUpgradeMessage();
  }
  
  // Update last check time for offline grace period
  localStorage.setItem('last_offline_check', new Date().getTime().toString());
}

// Initialize with server verification
initializeApp();
                
                

This approach offers the strongest protection by verifying licenses on your server, where users cannot tamper with the code. Key features include:

  • Server-side verification that cannot be bypassed by client-side manipulation
  • Unique instance ID to prevent sharing licenses across multiple installations
  • Offline grace period for legitimate users with temporary internet issues
  • Periodic re-verification to ensure continued license validity
  • Feature-based licensing that can enable/disable specific functionality

User Experience Considerations

A well-designed expiration system should provide a good user experience, even when features expire:

Clear Communication

Always inform users about time limitations upfront and provide clear notifications as the expiration date approaches:

/**
 * Check and display appropriate expiration notifications
 * @param {Date} expirationDate - When the code will expire
 */
function checkExpirationStatus(expirationDate) {
  const currentDate = new Date();
  const daysRemaining = Math.ceil((expirationDate - currentDate) / (1000 * 60 * 60 * 24));
  
  if (daysRemaining <= 0) {
    // Already expired
    showExpiredMessage();
  } else if (daysRemaining <= 7) {
    // Expiring soon - within a week
    showExpiringMessage(daysRemaining);
  } else if (daysRemaining <= 30) {
    // Approaching expiration - within a month
    showUpgradeReminder(daysRemaining);
  }
}

function showExpiringMessage(days) {
  const message = `
    

Your trial expires in ${days} day${days !== 1 ? 's' : ''}!

Upgrade now to keep access to all premium features.

`; // Display message to user const noticeElement = document.createElement('div'); noticeElement.innerHTML = message; noticeElement.className = 'floating-notice'; document.body.appendChild(noticeElement); // Add event listeners noticeElement.querySelector('.upgrade-button').addEventListener('click', function() { window.location.href = 'https://your-product.com/upgrade'; }); noticeElement.querySelector('.dismiss-button').addEventListener('click', function() { noticeElement.remove(); // Don't show again for 24 hours localStorage.setItem('notice_dismissed', new Date().getTime().toString()); }); }

Graceful Degradation

Instead of completely disabling your application, consider graceful degradation where basic functionality remains available:

/**
 * Enable features based on license status
 * @param {object} licenseStatus - Current license status
 */
function enableFeaturesBasedOnLicense(licenseStatus) {
  // Core features always available
  enableCoreFeatures();
  
  if (licenseStatus.valid) {
    // Premium features for valid licenses
    enablePremiumFeatures();
    
    // Specific feature flags from license
    licenseStatus.features.forEach(featureFlag => {
      enableFeatureByFlag(featureFlag);
    });
  } else if (licenseStatus.expired && licenseStatus.wasValid) {
    // User had a valid license before, enable read-only mode
    enableReadOnlyMode();
    showRenewalMessage();
  } else {
    // Never had a valid license or in trial mode
    enableTrialFeatures();
    showUpgradeMessage();
  }
}

function enableReadOnlyMode() {
  // Allow viewing content but disable editing
  document.querySelectorAll('input, textarea, select, button.edit').forEach(element => {
    element.disabled = true;
  });
  
  // Add visual indication
  document.body.classList.add('read-only-mode');
  
  // Show read-only banner
  const banner = document.createElement('div');
  banner.className = 'read-only-banner';
  banner.innerHTML = 'Read-Only Mode: Your license has expired. Renew now to restore full functionality.';
  document.body.prepend(banner);
}

Conversion Optimization

Design your expiration system to maximize conversion from trial to paid versions:

  • Include direct links to purchase or upgrade pages
  • Offer special discounts as the expiration date approaches
  • Highlight the value of premium features that will be lost
  • Make the upgrade process as frictionless as possible

Integration with JS Obfuscator Pro

JS Obfuscator Pro provides built-in features for implementing secure code expiration. Here's how to use them:

JS Obfuscator Pro Integration Advanced

To implement expiration using JS Obfuscator Pro:

  1. Open the "Protection" panel in JS Obfuscator Pro
  2. Enable "Code Expiration" option
  3. Set the expiration date using the date picker
  4. Choose the action to take when code expires:
Expiration Action Description Best For
Halt Execution Completely stops code execution when expired Strict trial versions
Redirect User Sends the user to a specified URL (e.g., renewal page) Trial versions with direct conversion path
Show Message Displays a custom message explaining the expiration Informational expiration notices
Custom Action Executes your custom JavaScript code on expiration Complex degradation or custom user experiences

JS Obfuscator Pro handles all the technical implementation details, including:

  • Secure time verification that's resistant to tampering
  • Obfuscation of the expiration mechanism
  • Multiple verification points to prevent bypass attempts
  • Proper error handling for a smooth user experience
Pro Tip

For maximum security, combine JS Obfuscator Pro's expiration feature with server-side verification and domain locking to create a multi-layered protection system.

Conclusion

Implementing time-limited functionality in your JavaScript code is a powerful way to create trial versions, seasonal features, or subscription-based services. While no client-side protection is foolproof, combining multiple techniques provides robust security for most use cases.

To summarize the key approaches:

  • Basic Implementation: Simple date or usage-based checks for minimal protection
  • Anti-Tampering: Obfuscation and multi-source time verification to prevent clock manipulation
  • Server-Side Verification: The strongest approach, combining client and server checks
  • User Experience: Clear communication and graceful degradation for a positive user experience
  • JS Obfuscator Pro: Built-in tools to implement secure expiration with minimal effort

Remember that the level of protection should match the value of your code. For most commercial JavaScript products, a combination of obfuscation and server-side verification provides the optimal balance between security and implementation complexity.

Sarah Lee

About Sarah Lee

Sarah is a frontend developer and JavaScript security expert with over 8 years of experience developing secure web applications. She specializes in code protection techniques and has helped numerous companies implement effective licensing systems for their JavaScript products.

Comments

Comments are loading...