API Docs for: 0.50.0
Show:

File: src/Environment/Node.js

/*
    Copyright 2012-2013 Rustici Software

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
TinCan client library

@module TinCan
@submodule TinCan.Environment.Node
**/
(function () {
    /* globals require,Buffer,ArrayBuffer,Uint8Array */
    "use strict";
    var LOG_SRC = "Environment.Node",
        log = TinCan.prototype.log,
        querystring = require("querystring"),
        XMLHttpRequest = require("xhr2"),
        requestComplete,
        __createJSONSegment,
        __createAttachmentSegment;

    requestComplete = function (xhr, cfg) {
        log("requestComplete - xhr.status: " + xhr.status, LOG_SRC);
        log("requestComplete - xhr.responseText: " + xhr.responseText, LOG_SRC);
        var requestCompleteResult,
            httpStatus = xhr.status,
            notFoundOk = (cfg.ignore404 && httpStatus === 404);

        if ((httpStatus >= 200 && httpStatus < 400) || notFoundOk) {
            if (cfg.callback) {
                cfg.callback(null, xhr);
                return;
            }

            requestCompleteResult = {
                err: null,
                xhr: xhr
            };
            return requestCompleteResult;
        }

        requestCompleteResult = {
            err: httpStatus,
            xhr: xhr
        };
        if (httpStatus === 0) {
            log("[warning] There was a problem communicating with the Learning Record Store. Aborted, offline, or invalid CORS endpoint (" + httpStatus + ")", LOG_SRC);
        }
        else {
            log("[warning] There was a problem communicating with the Learning Record Store. (" + httpStatus + " | " + xhr.responseText+ ")", LOG_SRC);
        }
        if (cfg.callback) {
            cfg.callback(httpStatus, xhr);
        }
        return requestCompleteResult;
    };

    //
    // Override LRS' init method to set up our request handling
    // capabilities, basically empty implementation here so that
    // we don't get a no-env loaded message
    //
    TinCan.LRS.prototype._initByEnvironment = function () {};

    //
    // use XMLHttpRequest module instead of standard Node.js http/https
    // modules since we have to support both, and because the callbacks
    // provided via the methods calling _makeRequest expect the xhr to
    // have a certain interface, that interface happens to be the browser
    // version of XHR since that's where it started, so rather than
    // changing them to use a different wrapped request/response object
    // set just use a wrapped version of the node objects which is what
    // XMLHttpRequest module provides
    //
    TinCan.LRS.prototype._makeRequest = function (fullUrl, headers, cfg) {
        log("_makeRequest using http/https", LOG_SRC);
        var xhr,
            url = fullUrl,
            async = typeof cfg.callback !== "undefined",
            prop
        ;
        if (typeof cfg.params !== "undefined" && Object.keys(cfg.params).length > 0) {
            url += "?" + querystring.stringify(cfg.params);
        }

        xhr = new XMLHttpRequest();
        xhr.open(cfg.method, url, async);

        if (cfg.expectMultipart) {
            xhr.responseType = "arraybuffer";
        }

        for (prop in headers) {
            if (headers.hasOwnProperty(prop)) {
                xhr.setRequestHeader(prop, headers[prop]);
            }
        }

        if (typeof cfg.data !== "undefined") {
            cfg.data += "";
        }

        if (async) {
            xhr.onreadystatechange = function () {
                log("xhr.onreadystatechange - xhr.readyState: " + xhr.readyState, LOG_SRC);
                if (xhr.readyState === 4) {
                    requestComplete(xhr, cfg);
                }
            };
        }

        xhr.send(cfg.data);

        if (async) {
            return xhr;
        }

        return requestComplete(xhr, cfg);
    };

    //
    // Synchronos xhr handling is unsupported in node
    //
    TinCan.LRS.syncEnabled = false;

    TinCan.LRS.prototype._getMultipartRequestData = function (boundary, jsonContent, requestAttachments) {
        var parts = [],
            i;

        parts.push(
            __createJSONSegment(
                boundary,
                jsonContent
            )
        );
        for (i = 0; i < requestAttachments.length; i += 1) {
            if (requestAttachments[i].content !== null) {
                parts.push(
                    __createAttachmentSegment(
                        boundary,
                        requestAttachments[i].content,
                        requestAttachments[i].sha2,
                        requestAttachments[i].contentType
                    )
                );
            }
        }
        if (typeof Buffer.from === "undefined") {
            parts.push( new Buffer("\r\n--" + boundary + "--\r\n") );
        }
        else {
            parts.push( Buffer.from("\r\n--" + boundary + "--\r\n") );
        }

        return Buffer.concat(parts);
    };

    __createJSONSegment = function (boundary, jsonContent) {
        var content = [
                "--" + boundary,
                "Content-Type: application/json",
                "",
                JSON.stringify(jsonContent)
            ].join("\r\n");

        content += "\r\n";

        if (typeof Buffer.from === "undefined") {
            return new Buffer(content);
        }
        return Buffer.from(content);
    };

    __createAttachmentSegment = function (boundary, content, sha2, contentType) {
        var bufferParts = [],
            header = [
                "--" + boundary,
                "Content-Type: " + contentType,
                "Content-Transfer-Encoding: binary",
                "X-Experience-API-Hash: " + sha2
            ].join("\r\n");

        header += "\r\n\r\n";

        if (typeof Buffer.from === "undefined") {
            bufferParts.push( new Buffer(header) );
            bufferParts.push( new Buffer(content) );
        }
        else {
            bufferParts.push(Buffer.from(header));
            bufferParts.push(Buffer.from(content));
        }

        return Buffer.concat(bufferParts);
    };

    TinCan.Utils.stringToArrayBuffer = function (content, encoding) {
        var b,
            ab,
            view,
            i;

        if (! encoding) {
            encoding = TinCan.Utils.defaultEncoding;
        }

        if (typeof Buffer.from === "undefined") {
            // for Node.js prior to v4.x
            b = new Buffer(content, encoding);

            ab = new ArrayBuffer(b.length);
            view = new Uint8Array(ab);
            for (i = 0; i < b.length; i += 1) {
                view[i] = b[i];
            }

            return ab;
        }

        b = Buffer.from(content, encoding);
        ab = b.buffer;

        //
        // this .slice is required because of the internals of how Buffer is
        // implemented, it uses a shared ArrayBuffer underneath for small buffers
        // see http://stackoverflow.com/a/31394257/1464957
        //
        return ab.slice(b.byteOffset, b.byteOffset + b.byteLength);
    };

    TinCan.Utils.stringFromArrayBuffer = function (content, encoding) {
        var b,
            view,
            i;

        if (! encoding) {
            encoding = TinCan.Utils.defaultEncoding;
        }

        if (typeof Buffer.from === "undefined") {
            // for Node.js prior to v4.x
            b = new Buffer(content.byteLength);

            view = new Uint8Array(content);
            for (i = 0; i < b.length; i += 1) {
                b[i] = view[i];
            }
        }
        else {
            b = Buffer.from(content);
        }

        return b.toString(encoding);
    };
}());