<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/**
 * JSONUtil -- Provides JSON++ marshalling support.
 *
 * Like regular JSON, with the following differences:
 *  1. Marshalls to/from FOAM Objects, rather than maps.
 *  2. Object Model information is encoded as 'model: "ModelName"'
 *  3. Default Values are not marshalled, saving disk space and network bandwidth.
 *  4. Support for marshalling functions.
 *  5. Support for property filtering, ie. only output non-transient properties.
 *  6. Support for 'pretty' and 'compact' modes.
 *
 *  TODO:
 *    Replace with JSONParser.js, when finished.
 *    Maybe rename to FON (FOAM Object Notation, pronounced 'phone') to avoid
 *    confusion with regular JSON syntax.
 **/

var AbstractFormatter = {
  /** @param p a predicate function or an mLang **/
  where: function(p) {
    return {
      __proto__: this,
      p: ( p.f && p.f.bind(p) ) || p
    };
  },

  p: function() { return true; }
};

var JSONUtil = {

  keys_: {},

  keyify: function(str) {
    return this.keys_[str] || (
      this.keys_[str] =
        /^[a-zA-Z\$_][0-9a-zA-Z$_]*$/.test(str) ?
        str :
        '"' + str + '"'
    );
  },

  escape: function(str) {
    return str
      .replace(/\\/g, '\\\\')
      .replace(/"/g, '\\"')
      .replace(/[\x00-\x1f]/g, function(c) {
        return "\\u00" + ((c.charCodeAt(0) < 0x10) ?
                          '0' + c.charCodeAt(0).toString(16) :
                          c.charCodeAt(0).toString(16));
      });
  },

  parseToMap: function(str) {
    return eval('(' + str + ')');
  },

  parse: function(X, str, seq) {
    return this.mapToObj(X, this.parseToMap(str), undefined, seq);
  },

  arrayToObjArray: function(X, a, opt_defaultModel, seq) {
    for ( var i = 0 ; i < a.length ; i++ ) {
      a[i] = this.mapToObj(X, a[i], opt_defaultModel, seq);
    }
    return a;
  },

  /**
   * Convert JS-Maps which contain the 'model_' property, into
   * instances of that model.
   **/
  mapToObj: function(X, obj, opt_defaultModel, seq) {
    if ( ! obj || typeof obj.model_ === 'object' ) return obj;

    if ( Array.isArray(obj) ) return this.arrayToObjArray(X, obj, undefined, seq);

    if ( obj instanceof Function ) return obj;

    if ( obj instanceof Date ) return obj;

    if ( obj instanceof Object ) {
      var j = 0;
      for ( var key in obj ) {
        if ( key != 'model_' && key != 'prototype_' ) obj[key] = this.mapToObj(X, obj[key], null, seq);
        j++;
      }
/*      var keys = Object.keys(obj);
      for ( var j = 0, key; key = keys[j]; j++ ) {
        if ( key != 'model_' && key != 'prototype_' ) obj[key] = this.mapToObj(obj[key]);
      }
      */

      if ( opt_defaultModel && ! obj.model_ ) return opt_defaultModel.create(obj);

      if ( obj.model_ ) {
        var newObj = FOAM.lookup(obj.model_, X);
        if ( ! newObj && seq ) {
          var future = afuture();
          seq.push(future.get);

          arequire(obj.model_)(function(model) {
            if ( ! model ) {
               if ( obj.model_ !== 'Template' )
                 console.warn('Failed to dynamically load: ', obj.model_);
              future.set(obj);
              return;
            }
            var tmp = model.create(obj);
            obj.become(tmp);
            future.set(obj);
          });
        }
        return newObj ? newObj.create(obj, X) : obj;
      }
      return obj
    }

    return obj;
  },

  compact: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      var buf = '';

      this.output(function() {
        for (var i = 0; i < arguments.length; i++)
          buf += arguments[i];
      }, obj);

      return buf;
    },

    output: function(out, obj) {
      if ( Array.isArray(obj) ) {
        this.outputArray_(out, obj);
      }
      else if ( typeof obj === 'string' ) {
        out('"');
        out(JSONUtil.escape(obj));
        out('"');
      }
      else if ( obj instanceof Function ) {
        this.outputFunction_(out, obj);
      }
      else if ( obj instanceof Date ) {
        out(obj.getTime());
      }
      else if ( obj instanceof Object ) {
        if ( obj.model_ )
          this.outputObject_(out, obj);
        else
          this.outputMap_(out, obj);
      }
      else if ( typeof obj === 'number' ) {
        if ( ! isFinite(obj) ) obj = null;
        out(obj);
      }
      else {
        if ( obj === undefined ) obj = null;
        out(obj);
      }
    },

    outputObject_: function(out, obj, opt_defaultModel) {
      var str   = '';
      var first = true;

      out('{');
      if ( obj.model_.id !== opt_defaultModel ) {
        this.outputModel_(out, obj);
        first = false;
     }

      for ( var key in obj.model_.properties_ ) {
        var prop = obj.model_.properties_[key];

        if ( ! this.p(prop) ) continue;

        if ( prop.name in obj.instance_ ) {
          var val = obj[prop.name];
          if ( val == prop.defaultValue ) continue;
          if ( Array.isArray(val) && ! val.length ) continue;
          if ( ! first ) out(',');
          out(JSONUtil.keyify(prop.name), ': ');
          this.output(out, val);
          first = false;
        }
      }

      out('}');
    },

    outputModel_: function(out, obj) {
      out('model_:"')
      if ( obj.model_.package ) out(obj.model_.package, '.')
      out(obj.model_.name, '"');
    },

    outputMap_: function(out, obj) {
      var str   = '';
      var first = true;

      out('{');

      for ( var key in obj ) {
        var val = obj[key];

        if ( ! first ) out(',');
        out(JSONUtil.keyify(key), ': ');
        this.output(out, val);

        first = false;
      }

      out('}');
    },

    outputArray_: function(out, a, opt_defaultModel) {
      if ( a.length == 0 ) { out('[]'); return out; }

      var str   = '';
      var first = true;

      out('[');

      for ( var i = 0 ; i < a.length ; i++, first = false ) {
        var obj = a[i];

        if ( ! first ) out(',');

        this.output(out, obj);
      }

      out(']');
    },

    outputFunction_: function(out, obj) { out(obj); }
  },

  pretty: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      var buf = '';

      this.output(function() {
        for (var i = 0; i < arguments.length; i++)
          buf += arguments[i];
      }, obj);

      return buf;
    },

    output: function(out, obj, opt_indent) {
      var indent = opt_indent || '';

      if ( Array.isArray(obj) ) {
        this.outputArray_(out, obj, null, indent);
      }
      else if ( typeof obj == 'string' ) {
        out('"');
        out(JSONUtil.escape(obj));
        out('"');
      }
      else if ( obj instanceof Function ) {
        this.outputFunction_(out, obj, indent);
      }
      else if ( obj instanceof Date ) {
        out("new Date('", obj, "')");
      }
      else if ( obj instanceof Object ) {
        if ( obj.model_ )
          this.outputObject_(out, obj, null, indent);
        else
          this.outputMap_(out, obj, indent);
      } else if ( typeof obj === 'number' ) {
        if ( ! isFinite(obj) ) obj = null;
        out(obj);
      } else {
        if ( obj === undefined ) obj = null;
        out(obj);
      }
    },

    outputObject_: function(out, obj, opt_defaultModel, opt_indent) {
      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out(/*"\n", */indent, '{\n');
      if ( obj.model_.id !== opt_defaultModel ) {
        this.outputModel_(out, obj, nestedIndent);
        first = false;
      }

      for ( var key in obj.model_.properties_ ) {
        var prop = obj.model_.properties_[key];

        if ( ! this.p(prop) ) continue;

        if ( prop.name === 'parent' ) continue;
        if ( prop.name in obj.instance_ ) {
          var val = obj[prop.name];
          if ( val == prop.defaultValue ) continue;
          if ( Array.isArray(val) && ! val.length ) continue;
          if ( ! first ) out(',\n');
          out(nestedIndent, JSONUtil.keyify(prop.name), ': ');
          if ( Array.isArray(val) && prop.subType ) {
            this.outputArray_(out, val, prop.subType, nestedIndent);
          } else {
            this.output(out, val, nestedIndent);
          }
          first = false;
        }
      }

      out('\n', indent, '}');
    },

    outputModel_: function(out, obj, indent) {
      out(indent, 'model_: "', obj.model_.id, '"');
    },

    outputMap_: function(out, obj, opt_indent) {
      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out(/*"\n",*/ indent, '{\n', nestedIndent);

      for ( var key in obj )
      {
        var val = obj[key];

        if ( ! first ) out(',\n');
        out(nestedIndent, JSONUtil.keyify(key), ': ');
        this.output(out, val, nestedIndent);

        first = false;
      }

      out('\n', indent, '}');
    },

    outputArray_: function(out, a, opt_defaultModel, opt_indent) {
      if ( a.length == 0 ) { out('[]'); return out; }

      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out('[\n');

      for ( var i = 0 ; i < a.length ; i++, first = false ) {
        var obj = a[i];

        if ( ! first ) out(',\n');

        if ( typeof obj === 'string' ) {
          out(nestedIndent);
          this.output(out, obj, nestedIndent);
        } else {
          this.outputObject_(out, obj, opt_defaultModel, nestedIndent);
        }
      }

      out('\n', indent, ']');
    },

    outputFunction_: function(out, obj, indent) {
      var str = obj.toString();
      var lines = str.split('\n');

      if ( lines.length == 1 ) { out(str); return; }

      var minIndent = 10000;
      for ( var i = 0 ; i < lines.length ; i++ ) {
        var j = 0;
        for ( ; j < lines[i].length && lines[i].charAt(j) === ' ' && j < minIndent ; j++ );
        if ( j > 0 && j < minIndent ) minIndent = j;
      }

      if ( minIndent === 10000 ) { out(str); return; }

      for ( var i = 0 ; i < lines.length ; i++ ) {
        if ( lines[i].length && lines[i].charAt(0) === ' ' ) {
          lines[i] = indent + lines[i].substring(minIndent);
        }
        out(lines[i]);
        if ( i < lines.length-1 ) out('\n');
      }
    }
  },

  moreCompact: {
    __proto__: AbstractFormatter,
    // TODO: use short-names
  },

  compressed: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      return Iuppiter.Base64.encode(Iuppiter.compress(JSONUtil.compact.stringify(obj),true));
    }
  }

};

