You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-19 16:42:09 +03:00
Split out matrix.js into different files. Glue things back.
Added a models directory. Added store, http-api and client files. Slowly transitioning to the architecture outlined in SYJS-5.
This commit is contained in:
674
dist/browser-matrix-dev.js
vendored
674
dist/browser-matrix-dev.js
vendored
@@ -1,24 +1,18 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
(function (global){
|
||||
var matrixcs = require("./lib/matrix");
|
||||
matrixcs.request(require("browser-request"));
|
||||
module.exports = matrixcs; // keep export for browserify package deps
|
||||
global.matrixcs = matrixcs;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./lib/matrix":4,"browser-request":7}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
var MatrixHttpApi = require("./http-api");
|
||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- CS: complete register function (doing stages)
|
||||
- Internal: rate limiting
|
||||
- Identity server: linkEmail, authEmail, bindEmail, lookup3pid
|
||||
- uploadContent (?)
|
||||
*/
|
||||
|
||||
// expose the underlying request object so different environments can use
|
||||
// different request libs (e.g. request or browser-request)
|
||||
var request;
|
||||
/**
|
||||
* The function used to perform HTTP requests.
|
||||
* @param {Function} r The request function which accepts (opts, callback)
|
||||
*/
|
||||
module.exports.request = function(r) {
|
||||
request = r;
|
||||
};
|
||||
// TODO:
|
||||
// Internal: rate limiting
|
||||
|
||||
/*
|
||||
* Construct a Matrix Client.
|
||||
@@ -28,8 +22,9 @@ module.exports.request = function(r) {
|
||||
* noUserAgent: true // to avoid warnings whilst setting UA headers
|
||||
* debug: true // to use console.err() style debugging from the lib
|
||||
* @param {Object} store The data store (if any) for this client.
|
||||
* @param {Function} request The request fn to use.
|
||||
*/
|
||||
function MatrixClient(credentials, config, store) {
|
||||
function MatrixClient(credentials, config, store, request) {
|
||||
if (typeof credentials === "string") {
|
||||
credentials = {
|
||||
"baseUrl": credentials
|
||||
@@ -43,9 +38,6 @@ function MatrixClient(credentials, config, store) {
|
||||
throw new Error("Missing required key: " + requiredKeys[i]);
|
||||
}
|
||||
}
|
||||
if (config && config.noUserAgent) {
|
||||
HEADERS = undefined;
|
||||
}
|
||||
this.config = config;
|
||||
this.credentials = credentials;
|
||||
this.store = store;
|
||||
@@ -53,232 +45,8 @@ function MatrixClient(credentials, config, store) {
|
||||
// track our position in the overall eventstream
|
||||
this.fromToken = undefined;
|
||||
this.clientRunning = false;
|
||||
this._http = new MatrixHttpApi(credentials, config, request);
|
||||
}
|
||||
/**
|
||||
* The high-level Matrix Client class.
|
||||
*/
|
||||
module.exports.MatrixClient = MatrixClient; // expose the class
|
||||
|
||||
/**
|
||||
* Create a new Matrix Client.
|
||||
* @param {Object} credentials The Matrix credentials to use.
|
||||
* @param {Object} config The config options for the client
|
||||
* @param {Store} store The type of store to use.
|
||||
* @return {MatrixClient} A new Matrix Client
|
||||
*/
|
||||
module.exports.createClient = function(credentials, config, store) {
|
||||
return new MatrixClient(credentials, config, store);
|
||||
};
|
||||
|
||||
var CLIENT_PREFIX = "/_matrix/client/api/v1";
|
||||
var CLIENT_V2_PREFIX = "/_matrix/client/v2_alpha";
|
||||
var HEADERS = {
|
||||
"User-Agent": "matrix-js"
|
||||
};
|
||||
|
||||
// Basic DAOs to abstract slightly from the line protocol and let the
|
||||
// application customise events with domain-specific info
|
||||
// (e.g. chat-specific semantics) if it so desires.
|
||||
|
||||
/*
|
||||
* Construct a Matrix Event object
|
||||
* @param {Object} event The raw event to be wrapped in this DAO
|
||||
*/
|
||||
function MatrixEvent(event) {
|
||||
this.event = event || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* An event from Matrix.
|
||||
*/
|
||||
module.exports.MatrixEvent = MatrixEvent;
|
||||
|
||||
MatrixEvent.prototype = {
|
||||
getId: function() {
|
||||
return this.event.event_id;
|
||||
},
|
||||
getSender: function() {
|
||||
return this.event.user_id;
|
||||
},
|
||||
getType: function() {
|
||||
return this.event.type;
|
||||
},
|
||||
getRoomId: function() {
|
||||
return this.event.room_id;
|
||||
},
|
||||
getTs: function() {
|
||||
return this.event.ts;
|
||||
},
|
||||
getContent: function() {
|
||||
return this.event.content;
|
||||
},
|
||||
isState: function() {
|
||||
return this.event.state_key !== undefined;
|
||||
},
|
||||
};
|
||||
|
||||
function MatrixInMemoryStore() {
|
||||
this.rooms = {
|
||||
// state: { },
|
||||
// timeline: [ ],
|
||||
};
|
||||
|
||||
this.presence = {
|
||||
// presence objects keyed by userId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-memory store for Matrix.
|
||||
*/
|
||||
module.exports.MatrixInMemoryStore = MatrixInMemoryStore;
|
||||
|
||||
// XXX: this is currently quite procedural - we could possibly pass back
|
||||
// models of Rooms, Users, Events, etc instead.
|
||||
MatrixInMemoryStore.prototype = {
|
||||
|
||||
/*
|
||||
* Add an array of one or more state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvents: function(stateEvents) {
|
||||
// we store stateEvents indexed by room, event type and state key.
|
||||
for (var i = 0; i < stateEvents.length; i++) {
|
||||
var event = stateEvents[i].event;
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].state === undefined) {
|
||||
this.rooms[roomId].state = {};
|
||||
}
|
||||
if (this.rooms[roomId].state[event.type] === undefined) {
|
||||
this.rooms[roomId].state[event.type] = {};
|
||||
}
|
||||
this.rooms[roomId].state[event.type][event.state_key] = stateEvents[i];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Add a single state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvent: function(stateEvent) {
|
||||
this.setStateEvents([stateEvent]);
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a list of MatrixEvents from the store
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned (optional)
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* (optional, requires type to be specified)
|
||||
* @return {MatrixEvent[]} an array of MatrixEvents from the store,
|
||||
* filtered by roomid, type and state key.
|
||||
*/
|
||||
getStateEvents: function(roomId, type, stateKey) {
|
||||
var stateEvents = [];
|
||||
if (stateKey === undefined && type === undefined) {
|
||||
for (type in this.rooms[roomId].state) {
|
||||
if (this.rooms[roomId].state.hasOwnProperty(type)) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(
|
||||
this.rooms[roomId].state[type][stateKey]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else if (stateKey === undefined) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(this.rooms[roomId].state[type][stateKey]);
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else {
|
||||
return [this.rooms[roomId].state[type][stateKey]];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a single state MatrixEvent from the store for the given roomId
|
||||
* and type.
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* @return {MatrixEvent} a single MatrixEvent from the store, filtered
|
||||
* by roomid, type and state key.
|
||||
*/
|
||||
getStateEvent: function(roomId, type, stateKey) {
|
||||
return this.rooms[roomId].state[type][stateKey];
|
||||
},
|
||||
|
||||
/*
|
||||
* Adds a list of arbitrary MatrixEvents into the store.
|
||||
* If the event is a state event, it is also updates state.
|
||||
*/
|
||||
setEvents: function(events) {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
var event = events[i].event;
|
||||
if (event.type === "m.presence") {
|
||||
this.setPresenceEvents([events[i]]);
|
||||
continue;
|
||||
}
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].timeline === undefined) {
|
||||
this.rooms[roomId].timeline = [];
|
||||
}
|
||||
if (event.state_key !== undefined) {
|
||||
this.setStateEvents([events[i]]);
|
||||
}
|
||||
this.rooms[roomId].timeline.push(events[i]);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the timeline of events for a given room
|
||||
* TODO: ordering!
|
||||
*/
|
||||
getEvents: function(roomId) {
|
||||
return this.room[roomId].timeline;
|
||||
},
|
||||
|
||||
setPresenceEvents: function(presenceEvents) {
|
||||
for (var i = 0; i < presenceEvents.length; i++) {
|
||||
var matrixEvent = presenceEvents[i];
|
||||
this.presence[matrixEvent.event.user_id] = matrixEvent;
|
||||
}
|
||||
},
|
||||
|
||||
getPresenceEvents: function(userId) {
|
||||
return this.presence[userId];
|
||||
},
|
||||
|
||||
getRoomList: function() {
|
||||
var roomIds = [];
|
||||
for (var roomId in this.rooms) {
|
||||
if (this.rooms.hasOwnProperty(roomId)) {
|
||||
roomIds.push(roomId);
|
||||
}
|
||||
}
|
||||
return roomIds;
|
||||
},
|
||||
|
||||
// TODO
|
||||
//setMaxHistoryPerRoom: function(maxHistory) {},
|
||||
|
||||
// TODO
|
||||
//reapOldMessages: function() {},
|
||||
};
|
||||
|
||||
MatrixClient.prototype = {
|
||||
isLoggedIn: function() {
|
||||
return this.credentials.accessToken !== undefined &&
|
||||
@@ -413,7 +181,8 @@ MatrixClient.prototype = {
|
||||
|
||||
var self = this;
|
||||
if (!this.fromToken) {
|
||||
this.initialSync(historyLen, function(err, data) {
|
||||
this._http.initialSync(historyLen, function(err, data) {
|
||||
var i, j;
|
||||
if (err) {
|
||||
if (this.config && this.config.debug) {
|
||||
console.error(
|
||||
@@ -422,9 +191,28 @@ MatrixClient.prototype = {
|
||||
);
|
||||
}
|
||||
callback(err);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (self.store) {
|
||||
var eventMapper = function(event) {
|
||||
return new MatrixEvent(event);
|
||||
};
|
||||
// intercept the results and put them into our store
|
||||
self.store.setPresenceEvents(
|
||||
map(data.presence, eventMapper)
|
||||
);
|
||||
for (i = 0; i < data.rooms.length; i++) {
|
||||
self.store.setStateEvents(
|
||||
map(data.rooms[i].state, eventMapper)
|
||||
);
|
||||
self.store.setEvents(
|
||||
map(data.rooms[i].messages.chunk, eventMapper)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
var events = [];
|
||||
var i, j;
|
||||
for (i = 0; i < data.presence.length; i++) {
|
||||
events.push(new MatrixEvent(data.presence[i]));
|
||||
}
|
||||
@@ -439,9 +227,10 @@ MatrixClient.prototype = {
|
||||
}
|
||||
}
|
||||
callback(undefined, events, false);
|
||||
}
|
||||
|
||||
self.clientRunning = true;
|
||||
self._pollForEvents(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -454,7 +243,7 @@ MatrixClient.prototype = {
|
||||
if (!this.clientRunning) {
|
||||
return;
|
||||
}
|
||||
this.eventStream(this.fromToken, 30000, function(err, data) {
|
||||
this._http.eventStream(this.fromToken, 30000, function(err, data) {
|
||||
if (err) {
|
||||
if (this.config && this.config.debug) {
|
||||
console.error(
|
||||
@@ -468,14 +257,25 @@ MatrixClient.prototype = {
|
||||
setTimeout(function() {
|
||||
self._pollForEvents(callback);
|
||||
}, 2000);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.store) {
|
||||
self.store.setEvents(map(data.chunk,
|
||||
function(event) {
|
||||
return new MatrixEvent(event);
|
||||
}
|
||||
));
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
var events = [];
|
||||
for (var j = 0; j < data.chunk.length; j++) {
|
||||
events.push(new MatrixEvent(data.chunk[j]));
|
||||
}
|
||||
callback(undefined, events, true);
|
||||
self._pollForEvents(callback);
|
||||
}
|
||||
self._pollForEvents(callback);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -486,7 +286,65 @@ MatrixClient.prototype = {
|
||||
stopClient: function() {
|
||||
this.clientRunning = false;
|
||||
},
|
||||
};
|
||||
|
||||
var map = function(array, fn) {
|
||||
var results = new Array(array.length);
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
results[i] = fn(array[i]);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* The high-level Matrix Client class.
|
||||
*/
|
||||
module.exports = MatrixClient; // expose the class
|
||||
|
||||
},{"./http-api":3,"./models/event":5}],3:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- CS: complete register function (doing stages)
|
||||
- Identity server: linkEmail, authEmail, bindEmail, lookup3pid
|
||||
- uploadContent (?)
|
||||
*/
|
||||
var CLIENT_PREFIX = "/_matrix/client/api/v1";
|
||||
var CLIENT_V2_PREFIX = "/_matrix/client/v2_alpha";
|
||||
var HEADERS = {
|
||||
"User-Agent": "matrix-js"
|
||||
};
|
||||
|
||||
/*
|
||||
* Construct a MatrixHttpApi.
|
||||
* @param {Object} credentials The credentials for this client
|
||||
* @param {Object} config The config for this client.
|
||||
* @param {Function} request The request function for doing HTTP requests
|
||||
*/
|
||||
function MatrixHttpApi(credentials, config, request) {
|
||||
if (typeof credentials === "string") {
|
||||
credentials = {
|
||||
"baseUrl": credentials
|
||||
};
|
||||
}
|
||||
var requiredKeys = [
|
||||
"baseUrl"
|
||||
];
|
||||
for (var i = 0; i < requiredKeys.length; i++) {
|
||||
if (!credentials.hasOwnProperty(requiredKeys[i])) {
|
||||
throw new Error("Missing required key: " + requiredKeys[i]);
|
||||
}
|
||||
}
|
||||
if (config && config.noUserAgent) {
|
||||
HEADERS = undefined;
|
||||
}
|
||||
this.config = config;
|
||||
this.credentials = credentials;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
MatrixHttpApi.prototype = {
|
||||
// Room operations
|
||||
// ===============
|
||||
|
||||
@@ -801,31 +659,8 @@ MatrixClient.prototype = {
|
||||
var params = {
|
||||
limit: limit
|
||||
};
|
||||
var self = this;
|
||||
return this._doAuthedRequest(
|
||||
function(err, data) {
|
||||
if (self.store) {
|
||||
var eventMapper = function(event) {
|
||||
return new MatrixEvent(event);
|
||||
};
|
||||
// intercept the results and put them into our store
|
||||
self.store.setPresenceEvents(
|
||||
map(data.presence, eventMapper)
|
||||
);
|
||||
for (var i = 0; i < data.rooms.length; i++) {
|
||||
self.store.setStateEvents(
|
||||
map(data.rooms[i].state, eventMapper)
|
||||
);
|
||||
self.store.setEvents(
|
||||
map(data.rooms[i].messages.chunk, eventMapper)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
}
|
||||
callback(err, data); // continue with original callback
|
||||
}, "GET", "/initialSync", params
|
||||
callback, "GET", "/initialSync", params
|
||||
);
|
||||
},
|
||||
|
||||
@@ -871,21 +706,7 @@ MatrixClient.prototype = {
|
||||
from: from,
|
||||
timeout: timeout
|
||||
};
|
||||
var self = this;
|
||||
return this._doAuthedRequest(
|
||||
function(err, data) {
|
||||
if (self.store) {
|
||||
self.store.setEvents(map(data.chunk,
|
||||
function(event) {
|
||||
return new MatrixEvent(event);
|
||||
}
|
||||
));
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
}
|
||||
callback(err, data); // continue with original callback
|
||||
}, "GET", "/events", params);
|
||||
return this._doAuthedRequest(callback, "GET", "/events", params);
|
||||
},
|
||||
|
||||
// Registration/Login operations
|
||||
@@ -896,7 +717,6 @@ MatrixClient.prototype = {
|
||||
return this._doAuthedRequest(
|
||||
callback, "POST", "/login", undefined, data
|
||||
);
|
||||
// XXX: surely we should store the results of this into our credentials
|
||||
},
|
||||
|
||||
register: function(loginType, data, callback) {
|
||||
@@ -1008,8 +828,8 @@ MatrixClient.prototype = {
|
||||
|
||||
/**
|
||||
* Get the content repository url with query parameters.
|
||||
* @return {Object} An object with a 'base', 'path' and 'params' for
|
||||
* base URL, path and query parameters respectively.
|
||||
* @return {Object} An object with a 'base', 'path' and 'params' for base URL,
|
||||
* path and query parameters respectively.
|
||||
*/
|
||||
getContentUri: function() {
|
||||
var params = {
|
||||
@@ -1054,7 +874,7 @@ MatrixClient.prototype = {
|
||||
throw Error("Expected callback to be a function");
|
||||
}
|
||||
|
||||
return request(
|
||||
return this.request(
|
||||
{
|
||||
uri: uri,
|
||||
method: method,
|
||||
@@ -1112,15 +932,251 @@ var isFunction = function(value) {
|
||||
return Object.prototype.toString.call(value) == "[object Function]";
|
||||
};
|
||||
|
||||
var map = function(array, fn) {
|
||||
var results = new Array(array.length);
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
results[i] = fn(array[i]);
|
||||
}
|
||||
return results;
|
||||
/**
|
||||
* The Matrix HTTP API class.
|
||||
*/
|
||||
module.exports = MatrixHttpApi;
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
/** The Matrix Event class */
|
||||
module.exports.MatrixEvent = require("./models/event").MatrixEvent;
|
||||
/** An in-memory store for the SDK */
|
||||
module.exports.MatrixInMemoryStore = require("./store/memory");
|
||||
/** The raw HTTP API */
|
||||
module.exports.MatrixHttpApi = require("./http-api");
|
||||
/** The managed client class */
|
||||
module.exports.MatrixClient = require("./client");
|
||||
|
||||
// expose the underlying request object so different environments can use
|
||||
// different request libs (e.g. request or browser-request)
|
||||
var request;
|
||||
/**
|
||||
* The function used to perform HTTP requests.
|
||||
* @param {Function} r The request function which accepts (opts, callback)
|
||||
*/
|
||||
module.exports.request = function(r) {
|
||||
request = r;
|
||||
};
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
/**
|
||||
* Create a new Matrix Client.
|
||||
* @param {Object} credentials The Matrix credentials to use.
|
||||
* @param {Object} config The config options for the client
|
||||
* @param {Store} store The type of store to use.
|
||||
* @return {MatrixClient} A new Matrix Client
|
||||
*/
|
||||
module.exports.createClient = function(credentials, config, store) {
|
||||
return new module.exports.MatrixClient(credentials, config, store, request);
|
||||
};
|
||||
|
||||
|
||||
},{"./client":2,"./http-api":3,"./models/event":5,"./store/memory":6}],5:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Construct a Matrix Event object
|
||||
* @param {Object} event The raw event to be wrapped in this DAO
|
||||
*/
|
||||
function MatrixEvent(event) {
|
||||
this.event = event || {};
|
||||
}
|
||||
MatrixEvent.prototype = {
|
||||
getId: function() {
|
||||
return this.event.event_id;
|
||||
},
|
||||
getSender: function() {
|
||||
return this.event.user_id;
|
||||
},
|
||||
getType: function() {
|
||||
return this.event.type;
|
||||
},
|
||||
getRoomId: function() {
|
||||
return this.event.room_id;
|
||||
},
|
||||
getTs: function() {
|
||||
return this.event.ts;
|
||||
},
|
||||
getContent: function() {
|
||||
return this.event.content;
|
||||
},
|
||||
isState: function() {
|
||||
return this.event.state_key !== undefined;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* An event from Matrix.
|
||||
*/
|
||||
module.exports.MatrixEvent = MatrixEvent;
|
||||
|
||||
},{}],6:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
function MatrixInMemoryStore() {
|
||||
this.rooms = {
|
||||
// state: { },
|
||||
// timeline: [ ],
|
||||
};
|
||||
|
||||
this.presence = {
|
||||
// presence objects keyed by userId
|
||||
};
|
||||
}
|
||||
|
||||
// XXX: this is currently quite procedural - we could possibly pass back
|
||||
// models of Rooms, Users, Events, etc instead.
|
||||
MatrixInMemoryStore.prototype = {
|
||||
|
||||
/*
|
||||
* Add an array of one or more state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvents: function(stateEvents) {
|
||||
// we store stateEvents indexed by room, event type and state key.
|
||||
for (var i = 0; i < stateEvents.length; i++) {
|
||||
var event = stateEvents[i].event;
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].state === undefined) {
|
||||
this.rooms[roomId].state = {};
|
||||
}
|
||||
if (this.rooms[roomId].state[event.type] === undefined) {
|
||||
this.rooms[roomId].state[event.type] = {};
|
||||
}
|
||||
this.rooms[roomId].state[event.type][event.state_key] = stateEvents[i];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Add a single state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvent: function(stateEvent) {
|
||||
this.setStateEvents([stateEvent]);
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a list of MatrixEvents from the store
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned (optional)
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* (optional, requires type to be specified)
|
||||
* @return {MatrixEvent[]} an array of MatrixEvents from the store,
|
||||
* filtered by roomid, type and state key.
|
||||
*/
|
||||
getStateEvents: function(roomId, type, stateKey) {
|
||||
var stateEvents = [];
|
||||
if (stateKey === undefined && type === undefined) {
|
||||
for (type in this.rooms[roomId].state) {
|
||||
if (this.rooms[roomId].state.hasOwnProperty(type)) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(
|
||||
this.rooms[roomId].state[type][stateKey]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else if (stateKey === undefined) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(this.rooms[roomId].state[type][stateKey]);
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else {
|
||||
return [this.rooms[roomId].state[type][stateKey]];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a single state MatrixEvent from the store for the given roomId
|
||||
* and type.
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* @return {MatrixEvent} a single MatrixEvent from the store, filtered
|
||||
* by roomid, type and state key.
|
||||
*/
|
||||
getStateEvent: function(roomId, type, stateKey) {
|
||||
return this.rooms[roomId].state[type][stateKey];
|
||||
},
|
||||
|
||||
/*
|
||||
* Adds a list of arbitrary MatrixEvents into the store.
|
||||
* If the event is a state event, it is also updates state.
|
||||
*/
|
||||
setEvents: function(events) {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
var event = events[i].event;
|
||||
if (event.type === "m.presence") {
|
||||
this.setPresenceEvents([events[i]]);
|
||||
continue;
|
||||
}
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].timeline === undefined) {
|
||||
this.rooms[roomId].timeline = [];
|
||||
}
|
||||
if (event.state_key !== undefined) {
|
||||
this.setStateEvents([events[i]]);
|
||||
}
|
||||
this.rooms[roomId].timeline.push(events[i]);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the timeline of events for a given room
|
||||
* TODO: ordering!
|
||||
*/
|
||||
getEvents: function(roomId) {
|
||||
return this.room[roomId].timeline;
|
||||
},
|
||||
|
||||
setPresenceEvents: function(presenceEvents) {
|
||||
for (var i = 0; i < presenceEvents.length; i++) {
|
||||
var matrixEvent = presenceEvents[i];
|
||||
this.presence[matrixEvent.event.user_id] = matrixEvent;
|
||||
}
|
||||
},
|
||||
|
||||
getPresenceEvents: function(userId) {
|
||||
return this.presence[userId];
|
||||
},
|
||||
|
||||
getRoomList: function() {
|
||||
var roomIds = [];
|
||||
for (var roomId in this.rooms) {
|
||||
if (this.rooms.hasOwnProperty(roomId)) {
|
||||
roomIds.push(roomId);
|
||||
}
|
||||
}
|
||||
return roomIds;
|
||||
},
|
||||
|
||||
// TODO
|
||||
//setMaxHistoryPerRoom: function(maxHistory) {},
|
||||
|
||||
// TODO
|
||||
//reapOldMessages: function() {},
|
||||
};
|
||||
|
||||
/**
|
||||
* An in-memory store for Matrix.
|
||||
*/
|
||||
module.exports = MatrixInMemoryStore;
|
||||
|
||||
},{}],7:[function(require,module,exports){
|
||||
// Browser Request
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -1616,12 +1672,4 @@ function b64_enc (data) {
|
||||
}));
|
||||
//UMD FOOTER END
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
(function (global){
|
||||
var matrixcs = require("./lib/matrix");
|
||||
matrixcs.request(require("browser-request"));
|
||||
module.exports = matrixcs; // keep export for browserify package deps
|
||||
global.matrixcs = matrixcs;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./lib/matrix":1,"browser-request":2}]},{},[3]);
|
||||
},{}]},{},[1]);
|
||||
|
293
lib/client.js
Normal file
293
lib/client.js
Normal file
@@ -0,0 +1,293 @@
|
||||
"use strict";
|
||||
var MatrixHttpApi = require("./http-api");
|
||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||
|
||||
// TODO:
|
||||
// Internal: rate limiting
|
||||
|
||||
/*
|
||||
* Construct a Matrix Client.
|
||||
* @param {Object} credentials The credentials for this client
|
||||
* @param {Object} config The config (if any) for this client.
|
||||
* Valid config params include:
|
||||
* noUserAgent: true // to avoid warnings whilst setting UA headers
|
||||
* debug: true // to use console.err() style debugging from the lib
|
||||
* @param {Object} store The data store (if any) for this client.
|
||||
* @param {Function} request The request fn to use.
|
||||
*/
|
||||
function MatrixClient(credentials, config, store, request) {
|
||||
if (typeof credentials === "string") {
|
||||
credentials = {
|
||||
"baseUrl": credentials
|
||||
};
|
||||
}
|
||||
var requiredKeys = [
|
||||
"baseUrl"
|
||||
];
|
||||
for (var i = 0; i < requiredKeys.length; i++) {
|
||||
if (!credentials.hasOwnProperty(requiredKeys[i])) {
|
||||
throw new Error("Missing required key: " + requiredKeys[i]);
|
||||
}
|
||||
}
|
||||
this.config = config;
|
||||
this.credentials = credentials;
|
||||
this.store = store;
|
||||
|
||||
// track our position in the overall eventstream
|
||||
this.fromToken = undefined;
|
||||
this.clientRunning = false;
|
||||
this._http = new MatrixHttpApi(credentials, config, request);
|
||||
}
|
||||
MatrixClient.prototype = {
|
||||
isLoggedIn: function() {
|
||||
return this.credentials.accessToken !== undefined &&
|
||||
this.credentials.userId !== undefined;
|
||||
},
|
||||
|
||||
// Higher level APIs
|
||||
// =================
|
||||
|
||||
// TODO: stuff to handle:
|
||||
// local echo
|
||||
// event dup suppression? - apparently we should still be doing this
|
||||
// tracking current display name / avatar per-message
|
||||
// pagination
|
||||
// re-sending (including persisting pending messages to be sent)
|
||||
// - Need a nice way to callback the app for arbitrary events like
|
||||
// displayname changes
|
||||
// due to ambiguity (or should this be on a chat-specific layer)?
|
||||
// reconnect after connectivity outages
|
||||
|
||||
/*
|
||||
* Helper method for retrieving the name of a room suitable for display
|
||||
* in the UI
|
||||
* TODO: in future, this should be being generated serverside.
|
||||
* @param {String} roomId ID of room whose name is to be resolved
|
||||
* @return {String} human-readable label for room.
|
||||
*/
|
||||
getFriendlyRoomName: function(roomId) {
|
||||
// we need a store to track the inputs for calculating room names
|
||||
if (!this.store) {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
// check for an alias, if any. for now, assume first alias is the
|
||||
// official one.
|
||||
var alias;
|
||||
var mRoomAliases = this.store.getStateEvents(roomId, 'm.room.aliases')[0];
|
||||
if (mRoomAliases) {
|
||||
alias = mRoomAliases.event.content.aliases[0];
|
||||
}
|
||||
|
||||
var mRoomName = this.store.getStateEvent(roomId, 'm.room.name', '');
|
||||
if (mRoomName) {
|
||||
return mRoomName.event.content.name + (alias ? " (" + alias + ")" : "");
|
||||
}
|
||||
else if (alias) {
|
||||
return alias;
|
||||
}
|
||||
else {
|
||||
var userId = this.credentials.userId;
|
||||
var members = this.store.getStateEvents(roomId, 'm.room.member')
|
||||
.filter(function(event) {
|
||||
return event.event.user_id !== userId;
|
||||
});
|
||||
|
||||
if (members.length === 0) {
|
||||
return "Unknown";
|
||||
}
|
||||
else if (members.length == 1) {
|
||||
return (
|
||||
members[0].event.content.displayname ||
|
||||
members[0].event.user_id
|
||||
);
|
||||
}
|
||||
else if (members.length == 2) {
|
||||
return (
|
||||
(members[0].event.content.displayname ||
|
||||
members[0].event.user_id) +
|
||||
" and " +
|
||||
(members[1].event.content.displayname ||
|
||||
members[1].event.user_id)
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
(members[0].event.content.displayname ||
|
||||
members[0].event.user_id) +
|
||||
" and " +
|
||||
(members.length - 1) + " others"
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper method for retrieving the name of a user suitable for display
|
||||
* in the UI in the context of a room - i.e. disambiguating from any
|
||||
* other users in the room.
|
||||
* XXX: This could perhaps also be generated serverside, perhaps by just passing
|
||||
* a 'disambiguate' flag down on membership entries which have ambiguous
|
||||
* displaynames?
|
||||
* @param {String} userId ID of the user whose name is to be resolved
|
||||
* @param {String} roomId ID of room to be used as the context for
|
||||
* resolving the name.
|
||||
* @return {String} human-readable name of the user.
|
||||
*/
|
||||
getFriendlyDisplayName: function(userId, roomId) {
|
||||
// we need a store to track the inputs for calculating display names
|
||||
if (!this.store) { return userId; }
|
||||
|
||||
var displayName;
|
||||
var memberEvent = this.store.getStateEvent(roomId, 'm.room.member', userId);
|
||||
if (memberEvent && memberEvent.event.content.displayname) {
|
||||
displayName = memberEvent.event.content.displayname;
|
||||
}
|
||||
else {
|
||||
return userId;
|
||||
}
|
||||
|
||||
var members = this.store.getStateEvents(roomId, 'm.room.member')
|
||||
.filter(function(event) {
|
||||
return event.event.content.displayname === displayName;
|
||||
});
|
||||
|
||||
if (members.length > 1) {
|
||||
return displayName + " (" + userId + ")";
|
||||
}
|
||||
else {
|
||||
return displayName;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* High level helper method to call initialSync, emit the resulting events,
|
||||
* and then start polling the eventStream for new events.
|
||||
* @param {function} callback Callback invoked whenever new event are available
|
||||
* @param {Number} historyLen amount of historical timeline events to
|
||||
* emit during from the initial sync.
|
||||
*/
|
||||
startClient: function(callback, historyLen) {
|
||||
historyLen = historyLen || 12;
|
||||
|
||||
var self = this;
|
||||
if (!this.fromToken) {
|
||||
this._http.initialSync(historyLen, function(err, data) {
|
||||
var i, j;
|
||||
if (err) {
|
||||
if (this.config && this.config.debug) {
|
||||
console.error(
|
||||
"startClient error on initialSync: %s",
|
||||
JSON.stringify(err)
|
||||
);
|
||||
}
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
if (self.store) {
|
||||
var eventMapper = function(event) {
|
||||
return new MatrixEvent(event);
|
||||
};
|
||||
// intercept the results and put them into our store
|
||||
self.store.setPresenceEvents(
|
||||
map(data.presence, eventMapper)
|
||||
);
|
||||
for (i = 0; i < data.rooms.length; i++) {
|
||||
self.store.setStateEvents(
|
||||
map(data.rooms[i].state, eventMapper)
|
||||
);
|
||||
self.store.setEvents(
|
||||
map(data.rooms[i].messages.chunk, eventMapper)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
var events = [];
|
||||
for (i = 0; i < data.presence.length; i++) {
|
||||
events.push(new MatrixEvent(data.presence[i]));
|
||||
}
|
||||
for (i = 0; i < data.rooms.length; i++) {
|
||||
for (j = 0; j < data.rooms[i].state.length; j++) {
|
||||
events.push(new MatrixEvent(data.rooms[i].state[j]));
|
||||
}
|
||||
for (j = 0; j < data.rooms[i].messages.chunk.length; j++) {
|
||||
events.push(
|
||||
new MatrixEvent(data.rooms[i].messages.chunk[j])
|
||||
);
|
||||
}
|
||||
}
|
||||
callback(undefined, events, false);
|
||||
}
|
||||
|
||||
self.clientRunning = true;
|
||||
self._pollForEvents(callback);
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._pollForEvents(callback);
|
||||
}
|
||||
},
|
||||
|
||||
_pollForEvents: function(callback) {
|
||||
var self = this;
|
||||
if (!this.clientRunning) {
|
||||
return;
|
||||
}
|
||||
this._http.eventStream(this.fromToken, 30000, function(err, data) {
|
||||
if (err) {
|
||||
if (this.config && this.config.debug) {
|
||||
console.error(
|
||||
"error polling for events via eventStream: %s",
|
||||
JSON.stringify(err)
|
||||
);
|
||||
}
|
||||
callback(err);
|
||||
// retry every few seconds
|
||||
// FIXME: this should be exponential backoff with an option to nudge
|
||||
setTimeout(function() {
|
||||
self._pollForEvents(callback);
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.store) {
|
||||
self.store.setEvents(map(data.chunk,
|
||||
function(event) {
|
||||
return new MatrixEvent(event);
|
||||
}
|
||||
));
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
var events = [];
|
||||
for (var j = 0; j < data.chunk.length; j++) {
|
||||
events.push(new MatrixEvent(data.chunk[j]));
|
||||
}
|
||||
callback(undefined, events, true);
|
||||
}
|
||||
self._pollForEvents(callback);
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* High level helper method to stop the client from polling and allow a
|
||||
* clean shutdown.
|
||||
*/
|
||||
stopClient: function() {
|
||||
this.clientRunning = false;
|
||||
},
|
||||
};
|
||||
|
||||
var map = function(array, fn) {
|
||||
var results = new Array(array.length);
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
results[i] = fn(array[i]);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* The high-level Matrix Client class.
|
||||
*/
|
||||
module.exports = MatrixClient; // expose the class
|
634
lib/http-api.js
Normal file
634
lib/http-api.js
Normal file
@@ -0,0 +1,634 @@
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- CS: complete register function (doing stages)
|
||||
- Identity server: linkEmail, authEmail, bindEmail, lookup3pid
|
||||
- uploadContent (?)
|
||||
*/
|
||||
var CLIENT_PREFIX = "/_matrix/client/api/v1";
|
||||
var CLIENT_V2_PREFIX = "/_matrix/client/v2_alpha";
|
||||
var HEADERS = {
|
||||
"User-Agent": "matrix-js"
|
||||
};
|
||||
|
||||
/*
|
||||
* Construct a MatrixHttpApi.
|
||||
* @param {Object} credentials The credentials for this client
|
||||
* @param {Object} config The config for this client.
|
||||
* @param {Function} request The request function for doing HTTP requests
|
||||
*/
|
||||
function MatrixHttpApi(credentials, config, request) {
|
||||
if (typeof credentials === "string") {
|
||||
credentials = {
|
||||
"baseUrl": credentials
|
||||
};
|
||||
}
|
||||
var requiredKeys = [
|
||||
"baseUrl"
|
||||
];
|
||||
for (var i = 0; i < requiredKeys.length; i++) {
|
||||
if (!credentials.hasOwnProperty(requiredKeys[i])) {
|
||||
throw new Error("Missing required key: " + requiredKeys[i]);
|
||||
}
|
||||
}
|
||||
if (config && config.noUserAgent) {
|
||||
HEADERS = undefined;
|
||||
}
|
||||
this.config = config;
|
||||
this.credentials = credentials;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
MatrixHttpApi.prototype = {
|
||||
// Room operations
|
||||
// ===============
|
||||
|
||||
createRoom: function(options, callback) {
|
||||
// valid options include: room_alias_name, visibility, invite
|
||||
return this._doAuthedRequest(
|
||||
callback, "POST", "/createRoom", undefined, options
|
||||
);
|
||||
},
|
||||
|
||||
joinRoom: function(roomIdOrAlias, callback) {
|
||||
var path = encodeUri("/join/$roomid", { $roomid: roomIdOrAlias});
|
||||
return this._doAuthedRequest(callback, "POST", path, undefined, {});
|
||||
},
|
||||
|
||||
setRoomName: function(roomId, name, callback) {
|
||||
return this.sendStateEvent(roomId, "m.room.name", {name: name},
|
||||
undefined, callback);
|
||||
},
|
||||
|
||||
setRoomTopic: function(roomId, topic, callback) {
|
||||
return this.sendStateEvent(roomId, "m.room.topic", {topic: topic},
|
||||
undefined, callback);
|
||||
},
|
||||
|
||||
setPowerLevel: function(roomId, userId, powerLevel, event, callback) {
|
||||
var content = {
|
||||
users: {}
|
||||
};
|
||||
if (event && event.type == "m.room.power_levels") {
|
||||
content = event.content;
|
||||
}
|
||||
content.users[userId] = powerLevel;
|
||||
var path = encodeUri("/rooms/$roomId/state/m.room.power_levels", {
|
||||
$roomId: roomId
|
||||
});
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
},
|
||||
|
||||
getStateEvent: function(roomId, eventType, stateKey, callback) {
|
||||
var pathParams = {
|
||||
$roomId: roomId,
|
||||
$eventType: eventType,
|
||||
$stateKey: stateKey
|
||||
};
|
||||
var path = encodeUri("/rooms/$roomId/state/$eventType", pathParams);
|
||||
if (stateKey !== undefined) {
|
||||
path = encodeUri(path + "/$stateKey", pathParams);
|
||||
}
|
||||
return this._doAuthedRequest(
|
||||
callback, "GET", path
|
||||
);
|
||||
},
|
||||
|
||||
sendStateEvent: function(roomId, eventType, content, stateKey,
|
||||
callback) {
|
||||
var pathParams = {
|
||||
$roomId: roomId,
|
||||
$eventType: eventType,
|
||||
$stateKey: stateKey
|
||||
};
|
||||
var path = encodeUri("/rooms/$roomId/state/$eventType", pathParams);
|
||||
if (stateKey !== undefined) {
|
||||
path = encodeUri(path + "/$stateKey", pathParams);
|
||||
}
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
},
|
||||
|
||||
sendEvent: function(roomId, eventType, content, txnId, callback) {
|
||||
if (isFunction(txnId)) { callback = txnId; txnId = undefined; }
|
||||
|
||||
if (!txnId) {
|
||||
txnId = "m" + new Date().getTime();
|
||||
}
|
||||
|
||||
var path = encodeUri("/rooms/$roomId/send/$eventType/$txnId", {
|
||||
$roomId: roomId,
|
||||
$eventType: eventType,
|
||||
$txnId: txnId
|
||||
});
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
},
|
||||
|
||||
sendMessage: function(roomId, content, txnId, callback) {
|
||||
if (isFunction(txnId)) { callback = txnId; txnId = undefined; }
|
||||
return this.sendEvent(
|
||||
roomId, "m.room.message", content, txnId, callback
|
||||
);
|
||||
},
|
||||
|
||||
sendTextMessage: function(roomId, body, txnId, callback) {
|
||||
var content = {
|
||||
msgtype: "m.text",
|
||||
body: body
|
||||
};
|
||||
return this.sendMessage(roomId, content, txnId, callback);
|
||||
},
|
||||
|
||||
sendEmoteMessage: function(roomId, body, txnId, callback) {
|
||||
var content = {
|
||||
msgtype: "m.emote",
|
||||
body: body
|
||||
};
|
||||
return this.sendMessage(roomId, content, txnId, callback);
|
||||
},
|
||||
|
||||
sendImageMessage: function(roomId, url, info, text, callback) {
|
||||
if (isFunction(text)) { callback = text; text = undefined; }
|
||||
if (!text) { text = "Image"; }
|
||||
var content = {
|
||||
msgtype: "m.image",
|
||||
url: url,
|
||||
info: info,
|
||||
body: text
|
||||
};
|
||||
return this.sendMessage(roomId, content, callback);
|
||||
},
|
||||
|
||||
sendHtmlMessage: function(roomId, body, htmlBody, callback) {
|
||||
var content = {
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
body: body,
|
||||
formatted_body: htmlBody
|
||||
};
|
||||
return this.sendMessage(roomId, content, callback);
|
||||
},
|
||||
|
||||
sendTyping: function(roomId, isTyping, timeoutMs, callback) {
|
||||
var path = encodeUri("/rooms/$roomId/typing/$userId", {
|
||||
$roomId: roomId,
|
||||
$userId: this.credentials.userId
|
||||
});
|
||||
var data = {
|
||||
typing: isTyping
|
||||
};
|
||||
if (isTyping) {
|
||||
data.timeout = timeoutMs ? timeoutMs : 20000;
|
||||
}
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, data
|
||||
);
|
||||
},
|
||||
|
||||
redactEvent: function(roomId, eventId, callback) {
|
||||
var path = encodeUri("/rooms/$roomId/redact/$eventId", {
|
||||
$roomId: roomId,
|
||||
$eventId: eventId
|
||||
});
|
||||
return this._doAuthedRequest(callback, "POST", path, undefined, {});
|
||||
},
|
||||
|
||||
invite: function(roomId, userId, callback) {
|
||||
return this._membershipChange(roomId, userId, "invite", undefined,
|
||||
callback);
|
||||
},
|
||||
|
||||
leave: function(roomId, callback) {
|
||||
return this._membershipChange(roomId, undefined, "leave", undefined,
|
||||
callback);
|
||||
},
|
||||
|
||||
ban: function(roomId, userId, reason, callback) {
|
||||
return this._membershipChange(roomId, userId, "ban", reason,
|
||||
callback);
|
||||
},
|
||||
|
||||
unban: function(roomId, userId, callback) {
|
||||
// unbanning = set their state to leave
|
||||
return this._setMembershipState(
|
||||
roomId, userId, "leave", undefined, callback
|
||||
);
|
||||
},
|
||||
|
||||
kick: function(roomId, userId, reason, callback) {
|
||||
return this._setMembershipState(
|
||||
roomId, userId, "leave", reason, callback
|
||||
);
|
||||
},
|
||||
|
||||
_setMembershipState: function(roomId, userId, membershipValue, reason,
|
||||
callback) {
|
||||
if (isFunction(reason)) { callback = reason; reason = undefined; }
|
||||
|
||||
var path = encodeUri(
|
||||
"/rooms/$roomId/state/m.room.member/$userId",
|
||||
{ $roomId: roomId, $userId: userId}
|
||||
);
|
||||
|
||||
return this._doAuthedRequest(callback, "PUT", path, undefined, {
|
||||
membership: membershipValue,
|
||||
reason: reason
|
||||
});
|
||||
},
|
||||
|
||||
_membershipChange: function(roomId, userId, membership, reason,
|
||||
callback) {
|
||||
if (isFunction(reason)) { callback = reason; reason = undefined; }
|
||||
|
||||
var path = encodeUri("/rooms/$room_id/$membership", {
|
||||
$room_id: roomId,
|
||||
$membership: membership
|
||||
});
|
||||
return this._doAuthedRequest(
|
||||
callback, "POST", path, undefined, {
|
||||
user_id: userId, // may be undefined e.g. on leave
|
||||
reason: reason
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Profile operations
|
||||
// ==================
|
||||
|
||||
getProfileInfo: function(userId, info, callback) {
|
||||
if (isFunction(info)) { callback = info; info = undefined; }
|
||||
|
||||
var path = info ?
|
||||
encodeUri("/profile/$userId/$info",
|
||||
{ $userId: userId, $info: info }) :
|
||||
encodeUri("/profile/$userId",
|
||||
{ $userId: userId });
|
||||
return this._doAuthedRequest(callback, "GET", path);
|
||||
},
|
||||
|
||||
setProfileInfo: function(info, data, callback) {
|
||||
var path = encodeUri("/profile/$userId/$info", {
|
||||
$userId: this.credentials.userId,
|
||||
$info: info
|
||||
});
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, data
|
||||
);
|
||||
},
|
||||
|
||||
setDisplayName: function(name, callback) {
|
||||
return this.setProfileInfo(
|
||||
"displayname", { displayname: name }, callback
|
||||
);
|
||||
},
|
||||
|
||||
setAvatarUrl: function(url, callback) {
|
||||
return this.setProfileInfo(
|
||||
"avatar_url", { avatar_url: url }, callback
|
||||
);
|
||||
},
|
||||
|
||||
getThreePids: function(creds, bind, callback) {
|
||||
var path = "/account/3pid";
|
||||
return this._doAuthedV2Request(
|
||||
callback, "GET", path, undefined, undefined
|
||||
);
|
||||
},
|
||||
|
||||
addThreePid: function(creds, bind, callback) {
|
||||
var path = "/account/3pid";
|
||||
var data = {
|
||||
'threePidCreds': creds,
|
||||
'bind': bind
|
||||
};
|
||||
return this._doAuthedV2Request(
|
||||
callback, "POST", path, undefined, data
|
||||
);
|
||||
},
|
||||
|
||||
setPresence: function(presence, callback) {
|
||||
var path = encodeUri("/presence/$userId/status", {
|
||||
$userId: this.credentials.userId
|
||||
});
|
||||
var validStates = ["offline", "online", "unavailable"];
|
||||
if (validStates.indexOf(presence) == -1) {
|
||||
throw new Error("Bad presence value: " + presence);
|
||||
}
|
||||
var content = {
|
||||
presence: presence
|
||||
};
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
},
|
||||
|
||||
// Public (non-authed) operations
|
||||
// ==============================
|
||||
|
||||
publicRooms: function(callback) {
|
||||
return this._doRequest(callback, "GET", "/publicRooms");
|
||||
},
|
||||
|
||||
registerFlows: function(callback) {
|
||||
return this._doRequest(callback, "GET", "/register");
|
||||
},
|
||||
|
||||
loginFlows: function(callback) {
|
||||
return this._doRequest(callback, "GET", "/login");
|
||||
},
|
||||
|
||||
resolveRoomAlias: function(roomAlias, callback) {
|
||||
var path = encodeUri("/directory/room/$alias", {$alias: roomAlias});
|
||||
return this._doRequest(callback, "GET", path);
|
||||
},
|
||||
|
||||
// Syncing operations
|
||||
// ==================
|
||||
|
||||
initialSync: function(limit, callback) {
|
||||
var params = {
|
||||
limit: limit
|
||||
};
|
||||
return this._doAuthedRequest(
|
||||
callback, "GET", "/initialSync", params
|
||||
);
|
||||
},
|
||||
|
||||
roomInitialSync: function(roomId, limit, callback) {
|
||||
if (isFunction(limit)) { callback = limit; limit = undefined; }
|
||||
var path = encodeUri("/rooms/$roomId/initialSync",
|
||||
{$roomId: roomId}
|
||||
);
|
||||
if (!limit) {
|
||||
limit = 30;
|
||||
}
|
||||
return this._doAuthedRequest(
|
||||
callback, "GET", path, { limit: limit }
|
||||
);
|
||||
},
|
||||
|
||||
roomState: function(roomId, callback) {
|
||||
var path = encodeUri("/rooms/$roomId/state", {$roomId: roomId});
|
||||
return this._doAuthedRequest(callback, "GET", path);
|
||||
},
|
||||
|
||||
scrollback: function(roomId, from, limit, callback) {
|
||||
if (isFunction(limit)) { callback = limit; limit = undefined; }
|
||||
var path = encodeUri("/rooms/$roomId/messages", {$roomId: roomId});
|
||||
if (!limit) {
|
||||
limit = 30;
|
||||
}
|
||||
var params = {
|
||||
from: from,
|
||||
limit: limit,
|
||||
dir: 'b'
|
||||
};
|
||||
return this._doAuthedRequest(callback, "GET", path, params);
|
||||
},
|
||||
|
||||
eventStream: function(from, timeout, callback) {
|
||||
if (isFunction(timeout)) { callback = timeout; timeout = undefined;}
|
||||
if (!timeout) {
|
||||
timeout = 30000;
|
||||
}
|
||||
|
||||
var params = {
|
||||
from: from,
|
||||
timeout: timeout
|
||||
};
|
||||
return this._doAuthedRequest(callback, "GET", "/events", params);
|
||||
},
|
||||
|
||||
// Registration/Login operations
|
||||
// =============================
|
||||
|
||||
login: function(loginType, data, callback) {
|
||||
data.type = loginType;
|
||||
return this._doAuthedRequest(
|
||||
callback, "POST", "/login", undefined, data
|
||||
);
|
||||
},
|
||||
|
||||
register: function(loginType, data, callback) {
|
||||
data.type = loginType;
|
||||
return this._doAuthedRequest(
|
||||
callback, "POST", "/register", undefined, data
|
||||
);
|
||||
},
|
||||
|
||||
loginWithPassword: function(user, password, callback) {
|
||||
return this.login("m.login.password", {
|
||||
user: user,
|
||||
password: password
|
||||
}, callback);
|
||||
},
|
||||
|
||||
// Push operations
|
||||
// ===============
|
||||
|
||||
pushRules: function(callback) {
|
||||
return this._doAuthedRequest(callback, "GET", "/pushrules/");
|
||||
},
|
||||
|
||||
addPushRule: function(scope, kind, ruleId, body, callback) {
|
||||
// NB. Scope not uri encoded because devices need the '/'
|
||||
var path = encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||||
$kind: kind,
|
||||
$ruleId: ruleId
|
||||
});
|
||||
return this._doAuthedRequest(
|
||||
callback, "PUT", path, undefined, body
|
||||
);
|
||||
},
|
||||
|
||||
deletePushRule: function(scope, kind, ruleId, callback) {
|
||||
// NB. Scope not uri encoded because devices need the '/'
|
||||
var path = encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||||
$kind: kind,
|
||||
$ruleId: ruleId
|
||||
});
|
||||
return this._doAuthedRequest(callback, "DELETE", path);
|
||||
},
|
||||
|
||||
// VoIP operations
|
||||
// ===============
|
||||
|
||||
turnServer: function(callback) {
|
||||
return this._doAuthedRequest(callback, "GET", "/voip/turnServer");
|
||||
},
|
||||
|
||||
// URI functions
|
||||
// =============
|
||||
|
||||
getHttpUriForMxc: function(mxc, width, height, resizeMethod) {
|
||||
if (typeof mxc !== "string" || !mxc) {
|
||||
return mxc;
|
||||
}
|
||||
if (mxc.indexOf("mxc://") !== 0) {
|
||||
return mxc;
|
||||
}
|
||||
var serverAndMediaId = mxc.slice(6); // strips mxc://
|
||||
var prefix = "/_matrix/media/v1/download/";
|
||||
var params = {};
|
||||
|
||||
if (width) {
|
||||
params.width = width;
|
||||
}
|
||||
if (height) {
|
||||
params.height = height;
|
||||
}
|
||||
if (resizeMethod) {
|
||||
params.method = resizeMethod;
|
||||
}
|
||||
if (Object.keys(params).length > 0) {
|
||||
// these are thumbnailing params so they probably want the
|
||||
// thumbnailing API...
|
||||
prefix = "/_matrix/media/v1/thumbnail/";
|
||||
}
|
||||
|
||||
var fragmentOffset = serverAndMediaId.indexOf("#"),
|
||||
fragment = "";
|
||||
if (fragmentOffset >= 0) {
|
||||
fragment = serverAndMediaId.substr(fragmentOffset);
|
||||
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
|
||||
}
|
||||
return this.credentials.baseUrl + prefix + serverAndMediaId +
|
||||
(Object.keys(params).length === 0 ? "" :
|
||||
("?" + encodeParams(params))) + fragment;
|
||||
},
|
||||
|
||||
getIdenticonUri: function(identiconString, width, height) {
|
||||
if (!identiconString) {
|
||||
return;
|
||||
}
|
||||
if (!width) { width = 96; }
|
||||
if (!height) { height = 96; }
|
||||
var params = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
var path = encodeUri("/_matrix/media/v1/identicon/$ident", {
|
||||
$ident: identiconString
|
||||
});
|
||||
return this.credentials.baseUrl + path +
|
||||
(Object.keys(params).length === 0 ? "" :
|
||||
("?" + encodeParams(params)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the content repository url with query parameters.
|
||||
* @return {Object} An object with a 'base', 'path' and 'params' for base URL,
|
||||
* path and query parameters respectively.
|
||||
*/
|
||||
getContentUri: function() {
|
||||
var params = {
|
||||
access_token: this.credentials.accessToken
|
||||
};
|
||||
return {
|
||||
base: this.credentials.baseUrl,
|
||||
path: "/_matrix/media/v1/upload",
|
||||
params: params
|
||||
};
|
||||
},
|
||||
|
||||
// Internals
|
||||
// =========
|
||||
|
||||
_doAuthedRequest: function(callback, method, path, params, data) {
|
||||
if (!params) { params = {}; }
|
||||
params.access_token = this.credentials.accessToken;
|
||||
return this._doRequest(callback, method, path, params, data);
|
||||
},
|
||||
|
||||
_doAuthedV2Request: function(callback, method, path, params, data) {
|
||||
if (!params) { params = {}; }
|
||||
params.access_token = this.credentials.accessToken;
|
||||
return this._doV2Request(callback, method, path, params, data);
|
||||
},
|
||||
|
||||
_doRequest: function(callback, method, path, params, data) {
|
||||
var fullUri = this.credentials.baseUrl + CLIENT_PREFIX + path;
|
||||
if (!params) { params = {}; }
|
||||
return this._request(callback, method, fullUri, params, data);
|
||||
},
|
||||
|
||||
_doV2Request: function(callback, method, path, params, data) {
|
||||
var fullUri = this.credentials.baseUrl + CLIENT_V2_PREFIX + path;
|
||||
if (!params) { params = {}; }
|
||||
return this._request(callback, method, fullUri, params, data);
|
||||
},
|
||||
|
||||
_request: function(callback, method, uri, params, data) {
|
||||
if (callback !== undefined && !isFunction(callback)) {
|
||||
throw Error("Expected callback to be a function");
|
||||
}
|
||||
|
||||
return this.request(
|
||||
{
|
||||
uri: uri,
|
||||
method: method,
|
||||
withCredentials: false,
|
||||
qs: params,
|
||||
body: data,
|
||||
json: true,
|
||||
headers: HEADERS,
|
||||
_matrix_credentials: this.credentials
|
||||
},
|
||||
requestCallback(callback)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var encodeUri = function(pathTemplate, variables) {
|
||||
for (var key in variables) {
|
||||
if (!variables.hasOwnProperty(key)) { continue; }
|
||||
pathTemplate = pathTemplate.replace(
|
||||
key, encodeURIComponent(variables[key])
|
||||
);
|
||||
}
|
||||
return pathTemplate;
|
||||
};
|
||||
|
||||
// avoiding deps on jquery and co
|
||||
var encodeParams = function(params) {
|
||||
var qs = "";
|
||||
for (var key in params) {
|
||||
if (!params.hasOwnProperty(key)) { continue; }
|
||||
qs += "&" + encodeURIComponent(key) + "=" +
|
||||
encodeURIComponent(params[key]);
|
||||
}
|
||||
return qs.substring(1);
|
||||
};
|
||||
|
||||
var requestCallback = function(userDefinedCallback) {
|
||||
if (!userDefinedCallback) {
|
||||
return undefined;
|
||||
}
|
||||
return function(err, response, body) {
|
||||
if (err) {
|
||||
return userDefinedCallback(err);
|
||||
}
|
||||
if (response.statusCode >= 400) {
|
||||
return userDefinedCallback(body);
|
||||
}
|
||||
else {
|
||||
userDefinedCallback(null, body);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var isFunction = function(value) {
|
||||
return Object.prototype.toString.call(value) == "[object Function]";
|
||||
};
|
||||
|
||||
/**
|
||||
* The Matrix HTTP API class.
|
||||
*/
|
||||
module.exports = MatrixHttpApi;
|
1105
lib/matrix.js
1105
lib/matrix.js
File diff suppressed because it is too large
Load Diff
37
lib/models/event.js
Normal file
37
lib/models/event.js
Normal file
@@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Construct a Matrix Event object
|
||||
* @param {Object} event The raw event to be wrapped in this DAO
|
||||
*/
|
||||
function MatrixEvent(event) {
|
||||
this.event = event || {};
|
||||
}
|
||||
MatrixEvent.prototype = {
|
||||
getId: function() {
|
||||
return this.event.event_id;
|
||||
},
|
||||
getSender: function() {
|
||||
return this.event.user_id;
|
||||
},
|
||||
getType: function() {
|
||||
return this.event.type;
|
||||
},
|
||||
getRoomId: function() {
|
||||
return this.event.room_id;
|
||||
},
|
||||
getTs: function() {
|
||||
return this.event.ts;
|
||||
},
|
||||
getContent: function() {
|
||||
return this.event.content;
|
||||
},
|
||||
isState: function() {
|
||||
return this.event.state_key !== undefined;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* An event from Matrix.
|
||||
*/
|
||||
module.exports.MatrixEvent = MatrixEvent;
|
14
lib/models/room.js
Normal file
14
lib/models/room.js
Normal file
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
function Room(roomId) {
|
||||
this.roomId = roomId;
|
||||
this.name = roomId;
|
||||
this.timeline = [];
|
||||
this.oldState = null;
|
||||
this.currentState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Room class.
|
||||
*/
|
||||
module.exports = Room;
|
163
lib/store/memory.js
Normal file
163
lib/store/memory.js
Normal file
@@ -0,0 +1,163 @@
|
||||
"use strict";
|
||||
|
||||
function MatrixInMemoryStore() {
|
||||
this.rooms = {
|
||||
// state: { },
|
||||
// timeline: [ ],
|
||||
};
|
||||
|
||||
this.presence = {
|
||||
// presence objects keyed by userId
|
||||
};
|
||||
}
|
||||
|
||||
// XXX: this is currently quite procedural - we could possibly pass back
|
||||
// models of Rooms, Users, Events, etc instead.
|
||||
MatrixInMemoryStore.prototype = {
|
||||
|
||||
/*
|
||||
* Add an array of one or more state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvents: function(stateEvents) {
|
||||
// we store stateEvents indexed by room, event type and state key.
|
||||
for (var i = 0; i < stateEvents.length; i++) {
|
||||
var event = stateEvents[i].event;
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].state === undefined) {
|
||||
this.rooms[roomId].state = {};
|
||||
}
|
||||
if (this.rooms[roomId].state[event.type] === undefined) {
|
||||
this.rooms[roomId].state[event.type] = {};
|
||||
}
|
||||
this.rooms[roomId].state[event.type][event.state_key] = stateEvents[i];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Add a single state MatrixEvents into the store, overwriting
|
||||
* any existing state with the same {room, type, stateKey} tuple.
|
||||
*/
|
||||
setStateEvent: function(stateEvent) {
|
||||
this.setStateEvents([stateEvent]);
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a list of MatrixEvents from the store
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned (optional)
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* (optional, requires type to be specified)
|
||||
* @return {MatrixEvent[]} an array of MatrixEvents from the store,
|
||||
* filtered by roomid, type and state key.
|
||||
*/
|
||||
getStateEvents: function(roomId, type, stateKey) {
|
||||
var stateEvents = [];
|
||||
if (stateKey === undefined && type === undefined) {
|
||||
for (type in this.rooms[roomId].state) {
|
||||
if (this.rooms[roomId].state.hasOwnProperty(type)) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(
|
||||
this.rooms[roomId].state[type][stateKey]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else if (stateKey === undefined) {
|
||||
for (stateKey in this.rooms[roomId].state[type]) {
|
||||
if (this.rooms[roomId].state[type].hasOwnProperty(stateKey)) {
|
||||
stateEvents.push(this.rooms[roomId].state[type][stateKey]);
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
else {
|
||||
return [this.rooms[roomId].state[type][stateKey]];
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Return a single state MatrixEvent from the store for the given roomId
|
||||
* and type.
|
||||
* @param {String} roomId the Room ID whose state is to be returned
|
||||
* @param {String} type the type of the state events to be returned
|
||||
* @param {String} stateKey the stateKey of the state events to be returned
|
||||
* @return {MatrixEvent} a single MatrixEvent from the store, filtered
|
||||
* by roomid, type and state key.
|
||||
*/
|
||||
getStateEvent: function(roomId, type, stateKey) {
|
||||
return this.rooms[roomId].state[type][stateKey];
|
||||
},
|
||||
|
||||
/*
|
||||
* Adds a list of arbitrary MatrixEvents into the store.
|
||||
* If the event is a state event, it is also updates state.
|
||||
*/
|
||||
setEvents: function(events) {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
var event = events[i].event;
|
||||
if (event.type === "m.presence") {
|
||||
this.setPresenceEvents([events[i]]);
|
||||
continue;
|
||||
}
|
||||
var roomId = event.room_id;
|
||||
if (this.rooms[roomId] === undefined) {
|
||||
this.rooms[roomId] = {};
|
||||
}
|
||||
if (this.rooms[roomId].timeline === undefined) {
|
||||
this.rooms[roomId].timeline = [];
|
||||
}
|
||||
if (event.state_key !== undefined) {
|
||||
this.setStateEvents([events[i]]);
|
||||
}
|
||||
this.rooms[roomId].timeline.push(events[i]);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the timeline of events for a given room
|
||||
* TODO: ordering!
|
||||
*/
|
||||
getEvents: function(roomId) {
|
||||
return this.room[roomId].timeline;
|
||||
},
|
||||
|
||||
setPresenceEvents: function(presenceEvents) {
|
||||
for (var i = 0; i < presenceEvents.length; i++) {
|
||||
var matrixEvent = presenceEvents[i];
|
||||
this.presence[matrixEvent.event.user_id] = matrixEvent;
|
||||
}
|
||||
},
|
||||
|
||||
getPresenceEvents: function(userId) {
|
||||
return this.presence[userId];
|
||||
},
|
||||
|
||||
getRoomList: function() {
|
||||
var roomIds = [];
|
||||
for (var roomId in this.rooms) {
|
||||
if (this.rooms.hasOwnProperty(roomId)) {
|
||||
roomIds.push(roomId);
|
||||
}
|
||||
}
|
||||
return roomIds;
|
||||
},
|
||||
|
||||
// TODO
|
||||
//setMaxHistoryPerRoom: function(maxHistory) {},
|
||||
|
||||
// TODO
|
||||
//reapOldMessages: function() {},
|
||||
};
|
||||
|
||||
/**
|
||||
* An in-memory store for Matrix.
|
||||
*/
|
||||
module.exports = MatrixInMemoryStore;
|
Reference in New Issue
Block a user