Skip to content

Best practices for building your own live paste bin

Alex Vazquez edited this page Aug 5, 2013 · 5 revisions

This is a working document, and just the start. I'd like to see contribution from jsfiddle, codepen, dabblet and the like too as I think it'll help others, and probably help harden our own applications (I'd also be open to moving this doc out of jsbin's wiki in favour of "neutral land").

If you have a question, add it to the bottom of the wiki and I'll try to answer (or get an answer).

For now, here's a brain dump of items I'd like to flesh out (and more to come):

XSS from dynamically generated iframe to top level application

  • jsfiddle get around this by having the server deliver their iframe.
  • jsbin now has a "proxy runner" on a different domain (run.jsbin.com) that accepts postMessage commands. The inner frame generates the dynamic iframe and allows only a specific subset of commands to run. It also uses the sandbox attribute with all values set excet the allow-top-navigation to prevent navigating jsbin.com
  • CodePen's first line of defense is serving the IFrame on another sub domain (http://s.codepen.io). The second line of defense is the sandbox attribute which prevents access to the top level window. We give the sandbox attribute the following value 'allow-scripts allow-same-origin allow-pointer-lock'. We're also starting to implement a content security policy for CodePen. It's possibly the only way to ensure XSS attacks are stopped (at least for modern browsers).
  • Dabblet uses the Cross-Window messaging API and a different subdomain

Live dynamic generated iframe needs blocking code removed

Remove the following from the JavaScript:

  • alert
  • prompt
  • confirm
  • print
  • open

jsbin tops and tails the user's code by setting these functions to noop and then once the document has rendered, it restores them.

This allows alerts to run during run-time of the iframe, but not during live rendering, which allows the user to keep typing.

Dabblet requires the user to click a button or use a keyboard shortcut to run the JS (HTML & CSS update automatically) so this is not needed.

CodePen removes those same Javascript functions. We also detect if the sandbox attribute is supported. We'll err on breaking a working example if a visitor is using an old browser. If the sandbox attribute is not supported we strip the following values from Javascript.

  • .location
  • top.
  • location.replace
  • .submit
  • fromCharCode

Dealing with run-away code

By run-away code, this could mean infinite loops, recursion or intense processing inside of setInterval or requestAnimationFrame.

  • jsbin has code to (try to) prevent infinite loops occurring in the live render. It rewrites for, while and do loops with a counter check. If the counter hits 100,000, then it exits the loop sending a warning on the console (to be added to the editor UI). Obviously not perfect, but more people are affected by accidentally while loops while typing that you'd think! Note that recursive functions are not protected.
  • codepen has a kill on setInterval and rAF requests on the homepage (RS: unsure if codepen also has this on the full pens). After a few seconds, all callbacks to setInterval and rAF are noop'ed (which is why you'll see animations halt on their homepage).

More can be done to protect the editor from hanging.

Keep focus on editor during live rendering

This is a snippet used where win is the iframe.contentWindow. If the window receives focus that isn't from the mouse (i.e. because of an autofocusing element or JavaScript), then the window cancels the event. This is important as it leaves the user's focus on the editor as they're coding and allows the iframe to keep refreshing in real-time.

However, if the user clicks on the iframe, it should focus - so we listen for mousedown, and if that has fired first, we let the focus happen.

var click = false;
win.onmousedown = function () {
  click = true;
  setTimeout(function () {
    click = false;
  }, 10);
};
win.onfocus = function (event) {
  // allow the iframe to be clicked to create a fake focus
  if (click) {
    $('#live').focus();
    // also close any open dropdowns
    closedropdown();
  }
  return false;
};

Powerful editor

jsbin, codepen and jsfiddle use CodeMirror.

I've seen Ace being used (but forget where). Personally, I don't use Ace in jsbin because I want to support older versions of IE.

Dabblet's Lea Verou wrote her own editor, which gives Dabblet a nice different feel from the others, but also allows it to have smart inline previewers (like hovering over gradients shows the visual value).

