LimeLM
wyBuild
Support forum
wyDay blog
wyDay Home

Using TurboActivate with NodeJS or Electron

NodeJS and ElectronThis article will give step-by-step instructions on how to add software licensing (specifically, hardware-locked or node-locked licensing) to your Electron or NodeJS app. There's also a full example app that you can download and play with without needing to add licensing to your app. This article (and accompanying example app) are for NodeJS, but you can use TurboActivate with any generic NodeJS or Electron app.

By the end of this article you'll have working licensing integrated with your application, and thus the ability to sell individual copies of your software.

This article shows you how to add hardware-locked licensing to your JavaScript app (using TurboActivate). To add floating-licensing to your JavaScript app see the "Using TurboFloat with NodeJS or Electron " article.

Sign up or login and download the native TurboActivate library

Before you can do anything, you need to login to your LimeLM account (or sign up). Then download TurboActivate for Windows, macOS (Mac OS X), Linux, or BSD. It contains the native library and source code examples needed to integrate hardware-locked licensing in your NodeJS app:

Step-by-step walkthrough: adding licensing and trials to your NodeJS app

Now we're going to walk you through each step you need to take in adding licensing, online activation, and timed-trials to your NodeJS app. There are lots of ways you can add licensing to your app, but there are 2 popular styles:

  1. Separate versions of your app. This style of licensing is where you build 2 versions of your product: a trial version and a full version.

  2. Hybrid version of your app. This style of licensing is where your product is the trial version or the full version depending on whether the user is activated.

The first method (the separate versions method) is possible with TurboActivate, but we're not going to talk about it here because it's not very user friendly and it requires more work on your end. Instead we'll talk about making your app a hybrid Full/Trial app. That is, your users will be able to use your app in "trial mode" until either the trial expires or they purchase a product key and use it to activate your app.

Step 1. Signup for LimeLM, download TurboActivate

If you haven't already signed up for LimeLM then sign up now. All plans have a 30-day free trial. Or, if you're just putting your toes in the water, there's even a free plan that has no time limit and doesn't require a credit card.

After you've created your account, download TurboActivate and extract it anywhere. After you extract it, inside the extracted folder you'll find 2 folders: the "API" directory and the "bin-*" directory (for example, bin-windows, for the Windows TurboActivate package) that contains the TurboActivate library and the helper "systa" app required by NodeJS.

Step 2. Install NodeJS

This article and example app requires that you at least have NodeJS LTS installed. You can get the NodeJS LTS release over at nodejs.org.

Step 3. Create a new product in LimeLM

If you haven't already created a new product in LimeLM, do it now. You can change any value later, so don't worry about making a mistake.

Adding your first product to LimeLM

Step 4. Download TurboActivate.dat

