Documentation

Catching Responses

When you send API requests to Scatter it will send you back responses including a unique ID that you specify with the response. This allows you to resolve those responses later in a promise/future way.

There's three response types you need to handle in your application.

  • paired ( no id ) - an ACK response for the pair request.
  • rekey ( no id ) - This response is issued when either the appkey, nonce or nextNonce are no longer valid, because the user has removed link permissions or because your app's local storage of them is wiped.
  • api ( returns id ) - These are responses to API requests you have made to Scatter.
For the routes with no ID you will need to handle the promise/future on your own in a singleton manner. However you will only get one of that type at once which makes this easy. We will talk about resolving these requests later.
socket.onmessage = msg => {
    // Disregarding Handshaking/Upgrading
    if(msg.data.indexOf('42/scatter') === -1) return false;

    const [type, data] = JSON.parse(msg.data.replace('42/scatter,', ''));

    switch(type){
        case 'paired': return handlePairedResponse(data);
        case 'rekey':  return handleRekeyResponse();
        case 'api':    return handleApiResponse(data);
    }
}

Breaking it down
  • First we are disregarding everything which isn't an EVENT ( type code 42 ) by saying "any message without 42/scatter" in it should be disregarded.
  • Then we are turning the EVENT string into JSON by stripping the 42/scatter, from the string ( notice the comma ), and turning it into a JSON array. In JavaScript if you do const [a, b] = [1,2]; it simply gives you variables of the array indexes incrementally. So in our case we're just making variables out of [type, data].
  • Lastly we're just switch casing the type variable and pushing those requests into other function to keep our code clean. Notice that only paired and api are passing in the data object, since rekey doesn't need it.


The Paired Response

This continues the last section about pairing, and allows you to do some logic on your end to finish the pairing process and store the correct values in your application only when the request is successful.

function handlePairedResponse(result){
    // You should have a state variable on
    // your socket class that knows the "paired" state.
    paired = result;

    if(paired) {
        const savedKey = getStoredHashedAppkey();
        const hashed = appkey.indexOf('appkey:') > -1 ? sha256(appkey) : appkey;

        if (!savedKey || savedKey !== hashed) {
            setStoredHashedAppkey(hashed);
            appkey = hashed;
        }
    }
}

Breaking it down
  • paired = result; - paired should be a state variable either on your socket connection class or global to your application as this isn't the only place we will be referencing it. result is just a boolean which tells you whether the user has accepted the pairing or not.
  • Since this method fires when you send a pair request, and also when you send a rekey response we can't trust that the value inside of our state variable appkey matches what we have saved in our local persistent storage. It's possible that we are in the process of rekeying and therefor changing that key. Because of this we get a reference to our old saved key if it exists.
  • To handle both cases of pair/rekey we need to check if the appkey we currently have on state has appkey: in it. ( remember this prefix must be sent along with all clear text appkeys ). If the appkey does have appkey: then we sha256 that value. If not we simply use the appkey we have in state, as it means we aren't in the process of rekeying.
  • The if (!savedKey || savedKey !== hashed) - We really only need to re-save the appkey if it's changed. Don't waste disk IO on unchanged variables.


The Rekey Response

This is more of an event than a response, but it's best to handle it here as well. We talked about this a little towards the end of the last section Pairing with Scatter. Here's how we actually handle rekeying.

function handleRekeyResponse(){
    appkey = 'appkey:'+random();

    const json = {
        plugin:'YOUR_APP',
        data:{
            origin:'YOUR_APP',
            appkey
        }
    };

    socket.send('42/scatter,' + JSON.stringify(['rekeyed', json]);
}

You'll notice this is very similar to the initial pair request that we sent initially. In fact there's only three differences.
  • We are only setting the appkey variable here with a new random appkey, and never initializing it from persisted state.
  • Inside of the data object we don't specify passthrough
  • the event type for this is not pair, instead it's rekeyed.
This method eventually resolves to the paired response we handled above.



The API Response

This is the handler that will do the bulk of our interaction with Scatter. The only thing we're really doing here is resolving promises that we inserted into the openRequests array.

function handleApiResponse(response){
    const openRequest = openRequests.find(x => x.id === response.id);
    if(!openRequest) return;

    openRequests = openRequests.filter(x => x.id !== response.id);

    const isErrorResponse = typeof response.result === 'object'
        && response.result !== null
        && response.result.hasOwnProperty('isError');

    if(isErrorResponse) openRequest.reject(response.result);
    else openRequest.resolve(response.result);
}

Breaking it down
  • This method does not ACK itself back to Scatter with any socket.send response. The entire purpose of it is just to return results to our application, it shouldn't be handling any logic.
  • const openRequest = ... - We're trying to find any open promises we have previously stored so that we can resolve them now that we have their responses.
  • if(!openRequest) return; - If we can't find a Promise we're simply going to stop handling this request as there is nothing to return the result to.
  • openRequests = openRequest.filter(x => x.id !== result.id); - We're just removing the cached Promise to free up memory as it's consumed.
  • const isErrorResponse = ... - Sometimes you'll get errors back from Scatter. To make sure a response is an error it has three predicates. Errors must be an object ( remember we turned the response from a JSON string into an object before ), the result must not be null as null is an actual return value and lastly the response.result must have a property named isError
  • resolve/reject - In the case of JavaScript promises we have two ways to return results. There's resolve and reject. You can think of this like a try/catch where reject is the catch which forces an error result.