JSONUtil.stringify = JSONUtil.pretty.stringify.bind(JSONUtil.pretty);
JSONUtil.output = JSONUtil.pretty.output.bind(JSONUtil.pretty);;
JSONUtil.where = JSONUtil.pretty.where.bind(JSONUtil.pretty);;

var NOT_TRANSIENT = function(prop) { return ! prop.transient; };
<commit_msg>JSONUtil now has a new mode called 'prettyModel'.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/**
 * JSONUtil -- Provides JSON++ marshalling support.
 *
 * Like regular JSON, with the following differences:
 *  1. Marshalls to/from FOAM Objects, rather than maps.
 *  2. Object Model information is encoded as 'model: "ModelName"'
 *  3. Default Values are not marshalled, saving disk space and network bandwidth.
 *  4. Support for marshalling functions.
 *  5. Support for property filtering, ie. only output non-transient properties.
 *  6. Support for 'pretty' and 'compact' modes.
 *
 *  TODO:
 *    Replace with JSONParser.js, when finished.
 *    Maybe rename to FON (FOAM Object Notation, pronounced 'phone') to avoid
 *    confusion with regular JSON syntax.
 **/

var AbstractFormatter = {
  keyify: function(str) { return '"' + str + '"'; },

  /** @param p a predicate function or an mLang **/
  where: function(p) {
    return {
      __proto__: this,
      p: ( p.f && p.f.bind(p) ) || p
    };
  },

  p: function() { return true; }
};

var JSONUtil = {

  escape: function(str) {
    return str
      .replace(/\\/g, '\\\\')
      .replace(/"/g, '\\"')
      .replace(/[\x00-\x1f]/g, function(c) {
        return "\\u00" + ((c.charCodeAt(0) < 0x10) ?
                          '0' + c.charCodeAt(0).toString(16) :
                          c.charCodeAt(0).toString(16));
      });
  },

  parseToMap: function(str) {
    return eval('(' + str + ')');
  },

  parse: function(X, str, seq) {
    return this.mapToObj(X, this.parseToMap(str), undefined, seq);
  },

  arrayToObjArray: function(X, a, opt_defaultModel, seq) {
    for ( var i = 0 ; i < a.length ; i++ ) {
      a[i] = this.mapToObj(X, a[i], opt_defaultModel, seq);
    }
    return a;
  },

  /**
   * Convert JS-Maps which contain the 'model_' property, into
   * instances of that model.
   **/
  mapToObj: function(X, obj, opt_defaultModel, seq) {
    if ( ! obj || typeof obj.model_ === 'object' ) return obj;

    if ( Array.isArray(obj) ) return this.arrayToObjArray(X, obj, undefined, seq);

    if ( obj instanceof Function ) return obj;

    if ( obj instanceof Date ) return obj;

    if ( obj instanceof Object ) {
      var j = 0;
      for ( var key in obj ) {
        if ( key != 'model_' && key != 'prototype_' ) obj[key] = this.mapToObj(X, obj[key], null, seq);
        j++;
      }
/*      var keys = Object.keys(obj);
      for ( var j = 0, key; key = keys[j]; j++ ) {
        if ( key != 'model_' && key != 'prototype_' ) obj[key] = this.mapToObj(obj[key]);
      }
      */

      if ( opt_defaultModel && ! obj.model_ ) return opt_defaultModel.create(obj);

      if ( obj.model_ ) {
        var newObj = FOAM.lookup(obj.model_, X);
        if ( ! newObj && seq ) {
          var future = afuture();
          seq.push(future.get);

          arequire(obj.model_)(function(model) {
            if ( ! model ) {
               if ( obj.model_ !== 'Template' )
                 console.warn('Failed to dynamically load: ', obj.model_);
              future.set(obj);
              return;
            }
            var tmp = model.create(obj);
            obj.become(tmp);
            future.set(obj);
          });
        }
        return newObj ? newObj.create(obj, X) : obj;
      }
      return obj
    }

    return obj;
  },

  compact: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      var buf = '';

      this.output(function() {
        for (var i = 0; i < arguments.length; i++)
          buf += arguments[i];
      }, obj);

      return buf;
    },

    output: function(out, obj) {
      if ( Array.isArray(obj) ) {
        this.outputArray_(out, obj);
      }
      else if ( typeof obj === 'string' ) {
        out('"');
        out(JSONUtil.escape(obj));
        out('"');
      }
      else if ( obj instanceof Function ) {
        this.outputFunction_(out, obj);
      }
      else if ( obj instanceof Date ) {
        out(obj.getTime());
      }
      else if ( obj instanceof Object ) {
        if ( obj.model_ )
          this.outputObject_(out, obj);
        else
          this.outputMap_(out, obj);
      }
      else if ( typeof obj === 'number' ) {
        if ( ! isFinite(obj) ) obj = null;
        out(obj);
      }
      else {
        if ( obj === undefined ) obj = null;
        out(obj);
      }
    },

    outputObject_: function(out, obj, opt_defaultModel) {
      var str   = '';
      var first = true;

      out('{');
      if ( obj.model_.id !== opt_defaultModel ) {
        this.outputModel_(out, obj);
        first = false;
     }

      for ( var key in obj.model_.properties_ ) {
        var prop = obj.model_.properties_[key];

        if ( ! this.p(prop) ) continue;

        if ( prop.name in obj.instance_ ) {
          var val = obj[prop.name];
          if ( val == prop.defaultValue ) continue;
          if ( Array.isArray(val) && ! val.length ) continue;
          if ( ! first ) out(',');
          out(this.keyify(prop.name), ': ');
          this.output(out, val);
          first = false;
        }
      }

      out('}');
    },

    outputModel_: function(out, obj) {
      out('"model_":"')
      if ( obj.model_.package ) out(obj.model_.package, '.')
      out(obj.model_.name, '"');
    },

    outputMap_: function(out, obj) {
      var str   = '';
      var first = true;

      out('{');

      for ( var key in obj ) {
        var val = obj[key];

        if ( ! first ) out(',');
        out(this.keyify(key), ': ');
        this.output(out, val);

        first = false;
      }

      out('}');
    },

    outputArray_: function(out, a, opt_defaultModel) {
      if ( a.length == 0 ) { out('[]'); return out; }

      var str   = '';
      var first = true;

      out('[');

      for ( var i = 0 ; i < a.length ; i++, first = false ) {
        var obj = a[i];

        if ( ! first ) out(',');

        this.output(out, obj);
      }

      out(']');
    },

    outputFunction_: function(out, obj) { out(obj); }
  },

  pretty: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      var buf = '';

      this.output(function() {
        for (var i = 0; i < arguments.length; i++)
          buf += arguments[i];
      }, obj);

      return buf;
    },

    output: function(out, obj, opt_indent) {
      var indent = opt_indent || '';

      if ( Array.isArray(obj) ) {
        this.outputArray_(out, obj, null, indent);
      }
      else if ( typeof obj == 'string' ) {
        out('"');
        out(JSONUtil.escape(obj));
        out('"');
      }
      else if ( obj instanceof Function ) {
        this.outputFunction_(out, obj, indent);
      }
      else if ( obj instanceof Date ) {
        out("new Date('", obj, "')");
      }
      else if ( obj instanceof Object ) {
        if ( obj.model_ )
          this.outputObject_(out, obj, null, indent);
        else
          this.outputMap_(out, obj, indent);
      } else if ( typeof obj === 'number' ) {
        if ( ! isFinite(obj) ) obj = null;
        out(obj);
      } else {
        if ( obj === undefined ) obj = null;
        out(obj);
      }
    },

    outputObject_: function(out, obj, opt_defaultModel, opt_indent) {
      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out(/*"\n", */indent, '{\n');
      if ( obj.model_.id && obj.model_.id !== opt_defaultModel ) {
        this.outputModel_(out, obj, nestedIndent);
        first = false;
      }

      for ( var key in obj.model_.properties_ ) {
        var prop = obj.model_.properties_[key];

        if ( ! this.p(prop) ) continue;

        if ( prop.name === 'parent' ) continue;
        if ( prop.name in obj.instance_ ) {
          var val = obj[prop.name];
          if ( val == prop.defaultValue ) continue;
          if ( Array.isArray(val) && ! val.length ) continue;
          if ( ! first ) out(',\n');
          out(nestedIndent, this.keyify(prop.name), ': ');
          if ( Array.isArray(val) && prop.subType ) {
            this.outputArray_(out, val, prop.subType, nestedIndent);
          } else {
            this.output(out, val, nestedIndent);
          }
          first = false;
        }
      }

      out('\n', indent, '}');
    },

    outputModel_: function(out, obj, indent) {
      out(indent, '"model_": "', obj.model_.id, '"');
    },

    outputMap_: function(out, obj, opt_indent) {
      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out(/*"\n",*/ indent, '{\n', nestedIndent);

      for ( var key in obj )
      {
        var val = obj[key];

        if ( ! first ) out(',\n');
        out(nestedIndent, this.keyify(key), ': ');
        this.output(out, val, nestedIndent);

        first = false;
      }

      out('\n', indent, '}');
    },

    outputArray_: function(out, a, opt_defaultModel, opt_indent) {
      if ( a.length == 0 ) { out('[]'); return out; }

      var indent       = opt_indent || '';
      var nestedIndent = indent + '   ';
      var str          = '';
      var first        = true;

      out('[\n');

      for ( var i = 0 ; i < a.length ; i++, first = false ) {
        var obj = a[i];

        if ( ! first ) out(',\n');

        if ( typeof obj === 'string' || typeof obj === 'number' ) {
          out(nestedIndent);
          this.output(out, obj, nestedIndent);
        } else {
          this.outputObject_(out, obj, opt_defaultModel, nestedIndent);
        }
      }

      out('\n', indent, ']');
    },

    outputFunction_: function(out, obj, indent) {
      var str = obj.toString();
      var lines = str.split('\n');

      if ( lines.length == 1 ) { out(str); return; }

      var minIndent = 10000;
      for ( var i = 0 ; i < lines.length ; i++ ) {
        var j = 0;
        for ( ; j < lines[i].length && lines[i].charAt(j) === ' ' && j < minIndent ; j++ );
        if ( j > 0 && j < minIndent ) minIndent = j;
      }

      if ( minIndent === 10000 ) { out(str); return; }

      for ( var i = 0 ; i < lines.length ; i++ ) {
        if ( lines[i].length && lines[i].charAt(0) === ' ' ) {
          lines[i] = indent + lines[i].substring(minIndent);
        }
        out(lines[i]);
        if ( i < lines.length-1 ) out('\n');
      }
    }
  },

  moreCompact: {
    __proto__: AbstractFormatter,
    // TODO: use short-names
  },

  compressed: {
    __proto__: AbstractFormatter,

    stringify: function(obj) {
      return Iuppiter.Base64.encode(Iuppiter.compress(JSONUtil.compact.stringify(obj),true));
    }
  }

};

