User Search Event Handler

Author: Ian Ward
Language: JavaScript
Date: September 1, 2017

This sample app demonstrates how to stream a subset of data from a public Realm file to a single user’s private Realm.

This is useful in scenarios where the public Realm is very large, and the user is only interested in isolating certain records from it.

It wouldn’t make sense to download the entire public Realm to the device, so this mechanism mitigates this by copying the records that the user specifies.

How to try it out:

  1. Create text files named admin_token.base64 and access-token.enterprise, containing your ROS instance’s admin key, and your Enterprise/Professional token respectively into the to EventListener folder.
  2. Copy the Professional edition NPM package to the folder and update the filename in package.json if needed (Currently set as realm-1.4.3-professional.tgz).
  3. Run npm install to get the needed modules.
  4. Start the event handler by running node usersearch.js.

You can run the included UserSearch app to test it out. You will need to update the credentials in the file ViewController.swift to match a user existing on the server.

Using an _admin Realm

This code is similar but shows how to search the __admin realm which contains all of the User’s emails for instance. Editing the __admin realm could cause your ROS to have a critical failure but opening the __admin realm and just reading from it is fine.

How to try it out:

  1. Copy admin_token.base64 and access-token.enterprise to folder
  2. Copy the PE node.js binding package to the folder and update the filename in package.json if needed.
  3. Run npm install to get the needed modules.
  4. Start the event handler by running node usersearch.js.

You can run the included UserSearch app to test it out. You will need to update the credentials in the file ViewController.swift to match a user existing on the server.

const Realm = require('realm');
const fs    = require('fs');

const server_url        = 'realm://localhost:9080';
const REALM_ADMIN_TOKEN = fs.readFileSync('./admin_token.base64', 'utf-8');
const ENTERPRISE_TOKEN  = fs.readFileSync('./access-token.enterprise', 'utf-8');

Realm.Sync.setFeatureToken(ENTERPRISE_TOKEN); // needed to enable global listener functionality
Realm.Sync.setLogLevel('error');

const adminUser = Realm.Sync.User.adminUser(REALM_ADMIN_TOKEN);

// -------------------

function onAdmin(changeEvent) {
    console.log("admin available");

    const adminRealm = changeEvent.realm;

    function handleChange(changeEvent) {
        console.log(changeEvent.path);

        const changes = changeEvent.changes["UserSearch"];

        // no reason to do any work if there are no requests
        if (typeof changes === "undefined")
            return;
        if (changes.insertions.length == 0 && changes.modifications.length == 0)
            return;

        const searchRealm = changeEvent.realm; // workaround for GN returning new instance on every access
        const requests    = searchRealm.objects("UserSearch");
        const accounts    = adminRealm.objects('Account');

        // Find users matching the request
        searchRealm.write(() => {
            // handle new requests
            changes.insertions.forEach((index) => {
                const obj = requests[index];

                if (obj.pattern != "") {
                    const matches = accounts.filtered("provider_id CONTAINS[c] $0", obj.pattern);

                    matches.forEach((match) => {
                        obj.users.push({userid: match.user.id, email: match.provider_id});
                    });
                }
                obj.resultPattern = obj.pattern;
            });

            // live update result if request is modified
            changes.modifications.forEach((index) => {
                const obj = requests[index];

                if (obj.pattern == "") {
                    searchRealm.delete(obj.users);
                }
                else {
                    const matches = accounts.filtered("provider_id CONTAINS[c] $0", obj.pattern);

                    // remove users that are no longer matching
                    const toDelete = [];
                    obj.users.forEach((profile) => {
                        if (matches.filtered("user.id == $0", profile.userid).length == 0) {
                            toDelete.push(profile);
                        }
                    });
                    searchRealm.delete(toDelete);

                    // add new matches
                    matches.forEach((match) => {
                        if (obj.users.filtered("userid == $0", match.user.id).length == 0) {
                            obj.users.push({userid: match.user.id, email: match.provider_id});
                        }
                    });
                }
                obj.resultPattern = obj.pattern;
            });
        });
    }
    Realm.Sync.addListener(server_url, adminUser, "^/.*?/usersearch$", 'change', handleChange);

    // now that we have the admin realm, we don't need the listener anymore
    Realm.Sync.removeListener("^/__admin$", 'change', onAdmin)
}

Realm.Sync.addListener(server_url, adminUser, "^/__admin$", 'change', onAdmin);

console.log('listening');