mirror of
https://github.com/element-hq/synapse
synced 2024-09-12 05:55:11 +00:00
Merge branch 'develop' of github.com:matrix-org/synapse into room_config
This commit is contained in:
commit
3faa2ae78c
43 changed files with 11416 additions and 252 deletions
|
@ -1007,26 +1007,15 @@ for users from other servers entirely.
|
|||
Presence
|
||||
========
|
||||
|
||||
In the following messages, the presence state is an integer enumeration of the
|
||||
following states:
|
||||
0 : OFFLINE
|
||||
1 : BUSY
|
||||
2 : ONLINE
|
||||
3 : FREE_TO_CHAT
|
||||
|
||||
Aside from OFFLINE, the protocol doesn't assign any special meaning to these
|
||||
states; they are provided as an approximate signal for users to give to other
|
||||
users and for clients to present them in some way that may be useful. Clients
|
||||
could have different behaviours for different states of the user's presence, for
|
||||
example to decide how much prominence or sound to use for incoming event
|
||||
notifications.
|
||||
In the following messages, the presence state is a presence string as described in
|
||||
the main specification document.
|
||||
|
||||
Getting/Setting your own presence state
|
||||
---------------------------------------
|
||||
REST Path: /presence/$user_id/status
|
||||
Valid methods: GET/PUT
|
||||
Required keys:
|
||||
state : [0|1|2|3] - The user's new presence state
|
||||
presence : <string> - The user's new presence state
|
||||
Optional keys:
|
||||
status_msg : text string provided by the user to explain their status
|
||||
|
||||
|
@ -1039,7 +1028,7 @@ Fetching your presence list
|
|||
following keys:
|
||||
{
|
||||
"user_id" : string giving the observed user's ID
|
||||
"state" : int giving their status
|
||||
"presence" : int giving their status
|
||||
"status_msg" : optional text string
|
||||
"displayname" : optional text string from the user's profile
|
||||
"avatar_url" : optional text string from the user's profile
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
"swaggerVersion": "1.2",
|
||||
"apis": [
|
||||
{
|
||||
"path": "/login",
|
||||
"path": "-login",
|
||||
"description": "Login operations"
|
||||
},
|
||||
{
|
||||
"path": "/registration",
|
||||
"path": "-registration",
|
||||
"description": "Registration operations"
|
||||
},
|
||||
{
|
||||
"path": "/rooms",
|
||||
"path": "-rooms",
|
||||
"description": "Room operations"
|
||||
},
|
||||
{
|
||||
"path": "/profile",
|
||||
"path": "-profile",
|
||||
"description": "Profile operations"
|
||||
},
|
||||
{
|
||||
"path": "/presence",
|
||||
"path": "-presence",
|
||||
"description": "Presence operations"
|
||||
},
|
||||
{
|
||||
"path": "/events",
|
||||
"path": "-events",
|
||||
"description": "Event operations"
|
||||
},
|
||||
{
|
||||
"path": "/directory",
|
||||
"path": "-directory",
|
||||
"description": "Directory operations"
|
||||
}
|
||||
],
|
||||
|
|
5
docs/client-server/web/README
Normal file
5
docs/client-server/web/README
Normal file
|
@ -0,0 +1,5 @@
|
|||
To get this running:
|
||||
ln -s ../swagger_matrix
|
||||
python -m SimpleHTTPServer
|
||||
|
||||
Go to http://localhost:8000/swagger.html
|
38
docs/client-server/web/files/backbone-min.js
vendored
Normal file
38
docs/client-server/web/files/backbone-min.js
vendored
Normal 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);
|
16
docs/client-server/web/files/css
Normal file
16
docs/client-server/web/files/css
Normal 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;
|
||||
}
|
2278
docs/client-server/web/files/handlebars-1.0.0.js
Normal file
2278
docs/client-server/web/files/handlebars-1.0.0.js
Normal file
File diff suppressed because it is too large
Load diff
1
docs/client-server/web/files/highlight.7.3.pack.js
Normal file
1
docs/client-server/web/files/highlight.7.3.pack.js
Normal file
File diff suppressed because one or more lines are too long
2
docs/client-server/web/files/jquery-1.8.0.min.js
vendored
Normal file
2
docs/client-server/web/files/jquery-1.8.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
18
docs/client-server/web/files/jquery.ba-bbq.min.js
vendored
Normal file
18
docs/client-server/web/files/jquery.ba-bbq.min.js
vendored
Normal 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);
|
1
docs/client-server/web/files/jquery.slideto.min.js
vendored
Normal file
1
docs/client-server/web/files/jquery.slideto.min.js
vendored
Normal 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);
|
8
docs/client-server/web/files/jquery.wiggle.min.js
vendored
Normal file
8
docs/client-server/web/files/jquery.wiggle.min.js
vendored
Normal 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();}});}});};
|
125
docs/client-server/web/files/reset.css
Normal file
125
docs/client-server/web/files/reset.css
Normal 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;
|
||||
}
|
1221
docs/client-server/web/files/screen.css
Normal file
1221
docs/client-server/web/files/screen.css
Normal file
File diff suppressed because it is too large
Load diff
2765
docs/client-server/web/files/shred.bundle.js
Normal file
2765
docs/client-server/web/files/shred.bundle.js
Normal file
File diff suppressed because it is too large
Load diff
211
docs/client-server/web/files/swagger-oauth.js
Normal file
211
docs/client-server/web/files/swagger-oauth.js
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2315
docs/client-server/web/files/swagger-ui.js
Normal file
2315
docs/client-server/web/files/swagger-ui.js
Normal file
File diff suppressed because it is too large
Load diff
1604
docs/client-server/web/files/swagger.js
Normal file
1604
docs/client-server/web/files/swagger.js
Normal file
File diff suppressed because it is too large
Load diff
32
docs/client-server/web/files/underscore-min.js
vendored
Normal file
32
docs/client-server/web/files/underscore-min.js
vendored
Normal 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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);
|
78
docs/client-server/web/swagger.html
Normal file
78
docs/client-server/web/swagger.html
Normal 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>
|
|
@ -168,6 +168,10 @@ Some standard error codes are below:
|
|||
:``M_NOT_FOUND``:
|
||||
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:
|
||||
|
||||
:``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: Keys for speccing a room name / room topic / invite these users?
|
||||
|
||||
Modifying aliases
|
||||
-----------------
|
||||
|
@ -284,12 +289,37 @@ Permissions
|
|||
|
||||
Joining rooms
|
||||
-------------
|
||||
- What is joining? What permissions / access does it give you? How does this affect /initialSync?
|
||||
- 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?
|
||||
- TODO: What does the home server have to do to join a user to a room?
|
||||
|
||||
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
|
||||
--------------
|
||||
|
@ -331,31 +361,162 @@ See the "Room events" section for more information on ``m.room.member``.
|
|||
|
||||
Leaving rooms
|
||||
-------------
|
||||
- API to hit (``$roomid/leave``). See "Room events" for more info.
|
||||
- Must be joined to leave. How does this affect /initialSync?
|
||||
- Not ever being in a room is NOT equivalent to have left it (due to membership: leave).
|
||||
- Need to be re-invited if invite-only room.
|
||||
- If no more HSes in room, can delete room?
|
||||
- Is there a dance?
|
||||
A user can leave a room to stop receiving events for that room. A user must have
|
||||
joined the room before they are eligible to leave the room. If the room is an
|
||||
"invite-only" room, they will need to be re-invited before they can re-join the room.
|
||||
To leave a room, a request should be made to ``/rooms/<room id>/leave`` with::
|
||||
|
||||
{}
|
||||
|
||||
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
|
||||
----------------
|
||||
- Split into state and non-state data
|
||||
- Explain what they are, semantics, give examples of clobbering / not, use cases (msgs vs room names).
|
||||
Not too much detail on the actual event contents.
|
||||
- API to hit.
|
||||
- Extensibility provided by the API for custom events. Examples.
|
||||
- How this hooks into ``initialSync``.
|
||||
- See the "Room Events" section for actual spec on each type.
|
||||
Room events can be split into two categories:
|
||||
|
||||
Syncing a room
|
||||
--------------
|
||||
- Single room initial sync. API to hit. Why it might be used (lazy loading)
|
||||
:State Events:
|
||||
These are events which replace events that came before it, depending on a set of unique keys.
|
||||
These keys are the event ``type`` and a ``state_key``. Events with the same set of keys will
|
||||
be overwritten. Typically, state events are used to store state, hence their name.
|
||||
|
||||
:Non-state events:
|
||||
These are events which cannot be overwritten after sending. The list of events continues
|
||||
to grow as more events are sent. As this list grows, it becomes necessary to
|
||||
provide a mechanism for navigating this list. Pagination APIs are used to view the list
|
||||
of historical non-state events. Typically, non-state events are used to send messages.
|
||||
|
||||
This specification outlines several events, all with the event type prefix ``m.``. However,
|
||||
applications may wish to add their own type of event, and this can be achieved using the
|
||||
REST API detailed in the following sections. If new events are added, the event ``type``
|
||||
key SHOULD follow the Java package naming convention, e.g. ``com.example.myapp.event``.
|
||||
This ensures event types are suitably namespaced for each application and reduces the
|
||||
risk of clashes.
|
||||
|
||||
State events
|
||||
------------
|
||||
State events can be sent by ``PUT`` ing to ``/rooms/<room id>/state/<event type>/<state key>``.
|
||||
These events will be overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all match.
|
||||
If the state event has no ``state_key``, it can be omitted from the path. These requests
|
||||
**cannot use transaction IDs** like other ``PUT`` paths because they cannot be differentiated
|
||||
from the ``state_key``. Furthermore, ``POST`` is unsupported on state paths. Valid requests
|
||||
look like::
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.example.event
|
||||
{ "key" : "without a state key" }
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.another.example.event/foo
|
||||
{ "key" : "with 'foo' as the state key" }
|
||||
|
||||
In contrast, these requests are invalid::
|
||||
|
||||
POST /rooms/!roomid:domain/state/m.example.event/
|
||||
{ "key" : "cannot use POST here" }
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
|
||||
{ "key" : "txnIds are not supported" }
|
||||
|
||||
Care should be taken to avoid setting the wrong ``state key``::
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.another.example.event/11
|
||||
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
|
||||
|
||||
The ``state_key`` is often used to store state about individual users, by using the user ID as the
|
||||
``state_key`` value. For example::
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
|
||||
{ "animal" : "cat", "reason": "fluffy" }
|
||||
|
||||
In some cases, there may be no need for a ``state_key``, so it can be omitted::
|
||||
|
||||
PUT /rooms/!roomid:domain/state/m.room.bgd.color
|
||||
{ "color": "red", "hex": "#ff0000" }
|
||||
|
||||
See "Room Events" for the ``m.`` event specification.
|
||||
|
||||
Non-state events
|
||||
----------------
|
||||
Non-state events can be sent by sending a request to ``/rooms/<room id>/send/<event type>``.
|
||||
These requests *can* use transaction IDs and ``PUT``/``POST`` methods. Non-state events
|
||||
allow access to historical events and pagination, making it best suited for sending messages.
|
||||
For example::
|
||||
|
||||
POST /rooms/!roomid:domain/send/m.custom.example.message
|
||||
{ "text": "Hello world!" }
|
||||
|
||||
PUT /rooms/!roomid:domain/send/m.custom.example.message/11
|
||||
{ "text": "Goodbye world!" }
|
||||
|
||||
See "Room Events" for the ``m.`` event specification.
|
||||
|
||||
Syncing rooms
|
||||
-------------
|
||||
When a client logs in, they may have a list of rooms which they have already joined. These rooms
|
||||
may also have a list of events associated with them. The purpose of 'syncing' is to present the
|
||||
current room and event information in a convenient, compact manner. The events returned are not
|
||||
limited to room events; presence events will also be returned. There are two APIs provided:
|
||||
|
||||
- ``/initialSync`` : A global sync which will present room and event information for all rooms
|
||||
the user has joined.
|
||||
|
||||
- ``/rooms/<room id>/initialSync`` : A sync scoped to a single room. Presents room and event
|
||||
information for this room only.
|
||||
|
||||
- TODO: JSON response format for both types
|
||||
- TODO: when would you use global? when would you use scoped?
|
||||
|
||||
Getting events for a room
|
||||
-------------------------
|
||||
There are several APIs provided to ``GET`` events for a room:
|
||||
|
||||
``/rooms/<room id>/state/<event type>/<state key>``
|
||||
Description:
|
||||
Get the state event identified.
|
||||
Response format:
|
||||
A JSON object representing the state event **content**.
|
||||
Example:
|
||||
``/rooms/!room:domain.com/state/m.room.name`` returns ``{ "name": "Room name" }``
|
||||
|
||||
``/rooms/<room id>/state``
|
||||
Description:
|
||||
Get all state events for a room.
|
||||
Response format:
|
||||
``[ { state event }, { state event }, ... ]``
|
||||
Example:
|
||||
TODO
|
||||
|
||||
|
||||
``/rooms/<room id>/members``
|
||||
Description:
|
||||
Get all ``m.room.member`` state events.
|
||||
Response format:
|
||||
``{ "start": "token", "end": "token", "chunk": [ { m.room.member event }, ... ] }``
|
||||
Example:
|
||||
TODO
|
||||
|
||||
|
||||
|
||||
- ``/rooms/<room id>/messages`` : Get all ``m.room.message`` events.
|
||||
- ``/rooms/<room id>/initialSync`` : Get all relevant events for a room.
|
||||
|
||||
Getting grouped state events
|
||||
----------------------------
|
||||
- ``/members`` and ``/messages`` and the events they return.
|
||||
- ``/state`` and it returns ALL THE THINGS.
|
||||
|
||||
Room Events
|
||||
===========
|
||||
|
@ -363,23 +524,109 @@ Room Events
|
|||
This specification outlines several standard event types, all of which are
|
||||
prefixed with ``m.``
|
||||
|
||||
State messages
|
||||
--------------
|
||||
- m.room.name
|
||||
- m.room.topic
|
||||
- m.room.member
|
||||
- m.room.config
|
||||
- m.room.invite_join
|
||||
``m.room.name``
|
||||
Summary:
|
||||
Set the human-readable name for the room.
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
``{ "name" : "string" }``
|
||||
Example:
|
||||
``{ "name" : "My Room" }``
|
||||
Description:
|
||||
A room has an opaque room ID which is not human-friendly to read. A room alias is
|
||||
human-friendly, but not all rooms have room aliases. The room name is a human-friendly
|
||||
string designed to be displayed to the end-user. The room name is not *unique*, as
|
||||
multiple rooms can have the same room name set. The room name can also be set when
|
||||
creating a room using ``/createRoom`` with the ``name`` key.
|
||||
|
||||
What are they, when are they used, what do they contain, how should they be used.
|
||||
Link back to explanatory sections (e.g. invite/join/leave sections for m.room.member)
|
||||
``m.room.topic``
|
||||
Summary:
|
||||
Set a topic for the room.
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
``{ "topic" : "string" }``
|
||||
Example:
|
||||
``{ "topic" : "Welcome to the real world." }``
|
||||
Description:
|
||||
A topic is a short message detailing what is currently being discussed in the room.
|
||||
It can also be used as a way to display extra information about the room, which may
|
||||
not be suitable for the room name.
|
||||
|
||||
Non-state messages
|
||||
------------------
|
||||
- m.room.message
|
||||
- m.room.message.feedback (and compressed format)
|
||||
``m.room.member``
|
||||
Summary:
|
||||
The current membership state of a user in the room.
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
``{ "membership" : "enum[ invite|join|leave|ban ]" }``
|
||||
Example:
|
||||
``{ "membership" : "join" }``
|
||||
Description:
|
||||
Adjusts the membership state for a user in a room. It is preferable to use the
|
||||
membership APIs (``/rooms/<room id>/invite`` etc) when performing membership actions
|
||||
rather than adjusting the state directly as there are a restricted set of valid
|
||||
transformations. For example, user A cannot force user B to join a room, and trying
|
||||
to force this state change directly will fail. See the "Rooms" section for how to
|
||||
use the membership APIs.
|
||||
|
||||
What are they, when are they used, what do they contain, how should they be used
|
||||
``m.room.config``
|
||||
Summary:
|
||||
The room config.
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
TODO
|
||||
Example:
|
||||
TODO
|
||||
Description:
|
||||
TODO
|
||||
|
||||
``m.room.invite_join``
|
||||
Summary:
|
||||
TODO.
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
TODO
|
||||
Example:
|
||||
TODO
|
||||
Description:
|
||||
TODO
|
||||
|
||||
``m.room.message``
|
||||
Summary:
|
||||
A message.
|
||||
Type:
|
||||
Non-state event
|
||||
JSON format:
|
||||
``{ "msgtype": "string" }``
|
||||
Example:
|
||||
``{ "msgtype": "m.text", "body": "Testing" }``
|
||||
Description:
|
||||
This event is used when sending messages in a room. Messages are not limited to be text.
|
||||
The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc.
|
||||
Whilst not required, the ``body`` key SHOULD be used with every kind of ``msgtype`` as
|
||||
a fallback mechanism when a client cannot render the message. For more information on
|
||||
the types of messages which can be sent, see "m.room.message msgtypes".
|
||||
|
||||
``m.room.message.feedback``
|
||||
Summary:
|
||||
A receipt for a message.
|
||||
Type:
|
||||
Non-state event
|
||||
JSON format:
|
||||
``{ "type": "enum [ delivered|read ]", "target_event_id": "string" }``
|
||||
Example:
|
||||
``{ "type": "delivered", "target_event_id": "e3b2icys" }``
|
||||
Description:
|
||||
Feedback events are events sent to acknowledge a message in some way. There are two
|
||||
supported acknowledgements: ``delivered`` (sent when the event has been received) and
|
||||
``read`` (sent when the event has been observed by the end-user). The ``target_event_id``
|
||||
should reference the ``m.room.message`` event being acknowledged.
|
||||
|
||||
- voip?
|
||||
|
||||
m.room.message msgtypes
|
||||
-----------------------
|
||||
|
@ -490,7 +737,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
|
||||
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
|
||||
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.
|
||||
- ``unavailable`` : The user is not reachable at this time.
|
||||
|
@ -500,18 +747,26 @@ 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
|
||||
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
|
||||
status choice among multiple devices to ensure the user gets a consistent
|
||||
experience.
|
||||
|
||||
In addition, the server maintains a timestamp of the last time it saw an active
|
||||
action from the user; either sending a message to a room, or changing presence
|
||||
state from a lower to a higher level of availability (thus: changing state from
|
||||
``unavailable`` to ``online`` will count as an action for being active, whereas
|
||||
in the other direction will not). This timestamp is presented via a key called
|
||||
``last_active_ago``, which gives the relative number of miliseconds since the
|
||||
message is generated/emitted, that the user was last seen active.
|
||||
|
||||
Idle Time
|
||||
---------
|
||||
As well as the basic ``state`` field, the presence information can also show a sense
|
||||
of an "idle timer". This should be maintained individually by the user's
|
||||
clients, and the home server can take the highest reported time as that to
|
||||
report. When a user is offline, the home server can still report when the user was last
|
||||
seen online.
|
||||
As well as the basic ``presence`` field, the presence information can also show
|
||||
a sense of an "idle timer". This should be maintained individually by the
|
||||
user's clients, and the home server can take the highest reported time as that
|
||||
to report. When a user is offline, the home server can still report when the
|
||||
user was last seen online.
|
||||
|
||||
Transmission
|
||||
------------
|
||||
|
|
|
@ -76,6 +76,10 @@ class MessageHandler(BaseRoomHandler):
|
|||
Raises:
|
||||
SynapseError if something went wrong.
|
||||
"""
|
||||
# TODO(paul): Why does 'event' not have a 'user' object?
|
||||
user = self.hs.parse_userid(event.user_id)
|
||||
assert(user.is_mine)
|
||||
|
||||
if stamp_event:
|
||||
event.content["hsob_ts"] = int(self.clock.time_msec())
|
||||
|
||||
|
@ -86,6 +90,10 @@ class MessageHandler(BaseRoomHandler):
|
|||
|
||||
yield self._on_new_room_event(event, snapshot)
|
||||
|
||||
self.hs.get_handlers().presence_handler.bump_presence_active_time(
|
||||
user
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_messages(self, user_id=None, room_id=None, pagin_config=None,
|
||||
feedback=False):
|
||||
|
|
|
@ -52,6 +52,13 @@ def partitionbool(l, func):
|
|||
|
||||
class PresenceHandler(BaseHandler):
|
||||
|
||||
STATE_LEVELS = {
|
||||
PresenceState.OFFLINE: 0,
|
||||
PresenceState.UNAVAILABLE: 1,
|
||||
PresenceState.ONLINE: 2,
|
||||
PresenceState.FREE_FOR_CHAT: 3,
|
||||
}
|
||||
|
||||
def __init__(self, hs):
|
||||
super(PresenceHandler, self).__init__(hs)
|
||||
|
||||
|
@ -135,7 +142,7 @@ class PresenceHandler(BaseHandler):
|
|||
return self._user_cachemap[user]
|
||||
else:
|
||||
statuscache = UserPresenceCache()
|
||||
statuscache.update({"state": PresenceState.OFFLINE}, user)
|
||||
statuscache.update({"presence": PresenceState.OFFLINE}, user)
|
||||
return statuscache
|
||||
|
||||
def registered_user(self, user):
|
||||
|
@ -173,19 +180,24 @@ class PresenceHandler(BaseHandler):
|
|||
observed_user=target_user
|
||||
)
|
||||
|
||||
if visible:
|
||||
state = yield self.store.get_presence_state(
|
||||
target_user.localpart
|
||||
)
|
||||
else:
|
||||
if 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:
|
||||
# TODO(paul): Have remote server send us permissions set
|
||||
state = self._get_or_offline_usercache(target_user).get_state()
|
||||
|
||||
if "mtime" in state and (state["mtime"] is not None):
|
||||
state["mtime_age"] = int(
|
||||
self.clock.time_msec() - state.pop("mtime")
|
||||
if "last_active" in state:
|
||||
state["last_active_ago"] = int(
|
||||
self.clock.time_msec() - state.pop("last_active")
|
||||
)
|
||||
defer.returnValue(state)
|
||||
|
||||
|
@ -202,20 +214,33 @@ class PresenceHandler(BaseHandler):
|
|||
if target_user != auth_user:
|
||||
raise AuthError(400, "Cannot set another user's displayname")
|
||||
|
||||
# TODO(paul): Sanity-check 'state'
|
||||
if "status_msg" not in state:
|
||||
state["status_msg"] = None
|
||||
|
||||
for k in state.keys():
|
||||
if k not in ("state", "status_msg"):
|
||||
if k not in ("presence", "state", "status_msg"):
|
||||
raise SynapseError(
|
||||
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",
|
||||
target_user.localpart, state["state"])
|
||||
target_user.localpart, state["presence"])
|
||||
|
||||
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([
|
||||
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
|
||||
|
||||
if now_online and not was_polling:
|
||||
|
@ -240,6 +266,12 @@ class PresenceHandler(BaseHandler):
|
|||
# we don't have to do this all the time
|
||||
self.changed_presencelike_data(target_user, state)
|
||||
|
||||
def bump_presence_active_time(self, user, now=None):
|
||||
if now is None:
|
||||
now = self.clock.time_msec()
|
||||
|
||||
self.changed_presencelike_data(user, {"last_active": now})
|
||||
|
||||
def changed_presencelike_data(self, user, state):
|
||||
statuscache = self._get_or_make_usercache(user)
|
||||
|
||||
|
@ -251,12 +283,12 @@ class PresenceHandler(BaseHandler):
|
|||
@log_function
|
||||
def started_user_eventstream(self, user):
|
||||
# TODO(paul): Use "last online" state
|
||||
self.set_state(user, user, {"state": PresenceState.ONLINE})
|
||||
self.set_state(user, user, {"presence": PresenceState.ONLINE})
|
||||
|
||||
@log_function
|
||||
def stopped_user_eventstream(self, user):
|
||||
# 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
|
||||
def user_joined_room(self, user, room_id):
|
||||
|
@ -385,9 +417,9 @@ class PresenceHandler(BaseHandler):
|
|||
observed_user = self.hs.parse_userid(p.pop("observed_user_id"))
|
||||
p["observed_user"] = observed_user
|
||||
p.update(self._get_or_offline_usercache(observed_user).get_state())
|
||||
if "mtime" in p:
|
||||
p["mtime_age"] = int(
|
||||
self.clock.time_msec() - p.pop("mtime")
|
||||
if "last_active" in p:
|
||||
p["last_active_ago"] = int(
|
||||
self.clock.time_msec() - p.pop("last_active")
|
||||
)
|
||||
|
||||
defer.returnValue(presence)
|
||||
|
@ -576,21 +608,30 @@ class PresenceHandler(BaseHandler):
|
|||
def _push_presence_remote(self, user, destination, state=None):
|
||||
if state is None:
|
||||
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(
|
||||
"collect_presencelike_data", user, state
|
||||
)
|
||||
|
||||
if "mtime" in state:
|
||||
if "last_active" in state:
|
||||
state = dict(state)
|
||||
state["mtime_age"] = int(
|
||||
self.clock.time_msec() - state.pop("mtime")
|
||||
state["last_active_ago"] = int(
|
||||
self.clock.time_msec() - state.pop("last_active")
|
||||
)
|
||||
|
||||
user_state = {
|
||||
"user_id": user.to_string(),
|
||||
}
|
||||
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(
|
||||
destination=destination,
|
||||
|
@ -622,9 +663,14 @@ class PresenceHandler(BaseHandler):
|
|||
state = dict(push)
|
||||
del state["user_id"]
|
||||
|
||||
if "mtime_age" in state:
|
||||
state["mtime"] = int(
|
||||
self.clock.time_msec() - state.pop("mtime_age")
|
||||
# Legacy handling
|
||||
if "presence" not in state:
|
||||
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)
|
||||
|
@ -639,7 +685,7 @@ class PresenceHandler(BaseHandler):
|
|||
statuscache=statuscache,
|
||||
)
|
||||
|
||||
if state["state"] == PresenceState.OFFLINE:
|
||||
if state["presence"] == PresenceState.OFFLINE:
|
||||
del self._user_cachemap[user]
|
||||
|
||||
for poll in content.get("poll", []):
|
||||
|
@ -672,10 +718,9 @@ class PresenceHandler(BaseHandler):
|
|||
yield defer.DeferredList(deferreds)
|
||||
|
||||
@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=[],
|
||||
remote_domains=[],
|
||||
statuscache=None):
|
||||
remote_domains=[]):
|
||||
|
||||
localusers, remoteusers = partitionbool(
|
||||
users_to_push,
|
||||
|
@ -804,6 +849,7 @@ class UserPresenceCache(object):
|
|||
|
||||
def update(self, state, serial):
|
||||
assert("mtime_age" not in state)
|
||||
assert("state" not in state)
|
||||
|
||||
self.state.update(state)
|
||||
# Delete keys that are now 'None'
|
||||
|
@ -820,15 +866,21 @@ class UserPresenceCache(object):
|
|||
|
||||
def get_state(self):
|
||||
# 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):
|
||||
content = self.get_state()
|
||||
content["user_id"] = user.to_string()
|
||||
|
||||
if "mtime" in content:
|
||||
content["mtime_age"] = int(
|
||||
clock.time_msec() - content.pop("mtime")
|
||||
if "last_active" in content:
|
||||
content["last_active_ago"] = int(
|
||||
clock.time_msec() - content.pop("last_active")
|
||||
)
|
||||
|
||||
return {"type": "m.presence", "content": content}
|
||||
|
|
|
@ -48,7 +48,11 @@ class PresenceStatusRestServlet(RestServlet):
|
|||
try:
|
||||
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:
|
||||
state["status_msg"] = content.pop("status_msg")
|
||||
|
|
|
@ -35,8 +35,6 @@ ONLINE = PresenceState.ONLINE
|
|||
|
||||
|
||||
logging.getLogger().addHandler(logging.NullHandler())
|
||||
#logging.getLogger().addHandler(logging.StreamHandler())
|
||||
#logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self.assertEquals({"state": ONLINE, "status_msg": "Online"},
|
||||
self.assertEquals(
|
||||
{"state": ONLINE, "presence": ONLINE, "status_msg": "Online"},
|
||||
state
|
||||
)
|
||||
mocked_get.assert_called_with("apple")
|
||||
|
@ -157,7 +156,8 @@ class PresenceStateTestCase(unittest.TestCase):
|
|||
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
|
||||
)
|
||||
mocked_get.assert_called_with("apple")
|
||||
|
@ -175,7 +175,10 @@ class PresenceStateTestCase(unittest.TestCase):
|
|||
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
|
||||
def test_get_disallowed_state(self):
|
||||
|
@ -202,20 +205,20 @@ class PresenceStateTestCase(unittest.TestCase):
|
|||
|
||||
yield self.handler.set_state(
|
||||
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",
|
||||
{"state": UNAVAILABLE, "status_msg": "Away"})
|
||||
self.mock_start.assert_called_with(self.u_apple,
|
||||
state={
|
||||
"state": UNAVAILABLE,
|
||||
"presence": UNAVAILABLE,
|
||||
"status_msg": "Away",
|
||||
"mtime": 1000000, # MockClock
|
||||
"last_active": 1000000, # MockClock
|
||||
})
|
||||
|
||||
yield self.handler.set_state(
|
||||
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)
|
||||
|
||||
|
@ -449,28 +452,35 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||
@defer.inlineCallbacks
|
||||
def test_get_presence_list(self):
|
||||
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(
|
||||
observer_user=self.u_apple)
|
||||
|
||||
self.assertEquals([{"observed_user": self.u_banana,
|
||||
"state": OFFLINE}], presence)
|
||||
self.assertEquals([
|
||||
{"observed_user": self.u_banana,
|
||||
"presence": OFFLINE,
|
||||
"state": OFFLINE},
|
||||
], presence)
|
||||
|
||||
self.datastore.get_presence_list.assert_called_with("apple",
|
||||
accepted=None)
|
||||
|
||||
accepted=None
|
||||
)
|
||||
|
||||
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(
|
||||
observer_user=self.u_apple, accepted=True)
|
||||
observer_user=self.u_apple, accepted=True
|
||||
)
|
||||
|
||||
self.assertEquals([{"observed_user": self.u_banana,
|
||||
"state": OFFLINE}], presence)
|
||||
self.assertEquals([
|
||||
{"observed_user": self.u_banana,
|
||||
"presence": OFFLINE,
|
||||
"state": OFFLINE},
|
||||
], presence)
|
||||
|
||||
self.datastore.get_presence_list.assert_called_with("apple",
|
||||
accepted=True)
|
||||
|
@ -611,6 +621,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
|
||||
# TODO(paul): Gut-wrenching
|
||||
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.add(self.u_banana)
|
||||
apple_set.add(self.u_clementine)
|
||||
|
@ -618,7 +631,8 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||
|
||||
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(
|
||||
|
@ -627,8 +641,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
{"type": "m.presence",
|
||||
"content": {
|
||||
"user_id": "@apple:test",
|
||||
"presence": 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(
|
||||
observer_user=self.u_apple, accepted=True)
|
||||
|
||||
self.assertEquals([
|
||||
{"observed_user": self.u_banana, "state": OFFLINE},
|
||||
{"observed_user": self.u_clementine, "state": OFFLINE}],
|
||||
presence)
|
||||
self.assertEquals(
|
||||
[
|
||||
{"observed_user": self.u_banana,
|
||||
"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,
|
||||
{"state": ONLINE})
|
||||
{"presence": ONLINE}
|
||||
)
|
||||
|
||||
self.clock.advance_time(2)
|
||||
|
||||
|
@ -651,9 +674,11 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
|
||||
self.assertEquals([
|
||||
{"observed_user": self.u_banana,
|
||||
"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"mtime_age": 2000},
|
||||
"last_active_ago": 2000},
|
||||
{"observed_user": self.u_clementine,
|
||||
"presence": OFFLINE,
|
||||
"state": OFFLINE},
|
||||
], presence)
|
||||
|
||||
|
@ -666,8 +691,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
{"type": "m.presence",
|
||||
"content": {
|
||||
"user_id": "@banana:test",
|
||||
"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"mtime_age": 2000
|
||||
"last_active_ago": 2000
|
||||
}},
|
||||
]
|
||||
)
|
||||
|
@ -682,8 +708,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"user_id": "@apple:test",
|
||||
"presence": u"online",
|
||||
"state": u"online",
|
||||
"mtime_age": 0},
|
||||
"last_active_ago": 0},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
@ -699,11 +726,14 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
|
||||
# TODO(paul): Gut-wrenching
|
||||
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.add(self.u_potato.domain)
|
||||
|
||||
yield self.handler.set_state(self.u_apple, self.u_apple,
|
||||
{"state": ONLINE}
|
||||
{"presence": ONLINE}
|
||||
)
|
||||
|
||||
yield put_json.await_calls()
|
||||
|
@ -726,7 +756,7 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
"push": [
|
||||
{"user_id": "@potato:remote",
|
||||
"state": "online",
|
||||
"mtime_age": 1000},
|
||||
"last_active_ago": 1000},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
@ -741,8 +771,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
{"type": "m.presence",
|
||||
"content": {
|
||||
"user_id": "@potato:remote",
|
||||
"presence": 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)
|
||||
|
||||
self.assertEquals({"state": ONLINE, "mtime_age": 3000}, state)
|
||||
self.assertEquals(
|
||||
{"state": ONLINE, "presence": ONLINE, "last_active_ago": 3000},
|
||||
state
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
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].update(
|
||||
{
|
||||
"state": PresenceState.ONLINE,
|
||||
"mtime": self.clock.time_msec(),
|
||||
"presence": PresenceState.ONLINE,
|
||||
"last_active": self.clock.time_msec(),
|
||||
}, self.u_clementine
|
||||
)
|
||||
|
||||
|
@ -781,8 +815,9 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
{"type": "m.presence",
|
||||
"content": {
|
||||
"user_id": "@clementine:test",
|
||||
"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"mtime_age": 0,
|
||||
"last_active_ago": 0,
|
||||
}}
|
||||
]
|
||||
)
|
||||
|
@ -798,7 +833,8 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"user_id": "@apple:test",
|
||||
"state": "online"},
|
||||
"presence": "online",
|
||||
"state": "online"},
|
||||
],
|
||||
}
|
||||
),
|
||||
|
@ -812,7 +848,8 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"user_id": "@banana:test",
|
||||
"state": "offline"},
|
||||
"presence": "offline",
|
||||
"state": "offline"},
|
||||
],
|
||||
}
|
||||
),
|
||||
|
@ -823,7 +860,7 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
# TODO(paul): Gut-wrenching
|
||||
self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
|
||||
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]
|
||||
|
||||
yield self.distributor.fire("user_joined_room", self.u_potato,
|
||||
|
@ -841,7 +878,8 @@ class PresencePushTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"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].update(
|
||||
{"state": ONLINE}, self.u_clementine)
|
||||
{"presence": ONLINE}, self.u_clementine)
|
||||
self.room_members.append(self.u_potato)
|
||||
|
||||
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
||||
|
@ -935,7 +973,8 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
def get_presence_state(user_localpart):
|
||||
return defer.succeed(
|
||||
{"state": self.current_user_state[user_localpart],
|
||||
"status_msg": None}
|
||||
"status_msg": None,
|
||||
"mtime": 123456000}
|
||||
)
|
||||
self.datastore.get_presence_state = get_presence_state
|
||||
|
||||
|
@ -969,7 +1008,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
# apple goes online
|
||||
yield self.handler.set_state(
|
||||
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
|
||||
|
@ -992,8 +1031,9 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
|
||||
# banana goes online
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_banana, auth_user=self.u_banana,
|
||||
state={"state": ONLINE})
|
||||
target_user=self.u_banana, auth_user=self.u_banana,
|
||||
state={"presence": ONLINE}
|
||||
)
|
||||
|
||||
# apple and banana should now both see each other online
|
||||
self.mock_update_client.assert_has_calls([
|
||||
|
@ -1013,8 +1053,9 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
|
||||
# apple goes offline
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_apple, auth_user=self.u_apple,
|
||||
state={"state": OFFLINE})
|
||||
target_user=self.u_apple, auth_user=self.u_apple,
|
||||
state={"presence": OFFLINE}
|
||||
)
|
||||
|
||||
# banana should now be told apple is offline
|
||||
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("clementine" in self.handler._local_pushmap)
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_remote_poll_send(self):
|
||||
put_json = self.mock_http_client.put_json
|
||||
|
@ -1057,8 +1097,9 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
|
||||
# clementine goes online
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_clementine, auth_user=self.u_clementine,
|
||||
state={"state": ONLINE})
|
||||
target_user=self.u_clementine, auth_user=self.u_clementine,
|
||||
state={"presence": ONLINE}
|
||||
)
|
||||
|
||||
yield put_json.await_calls()
|
||||
|
||||
|
@ -1085,7 +1126,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
# fig goes online; shouldn't send a second poll
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_fig, auth_user=self.u_fig,
|
||||
state={"state": ONLINE}
|
||||
state={"presence": ONLINE}
|
||||
)
|
||||
|
||||
# reactor.iterate(delay=0)
|
||||
|
@ -1095,7 +1136,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
# fig goes offline
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_fig, auth_user=self.u_fig,
|
||||
state={"state": OFFLINE}
|
||||
state={"presence": OFFLINE}
|
||||
)
|
||||
|
||||
reactor.iterate(delay=0)
|
||||
|
@ -1116,8 +1157,9 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
|
||||
# clementine goes offline
|
||||
yield self.handler.set_state(
|
||||
target_user=self.u_clementine, auth_user=self.u_clementine,
|
||||
state={"state": OFFLINE})
|
||||
target_user=self.u_clementine, auth_user=self.u_clementine,
|
||||
state={"presence": OFFLINE}
|
||||
)
|
||||
|
||||
yield put_json.await_calls()
|
||||
|
||||
|
@ -1135,6 +1177,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"user_id": "@banana:test",
|
||||
"presence": "offline",
|
||||
"state": "offline",
|
||||
"status_msg": None},
|
||||
],
|
||||
|
|
|
@ -166,7 +166,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
# TODO(paul): Gut-wrenching
|
||||
from synapse.handlers.presence import UserPresenceCache
|
||||
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())
|
||||
apple_set.add(self.u_banana)
|
||||
|
@ -182,11 +186,13 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
|
||||
self.assertEquals([
|
||||
{"observed_user": self.u_banana,
|
||||
"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"mtime_age": 0,
|
||||
"last_active_ago": 0,
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo"},
|
||||
{"observed_user": self.u_clementine,
|
||||
"presence": OFFLINE,
|
||||
"state": OFFLINE}],
|
||||
presence)
|
||||
|
||||
|
@ -199,8 +205,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
|
||||
statuscache = self.mock_update_client.call_args[1]["statuscache"]
|
||||
self.assertEquals({
|
||||
"state": ONLINE,
|
||||
"mtime": 1000000, # MockClock
|
||||
"presence": ONLINE,
|
||||
"last_active": 1000000, # MockClock
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo",
|
||||
}, statuscache.state)
|
||||
|
@ -222,8 +228,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
|
||||
statuscache = self.mock_update_client.call_args[1]["statuscache"]
|
||||
self.assertEquals({
|
||||
"state": ONLINE,
|
||||
"mtime": 1000000, # MockClock
|
||||
"presence": ONLINE,
|
||||
"last_active": 1000000, # MockClock
|
||||
"displayname": "I am an Apple",
|
||||
"avatar_url": "http://foo",
|
||||
}, statuscache.state)
|
||||
|
@ -241,7 +247,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
# TODO(paul): Gut-wrenching
|
||||
from synapse.handlers.presence import UserPresenceCache
|
||||
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())
|
||||
apple_set.add(self.u_potato.domain)
|
||||
|
@ -255,8 +265,9 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
content={
|
||||
"push": [
|
||||
{"user_id": "@apple:test",
|
||||
"presence": "online",
|
||||
"state": "online",
|
||||
"mtime_age": 0,
|
||||
"last_active_ago": 0,
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo"},
|
||||
],
|
||||
|
@ -293,14 +304,16 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
|
|||
statuscache=ANY)
|
||||
|
||||
statuscache = self.mock_update_client.call_args[1]["statuscache"]
|
||||
self.assertEquals({"state": ONLINE,
|
||||
self.assertEquals({"presence": ONLINE,
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo"}, statuscache.state)
|
||||
|
||||
state = yield self.handlers.presence_handler.get_state(self.u_potato,
|
||||
self.u_apple)
|
||||
|
||||
self.assertEquals({"state": ONLINE,
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo"},
|
||||
state)
|
||||
self.assertEquals(
|
||||
{"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"displayname": "Frank",
|
||||
"avatar_url": "http://foo"},
|
||||
state)
|
||||
|
|
|
@ -98,8 +98,10 @@ class PresenceStateTestCase(unittest.TestCase):
|
|||
"/presence/%s/status" % (myid), None)
|
||||
|
||||
self.assertEquals(200, code)
|
||||
self.assertEquals({"state": ONLINE, "status_msg": "Available"},
|
||||
response)
|
||||
self.assertEquals(
|
||||
{"presence": ONLINE, "state": ONLINE, "status_msg": "Available"},
|
||||
response
|
||||
)
|
||||
mocked_get.assert_called_with("apple")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
|
@ -109,7 +111,7 @@ class PresenceStateTestCase(unittest.TestCase):
|
|||
|
||||
(code, response) = yield self.mock_resource.trigger("PUT",
|
||||
"/presence/%s/status" % (myid),
|
||||
'{"state": "unavailable", "status_msg": "Away"}')
|
||||
'{"presence": "unavailable", "status_msg": "Away"}')
|
||||
|
||||
self.assertEquals(200, code)
|
||||
mocked_set.assert_called_with("apple",
|
||||
|
@ -173,9 +175,9 @@ class PresenceListTestCase(unittest.TestCase):
|
|||
"/presence/list/%s" % (myid), None)
|
||||
|
||||
self.assertEquals(200, code)
|
||||
self.assertEquals(
|
||||
[{"user_id": "@banana:test", "state": OFFLINE}], response
|
||||
)
|
||||
self.assertEquals([
|
||||
{"user_id": "@banana:test", "presence": OFFLINE, "state": OFFLINE},
|
||||
], response)
|
||||
|
||||
self.datastore.get_presence_list.assert_called_with(
|
||||
"apple", accepted=True
|
||||
|
@ -314,7 +316,8 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||
[])
|
||||
|
||||
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",
|
||||
"/events?from=0_1_0&timeout=0", None)
|
||||
|
@ -324,8 +327,9 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||
{"type": "m.presence",
|
||||
"content": {
|
||||
"user_id": "@banana:test",
|
||||
"presence": ONLINE,
|
||||
"state": ONLINE,
|
||||
"displayname": "Frank",
|
||||
"mtime_age": 0,
|
||||
"last_active_ago": 0,
|
||||
}},
|
||||
]}, response)
|
||||
|
|
|
@ -51,7 +51,7 @@ class RoomPermissionsTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
@ -398,7 +398,7 @@ class RoomsMemberListTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
@ -476,7 +476,7 @@ class RoomsCreateTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
@ -566,7 +566,7 @@ class RoomTopicTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
@ -669,7 +669,7 @@ class RoomMemberStateTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
@ -794,7 +794,7 @@ class RoomMessagesTestCase(RestTestCase):
|
|||
persistence_service.get_latest_pdus_in_context.return_value = []
|
||||
|
||||
hs = HomeServer(
|
||||
"test",
|
||||
"red",
|
||||
db_pool=None,
|
||||
http_client=None,
|
||||
datastore=MemoryDataStore(),
|
||||
|
|
|
@ -188,8 +188,9 @@ class MemoryDataStore(object):
|
|||
|
||||
def get_rooms_for_user_where_membership_is(self, user_id, membership_list):
|
||||
return [
|
||||
r for r in self.members
|
||||
if self.members[r].get(user_id).membership in membership_list
|
||||
self.members[r].get(user_id) for r in self.members
|
||||
if user_id in self.members[r] and
|
||||
self.members[r][user_id].membership in membership_list
|
||||
]
|
||||
|
||||
def get_room_events_stream(self, user_id=None, from_key=None, to_key=None,
|
||||
|
|
|
@ -21,8 +21,8 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService',
|
||||
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService) {
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService', 'matrixPhoneService',
|
||||
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService, matrixPhoneService) {
|
||||
|
||||
// Check current URL to avoid to display the logout button on the login page
|
||||
$scope.location = $location.path();
|
||||
|
@ -36,8 +36,12 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||
eventStreamService.resume();
|
||||
mPresence.start();
|
||||
}
|
||||
|
||||
$scope.user_id = matrixService.config().user_id;
|
||||
|
||||
$scope.user_id;
|
||||
var config = matrixService.config();
|
||||
if (config) {
|
||||
$scope.user_id = matrixService.config().user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a given page.
|
||||
|
@ -84,7 +88,27 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||
$scope.updateHeader = function() {
|
||||
$scope.user_id = matrixService.config().user_id;
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
|
||||
$rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) {
|
||||
console.trace("incoming call");
|
||||
call.onError = $scope.onCallError;
|
||||
call.onHangup = $scope.onCallHangup;
|
||||
$rootScope.currentCall = call;
|
||||
});
|
||||
|
||||
$scope.answerCall = function() {
|
||||
$scope.currentCall.answer();
|
||||
};
|
||||
|
||||
$scope.hangupCall = function() {
|
||||
$scope.currentCall.hangup();
|
||||
$scope.currentCall = undefined;
|
||||
};
|
||||
|
||||
$rootScope.onCallError = function(errStr) {
|
||||
$scope.feedback = errStr;
|
||||
}
|
||||
|
||||
$rootScope.onCallHangup = function() {
|
||||
}
|
||||
}]);
|
||||
|
|
|
@ -70,7 +70,7 @@ angular.module('matrixWebClient')
|
|||
});
|
||||
|
||||
filtered.sort(function (a, b) {
|
||||
return ((a["mtime_age"] || 10e10) > (b["mtime_age"] || 10e10) ? 1 : -1);
|
||||
return ((a["last_active_ago"] || 10e10) > (b["last_active_ago"] || 10e10) ? 1 : -1);
|
||||
});
|
||||
return filtered;
|
||||
};
|
||||
|
@ -79,4 +79,43 @@ angular.module('matrixWebClient')
|
|||
return function(text) {
|
||||
return $sce.trustAsHtml(text);
|
||||
};
|
||||
}])
|
||||
|
||||
// Compute the room name according to information we have
|
||||
.filter('roomName', ['$rootScope', 'matrixService', function($rootScope, matrixService) {
|
||||
return function(room_id) {
|
||||
var roomName;
|
||||
|
||||
// If there is an alias, use it
|
||||
// TODO: only one alias is managed for now
|
||||
var alias = matrixService.getRoomIdToAliasMapping(room_id);
|
||||
if (alias) {
|
||||
roomName = alias;
|
||||
}
|
||||
|
||||
if (undefined === roomName) {
|
||||
// Else, build the name from its users
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
if (room.members) {
|
||||
// Limit the room renaming to 1:1 room
|
||||
if (2 === Object.keys(room.members).length) {
|
||||
for (var i in room.members) {
|
||||
var member = room.members[i];
|
||||
if (member.user_id !== matrixService.config().user_id) {
|
||||
roomName = member.content.displayname ? member.content.displayname : member.user_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (undefined === roomName) {
|
||||
// By default, use the room ID
|
||||
roomName = room_id;
|
||||
}
|
||||
|
||||
return roomName;
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -43,6 +43,10 @@ a:active { color: #000; }
|
|||
height: 32px;
|
||||
}
|
||||
|
||||
#callBar {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#headerContent {
|
||||
color: #ccc;
|
||||
max-width: 1280px;
|
||||
|
|
|
@ -44,6 +44,19 @@
|
|||
<div id="header">
|
||||
<!-- Do not show buttons on the login page -->
|
||||
<div id="headerContent" ng-hide="'/login' == location || '/register' == location">
|
||||
<div id="callBar">
|
||||
<div ng-show="currentCall.state == 'ringing'">
|
||||
Incoming call from {{ currentCall.user_id }}
|
||||
<button ng-click="answerCall()">Answer</button>
|
||||
<button ng-click="hangupCall()">Reject</button>
|
||||
</div>
|
||||
<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 == 'connecting'">Call Connecting...</span>
|
||||
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
|
||||
<span ng-show="currentCall.state == 'ended'">Call Ended</span>
|
||||
<span style="display: none; ">{{ currentCall.state }}</span>
|
||||
</div>
|
||||
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
|
||||
|
||||
<button ng-click='goToPage("/")'>Home</button>
|
||||
|
@ -56,7 +69,7 @@
|
|||
|
||||
<div id="footer" ng-hide="location.indexOf('/room') == 0">
|
||||
<div id="footerContent">
|
||||
© 2014 Matrix.org
|
||||
© 2014 Matrix.org
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -33,8 +33,7 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
|||
console.log("Invited to room " + event.room_id);
|
||||
// FIXME push membership to top level key to match /im/sync
|
||||
event.membership = event.content.membership;
|
||||
// FIXME bodge a nicer name than the room ID for this invite.
|
||||
event.room_display_name = event.user_id + "'s room";
|
||||
|
||||
$scope.rooms[event.room_id] = event;
|
||||
}
|
||||
});
|
||||
|
@ -43,6 +42,11 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
|||
$scope.rooms[event.room_id].lastMsg = event;
|
||||
}
|
||||
});
|
||||
$scope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) {
|
||||
if (isLive) {
|
||||
$scope.rooms[event.room_id].lastMsg = event;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -83,7 +87,9 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
|||
};
|
||||
|
||||
$scope.onInit = function() {
|
||||
refresh();
|
||||
eventHandlerService.waitForInitialSyncCompletion().then(function() {
|
||||
refresh();
|
||||
});
|
||||
};
|
||||
|
||||
}]);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
||||
<tr>
|
||||
<td class="recentsRoomName">
|
||||
{{ room.room_display_name }}
|
||||
{{ room.room_id | roomName }}
|
||||
</td>
|
||||
<td class="recentsRoomSummaryTS">
|
||||
{{ (room.lastMsg.ts) | date:'MMM d HH:mm' }}
|
||||
|
@ -51,7 +51,9 @@
|
|||
</div>
|
||||
|
||||
<div ng-switch-default>
|
||||
{{ room.lastMsg }}
|
||||
<div ng-if="room.lastMsg.type.indexOf('m.call.') == 0">
|
||||
Call
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -82,13 +82,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
updatePresence(event);
|
||||
});
|
||||
|
||||
$rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) {
|
||||
console.trace("incoming call");
|
||||
call.onError = $scope.onCallError;
|
||||
call.onHangup = $scope.onCallHangup;
|
||||
$scope.currentCall = call;
|
||||
});
|
||||
|
||||
$scope.memberCount = function() {
|
||||
return Object.keys($scope.members).length;
|
||||
};
|
||||
|
@ -100,15 +93,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
}
|
||||
};
|
||||
|
||||
$scope.answerCall = function() {
|
||||
$scope.currentCall.answer();
|
||||
};
|
||||
|
||||
$scope.hangupCall = function() {
|
||||
$scope.currentCall.hangup();
|
||||
$scope.currentCall = undefined;
|
||||
};
|
||||
|
||||
var paginate = function(numItems) {
|
||||
// console.log("paginate " + numItems);
|
||||
if ($scope.state.paginating || !$scope.room_id) {
|
||||
|
@ -181,11 +165,11 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
var isNewMember = !(target_user_id in $scope.members);
|
||||
if (isNewMember) {
|
||||
// FIXME: why are we copying these fields around inside chunk?
|
||||
if ("state" in chunk.content) {
|
||||
chunk.presenceState = chunk.content.state; // why is this renamed?
|
||||
if ("presence" in chunk.content) {
|
||||
chunk.presence = chunk.content.presence;
|
||||
}
|
||||
if ("mtime_age" in chunk.content) {
|
||||
chunk.mtime_age = chunk.content.mtime_age;
|
||||
if ("last_active_ago" in chunk.content) {
|
||||
chunk.last_active_ago = chunk.content.last_active_ago;
|
||||
}
|
||||
if ("displayname" in chunk.content) {
|
||||
chunk.displayname = chunk.content.displayname;
|
||||
|
@ -201,9 +185,15 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
}
|
||||
}
|
||||
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];
|
||||
member.content.membership = chunk.content.membership;
|
||||
member.membership = chunk.content.membership;
|
||||
if ("presence" in chunk.content) {
|
||||
member.presence = chunk.content.presence;
|
||||
}
|
||||
if ("last_active_ago" in chunk.content) {
|
||||
member.last_active_ago = chunk.content.last_active_ago;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -221,13 +211,12 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
var member = $scope.members[chunk.content.user_id];
|
||||
|
||||
// XXX: why not just pass the chunk straight through?
|
||||
if ("state" in chunk.content) {
|
||||
member.presenceState = chunk.content.state;
|
||||
if ("presence" in chunk.content) {
|
||||
member.presence = chunk.content.presence;
|
||||
}
|
||||
|
||||
if ("mtime_age" in chunk.content) {
|
||||
// FIXME: should probably keep updating mtime_age in realtime like FB does
|
||||
member.mtime_age = chunk.content.mtime_age;
|
||||
if ("last_active_ago" in chunk.content) {
|
||||
member.last_active_ago = chunk.content.last_active_ago;
|
||||
}
|
||||
|
||||
// this may also contain a new display name or avatar url, so check.
|
||||
|
@ -331,6 +320,11 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
// Make sure the initialSync has been before going further
|
||||
eventHandlerService.waitForInitialSyncCompletion().then(
|
||||
function() {
|
||||
|
||||
// Some data has been retrieved from the iniialSync request
|
||||
// So, the relative time starts here
|
||||
$scope.now = new Date().getTime();
|
||||
|
||||
var needsToJoin = true;
|
||||
|
||||
// The room members is available in the data fetched by initialSync
|
||||
|
@ -377,10 +371,22 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
|
||||
// Make recents highlight the current room
|
||||
$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);
|
||||
|
||||
updateMemberListPresenceAge();
|
||||
};
|
||||
|
||||
$scope.inviteUser = function(user_id) {
|
||||
|
@ -455,16 +461,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||
|
||||
$scope.startVoiceCall = function() {
|
||||
var call = new MatrixCall($scope.room_id);
|
||||
call.onError = $scope.onCallError;
|
||||
call.onHangup = $scope.onCallHangup;
|
||||
call.onError = $rootScope.onCallError;
|
||||
call.onHangup = $rootScope.onCallHangup;
|
||||
call.placeCall();
|
||||
$scope.currentCall = call;
|
||||
$rootScope.currentCall = call;
|
||||
}
|
||||
|
||||
$scope.onCallError = function(errStr) {
|
||||
$scope.feedback = errStr;
|
||||
}
|
||||
|
||||
$scope.onCallHangup = function() {
|
||||
}
|
||||
}]);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div id="roomHeader">
|
||||
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||
<div id="roomName">
|
||||
{{ room_alias || room_id }}
|
||||
{{ room_id | roomName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -26,8 +26,8 @@
|
|||
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
||||
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
||||
</td>
|
||||
<td class="userPresence" ng-class="(member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
||||
<span ng-show="member.mtime_age">{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>ago</span>
|
||||
<td class="userPresence" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
||||
<span ng-show="member.last_active_ago">{{ member.last_active_ago + (now - member.last_updated) | duration }}<br/>ago</span>
|
||||
</td>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -100,18 +100,7 @@
|
|||
<button ng-click="inviteUser(userIDToInvite)">Invite</button>
|
||||
</span>
|
||||
<button ng-click="leaveRoom()">Leave</button>
|
||||
<button ng-click="startVoiceCall()" ng-show="currentCall == undefined && memberCount() == 2">Voice Call</button>
|
||||
<div ng-show="currentCall.state == 'ringing'">
|
||||
Incoming call from {{ currentCall.user_id }}
|
||||
<button ng-click="answerCall()">Answer</button>
|
||||
<button ng-click="hangupCall()">Reject</button>
|
||||
</div>
|
||||
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing'">Hang up</button>
|
||||
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
|
||||
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
|
||||
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
|
||||
<span ng-show="currentCall.state == 'ended'">Call Ended</span>
|
||||
<span style="display: none; ">{{ currentCall.state }}</span>
|
||||
<button ng-click="startVoiceCall()" ng-show="(currentCall == undefined || currentCall.state == 'ended') && memberCount() == 2">Voice Call</button>
|
||||
</div>
|
||||
|
||||
{{ feedback }}
|
||||
|
|
Loading…
Reference in a new issue