JSONUtil.prettyModel = {
  __proto__: JSONUtil.pretty,

  outputModel_: function(out, obj, indent) {
    out(indent, 'model_: "', obj.model_.id, '"');
  },

  keys_: {},

  keyify: function(str) {
    return this.keys_[str] || (
      this.keys_[str] =
        /^[a-zA-Z\$_][0-9a-zA-Z$_]*$/.test(str) ?
        str :
        '"' + str + '"'
    );
  }
};

JSONUtil.stringify = JSONUtil.pretty.stringify.bind(JSONUtil.pretty);
JSONUtil.output = JSONUtil.pretty.output.bind(JSONUtil.pretty);;
JSONUtil.where = JSONUtil.pretty.where.bind(JSONUtil.pretty);;

var NOT_TRANSIENT = function(prop) { return ! prop.transient; };
<|endoftext|>


<commit_before>/**
 * Stores information on sip devices currently registered in the server.
 * This implementation won't scale to thousands of devices.
 *
 * @author Pedro Sanders
 * @since v1
 */
function LocationService() {
    const HashMap = Packages.java.util.HashMap
    const db = new HashMap()

    this.put = (addressOfRecord, contactAddress) => { db.put(addressOfRecord.toString(), contactAddress) }

    // It would be nice if we could enforce the use of 'tel' scheme for DIDs requests
    this.get = addressOfRecord => {
        if (addressOfRecord instanceof Packages.javax.sip.address.URI ||
            addressOfRecord instanceof Packages.javax.sip.address.SipURI) {

            const sipAddress = addressOfRecord.getScheme() + ":" + addressOfRecord.getUser() + '@' + addressOfRecord.getHost()

            return db.get(sipAddress) || db.get('tel:' + addressOfRecord.getUser())
        } if (k instanceof Packages.javax.sip.address.TelURL) {
            return db.get('tel:' + addressOfRecord.getPhoneNumber())
        }

        return db.get(addressOfRecord)
    }

    this.remove = k => { db.remove(k) }

    this.listAllAsJSON = () => {
        let s = []
        let aors = db.keySet().iterator()

        while(aors.hasNext()) {
            let key = aors.next()
            let tmp = {'addressOfRecord': key, 'contactAddress': db.get(key).toString()}
            s.push(tmp)
        }

        return JSON.stringify(s)
    }
}

<commit_msg>Improve nat traversal<commit_after>/**
 * Stores information on sip devices currently registered in the server.
 * This implementation won't scale to thousands of devices.
 *
 * @author Pedro Sanders
 * @since v1
 */
function LocationService() {
    const HashMap = Packages.java.util.HashMap
    const db = new HashMap()

    function getKey(route) { return route.sentByAddress + route.sentByPort + route.received + route.rport}

    this.put = (addressOfRecord, route) => {
        const routes = db.get(addressOfRecord) || new HashMap()
        const routeKey = getKey(route)
        routes.put(routeKey, route)
        db.put(addressOfRecord, routes)
    }

    // It would be nice if we could enforce the use of 'tel' scheme for DIDs requests
    this.getAORContacts = addressOfRecord => {
        let routes

        if (addressOfRecord instanceof Packages.javax.sip.address.SipURI) {
            const recordKey = addressOfRecord.getScheme() + ":" + addressOfRecord.getUser() + '@' + addressOfRecord.getHost()
            routes =  db.get(recordKey) || db.get('tel:' + addressOfRecord.getUser()) || new HashMap()
        } else if (addressOfRecord instanceof Packages.javax.sip.address.TelURL) {
            routes = db.get('tel:' + addressOfRecord.getUser()) || new HashMap()
        } else {
            routes = db.get(addressOfRecord)
        }

        const iterator = routes.values().iterator()

        return iterator
    }

    this.remove = addressOfRecord => { db.remove(addressOfRecord) }

    this.listAllAsJSON = () => {
        let s = []
        let aors = db.keySet().iterator()

        while(aors.hasNext()) {
            let key = aors.next()

            let i = db.get(key).values().iterator()
            let contactInfo = ''

            while(i.hasNext()) {
                const rObj = i.next()
                const r = '<' + rObj.contactURI + '>;nat=' + rObj.nat + ';timestamp=' + rObj.registeredOn
                if (i.hasNext()) r = r + ','
                contactInfo = contactInfo + r
            }

            let tmp = {
                'addressOfRecord': key,
                'contactInfo': contactInfo
            }
            s.push(tmp)
        }

        return JSON.stringify(s)
    }
}

<|endoftext|>


<commit_before>'use strict';

const assert = require('assert');
const pathlib = require('path');
const EventEmitter = require('events').EventEmitter;
const util = require('util');
const rimraf = require('rimraf');
const Loggr = require('../loggr');
const http = require('http');
const WS = require('ws');
const truncateString = require('lodash.truncate');
const treeKill = require('tree-kill');
const delay = require('../delay');
const screenshotMarkerImg = require('./screenshot-marker');
const screenshotjs = require('../screenshot-js');
const bufferImageSearch = require('../buffer-image-search');

const TEMP_DELETE_RETRIES = BrowserSpawnerBase.TEMP_DELETE_RETRIES = 3;
const TEMP_DELETE_TIMEOUT = BrowserSpawnerBase.TEMP_DELETE_TIMEOUT = 1000;

const DEFAULT_SPAWNER_PORT = 24556;

class TimeoutError extends Error {}

/**
 * @typedef {BrowserSpawnerConfig}
 *
 * @property {String} name - Display name for this browser
 * @property {String} path - Path to browser executable
 * @property {String} [tempDir] - Temporary profile dir path. Defaults to a random directory in cwd
 * @property {Number} width - Recorded app viewport width
 * @property {Number} height - Recorded app viewport height
 * @property {Object} [logger] - Custom Loggr instance
 * @property {Number} [waitForConnectionTimeoutMs = 60000]
 * @property {Number} [waitForConnectionPollIntervalMs = 500]
 * @property {Number} [visibilityTimeoutMs = 60000]
 * @property {Number} [visibilityPollIntervalMs = 3000]
 */

// TODO es6 class

/**
 * @param {BrowserSpawnerConfig} conf
 */
function BrowserSpawnerBase(conf) {
    EventEmitter.call(this);

    assert(conf, 'BrowserSpawnerBase: missing conf');

    this._conf = Object.assign({}, {
        tempDir: pathlib.resolve(conf.tempDir || this._getDefaultTempDir()),
        waitForConnectionTimeoutMs: 60000,
        waitForConnectionPollIntervalMs: 500,
        visibilityTimeoutMs: 60000,
        visibilityPollIntervalMs: 3000,
    }, conf);

    this._log = conf.logger || new Loggr({
        namespace: `BrowserSpawner ${this._conf.name}`,
        indent: '  ',
    });

    this._process = null;

    this._httpServer = http.createServer((request, response) => response.end('404'));
    this._wsServer = null;
    this._wsConn = null;

    Object.defineProperties(this, {
        name:  {
            get: function () {
                return this._conf.name;
            },
        },
        path: {
            get: function () {
                return this._conf.path;
            },
        },
    });
}

util.inherits(BrowserSpawnerBase, EventEmitter);

/**
 *
 * @param {String} url - URL to open
 * @return {Promise}
 */
BrowserSpawnerBase.prototype.start = async function () {
    this._wsServer = new WS.Server({ server: this._httpServer, perMessageDeflate: false });

    this._wsServer.on('connection', this._onWsConnection.bind(this));

    this._wsServer.on('error', err => {
        this._log.error(err.stack || err.message);
    });

    await new Promise(res => this._httpServer.listen(DEFAULT_SPAWNER_PORT, res));

    await this._startBrowser(pathlib.resolve(__dirname, 'browser-spawner-context.html'));
    await this._waitForConnection();

    // TODO await this with events?
    this._sendWsMessage({
        type: 'set-iframe-size',
        width: this._conf.width,
        height: this._conf.height,
    });
    await delay(500);
};

BrowserSpawnerBase.prototype._isConnected = function () {
    return this._wsConn !== null;
}

BrowserSpawnerBase.prototype._waitForConnection = async function () {
    const startTime = Date.now();
    const timeoutMs = this._conf.waitForConnectionTimeoutMs;

    while (!this._isConnected()) {
        if (startTime + timeoutMs < Date.now()) {
            throw new TimeoutError(`BrowserSpawnerBase._waitForConnection: timeout after ${timeoutMs}ms`);
        }

        await delay(this._conf.waitForConnectionPollIntervalMs);
    }
};

BrowserSpawnerBase.prototype._onWsConnection = function (conn) {
    this._wsConn = conn;
};

BrowserSpawnerBase.prototype.isBrowserVisible = async function () {
    const screenshot = await screenshotjs();
    const markerPositions = bufferImageSearch(screenshot, screenshotMarkerImg);
    const isVisible = markerPositions.length === 2;

    if (markerPositions.length !== 0 && markerPositions.length !== 2) {
        this._log.warn(`isBrowserVisible: invalid marker position count: ${markerPositions.length}`);
    }

    this._log.debug(`isBrowserVisible: ${isVisible}`);

    return isVisible;
};

BrowserSpawnerBase.prototype.waitForBrowserVisible = async function () {
    const startTime = Date.now();
    const timeoutMs = this._conf.visibilityTimeoutMs;

    while (!this.isBrowserVisible()) {
        if (startTime + timeoutMs < Date.now()) {
            throw new TimeoutError(`BrowserSpawnerBase.waitForBrowserVisible: timeout after ${timeoutMs}ms`);
        }

        await delay(this._conf.visibilityPollIntervalMs);
    }
};

