\n\n▄████▄ ██▀███ ▓██ ██▓ ██▓███ ▄▄▄█████▓ ▄▄▄ ██▓ ██ ▄█▀ \n▒██▀ ▀█ ▓██ ▒ ██▒▒██ ██▒▓██░ ██▒▓ ██▒ ▓▒▒████▄ ▓██▒ ██▄█▒ \n▒▓█ ▄ ▓██ ░▄█ ▒ ▒██ ██░▓██░ ██▓▒▒ ▓██░ ▒░▒██ ▀█▄ ▒██░ ▓███▄░ \n▒▓▓▄ ▄██▒▒██▀▀█▄ ░ ▐██▓░▒██▄█▓▒ ▒░ ▓██▓ ░ ░██▄▄▄▄██ ▒██░ ▓██ █▄ \n▒ ▓███▀ ░░██▓ ▒██▒ ░ ██▒▓░▒██▒ ░ ░ ▒██▒ ░ ▓█ ▓██▒░██████▒▒██▒ █▄ \n░ ░▒ ▒ ░░ ▒▓ ░▒▓░ ██▒▒▒ ▒▓▒░ ░ ░ ▒ ░░ ▒▒ ▓▒█░░ ▒░▓ ░▒ ▒▒ ▓▒ \n ░ ▒ ░▒ ░ ▒░▓██ ░▒░ ░▒ ░ ░ ▒ ▒▒ ░░ ░ ▒ ░░ ░▒ ▒░ \n░ ░░ ░ ▒ ▒ ░░ ░░ ░ ░ ▒ ░ ░ ░ ░░ ░ \n░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ \n░ ░ ░ \n https://github.com/hexagon/cryptalk \n \n Tip of the day: /help \n----------------------------------------------------------------------",nick:{maxLen:20,minLen:2},key:{maxLen:1024,minLen:8},room:{minLen:1,maxLen:64},notifications:{maxOnePerMs:3e3}}),define("templates",{help:" \nCryptalk, encrypted instant chat. \n \n----------------------------------------------------------------------\t\n \nClient: \t\t\t\n\t/key\t\tStrongPassphrase\tSets encryption key \n\t/nick\t\tNickName\t\tSets an optional nick \n\t/mute \t\t\t\t\tAudio on\t\t\t\t\t\t\t\t\t\n\t/unmute \t\t\t\tAudio off\t\t\t\t\t\t\t\t\t\n\t/clear\t\t\t\t\tClear on-screen buffer \n\t/help\t\t\t\t\tThis \n\t/title\t\t\t\t\tSet your local page title\t\t\t\t\t\n\t/torch\t\tAfterSeconds\t\tConsole messages are torched \t\t\n\t\t\t\t\t\tafter this amount of seconds \t\t\t\t\t\n\t\t\t\t\t\t(default 600).\t\t\t\t\t\t\t\t\t\n \nRoom: \t\t\t\t\n\t/join\t\tRoomId\t\t\tJoin a room\t \n\t/leave\t\t\t\t\tLeave the room \n\t/count\t\t\t\t\tCount participants \n \nHost: \t\t \t\n\t/hosts\t\t\t\t\tList available hosts \t\t\n\t/connect\tHostIndex\t\tConnect to selected host \t\n\t/disconnect\t\t\t\tDisconnect from host \t\t\t \n \nYou can select any of the five last commands/messages with up/down key.\n \nDue to security reasons, /key command is not saved, and command \nhistory is automatically cleared after one minute of inactivity. \n \nIt is highly recommended to use incognito mode while chatting, \nto prevent browsers from keeping history or cache. \n \n----------------------------------------------------------------------\t\n
",default_nick:"Anonymous",post:{motd:'{text}',info:'[{timestamp}] INF> {text}',server:'[{timestamp}] SRV> {text}',error:'[{timestamp}] ERR> {text}',message:'[{timestamp}] MSG> {nick}> {text}'},messages:{key_to_short:"Hmm, that's a weak key, try again...",key_to_long:"Man that's a long key. Make it a tad short, 'kay?",key_ok:"Key set, you can now start communicating.",key_no_host:"You have to connect to a host before setting the key.",join_no_host:"You have to connect to a host before joining a room.",nick_to_short:"Nickname is too short, it has to be at least {nick_minLen} characters long. Try again.",nick_to_long:"Nickname is too long, it can be at most {nick_maxLen} characters long. Try again.",nick_set:"From now on, you're referred to as '{nick}'.",msg_no_room:"You have to join a room before sending messages. See /help.",not_in_room:"You have to be in a room to count participants...",msg_no_key:"You have to set an encryption key before sending a message. See /help.",leave_from_nowhere:"How are you supposed to leave, while being nowhere?",torch_is_now:"Messages are now torched after {ttl} seconds.",torch_not_set:"Invalid torch delay entered, nothing changed. See /help.",title_set:"The title of this window is now '{title}'.",muted:"Notifications and sounds are now muted.",unmuted:"Notifications and sounds are now on.",unrecognized_command:'Unrecognized command: "{commandName}"',room_name_too_long:"Isn't that a bit long?",room_name_too_short:"Nah, too short.",joined_room:"Joined room {roomName}.",left_room:"Left room {roomName}.",already_in_room:"You are already in a room ({room}), stoopid.",unable_to_decrypt:"Unabled to decrypt received message, keys does not match.",socket_error:"A network error has occurred. A restart may be required to bring back full functionality.
Examine the logs for more details.",connecting:"Connecting to host {host}...",connected:"A connection to the server has been established. Happy chatting!",disconnected:"Disconnected from host {host}.",already_connected:"You have to disconnect from {host} before joining another.",reconnect_no_host:"There is no host to reconnect with.",host_available:'{index}\t[AVAILABLE]\t{name}\n',host_unavailable:'{index}\t[UNAVAILABLE]\t{name}\n'},server:{person_joined:"A person joined this room.",person_left:"A person left this room.",person_count:"There is {payload} person(s) in this room, including you.",command_failed:"Server command failed, you're probably trying to du something bogus.",bogus:"Received a bogus message from server."},client:{title:"Cryptalk - Offline"}}),define("hosts",{autoconnect:0,hosts:[{name:"default",host:"",path:"/js/lib/settings.js"}]}),define("window",["castrato"],function(t){var e={},n=function(){t.emit("window:focused")},o=function(){t.emit("window:blurred")};return e.setTitle=function(t){document.title=t},e.getTitle=function(){return document.title},window.addEventListener?(window.addEventListener("focus",n,!0),window.addEventListener("blur",o,!0)):(window.observe("focusin",n),window.observe("focusout",o)),t.on("window:title",e.setTitle),e}),define("host",["$","castrato","settings","templates","hosts","window"],function(t,e,n,o,i,r){var s,c,a={},u=function(t){s&&s.emit(t.data,t.payload)},c=function(){e.emit("info",JSON.stringify(c||{}))},f=function(n,r){var s,c=0,a=i.hosts.length,u="\n",f=function(n,i,s){return function(c){n.settings=s?c:0,u+=t.template(o.messages[s?"host_available":"host_unavailable"],{name:n.name,path:n.path,index:i}),0===--a&&(e.emit("console:info",u),r())}};for(n=n&&"force"===n.toLowerCase();s=i.hosts[c];)n||void 0===s.settings?require([s.path],f(s,c,1),f(s,c,0)):s.settings?f(s,c,1)():f(s,c,0)(),c++},d=function(r,u){e.emit("console:lockInput");var f,r=void 0==r?i.autoconnect:r;if(c&&c.connected)return e.emit("console:error",t.template(o.messages.already_connected,{host:c.name||"localhost"})),void e.emit("console:unlockInput");if(t.isDigits(r)){if(!(c=i.hosts[+r]))return e.emit("console:error","Undefined host index: "+r),void e.emit("console:unlockInput");c.settings?n=c.settings:f=c.path}else r?f=r.settings:n=r.settings;return f?require([f],function(t){return c.settings=t,d(r,u)},function(){e.emit("console:error","Could not fetch host settings: "+f),e.emit("console:unlockInput")}):(e.emit("console:info",t.template(o.messages.connecting,{host:c.name||"localhost"})),e.emit("console:motd",c.settings.motd),s=t.io(c.host,{forceNew:!0,"force new connection":!0}),void s.on("room:joined",function(n){e.emit("console:info",t.template(o.messages.joined_room,{roomName:t.escapeHtml(a.room)})),s.emit("room:count")}).on("room:left",function(){e.emit("console:info",t.template(o.messages.left_room,{roomName:t.escapeHtml(a.room)})),e.emit("room:changed",!1)}).on("message:send",function(n){var i=t.AES.decrypt(n.msg,t.SHA1(a.room)+a.key),r=t.escapeHtml(i),s=n.nick?t.escapeHtml(t.AES.decrypt(n.nick,t.SHA1(a.room)+a.key)):o.default_nick;i?e.emit("console:message",{message:r,nick:s}):e.emit("console:error",o.messages.unable_to_decrypt)}).on("message:server",function(n){if(n.msg){var i=t.escapeHtml(n.msg);if(o.server[i])if(void 0!==n.payload){var r=t.escapeHtml(n.payload);e.emit("console:server",t.template(o.server[i],{payload:r}))}else e.emit("console:server",o.server[i]);else e.emit("console:error",o.server.bogus)}else e.emit("console:error",o.server.bogus)}).on("connect",function(){e.emit("console:info",t.template(o.messages.connected,{host:c.name||"localhost"})),e.emit("window:title",c.settings.title),e.emit("console:unlockInput"),u(),c.connected=1}).on("disconnect",function(){room=0,key=0,c.connected=0,e.emit("console:info",t.template(o.messages.disconnected,{host:c.name||"localhost"})),e.emit("room:changed",void 0),e.emit("window:title",o.client.title)}).on("connect_error",function(){room=0,key=0,c.connected=0,e.emit("console:error",o.messages.socket_error),e.emit("console:unlockInput")}))},l=function(){s.disconnect()},m=function(t){a=Object.assign({},a,t)};e.on("command:host",c),e.on("command:hosts",f),e.on("command:connect",d),e.on("command:disconnect",l),e.on("command:reconnect",l),e.on("socket:emit",u),e.on("host:param",m)}),define("client",["$","castrato","settings","templates"],function(t,e,n,o){var i,r,s=function(t){return t.length>n.key.maxLen?e.emit("console:error",o.messages.key_to_long):t.lengthn.nick.maxLen?e("console:error",t.template(o.messages.nick_to_long,{nick_maxLen:n.nick.maxLen})):r.length=n.room.maxLen?e.emit("console:error",t.template(o.messages.room_name_too_long)):r.lengthn.notifications.maxOnePerMs)if(void 0===i&&(i=!1),u&&"granted"===Notification.permission){var r=new Notification(t,{body:e,icon:o});r.onshow=function(){setTimeout(function(){r.close()},3e3)},c=f()}else i&&g("Attention",1e3)};u=void 0!==window.Notification,t.on("notification:send",function(t){y(t.title,t.body,t.icon,!0)}),t.on("notification:on",function(){d()}),t.on("notification:off",function(){l()}),h(),l(),c=f(),m()}),define("queue",[],function(){var t={},e=[],n=function(){return performance.now()||Date.now()};return t.add_function_delayed=function(t,o,i){e.push({func:o,pushed:n(),delay:t,data:i})},t.get=function(){return e},t.run=function(){for(var o,i,r=0;o=e[r++];)n()-o.pushed>o.delay&&(o.func(),e.splice(r-1,1));if(e.length){for(i=n();n()-i<1;);t.run()}},t}),define("audio",["queue","castrato","templates"],function(t,e,n){var o=!1,i=!0,r=!1,s=function(e,n){if(n=void 0===n?0:n,o&&i&&n0&&i<3600?(e.emit("console:info",t.template(o.messages.torch_is_now,{ttl:i})),n.ttl=1e3*i):e.emit("console:error",t.template(o.messages.torch_not_set))},param:function(t){s=Object.assign({},s,t)},showNotification:function(t,n,o){var r="message"!==t?"Cryptalk":n,s="message"===t?"gfx/icon_128x128.png":"error"==t?"gfx/icon_128x128_error.png":"gfx/icon_128x128_info.png";e.emit("notification:send",{title:r.substring(0,20),body:o.substring(0,80),icon:s}),"message"===t&&e.emit("audio:play",i.message)},motd:function(t){c.post("motd",t)},info:function(t){c.post("info",t)},error:function(t){c.post("error",t)},server:function(t){c.post("server",t)},message:function(t){c.post("message",t.message,t.nick)},clearInput:function(){r.input[0].value=""},clear:function(){r.chat[0].innerHTML=""},lockInput:function(){r.input[0].setAttribute("disabled","disabled"),r.inputWrapper[0].className="loading"},unlockInput:function(){r.input[0].removeAttribute("disabled"),r.inputWrapper[0].className="",r.input.focus()},_require:function(t,e){c.lockInput(),c.post("info","Requiring "+t+"..."),require([t],function(){c.post("info","Successfully required "+t+"."),c.unlockInput(),e()},function(n){c.post("error",'An error occurred while trying to load "'+t+'":\n'+n),c.unlockInput(),e()})}},a=function(n){var i,a,u,f;if(!n.ctrlKey&&!n.altKey&&r.input[0]!==t.activeElement())return r.input.focus();if(13===n.keyCode&&(i=r.input[0].value))if("/"===(i[0]||i.slice(0,1)))a=t.ssplit(i.slice(1)," "),f=a[0],u=a[1],e.emit("command:"+f,u,function(e,n){return n?void c.clearInput():c.post("error",t.template(o.messages.unrecognized_command,{commandName:f}))});else{if(!s.room||!s.key)return s.room?c.post("error",o.messages.msg_no_key):c.post("error",o.messages.msg_no_room);e.emit("socket:emit",{data:"message:send",payload:{room:t.SHA1(s.room),msg:t.AES.encrypt(i,t.SHA1(s.room)+s.key).toString(),nick:!!s.nick&&t.AES.encrypt(s.nick,t.SHA1(s.room)+s.key).toString()}}),c.clearInput()}};t(document).on("keydown",a),r.input.focus();for(var u in c)"_require"!==u&&"post"!==u&&e.on("console:"+u,c[u]);e.on("console:require",c._require),e.on("console:post",function(t){c.post(t.type,t.data,t.nick)})}),define("cryptalk",["castrato","host","client","console"],function(t,e,n){t.on("window:focused",function(){t.emit("audio:off"),t.emit("notification:off")}).on("window:blurred",function(){t.emit("audio:on"),t.emit("notification:on")}).on("command:mute",function(){t.emit("audio:mute")}).on("command:unmute",function(){t.emit("audio:unmute")}).on("room:changed",function(e){t.emit("console:param",{room:e}).emit("host:param",{room:e})}).on("nick:changed",function(e){t.emit("console:param",{nick:e})}).on("key:changed",function(e){t.emit("console:param",{key:e}).emit("host:param",{key:e})}).emit("command:connect",void 0,function(){(hash=window.location.hash)&&(parts=hash.slice(1).split(":"),parts[0]&&t.emit("command:join",parts[0]),parts[1]&&t.emit("command:key",parts[1]))})}),require.config({baseUrl:"js/lib/",paths:{websocket:"/socket.io/socket.io"},packages:[{name:"crypto-js",location:"../vendor/crypto-js-3.1.9",main:"index"}]}),require(["cryptalk"]),define("main",function(){});
\ No newline at end of file
diff --git a/public/js/lib/$.js b/public/js/lib/$.js
index af7f9ab..0a46c30 100644
--- a/public/js/lib/$.js
+++ b/public/js/lib/$.js
@@ -1,194 +1,38 @@
-define(['websocket', 'crypto-js/aes', 'crypto-js/sha1', 'crypto-js/enc-utf8'], function (websocket, aes, sha1, utf8) {
+define(['$.utils', '$.proto'], function (utils, proto) {
- var exports = {
- selector: 0,
- utilities: {},
- prototype: {}
- },
+ // Create a custom edition of Array, extended with $.proto
+ var ElementArray = function () {};
+ ElementArray.prototype = new Array;
+ ElementArray.constructor = Array;
+ for(var key in proto) ElementArray.prototype[key] = proto[key];
- // Shortcuts
- utils = exports.utilities,
- proto = exports.prototype,
+ // Create to actual dollar function
+ function Dollar (selector) {
- /**
- * Regex for matching NaN.
- *
- * @property reNaN
- * @type {Regex}
- * @private
- */
- reDigits = /^\d+$/;
-
- // The DOM selector engine
- exports.selector = function (selector) {
var match,
- matches = [];
+ matches = new ElementArray();
- if (selector === document) {
- matches.push(document);
- } else {
- selector = selector.slice(1);
-
- if ((match = document.getElementById(selector))) {
- matches.push(match);
- }
- }
-
- return matches;
- };
-
- // Namespace SHA1
- utils.SHA1 = function (string) {
- return sha1(string).toString();
- };
-
- // Namespace encode
- utils.AES = {
- decrypt: function (string, fgh) {
- return aes.decrypt(string, fgh).toString(utf8);
- },
-
- encrypt: function (string, fgh) {
- return aes.encrypt(string, fgh).toString();
- }
- };
-
- // Namespace websocket
- utils.io = websocket;
-
- utils.ssplit = function (string, seperator) {
- var components = string.split(seperator);
- return [components.shift(), components.join(seperator)];
- };
-
- utils.activeElement = function () {
- try { return document.activeElement; } catch (e) {}
- };
-
- /**
- * Removes all characters but 0 - 9 from given string.
- *
- * @method digits
- * @param {String} str The string to sanitize
- * @return {String} The sanitized string
- * @example
- * $.digits('foo8bar'); // `8`
- * $.digits('->#5*duckM4N!!!111'); // `54111`
- */
- utils.isDigits = function(value) {
- return reDigits.test(value);
- };
-
- /**
- * A very simple templating function.
- * @param {} str [description]
- * @param {[type]} map [description]
- * @return {[type]} [description]
- */
- utils.template = function (str, map) {
- return str && str.replace(/{(\w+)}/gi, function(outer, inner) {
- return map.hasOwnProperty(inner) ? map[inner] : outer /* '' */;
- });
- };
-
- utils.getJSON = function (path, onSuccess, onError) {
- var data, request = new XMLHttpRequest();
- request.open('GET', path, true);
-
- request.onreadystatechange = function() {
- if (this.readyState === 4) {
- if (this.status >= 200 && this.status < 400) {
- try {
- onSuccess && onSuccess(JSON.parse(this.responseText));
- } catch (e) {
- onError && onError();
+ if (selector !== undefined) {
+ if (selector === document) {
+ matches.push(document);
+ } else if (selector === window) {
+ matches.push(window);
+ } else {
+ if ((match = document.querySelectorAll(selector))) {
+ for( var i=0; i < match.length; i++) {
+ matches.push(match[i]);
}
- } else {
- onError && onError();
}
}
- };
-
- request.send();
- request = null;
- };
-
- // Part of this is originating from mustasche.js
- // Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43
- // License: https://github.com/janl/mustache.js/blob/master/LICENSE
- utils.escapeHtml = (function () {
- var pattern = /[&<>"'\/]/g,
- entities = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": ''',
- '/': '/'
- };
-
- return function (string) {
- return String(string).replace(pattern, function (s) {
- return entities[s];
- });
- };
- }());
-
- // Extremely naive implementations of .html() and .append()
- proto.html = function (string) {
- this.forEach(function (element) {
- element.innerHTML = string;
- });
- return this;
- };
-
- proto.append = function (string) {
- this.forEach(function (element) {
- element.innerHTML += string;
- });
- return this;
- };
-
- proto.first = function () {
- return this[0];
- };
-
- // Naive implementations of .on()
- proto.on = function (eventName, callback) {
- this.forEach(function (element) {
- if (element.addEventListener) {
- element.addEventListener(eventName, callback, false);
- } else if (element.attachEvent) {
- element.attachEvent('on' + eventName, callback);
- }
- });
- return this;
- };
-
- proto.focus = function () {
- // It doesn't make sense to focus all matched elements. So we settle for the first one
- if(this[0]) {
- this[0].focus();
}
- return this;
- };
-
- // Prepare the dollar function
- function dollar (selector) {
- // Get array of matches given by selector
- var matches = exports.selector(selector);
-
- // Extend the result array with the functions from exports.prototype
- matches.__proto__ = Object.assign(matches.__proto__, exports.prototype);
-
- // Return matches, with extended functions (.on, .html, .first etc.)
return matches;
+
}
- // Extend the dollar function with the functions of exports.utilities (.isDigits etc.)
- dollar.__proto__ = exports.utilities;
+ // Add utils to Dollar
+ for(var key in utils) Dollar[key] = utils[key];
- return dollar;
+ return Dollar;
});
\ No newline at end of file
diff --git a/public/js/lib/$.proto.js b/public/js/lib/$.proto.js
new file mode 100644
index 0000000..381fcf5
--- /dev/null
+++ b/public/js/lib/$.proto.js
@@ -0,0 +1,47 @@
+define(function () {
+
+ var
+ exports = {};
+
+ // Extremely naive implementations of .html() and .append()
+ exports.html = function (string) {
+ this.forEach(function (element) {
+ element.innerHTML = string;
+ });
+ return this;
+ };
+
+ exports.append = function (string) {
+ this.forEach(function (element) {
+ element.innerHTML += string;
+ });
+ return this;
+ };
+
+ exports.first = function () {
+ return this[0];
+ };
+
+ // Naive implementations of .on()
+ exports.on = function (eventName, callback) {
+ this.forEach(function (element) {
+ if (element.addEventListener) {
+ element.addEventListener(eventName, callback, false);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + eventName, callback);
+ }
+ });
+ return this;
+ };
+
+ exports.focus = function () {
+ // It doesn't make sense to focus all matched elements. So we settle for the first one
+ if(this[0]) {
+ this[0].focus();
+ }
+ return this;
+ };
+
+ return exports;
+
+});
diff --git a/public/js/lib/$.utils.js b/public/js/lib/$.utils.js
new file mode 100644
index 0000000..1cfe1e2
--- /dev/null
+++ b/public/js/lib/$.utils.js
@@ -0,0 +1,107 @@
+define(['websocket','crypto-js/aes', 'crypto-js/sha1', 'crypto-js/enc-utf8'],function (websocket, aes, sha1, utf8) {
+
+ var
+ exports = {},
+
+ reDigits = /^\d+$/;
+
+ // Namespace websocket
+ exports.io = websocket;
+
+ // Namespace SHA1
+ exports.SHA1 = function (string) {
+ return sha1(string).toString();
+ };
+
+ // Namespace encode
+ exports.AES = {
+ decrypt: function (string, fgh) {
+ return aes.decrypt(string, fgh).toString(utf8);
+ },
+
+ encrypt: function (string, fgh) {
+ return aes.encrypt(string, fgh).toString();
+ }
+ };
+
+ exports.ssplit = function (string, seperator) {
+ var components = string.split(seperator);
+ return [components.shift(), components.join(seperator)];
+ };
+
+ exports.activeElement = function () {
+ try { return document.activeElement; } catch (e) {}
+ };
+
+ /**
+ * Removes all characters but 0 - 9 from given string.
+ *
+ * @method digits
+ * @param {String} str The string to sanitize
+ * @return {String} The sanitized string
+ * @example
+ * $.digits('foo8bar'); // `8`
+ * $.digits('->#5*duckM4N!!!111'); // `54111`
+ */
+ exports.isDigits = function(value) {
+ return reDigits.test(value);
+ };
+
+ /**
+ * A very simple templating function.
+ * @param {} str [description]
+ * @param {[type]} map [description]
+ * @return {[type]} [description]
+ */
+ exports.template = function (str, map) {
+ return str && str.replace(/{(\w+)}/gi, function(outer, inner) {
+ return map.hasOwnProperty(inner) ? map[inner] : outer /* '' */;
+ });
+ };
+
+ exports.getJSON = function (path, onSuccess, onError) {
+ var data, request = new XMLHttpRequest();
+ request.open('GET', path, true);
+
+ request.onreadystatechange = function() {
+ if (this.readyState === 4) {
+ if (this.status >= 200 && this.status < 400) {
+ try {
+ onSuccess && onSuccess(JSON.parse(this.responseText));
+ } catch (e) {
+ onError && onError();
+ }
+ } else {
+ onError && onError();
+ }
+ }
+ };
+
+ request.send();
+ request = null;
+ };
+
+ // Part of this is originating from mustasche.js
+ // Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43
+ // License: https://github.com/janl/mustache.js/blob/master/LICENSE
+ exports.escapeHtml = (function () {
+ var pattern = /[&<>"'\/]/g,
+ entities = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/'
+ };
+
+ return function (string) {
+ return String(string).replace(pattern, function (s) {
+ return entities[s];
+ });
+ };
+ }());
+
+ return exports;
+
+});