Don't forget your mobile users

For jsbin, I disable a lot of CodeMirror's functionality when the user visits using a mobile device (UA sniffed - which is bad I know).

I'm not sure this is a best practise, but I've seen real users wanting to use jsbin with their iPad. It's something you should consider.

CodeMirror has/had an iOS issue where if you create an editor from a <textarea> and that textarea had border: 0; it will prevent the keyboard from opening, so watch out for that.

Reporting abuse

Abuse will occur as some point in a paste bin with a rendered output. It's a matter of time. Although I'd love to say the "community" will come to your rescue, it'll likely be some authority that asks to shutdown your service (or hopefully just those urls).

Give your users a way to report this abuse (even if your users end up being the Police).

jsbin had a report abuse button, but users kept mistaking this (somehow) for a register button(!!). So it's now just "send feedback" in the help menu.

CodePen: We include a "Report Abuse" link in the footer of all Full Page previews. It's very important that you IFrame the full page view and service it on another domain like in the editor. If a malicious user shares the full page URL they'll be able to steal the visitor's session on your domain.

How to embed in blogs

Obviously via an iframe - but iframes have issues in sizing correctly under narrow viewports. For an example of this, see this example of jsbin in my blog on a mobile phone. The viewport is set to width=device-width but the iframe forces the width of the page to 600px+.

I've yet to find a solution to this (perhaps jsfiddle have solved this?).

CodePen: We have this issue as well. (screenshot) (example). We also double-iframe here as the final result is iframed within the embed iframe - which causes scrolling issues. Working on it!

Escaping user content for your database

To make sure quotes and slashes are saved and restored correctly.

URL schema

Think long and hard about your url schema before you release. jsbin is in the odd position where usernames appear in the same place as bin urls because they weren't considered up front.

Keyboard shortcuts

Make use of these. You're building an app, so give it first class shortcuts, in particular ctrl/cmd+s for saving.

However, it's bad practise to override native browser functionality like new tab, closing tabs, etc.

jsbin has a ban list based on Rodney Rehn's blog post on reclaiming your keyboard shortcuts. Since jsbin uses CodeMirror, it nukes those untouchable keyboard shortcuts with the following:

delete CodeMirror.keyMap['default']['Cmd-L'];
delete CodeMirror.keyMap['default']['Cmd-T'];
delete CodeMirror.keyMap['default']['Cmd-W'];
delete CodeMirror.keyMap['default']['Cmd-J'];
delete CodeMirror.keyMap['default']['Cmd-R'];

CodePen: we can attest to not wanting to override native browser keyboard shortcuts. We originally had Cmd-1,2,3 do special editor expanding things, which overwrote tab switching, which was not appreciated. Because these apps are so editor-focused, we're in a postion where we pretty much have to use modifier keys. Option (by itself) isn't a good choice because it is used to create special characters. Cmd has a lot of "taken" keys. Cmd+Shift is good, but still has some taken keys (e.g. 3&4 do screenshots on Macs).

Dabblet overrides Cmd+1,2,3 etc, but in a smart way: When you’re already on that tab, they trigger the default browser functionality. Best of both worlds.

Log in method

Consider your audience. It's likely they're programmers, so github auth seems like a very sensible idea (dabblet, codepen and jsbin make use of this).

For jsbin it is important that github (or twitter) wasn't a prerequisite to allow for school students to easily log in.

Developer questions

Should the iframe get destroyed every time?

Dabblet: reapplies the HTML each time (which could cause artefacts, but is rare) source

jsbin: creates a second iframe under the existing one, and once rendered, removes the first iframe (like a buffering of iframes). The reason the iframe is destroyed is to ensure the JavaScript is removed from memory - ie. a while loop could be killing the browser.

codepen: we do the same as jsbin. recreating the iframe every time also makes the onload functions call reliable while a user is editing their Pen.