You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/wasm
This commit is contained in:
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,3 +1,47 @@
|
|||||||
|
Changes in [0.12.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.0) (2018-10-16)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.0-rc.1...v0.12.0)
|
||||||
|
|
||||||
|
* No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [0.12.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.0-rc.1) (2018-10-11)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.1...v0.12.0-rc.1)
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
----------------
|
||||||
|
* If js-sdk finds data in the store that is incompatible with the options currently being used,
|
||||||
|
it will emit sync state ERROR with an error of type InvalidStoreError. It will also stop trying
|
||||||
|
to sync in this situation: the app must stop the client and then either clear the store or
|
||||||
|
change the options (in this case, enable or disable lazy loading of members) and then start
|
||||||
|
the client again.
|
||||||
|
|
||||||
|
All Changes
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* never replace /sync'ed memberships with OOB ones
|
||||||
|
[\#760](https://github.com/matrix-org/matrix-js-sdk/pull/760)
|
||||||
|
* Don't fail to start up if lazy load check fails
|
||||||
|
[\#759](https://github.com/matrix-org/matrix-js-sdk/pull/759)
|
||||||
|
* Make e2e work on Edge
|
||||||
|
[\#754](https://github.com/matrix-org/matrix-js-sdk/pull/754)
|
||||||
|
* throw error with same name and message over idb worker boundary
|
||||||
|
[\#758](https://github.com/matrix-org/matrix-js-sdk/pull/758)
|
||||||
|
* Default to a room version of 1 when there is no room create event
|
||||||
|
[\#755](https://github.com/matrix-org/matrix-js-sdk/pull/755)
|
||||||
|
* Silence bluebird warnings
|
||||||
|
[\#757](https://github.com/matrix-org/matrix-js-sdk/pull/757)
|
||||||
|
* allow non-ff merge from release branch into master
|
||||||
|
[\#750](https://github.com/matrix-org/matrix-js-sdk/pull/750)
|
||||||
|
* Reject with the actual error on indexeddb error
|
||||||
|
[\#751](https://github.com/matrix-org/matrix-js-sdk/pull/751)
|
||||||
|
* Update mocha to v5
|
||||||
|
[\#744](https://github.com/matrix-org/matrix-js-sdk/pull/744)
|
||||||
|
* disable lazy loading for guests as they cant create filters
|
||||||
|
[\#748](https://github.com/matrix-org/matrix-js-sdk/pull/748)
|
||||||
|
* Revert "Add getMediaLimits to client"
|
||||||
|
[\#745](https://github.com/matrix-org/matrix-js-sdk/pull/745)
|
||||||
|
|
||||||
Changes in [0.11.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1) (2018-10-01)
|
Changes in [0.11.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1) (2018-10-01)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.1-rc.1...v0.11.1)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.1-rc.1...v0.11.1)
|
||||||
|
52
README.md
52
README.md
@@ -30,9 +30,61 @@ In Node.js
|
|||||||
console.log("Public Rooms: %s", JSON.stringify(data));
|
console.log("Public Rooms: %s", JSON.stringify(data));
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
See below for how to include libolm to enable end-to-end-encryption. Please check
|
See below for how to include libolm to enable end-to-end-encryption. Please check
|
||||||
[the Node.js terminal app](examples/node) for a more complex example.
|
[the Node.js terminal app](examples/node) for a more complex example.
|
||||||
|
|
||||||
|
To start the client:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await client.startClient({initialSyncLimit: 10});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can perform a call to `/sync` to get the current state of the client:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
client.once('sync', function(state, prevState, res) {
|
||||||
|
if(state === 'PREPARED') {
|
||||||
|
console.log("prepared");
|
||||||
|
} else {
|
||||||
|
console.log(state);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To send a message:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var content = {
|
||||||
|
"body": "message text",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
};
|
||||||
|
client.sendEvent("roomId", "m.room.message", content, "", (err, res) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To listen for message events:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
client.on("Room.timeline", function(event, room, toStartOfTimeline) {
|
||||||
|
if (event.getType() !== "m.room.message") {
|
||||||
|
return; // only use messages
|
||||||
|
}
|
||||||
|
console.log(event.event.content.body);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the `matrix-js-sdk` client uses the `MatrixInMemoryStore` to store events as they are received. For example to iterate through the currently stored timeline for a room:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Object.keys(client.store.rooms).forEach((roomId) => {
|
||||||
|
client.getRoom(roomId).timeline.forEach(t => {
|
||||||
|
console.log(t.event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
What does this SDK do?
|
What does this SDK do?
|
||||||
----------------------
|
----------------------
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "matrix-js-sdk",
|
"name": "matrix-js-sdk",
|
||||||
"version": "0.11.1",
|
"version": "0.12.0",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt"
|
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
"url": "https://github.com/matrix-org/matrix-js-sdk"
|
"url": "https://github.com/matrix-org/matrix-js-sdk"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@@ -201,22 +201,6 @@ describe("RoomMember", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("supersedesOutOfBand", function() {
|
|
||||||
it("should be set by markSupersedesOutOfBand", function() {
|
|
||||||
const member = new RoomMember();
|
|
||||||
expect(member.supersedesOutOfBand()).toEqual(false);
|
|
||||||
member.markSupersedesOutOfBand();
|
|
||||||
expect(member.supersedesOutOfBand()).toEqual(true);
|
|
||||||
});
|
|
||||||
it("should be cleared by clearSupersedesOutOfBand", function() {
|
|
||||||
const member = new RoomMember();
|
|
||||||
member.markSupersedesOutOfBand();
|
|
||||||
expect(member.supersedesOutOfBand()).toEqual(true);
|
|
||||||
member.clearSupersedesOutOfBand();
|
|
||||||
expect(member.supersedesOutOfBand()).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setMembershipEvent", function() {
|
describe("setMembershipEvent", function() {
|
||||||
const joinEvent = utils.mkMembership({
|
const joinEvent = utils.mkMembership({
|
||||||
event: true,
|
event: true,
|
||||||
|
@@ -299,41 +299,25 @@ describe("RoomState", function() {
|
|||||||
expect(eventReceived).toEqual(true);
|
expect(eventReceived).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should overwrite existing members", function() {
|
it("should never overwrite existing members", function() {
|
||||||
const oobMemberEvent = utils.mkMembership({
|
const oobMemberEvent = utils.mkMembership({
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
user: userA, mship: "join", room: roomId, event: true,
|
||||||
});
|
});
|
||||||
state.markOutOfBandMembersStarted();
|
state.markOutOfBandMembersStarted();
|
||||||
state.setOutOfBandMembers([oobMemberEvent]);
|
state.setOutOfBandMembers([oobMemberEvent]);
|
||||||
const memberA = state.getMember(userA);
|
const memberA = state.getMember(userA);
|
||||||
expect(memberA.events.member.getId()).toEqual(oobMemberEvent.getId());
|
expect(memberA.events.member.getId()).toNotEqual(oobMemberEvent.getId());
|
||||||
expect(memberA.isOutOfBand()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow later state events to overwrite", function() {
|
|
||||||
const oobMemberEvent = utils.mkMembership({
|
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
|
||||||
});
|
|
||||||
const memberEvent = utils.mkMembership({
|
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
state.markOutOfBandMembersStarted();
|
|
||||||
state.setOutOfBandMembers([oobMemberEvent]);
|
|
||||||
state.setStateEvents([memberEvent]);
|
|
||||||
|
|
||||||
const memberA = state.getMember(userA);
|
|
||||||
expect(memberA.events.member.getId()).toEqual(memberEvent.getId());
|
|
||||||
expect(memberA.isOutOfBand()).toEqual(false);
|
expect(memberA.isOutOfBand()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should emit members when updating a member", function() {
|
it("should emit members when updating a member", function() {
|
||||||
|
const doesntExistYetUserId = "@doesntexistyet:hs";
|
||||||
const oobMemberEvent = utils.mkMembership({
|
const oobMemberEvent = utils.mkMembership({
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
|
||||||
});
|
});
|
||||||
let eventReceived = false;
|
let eventReceived = false;
|
||||||
state.once('RoomState.members', (_, __, member) => {
|
state.once('RoomState.members', (_, __, member) => {
|
||||||
expect(member.userId).toEqual(userA);
|
expect(member.userId).toEqual(doesntExistYetUserId);
|
||||||
eventReceived = true;
|
eventReceived = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,28 +325,6 @@ describe("RoomState", function() {
|
|||||||
state.setOutOfBandMembers([oobMemberEvent]);
|
state.setOutOfBandMembers([oobMemberEvent]);
|
||||||
expect(eventReceived).toEqual(true);
|
expect(eventReceived).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should not overwrite members updated since starting loading oob",
|
|
||||||
function() {
|
|
||||||
const oobMemberEvent = utils.mkMembership({
|
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingMemberEvent = utils.mkMembership({
|
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
state.markOutOfBandMembersStarted();
|
|
||||||
state.setStateEvents([existingMemberEvent]);
|
|
||||||
expect(state.getMember(userA).supersedesOutOfBand()).toEqual(true);
|
|
||||||
state.setOutOfBandMembers([oobMemberEvent]);
|
|
||||||
|
|
||||||
const memberA = state.getMember(userA);
|
|
||||||
expect(memberA.events.member.getId()).toEqual(existingMemberEvent.getId());
|
|
||||||
expect(memberA.isOutOfBand()).toEqual(false);
|
|
||||||
expect(memberA.supersedesOutOfBand()).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("clone", function() {
|
describe("clone", function() {
|
||||||
@@ -386,20 +348,6 @@ describe("RoomState", function() {
|
|||||||
expect(state.getJoinedMemberCount()).toEqual(copy.getJoinedMemberCount());
|
expect(state.getJoinedMemberCount()).toEqual(copy.getJoinedMemberCount());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should copy supersedes flag when OOB loading is progress",
|
|
||||||
function() {
|
|
||||||
// include OOB members in copy
|
|
||||||
state.markOutOfBandMembersStarted();
|
|
||||||
state.setStateEvents([utils.mkMembership({
|
|
||||||
user: userA, mship: "join", room: roomId, event: true,
|
|
||||||
})]);
|
|
||||||
const copy = state.clone();
|
|
||||||
const memberA = state.getMember(userA);
|
|
||||||
const memberACopy = copy.getMember(userA);
|
|
||||||
expect(memberA.supersedesOutOfBand()).toEqual(true);
|
|
||||||
expect(memberACopy.supersedesOutOfBand()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should mark old copy as not waiting for out of band anymore", function() {
|
it("should mark old copy as not waiting for out of band anymore", function() {
|
||||||
state.markOutOfBandMembersStarted();
|
state.markOutOfBandMembersStarted();
|
||||||
const copy = state.clone();
|
const copy = state.clone();
|
||||||
|
@@ -44,7 +44,6 @@ const ContentHelpers = require("./content-helpers");
|
|||||||
|
|
||||||
import ReEmitter from './ReEmitter';
|
import ReEmitter from './ReEmitter';
|
||||||
import RoomList from './crypto/RoomList';
|
import RoomList from './crypto/RoomList';
|
||||||
import {InvalidStoreError} from './errors';
|
|
||||||
|
|
||||||
import Crypto from './crypto';
|
import Crypto from './crypto';
|
||||||
import { isCryptoAvailable } from './crypto';
|
import { isCryptoAvailable } from './crypto';
|
||||||
@@ -53,16 +52,6 @@ import { isCryptoAvailable } from './crypto';
|
|||||||
// and need to migrate, but they spam the console with warnings.
|
// and need to migrate, but they spam the console with warnings.
|
||||||
Promise.config({warnings: false});
|
Promise.config({warnings: false});
|
||||||
|
|
||||||
const LAZY_LOADING_MESSAGES_FILTER = {
|
|
||||||
lazy_load_members: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LAZY_LOADING_SYNC_FILTER = {
|
|
||||||
room: {
|
|
||||||
state: LAZY_LOADING_MESSAGES_FILTER,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const SCROLLBACK_DELAY_MS = 3000;
|
const SCROLLBACK_DELAY_MS = 3000;
|
||||||
const CRYPTO_ENABLED = isCryptoAvailable();
|
const CRYPTO_ENABLED = isCryptoAvailable();
|
||||||
@@ -772,6 +761,17 @@ MatrixClient.prototype.getGroups = function() {
|
|||||||
return this.store.getGroups();
|
return this.store.getGroups();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the config for the media repository.
|
||||||
|
* @param {module:client.callback} callback Optional.
|
||||||
|
* @return {module:client.Promise} Resolves with an object containing the config.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.getMediaConfig = function(callback) {
|
||||||
|
return this._http.requestWithPrefix(
|
||||||
|
callback, "GET", "/config", undefined, undefined, httpApi.PREFIX_MEDIA_R0,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Room ops
|
// Room ops
|
||||||
// ========
|
// ========
|
||||||
|
|
||||||
@@ -2068,7 +2068,7 @@ MatrixClient.prototype.getEventTimeline = function(timelineSet, eventId) {
|
|||||||
|
|
||||||
let params = undefined;
|
let params = undefined;
|
||||||
if (this._clientOpts.lazyLoadMembers) {
|
if (this._clientOpts.lazyLoadMembers) {
|
||||||
params = {filter: JSON.stringify(LAZY_LOADING_MESSAGES_FILTER)};
|
params = {filter: JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER)};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we should implement a backoff (as per scrollback()) to deal more
|
// TODO: we should implement a backoff (as per scrollback()) to deal more
|
||||||
@@ -2149,7 +2149,7 @@ function(roomId, fromToken, limit, dir, timelineFilter = undefined) {
|
|||||||
if (this._clientOpts.lazyLoadMembers) {
|
if (this._clientOpts.lazyLoadMembers) {
|
||||||
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
|
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
|
||||||
// so the timelineFilter doesn't get written into it below
|
// so the timelineFilter doesn't get written into it below
|
||||||
filter = Object.assign({}, LAZY_LOADING_MESSAGES_FILTER);
|
filter = Object.assign({}, Filter.LAZY_LOADING_MESSAGES_FILTER);
|
||||||
}
|
}
|
||||||
if (timelineFilter) {
|
if (timelineFilter) {
|
||||||
// XXX: it's horrific that /messages' filter parameter doesn't match
|
// XXX: it's horrific that /messages' filter parameter doesn't match
|
||||||
@@ -3106,29 +3106,6 @@ MatrixClient.prototype.startClient = async function(opts) {
|
|||||||
// shallow-copy the opts dict before modifying and storing it
|
// shallow-copy the opts dict before modifying and storing it
|
||||||
opts = Object.assign({}, opts);
|
opts = Object.assign({}, opts);
|
||||||
|
|
||||||
if (opts.lazyLoadMembers && this.isGuest()) {
|
|
||||||
opts.lazyLoadMembers = false;
|
|
||||||
}
|
|
||||||
if (opts.lazyLoadMembers) {
|
|
||||||
const supported = await this.doesServerSupportLazyLoading();
|
|
||||||
if (supported) {
|
|
||||||
opts.filter = await this.createFilter(LAZY_LOADING_SYNC_FILTER);
|
|
||||||
} else {
|
|
||||||
console.log("LL: lazy loading requested but not supported " +
|
|
||||||
"by server, so disabling");
|
|
||||||
opts.lazyLoadMembers = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// need to vape the store when enabling LL and wasn't enabled before
|
|
||||||
const shouldClear = await this._wasLazyLoadingToggled(opts.lazyLoadMembers);
|
|
||||||
if (shouldClear) {
|
|
||||||
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
|
|
||||||
throw new InvalidStoreError(reason, !!opts.lazyLoadMembers);
|
|
||||||
}
|
|
||||||
if (opts.lazyLoadMembers && this._crypto) {
|
|
||||||
this._crypto.enableLazyLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.crypto = this._crypto;
|
opts.crypto = this._crypto;
|
||||||
opts.canResetEntireTimeline = (roomId) => {
|
opts.canResetEntireTimeline = (roomId) => {
|
||||||
if (!this._canResetTimelineCallback) {
|
if (!this._canResetTimelineCallback) {
|
||||||
@@ -3137,40 +3114,19 @@ MatrixClient.prototype.startClient = async function(opts) {
|
|||||||
return this._canResetTimelineCallback(roomId);
|
return this._canResetTimelineCallback(roomId);
|
||||||
};
|
};
|
||||||
this._clientOpts = opts;
|
this._clientOpts = opts;
|
||||||
await this._storeClientOptions(this._clientOpts);
|
|
||||||
this._syncApi = new SyncApi(this, opts);
|
this._syncApi = new SyncApi(this, opts);
|
||||||
this._syncApi.sync();
|
this._syncApi.sync();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the lazy loading option different than in previous session?
|
|
||||||
* @param {bool} lazyLoadMembers current options for lazy loading
|
|
||||||
* @return {bool} whether or not the option has changed compared to the previous session */
|
|
||||||
MatrixClient.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) {
|
|
||||||
lazyLoadMembers = !!lazyLoadMembers;
|
|
||||||
// assume it was turned off before
|
|
||||||
// if we don't know any better
|
|
||||||
let lazyLoadMembersBefore = false;
|
|
||||||
const isStoreNewlyCreated = await this.store.isNewlyCreated();
|
|
||||||
if (!isStoreNewlyCreated) {
|
|
||||||
const prevClientOptions = await this.store.getClientOptions();
|
|
||||||
if (prevClientOptions) {
|
|
||||||
lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers;
|
|
||||||
}
|
|
||||||
return lazyLoadMembersBefore !== lazyLoadMembers;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* store client options with boolean/string/numeric values
|
* store client options with boolean/string/numeric values
|
||||||
* to know in the next session what flags the sync data was
|
* to know in the next session what flags the sync data was
|
||||||
* created with (e.g. lazy loading)
|
* created with (e.g. lazy loading)
|
||||||
* @param {object} opts the complete set of client options
|
* @param {object} opts the complete set of client options
|
||||||
* @return {Promise} for store operation */
|
* @return {Promise} for store operation */
|
||||||
MatrixClient.prototype._storeClientOptions = function(opts) {
|
MatrixClient.prototype._storeClientOptions = function() {
|
||||||
const primTypes = ["boolean", "string", "number"];
|
const primTypes = ["boolean", "string", "number"];
|
||||||
const serializableOpts = Object.entries(opts)
|
const serializableOpts = Object.entries(this._clientOpts)
|
||||||
.filter(([key, value]) => {
|
.filter(([key, value]) => {
|
||||||
return primTypes.includes(typeof value);
|
return primTypes.includes(typeof value);
|
||||||
})
|
})
|
||||||
|
@@ -92,6 +92,19 @@ export default class IndexedDBCryptoStore {
|
|||||||
console.log(`connected to indexeddb ${this._dbName}`);
|
console.log(`connected to indexeddb ${this._dbName}`);
|
||||||
resolve(new IndexedDBCryptoStoreBackend.Backend(db));
|
resolve(new IndexedDBCryptoStoreBackend.Backend(db));
|
||||||
};
|
};
|
||||||
|
}).then((backend) => {
|
||||||
|
// Edge has IndexedDB but doesn't support compund keys which we use fairly extensively.
|
||||||
|
// Try a dummy query which will fail if the browser doesn't support compund keys, so
|
||||||
|
// we can fall back to a different backend.
|
||||||
|
return backend.doTxn(
|
||||||
|
'readonly',
|
||||||
|
[IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS],
|
||||||
|
(txn) => {
|
||||||
|
backend.getEndToEndInboundGroupSession('', '', txn, () => {});
|
||||||
|
}).then(() => {
|
||||||
|
return backend;
|
||||||
|
},
|
||||||
|
);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.warn(
|
console.warn(
|
||||||
`unable to connect to indexeddb ${this._dbName}` +
|
`unable to connect to indexeddb ${this._dbName}` +
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// because of http://babeljs.io/docs/usage/caveats/#classes
|
// because of http://babeljs.io/docs/usage/caveats/#classes
|
||||||
function InvalidStoreError(reason, value) {
|
function InvalidStoreError(reason, value) {
|
||||||
const message = `Store is invalid because ${reason}, ` +
|
const message = `Store is invalid because ${reason}, ` +
|
||||||
`please delete all data and retry`;
|
`please stop the client, delete all data and start the client again`;
|
||||||
const instance = Reflect.construct(Error, [message]);
|
const instance = Reflect.construct(Error, [message]);
|
||||||
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
|
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
|
||||||
instance.reason = reason;
|
instance.reason = reason;
|
||||||
|
@@ -51,6 +51,17 @@ function Filter(userId, filterId) {
|
|||||||
this.definition = {};
|
this.definition = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Filter.LAZY_LOADING_MESSAGES_FILTER = {
|
||||||
|
lazy_load_members: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.LAZY_LOADING_SYNC_FILTER = {
|
||||||
|
room: {
|
||||||
|
state: Filter.LAZY_LOADING_MESSAGES_FILTER,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ID of this filter on your homeserver (if known)
|
* Get the ID of this filter on your homeserver (if known)
|
||||||
* @return {?Number} The filter ID
|
* @return {?Number} The filter ID
|
||||||
|
@@ -59,7 +59,6 @@ function RoomMember(roomId, userId) {
|
|||||||
member: null,
|
member: null,
|
||||||
};
|
};
|
||||||
this._isOutOfBand = false;
|
this._isOutOfBand = false;
|
||||||
this._supersedesOutOfBand = false;
|
|
||||||
this._updateModifiedTime();
|
this._updateModifiedTime();
|
||||||
}
|
}
|
||||||
utils.inherits(RoomMember, EventEmitter);
|
utils.inherits(RoomMember, EventEmitter);
|
||||||
@@ -80,31 +79,6 @@ RoomMember.prototype.isOutOfBand = function() {
|
|||||||
return this._isOutOfBand;
|
return this._isOutOfBand;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the member supersede an incoming out-of-band
|
|
||||||
* member? If so the out-of-band member should be ignored.
|
|
||||||
* @return {bool}
|
|
||||||
*/
|
|
||||||
RoomMember.prototype.supersedesOutOfBand = function() {
|
|
||||||
return this._supersedesOutOfBand;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark the member as superseding the future incoming
|
|
||||||
* out-of-band members.
|
|
||||||
*/
|
|
||||||
RoomMember.prototype.markSupersedesOutOfBand = function() {
|
|
||||||
this._supersedesOutOfBand = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the member superseding the future incoming
|
|
||||||
* out-of-band members, as loading finished or failed.
|
|
||||||
*/
|
|
||||||
RoomMember.prototype.clearSupersedesOutOfBand = function() {
|
|
||||||
this._supersedesOutOfBand = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this room member's membership event. May fire "RoomMember.name" if
|
* Update this room member's membership event. May fire "RoomMember.name" if
|
||||||
* this event updates this member's name.
|
* this event updates this member's name.
|
||||||
|
@@ -220,8 +220,7 @@ RoomState.prototype.clone = function() {
|
|||||||
// if loading is in progress (through _oobMemberFlags)
|
// if loading is in progress (through _oobMemberFlags)
|
||||||
// since these are not new members, we're merely copying them
|
// since these are not new members, we're merely copying them
|
||||||
// set the status to not started
|
// set the status to not started
|
||||||
// after copying, we set back the status and
|
// after copying, we set back the status
|
||||||
// copy the superseding flag from the current state
|
|
||||||
const status = this._oobMemberFlags.status;
|
const status = this._oobMemberFlags.status;
|
||||||
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
||||||
|
|
||||||
@@ -249,14 +248,6 @@ RoomState.prototype.clone = function() {
|
|||||||
copyMember.markOutOfBand();
|
copyMember.markOutOfBand();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this._oobMemberFlags.status == OOB_STATUS_INPROGRESS) {
|
|
||||||
// copy markSupersedesOutOfBand flags
|
|
||||||
this.getMembers().forEach((member) => {
|
|
||||||
if (member.supersedesOutOfBand()) {
|
|
||||||
const copyMember = copy.getMember(member.userId);
|
|
||||||
copyMember.markSupersedesOutOfBand();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
@@ -341,11 +332,6 @@ RoomState.prototype.setStateEvents = function(stateEvents) {
|
|||||||
|
|
||||||
const member = self._getOrCreateMember(userId, event);
|
const member = self._getOrCreateMember(userId, event);
|
||||||
member.setMembershipEvent(event, self);
|
member.setMembershipEvent(event, self);
|
||||||
// if out of band members are loading,
|
|
||||||
// mark the member as more recent
|
|
||||||
if (self._oobMemberFlags.status == OOB_STATUS_INPROGRESS) {
|
|
||||||
member.markSupersedesOutOfBand();
|
|
||||||
}
|
|
||||||
|
|
||||||
self._updateMember(member);
|
self._updateMember(member);
|
||||||
self.emit("RoomState.members", event, self, member);
|
self.emit("RoomState.members", event, self, member);
|
||||||
@@ -434,12 +420,6 @@ RoomState.prototype.markOutOfBandMembersFailed = function() {
|
|||||||
if (this._oobMemberFlags.status !== OOB_STATUS_INPROGRESS) {
|
if (this._oobMemberFlags.status !== OOB_STATUS_INPROGRESS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// the request failed, there is nothing to supersede
|
|
||||||
// in case of a retry, these event would not supersede the
|
|
||||||
// retry anymore.
|
|
||||||
this.getMembers().forEach((m) => {
|
|
||||||
m.clearSupersedesOutOfBand();
|
|
||||||
});
|
|
||||||
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -483,27 +463,15 @@ RoomState.prototype._setOutOfBandMember = function(stateEvent) {
|
|||||||
}
|
}
|
||||||
const userId = stateEvent.getStateKey();
|
const userId = stateEvent.getStateKey();
|
||||||
const existingMember = this.getMember(userId);
|
const existingMember = this.getMember(userId);
|
||||||
if (existingMember) {
|
// never replace members received as part of the sync
|
||||||
const existingMemberEvent = existingMember.events.member;
|
if (existingMember && !existingMember.isOutOfBand()) {
|
||||||
// ignore out of band members with events we are
|
return;
|
||||||
// already aware of.
|
|
||||||
if (existingMemberEvent.getId() === stateEvent.getId()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// this member was updated since we started
|
|
||||||
// loading the out of band members.
|
|
||||||
// Ignore the out of band member and clear
|
|
||||||
// the "supersedes" flag as the out of members are now loaded
|
|
||||||
if (existingMember.supersedesOutOfBand()) {
|
|
||||||
existingMember.clearSupersedesOutOfBand();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = this._getOrCreateMember(userId, stateEvent);
|
const member = this._getOrCreateMember(userId, stateEvent);
|
||||||
member.setMembershipEvent(stateEvent, this);
|
member.setMembershipEvent(stateEvent, this);
|
||||||
// needed to know which members need to be stored seperately
|
// needed to know which members need to be stored seperately
|
||||||
// as the are not part of the sync accumulator
|
// as they are not part of the sync accumulator
|
||||||
// this is cleared by setMembershipEvent so when it's updated through /sync
|
// this is cleared by setMembershipEvent so when it's updated through /sync
|
||||||
member.markOutOfBand();
|
member.markOutOfBand();
|
||||||
|
|
||||||
|
72
src/sync.js
72
src/sync.js
@@ -33,6 +33,8 @@ const utils = require("./utils");
|
|||||||
const Filter = require("./filter");
|
const Filter = require("./filter");
|
||||||
const EventTimeline = require("./models/event-timeline");
|
const EventTimeline = require("./models/event-timeline");
|
||||||
|
|
||||||
|
import {InvalidStoreError} from './errors';
|
||||||
|
|
||||||
const DEBUG = true;
|
const DEBUG = true;
|
||||||
|
|
||||||
// /sync requests allow you to set a timeout= but the request may continue
|
// /sync requests allow you to set a timeout= but the request may continue
|
||||||
@@ -100,6 +102,7 @@ function SyncApi(client, opts) {
|
|||||||
this._connectionReturnedDefer = null;
|
this._connectionReturnedDefer = null;
|
||||||
this._notifEvents = []; // accumulator of sync events in the current sync response
|
this._notifEvents = []; // accumulator of sync events in the current sync response
|
||||||
this._failedSyncCount = 0; // Number of consecutive failed /sync requests
|
this._failedSyncCount = 0; // Number of consecutive failed /sync requests
|
||||||
|
this._storeIsInvalid = false; // flag set if the store needs to be cleared before we can start
|
||||||
|
|
||||||
if (client.getNotifTimelineSet()) {
|
if (client.getNotifTimelineSet()) {
|
||||||
client.reEmitter.reEmit(client.getNotifTimelineSet(),
|
client.reEmitter.reEmit(client.getNotifTimelineSet(),
|
||||||
@@ -422,6 +425,26 @@ SyncApi.prototype.recoverFromSyncStartupError = async function(savedSyncPromise,
|
|||||||
await keepaliveProm;
|
await keepaliveProm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the lazy loading option different than in previous session?
|
||||||
|
* @param {bool} lazyLoadMembers current options for lazy loading
|
||||||
|
* @return {bool} whether or not the option has changed compared to the previous session */
|
||||||
|
SyncApi.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) {
|
||||||
|
lazyLoadMembers = !!lazyLoadMembers;
|
||||||
|
// assume it was turned off before
|
||||||
|
// if we don't know any better
|
||||||
|
let lazyLoadMembersBefore = false;
|
||||||
|
const isStoreNewlyCreated = await this.client.store.isNewlyCreated();
|
||||||
|
if (!isStoreNewlyCreated) {
|
||||||
|
const prevClientOptions = await this.client.store.getClientOptions();
|
||||||
|
if (prevClientOptions) {
|
||||||
|
lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers;
|
||||||
|
}
|
||||||
|
return lazyLoadMembersBefore !== lazyLoadMembers;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point
|
* Main entry point
|
||||||
*/
|
*/
|
||||||
@@ -444,6 +467,8 @@ SyncApi.prototype.sync = function() {
|
|||||||
// 1) We need to get push rules so we can check if events should bing as we get
|
// 1) We need to get push rules so we can check if events should bing as we get
|
||||||
// them from /sync.
|
// them from /sync.
|
||||||
// 2) We need to get/create a filter which we can use for /sync.
|
// 2) We need to get/create a filter which we can use for /sync.
|
||||||
|
// 3) We need to check the lazy loading option matches what was used in the
|
||||||
|
// stored sync. If it doesn't, we can't use the stored sync.
|
||||||
|
|
||||||
async function getPushRules() {
|
async function getPushRules() {
|
||||||
try {
|
try {
|
||||||
@@ -458,9 +483,47 @@ SyncApi.prototype.sync = function() {
|
|||||||
getPushRules();
|
getPushRules();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getFilter(); // Now get the filter and start syncing
|
checkLazyLoadStatus(); // advance to the next stage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkLazyLoadStatus = async () => {
|
||||||
|
if (this.opts.lazyLoadMembers && client.isGuest()) {
|
||||||
|
this.opts.lazyLoadMembers = false;
|
||||||
|
}
|
||||||
|
if (this.opts.lazyLoadMembers) {
|
||||||
|
const supported = await client.doesServerSupportLazyLoading();
|
||||||
|
if (supported) {
|
||||||
|
this.opts.filter = await client.createFilter(
|
||||||
|
Filter.LAZY_LOADING_SYNC_FILTER,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("LL: lazy loading requested but not supported " +
|
||||||
|
"by server, so disabling");
|
||||||
|
this.opts.lazyLoadMembers = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// need to vape the store when enabling LL and wasn't enabled before
|
||||||
|
const shouldClear = await this._wasLazyLoadingToggled(this.opts.lazyLoadMembers);
|
||||||
|
if (shouldClear) {
|
||||||
|
this._storeIsInvalid = true;
|
||||||
|
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
|
||||||
|
const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers);
|
||||||
|
this._updateSyncState("ERROR", { error });
|
||||||
|
// bail out of the sync loop now: the app needs to respond to this error.
|
||||||
|
// we leave the state as 'ERROR' which isn't great since this normally means
|
||||||
|
// we're retrying. The client must be stopped before clearing the stores anyway
|
||||||
|
// so the app should stop the client, clear the store and start it again.
|
||||||
|
console.warn("InvalidStoreError: store is not usable: stopping sync.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.opts.lazyLoadMembers && this._crypto) {
|
||||||
|
this.opts.crypto.enableLazyLoading();
|
||||||
|
}
|
||||||
|
await this.client._storeClientOptions();
|
||||||
|
|
||||||
|
getFilter(); // Now get the filter and start syncing
|
||||||
|
};
|
||||||
|
|
||||||
async function getFilter() {
|
async function getFilter() {
|
||||||
let filter;
|
let filter;
|
||||||
if (self.opts.filter) {
|
if (self.opts.filter) {
|
||||||
@@ -588,7 +651,12 @@ SyncApi.prototype._syncFromCache = async function(savedSync) {
|
|||||||
console.error("Error processing cached sync", e.stack || e);
|
console.error("Error processing cached sync", e.stack || e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateSyncState("PREPARED", syncEventData);
|
// Don't emit a prepared if we've bailed because the store is invalid:
|
||||||
|
// in this case the client will not be usable until stopped & restarted
|
||||||
|
// so this would be useless and misleading.
|
||||||
|
if (!this._storeIsInvalid) {
|
||||||
|
this._updateSyncState("PREPARED", syncEventData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user