Go to your version page in LimeLM. Download the "TurboActivate.dat" file, and make a note of the Version GUID you see on your product version page (you'll be using it in the next step).

TurboActivate.dat and Version GUID

You'll be copying this TurboActivate.dat files in the next step to sit alongside the native "systa.exe" or "systa" files in each of the platforms you want to support.

Side-note about TurboActivate.dat file: this file is an "information file" about your product version, and gives TurboActivate enough information to verify product keys and cryptographically signed activation data locally. It's a read-only file (license data will never be written to it).

Step 5. Native library and helper app (systa)

TurboActivate uses native system calls to generate our proprietary "fingerprint" of the computer your customer is activating your software on. And because JavaScript / NodeJS doesn't currently have the ability to directly call native libraries you need to use the "helper" app that we provide ("systa.exe" on Windows, "systa" on macOS, Linux, FreeBSD) alongside the native TurboActivate library. See step 1, above for where to find the TurboActivate library and helper "systa" app.

If you're playing around with our example NodeJS app, the layout of the JavaScript files and the native files is like so:


main.js
turboactivate.js
native
...Windows
......systa.exe
......TurboActivate.dll
......TurboActivate.dat
...Linux
......systa
......libTurboActivate.so
......TurboActivate.dat
...FreeBSD
......systa
......libTurboActivate.so
......TurboActivate.dat
...Mac
......systa
......libTurboActivate.dylib
......TurboActivate.dat

The systa file needs to be executable (meaning have the executable permission). To do this, navigate to the directory in a terminal and give the systa file the correct permissions. For example, on macOS this would look something like this:

cd /path/to/your/app/native/Mac
chmod u+x systa

Then, to test that the permissions were granted correctly, run the systa ("./systa") and you should see a brief description with the version numbers:

systa (NodeJS / Adobe AIR helper process) version: 4.1.2.0
TurboActivate library version: 4.1.2.0

Always use the latest version of TurboActivate and
the latest version of this helper executable (systa).

If you didn't set the executable permissions correctly then you will get an error something like this:

-bash: ./systa: Permission denied

As you can see there's the "main.js" file (which is the example app), there's the "turboactivate.js" (which calls the native apps / libraries) and there's the actual native apps organized in the "native" directory which is relative to your app entry-point (which, in the example, is the "main.js" file).

Step 6. Creating the TurboActivate instance

Now, inside your app, you need to create a new TurboActivate object with the Version GUID found on the same page you downloaded the TurboActivate.dat from. In the example app we include the "turboactivate.js" file and then create the "TurboActivate" object with your version GUID like so:

var TurboActivate = require('./turboactivate.js');

//TODO: goto the version page at LimeLM and paste this GUID here
var ta = new TurboActivate("Paste GUID Here");

Replace the "Paste GUID Here" string with the Version GUID string your copied from your version page.

Step 7. Checking if the customer is genuinely activated

There are many ways you can use TurboActivate to add licensing to your application. For this example we're going to keep it simple.

First, create a new function "BeginYourApp()" that starts the logic of your app:

function BeginYourApp(inTrial = false, trialDays = 0)
{
    console.log("Hello world! Here's where your app code would begin.");
}

And then it's simply a matter of making a call to IsGenuineEx() to see if the user is activated and to re-verify the activation with the LimeLM servers every 90 days, with a 14 day grace period:

ta.IsGenuineEx(DaysBetweenChecks, GracePeriodLength, true)
    .then((retObj) => {

        // if activated, begin the app immediately
        if (retObj === TA_OK
            || retObj === TA_E_FEATURES_CHANGED
            || retObj === TA_E_INET
            || retObj === TA_E_INET_DELAYED)
        {
            BeginYourApp();
            return null;
        }
        else // TA_FAIL
            return ta.IsActivated();
    })
    .then((retObj) => { // result from IsActivated()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj === TA_OK)
        {
            // If IsGenuineEx() is telling us we're not activated
            // but the IsActivated() function is telling us that the
            // activation data on the computer is valid (i.e. the
            // crypto-signed-fingerprint matches the computer) then
            // that means that the customer has passed the grace
            // period and they must re-verify with the servers to
            // continue to use your app.

            //Note: DO NOT allow the customer to just continue to use
            // your app indefinitely with absolutely no reverification
            // with the servers. If you want to do that then don't use
            // IsGenuine() or IsGenuineEx() at all -- just
            // use IsActivated().
            PromptUserToReverify();
            return null;
        }
        else // TA_FAIL (not activated, start the trial)
        {
            // Start / re-verify the trial, provide a function
            // to be called asynchronously when the trial expires
            // and pass in the type of trial (see trialFlags).

            // You still need to call TrialDaysRemaining() to
            // see the trial days remaining (because the callback is
            // *not*) called if the trial has already expired outside
            // the life of this program.
            return ta.UseTrial(TATrialChange, trialFlags);
        }
    })
    .then((retObj) => { // result from UseTrial()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj === TA_OK)
            return ta.TrialDaysRemaining(trialFlags);
        else if (retObj === TA_E_TRIAL_EXPIRED)
        {
            TrialExpired();
            return null;
        }
        else
            HardFailure("Failed to start the trial", retObj);
    })
    .then((retObj) => { // result from TrialDaysRemaining()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj[0] === TA_OK)
        {
            // if the customer is in a trial, being your app
            if (retObj[1] > 0)
                BeginYourApp(true, retObj[1]);
            else // no more trial days remaining
            {
                TrialExpired();
                return null;
            }
        }
        else
        {
            HardFailure("Failed to get the trial days remaining",
                retObj[0]);
        }
    })
    .catch((retObj) => { // an error somewhere in the processing

        HardFailure("Something failed! " + retObj, 1);
    });

This is a complete example showing how to check if the customer is genuinely activated, and how to handle the error cases. While it's longer than "toy" licensing solutions, it's built for the real world.

The code does the following:

  1. It creates the new TurboActivate instance with your Version GUID.

  2. Checks if the customer is activated and re-verifies with the servers every 90 days (with a 14-day grace period).

  3. And if IsGenuineEx() tells you the customer is not genuine then you can use IsActivated() to determine if they're "not genuine" because they were never activated (or have been deactivated remotely by your organization) or if it's because the customer has gone more than daysBetweenChecks + gracePeriodLength days since re-verifying with the activation servers.

  4. And if it's a case where the customer must re-verify with the activation servers, then prompt the user in a "for loop" that lets them do that.

Step 8. Prompting for the user's product key

If the user has never activated, or if they've since deactivated, then you'll need to prompt the user to enter their product key. You can do this a couple of ways:

  1. If you're just targeting Windows you can use our pre-built the TurboActivate Wizard to prompt the customer to enter their product key.

  2. Or, prompt the user directly in your app and then save the product key (using the CheckAndSavePKey() function) and activate your app (using the Activate() function)

Here's an example of how you might prompt the user for their product key and activate the product key on the computer:

var userEnteredPkey;

//TODO: prompt the user for the product key

// validate and save the product key if it's valid
ta.CheckAndSavePKey(userEnteredPkey, TA_USER)
    .then((retObj) => {

        // the product key was valid and saved successfully
        if (retObj === TA_OK)
            return ta.Activate();
        else // TA_FAIL
        {
            HardFailure("Something failed! " + retObj, 1);
            return null;
        }
    })
    .then((retObj) => { // result from Activate()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj === TA_OK)
        {
            // the customer activated successfully, begin your app
            BeginYourApp();
        }
        else
        {
            HardFailure("Activation failed.", retObj);
        }
    })
    .catch((retObj) => { // an error somewhere in the processing

        HardFailure("Something failed! " + retObj, 1);
    });

A note about TA_USER vs. TA_SYSTEM

In the example above you can see we're using TA_USER in the CheckAndSavePKey function. This tells TurboActivate to store the activation data in directories that the user has the ability to write to. If you were using TA_SYSTEM instead, this would tell TurboActivate to store the activation data to directories where all users on that machine would have the ability to read/write the activation data.

In both cases the activation data locks to the machine. The only difference is where the activation data is stored. We typically recommend using TA_SYSTEM, however the only downside to using that flag is that the first time (and only the first time) you call a function with the TA_SYSTEM flag your app needs "elevated" or "sudo" permission.

Step 9. Adding trial functionality

In this example we're going to use verified trials because they are accurate, fast, and allows you to track conversion of customers.

The first step is to actually tell TurboActivate you'll be using verified trials. Create a trialFlags at the top of your main form that will store this information:

// Set the trial flags you want to use. Here we've selected that
// the trial data should be stored user-wide (TA_USER) and that we
// should use un-resetable verified trials (TA_VERIFIED_TRIAL).
const trialFlags = TA_USER | TA_VERIFIED_TRIAL;

// Don't use 0 for either of these values.
// We recommend 90, 14. But if you want to lower the values
// we don't recommend going below 7 days for each value.
// Anything lower and you're just punishing legit users.
const DaysBetweenChecks = 90;
const GracePeriodLength = 14;

ta.IsGenuineEx(DaysBetweenChecks, GracePeriodLength, true)
    .then((retObj) => {

    // ...

Starting the verified trial and seeing how many days are remaining is as simple as this:

ta.UseTrial(TATrialChange, trialFlags)
    .then((retObj) => { // result from UseTrial()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj === TA_OK)
            return ta.TrialDaysRemaining(trialFlags);
        else if (retObj === TA_E_TRIAL_EXPIRED)
        {
            TrialExpired();
            return null;
        }
        else
            HardFailure("Failed to start the trial", retObj);
    })
    .then((retObj) => { // result from TrialDaysRemaining()

        // if null, fall through
        if (retObj === null)
            return null;

        if (retObj[0] === TA_OK)
        {
            // if the customer is in a trial, being your app
            if (retObj[1] > 0)
                BeginYourApp(true, retObj[1]);
            else // no more trial days remaining
            {
                TrialExpired();
                return null;
            }
        }
        else
        {
            HardFailure("Failed to get the trial days remaining",
                retObj[0]);
        }
    })
    .catch((retObj) => { // an error somewhere in the processing

        HardFailure("Something failed! " + retObj, 1);
    });

Briefly, this code...:

  1. Starts or reverifies the verified trial (depending on whether it's the first time the code is run on the end-user's machine)

  2. Handles trial expiration by calling a function call "TrialExpired()" that you would need to define. (See the example for a simple stub)

  3. If there are trial day remaining it calls another function you would need to define ("BeginYourApp()") and it passes along whether it's a trial and how many trial days are remaining.

  4. Lastly, in the call "ta.UseTrial(TATrialChange, trialFlags)" another function that you need to create ("TATrialChange()") is passed along as a callback function (the "Trial Changed" event) that will be called asynchronously:

/*
 This callback function is called by the TurboActivate object
 when the trial has expired or the customer is trying to
 fraudulently add more time.
 */
function TATrialChange(status)
{
    //TODO: implement
    switch (status)
    {
        case TA_CB_EXPIRED:
        case TA_CB_EXPIRED_FRAUD:
        default:
            // just call the trial expired function defined below
			// so as to not recreate the same functionality used
			// elsewhere.
            TrialExpired();
            break;
    }
}

With the event handler you'll be able to disable features in your app at runtime without having to poll TurboActivate's functions. It will handle the details of detecting normal trial expiration as well as unusual behavior with the customer's date/time settings (various types of client-side fraud).