Merge branch 'develop' into server2server_tls

This commit is contained in:
Mark Haines 2014-09-01 15:51:44 +01:00
commit f5755bcadf
34 changed files with 11045 additions and 142 deletions

View File

@ -1026,7 +1026,7 @@ Getting/Setting your own presence state
REST Path: /presence/$user_id/status REST Path: /presence/$user_id/status
Valid methods: GET/PUT Valid methods: GET/PUT
Required keys: Required keys:
state : [0|1|2|3] - The user's new presence state presence : [0|1|2|3] - The user's new presence state
Optional keys: Optional keys:
status_msg : text string provided by the user to explain their status status_msg : text string provided by the user to explain their status
@ -1039,7 +1039,7 @@ Fetching your presence list
following keys: following keys:
{ {
"user_id" : string giving the observed user's ID "user_id" : string giving the observed user's ID
"state" : int giving their status "presence" : int giving their status
"status_msg" : optional text string "status_msg" : optional text string
"displayname" : optional text string from the user's profile "displayname" : optional text string from the user's profile
"avatar_url" : optional text string from the user's profile "avatar_url" : optional text string from the user's profile

View File

@ -3,31 +3,31 @@
"swaggerVersion": "1.2", "swaggerVersion": "1.2",
"apis": [ "apis": [
{ {
"path": "/login", "path": "-login",
"description": "Login operations" "description": "Login operations"
}, },
{ {
"path": "/registration", "path": "-registration",
"description": "Registration operations" "description": "Registration operations"
}, },
{ {
"path": "/rooms", "path": "-rooms",
"description": "Room operations" "description": "Room operations"
}, },
{ {
"path": "/profile", "path": "-profile",
"description": "Profile operations" "description": "Profile operations"
}, },
{ {
"path": "/presence", "path": "-presence",
"description": "Presence operations" "description": "Presence operations"
}, },
{ {
"path": "/events", "path": "-events",
"description": "Event operations" "description": "Event operations"
}, },
{ {
"path": "/directory", "path": "-directory",
"description": "Directory operations" "description": "Directory operations"
} }
], ],

View File

@ -0,0 +1,5 @@
To get this running:
ln -s ../swagger_matrix
python -m SimpleHTTPServer
Go to http://localhost:8000/swagger.html

View File

@ -0,0 +1,38 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);

View File

@ -0,0 +1,16 @@
/* latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url(http://fonts.gstatic.com/s/droidsans/v5/s-BiyweUPV0v-yRb-cjciPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(http://fonts.gstatic.com/s/droidsans/v5/EFpQQyG9GqCrobXxL-KRMYWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);

View File

@ -0,0 +1 @@
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);

View File

@ -0,0 +1,8 @@
/*
jQuery Wiggle
Author: WonderGroup, Jordan Thomas
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
*/
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};

View File

