Considerations when implementing JSSP projects

General JavaScript programming considerations

  • The JSSP uses the Microsoft JSSP engine for JavaScript execution. See About the JSSP Engine for details about the engine.
  • You can use awaits, async, and promises in your code. See the sample projects for examples of using these executions in JavaScript.
  • You cannot use any polyfills that target Node.JS. For example, use fetch and not node-fetch. If you don't have access to a polyfill, you can try to create one from a node.js package using a tool like browserify, but this requires extensive testing to ensure JSSP supports the types of browser calls.
  • You will not be able to directly use Node.js modules since the JSSP engine does not run on the V8 engine. If you need to use a Node.js module, see if you can use a polyfill approach or use browserify to bundle dependencies.
  • JSSP uses XMLHttpRequest (XHR) Requests to interact with third-party APIs. See Working with XHR Requests for details about using XHR
  • Avoid doing computationally expensive work in your JS code. By default there is a 5-second timeout for JS script computation. Essentially, you need to limit any computation done in your JS code for any one service method operation to less than 5 seconds. Note that the timeout excludes the intervals to send and receive XHR requests, and therefore also excludes long-running operations such as file uploads/downloads.
  • Do not use namespaced values for properties and methods; this results in errors when associating generated SmartObjects.
    Copy

    Example of flatting a Twitter response by using dots for the payload and concatenating likes

    function onExecuteSomeMethod(properties) {
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 3 || xhr.status !== 200) return;
            const data = JSON.parse(xhr.responseText);
            let likes = 0;
            for (var i = 0; i & lt; response.data.posts.length; i++) {
                likes += data.posts[i].likes;
            }
            postResult({
                'fullName': data.name.first + response.data.name.second,
                'firstName': data.name.first,
                'lastName': data.name.last,
                'address1': data.address.lines[0],
                'address2': data.address.lines[1],
                'addressCity': data.address.city,
                'country': data.address.country,
                'zip': data.address.zip,
                'contact': data.email || response.data.twitter,
                likes
            });
        }
        xhr.open("GET", "https://example.com/users" + encodeURIComponent(properties["username"]));
        xhr.send();
    }
  • Console output methods only work if you pass in a parameter. For example, console.count() won't write anything to console output, but console.count(foo) will if foo is previously defined. This applies to console methods such as console.count(), console.profile(), console.time(), console.assert(false), .console.error(), and console.exception(). See Troubleshooting and Debugging for more information on testing and console logging with JSSP.
  • The PostResult function accepts arrays, so you can use object array mapping as demonstrated in this component: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map and in the sample projects. You do not have to use for loops for list items.
  • For protection, you should always encode any parameters that are passed to your SmartObject methods using the encodeURIComponent method. For example, in the following snippet, encoding is done only on the parameter that is passed to the method. You might be tempted to encode the entire component string, but that can result in badly-formed requests. If you think you have a bad request, log a message to the console that includes your URL, and then use that URL in a tool like Postman to troubleshoot what might be happening.
    Copy

    Using the encodeURIComponent method

    let component = "?$filter=startswith(displayName, '" + encodeURIComponent(parameters[TeamDisplayNameStartsWith]) + "')&$select=id,displayName,resourceProvisioningOptions";
    var url = baseUriEndpoint + "/groups" + component;

Working with XMLHTTP (XHR) Requests