/**
 * @abstract
 * @param {String} spawnerControlUrl
 * @return {Promise}
 */
BrowserSpawnerBase.prototype._startBrowser = async function () {
    throw new Error('BrowserSpawnerBase::_startBrowser: not implemented');
};

BrowserSpawnerBase.prototype.open = async function (url) {
    if (!this._wsConn) {
        throw new Error('BrowserSpawnerBase::open: not connected');
    }

    const truncatedUrl = truncateString(url, {
        length: 120,
        omission: ' [...]',
    });

    this._log.info(`opening address "${truncatedUrl}"`);

    if (truncatedUrl.length < url) {
        this._log.trace(`opening address "${url}"`);
    }

    this._sendWsMessage({ type: 'open', url: url });

    // hack await
    await delay(500);
};

BrowserSpawnerBase.prototype._sendWsMessage = function (msgArg) {
    let msg = msgArg;
    if (typeof msgArg === 'object') {
        msg = JSON.stringify(msgArg);
    }

    this._wsConn.send(msg);
};

BrowserSpawnerBase.prototype.stop = async function () {
    if (this._process && this._wsServer) {
        await new Promise(resolve => {
            this._wsServer.close(resolve);
        });

        await new Promise((resolve, reject) => {
            treeKill(this._process.pid, err => err ? reject(err) : resolve());
        });

        await new Promise(res => this._httpServer.close(res));
        this._wsConn = null;
        this._process = null;
    }
    else {
        throw new Error('Process or WS Server not running');
    }
};

/**
 *
 * @param {String} eventName - 'close', 'error'
 * @param {Function} callback - Params: 'close' event: none; 'error' event: error
 */
// BrowserSpawnerBase.prototype.on = function () {
//     return Promise.reject(new Error('BrowserSpawnerBase::on: not implemented'))
// };

BrowserSpawnerBase.prototype._getDefaultTempDir = function () {
    throw new Error('BrowserSpawnerBase::_getDefaultTempDir: not implemented');
};

BrowserSpawnerBase.prototype._deleteTempDir = function () {
    // TODO delete all previous temp dirs too

    return new Promise((resolve) => {
        this._log.debug('BrowserSpawnerBase: deleting temp dir');

        let attempts = 0;

        const loop = () => {
            attempts++;

            if (attempts > TEMP_DELETE_RETRIES) {
                throw new Error('BrowserSpawnerBase: maximum retries reached');
            }

            rimraf(this._conf.tempDir, (maybeError) => {
                if (maybeError) {
                    this._log.debug(`BrowserSpawnerBase: deleting temp dir failed: ${maybeError}`);

                    setTimeout(loop, TEMP_DELETE_TIMEOUT);
                }
                else {
                    resolve();
                }
            });
        };

        loop();
    });
};

exports = module.exports = BrowserSpawnerBase;
BrowserSpawnerBase.TimeoutError = TimeoutError;
<commit_msg>Catch and log error when browser fails to close<commit_after>'use strict';

const assert = require('assert');
const pathlib = require('path');
const EventEmitter = require('events').EventEmitter;
const util = require('util');
const rimraf = require('rimraf');
const Loggr = require('../loggr');
const http = require('http');
const WS = require('ws');
const truncateString = require('lodash.truncate');
const treeKill = require('tree-kill');
const delay = require('../delay');
const screenshotMarkerImg = require('./screenshot-marker');
const screenshotjs = require('../screenshot-js');
const bufferImageSearch = require('../buffer-image-search');

const TEMP_DELETE_RETRIES = BrowserSpawnerBase.TEMP_DELETE_RETRIES = 3;
const TEMP_DELETE_TIMEOUT = BrowserSpawnerBase.TEMP_DELETE_TIMEOUT = 1000;

const DEFAULT_SPAWNER_PORT = 24556;

class TimeoutError extends Error {}

/**
 * @typedef {BrowserSpawnerConfig}
 *
 * @property {String} name - Display name for this browser
 * @property {String} path - Path to browser executable
 * @property {String} [tempDir] - Temporary profile dir path. Defaults to a random directory in cwd
 * @property {Number} width - Recorded app viewport width
 * @property {Number} height - Recorded app viewport height
 * @property {Object} [logger] - Custom Loggr instance
 * @property {Number} [waitForConnectionTimeoutMs = 60000]
 * @property {Number} [waitForConnectionPollIntervalMs = 500]
 * @property {Number} [visibilityTimeoutMs = 60000]
 * @property {Number} [visibilityPollIntervalMs = 3000]
 */

// TODO es6 class

/**
 * @param {BrowserSpawnerConfig} conf
 */
function BrowserSpawnerBase(conf) {
    EventEmitter.call(this);

    assert(conf, 'BrowserSpawnerBase: missing conf');

    this._conf = Object.assign({}, {
        tempDir: pathlib.resolve(conf.tempDir || this._getDefaultTempDir()),
        waitForConnectionTimeoutMs: 60000,
        waitForConnectionPollIntervalMs: 500,
        visibilityTimeoutMs: 60000,
        visibilityPollIntervalMs: 3000,
    }, conf);

    this._log = conf.logger || new Loggr({
        namespace: `BrowserSpawner ${this._conf.name}`,
        indent: '  ',
    });

    this._process = null;

    this._httpServer = http.createServer((request, response) => response.end('404'));
    this._wsServer = null;
    this._wsConn = null;

    Object.defineProperties(this, {
        name:  {
            get: function () {
                return this._conf.name;
            },
        },
        path: {
            get: function () {
                return this._conf.path;
            },
        },
    });
}

util.inherits(BrowserSpawnerBase, EventEmitter);

/**
 *
 * @param {String} url - URL to open
 * @return {Promise}
 */
BrowserSpawnerBase.prototype.start = async function () {
    this._wsServer = new WS.Server({ server: this._httpServer, perMessageDeflate: false });

    this._wsServer.on('connection', this._onWsConnection.bind(this));

    this._wsServer.on('error', err => {
        this._log.error(err.stack || err.message);
    });

    await new Promise(res => this._httpServer.listen(DEFAULT_SPAWNER_PORT, res));

    await this._startBrowser(pathlib.resolve(__dirname, 'browser-spawner-context.html'));
    await this._waitForConnection();

    // TODO await this with events?
    this._sendWsMessage({
        type: 'set-iframe-size',
        width: this._conf.width,
        height: this._conf.height,
    });
    await delay(500);
};

BrowserSpawnerBase.prototype._isConnected = function () {
    return this._wsConn !== null;
}

BrowserSpawnerBase.prototype._waitForConnection = async function () {
    const startTime = Date.now();
    const timeoutMs = this._conf.waitForConnectionTimeoutMs;

    while (!this._isConnected()) {
        if (startTime + timeoutMs < Date.now()) {
            throw new TimeoutError(`BrowserSpawnerBase._waitForConnection: timeout after ${timeoutMs}ms`);
        }

        await delay(this._conf.waitForConnectionPollIntervalMs);
    }
};

BrowserSpawnerBase.prototype._onWsConnection = function (conn) {
    this._wsConn = conn;
};

BrowserSpawnerBase.prototype.isBrowserVisible = async function () {
    const screenshot = await screenshotjs();
    const markerPositions = bufferImageSearch(screenshot, screenshotMarkerImg);
    const isVisible = markerPositions.length === 2;

    if (markerPositions.length !== 0 && markerPositions.length !== 2) {
        this._log.warn(`isBrowserVisible: invalid marker position count: ${markerPositions.length}`);
    }

    this._log.debug(`isBrowserVisible: ${isVisible}`);

    return isVisible;
};

BrowserSpawnerBase.prototype.waitForBrowserVisible = async function () {
    const startTime = Date.now();
    const timeoutMs = this._conf.visibilityTimeoutMs;

    while (!this.isBrowserVisible()) {
        if (startTime + timeoutMs < Date.now()) {
            throw new TimeoutError(`BrowserSpawnerBase.waitForBrowserVisible: timeout after ${timeoutMs}ms`);
        }

        await delay(this._conf.visibilityPollIntervalMs);
    }
};

/**
 * @abstract
 * @param {String} spawnerControlUrl
 * @return {Promise}
 */
BrowserSpawnerBase.prototype._startBrowser = async function () {
    throw new Error('BrowserSpawnerBase::_startBrowser: not implemented');
};

BrowserSpawnerBase.prototype.open = async function (url) {
    if (!this._wsConn) {
        throw new Error('BrowserSpawnerBase::open: not connected');
    }

    const truncatedUrl = truncateString(url, {
        length: 120,
        omission: ' [...]',
    });

    this._log.info(`opening address "${truncatedUrl}"`);

    if (truncatedUrl.length < url) {
        this._log.trace(`opening address "${url}"`);
    }

    this._sendWsMessage({ type: 'open', url: url });

    // hack await
    await delay(500);
};

BrowserSpawnerBase.prototype._sendWsMessage = function (msgArg) {
    let msg = msgArg;
    if (typeof msgArg === 'object') {
        msg = JSON.stringify(msgArg);
    }

    this._wsConn.send(msg);
};

BrowserSpawnerBase.prototype.stop = async function () {
    if (this._process && this._wsServer) {
        await new Promise(resolve => {
            this._wsServer.close(resolve);
        });

        try {
            await new Promise((resolve, reject) => {
                treeKill(this._process.pid, err => err ? reject(err) : resolve());
            });
        }
        catch (error) {
            this._log.error(`failed to stop browser: ${error.stack || error.message}`)
        }

        await new Promise(res => this._httpServer.close(res));
        this._wsConn = null;
        this._process = null;
    }
    else {
        throw new Error('Process or WS Server not running');
    }
};

/**
 *
 * @param {String} eventName - 'close', 'error'
 * @param {Function} callback - Params: 'close' event: none; 'error' event: error
 */
// BrowserSpawnerBase.prototype.on = function () {
//     return Promise.reject(new Error('BrowserSpawnerBase::on: not implemented'))
// };

BrowserSpawnerBase.prototype._getDefaultTempDir = function () {
    throw new Error('BrowserSpawnerBase::_getDefaultTempDir: not implemented');
};