@ -0,0 +1,125 @@
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,211 @@
var appName;
var popupMask;
var popupDialog;
var clientId;
var realm;
function handleLogin() {
var scopes = [];
if(window.swaggerUi.api.authSchemes
&& window.swaggerUi.api.authSchemes.oauth2
&& window.swaggerUi.api.authSchemes.oauth2.scopes) {
scopes = window.swaggerUi.api.authSchemes.oauth2.scopes;
}
if(window.swaggerUi.api
&& window.swaggerUi.api.info) {
appName = window.swaggerUi.api.info.title;
}
if(popupDialog.length > 0)
popupDialog = popupDialog.last();
else {
popupDialog = $(
[
'<div class="api-popup-dialog">',
'<div class="api-popup-title">Select OAuth2.0 Scopes</div>',
'<div class="api-popup-content">',
'<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
'<a href="#">Learn how to use</a>',
'</p>',
'<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',
'<ul class="api-popup-scopes">',
'</ul>',
'<p class="error-msg"></p>',
'<div class="api-popup-actions"><button class="api-popup-authbtn api-button green" type="button">Authorize</button><button class="api-popup-cancel api-button gray" type="button">Cancel</button></div>',
'</div>',
'</div>'].join(''));
$(document.body).append(popupDialog);
popup = popupDialog.find('ul.api-popup-scopes').empty();
for (i = 0; i < scopes.length; i ++) {
scope = scopes[i];
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"/>' + '<label for="scope_' + i + '">' + scope.scope;
if (scope.description) {
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
}
str += '</label></li>';
popup.append(str);
}
}
var $win = $(window),
dw = $win.width(),
dh = $win.height(),
st = $win.scrollTop(),
dlgWd = popupDialog.outerWidth(),
dlgHt = popupDialog.outerHeight(),
top = (dh -dlgHt)/2 + st,
left = (dw - dlgWd)/2;
popupDialog.css({
top: (top < 0? 0 : top) + 'px',
left: (left < 0? 0 : left) + 'px'
});
popupDialog.find('button.api-popup-cancel').click(function() {
popupMask.hide();
popupDialog.hide();
});
popupDialog.find('button.api-popup-authbtn').click(function() {
popupMask.hide();
popupDialog.hide();
var authSchemes = window.swaggerUi.api.authSchemes;
var host = window.location;
var redirectUrl = host.protocol + '//' + host.host + "/o2c.html";
var url = null;
var p = window.swaggerUi.api.authSchemes;
for (var key in p) {
if (p.hasOwnProperty(key)) {
var o = p[key].grantTypes;
for(var t in o) {
if(o.hasOwnProperty(t) && t === 'implicit') {
var dets = o[t];
url = dets.loginEndpoint.url + "?response_type=token";
window.swaggerUi.tokenName = dets.tokenName;
}
}
}
}
var scopes = []
var o = $('.api-popup-scopes').find('input:checked');
for(k =0; k < o.length; k++) {
scopes.push($(o[k]).attr("scope"));
}
window.enabledScopes=scopes;
url += '&redirect_uri=' + encodeURIComponent(redirectUrl);
url += '&realm=' + encodeURIComponent(realm);
url += '&client_id=' + encodeURIComponent(clientId);
url += '&scope=' + encodeURIComponent(scopes);
window.open(url);
});
popupMask.show();
popupDialog.show();
return;
}
function handleLogout() {
for(key in window.authorizations.authz){
window.authorizations.remove(key)
}
window.enabledScopes = null;
$('.api-ic.ic-on').addClass('ic-off');
$('.api-ic.ic-on').removeClass('ic-on');
// set the info box
$('.api-ic.ic-warning').addClass('ic-error');
$('.api-ic.ic-warning').removeClass('ic-warning');
}
function initOAuth(opts) {
var o = (opts||{});
var errors = [];
appName = (o.appName||errors.push("missing appName"));
popupMask = (o.popupMask||$('#api-common-mask'));
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
clientId = (o.clientId||errors.push("missing client id"));
realm = (o.realm||errors.push("missing realm"));
if(errors.length > 0){
log("auth unable initialize oauth: " + errors);
return;
}
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
$('.api-ic').click(function(s) {
if($(s.target).hasClass('ic-off'))
handleLogin();
else {
handleLogout();
}
false;
});
}
function onOAuthComplete(token) {
if(token) {
if(token.error) {
var checkbox = $('input[type=checkbox],.secured')
checkbox.each(function(pos){
checkbox[pos].checked = false;
});
alert(token.error);
}
else {
var b = token[window.swaggerUi.tokenName];
if(b){
// if all roles are satisfied
var o = null;
$.each($('.auth #api_information_panel'), function(k, v) {
var children = v;
if(children && children.childNodes) {
var requiredScopes = [];
$.each((children.childNodes), function (k1, v1){
var inner = v1.innerHTML;
if(inner)
requiredScopes.push(inner);
});
var diff = [];
for(var i=0; i < requiredScopes.length; i++) {
var s = requiredScopes[i];
if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {
diff.push(s);
}
}
if(diff.length > 0){
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');
$(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');
// sorry, not all scopes are satisfied
$(o).find('.api-ic').addClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
else {
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');
$(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');
// all scopes are satisfied
$(o).find('.api-ic').addClass('ic-info');
$(o).find('.api-ic').removeClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
}
});
window.authorizations.add("oauth2", new ApiKeyAuthorization("Authorization", "Bearer " + b, "header"));
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Matrix Client-Server API Documentation</title>
<link href="./files/css" rel="stylesheet" type="text/css">
<link href="./files/reset.css" media="screen" rel="stylesheet" type="text/css">
<link href="./files/screen.css" media="screen" rel="stylesheet" type="text/css">
<link href="./files/reset.css" media="print" rel="stylesheet" type="text/css">
<link href="./files/screen.css" media="print" rel="stylesheet" type="text/css">
<script type="text/javascript" src="./files/shred.bundle.js"></script>
<script src="./files/jquery-1.8.0.min.js" type="text/javascript"></script>
<script src="./files/jquery.slideto.min.js" type="text/javascript"></script>
<script src="./files/jquery.wiggle.min.js" type="text/javascript"></script>
<script src="./files/jquery.ba-bbq.min.js" type="text/javascript"></script>
<script src="./files/handlebars-1.0.0.js" type="text/javascript"></script>
<script src="./files/underscore-min.js" type="text/javascript"></script>
<script src="./files/backbone-min.js" type="text/javascript"></script>
<script src="./files/swagger.js" type="text/javascript"></script>
<script src="./files/swagger-ui.js" type="text/javascript"></script>
<script src="./files/highlight.7.3.pack.js" type="text/javascript"></script>
<!-- enabling this will enable oauth2 implicit scope support -->
<script src="./files/swagger-oauth.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
url: "http://localhost:8000/swagger_matrix/api-docs",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
log("Loaded SwaggerUI");
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
realm: "your-realms",
appName: "your-app-name"
});
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none"
});
$('#input_apiKey').change(function() {
var key = $('#input_apiKey')[0].value;
log("key: " + key);
if(key && key.trim() != "") {
log("added key " + key);
window.authorizations.add("key", new ApiKeyAuthorization("access_token", key, "query"));
}
})
window.swaggerUi.load();
});
</script>
</head>
<body class="swagger-section">
<div id="header">
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.wordnik.com/">swagger</a>
<form id="api_selector">
<div class="input"><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"></div>
<div class="input"><input placeholder="access_token" id="input_apiKey" name="apiKey" type="text"></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap message-fail">Can't read from server. It may not have the appropriate access-control-origin settings.</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body></html>

View File

@ -168,6 +168,10 @@ Some standard error codes are below:
:``M_NOT_FOUND``: :``M_NOT_FOUND``:
No resource was found for this request. No resource was found for this request.
:``M_LIMIT_EXCEEDED``:
Too many requests have been sent in a short period of time. Wait a while then
try again.
Some requests have unique error codes: Some requests have unique error codes:
:``M_USER_IN_USE``: :``M_USER_IN_USE``:
@ -273,6 +277,7 @@ Example::
} }
- TODO: This creates a room creation event which serves as the root of the PDU graph for this room. - TODO: This creates a room creation event which serves as the root of the PDU graph for this room.
- TODO: Keys for speccing a room name / room topic / invite these users?
Modifying aliases Modifying aliases
----------------- -----------------
@ -284,12 +289,37 @@ Permissions
Joining rooms Joining rooms
------------- -------------
- What is joining? What permissions / access does it give you? How does this affect /initialSync? - TODO: What does the home server have to do to join a user to a room?
- API to hit (``/join/$alias or id``). Explain how alias joining works (auto-resolving). See "Room events" for more info.
- What does the home server have to do?
- Rooms that DON'T need an invite to join. This follows through onto inviting users section.
- Outline invite join dance?
Users need to join a room in order to send and receive events in that room. A user can join a
room by making a request to ``/join/<room alias or id>`` with::
{}
Alternatively, a user can make a request to ``/rooms/<room id>/join`` with the same request content.
This is only provided for symmetry with the other membership APIs: ``/rooms/<room id>/invite`` and
``/rooms/<room id>/leave``. If a room alias was specified, it will be automatically resolved to
a room ID, which will then be joined. The room ID that was joined will be returned in response::
{
"room_id": "!roomid:domain"
}
The membership state for the joining user can also be modified directly to be ``join``
by sending the following request to
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
{
"membership": "join"
}
See the "Room events" section for more information on ``m.room.member``.
After the user has joined a room, they will receive subsequent events in that room. This room
will now appear as an entry in the ``/initialSync`` API.
Some rooms enforce that a user is *invited* to a room before they can join that room. Other
rooms will allow anyone to join the room even if they have not received an invite.
Inviting users Inviting users
-------------- --------------
@ -331,12 +361,33 @@ See the "Room events" section for more information on ``m.room.member``.
Leaving rooms Leaving rooms
------------- -------------
- API to hit (``$roomid/leave``). See "Room events" for more info. A user can leave a room to stop receiving events for that room. A user must have
- Must be joined to leave. How does this affect /initialSync? joined the room before they are eligible to leave the room. If the room is an
- Not ever being in a room is NOT equivalent to have left it (due to membership: leave). "invite-only" room, they will need to be re-invited before they can re-join the room.
- Need to be re-invited if invite-only room. To leave a room, a request should be made to ``/rooms/<room id>/leave`` with::
- If no more HSes in room, can delete room?
- Is there a dance? {}
Alternatively, the membership state for this user in this room can be modified
directly by sending the following request to
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
{
"membership": "leave"
}
See the "Room events" section for more information on ``m.room.member``.
Once a user has left a room, that room will no longer appear on the ``/initialSync``
API. Be aware that leaving a room is not equivalent to have never been
in that room. A user who has previously left a room still maintains some residual state in
that room. Their membership state will be marked as ``leave``. This contrasts with
a user who has *never been invited or joined to that room* who will not have any
membership state for that room.
If all members in a room leave, that room becomes eligible for deletion.
- TODO: Grace period before deletion?
- TODO: Under what conditions should a room NOT be purged?
Events in a room Events in a room
---------------- ----------------
@ -350,7 +401,7 @@ Events in a room
Syncing a room Syncing a room
-------------- --------------
- Single room initial sync. API to hit. Why it might be used (lazy loading) - Single room initial sync. ``rooms/<room id>/initialSync`` API to hit. Why it might be used (lazy loading)
Getting grouped state events Getting grouped state events
---------------------------- ----------------------------
@ -378,6 +429,7 @@ Non-state messages
------------------ ------------------
- m.room.message - m.room.message
- m.room.message.feedback (and compressed format) - m.room.message.feedback (and compressed format)
- voip?
What are they, when are they used, what do they contain, how should they be used What are they, when are they used, what do they contain, how should they be used
@ -490,7 +542,7 @@ Each user has the concept of presence information. This encodes the
"availability" of that user, suitable for display on other user's clients. This "availability" of that user, suitable for display on other user's clients. This
is transmitted as an ``m.presence`` event and is one of the few events which is transmitted as an ``m.presence`` event and is one of the few events which
are sent *outside the context of a room*. The basic piece of presence information are sent *outside the context of a room*. The basic piece of presence information
is represented by the ``state`` key, which is an enum of one of the following: is represented by the ``presence`` key, which is an enum of one of the following:
- ``online`` : The default state when the user is connected to an event stream. - ``online`` : The default state when the user is connected to an event stream.
- ``unavailable`` : The user is not reachable at this time. - ``unavailable`` : The user is not reachable at this time.
@ -500,18 +552,18 @@ is represented by the ``state`` key, which is an enum of one of the following:
- ``hidden`` : TODO. Behaves as offline, but allows the user to see the client - ``hidden`` : TODO. Behaves as offline, but allows the user to see the client
state anyway and generally interact with client features. state anyway and generally interact with client features.
This basic ``state`` field applies to the user as a whole, regardless of how many This basic ``presence`` field applies to the user as a whole, regardless of how many
client devices they have connected. The home server should synchronise this client devices they have connected. The home server should synchronise this
status choice among multiple devices to ensure the user gets a consistent status choice among multiple devices to ensure the user gets a consistent
experience. experience.
Idle Time Idle Time
--------- ---------
As well as the basic ``state`` field, the presence information can also show a sense As well as the basic ``presence`` field, the presence information can also show
of an "idle timer". This should be maintained individually by the user's a sense of an "idle timer". This should be maintained individually by the
clients, and the home server can take the highest reported time as that to user's clients, and the home server can take the highest reported time as that
report. When a user is offline, the home server can still report when the user was last to report. When a user is offline, the home server can still report when the
seen online. user was last seen online.
Transmission Transmission
------------ ------------

