Marty Zigman

Conversations with Marty Zigman

Certified Administrator • ERP • SuiteCloud

How To: Code Pattern for NetSuite Driven JSONP Cross Domain JavaScript

NetSuite Technical

Tags: , , , , ,

This article is relevant if you are programming NetSuite and you need to output JSON data to a client but you are having security problems due to the “Same Orgin Policy” (SOP).

Background

Inspired by work that Joe Son has done to show off  JSONP in NetSuite, I thought it would be good to break it down into simpler terms to aid understanding of the process.  In addition, there are many people developing web sites independent of NetSuite and yet need data that resides in the NetSuite platform.

This article provides the basic plumbing and includes a working script so that you can build your own code template that should work on any web site (Magento, WordPress, Drupal, Joomla, or plain HTML).

By the way, Joe Son runs the “Knowledge Share Care” web site.  He and I have many conversations and we enjoy helping each other master the NetSuite development environment.

Cross-site Scripting Security Challenges

In order to produce a more secure web browsing experience, all web browsers have a mechanism to prevent foreign servers from injecting JavaScript into the user environment.  Often referenced as XSS or SOP, you can learn more about it on WikiPedia.

However, many advanced sites that offer application programming interfaces (API) want to expose data and logic capacities to third parties.  The browser’s security context prevented full access to these third party sites.  As a result, a concept called JSONP (JSON with Padding) was developed that exploits an open area of the browser for third party scripts.  You can learn more about JSONP on the WikiPedia.

Sample JSONP Code for NetSuite Suitelets

You are welcome to download the HTML file: suitelet-jsonp-example which contains a working JSONP example.  We developed this against a development NetSuite account.  Let me know if it stops working.

Here is the code in view format.

<!DOCTYPE html>
<html>
<head>
<body>
<a href="https://www.prolecto.com/services/innovations/">
<img src="https://www.prolecto.com/assets/media-kit/prolecto-logo--color.png" border="0"></a>
<h1>Sample NetSuite Client Side JSONP created by  <a href="https://www.prolecto.com/about-us/marty-zigman/"> Marty Zigman</a> @ Prolecto Resources, Inc.</h1>
<a href="https://www.prolecto.com/services/innovations/">
<img src="https://www.prolecto.com/assets/media-kit/prolecto-logo--white.png" border="0"></a>
<h1><a href="https://www.prolecto.com/services/innovations/">Contact Us</a> if you want help developing your specialized services.</h1>
View HTML Source for Script Code

<script>

// typical NetSuite Suitelet URL that is available publicly (anonymous) via your deployment
var u = "https://forms.na1.netsuite.com/app/site/hosting/scriptlet.nl?script=122&deploy=1&compid=TSTDRV1030358&h=44d4e730edd613155036";

// name of the call back function that drives the JSONP mechanism; this string must be the same
// as your function that will ultimately process your request.
cb = 'myCallBack'

//here is the function that finally gets the data and where you do the work you really care about
function myCallBack(data){
	// place your code here to do the work

	// sample processing debug statements
	var s = 'retrieved this data from suitelet: ';
	console.log(s + data);
	alert (s + data);
}

//This function creates a script in the browser environment
//by making our Suitelet look like something we would call in a script source tag.
//It calls our SuiteLet and which returns the data.  When the data returns, it must
//come back as JavaScript so it can be further processed via our callback.  See the sample
//server side Suitelet code to understand the formatting
function getJSONP(url) {

	// created by Marty Zigman @ Prolecto Resources, Inc.
    var script = document.createElement('script');	// create a DOM script element

    // find the head portion of the document so we can attach our script
    var head = document.getElementsByTagName('head')[0] || document.documentElement;

    // add provide the URL to the dynamic script tag
    script.src = url

    //add the script to the head tag which will then fire it
    head.appendChild(script);
}

// Now that the client side functions are setup, we need to call our function to do the work
// Our Server Side SuiteLet is going to expect a parameter called "callback" to know to treat the call as "JSONP".
// we must format the url to have the call back parameter
getJSONP(u + '&callback=' + cb);
</script>

<!--
//this code is here for convenience and does NOT run in the browser. It is SuiteLet code that runs on the
//NetSuite server.  It should NOT be executing on the client.  The assumption is that you know how to get
//create Suitelets and deploy them on NetSuite