XMLHttpRequest is how you make internet outbound requests to interact with 3rd party APIs. The complete JS XMLHttpRequest specification is located at XMLHttpRequest standard. Review the bullets below for specific considerations and notes about the use of XHR in JSSP.

  • You can only make XHR request to the outbound internet and cannot access services on the local network
  • No timeout setting is currently available for the XHR request
  • You should not use synchronous i/o on your XHR requests. JSSP does not support this
  • new XMLHttpRequest() is the constructor
  • readyState: see https://xhr.spec.whatwg.org/#states
  • error: Allows you to get the error that has been thrown during an XHR request
  • open(method, url):
    • Opens the XHR request that must point to the outbound internet.
    • Supported methods: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
    • Unsupported methods: CONNECT, TRACE, TRACK
    • Async argument is assumed to always be true
    • Username and password parameters for the Open() overload are not implemented. You can use the Static Authentication mode for the Service Instance and the product will pass the username and password specified in the Service Instance configuration.
    • Not handling the case where a fetch can be ongoing
  • send(body):
    • Sends the request through the network calling the 3rd party API.
    • extractedContentType is not implemented currently. Content-Type can be set via a header
    • Synchronous flag is not completely implemented, as the async operations per spec requires tracking every time a chunk is read from the response stream and updating counters accordingly. Currently it behaves more like the "synchronous flag" operation, but handled error handling and callbacks in the async pattern
    • Timeout flag is not implemented
    • Load/Loadend events with transmitted and length parameters are not set
  • setrequestheader():
    • Adds a header to the request
    • Forbidden headers: ACCEPT-CHARSET, ACCEPT-ENCODING, ACCESS-CONTROL-REQUEST-HEADERS, ACCESS-CONTROL-REQUEST-METHOD, CONNECTION, CONTENT-LENGTH, COOKIE, COOKIE2, DATE, DNT, EXPECT, HOST, KEEP-ALIVE, ORIGIN, REFERER, TE, TRAILER, TRANSFER-ENCODING, UPGRADE, VIA
    • Not handling combining Name/Value in the author request headers
  • withCredentials
    • Boolean attribute indicating that the request should use either Basic or OAuth authorization. If set to true, the default Authorization header will be passed-through from the product out to the 3rd party service via the header that is set on the Service Instance configuration. By default the Authorization Header Format is set to Bearer {0}, where {0} is the format specifier where the token will be added and Authorization Header Name is used to override which header is used for auth.
  • response
    • getResponseHeader(name): Gets a header that is contained in the response
    • status: The HTTP status code of the response.
    • responseText: The raw text returned from the response.
    • response: the content returned from the response. Can be a string, JSON or the BLOB content (used for File & Image).
    • responseType
      • If the response type is set to "json" it will parse the response as JSON.
      • If the response type is set to "text" it will be parsed as a string.
      • .If the response type is set to "blob" the content will be treated as a blob.
      • Does not check for xml MIME type, see point 3 in the "text" response section of https://xhr.spec.whatwg.org/#response-body
      • Does not do decoding on received bytes for different text encoding types, see points 4 and 5 in https://xhr.spec.whatwg.org/#response-body.
      • As we don't have the possibility of the current global object being a window, these checks are not implemented, see point 1 and 3 in https://xhr.spec.whatwg.org/#response-body
    • responseURL; The final URL of the response including redirects
  • events
    • onreadystatechange: An event callback used whenever the readyState attribute changes.
    • onerror: An event callback when an error on the network occurs.
  • FormData: The FormData object is used for File upload scenarios within JSSP
    • Supported methods inlucde new FormData(), append(name, value) and append(name, value, filename)

Data Types considerations

  • Review the list of Data Types to see what data types are supported by Service Objects. You cannot define custom data types in the Service Object definition.
  • Working with Number types
    • JS doesn't support int64 (only doubles), but JSSP will parse a 64-bit integer string to a int64 to pass back to the product.
    • NaN and Infinity return 0 for number data types. Undefined returns null.
  • Working with Dates:
    • When you need to handle dates in your JavaScript, make sure you understand how the Date object in JavaScript works and how it may differ from C# and product dates. If you want to test how a date is handled, open the developer tools in your browser (F12) and go to the console. In the console you can type in date functions with various strings to see how JavaScript is handling a particular date or some date-based math functions.
    • Dates that return Not A Number (NAN) in JavaScript are handled as NULL values in the product. Keep this in mind if you see null dates where you expect to see a date value.
    • Date types assume the culture of the server where the JSSP Service is running.
    • You must pass back a Date() object on postResult, or a double that represents the time, such as Date.parse("").
    • Using 0001-01-01 in a timezone that is negative will cause an exception.
    • Use a Date object in your JavaScript code. Do not try to manage date parsing and handling on your own.

About the JSSP Engine

The JSSP uses a JavaScript execution engine, which runs as a service on the environment.

The engine for the JSSP includes a small subset of what JavaScript developers can use in the context of a web browser, so keep that in mind as you're planning your JS broker projects.

The engine has the ability to use XHR to make web requests, communicate with the SmartObject APIs, use cached OAuth tokens, and console log methods. If you try to include dependencies that rely on other common, browser-based functionality such as local disk, temporary storage, or network access, those methods will fail.

The JavaScript engine used in the product does not have the same functionality as a JavaScript engine in a web browser. You will not be able to directly use Node.js modules. If you need to use a Node.js module, see if you can use a polyfill approach or use browserify to bundle dependencies.