View File

@ -52,6 +52,13 @@ def partitionbool(l, func):
class PresenceHandler(BaseHandler): class PresenceHandler(BaseHandler):
STATE_LEVELS = {
PresenceState.OFFLINE: 0,
PresenceState.UNAVAILABLE: 1,
PresenceState.ONLINE: 2,
PresenceState.FREE_FOR_CHAT: 3,
}
def __init__(self, hs): def __init__(self, hs):
super(PresenceHandler, self).__init__(hs) super(PresenceHandler, self).__init__(hs)
@ -135,7 +142,7 @@ class PresenceHandler(BaseHandler):
return self._user_cachemap[user] return self._user_cachemap[user]
else: else:
statuscache = UserPresenceCache() statuscache = UserPresenceCache()
statuscache.update({"state": PresenceState.OFFLINE}, user) statuscache.update({"presence": PresenceState.OFFLINE}, user)
return statuscache return statuscache
def registered_user(self, user): def registered_user(self, user):
@ -173,19 +180,24 @@ class PresenceHandler(BaseHandler):
observed_user=target_user observed_user=target_user
) )
if visible: if not visible:
state = yield self.store.get_presence_state(
target_user.localpart
)
else:
raise SynapseError(404, "Presence information not visible") raise SynapseError(404, "Presence information not visible")
state = yield self.store.get_presence_state(target_user.localpart)
if "mtime" in state:
del state["mtime"]
state["presence"] = state["state"]
if target_user in self._user_cachemap:
state["last_active"] = (
self._user_cachemap[target_user].get_state()["last_active"]
)
else: else:
# TODO(paul): Have remote server send us permissions set # TODO(paul): Have remote server send us permissions set
state = self._get_or_offline_usercache(target_user).get_state() state = self._get_or_offline_usercache(target_user).get_state()
if "mtime" in state and (state["mtime"] is not None): if "last_active" in state:
state["mtime_age"] = int( state["last_active_ago"] = int(
self.clock.time_msec() - state.pop("mtime") self.clock.time_msec() - state.pop("last_active")
) )
defer.returnValue(state) defer.returnValue(state)
@ -202,20 +214,33 @@ class PresenceHandler(BaseHandler):
if target_user != auth_user: if target_user != auth_user:
raise AuthError(400, "Cannot set another user's displayname") raise AuthError(400, "Cannot set another user's displayname")
# TODO(paul): Sanity-check 'state'
if "status_msg" not in state: if "status_msg" not in state:
state["status_msg"] = None state["status_msg"] = None
for k in state.keys(): for k in state.keys():
if k not in ("state", "status_msg"): if k not in ("presence", "state", "status_msg"):
raise SynapseError( raise SynapseError(
400, "Unexpected presence state key '%s'" % (k,) 400, "Unexpected presence state key '%s'" % (k,)
) )
# Handle legacy "state" key for now
if "state" in state:
state["presence"] = state.pop("state")
if state["presence"] not in self.STATE_LEVELS:
raise SynapseError(400, "'%s' is not a valid presence state" %
state["presence"]
)
logger.debug("Updating presence state of %s to %s", logger.debug("Updating presence state of %s to %s",
target_user.localpart, state["state"]) target_user.localpart, state["presence"])
state_to_store = dict(state) state_to_store = dict(state)
state_to_store["state"] = state_to_store.pop("presence")
statuscache=self._get_or_offline_usercache(target_user)
was_level = self.STATE_LEVELS[statuscache.get_state()["presence"]]
now_level = self.STATE_LEVELS[state["presence"]]
yield defer.DeferredList([ yield defer.DeferredList([
self.store.set_presence_state( self.store.set_presence_state(
@ -226,9 +251,10 @@ class PresenceHandler(BaseHandler):
), ),
]) ])
state["mtime"] = self.clock.time_msec() if now_level > was_level:
state["last_active"] = self.clock.time_msec()
now_online = state["state"] != PresenceState.OFFLINE now_online = state["presence"] != PresenceState.OFFLINE
was_polling = target_user in self._user_cachemap was_polling = target_user in self._user_cachemap
if now_online and not was_polling: if now_online and not was_polling:
@ -251,12 +277,12 @@ class PresenceHandler(BaseHandler):
@log_function @log_function
def started_user_eventstream(self, user): def started_user_eventstream(self, user):
# TODO(paul): Use "last online" state # TODO(paul): Use "last online" state
self.set_state(user, user, {"state": PresenceState.ONLINE}) self.set_state(user, user, {"presence": PresenceState.ONLINE})
@log_function @log_function
def stopped_user_eventstream(self, user): def stopped_user_eventstream(self, user):
# TODO(paul): Save current state as "last online" state # TODO(paul): Save current state as "last online" state
self.set_state(user, user, {"state": PresenceState.OFFLINE}) self.set_state(user, user, {"presence": PresenceState.OFFLINE})
@defer.inlineCallbacks @defer.inlineCallbacks
def user_joined_room(self, user, room_id): def user_joined_room(self, user, room_id):
@ -385,9 +411,9 @@ class PresenceHandler(BaseHandler):
observed_user = self.hs.parse_userid(p.pop("observed_user_id")) observed_user = self.hs.parse_userid(p.pop("observed_user_id"))
p["observed_user"] = observed_user p["observed_user"] = observed_user
p.update(self._get_or_offline_usercache(observed_user).get_state()) p.update(self._get_or_offline_usercache(observed_user).get_state())
if "mtime" in p: if "last_active" in p:
p["mtime_age"] = int( p["last_active_ago"] = int(
self.clock.time_msec() - p.pop("mtime") self.clock.time_msec() - p.pop("last_active")
) )
defer.returnValue(presence) defer.returnValue(presence)
@ -576,21 +602,30 @@ class PresenceHandler(BaseHandler):
def _push_presence_remote(self, user, destination, state=None): def _push_presence_remote(self, user, destination, state=None):
if state is None: if state is None:
state = yield self.store.get_presence_state(user.localpart) state = yield self.store.get_presence_state(user.localpart)
del state["mtime"]
state["presence"] = state["state"]
if user in self._user_cachemap:
state["last_active"] = (
self._user_cachemap[user].get_state()["last_active"]
)
yield self.distributor.fire( yield self.distributor.fire(
"collect_presencelike_data", user, state "collect_presencelike_data", user, state
) )
if "mtime" in state: if "last_active" in state:
state = dict(state) state = dict(state)
state["mtime_age"] = int( state["last_active_ago"] = int(
self.clock.time_msec() - state.pop("mtime") self.clock.time_msec() - state.pop("last_active")
) )
user_state = { user_state = {
"user_id": user.to_string(), "user_id": user.to_string(),
} }
user_state.update(**state) user_state.update(**state)
if "state" in user_state and "presence" not in user_state:
user_state["presence"] = user_state["state"]
yield self.federation.send_edu( yield self.federation.send_edu(
destination=destination, destination=destination,
@ -622,9 +657,14 @@ class PresenceHandler(BaseHandler):
state = dict(push) state = dict(push)
del state["user_id"] del state["user_id"]
if "mtime_age" in state: # Legacy handling
state["mtime"] = int( if "presence" not in state:
self.clock.time_msec() - state.pop("mtime_age") state["presence"] = state["state"]
del state["state"]
if "last_active_ago" in state:
state["last_active"] = int(
self.clock.time_msec() - state.pop("last_active_ago")
) )
statuscache = self._get_or_make_usercache(user) statuscache = self._get_or_make_usercache(user)
@ -639,7 +679,7 @@ class PresenceHandler(BaseHandler):
statuscache=statuscache, statuscache=statuscache,
) )
if state["state"] == PresenceState.OFFLINE: if state["presence"] == PresenceState.OFFLINE:
del self._user_cachemap[user] del self._user_cachemap[user]
for poll in content.get("poll", []): for poll in content.get("poll", []):
@ -672,10 +712,9 @@ class PresenceHandler(BaseHandler):
yield defer.DeferredList(deferreds) yield defer.DeferredList(deferreds)
@defer.inlineCallbacks @defer.inlineCallbacks
def push_update_to_local_and_remote(self, observed_user, def push_update_to_local_and_remote(self, observed_user, statuscache,
users_to_push=[], room_ids=[], users_to_push=[], room_ids=[],
remote_domains=[], remote_domains=[]):
statuscache=None):
localusers, remoteusers = partitionbool( localusers, remoteusers = partitionbool(
users_to_push, users_to_push,
@ -804,6 +843,7 @@ class UserPresenceCache(object):
def update(self, state, serial): def update(self, state, serial):
assert("mtime_age" not in state) assert("mtime_age" not in state)
assert("state" not in state)
self.state.update(state) self.state.update(state)
# Delete keys that are now 'None' # Delete keys that are now 'None'
@ -820,15 +860,21 @@ class UserPresenceCache(object):
def get_state(self): def get_state(self):
# clone it so caller can't break our cache # clone it so caller can't break our cache
return dict(self.state) state = dict(self.state)
# Legacy handling
if "presence" in state:
state["state"] = state["presence"]
return state
def make_event(self, user, clock): def make_event(self, user, clock):
content = self.get_state() content = self.get_state()
content["user_id"] = user.to_string() content["user_id"] = user.to_string()
if "mtime" in content: if "last_active" in content:
content["mtime_age"] = int( content["last_active_ago"] = int(
clock.time_msec() - content.pop("mtime") clock.time_msec() - content.pop("last_active")
) )
return {"type": "m.presence", "content": content} return {"type": "m.presence", "content": content}

View File

@ -48,7 +48,11 @@ class PresenceStatusRestServlet(RestServlet):
try: try:
content = json.loads(request.content.read()) content = json.loads(request.content.read())
state["state"] = content.pop("state") # Legacy handling
if "state" in content:
state["presence"] = content.pop("state")
else:
state["presence"] = content.pop("presence")
if "status_msg" in content: if "status_msg" in content:
state["status_msg"] = content.pop("status_msg") state["status_msg"] = content.pop("status_msg")

View File

@ -35,8 +35,6 @@ ONLINE = PresenceState.ONLINE
logging.getLogger().addHandler(logging.NullHandler()) logging.getLogger().addHandler(logging.NullHandler())
#logging.getLogger().addHandler(logging.StreamHandler())
#logging.getLogger().setLevel(logging.DEBUG)
def _expect_edu(destination, edu_type, content, origin="test"): def _expect_edu(destination, edu_type, content, origin="test"):
@ -141,7 +139,8 @@ class PresenceStateTestCase(unittest.TestCase):
target_user=self.u_apple, auth_user=self.u_apple target_user=self.u_apple, auth_user=self.u_apple
) )
self.assertEquals({"state": ONLINE, "status_msg": "Online"}, self.assertEquals(
{"state": ONLINE, "presence": ONLINE, "status_msg": "Online"},
state state
) )
mocked_get.assert_called_with("apple") mocked_get.assert_called_with("apple")
@ -157,7 +156,8 @@ class PresenceStateTestCase(unittest.TestCase):
target_user=self.u_apple, auth_user=self.u_banana target_user=self.u_apple, auth_user=self.u_banana
) )
self.assertEquals({"state": ONLINE, "status_msg": "Online"}, self.assertEquals(
{"state": ONLINE, "presence": ONLINE, "status_msg": "Online"},
state state
) )
mocked_get.assert_called_with("apple") mocked_get.assert_called_with("apple")
@ -175,7 +175,10 @@ class PresenceStateTestCase(unittest.TestCase):
target_user=self.u_apple, auth_user=self.u_clementine target_user=self.u_apple, auth_user=self.u_clementine
) )
self.assertEquals({"state": ONLINE, "status_msg": "Online"}, state) self.assertEquals(
{"state": ONLINE, "presence": ONLINE, "status_msg": "Online"},
state
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_get_disallowed_state(self): def test_get_disallowed_state(self):
@ -202,20 +205,20 @@ class PresenceStateTestCase(unittest.TestCase):
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_apple, auth_user=self.u_apple, target_user=self.u_apple, auth_user=self.u_apple,
state={"state": UNAVAILABLE, "status_msg": "Away"}) state={"presence": UNAVAILABLE, "status_msg": "Away"})
mocked_set.assert_called_with("apple", mocked_set.assert_called_with("apple",
{"state": UNAVAILABLE, "status_msg": "Away"}) {"state": UNAVAILABLE, "status_msg": "Away"})
self.mock_start.assert_called_with(self.u_apple, self.mock_start.assert_called_with(self.u_apple,
state={ state={
"state": UNAVAILABLE, "presence": UNAVAILABLE,
"status_msg": "Away", "status_msg": "Away",
"mtime": 1000000, # MockClock "last_active": 1000000, # MockClock
}) })
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_apple, auth_user=self.u_apple, target_user=self.u_apple, auth_user=self.u_apple,
state={"state": OFFLINE}) state={"presence": OFFLINE})
self.mock_stop.assert_called_with(self.u_apple) self.mock_stop.assert_called_with(self.u_apple)
@ -449,28 +452,35 @@ class PresenceInvitesTestCase(unittest.TestCase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_get_presence_list(self): def test_get_presence_list(self):
self.datastore.get_presence_list.return_value = defer.succeed( self.datastore.get_presence_list.return_value = defer.succeed(
[{"observed_user_id": "@banana:test"}] [{"observed_user_id": "@banana:test"}]
) )
presence = yield self.handler.get_presence_list( presence = yield self.handler.get_presence_list(
observer_user=self.u_apple) observer_user=self.u_apple)
self.assertEquals([{"observed_user": self.u_banana, self.assertEquals([
"state": OFFLINE}], presence) {"observed_user": self.u_banana,
"presence": OFFLINE,
"state": OFFLINE},
], presence)
self.datastore.get_presence_list.assert_called_with("apple", self.datastore.get_presence_list.assert_called_with("apple",
accepted=None) accepted=None
)
self.datastore.get_presence_list.return_value = defer.succeed( self.datastore.get_presence_list.return_value = defer.succeed(
[{"observed_user_id": "@banana:test"}] [{"observed_user_id": "@banana:test"}]
) )
presence = yield self.handler.get_presence_list( presence = yield self.handler.get_presence_list(
observer_user=self.u_apple, accepted=True) observer_user=self.u_apple, accepted=True
)
self.assertEquals([{"observed_user": self.u_banana, self.assertEquals([
"state": OFFLINE}], presence) {"observed_user": self.u_banana,
"presence": OFFLINE,
"state": OFFLINE},
], presence)
self.datastore.get_presence_list.assert_called_with("apple", self.datastore.get_presence_list.assert_called_with("apple",
accepted=True) accepted=True)
@ -611,6 +621,9 @@ class PresencePushTestCase(unittest.TestCase):
# TODO(paul): Gut-wrenching # TODO(paul): Gut-wrenching
self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
self.handler._user_cachemap[self.u_apple].update(
{"presence": OFFLINE}, serial=0
)
apple_set = self.handler._local_pushmap.setdefault("apple", set()) apple_set = self.handler._local_pushmap.setdefault("apple", set())
apple_set.add(self.u_banana) apple_set.add(self.u_banana)
apple_set.add(self.u_clementine) apple_set.add(self.u_clementine)
@ -618,7 +631,8 @@ class PresencePushTestCase(unittest.TestCase):
self.assertEquals(self.event_source.get_current_key(), 0) self.assertEquals(self.event_source.get_current_key(), 0)
yield self.handler.set_state(self.u_apple, self.u_apple, yield self.handler.set_state(self.u_apple, self.u_apple,
{"state": ONLINE}) {"presence": ONLINE}
)
self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(self.event_source.get_current_key(), 1)
self.assertEquals( self.assertEquals(
@ -627,8 +641,9 @@ class PresencePushTestCase(unittest.TestCase):
{"type": "m.presence", {"type": "m.presence",
"content": { "content": {
"user_id": "@apple:test", "user_id": "@apple:test",
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 0, "last_active_ago": 0,
}}, }},
], ],
) )
@ -636,13 +651,21 @@ class PresencePushTestCase(unittest.TestCase):
presence = yield self.handler.get_presence_list( presence = yield self.handler.get_presence_list(
observer_user=self.u_apple, accepted=True) observer_user=self.u_apple, accepted=True)
self.assertEquals([ self.assertEquals(
{"observed_user": self.u_banana, "state": OFFLINE}, [
{"observed_user": self.u_clementine, "state": OFFLINE}], {"observed_user": self.u_banana,
presence) "presence": OFFLINE,
"state": OFFLINE},
{"observed_user": self.u_clementine,
"presence": OFFLINE,
"state": OFFLINE},
],
presence
)
yield self.handler.set_state(self.u_banana, self.u_banana, yield self.handler.set_state(self.u_banana, self.u_banana,
{"state": ONLINE}) {"presence": ONLINE}
)
self.clock.advance_time(2) self.clock.advance_time(2)
@ -651,9 +674,11 @@ class PresencePushTestCase(unittest.TestCase):
self.assertEquals([ self.assertEquals([
{"observed_user": self.u_banana, {"observed_user": self.u_banana,
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 2000}, "last_active_ago": 2000},
{"observed_user": self.u_clementine, {"observed_user": self.u_clementine,
"presence": OFFLINE,
"state": OFFLINE}, "state": OFFLINE},
], presence) ], presence)
@ -666,8 +691,9 @@ class PresencePushTestCase(unittest.TestCase):
{"type": "m.presence", {"type": "m.presence",
"content": { "content": {
"user_id": "@banana:test", "user_id": "@banana:test",
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 2000 "last_active_ago": 2000
}}, }},
] ]
) )
@ -682,8 +708,9 @@ class PresencePushTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@apple:test", {"user_id": "@apple:test",
"presence": u"online",
"state": u"online", "state": u"online",
"mtime_age": 0}, "last_active_ago": 0},
], ],
} }
) )
@ -699,11 +726,14 @@ class PresencePushTestCase(unittest.TestCase):
# TODO(paul): Gut-wrenching # TODO(paul): Gut-wrenching
self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
self.handler._user_cachemap[self.u_apple].update(
{"presence": OFFLINE}, serial=0
)
apple_set = self.handler._remote_sendmap.setdefault("apple", set()) apple_set = self.handler._remote_sendmap.setdefault("apple", set())
apple_set.add(self.u_potato.domain) apple_set.add(self.u_potato.domain)
yield self.handler.set_state(self.u_apple, self.u_apple, yield self.handler.set_state(self.u_apple, self.u_apple,
{"state": ONLINE} {"presence": ONLINE}
) )
yield put_json.await_calls() yield put_json.await_calls()
@ -726,7 +756,7 @@ class PresencePushTestCase(unittest.TestCase):
"push": [ "push": [
{"user_id": "@potato:remote", {"user_id": "@potato:remote",
"state": "online", "state": "online",
"mtime_age": 1000}, "last_active_ago": 1000},
], ],
} }
) )
@ -741,8 +771,9 @@ class PresencePushTestCase(unittest.TestCase):
{"type": "m.presence", {"type": "m.presence",
"content": { "content": {
"user_id": "@potato:remote", "user_id": "@potato:remote",
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 1000, "last_active_ago": 1000,
}} }}
] ]
) )
@ -751,7 +782,10 @@ class PresencePushTestCase(unittest.TestCase):
state = yield self.handler.get_state(self.u_potato, self.u_apple) state = yield self.handler.get_state(self.u_potato, self.u_apple)
self.assertEquals({"state": ONLINE, "mtime_age": 3000}, state) self.assertEquals(
{"state": ONLINE, "presence": ONLINE, "last_active_ago": 3000},
state
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_join_room_local(self): def test_join_room_local(self):
@ -763,8 +797,8 @@ class PresencePushTestCase(unittest.TestCase):
self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
self.handler._user_cachemap[self.u_clementine].update( self.handler._user_cachemap[self.u_clementine].update(
{ {
"state": PresenceState.ONLINE, "presence": PresenceState.ONLINE,
"mtime": self.clock.time_msec(), "last_active": self.clock.time_msec(),
}, self.u_clementine }, self.u_clementine
) )
@ -781,8 +815,9 @@ class PresencePushTestCase(unittest.TestCase):
{"type": "m.presence", {"type": "m.presence",
"content": { "content": {
"user_id": "@clementine:test", "user_id": "@clementine:test",
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 0, "last_active_ago": 0,
}} }}
] ]
) )
@ -798,7 +833,8 @@ class PresencePushTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@apple:test", {"user_id": "@apple:test",
"state": "online"}, "presence": "online",
"state": "online"},
], ],
} }
), ),
@ -812,7 +848,8 @@ class PresencePushTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@banana:test", {"user_id": "@banana:test",
"state": "offline"}, "presence": "offline",
"state": "offline"},
], ],
} }
), ),
@ -823,7 +860,7 @@ class PresencePushTestCase(unittest.TestCase):
# TODO(paul): Gut-wrenching # TODO(paul): Gut-wrenching
self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
self.handler._user_cachemap[self.u_apple].update( self.handler._user_cachemap[self.u_apple].update(
{"state": PresenceState.ONLINE}, self.u_apple) {"presence": PresenceState.ONLINE}, self.u_apple)
self.room_members = [self.u_apple, self.u_banana] self.room_members = [self.u_apple, self.u_banana]
yield self.distributor.fire("user_joined_room", self.u_potato, yield self.distributor.fire("user_joined_room", self.u_potato,
@ -841,7 +878,8 @@ class PresencePushTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@clementine:test", {"user_id": "@clementine:test",
"state": "online"}, "presence": "online",
"state": "online"},
], ],
} }
), ),
@ -851,7 +889,7 @@ class PresencePushTestCase(unittest.TestCase):
self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
self.handler._user_cachemap[self.u_clementine].update( self.handler._user_cachemap[self.u_clementine].update(
{"state": ONLINE}, self.u_clementine) {"presence": ONLINE}, self.u_clementine)
self.room_members.append(self.u_potato) self.room_members.append(self.u_potato)
yield self.distributor.fire("user_joined_room", self.u_clementine, yield self.distributor.fire("user_joined_room", self.u_clementine,
@ -935,7 +973,8 @@ class PresencePollingTestCase(unittest.TestCase):
def get_presence_state(user_localpart): def get_presence_state(user_localpart):
return defer.succeed( return defer.succeed(
{"state": self.current_user_state[user_localpart], {"state": self.current_user_state[user_localpart],
"status_msg": None} "status_msg": None,
"mtime": 123456000}
) )
self.datastore.get_presence_state = get_presence_state self.datastore.get_presence_state = get_presence_state
@ -969,7 +1008,7 @@ class PresencePollingTestCase(unittest.TestCase):
# apple goes online # apple goes online
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_apple, auth_user=self.u_apple, target_user=self.u_apple, auth_user=self.u_apple,
state={"state": ONLINE} state={"presence": ONLINE}
) )
# apple should see both banana and clementine currently offline # apple should see both banana and clementine currently offline
@ -992,8 +1031,9 @@ class PresencePollingTestCase(unittest.TestCase):
# banana goes online # banana goes online
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_banana, auth_user=self.u_banana, target_user=self.u_banana, auth_user=self.u_banana,
state={"state": ONLINE}) state={"presence": ONLINE}
)
# apple and banana should now both see each other online # apple and banana should now both see each other online
self.mock_update_client.assert_has_calls([ self.mock_update_client.assert_has_calls([
@ -1013,8 +1053,9 @@ class PresencePollingTestCase(unittest.TestCase):
# apple goes offline # apple goes offline
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_apple, auth_user=self.u_apple, target_user=self.u_apple, auth_user=self.u_apple,
state={"state": OFFLINE}) state={"presence": OFFLINE}
)
# banana should now be told apple is offline # banana should now be told apple is offline
self.mock_update_client.assert_has_calls([ self.mock_update_client.assert_has_calls([
@ -1027,7 +1068,6 @@ class PresencePollingTestCase(unittest.TestCase):
self.assertFalse("banana" in self.handler._local_pushmap) self.assertFalse("banana" in self.handler._local_pushmap)
self.assertFalse("clementine" in self.handler._local_pushmap) self.assertFalse("clementine" in self.handler._local_pushmap)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_remote_poll_send(self): def test_remote_poll_send(self):
put_json = self.mock_http_client.put_json put_json = self.mock_http_client.put_json
@ -1057,8 +1097,9 @@ class PresencePollingTestCase(unittest.TestCase):
# clementine goes online # clementine goes online
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_clementine, auth_user=self.u_clementine, target_user=self.u_clementine, auth_user=self.u_clementine,
state={"state": ONLINE}) state={"presence": ONLINE}
)
yield put_json.await_calls() yield put_json.await_calls()
@ -1085,7 +1126,7 @@ class PresencePollingTestCase(unittest.TestCase):
# fig goes online; shouldn't send a second poll # fig goes online; shouldn't send a second poll
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_fig, auth_user=self.u_fig, target_user=self.u_fig, auth_user=self.u_fig,
state={"state": ONLINE} state={"presence": ONLINE}
) )
# reactor.iterate(delay=0) # reactor.iterate(delay=0)
@ -1095,7 +1136,7 @@ class PresencePollingTestCase(unittest.TestCase):
# fig goes offline # fig goes offline
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_fig, auth_user=self.u_fig, target_user=self.u_fig, auth_user=self.u_fig,
state={"state": OFFLINE} state={"presence": OFFLINE}
) )
reactor.iterate(delay=0) reactor.iterate(delay=0)
@ -1116,8 +1157,9 @@ class PresencePollingTestCase(unittest.TestCase):
# clementine goes offline # clementine goes offline
yield self.handler.set_state( yield self.handler.set_state(
target_user=self.u_clementine, auth_user=self.u_clementine, target_user=self.u_clementine, auth_user=self.u_clementine,
state={"state": OFFLINE}) state={"presence": OFFLINE}
)
yield put_json.await_calls() yield put_json.await_calls()
@ -1135,6 +1177,7 @@ class PresencePollingTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@banana:test", {"user_id": "@banana:test",
"presence": "offline",
"state": "offline", "state": "offline",
"status_msg": None}, "status_msg": None},
], ],

View File

@ -166,7 +166,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
# TODO(paul): Gut-wrenching # TODO(paul): Gut-wrenching
from synapse.handlers.presence import UserPresenceCache from synapse.handlers.presence import UserPresenceCache
self.handlers.presence_handler._user_cachemap[self.u_apple] = ( self.handlers.presence_handler._user_cachemap[self.u_apple] = (
UserPresenceCache()) UserPresenceCache()
)
self.handlers.presence_handler._user_cachemap[self.u_apple].update(
{"presence": OFFLINE}, serial=0
)
apple_set = self.handlers.presence_handler._local_pushmap.setdefault( apple_set = self.handlers.presence_handler._local_pushmap.setdefault(
"apple", set()) "apple", set())
apple_set.add(self.u_banana) apple_set.add(self.u_banana)
@ -182,11 +186,13 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
self.assertEquals([ self.assertEquals([
{"observed_user": self.u_banana, {"observed_user": self.u_banana,
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"mtime_age": 0, "last_active_ago": 0,
"displayname": "Frank", "displayname": "Frank",
"avatar_url": "http://foo"}, "avatar_url": "http://foo"},
{"observed_user": self.u_clementine, {"observed_user": self.u_clementine,
"presence": OFFLINE,
"state": OFFLINE}], "state": OFFLINE}],
presence) presence)
@ -199,8 +205,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
statuscache = self.mock_update_client.call_args[1]["statuscache"] statuscache = self.mock_update_client.call_args[1]["statuscache"]
self.assertEquals({ self.assertEquals({
"state": ONLINE, "presence": ONLINE,
"mtime": 1000000, # MockClock "last_active": 1000000, # MockClock
"displayname": "Frank", "displayname": "Frank",
"avatar_url": "http://foo", "avatar_url": "http://foo",
}, statuscache.state) }, statuscache.state)
@ -222,8 +228,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
statuscache = self.mock_update_client.call_args[1]["statuscache"] statuscache = self.mock_update_client.call_args[1]["statuscache"]
self.assertEquals({ self.assertEquals({
"state": ONLINE, "presence": ONLINE,
"mtime": 1000000, # MockClock "last_active": 1000000, # MockClock
"displayname": "I am an Apple", "displayname": "I am an Apple",
"avatar_url": "http://foo", "avatar_url": "http://foo",
}, statuscache.state) }, statuscache.state)
@ -241,7 +247,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
# TODO(paul): Gut-wrenching # TODO(paul): Gut-wrenching
from synapse.handlers.presence import UserPresenceCache from synapse.handlers.presence import UserPresenceCache
self.handlers.presence_handler._user_cachemap[self.u_apple] = ( self.handlers.presence_handler._user_cachemap[self.u_apple] = (
UserPresenceCache()) UserPresenceCache()
)
self.handlers.presence_handler._user_cachemap[self.u_apple].update(
{"presence": OFFLINE}, serial=0
)
apple_set = self.handlers.presence_handler._remote_sendmap.setdefault( apple_set = self.handlers.presence_handler._remote_sendmap.setdefault(
"apple", set()) "apple", set())
apple_set.add(self.u_potato.domain) apple_set.add(self.u_potato.domain)
@ -255,8 +265,9 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
content={ content={
"push": [ "push": [
{"user_id": "@apple:test", {"user_id": "@apple:test",
"presence": "online",
"state": "online", "state": "online",
"mtime_age": 0, "last_active_ago": 0,
"displayname": "Frank", "displayname": "Frank",
"avatar_url": "http://foo"}, "avatar_url": "http://foo"},
], ],
@ -293,14 +304,16 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
statuscache=ANY) statuscache=ANY)
statuscache = self.mock_update_client.call_args[1]["statuscache"] statuscache = self.mock_update_client.call_args[1]["statuscache"]
self.assertEquals({"state": ONLINE, self.assertEquals({"presence": ONLINE,
"displayname": "Frank", "displayname": "Frank",
"avatar_url": "http://foo"}, statuscache.state) "avatar_url": "http://foo"}, statuscache.state)
state = yield self.handlers.presence_handler.get_state(self.u_potato, state = yield self.handlers.presence_handler.get_state(self.u_potato,
self.u_apple) self.u_apple)
self.assertEquals({"state": ONLINE, self.assertEquals(
"displayname": "Frank", {"presence": ONLINE,
"avatar_url": "http://foo"}, "state": ONLINE,
state) "displayname": "Frank",
"avatar_url": "http://foo"},
state)

View File

@ -98,8 +98,10 @@ class PresenceStateTestCase(unittest.TestCase):
"/presence/%s/status" % (myid), None) "/presence/%s/status" % (myid), None)
self.assertEquals(200, code) self.assertEquals(200, code)
self.assertEquals({"state": ONLINE, "status_msg": "Available"}, self.assertEquals(
response) {"presence": ONLINE, "state": ONLINE, "status_msg": "Available"},
response
)
mocked_get.assert_called_with("apple") mocked_get.assert_called_with("apple")
@defer.inlineCallbacks @defer.inlineCallbacks
@ -109,7 +111,7 @@ class PresenceStateTestCase(unittest.TestCase):
(code, response) = yield self.mock_resource.trigger("PUT", (code, response) = yield self.mock_resource.trigger("PUT",
"/presence/%s/status" % (myid), "/presence/%s/status" % (myid),
'{"state": "unavailable", "status_msg": "Away"}') '{"presence": "unavailable", "status_msg": "Away"}')
self.assertEquals(200, code) self.assertEquals(200, code)
mocked_set.assert_called_with("apple", mocked_set.assert_called_with("apple",
@ -173,9 +175,9 @@ class PresenceListTestCase(unittest.TestCase):
"/presence/list/%s" % (myid), None) "/presence/list/%s" % (myid), None)
self.assertEquals(200, code) self.assertEquals(200, code)
self.assertEquals( self.assertEquals([
[{"user_id": "@banana:test", "state": OFFLINE}], response {"user_id": "@banana:test", "presence": OFFLINE, "state": OFFLINE},
) ], response)
self.datastore.get_presence_list.assert_called_with( self.datastore.get_presence_list.assert_called_with(
"apple", accepted=True "apple", accepted=True
@ -314,7 +316,8 @@ class PresenceEventStreamTestCase(unittest.TestCase):
[]) [])
yield self.presence.set_state(self.u_banana, self.u_banana, yield self.presence.set_state(self.u_banana, self.u_banana,
state={"state": ONLINE}) state={"presence": ONLINE}
)
(code, response) = yield self.mock_resource.trigger("GET", (code, response) = yield self.mock_resource.trigger("GET",
"/events?from=0_1_0&timeout=0", None) "/events?from=0_1_0&timeout=0", None)
@ -324,8 +327,9 @@ class PresenceEventStreamTestCase(unittest.TestCase):
{"type": "m.presence", {"type": "m.presence",
"content": { "content": {
"user_id": "@banana:test", "user_id": "@banana:test",
"presence": ONLINE,
"state": ONLINE, "state": ONLINE,
"displayname": "Frank", "displayname": "Frank",
"mtime_age": 0, "last_active_ago": 0,
}}, }},
]}, response) ]}, response)

View File

@ -201,9 +201,15 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
} }
} }
else { else {
// selectively update membership else it will nuke the picture and displayname too :/ // selectively update membership and presence else it will nuke the picture and displayname too :/
var member = $scope.members[target_user_id]; var member = $scope.members[target_user_id];
member.content.membership = chunk.content.membership; member.membership = chunk.content.membership;
if ("state" in chunk.content) {
member.presenceState = chunk.content.state;
}
if ("mtime_age" in chunk.content) {
member.mtime_age = chunk.content.mtime_age;
}
} }
}; };
@ -331,6 +337,11 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
// Make sure the initialSync has been before going further // Make sure the initialSync has been before going further
eventHandlerService.waitForInitialSyncCompletion().then( eventHandlerService.waitForInitialSyncCompletion().then(
function() { function() {
// Some data has been retrieved from the iniialSync request
// So, the relative time starts here
$scope.now = new Date().getTime();
var needsToJoin = true; var needsToJoin = true;
// The room members is available in the data fetched by initialSync // The room members is available in the data fetched by initialSync
@ -377,10 +388,22 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
// Make recents highlight the current room // Make recents highlight the current room
$scope.recentsSelectedRoomID = $scope.room_id; $scope.recentsSelectedRoomID = $scope.room_id;
// Get the up-to-date the current member list
matrixService.getMemberList($scope.room_id).then(
function(response) {
for (var i = 0; i < response.data.chunk.length; i++) {
var chunk = response.data.chunk[i];
updateMemberList(chunk);
updateMemberListPresenceAge();
}
},
function(error) {
$scope.feedback = "Failed get member list: " + error.data.error;
}
);
paginate(MESSAGES_PER_PAGINATION); paginate(MESSAGES_PER_PAGINATION);
updateMemberListPresenceAge();
}; };
$scope.inviteUser = function(user_id) { $scope.inviteUser = function(user_id) {

View File

@ -106,7 +106,7 @@
<button ng-click="answerCall()">Answer</button> <button ng-click="answerCall()">Answer</button>
<button ng-click="hangupCall()">Reject</button> <button ng-click="hangupCall()">Reject</button>
</div> </div>
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing'">Hang up</button> <button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span> <span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span> <span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
<span ng-show="currentCall.state == 'connected'">Call Connected</span> <span ng-show="currentCall.state == 'connected'">Call Connected</span>