BrowserSpawnerBase.prototype._deleteTempDir = function () {
    // TODO delete all previous temp dirs too

    return new Promise((resolve) => {
        this._log.debug('BrowserSpawnerBase: deleting temp dir');

        let attempts = 0;

        const loop = () => {
            attempts++;

            if (attempts > TEMP_DELETE_RETRIES) {
                throw new Error('BrowserSpawnerBase: maximum retries reached');
            }

            rimraf(this._conf.tempDir, (maybeError) => {
                if (maybeError) {
                    this._log.debug(`BrowserSpawnerBase: deleting temp dir failed: ${maybeError}`);

                    setTimeout(loop, TEMP_DELETE_TIMEOUT);
                }
                else {
                    resolve();
                }
            });
        };

        loop();
    });
};

exports = module.exports = BrowserSpawnerBase;
BrowserSpawnerBase.TimeoutError = TimeoutError;
<|endoftext|>


<commit_before>DITTO.chat = {
    message_template: $('#message_template').text(),
    message_input: $('#msg').find('input[type=text]'),
    msgs: $('#msgs'),

    themes: ['sky', 'vine', 'lava', 'gray', 'industrial', 'social'],
    avatars: {},

    privateMessageCallbacks: [],
    outgoingMessageCallbacks: [],

    me: DITTO.chat_name.split('@')[0],

    titleTogglePeriod: 1000,  // can't be any smaller for chrome/ffox

    renderMessage: function (from, msg) {
	// construct skeleton message from template
	var formatted_message = $(this.message_template);

	// add message text
        formatted_message.find('.media-body').text(msg);

	// configure avatar
	var avatar = formatted_message.find('img');
	var avatar_theme = this.avatars[from];
	if (!avatar_theme) {
	    avatar_theme = this.themes[Math.floor(Math.random() * (this.themes.length - 1))];
	    this.avatars[from] = avatar_theme;
	}
	// TODO check .attr with untrusted input is safe!
	avatar.attr('data-src', 'holder.js/50x50/auto/' + avatar_theme + '/text:' + from);
	avatar.attr('alt', from);
	// decide whether avatar goes to the left or right
	if (from === this.me) {
	    formatted_message.find('.media-left').remove();
	    formatted_message.find('.media-body').css('text-align', 'right');
	} else {
	    formatted_message.find('.media-right').remove();
	}
	Holder.run({images:formatted_message.find('img')[0]});

	// add message to page and scroll message in to view
        this.msgs.append(formatted_message);
        this.msgs.scrollTop(this.msgs[0].scrollHeight);
    },

    addPrivateMessageCallback: function (callback) {
	this.privateMessageCallbacks.push(callback);
    },

    addOutgoingMessageCallback: function (callback) {
	this.outgoingMessageCallbacks.push(callback);
    },

    in_focus: true,

    newMessageText: 'New Message...',

    isPageHidden: function () {
	return document.webkitHidden;
    },

    notifyNewMessage: function (callback) {
	// TOOD play sound
	// TODO desktop alert
	var title = $('title');
	this.orig_page_title = title.text();
	title.text(this.newMessageText);
	var self = this;
	this.title_interval = window.setInterval(
	    function () { self.toggleTitle.apply(self); },
	    this.titleTogglePeriod
	);
    },

    toggleTitle: function () {
	var title = $('title');
	if (!this.isPageHidden()) {
	    window.clearTimeout(this.title_interval);
	    title.text(this.orig_page_title);
	} else {
	    if (title.text() === this.newMessageText) {
		title.text(this.orig_page_title);
	    } else {
		title.text(this.newMessageText);
	    }
	}
    }
};

$(document).ready(function () {
    var connection = null;

    DITTO.chat.message_input.focus();

    $('#msg').submit(function (e) {
        e.preventDefault();
	if (!connection) {
	    // TODO give user feedback that we're waiting on the connection?
	    return;
	}
        var msg = DITTO.chat.message_input.val();
        if (msg) {
            DITTO.chat.message_input.val('');
	    DITTO.chat.sendMessage(msg);
        }
    });

    function connect () {
	connection = new Strophe.Connection('ws://' + DITTO.chat_host + ':5280/ws-xmpp');
	connection.rawInput = rawInput;
	connection.rawOutput = rawOutput;

	connection.connect(
	    DITTO.chat_name, DITTO.chat_pass, onConnect
	);
    }

    function onConnect (status) {
	if (status == Strophe.Status.CONNECTING) {
	    console.log('Strophe is connecting.');

	} else if (status == Strophe.Status.CONNFAIL) {
	    console.log('Strophe failed to connect.');

	} else if (status == Strophe.Status.DISCONNECTING) {
	    console.log('Strophe is disconnecting.');

	} else if (status == Strophe.Status.DISCONNECTED) {
	    console.log('Strophe is disconnected.');
            $(document).trigger('disconnected.ditto.chat', connection);

	} else if (status == Strophe.Status.CONNECTED) {
	    console.log('Strophe is connected.');
            $(document).trigger('connected.ditto.chat', connection);
	}
    }

    function rawInput(data) {
	console.log('RECV: ', data);
    }

    function rawOutput(data) {
	console.log('SENT: ', data);
    }

    connect();
});
<commit_msg>timeout instead of interval, not sure it's any smoother<commit_after>DITTO.chat = {
    message_template: $('#message_template').text(),
    message_input: $('#msg').find('input[type=text]'),
    msgs: $('#msgs'),

    themes: ['sky', 'vine', 'lava', 'gray', 'industrial', 'social'],
    avatars: {},

    privateMessageCallbacks: [],
    outgoingMessageCallbacks: [],

    me: DITTO.chat_name.split('@')[0],

    titleTogglePeriod: 1000,  // can't be any smaller for chrome/ffox

    renderMessage: function (from, msg) {
	// construct skeleton message from template
	var formatted_message = $(this.message_template);

	// add message text
        formatted_message.find('.media-body').text(msg);

	// configure avatar
	var avatar = formatted_message.find('img');
	var avatar_theme = this.avatars[from];
	if (!avatar_theme) {
	    avatar_theme = this.themes[Math.floor(Math.random() * (this.themes.length - 1))];
	    this.avatars[from] = avatar_theme;
	}
	// TODO check .attr with untrusted input is safe!
	avatar.attr('data-src', 'holder.js/50x50/auto/' + avatar_theme + '/text:' + from);
	avatar.attr('alt', from);
	// decide whether avatar goes to the left or right
	if (from === this.me) {
	    formatted_message.find('.media-left').remove();
	    formatted_message.find('.media-body').css('text-align', 'right');
	} else {
	    formatted_message.find('.media-right').remove();
	}
	Holder.run({images:formatted_message.find('img')[0]});

	// add message to page and scroll message in to view
        this.msgs.append(formatted_message);
        this.msgs.scrollTop(this.msgs[0].scrollHeight);
    },

    addPrivateMessageCallback: function (callback) {
	this.privateMessageCallbacks.push(callback);
    },

    addOutgoingMessageCallback: function (callback) {
	this.outgoingMessageCallbacks.push(callback);
    },

    in_focus: true,

    newMessageText: 'New Message...',

    isPageHidden: function () {
	return document.webkitHidden;
    },

    notifyNewMessage: function (callback) {
	// TOOD play sound
	// TODO desktop alert
	var title = $('title');
	this.orig_page_title = title.text();
	title.text(this.newMessageText);
	var self = this;
	this.title_interval = window.setTimeout(
	    function () { self.toggleTitle(); },
	    this.titleTogglePeriod
	);
    },

    toggleTitle: function () {
	var title = $('title');
	if (!this.isPageHidden()) {
	    title.text(this.orig_page_title);
	} else {
	    if (title.text() === this.newMessageText) {
		title.text(this.orig_page_title);
	    } else {
		title.text(this.newMessageText);
	    }
	    var self = this;
	    window.setTimeout(
		function () { self.toggleTitle(); },
		this.titleTogglePeriod
	    );
	}
    }
};

$(document).ready(function () {
    var connection = null;

    DITTO.chat.message_input.focus();

    $('#msg').submit(function (e) {
        e.preventDefault();
	if (!connection) {
	    // TODO give user feedback that we're waiting on the connection?
	    return;
	}
        var msg = DITTO.chat.message_input.val();
        if (msg) {
            DITTO.chat.message_input.val('');
	    DITTO.chat.sendMessage(msg);
        }
    });

    function connect () {
	connection = new Strophe.Connection('ws://' + DITTO.chat_host + ':5280/ws-xmpp');
	connection.rawInput = rawInput;
	connection.rawOutput = rawOutput;

	connection.connect(
	    DITTO.chat_name, DITTO.chat_pass, onConnect
	);
    }

    function onConnect (status) {
	if (status == Strophe.Status.CONNECTING) {
	    console.log('Strophe is connecting.');

	} else if (status == Strophe.Status.CONNFAIL) {
	    console.log('Strophe failed to connect.');

	} else if (status == Strophe.Status.DISCONNECTING) {
	    console.log('Strophe is disconnecting.');

	} else if (status == Strophe.Status.DISCONNECTED) {
	    console.log('Strophe is disconnected.');
            $(document).trigger('disconnected.ditto.chat', connection);

	} else if (status == Strophe.Status.CONNECTED) {
	    console.log('Strophe is connected.');
            $(document).trigger('connected.ditto.chat', connection);
	}
    }

    function rawInput(data) {
	console.log('RECV: ', data);
    }

    function rawOutput(data) {
	console.log('SENT: ', data);
    }

    connect();
});
<|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import {TextInput, StyleSheet} from 'react-native'
import * as c from '@frogpond/colors'
import {AllHtmlEntities} from 'html-entities'

const entities = new AllHtmlEntities()

const styles = StyleSheet.create({
	text: {
		color: c.black,
		backgroundColor: c.white,
		paddingHorizontal: 15,
		paddingTop: 10,
		paddingBottom: 10,
	},
})

export const SelectableCell = ({text}: {text: string}) => (
	<TextInput
		dataDetectorTypes="all"
		editable={false}
		multiline={true}
		style={styles.text}
		value={entities.decode(text)}
	/>
)
<commit_msg>remove entity decoding from SelectableCell<commit_after>// @flow
import * as React from 'react'
import {TextInput, StyleSheet} from 'react-native'
import * as c from '@frogpond/colors'