function getLastPrice(request, response){

	//this is the business function that gets the data;  your Suitelet needs to do work to return back JSON
	function getPrice(){
		try {
		      var price = new btcPrice();
		      price.getLast();
		      return price.toJSON();

		  } catch (e) {
		      if (e instanceof nlobjError) {
		          nlapiLogExecution('ERROR', 'Error Result', e.getCode() + " : " + e.getDetails());
		          return {'result':CONST_RESULT_ERROR, 'error': e.getDetails()};
		      } else {
		          nlapiLogExecution('ERROR', 'Error Result', 'Unexpected  error : ' + e.toString());
		          return {'result':CONST_RESULT_ERROR, 'error': e.getDetails()};
		      };
		  };
	};

	var result = getPrice();
	nlapiLogExecution('DEBUG', 'result getLastPrice', JSON.stringify(result));

	// we now have the data.  But we are not sure how to return it (JSON or JSONP).

	// see if this is a JSONP request for Same Origin Policy by supporting
	// a parameter from the client called "callback".  If it is there, then they
	// want JSONP

	var callBackFuncStr = request.getParameter('callback');

	// we found the parameter, they want JSONP
	if (callBackFuncStr != null){

		//simply wrap the data into a javascript function call that will execute on the client
		//as soon as it is returned; remember, your client side function has to be there named
		//appropriate to receive the result
		var strJson = callBackFuncStr+'(\''+JSON.stringify(result)+'\');';
		nlapiLogExecution('DEBUG', 'JSONP Offer', strJson);
		response.setContentType('JAVASCRIPT')
		response.write(strJson);

	} else {

		//basic JSON request; works great in server-to-server implementations

		//return basic JSON without concern for Same Orgin Policy
		response.setContentType('JAVASCRIPT')
		response.write(JSON.stringify(result));
	};

-->

</body>
</html>

GET vs. POST Considerations

You may notice that the example provided is not doing a POST. JSONP does not readily support making POST events. While there are some new capacities opening for Cross Origin Resource Sharing, they are not supported on the NetSuite platform. This means that you are limited to GET calls which effectively are putting parameters on the URL. Remember there are limitations to the amount of information you can put on the URL.

If you use the popular JQuery tool to produce a $.post() call, JQuery will behind the scenes change the POST to a GET. While there is some debate, readers may want to read more information here.

JQuery Syntax for NetSuite JSONP

While the code example above is in native JavaScript, many readers are likely using JQuery to create their client side HTML pages to reference a NetSuite JSONP Suitelet.  Remember  that JSONP only works with a HTTP GET.   If you want to send data to the server, craft it as JSON.  Use the following syntax:

//produce the JSON payload; JQuery will automatically add it to the URL for a JSONP call.
var data = {id: '24567', user: '23455'};

//note JQuery wants to see "callback=?" at the end of the URL; it tells the .getJSON call that this JSON request is of type JSONP
var url = 'https://forms.netsuite.com/app/site/hosting/scriptlet.nl?script=23&deploy=1&compid=434534&h=88603ee219c4dc2d8b56&callback=?';

//finally, the call. For convenience, we sent the data to our custom myCallBack function -- yet not mandatory in this implementation; the call back is in
// already referenced via the .done() method. Other JQuery capacities are can take care of failed calls
$.getJSON(url, data)
    .done(function(data){
        myCallBack(data)
        })

Conclusion

If you are ready to unlock the potential in your NetSuite platform, contact us.  We love to innovate on the platform.

 

Marty Zigman LinkedIn

Marty Zigman

Holding three official certifications, Marty is widely recognized as a top NetSuite expert and leads a team of senior professionals at Prolecto Resources, Inc. A former Deloitte & Touche CPA and technology executive with CTO roles, he brings over 35 years of leadership in ERP, CRM, and eCommerce business systems. Contact Marty to engage directly.

BiographyYouTubeLinkedInX (Twitter)

24 thoughts on “How To: Code Pattern for NetSuite Driven JSONP Cross Domain JavaScript

  1. The only problem I have with JSONP and the use of Suitelets is the fact that your Suitelet has to be available anonymously. If they allowed for RESTlets to be used cross domain that would open up a whole array of uses.

    Reply
  2. Negative. CORS is not allowed.

    Access-Control-Allow-Origin: none

    I believe. I have brought it up in the last two roadmap sessions to Elham at SuiteWorld.

    I was pursuing AJAX calls from mobile apps for a while, where having your credentials in the code woutldn’t be a problem as it was all compiled into native using Phonegap build.

    That was just for yucks, though. I was really wanting to use them from either Portlets or Suitelets. Since forms. system. and rest. are different domains, you can’t. You can get away with using JSONP and a Suitelet as a work around. And it works great, I just don’t like the idea of security through obscurity.

    Reply
  3. I would love to be able to explore more with the SSP stuff. It kind of reminds me of Classic ASP with some MVC thrown in. I haven’t built anything yet using it, but I would like to, at some point.

    Reply
  4. Hi Marty,

    I have a question…

    I developed a Suitelet Form available externally, and used a RESTlet to get the information from the form to create a record.

    In order for the RESTlet to be intialize we must pass credentials, etc in the Suitelet… is this a security risk?

    Can the credentials some how be exposed?

    Thanks,
    David

    Reply
  5. Hi Marty,

    I must have confused you with the last post.

    My implementation works, I just wanted to know if there were any security risks.

    Here’s the logic behind the implementation:
    1. Create Suitelet Form
    2. The deployment of the Suitelet form will be available without login – accessible outside NetSuite through External URL
    3. Create RESTlet to create record based on Suitlet Form input
    3a. Suitlets available without login have limited API’s – RESTlet allows the use of nlapiCreateRecord
    4. Suitelet Form contains header information and credentials to be passed to RESTlet, as well as the JSON string data (form input)

    Are there any risks with the Suitelet form, which is available without login (avaible via external URL), containing the login credentials?

    Reply
  6. I’m really interested in using this method to get a user’s cart data from outside the netsuite.com domain. Possible?

    We’re running into an issue where users’ cart data isn’t available to suitescripts, but that seems crazy. Why wouldn’t you expose that data? Is there NO way to get cart data from outside netsuite.com? We’ve also tried sending the request through a proxy, but then it’s not possible to get the user’s cart ID to send as part of the request headers.

    Reply
  7. Is there a unique ID for shopping carts that we can access for use in an API call?

    When a user adds an item to the cart we would like to send the cart’s ID in the API call so we can track abandoned carts.

    Reply
  8. Hi Marty ,
    Thank you for you helpful articles. I have a question. I have an MVC application with Login using Netsuite Credentials (Suitetalk) and through the app i perform search, insert , and update. Now we need to open this application directly from netsuite customer page through a button. Once the MVC application is launched, it should not ask for the user credentials since they already logged into netsuite. How do I do this ? any help is greatly appreciated . Thanks

    Reply
  9. I have an integration with ShipStation where Shipstation makes a GET request to a Suitelet (ENDPOINT) and the Suitelet internally calls (with oAuth) a Restlet to get the data. This works fine.

    The issue is, Shipstation is supposed to send the response (the shipping details like Tracking number etc.) to the same ENDPOINT, as “POST”. It fails with “405 Method not allowed” error.

    I have checked the deployment, Available without login, Run as Administrator , Event Type (tested with alloptions: BLANK, GET request, POST request). No Success

    After readign this post, I added &callback=? at the end of the endpoint.

    Nothing works.
    Can you suggest anything ?

    Reply
  10. Manoj,

    I’m not sure if you’ve already found a solution to your issue, but I encountered that message when I setup my OAuth suitelet as well. The fix that worked for me was adding a header to my request for User-Agent:Mozilla/5.0

    Reply
  11. Michael: Yes I used the same. Actually, Shipstation doesn’t Allow to modify the header, so I ended up introducing an intermediate script which transforms the Response and request by adding this. So I am good for now. Thanks

    Reply
  12. Hello Marty,

    Can you share with me the code of this, I already sent you a message.
    I am a newbie and learning still these technologies.

    Reply
  13. Hey,

    I am running into trouble similar to the above article when trying to load a file in an iframe of a suitelet. I’m getting the error that “Blocked a frame with origin… from accessing a cross-origin frame”.
    Any ideas?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *