Note - the strategies described in this article can be used to migrate users from a different app, a separate brainCloud server environment, or even a completely different backend platform.

Often, when developing a new version of an app, it is useful to beta test it independently of your production brainCloud app.

On the surface, this is straightforward. brainCloud allows you to have multiple versions of your app - and to push configuration updates from one app to another. In fact, this is how most developers handle the standard development lifecycle from dev -> test -> staging -> prod.

But what if you want the user's to start with their existing app data for the beta trial? This makes for a much better beta (from both the end-user perspective and test perspective) - but is challenging to achieve in brainCloud because all end-user data is stored independently for each brainCloud app.

Ideally, the use case would be:

  • Install the beta client (potentially overwriting the production client on their device)

  • beta client launches - connects to the brainCloud beta app, passing in the user's authentication credentials. The following should happen on the server:

    • Authentication fails (because the user doesn't have a profile/account in the beta app yet)

    • The server should connect to the production app, and query to see if the user has an account there.

    • If the user *does* have an account on the production app, the user's production data should be returned - so that we can create a profile/account for the user in the beta app

    • We then return authentication success to the beta client, logging them into their newly created beta app profile/account.

This article provides step-by-step instructions on how to achieve this use case.

Note that this example will demonstrate transferring user email identity with user entities only, but it can be extended to other types of credentials and other user data (such as user files ) too.

Also note that we recommend that this migration be limited to end-user accounts that are using strong identities only (i.e. non-anonymous). Accomplishing this use case using anonymous credentials is more problematic - and special care would need to be taken to preserve the user's original anonymous credentials - otherwise access to their production profile/account would likely be lost when reverting back to the production version of the app.

Thus - our strong recommendation is to allow this mechanism for users with proper identities (email, universalId, Facebook, etc.) attached to their accounts only.

---

To set up account migration

In your source app (which contains the user's existing email identity and user entities) :

  • First, create the following S2S callable script in your source app to validate the credential that passed via the S2S call from your target app (your new app), and returns profileId and userEntities back to the caller. it's well-described as the in-line comments. (Note that this script is named validateAndExportUser under retriveuserdata folder, we will reference it from the caller later)

    "use strict";

    function main() {

    var response = {};

    // success: false will be returned if credential is wrong.
    // {
    // "duration": 113,
    // "data": {
    // "response": "Script Error (Line 17, Column 0): Wrapped com.braincloud.common.ProcessingException: Token does not match user.",
    // "success": false
    // },
    // "status": 200
    // }
    // and this script will stop execute the rest code below this call.
    var session = bridge.getSessionForValidatedCredential(data.externalId, data.authenticationType, data.externalAuthType, data.authenticationToken);

    bridge.logInfo("the session from getSessionForValidatedCredential-", session.toString());


    // then use sysGetUserExport to grab user entities and send back to caller
    var playerId = session.playerId;

    response.playerId = session.playerId;

    var optionsJson = {"includeEntities": true};

    var userProxy = bridge.getUserServiceProxy();

    var postResult = userProxy.sysGetUserExport(playerId, optionsJson);
    if (postResult.status == 200) {
    bridge.logInfoJson("the return from sysGetUserExport...", postResult);
    bridge.logInfoJson("the return from postResult.data[playerId]...", postResult.data[playerId]);
    response.aEntities = postResult.data[playerId].childEntities;
    }

    return response;
    }

    main();

  • Create an S2S server in your source app if you don't have one. We will use it to call the S2S scripts that we created from the above step.

  • Make sure there are several users with email identity and those users have user entities in your source app.

In your target app (to which you want to transfer user credential and user data):

  • Create a hook script in your target app as below to do the transferring job. it's well-described as the in-line comments. (Note that this script is invoked as authenticating postFail-hook, we can achieve the same result by using a pre-hook too depending on your use case)

    "use strict";

    // this is authenticate postFail hook triggered script, use it to check user email credential from a source app, and copy over user data - user entity for this example.

    function main() {

    var response = {};

    bridge.logInfoJson("Script inputs", data);

    // using user email credential for this example
    // it will invoke s2s call to source app 24667
    var emailAddress = data.callingMessage.externalId;
    var passWord = data.callingMessage.authenticationToken;
    var authenticationType = data.callingMessage.authenticationType;
    var externalAuthType = data.callingMessage.externalAuthName;

    var httpProxy = bridge.getHttpClientServiceProxy();

    // validating and retrieving user profileId from source app
    var serviceCode = "bcs2sdispatcher";
    var path = "";
    var headers = {
    "Content-Type": "application/json"
    };

    var appId = data.parms.appId;
    var serverName = data.parms.serverName;
    var serverSecret = data.parms.serverSecret;


    var jsonForScriptRun = {
    "appId": appId,
    "serverName": serverName,
    "serverSecret": serverSecret,
    "service": "script",
    "operation": "RUN",
    "data": {
    "scriptName": data.parms.scriptInSourceApp,
    "scriptData": {
    "externalId":emailAddress,
    "authenticationToken":passWord,
    "authenticationType":authenticationType,
    "externalAuthType":externalAuthType
    }
    }
    };

    var userProfileIdInSourceApp = "";
    var userProfileIdInCurrentApp = "";
    var userEntitiesArry = [];

    var postResultScriptRun = httpProxy.postJsonResponseJson(serviceCode, path, headers, jsonForScriptRun);
    if (postResultScriptRun.status == 200 && postResultScriptRun.data.json.success) {
    bridge.logDebugJson("postResultScriptRun return...", postResultScriptRun);
    userProfileIdInSourceApp = postResultScriptRun.data.json.response.playerId;
    userEntitiesArry = postResultScriptRun.data.json.response.aEntities;
    }

    if (userProfileIdInSourceApp) {
    // create user credentail (using sysCreateUserEmailPassword) in current app with notification -- d-a2160e8c27aa454288a03fcd6fc30163
    var userProxy = bridge.getUserServiceProxy();
    var postResultUserCreate = userProxy.sysCreateUserEmailPassword(emailAddress, passWord, emailAddress.split('@').shift(), "d-a2160e8c27aa454288a03fcd6fc30163");
    if (postResultUserCreate.status == 200) {
    userProfileIdInCurrentApp = postResultUserCreate.data.profileId;

    var userSession = bridge.getSessionForProfile(userProfileIdInCurrentApp);

    bridge.logDebug("userSession", userSession.toString());


    var entitiesCount = 0;
    if (userEntitiesArry.length > 0){
    var entityProxy = bridge.getEntityServiceProxy(userSession);
    userEntitiesArry.forEach(entity=>{
    var postResultCE = entityProxy.createEntity(entity.entityType, entity.data, entity.acl);
    if (postResultCE.status == 200) entitiesCount++;
    });
    }

    bridge.logDebug("entitiesCount:"+entitiesCount, entitiesCount);
    response.entitiesCreatedCount = entitiesCount;
    }
    }

    // key flag, using status 199 to force auth retry
    response.status = 199;
    return response;
    }

    main();

  • (The intake parameters as you can see, several parameters are taken from authenticating API --"externalId", "authenticationToken", "authenticationType", and "externalAuthType" --, and several parameters are taken from hooked parms, will show at next step)

  • Hook the script you created from the above step to authenticate API via brainCloud console API Hooks page.

    (Note that appId is your source app, put your custom server name that you created in your source code from the above step to serverName and custom server secret to serverSecret fields respectively, also the script name for transferring user entities and validating credentials accordingly to scriptInSourceApp fields)

  • From brainCloud console Web Services Whitelist page, create a service name it with bcs2sdispatcher, and assign brainCloud S2S dispatcher URL to it.
    The proper URL is https://sharedprod.braincloudservers.com/s2sdispatcher

  • As an option, set the SendGrid configuration for your target app and set up a notification template in your SendGrid console, so end-users can get an email notification once they are created. (Note that to replace the email template id in the script above with your email template id for sysCreateUserEmailPassword method)

Test

  • You have completed all the configurations in both source and target apps, you can test it via brainCloud console API Explorer or in your client target app. You can set authentication parameter forceCreate as false to check an end-user who doesn't exist in your target app but excites in your source app. You can see the user successfully logged in, even forceCreate is set as false.

  • Also, check user entities for this new user from the Monitoring | User Monitoring | User Entities page, you will find user entities are transferred there from the source app too.

  • Test putting a wrong credential (not existing in your source app) to authenticate an end-user, as expected you are blocked from login.

Did this answer your question?