const styles = StyleSheet.create({
	text: {
		color: c.black,
		backgroundColor: c.white,
		paddingHorizontal: 15,
		paddingTop: 10,
		paddingBottom: 10,
	},
})

export const SelectableCell = ({text}: {text: string}) => (
	<TextInput
		dataDetectorTypes="all"
		editable={false}
		multiline={true}
		style={styles.text}
		value={text}
	/>
)
<|endoftext|>


<commit_before>function layout(props) {
	return `<!doctype html>
	<html>
		<head>
			<title>${props.title}</title>
			<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
			${(props.reference.style || [])
				.filter(Boolean)
				.filter(style => Boolean(style.uri))
				.map(style => `<link rel="stylesheet" href="${style.uri}">`)
				.join('\n')}
		</head>
		<body>
			${(props.content.markup || [])
				.filter(Boolean)
				.filter(markup => Boolean(markup.content))
				.map(markup => markup.content)
				.join('\n')}
			${(props.reference.script || [])
				.filter(Boolean)
				.filter(script => Boolean(script.uri))
				.map(script => `<script src="${script.uri}"></script>`)
				.join('\n')}
		</body>
	</html>
	`;
}
export default layout;
<commit_msg>fix: avoid favicon requests for demos<commit_after>function layout(props) {
	return `<!doctype html>
	<html>
		<head>
			<title>${props.title}</title>
			<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
			<link rel="icon" href="data:;base64,iVBORw0KGgo=">
			${(props.reference.style || [])
				.filter(Boolean)
				.filter(style => Boolean(style.uri))
				.map(style => `<link rel="stylesheet" href="${style.uri}">`)
				.join('\n')}
		</head>
		<body>
			${(props.content.markup || [])
				.filter(Boolean)
				.filter(markup => Boolean(markup.content))
				.map(markup => markup.content)
				.join('\n')}
			${(props.reference.script || [])
				.filter(Boolean)
				.filter(script => Boolean(script.uri))
				.map(script => `<script src="${script.uri}"></script>`)
				.join('\n')}
		</body>
	</html>
	`;
}
export default layout;
<|endoftext|>


<commit_before>'use strict';

var debug = require('debug')('indigo:main'),
	express = require('express'),
	ejs = require('ejs'),
	fs = require('fs'),
	less = require('less'),
	routers = require('./libs/routers');

var reqModel, http,
	appdir, portNumber,
	logger, locales, indigo;

global.__appDir = process.cwd() + '/';

debug('__appDir: %s', __appDir);

module.exports = indigo = {

	init: function(nconf) {
		if (typeof(nconf) === 'string') { //path to app.json
			debug('indigo::init nconf - %s', nconf);
			nconf = require('nconf').
					use('file', { file: nconf });
		}

		appdir = __appDir + nconf.get('server:appdir');

		this.locales = locales = require('./libs/locales');
		this.logger = logger = require(nconf.get('logger:path') || './libs/logger')(nconf);
		this.debug = require('debug');

		var service = require(nconf.get('service:path') || './libs/rest')(nconf);

		if (!this.service) {
			Object.defineProperty(this, 'service', {
				get: function() { return Object.create(service); },
				enumerable: true
			});
		}

		reqModel = JSON.stringify(
				require(nconf.get('server:reqmodel:path') || './libs/reqmodel')(nconf));

		portNumber = Number(process.env.PORT || nconf.get('server:port'));

		locales.config(nconf); //initialize locales

		return nconf;
	},

	start: function(nconf, before, after) {

		this.nconf = nconf = this.init(nconf);

		var app = this.app = express();

		app.use(require(nconf.get('server:parser:path') || './libs/parser')(nconf)); //enabled req.body

		app.use(require(nconf.get('server:session:path') || './libs/session')(nconf)); //enabled req.session

		app.use('/', express.static(appdir));

		//http://localhost:8585/indigo/account/ru/templates/login
		app.use('/indigo/:routerPath/:locale/templates/:pageId', function(req, res) {
			req.model = JSON.parse(reqModel);
			locales.init(req, req.params.locale);

			var url = '/' + req.session.locale + '/templates/' + req.params.routerPath + '/' + req.params.pageId + '.html',
				newUrl = getNewURL(req, url);
			debug('template=%s redirect=%s', url, newUrl);
			res.redirect(newUrl);
		});

		routers.init(app, nconf, reqModel, routerRedirectListener);

		app.locals.inject = function(req, url) {
			var newUrl = getNewURL(req, '/' + req.session.locale + '/' + url, '/' + url);
			debug('inject: %s -> %s', url, newUrl);
			req.model.filename = appdir + newUrl;
			try {
				return ejs.render(fs.readFileSync(req.model.filename, 'utf-8'), req.model);
			} catch(e) {
				var code = new Date().getTime();
				logger.error('Code: ', code, '\n', e.toString());
				return '
Internal error. Please contact your system administrator

Code: ' + code;
			}
		};

		// Using the .html extension instead of
		// having to name the views as *.ejs
		app.engine('.html', ejs.__express);

		// Set the folder where the pages are kept
		app.set('views', appdir);

		// Start the server
		http = require('http').createServer(app);

		if (before) {
			before(http, app);
		}

		http.listen(portNumber, function() {
			logger.info('Server is running on port %s', portNumber);
			if (after) {
				after(http, app);
			}
		});
	},

	close: function(done) {
		http.close(done);
	},

	render: function(req, res, fileName, locales) {
		req.model.locales = locales || indigo.getLocales(req);
		req.model.req = req;
		if (fileName.indexOf('.') === -1) {
			fileName += '.html'; //attach default HTML extension
		}
		fileName = appdir + getNewURL(req, '/' + req.session.locale + '/' + fileName, '/' + fileName);
		if (!fs.existsSync(fileName)) {
			debug('FILE_NOT_FOUND %s', fileName);
			if (this.nconf.get('errors:404')) {
				res.setHeader('URL', req.url);
				res.setHeader('PATH', fileName);
				return res.redirect(this.nconf.get('errors:404'));
			} else {
				return res.status(404).end();
			}
		}
		res.render(fileName, req.model);
	},

	error: function(req, res, errorKey, errorCode) {
		var locales = indigo.getLocales(req);
		res.json(errorCode || 400, { error: locales.errors[errorKey] });
	},

	getLocales: function(req, paramName) {
		req.params = req.params || {};
		return locales.init(req, req.params[paramName || 'locale']);
	}
};

function getNewURL(req, url, redirectURL) {
	if (req.session.locale && !fs.existsSync(appdir + url) &&
		url.indexOf('/' + req.session.locale +'/') !== -1) { //try to get file from another locale directory
		debug('url=%s locale=%s lookup=%s', url, req.session.locale, req.session.localeLookup);
		for (var index in req.session.localeLookup) {
			var newUrl = url.replace('/' + req.session.locale + '/', '/' + req.session.localeLookup[index] + '/');
			if (fs.existsSync(appdir + newUrl)) {
				debug('getNewURL %s', newUrl);
				return newUrl;
			}
		}
	}
	if (!fs.existsSync(appdir + url)) {
		url = redirectURL || req.url || url;
	}
	return url;
}

function routerRedirectListener(req, res, next) {
	debug(req.method, req.url, req.originalUrl);

	if (!req.session.locale || req.params.locale !== req.session.locale) {
		locales.init(req, req.params.locale);
	}

	if (req.method === 'GET') {
		var newUrl = getNewURL(req, req.url);
		if (req.originalUrl.indexOf(newUrl) === -1) {
			debug('redirect: %s', newUrl);
			if (newUrl.lastIndexOf('.less') !== -1) {
				fs.readFile(appdir + newUrl, function(error, data) {
					if (!error) {
						data = data.toString();
						less.render(data, {
								filename: appdir + newUrl,
								compress: indigo.nconf.get('environment') !== 'dev'
							}, function (error, result) {
							if (!error) {
								res.statusCode = 302;
								res.set('Content-Type', 'text/css');
								res.write(result.css);
								res.end();
							} else {
								indigo.logger.error('ERROR_LESS: ' + newUrl + ' - ' + error);
								res.send(data);
							}
						});
					} else {
						res.redirect(newUrl);
					}
				});
			} else {
				res.redirect(newUrl);
			}
			return;
		}
	}

	next();
}<commit_msg>Added LAND key into header<commit_after>'use strict';

var debug = require('debug')('indigo:main'),
	express = require('express'),
	ejs = require('ejs'),
	fs = require('fs'),
	less = require('less'),
	routers = require('./libs/routers');

var reqModel, http,
	appdir, portNumber,
	logger, locales, indigo;

global.__appDir = process.cwd() + '/';

debug('__appDir: %s', __appDir);

module.exports = indigo = {

	init: function(nconf) {
		if (typeof(nconf) === 'string') { //path to app.json
			debug('indigo::init nconf - %s', nconf);
			nconf = require('nconf').
					use('file', { file: nconf });
		}

		appdir = __appDir + nconf.get('server:appdir');

		this.locales = locales = require('./libs/locales');
		this.logger = logger = require(nconf.get('logger:path') || './libs/logger')(nconf);
		this.debug = require('debug');

		var service = require(nconf.get('service:path') || './libs/rest')(nconf);

		if (!this.service) {
			Object.defineProperty(this, 'service', {
				get: function() { return Object.create(service); },
				enumerable: true
			});
		}

		reqModel = JSON.stringify(
				require(nconf.get('server:reqmodel:path') || './libs/reqmodel')(nconf));

		portNumber = Number(process.env.PORT || nconf.get('server:port'));

		locales.config(nconf); //initialize locales

		return nconf;
	},

	start: function(nconf, before, after) {

		this.nconf = nconf = this.init(nconf);

		var app = this.app = express();

		app.use(require(nconf.get('server:parser:path') || './libs/parser')(nconf)); //enabled req.body

		app.use(require(nconf.get('server:session:path') || './libs/session')(nconf)); //enabled req.session

		app.use('/', express.static(appdir));

		//http://localhost:8585/indigo/account/ru/templates/login
		app.use('/indigo/:routerPath/:locale/templates/:pageId', function(req, res) {
			req.model = JSON.parse(reqModel);
			locales.init(req, req.params.locale);

			var url = '/' + req.session.locale + '/templates/' + req.params.routerPath + '/' + req.params.pageId + '.html',
				newUrl = getNewURL(req, url);
			debug('template=%s redirect=%s', url, newUrl);
			res.redirect(newUrl);
		});

		routers.init(app, nconf, reqModel, routerRedirectListener);

		app.locals.inject = function(req, url) {
			var newUrl = getNewURL(req, '/' + req.session.locale + '/' + url, '/' + url);
			debug('inject: %s -> %s', url, newUrl);
			req.model.filename = appdir + newUrl;
			try {
				return ejs.render(fs.readFileSync(req.model.filename, 'utf-8'), req.model);
			} catch(e) {
				var code = new Date().getTime();
				logger.error('Code: ', code, '\n', e.toString());
				return '
Internal error. Please contact your system administrator

Code: ' + code;
			}
		};

		// Using the .html extension instead of
		// having to name the views as *.ejs
		app.engine('.html', ejs.__express);

		// Set the folder where the pages are kept
		app.set('views', appdir);

		// Start the server
		http = require('http').createServer(app);

		if (before) {
			before(http, app);
		}

		http.listen(portNumber, function() {
			logger.info('Server is running on port %s', portNumber);
			if (after) {
				after(http, app);
			}
		});
	},

	close: function(done) {
		http.close(done);
	},

	render: function(req, res, fileName, locales) {
		req.model.locales = locales || indigo.getLocales(req);
		req.model.req = req;
		res.setHeader('lang', req.model.locality.langugage);

		if (fileName.indexOf('.') === -1) {
			fileName += '.html'; //attach default HTML extension
		}
		fileName = appdir + getNewURL(req, '/' + req.session.locale + '/' + fileName, '/' + fileName);
		if (!fs.existsSync(fileName)) {
			debug('FILE_NOT_FOUND %s', fileName);
			if (this.nconf.get('errors:404')) {
				res.setHeader('url', req.url);
				res.setHeader('path', fileName);
				return res.redirect(this.nconf.get('errors:404'));
			} else {
				return res.status(404).end();
			}
		}
		res.render(fileName, req.model);
	},

	error: function(req, res, errorKey, errorCode) {
		var locales = indigo.getLocales(req);
		res.json(errorCode || 400, { error: locales.errors[errorKey] });
	},

	getLocales: function(req, paramName) {
		req.params = req.params || {};
		return locales.init(req, req.params[paramName || 'locale']);
	}
};

function getNewURL(req, url, redirectURL) {
	if (req.session.locale && !fs.existsSync(appdir + url) &&
		url.indexOf('/' + req.session.locale +'/') !== -1) { //try to get file from another locale directory
		debug('url=%s locale=%s lookup=%s', url, req.session.locale, req.session.localeLookup);
		for (var index in req.session.localeLookup) {
			var newUrl = url.replace('/' + req.session.locale + '/', '/' + req.session.localeLookup[index] + '/');
			if (fs.existsSync(appdir + newUrl)) {
				debug('getNewURL %s', newUrl);
				return newUrl;
			}
		}
	}
	if (!fs.existsSync(appdir + url)) {
		url = redirectURL || req.url || url;
	}
	return url;
}

function routerRedirectListener(req, res, next) {
	debug(req.method, req.url, req.originalUrl);

	if (!req.session.locale || req.params.locale !== req.session.locale) {
		locales.init(req, req.params.locale);
	}

	if (req.method === 'GET') {
		var newUrl = getNewURL(req, req.url);
		if (req.originalUrl.indexOf(newUrl) === -1) {
			debug('redirect: %s', newUrl);
			if (newUrl.lastIndexOf('.less') !== -1) {
				fs.readFile(appdir + newUrl, function(error, data) {
					if (!error) {
						data = data.toString();
						less.render(data, {
								filename: appdir + newUrl,
								compress: indigo.nconf.get('environment') !== 'dev'
							}, function (error, result) {
							if (!error) {
								res.statusCode = 302;
								res.set('Content-Type', 'text/css');
								res.write(result.css);
								res.end();
							} else {
								indigo.logger.error('ERROR_LESS: ' + newUrl + ' - ' + error);
								res.send(data);
							}
						});
					} else {
						res.redirect(newUrl);
					}
				});
			} else {
				res.redirect(newUrl);
			}
			return;
		}
	}

	next();
}<|endoftext|>


<commit_before>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View, WebView, Platform, ViewStylePropTypes} from 'react-native';
import Bus from './Bus';
import {webviewTarget, webviewProperties, webviewMethods, constructors, WEBVIEW_TARGET} from './webview-binders';
import CanvasRenderingContext2D from './CanvasRenderingContext2D';
import html from './index.html.js';
export {default as Image} from './Image';
export {default as Path2D} from './Path2D';
import './CanvasGradient';

@webviewTarget('canvas')
@webviewProperties({width: 300, height: 150})
@webviewMethods(['toDataURL'])
export default class Canvas extends Component {
  static propTypes = {
    style: PropTypes.shape(ViewStylePropTypes),
    baseUrl: PropTypes.string,
    originWhitelist: PropTypes.arrayOf(PropTypes.string),
  };

  addMessageListener = listener => {
    this.listeners.push(listener);
    return () => this.removeMessageListener(listener);
  };

  removeMessageListener = listener => {
    this.listeners.splice(this.listeners.indexOf(listener), 1);
  };

  loaded = false;
  /**
   * in the mounting process this.webview can be set to null
   */
  webviewPostMessage = message => this.webview && this.webview.postMessage(JSON.stringify(message));

  bus = new Bus(this.webviewPostMessage);
  listeners = [];
  context2D = new CanvasRenderingContext2D(this);

  constructor() {
    super();
    this.bus.pause();
  }

  getContext = (contextType, contextAttributes) => {
    switch (contextType) {
      case '2d': {
        return this.context2D;
      }
    }
    return null;
  };

  postMessage = async message => {
    const {stack} = new Error();
    const {type, payload} = await this.bus.post({id: Math.random(), ...message});
    switch (type) {
      case 'error': {
        const error = new Error(payload.message);
        error.stack = stack;
        throw error;
      }
      case 'json': {
        return payload;
      }
      case 'blob': {
        return atob(payload);
      }
    }
  };

  handleMessage = e => {
    let data = JSON.parse(e.nativeEvent.data);
    switch (data.type) {
      case 'log': {
        // eslint-disable-line no-console
        console.log(...data.payload);
        break;
      }
      case 'error': {
        throw new Error(data.payload.message);
      }
      default: {
        if (data.payload) {
          const constructor = constructors[data.meta.constructor];
          if (constructor) {
            const {payload} = data;
            data = {
              ...data,
              payload: Object.assign(new constructor(this), payload, {[WEBVIEW_TARGET]: data.meta.target}),
            };
          }
          for (const listener of this.listeners) {
            listener(data.payload);
          }
        }
        this.bus.handle(data);
      }
    }
  };

  handleRef = element => {
    this.webview = element;
  };

  handleLoad = () => {
    this.loaded = true;
    this.bus.resume();
  };

  render() {
    const {width, height} = this;
    const {style, baseUrl = '', originWhitelist = ['*']} = this.props;
    if (Platform.OS === 'android') {
      return (
        <View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
          <WebView
            ref={this.handleRef}
            style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
            source={{html, baseUrl}}
            originWhitelist={originWhitelist}
            onMessage={this.handleMessage}
            onLoad={this.handleLoad}
            mixedContentMode="always"
            scalesPageToFit={false}
            javaScriptEnabled
            domStorageEnabled
            thirdPartyCookiesEnabled
          />
        </View>
      );
    }
    return (
      <View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
        <WebView
          ref={this.handleRef}
          style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
          source={{html, baseUrl: 'http://example.com'}}
          onMessage={this.handleMessage}
          onLoad={this.handleLoad}
          scrollEnabled={false}
          scalesPageToFit={false}
        />
      </View>
    );
  }
}
<commit_msg>Fix origin setting for iOS<commit_after>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View, WebView, Platform, ViewStylePropTypes} from 'react-native';
import Bus from './Bus';
import {webviewTarget, webviewProperties, webviewMethods, constructors, WEBVIEW_TARGET} from './webview-binders';
import CanvasRenderingContext2D from './CanvasRenderingContext2D';
import html from './index.html.js';
export {default as Image} from './Image';
export {default as Path2D} from './Path2D';
import './CanvasGradient';

@webviewTarget('canvas')
@webviewProperties({width: 300, height: 150})
@webviewMethods(['toDataURL'])
export default class Canvas extends Component {
  static propTypes = {
    style: PropTypes.shape(ViewStylePropTypes),
    baseUrl: PropTypes.string,
    originWhitelist: PropTypes.arrayOf(PropTypes.string),
  };

  addMessageListener = listener => {
    this.listeners.push(listener);
    return () => this.removeMessageListener(listener);
  };

  removeMessageListener = listener => {
    this.listeners.splice(this.listeners.indexOf(listener), 1);
  };

  loaded = false;
  /**
   * in the mounting process this.webview can be set to null
   */
  webviewPostMessage = message => this.webview && this.webview.postMessage(JSON.stringify(message));

  bus = new Bus(this.webviewPostMessage);
  listeners = [];
  context2D = new CanvasRenderingContext2D(this);

  constructor() {
    super();
    this.bus.pause();
  }

  getContext = (contextType, contextAttributes) => {
    switch (contextType) {
      case '2d': {
        return this.context2D;
      }
    }
    return null;
  };

  postMessage = async message => {
    const {stack} = new Error();
    const {type, payload} = await this.bus.post({id: Math.random(), ...message});
    switch (type) {
      case 'error': {
        const error = new Error(payload.message);
        error.stack = stack;
        throw error;
      }
      case 'json': {
        return payload;
      }
      case 'blob': {
        return atob(payload);
      }
    }
  };

  handleMessage = e => {
    let data = JSON.parse(e.nativeEvent.data);
    switch (data.type) {
      case 'log': {
        // eslint-disable-line no-console
        console.log(...data.payload);
        break;
      }
      case 'error': {
        throw new Error(data.payload.message);
      }
      default: {
        if (data.payload) {
          const constructor = constructors[data.meta.constructor];
          if (constructor) {
            const {payload} = data;
            data = {
              ...data,
              payload: Object.assign(new constructor(this), payload, {[WEBVIEW_TARGET]: data.meta.target}),
            };
          }
          for (const listener of this.listeners) {
            listener(data.payload);
          }
        }
        this.bus.handle(data);
      }
    }
  };

  handleRef = element => {
    this.webview = element;
  };

  handleLoad = () => {
    this.loaded = true;
    this.bus.resume();
  };

  render() {
    const {width, height} = this;
    const {style, baseUrl = '', originWhitelist = ['*']} = this.props;
    if (Platform.OS === 'android') {
      return (
        <View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
          <WebView
            ref={this.handleRef}
            style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
            source={{html, baseUrl}}
            originWhitelist={originWhitelist}
            onMessage={this.handleMessage}
            onLoad={this.handleLoad}
            mixedContentMode="always"
            scalesPageToFit={false}
            javaScriptEnabled
            domStorageEnabled
            thirdPartyCookiesEnabled
            allowUniversalAccessFromFileURLs
          />
        </View>
      );
    }
    return (
      <View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
        <WebView
          ref={this.handleRef}
          style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
          source={{html, baseUrl}}
          originWhitelist={originWhitelist}
          onMessage={this.handleMessage}
          onLoad={this.handleLoad}
          scrollEnabled={false}
          scalesPageToFit={false}
        />
      </View>
    );
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import './Collab.css';
import $ from 'jquery';
import { subscribeToRoom, submitCodeUpdate, leaveExistingLastRoom } from './Api';
import { ToastContainer, toast } from 'react-toastify';
import RoomInfo from './RoomInfo';
import CodeMirror from 'react-codemirror';
import 'react-toastify/dist/ReactToastify.min.css';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript';

class Collab extends Component {
    constructor(props) {
        super(props);

        let userName;
        if (this.props.userName == "" && localStorage.userName == undefined) {
            var isEntryIncorrect = true;
            while (isEntryIncorrect) {
                userName = prompt("What will you be known as on the page?");
                if (typeof(userName) == "string") {
                    userName = userName.trim();
                    if (userName !== "") {
                        localStorage.userName = userName;
                        isEntryIncorrect = false;
                    }
                }
                if (userName == null) {
                    isEntryIncorrect = false;
                }
            }
        }

        this.state = {
            userName: this.props.userName || localStorage.userName || "",
            roomId: this.props.match.params.room || "",
            code: '',
            options: {lineNumbers: true, mode: 'javascript'},
            collaborators: [],
            componentSocketId: 0,
        };
        this.handleChange = this.handleChange.bind(this);
    }

    addNotificationAlert(message) {
        toast(message);
    }

    componentWillReceiveProps(newProps) {
        this.setState({
            roomId: newProps.match.params.room,
            componentSocketId: 0,
            collaborators: [],
            code: ''
        })
        leaveExistingLastRoom();
        subscribeToRoom(newProps.match.params.room, this.state.userName,
            (err, roomId, socketId, connections) => this.handleConnections(err, roomId, socketId, connections),
            (err, code) => this.handleCodeUpdate(err, code),
            (err, socketId) => this.handleDisconnectingUser(err, socketId));
    }

    componentWillUnmount() {
        leaveExistingLastRoom();
    }

    componentDidMount() {
        subscribeToRoom(this.state.roomId, this.state.userName,
            (err, roomId, userName, socketId, connections) => this.handleConnections(err, roomId, userName, socketId, connections),
            (err, code) => this.handleCodeUpdate(err, code),
            (err, socketId) => this.handleDisconnectingUser(err, socketId));
    }

    render() {
        return (

                <ToastContainer />
                <CodeMirror id="codeSpace" value={this.state.code} options={this.state.options} onChange={this.handleChange} />
                <RoomInfo collaborators={this.state.collaborators}/>

        );
    }

    handleChange(newCode) {
        submitCodeUpdate(this.state.roomId, newCode);
        this.setState({
            code: newCode,
        })
    };

    handleCodeUpdate(err, code) {
        var cursorPosition = $('#codeSpace').prop("selectionStart");
        var surroundingCharacters = { beginningCharacter: $('#codeSpace').text()[cursorPosition - 1], endingCharacter: $('#codeSpace').text()[cursorPosition] };
        console.log(code);
        this.setState({
            code: code
        });
        var editor = $('.CodeMirror')[0].CodeMirror;
        editor.getDoc().setValue(this.state.code);
        if (code.length < cursorPosition) {
            //Code was truncated to be shorter than existing code
            $('#codeSpace').prop('selectionStart', code.length);
            $('#codeSpace').prop('selectionEnd', code.length);
        }
        else if (surroundingCharacters.beginningCharacter == code[cursorPosition - 1] && surroundingCharacters.endingCharacter == code[cursorPosition]) {
            //Code is in the same spot, don't move it
            $('#codeSpace').prop("selectionStart", cursorPosition);
            $('#codeSpace').prop("selectionEnd", cursorPosition);
        }
        else if (surroundingCharacters.beginningCharacter == code[cursorPosition - 2] && surroundingCharacters.endingCharacter == code[cursorPosition - 1]) {
            //Code was removed before the caret
            $('#codeSpace').prop('selectionStart', cursorPosition - 1)
            $('#codeSpace').prop('selectionEnd', cursorPosition - 1)
        }
        else {
            //Code was added/removed on the caret
            $('#codeSpace').prop('selectionStart', cursorPosition)
            $('#codeSpace').prop('selectionEnd', cursorPosition)
        }
    }

    handleConnections(err, roomId, socketId, userName, connections) {
        this.setState({
            roomId: roomId
        });
        if (this.state.componentSocketId === 0) {
            this.setState({
                collaborators: connections.currentConnections,
                componentSocketId: socketId,
            });
            this.addNotificationAlert("You joined the page! You are known as: " + userName);
        }
        else {
            this.setState(previousState => ({
                collaborators: connections.currentConnections
            }));
            this.addNotificationAlert("A new user has connected: " + userName);
        }
        if (!window.location.href.includes('/collab/')) {
            window.history.pushState(null, '', '/collab/' + roomId);
        }
        else {
            window.history.pushState(null, '', roomId);
        }
    }

    handleDisconnectingUser(err, socketId) {
        var leaverIndex = this.state.collaborators.indexOf(socketId);
        this.setState({
            collaborators: this.state.collaborators.splice(leaverIndex, 1)
        })
        this.addNotificationAlert("A user has disconnected: " + socketId);
    }
}

export default Collab;
<commit_msg>clean: added editor to component state for easier access<commit_after>import React, { Component } from 'react';
import './Collab.css';
import $ from 'jquery';
import { subscribeToRoom, submitCodeUpdate, leaveExistingLastRoom } from './Api';
import { ToastContainer, toast } from 'react-toastify';
import RoomInfo from './RoomInfo';
import CodeMirror from 'react-codemirror';
import 'react-toastify/dist/ReactToastify.min.css';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript';

class Collab extends Component {
    constructor(props) {
        super(props);

        let userName;
        if (this.props.userName == "" && localStorage.userName == undefined) {
            var isEntryIncorrect = true;
            while (isEntryIncorrect) {
                userName = prompt("What will you be known as on the page?");
                if (typeof(userName) == "string") {
                    userName = userName.trim();
                    if (userName !== "") {
                        localStorage.userName = userName;
                        isEntryIncorrect = false;
                    }
                }
                if (userName == null) {
                    isEntryIncorrect = false;
                }
            }
        }

        this.state = {
            userName: this.props.userName || localStorage.userName || "",
            roomId: this.props.match.params.room || "",
            code: '',
            options: {lineNumbers: true, mode: 'javascript'},
            collaborators: [],
            componentSocketId: 0,
            editor: null
        };
        this.handleChange = this.handleChange.bind(this);
    }

    addNotificationAlert(message) {
        toast(message);
    }

    componentWillReceiveProps(newProps) {
        this.setState({
            roomId: newProps.match.params.room,
            componentSocketId: 0,
            collaborators: [],
            code: ''
        })
        leaveExistingLastRoom();
        subscribeToRoom(newProps.match.params.room, this.state.userName,
            (err, roomId, socketId, connections) => this.handleConnections(err, roomId, socketId, connections),
            (err, code) => this.handleCodeUpdate(err, code),
            (err, socketId) => this.handleDisconnectingUser(err, socketId));
    }

    componentWillUnmount() {
        leaveExistingLastRoom();
    }

    componentDidMount() {
        subscribeToRoom(this.state.roomId, this.state.userName,
            (err, roomId, userName, socketId, connections) => this.handleConnections(err, roomId, userName, socketId, connections),
            (err, code) => this.handleCodeUpdate(err, code),
            (err, socketId) => this.handleDisconnectingUser(err, socketId));
        this.setState({
            editor: $('.CodeMirror')[0].CodeMirror
        })
    }

    render() {
        return (

                <ToastContainer />
                <CodeMirror id="codeSpace" value={this.state.code} options={this.state.options} onChange={this.handleChange} />
                <RoomInfo collaborators={this.state.collaborators}/>

        );
    }

    handleChange(newCode) {
        submitCodeUpdate(this.state.roomId, newCode);
        this.setState({
            code: newCode,
        })
    };

    handleCodeUpdate(err, code) {
        this.setState({
            code: code
        });
        this.state.editor.getDoc().setValue(this.state.code);
    }

    handleConnections(err, roomId, socketId, userName, connections) {
        this.setState({
            roomId: roomId
        });
        if (this.state.componentSocketId === 0) {
            this.setState({
                collaborators: connections.currentConnections,
                componentSocketId: socketId,
            });
            this.addNotificationAlert("You joined the page! You are known as: " + userName);
        }
        else {
            this.setState(previousState => ({
                collaborators: connections.currentConnections
            }));
            this.addNotificationAlert("A new user has connected: " + userName);
        }
        if (!window.location.href.includes('/collab/')) {
            window.history.pushState(null, '', '/collab/' + roomId);
        }
        else {
            window.history.pushState(null, '', roomId);
        }
    }

    handleDisconnectingUser(err, socketId) {
        var leaverIndex = this.state.collaborators.indexOf(socketId);
        this.setState({
            collaborators: this.state.collaborators.splice(leaverIndex, 1)
        })
        this.addNotificationAlert("A user has disconnected: " + socketId);
    }
}

export default Collab;
<|endoftext|>